From c44c84a1cae0b74fafff08c0ace052c4fc1994ea Mon Sep 17 00:00:00 2001 From: halx99 Date: Sat, 4 Feb 2023 15:03:54 +0800 Subject: [PATCH] Update thirdparty libs --- thirdparty/README.md | 20 +- thirdparty/astc/astcenc_mathlib.h | 2 - thirdparty/astc/astcenc_vecmathlib_sse_4.h | 10 +- .../btConvexConcaveCollisionAlgorithm.cpp | 5 +- .../BulletCollision/Gimpact/btBoxCollision.h | 12 + .../NarrowPhaseCollision/btGjkConvexCast.cpp | 8 +- .../NarrowPhaseCollision/btManifoldPoint.h | 8 +- .../MLCPSolvers/btLemkeAlgorithm.cpp | 146 +- .../MLCPSolvers/btLemkeAlgorithm.h | 3 +- thirdparty/bullet/LinearMath/btConvexHull.cpp | 4 +- thirdparty/bullet/LinearMath/btScalar.h | 2 +- thirdparty/bullet/LinearMath/btSerializer.h | 4 +- thirdparty/c-ares/CMakeLists.txt | 17 +- thirdparty/c-ares/README.md | 2 +- thirdparty/c-ares/docs/acountry.1 | 2 +- thirdparty/c-ares/docs/adig.1 | 24 +- thirdparty/c-ares/docs/ahost.1 | 2 +- thirdparty/c-ares/docs/ares_destroy_options.3 | 6 +- thirdparty/c-ares/docs/ares_dup.3 | 6 +- thirdparty/c-ares/docs/ares_expand_name.3 | 10 +- thirdparty/c-ares/docs/ares_expand_string.3 | 10 +- thirdparty/c-ares/docs/ares_free_data.3 | 8 +- thirdparty/c-ares/docs/ares_free_hostent.3 | 6 +- thirdparty/c-ares/docs/ares_free_string.3 | 6 +- thirdparty/c-ares/docs/ares_get_servers.3 | 11 +- thirdparty/c-ares/docs/ares_getaddrinfo.3 | 18 +- thirdparty/c-ares/docs/ares_gethostbyaddr.3 | 17 +- thirdparty/c-ares/docs/ares_gethostbyname.3 | 16 +- .../c-ares/docs/ares_gethostbyname_file.3 | 8 +- thirdparty/c-ares/docs/ares_getnameinfo.3 | 17 +- thirdparty/c-ares/docs/ares_getsock.3 | 8 +- thirdparty/c-ares/docs/ares_inet_ntop.3 | 8 +- thirdparty/c-ares/docs/ares_inet_pton.3 | 6 +- thirdparty/c-ares/docs/ares_init_options.3 | 21 +- thirdparty/c-ares/docs/ares_library_init.3 | 3 +- thirdparty/c-ares/docs/ares_mkquery.3 | 10 +- thirdparty/c-ares/docs/ares_parse_a_reply.3 | 10 +- .../c-ares/docs/ares_parse_aaaa_reply.3 | 10 +- thirdparty/c-ares/docs/ares_parse_mx_reply.3 | 9 +- .../c-ares/docs/ares_parse_naptr_reply.3 | 8 +- thirdparty/c-ares/docs/ares_parse_ns_reply.3 | 8 +- thirdparty/c-ares/docs/ares_parse_ptr_reply.3 | 10 +- thirdparty/c-ares/docs/ares_parse_soa_reply.3 | 8 +- thirdparty/c-ares/docs/ares_parse_srv_reply.3 | 8 +- thirdparty/c-ares/docs/ares_parse_txt_reply.3 | 14 +- thirdparty/c-ares/docs/ares_parse_uri_reply.3 | 8 +- thirdparty/c-ares/docs/ares_process.3 | 2 +- thirdparty/c-ares/docs/ares_query.3 | 17 +- thirdparty/c-ares/docs/ares_save_options.3 | 7 +- thirdparty/c-ares/docs/ares_search.3 | 17 +- thirdparty/c-ares/docs/ares_send.3 | 15 +- thirdparty/c-ares/docs/ares_set_local_dev.3 | 6 +- thirdparty/c-ares/docs/ares_set_local_ip4.3 | 6 +- thirdparty/c-ares/docs/ares_set_local_ip6.3 | 14 +- thirdparty/c-ares/docs/ares_set_servers.3 | 11 +- thirdparty/c-ares/docs/ares_set_servers_csv.3 | 9 +- .../c-ares/docs/ares_set_socket_callback.3 | 14 +- .../docs/ares_set_socket_configure_callback.3 | 16 +- .../c-ares/docs/ares_set_socket_functions.3 | 25 +- thirdparty/c-ares/docs/ares_set_sortlist.3 | 6 +- thirdparty/c-ares/docs/ares_strerror.3 | 6 +- thirdparty/c-ares/include/ares.h | 9 + thirdparty/c-ares/include/ares_version.h | 6 +- thirdparty/c-ares/src/lib/CMakeLists.txt | 5 + thirdparty/c-ares/src/lib/Makefile.inc | 1 - thirdparty/c-ares/src/lib/ares_data.h | 5 + thirdparty/c-ares/src/lib/ares_destroy.c | 5 + thirdparty/c-ares/src/lib/ares_expand_name.c | 6 +- thirdparty/c-ares/src/lib/ares_getaddrinfo.c | 75 +- thirdparty/c-ares/src/lib/ares_init.c | 521 ++----- thirdparty/c-ares/src/lib/ares_library_init.c | 91 +- thirdparty/c-ares/src/lib/ares_library_init.h | 43 - thirdparty/c-ares/src/lib/ares_private.h | 3 + thirdparty/c-ares/src/lib/ares_process.c | 13 + thirdparty/c-ares/src/lib/ares_strsplit.c | 184 +-- thirdparty/c-ares/src/lib/ares_strsplit.h | 19 +- thirdparty/ntcvt/ntcvt.hpp | 13 +- thirdparty/openal/CMakeLists.txt | 132 +- thirdparty/openal/ChangeLog | 52 + thirdparty/openal/README.md | 35 +- thirdparty/openal/al/auxeffectslot.cpp | 1215 +++++++-------- thirdparty/openal/al/auxeffectslot.h | 370 +++-- thirdparty/openal/al/buffer.cpp | 342 ++-- thirdparty/openal/al/eax/api.cpp | 3 +- thirdparty/openal/al/eax/api.h | 30 +- thirdparty/openal/al/eax/call.cpp | 24 +- thirdparty/openal/al/eax/call.h | 2 + thirdparty/openal/al/eax/effect.h | 40 +- thirdparty/openal/al/eax/exception.cpp | 13 +- thirdparty/openal/al/eax/exception.h | 17 +- thirdparty/openal/al/eax/fx_slots.cpp | 18 +- thirdparty/openal/al/eax/fx_slots.h | 11 +- thirdparty/openal/al/eax/utils.cpp | 22 +- thirdparty/openal/al/eax/utils.h | 4 +- thirdparty/openal/al/effect.cpp | 61 +- thirdparty/openal/al/effects/autowah.cpp | 10 +- thirdparty/openal/al/effects/chorus.cpp | 22 +- thirdparty/openal/al/effects/compressor.cpp | 10 +- thirdparty/openal/al/effects/distortion.cpp | 10 +- thirdparty/openal/al/effects/echo.cpp | 10 +- thirdparty/openal/al/effects/effects.cpp | 34 +- thirdparty/openal/al/effects/effects.h | 4 +- thirdparty/openal/al/effects/equalizer.cpp | 10 +- thirdparty/openal/al/effects/fshifter.cpp | 16 +- thirdparty/openal/al/effects/modulator.cpp | 16 +- thirdparty/openal/al/effects/pshifter.cpp | 10 +- thirdparty/openal/al/effects/reverb.cpp | 12 +- thirdparty/openal/al/effects/vmorpher.cpp | 18 +- thirdparty/openal/al/error.cpp | 2 +- thirdparty/openal/al/event.cpp | 37 +- thirdparty/openal/al/extension.cpp | 9 +- thirdparty/openal/al/filter.cpp | 77 +- thirdparty/openal/al/listener.cpp | 46 +- thirdparty/openal/al/source.cpp | 1376 ++++++++++------- thirdparty/openal/al/source.h | 2 + thirdparty/openal/al/state.cpp | 60 +- thirdparty/openal/alc/alc.cpp | 887 ++++++----- thirdparty/openal/alc/alconfig.cpp | 14 +- thirdparty/openal/alc/alu.cpp | 482 ++++-- thirdparty/openal/alc/alu.h | 2 +- thirdparty/openal/alc/backends/alsa.cpp | 51 +- thirdparty/openal/alc/backends/base.cpp | 46 +- thirdparty/openal/alc/backends/base.h | 10 +- thirdparty/openal/alc/backends/coreaudio.cpp | 47 +- thirdparty/openal/alc/backends/dsound.cpp | 3 + thirdparty/openal/alc/backends/jack.cpp | 21 +- thirdparty/openal/alc/backends/oboe.cpp | 1 + thirdparty/openal/alc/backends/opensl.cpp | 21 +- thirdparty/openal/alc/backends/pipewire.cpp | 206 ++- thirdparty/openal/alc/backends/pulseaudio.cpp | 553 ++++--- thirdparty/openal/alc/backends/sndio.cpp | 17 +- thirdparty/openal/alc/backends/wasapi.cpp | 391 +++-- thirdparty/openal/alc/backends/wave.cpp | 6 +- thirdparty/openal/alc/backends/winmm.cpp | 1 + thirdparty/openal/alc/context.cpp | 1046 ++++++------- thirdparty/openal/alc/context.h | 429 ++--- thirdparty/openal/alc/device.cpp | 4 +- thirdparty/openal/alc/effects/autowah.cpp | 45 +- thirdparty/openal/alc/effects/chorus.cpp | 62 +- thirdparty/openal/alc/effects/compressor.cpp | 33 +- thirdparty/openal/alc/effects/convolution.cpp | 157 +- thirdparty/openal/alc/effects/dedicated.cpp | 17 +- thirdparty/openal/alc/effects/distortion.cpp | 6 +- thirdparty/openal/alc/effects/echo.cpp | 4 +- thirdparty/openal/alc/effects/equalizer.cpp | 56 +- thirdparty/openal/alc/effects/fshifter.cpp | 105 +- thirdparty/openal/alc/effects/modulator.cpp | 42 +- thirdparty/openal/alc/effects/pshifter.cpp | 219 +-- thirdparty/openal/alc/effects/reverb.cpp | 1328 ++++++++-------- thirdparty/openal/alc/effects/vmorpher.cpp | 43 +- thirdparty/openal/alc/inprogext.h | 9 + thirdparty/openal/alc/panning.cpp | 391 +++-- thirdparty/openal/cmake/FindFFmpeg.cmake | 18 +- thirdparty/openal/cmake/FindPulseAudio.cmake | 9 - thirdparty/openal/cmake/FindSDL2.cmake | 191 --- thirdparty/openal/cmake/bin2h.script.cmake | 5 +- thirdparty/openal/common/alcomplex.cpp | 99 +- thirdparty/openal/common/alcomplex.h | 17 +- thirdparty/openal/common/alfstream.cpp | 138 +- thirdparty/openal/common/alfstream.h | 53 +- thirdparty/openal/common/almalloc.h | 18 +- thirdparty/openal/common/alnumeric.h | 10 +- thirdparty/openal/common/alspan.h | 123 +- thirdparty/openal/common/comptr.h | 2 +- thirdparty/openal/common/intrusive_ptr.h | 16 +- thirdparty/openal/common/opthelpers.h | 57 +- thirdparty/openal/common/phase_shifter.h | 104 +- .../openal/common/polyphase_resampler.cpp | 16 +- .../openal/common/polyphase_resampler.h | 2 + thirdparty/openal/common/strutils.cpp | 4 +- thirdparty/openal/common/threads.cpp | 14 +- thirdparty/openal/common/vecmat.h | 51 +- thirdparty/openal/common/win_main_utf8.h | 4 +- thirdparty/openal/core/ambdec.cpp | 410 ++--- thirdparty/openal/core/ambdec.h | 3 +- thirdparty/openal/core/ambidefs.cpp | 312 +++- thirdparty/openal/core/ambidefs.h | 67 +- thirdparty/openal/core/async_event.h | 20 +- thirdparty/openal/core/bformatdec.cpp | 80 +- thirdparty/openal/core/context.cpp | 28 +- thirdparty/openal/core/context.h | 23 +- thirdparty/openal/core/converter.cpp | 91 +- thirdparty/openal/core/converter.h | 17 +- thirdparty/openal/core/cpu_caps.cpp | 4 +- thirdparty/openal/core/devformat.cpp | 2 + thirdparty/openal/core/devformat.h | 1 + thirdparty/openal/core/device.h | 82 +- thirdparty/openal/core/effectslot.cpp | 6 - thirdparty/openal/core/effectslot.h | 9 +- thirdparty/openal/core/except.cpp | 7 +- thirdparty/openal/core/filters/biquad.cpp | 8 +- thirdparty/openal/core/filters/splitter.cpp | 7 +- thirdparty/openal/core/filters/splitter.h | 7 +- thirdparty/openal/core/front_stablizer.h | 17 +- thirdparty/openal/core/helpers.cpp | 21 +- thirdparty/openal/core/hrtf.cpp | 183 +-- thirdparty/openal/core/hrtf.h | 23 +- thirdparty/openal/core/logging.cpp | 78 +- thirdparty/openal/core/logging.h | 43 +- thirdparty/openal/core/mastering.cpp | 16 +- thirdparty/openal/core/mixer.cpp | 39 +- thirdparty/openal/core/mixer.h | 50 +- thirdparty/openal/core/mixer/defs.h | 6 +- thirdparty/openal/core/mixer/hrtfbase.h | 4 +- thirdparty/openal/core/mixer/mixer_c.cpp | 75 +- thirdparty/openal/core/mixer/mixer_neon.cpp | 157 +- thirdparty/openal/core/mixer/mixer_sse.cpp | 177 ++- thirdparty/openal/core/rtkit.cpp | 4 + thirdparty/openal/core/uhjfilter.cpp | 396 ++++- thirdparty/openal/core/uhjfilter.h | 223 ++- thirdparty/openal/core/voice.cpp | 323 ++-- thirdparty/openal/core/voice.h | 14 +- thirdparty/openal/examples/common/alhelpers.c | 35 + thirdparty/openal/hrtf/Default HRTF.mhr | Bin 80353 -> 159841 bytes thirdparty/openal/include/AL/al.h | 219 +-- thirdparty/openal/include/AL/alc.h | 48 +- thirdparty/openal/include/AL/alext.h | 20 - thirdparty/openal/presets/3D7.1.ambdec | 28 +- thirdparty/openal/presets/hex-quad.ambdec | 53 + thirdparty/openal/presets/presets.txt | 11 + thirdparty/openal/router/router.cpp | 34 +- thirdparty/pugixml/LICENSE.md | 2 +- thirdparty/pugixml/README.md | 2 +- thirdparty/pugixml/pugiconfig.hpp | 2 +- thirdparty/pugixml/pugixml.cpp | 240 +-- thirdparty/pugixml/pugixml.hpp | 49 +- thirdparty/stb/stb_image.h | 155 +- thirdparty/webp/sharpyuv/sharpyuv.c | 58 +- thirdparty/webp/sharpyuv/sharpyuv.h | 38 +- thirdparty/webp/sharpyuv/sharpyuv_cpu.c | 14 + thirdparty/webp/sharpyuv/sharpyuv_cpu.h | 22 + thirdparty/webp/sharpyuv/sharpyuv_csp.c | 2 +- thirdparty/webp/sharpyuv/sharpyuv_csp.h | 7 +- thirdparty/webp/sharpyuv/sharpyuv_dsp.c | 17 +- thirdparty/webp/sharpyuv/sharpyuv_dsp.h | 7 +- thirdparty/webp/sharpyuv/sharpyuv_gamma.c | 1 - thirdparty/webp/sharpyuv/sharpyuv_gamma.h | 2 +- thirdparty/webp/sharpyuv/sharpyuv_neon.c | 9 +- thirdparty/webp/sharpyuv/sharpyuv_sse2.c | 7 +- thirdparty/webp/src/dec/Makefile.am | 29 + thirdparty/webp/src/dec/vp8i_dec.h | 4 +- thirdparty/webp/src/dec/vp8l_dec.c | 2 +- thirdparty/webp/src/dec/webp_dec.c | 2 +- thirdparty/webp/src/demux/Makefile.am | 18 + thirdparty/webp/src/demux/demux.c | 4 +- thirdparty/webp/src/demux/libwebpdemux.pc.in | 11 + thirdparty/webp/src/demux/libwebpdemux.rc | 41 + thirdparty/webp/src/dsp/Makefile.am | 187 +++ .../webp/src/dsp/alpha_processing_sse2.c | 12 +- .../webp/src/dsp/alpha_processing_sse41.c | 2 +- thirdparty/webp/src/dsp/cpu.c | 2 +- thirdparty/webp/src/dsp/cpu.h | 2 + thirdparty/webp/src/dsp/dec_sse2.c | 93 +- thirdparty/webp/src/dsp/dec_sse41.c | 2 +- thirdparty/webp/src/dsp/enc_neon.c | 9 +- thirdparty/webp/src/dsp/enc_sse2.c | 67 +- thirdparty/webp/src/dsp/lossless.c | 12 +- thirdparty/webp/src/dsp/lossless_enc.c | 18 +- thirdparty/webp/src/dsp/lossless_enc_sse2.c | 8 +- thirdparty/webp/src/dsp/lossless_sse2.c | 88 +- thirdparty/webp/src/dsp/lossless_sse41.c | 7 +- thirdparty/webp/src/dsp/quant.h | 13 +- thirdparty/webp/src/dsp/rescaler_sse2.c | 6 +- thirdparty/webp/src/dsp/upsampling_sse2.c | 2 +- thirdparty/webp/src/dsp/yuv_sse2.c | 13 +- thirdparty/webp/src/dsp/yuv_sse41.c | 6 +- thirdparty/webp/src/enc/Makefile.am | 43 + thirdparty/webp/src/enc/analysis_enc.c | 8 +- thirdparty/webp/src/enc/picture_csp_enc.c | 29 +- thirdparty/webp/src/enc/vp8i_enc.h | 4 +- thirdparty/webp/src/enc/vp8l_enc.c | 11 +- thirdparty/webp/src/mux/Makefile.am | 22 + thirdparty/webp/src/mux/libwebpmux.pc.in | 12 + thirdparty/webp/src/mux/libwebpmux.rc | 41 + thirdparty/webp/src/mux/muxi.h | 4 +- thirdparty/webp/src/utils/Makefile.am | 52 + .../webp/src/utils/bit_reader_inl_utils.h | 4 +- thirdparty/webp/src/utils/huffman_utils.c | 2 +- thirdparty/webp/src/utils/utils.h | 12 +- thirdparty/webp/src/webp/format_constants.h | 2 +- thirdparty/webp/src/webp/types.h | 6 +- 281 files changed, 10186 insertions(+), 8458 deletions(-) delete mode 100644 thirdparty/c-ares/src/lib/ares_library_init.h delete mode 100644 thirdparty/openal/cmake/FindSDL2.cmake create mode 100644 thirdparty/openal/presets/hex-quad.ambdec create mode 100644 thirdparty/webp/sharpyuv/sharpyuv_cpu.c create mode 100644 thirdparty/webp/sharpyuv/sharpyuv_cpu.h create mode 100644 thirdparty/webp/src/dec/Makefile.am create mode 100644 thirdparty/webp/src/demux/Makefile.am create mode 100644 thirdparty/webp/src/demux/libwebpdemux.pc.in create mode 100644 thirdparty/webp/src/demux/libwebpdemux.rc create mode 100644 thirdparty/webp/src/dsp/Makefile.am create mode 100644 thirdparty/webp/src/enc/Makefile.am create mode 100644 thirdparty/webp/src/mux/Makefile.am create mode 100644 thirdparty/webp/src/mux/libwebpmux.pc.in create mode 100644 thirdparty/webp/src/mux/libwebpmux.rc create mode 100644 thirdparty/webp/src/utils/Makefile.am diff --git a/thirdparty/README.md b/thirdparty/README.md index 4d4c5854fe..195dae17cf 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -6,7 +6,7 @@ ## astc - [![Upstream](https://img.shields.io/github/v/release/ARM-software/astc-encoder?label=Upstream)](https://github.com/ARM-software/astc-encoder) -- Version: 4.3.0 +- Version: 4.3.1 - License: Apache-2.0 ## Box2D @@ -21,13 +21,13 @@ ## Bullet - [![Upstream](https://img.shields.io/github/v/release/bulletphysics/bullet3?label=Upstream)](https://github.com/bulletphysics/bullet3) -- Version: 3.24 +- Version: 3.25 - License: zlib - Update method: Compare `axmol/thirdparty/bullet` with `bullet3/src` ## c-ares - [![Upstream](https://img.shields.io/github/v/release/c-ares/c-ares?label=Upstream)](https://github.com/c-ares/c-ares) -- Version: git 1.18.1-c25d4eb (2138) +- Version: git 1.19.0-bb8f5bb (2184) - License: MIT ## Chipmunk2D @@ -161,7 +161,7 @@ ## OpenAL Soft - [![Upstream](https://img.shields.io/github/v/tag/kcat/openal-soft?label=Upstream)](https://github.com/kcat/openal-soft) -- Version: 1.22.2 +- Version: 1.23.0 - License: LGPL-2.1 ## OpenSSL @@ -180,8 +180,10 @@ - License: BSD-3-Clause ## pugixml -- [![Upstream](https://img.shields.io/github/v/tag/zeux/pugixml?label=Upstream)](https://github.com/zeux/pugixml) -- Version: 1.12.1 +- [![Upstream](https://img.shields.io/github/v/tag/halx99/pugixml?label=Upstream)](https://github.com/halx99/pugixml) by halx99 for string_view support + - original repo: https://github.com/zeux/pugixml + - all tests passed: https://github.com/halx99/pugixml/actions/runs/4090401630 +- Version: 1.13 - License: MIT ## rapidjson @@ -203,7 +205,7 @@ ## stb (stb_image) - Upstream: https://github.com/nothings/stb -- Version: 2.27 +- Version: 2.28 - License: MIT ## unzip (minizip-1.2) @@ -218,7 +220,7 @@ ## webp - [![Upstream](https://img.shields.io/github/v/tag/webmproject/libwebp?label=Upstream)](https://github.com/webmproject/libwebp) -- Version: 1.2.4 +- Version: 1.3.0 - License: Google Inc ## xsbase @@ -248,7 +250,7 @@ ## ntcvt - [![Upstream](https://img.shields.io/github/v/tag/simdsoft/ntcvt?label=Upstream)](https://github.com/simdsoft/ntcvt) -- Version: git-8422188 (7) +- Version: 0.0.2 - License: Apache-2.0 ## Some third party libs supporting axmol too: diff --git a/thirdparty/astc/astcenc_mathlib.h b/thirdparty/astc/astcenc_mathlib.h index 67e989e7f5..0540c4fedd 100644 --- a/thirdparty/astc/astcenc_mathlib.h +++ b/thirdparty/astc/astcenc_mathlib.h @@ -48,8 +48,6 @@ #define ASTCENC_SSE 42 #elif defined(__SSE4_1__) #define ASTCENC_SSE 41 - #elif defined(__SSE3__) - #define ASTCENC_SSE 30 #elif defined(__SSE2__) #define ASTCENC_SSE 20 #else diff --git a/thirdparty/astc/astcenc_vecmathlib_sse_4.h b/thirdparty/astc/astcenc_vecmathlib_sse_4.h index 76fe577a89..26dcc4a891 100644 --- a/thirdparty/astc/astcenc_vecmathlib_sse_4.h +++ b/thirdparty/astc/astcenc_vecmathlib_sse_4.h @@ -1046,7 +1046,7 @@ ASTCENC_SIMD_INLINE void vtable_prepare(vint4 t0, vint4& t0p) */ ASTCENC_SIMD_INLINE void vtable_prepare(vint4 t0, vint4 t1, vint4& t0p, vint4& t1p) { -#if ASTCENC_SSE >= 30 +#if ASTCENC_SSE >= 41 t0p = t0; t1p = t0 ^ t1; #else @@ -1062,7 +1062,7 @@ ASTCENC_SIMD_INLINE void vtable_prepare( vint4 t0, vint4 t1, vint4 t2, vint4 t3, vint4& t0p, vint4& t1p, vint4& t2p, vint4& t3p) { -#if ASTCENC_SSE >= 30 +#if ASTCENC_SSE >= 41 t0p = t0; t1p = t0 ^ t1; t2p = t1 ^ t2; @@ -1080,7 +1080,7 @@ ASTCENC_SIMD_INLINE void vtable_prepare( */ ASTCENC_SIMD_INLINE vint4 vtable_8bt_32bi(vint4 t0, vint4 idx) { -#if ASTCENC_SSE >= 30 +#if ASTCENC_SSE >= 41 // Set index byte MSB to 1 for unused bytes so shuffle returns zero __m128i idxx = _mm_or_si128(idx.m, _mm_set1_epi32(static_cast(0xFFFFFF00))); @@ -1102,7 +1102,7 @@ ASTCENC_SIMD_INLINE vint4 vtable_8bt_32bi(vint4 t0, vint4 idx) */ ASTCENC_SIMD_INLINE vint4 vtable_8bt_32bi(vint4 t0, vint4 t1, vint4 idx) { -#if ASTCENC_SSE >= 30 +#if ASTCENC_SSE >= 41 // Set index byte MSB to 1 for unused bytes so shuffle returns zero __m128i idxx = _mm_or_si128(idx.m, _mm_set1_epi32(static_cast(0xFFFFFF00))); @@ -1130,7 +1130,7 @@ ASTCENC_SIMD_INLINE vint4 vtable_8bt_32bi(vint4 t0, vint4 t1, vint4 idx) */ ASTCENC_SIMD_INLINE vint4 vtable_8bt_32bi(vint4 t0, vint4 t1, vint4 t2, vint4 t3, vint4 idx) { -#if ASTCENC_SSE >= 30 +#if ASTCENC_SSE >= 41 // Set index byte MSB to 1 for unused bytes so shuffle returns zero __m128i idxx = _mm_or_si128(idx.m, _mm_set1_epi32(static_cast(0xFFFFFF00))); diff --git a/thirdparty/bullet/BulletCollision/CollisionDispatch/btConvexConcaveCollisionAlgorithm.cpp b/thirdparty/bullet/BulletCollision/CollisionDispatch/btConvexConcaveCollisionAlgorithm.cpp index 8d59ba95ae..95bce9e7c0 100644 --- a/thirdparty/bullet/BulletCollision/CollisionDispatch/btConvexConcaveCollisionAlgorithm.cpp +++ b/thirdparty/bullet/BulletCollision/CollisionDispatch/btConvexConcaveCollisionAlgorithm.cpp @@ -103,7 +103,8 @@ void btConvexTriangleCallback::processTriangle(btVector3* triangle, int partId, if (m_convexBodyWrap->getCollisionShape()->isConvex()) { -#ifndef BT_DISABLE_CONVEX_CONCAVE_EARLY_OUT +#ifdef BT_ENABLE_CONVEX_CONCAVE_EARLY_OUT + //todo: check this issue https://github.com/bulletphysics/bullet3/issues/4263 //an early out optimisation if the object is separated from the triangle //projected on the triangle normal) { @@ -139,7 +140,7 @@ void btConvexTriangleCallback::processTriangle(btVector3* triangle, int partId, if (dist > contact_threshold) return; } -#endif //BT_DISABLE_CONVEX_CONCAVE_EARLY_OUT +#endif //BT_ENABLE_CONVEX_CONCAVE_EARLY_OUT btTriangleShape tm(triangle[0], triangle[1], triangle[2]); tm.setMargin(m_collisionMarginTriangle); diff --git a/thirdparty/bullet/BulletCollision/Gimpact/btBoxCollision.h b/thirdparty/bullet/BulletCollision/Gimpact/btBoxCollision.h index 182835c3b4..941dcc63ae 100644 --- a/thirdparty/bullet/BulletCollision/Gimpact/btBoxCollision.h +++ b/thirdparty/bullet/BulletCollision/Gimpact/btBoxCollision.h @@ -229,10 +229,12 @@ public: m_min[0] = BT_MIN3(V1[0], V2[0], V3[0]); m_min[1] = BT_MIN3(V1[1], V2[1], V3[1]); m_min[2] = BT_MIN3(V1[2], V2[2], V3[2]); + m_min[3] = 0.f; m_max[0] = BT_MAX3(V1[0], V2[0], V3[0]); m_max[1] = BT_MAX3(V1[1], V2[1], V3[1]); m_max[2] = BT_MAX3(V1[2], V2[2], V3[2]); + m_max[3] = 0.f; } btAABB(const btVector3 &V1, @@ -243,10 +245,12 @@ public: m_min[0] = BT_MIN3(V1[0], V2[0], V3[0]); m_min[1] = BT_MIN3(V1[1], V2[1], V3[1]); m_min[2] = BT_MIN3(V1[2], V2[2], V3[2]); + m_min[3] = 0.f; m_max[0] = BT_MAX3(V1[0], V2[0], V3[0]); m_max[1] = BT_MAX3(V1[1], V2[1], V3[1]); m_max[2] = BT_MAX3(V1[2], V2[2], V3[2]); + m_max[3] = 0.f; m_min[0] -= margin; m_min[1] -= margin; @@ -275,9 +279,11 @@ public: m_min[0] = SIMD_INFINITY; m_min[1] = SIMD_INFINITY; m_min[2] = SIMD_INFINITY; + m_min[3] = 0.f; m_max[0] = -SIMD_INFINITY; m_max[1] = -SIMD_INFINITY; m_max[2] = -SIMD_INFINITY; + m_max[3] = 0.f; } SIMD_FORCE_INLINE void increment_margin(btScalar margin) @@ -295,10 +301,12 @@ public: m_min[0] = other.m_min[0] - margin; m_min[1] = other.m_min[1] - margin; m_min[2] = other.m_min[2] - margin; + m_min[3] = 0.f; m_max[0] = other.m_max[0] + margin; m_max[1] = other.m_max[1] + margin; m_max[2] = other.m_max[2] + margin; + m_max[3] = 0.f; } template @@ -310,10 +318,12 @@ public: m_min[0] = BT_MIN3(V1[0], V2[0], V3[0]); m_min[1] = BT_MIN3(V1[1], V2[1], V3[1]); m_min[2] = BT_MIN3(V1[2], V2[2], V3[2]); + m_min[3] = 0.f; m_max[0] = BT_MAX3(V1[0], V2[0], V3[0]); m_max[1] = BT_MAX3(V1[1], V2[1], V3[1]); m_max[2] = BT_MAX3(V1[2], V2[2], V3[2]); + m_max[3] = 0.f; } template @@ -325,10 +335,12 @@ public: m_min[0] = BT_MIN3(V1[0], V2[0], V3[0]); m_min[1] = BT_MIN3(V1[1], V2[1], V3[1]); m_min[2] = BT_MIN3(V1[2], V2[2], V3[2]); + m_min[3] = 0.f; m_max[0] = BT_MAX3(V1[0], V2[0], V3[0]); m_max[1] = BT_MAX3(V1[1], V2[1], V3[1]); m_max[2] = BT_MAX3(V1[2], V2[2], V3[2]); + m_max[3] = 0.f; m_min[0] -= margin; m_min[1] -= margin; diff --git a/thirdparty/bullet/BulletCollision/NarrowPhaseCollision/btGjkConvexCast.cpp b/thirdparty/bullet/BulletCollision/NarrowPhaseCollision/btGjkConvexCast.cpp index 7ed3b11d5e..41da699863 100644 --- a/thirdparty/bullet/BulletCollision/NarrowPhaseCollision/btGjkConvexCast.cpp +++ b/thirdparty/bullet/BulletCollision/NarrowPhaseCollision/btGjkConvexCast.cpp @@ -4,8 +4,8 @@ Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it freely, +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. @@ -19,8 +19,10 @@ subject to the following restrictions: #include "btPointCollector.h" #include "LinearMath/btTransformUtil.h" -#ifdef MAX_ITERATIONS +#ifdef BT_USE_DOUBLE_PRECISION #define MAX_ITERATIONS 64 +#else +#define MAX_ITERATIONS 32 #endif btGjkConvexCast::btGjkConvexCast(const btConvexShape* convexA, const btConvexShape* convexB, btSimplexSolverInterface* simplexSolver) diff --git a/thirdparty/bullet/BulletCollision/NarrowPhaseCollision/btManifoldPoint.h b/thirdparty/bullet/BulletCollision/NarrowPhaseCollision/btManifoldPoint.h index 8cc373db2e..31323282cd 100644 --- a/thirdparty/bullet/BulletCollision/NarrowPhaseCollision/btManifoldPoint.h +++ b/thirdparty/bullet/BulletCollision/NarrowPhaseCollision/btManifoldPoint.h @@ -71,8 +71,8 @@ public: const btVector3& normal, btScalar distance) : m_localPointA(pointA), m_localPointB(pointB), - m_positionWorldOnB(0,0,0), - m_positionWorldOnA(0,0,0), + m_positionWorldOnB(0,0,0), + m_positionWorldOnA(0,0,0), m_normalWorldOnB(normal), m_distance1(distance), m_combinedFriction(btScalar(0.)), @@ -95,8 +95,8 @@ public: m_contactERP(0.f), m_frictionCFM(0.f), m_lifeTime(0), - m_lateralFrictionDir1(0,0,0), - m_lateralFrictionDir2(0,0,0) + m_lateralFrictionDir1(0,0,0), + m_lateralFrictionDir2(0,0,0) { } diff --git a/thirdparty/bullet/BulletDynamics/MLCPSolvers/btLemkeAlgorithm.cpp b/thirdparty/bullet/BulletDynamics/MLCPSolvers/btLemkeAlgorithm.cpp index 954ffaed75..1007d04e34 100644 --- a/thirdparty/bullet/BulletDynamics/MLCPSolvers/btLemkeAlgorithm.cpp +++ b/thirdparty/bullet/BulletDynamics/MLCPSolvers/btLemkeAlgorithm.cpp @@ -169,9 +169,12 @@ btVectorXu btLemkeAlgorithm::solve(unsigned int maxloops /* = 0*/) /*the column becomes part of the basis*/ basis[pivotRowIndex] = pivotColIndexOld; - - pivotRowIndex = findLexicographicMinimum(A, pivotColIndex); - + bool isRayTermination = false; + pivotRowIndex = findLexicographicMinimum(A, pivotColIndex, z0Row, isRayTermination); + if (isRayTermination) + { + break; // ray termination + } if (z0Row == pivotRowIndex) { //if z0 leaves the basis the solution is found --> one last elimination step is necessary GaussJordanEliminationStep(A, pivotRowIndex, pivotColIndex, basis); @@ -217,79 +220,100 @@ btVectorXu btLemkeAlgorithm::solve(unsigned int maxloops /* = 0*/) return solutionVector; } -int btLemkeAlgorithm::findLexicographicMinimum(const btMatrixXu& A, const int& pivotColIndex) +int btLemkeAlgorithm::findLexicographicMinimum(const btMatrixXu& A, const int& pivotColIndex, const int& z0Row, bool& isRayTermination) { - int RowIndex = 0; + isRayTermination = false; + btAlignedObjectArray activeRows; + + bool firstRow = true; + btScalar currentMin = 0.0; + int dim = A.rows(); - btAlignedObjectArray Rows; + for (int row = 0; row < dim; row++) { - btVectorXu vec(dim + 1); - vec.setZero(); //, INIT, 0.) - Rows.push_back(vec); - btScalar a = A(row, pivotColIndex); - if (a > 0) - { - Rows[row][0] = A(row, 2 * dim + 1) / a; - Rows[row][1] = A(row, 2 * dim) / a; - for (int j = 2; j < dim + 1; j++) - Rows[row][j] = A(row, j - 1) / a; + const btScalar denom = A(row, pivotColIndex); -#ifdef BT_DEBUG_OSTREAM - // if (DEBUGLEVEL) { - // cout << "Rows(" << row << ") = " << Rows[row] << endl; - // } -#endif + if (denom > btMachEps()) + { + const btScalar q = A(row, dim + dim + 1) / denom; + if (firstRow) + { + currentMin = q; + activeRows.push_back(row); + firstRow = false; + } + else if (fabs(currentMin - q) < btMachEps()) + { + activeRows.push_back(row); + } + else if (currentMin > q) + { + currentMin = q; + activeRows.clear(); + activeRows.push_back(row); + } } } - for (int i = 0; i < Rows.size(); i++) + if (activeRows.size() == 0) { - if (Rows[i].nrm2() > 0.) + isRayTermination = true; + return 0; + } + else if (activeRows.size() == 1) + { + return activeRows[0]; + } + + // if there are multiple rows, check if they contain the row for z_0. + for (int i = 0; i < activeRows.size(); i++) + { + if (activeRows[i] == z0Row) { - int j = 0; - for (; j < Rows.size(); j++) - { - if (i != j) - { - if (Rows[j].nrm2() > 0.) - { - btVectorXu test(dim + 1); - for (int ii = 0; ii < dim + 1; ii++) - { - test[ii] = Rows[j][ii] - Rows[i][ii]; - } - - //=Rows[j] - Rows[i] - if (!LexicographicPositive(test)) - break; - } - } - } - - if (j == Rows.size()) - { - RowIndex += i; - break; - } + return z0Row; } } - return RowIndex; -} + // look through the columns of the inverse of the basic matrix from left to right until the tie is broken. + for (int col = 0; col < dim ; col++) + { + btAlignedObjectArray activeRowsCopy(activeRows); + activeRows.clear(); + firstRow = true; + for (int i = 0; i ratio) + { + currentMin = ratio; + activeRows.clear(); + activeRows.push_back(row); + } + } - while (i < v.size() - 1 && fabs(v[i]) < btMachEps()) - i++; - if (v[i] > 0) - return true; - - return false; + if (activeRows.size() == 1) + { + return activeRows[0]; + } + } + // must not reach here. + isRayTermination = true; + return 0; } void btLemkeAlgorithm::GaussJordanEliminationStep(btMatrixXu& A, int pivotRowIndex, int pivotColumnIndex, const btAlignedObjectArray& basis) diff --git a/thirdparty/bullet/BulletDynamics/MLCPSolvers/btLemkeAlgorithm.h b/thirdparty/bullet/BulletDynamics/MLCPSolvers/btLemkeAlgorithm.h index 3c6bf72a23..6c498dd0a8 100644 --- a/thirdparty/bullet/BulletDynamics/MLCPSolvers/btLemkeAlgorithm.h +++ b/thirdparty/bullet/BulletDynamics/MLCPSolvers/btLemkeAlgorithm.h @@ -71,8 +71,7 @@ public: } protected: - int findLexicographicMinimum(const btMatrixXu& A, const int& pivotColIndex); - bool LexicographicPositive(const btVectorXu& v); + int findLexicographicMinimum(const btMatrixXu& A, const int& pivotColIndex, const int& z0Row, bool& isRayTermination); void GaussJordanEliminationStep(btMatrixXu& A, int pivotRowIndex, int pivotColumnIndex, const btAlignedObjectArray& basis); bool greaterZero(const btVectorXu& vector); bool validBasis(const btAlignedObjectArray& basis); diff --git a/thirdparty/bullet/LinearMath/btConvexHull.cpp b/thirdparty/bullet/LinearMath/btConvexHull.cpp index e7de2a3694..94b68baf54 100644 --- a/thirdparty/bullet/LinearMath/btConvexHull.cpp +++ b/thirdparty/bullet/LinearMath/btConvexHull.cpp @@ -678,7 +678,9 @@ HullError HullLibrary::CreateConvexHull(const HullDesc &desc, // describes the if (vcount < 8) vcount = 8; btAlignedObjectArray vertexSource; - vertexSource.resize(static_cast(vcount)); + btVector3 zero; + zero.setZero(); + vertexSource.resize(static_cast(vcount), zero); btVector3 scale; diff --git a/thirdparty/bullet/LinearMath/btScalar.h b/thirdparty/bullet/LinearMath/btScalar.h index 9f5408c792..dea1b05ea4 100644 --- a/thirdparty/bullet/LinearMath/btScalar.h +++ b/thirdparty/bullet/LinearMath/btScalar.h @@ -25,7 +25,7 @@ subject to the following restrictions: #include /* SVN $Revision$ on $Date$ from http://bullet.googlecode.com*/ -#define BT_BULLET_VERSION 324 +#define BT_BULLET_VERSION 325 inline int btGetVersion() { diff --git a/thirdparty/bullet/LinearMath/btSerializer.h b/thirdparty/bullet/LinearMath/btSerializer.h index 59695ab326..86709566b5 100644 --- a/thirdparty/bullet/LinearMath/btSerializer.h +++ b/thirdparty/bullet/LinearMath/btSerializer.h @@ -481,7 +481,7 @@ public: buffer[9] = '3'; buffer[10] = '2'; - buffer[11] = '4'; + buffer[11] = '5'; } virtual void startSerialization() @@ -512,7 +512,7 @@ public: currentPtr += BT_HEADER_LENGTH; for (int i = 0; i < m_chunkPtrs.size(); i++) { - int curLength = sizeof(btChunk) + m_chunkPtrs[i]->m_length; + int curLength = (int)sizeof(btChunk) + m_chunkPtrs[i]->m_length; memcpy(currentPtr, m_chunkPtrs[i], curLength); btAlignedFree(m_chunkPtrs[i]); currentPtr += curLength; diff --git a/thirdparty/c-ares/CMakeLists.txt b/thirdparty/c-ares/CMakeLists.txt index 194485a32b..7a29fef7d1 100644 --- a/thirdparty/c-ares/CMakeLists.txt +++ b/thirdparty/c-ares/CMakeLists.txt @@ -8,7 +8,7 @@ INCLUDE (CheckCSourceCompiles) INCLUDE (CheckStructHasMember) INCLUDE (CheckLibraryExists) -PROJECT (c-ares LANGUAGES C VERSION "1.18.0" ) +PROJECT (c-ares LANGUAGES C VERSION "1.19.0" ) # Set this version before release SET (CARES_VERSION "${PROJECT_VERSION}") @@ -26,7 +26,7 @@ INCLUDE (GNUInstallDirs) # include this *AFTER* PROJECT(), otherwise paths are w # For example, a version of 4:0:2 would generate output such as: # libname.so -> libname.so.2 # libname.so.2 -> libname.so.2.2.0 -SET (CARES_LIB_VERSIONINFO "7:1:5") +SET (CARES_LIB_VERSIONINFO "8:0:6") OPTION (CARES_STATIC "Build as a static library" OFF) @@ -82,9 +82,14 @@ SET (TARGETS_INST_DEST # Function in Library # CHECK_LIBRARY_EXISTS can't be used as it will return true if the function -# is found in a different dependent library. +# is found in a different required/dependent library. MACRO (CARES_FUNCTION_IN_LIBRARY func lib var) + + SET (_ORIG_CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES}") + SET (CMAKE_REQUIRED_LIBRARIES ) CHECK_FUNCTION_EXISTS ("${func}" "_CARES_FUNC_IN_LIB_GLOBAL_${func}") + SET (CMAKE_REQUIRED_LIBRARIES "${_ORIG_CMAKE_REQUIRED_LIBRARIES}") + IF ("${_CARES_FUNC_IN_LIB_GLOBAL_${func}}") SET (${var} FALSE) ELSE () @@ -689,6 +694,12 @@ IF (CARES_INSTALL) if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) if ( "${CPACK_PACKAGE_ARCHITECTURE}" STREQUAL "" ) + set( CPACK_PACKAGE_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}" ) + endif() + if ( "${CPACK_PACKAGE_ARCHITECTURE}" STREQUAL "" ) + if ( "${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows" ) + message( FATAL_ERROR "Failed to determine CPACK_PACKAGE_ARCHITECTURE. Is CMAKE_SYSTEM_PROCESSOR set?" ) + endif() # Note: the architecture should default to the local architecture, but it # in fact comes up empty. We call `uname -m` to ask the kernel instead. EXECUTE_PROCESS( COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE CPACK_PACKAGE_ARCHITECTURE ) diff --git a/thirdparty/c-ares/README.md b/thirdparty/c-ares/README.md index 24a96c453d..b507a5c288 100644 --- a/thirdparty/c-ares/README.md +++ b/thirdparty/c-ares/README.md @@ -3,7 +3,7 @@ c-ares [![Build Status](https://api.cirrus-ci.com/github/c-ares/c-ares.svg)](https://cirrus-ci.com/github/c-ares/c-ares) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/aevgc5914tm72pvs/branch/master?svg=true)](https://ci.appveyor.com/project/c-ares/c-ares/branch/master) -[![Coverage Status](https://coveralls.io/repos/c-ares/c-ares/badge.svg?branch=master&service=github)](https://coveralls.io/github/c-ares/c-ares?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/c-ares/c-ares/badge.svg)](https://coveralls.io/github/c-ares/c-ares) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/291/badge)](https://bestpractices.coreinfrastructure.org/projects/291) [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/c-ares.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:c-ares) [![Releases](https://coderelease.io/badge/c-ares/c-ares)](https://coderelease.io/github/repository/c-ares/c-ares) diff --git a/thirdparty/c-ares/docs/acountry.1 b/thirdparty/c-ares/docs/acountry.1 index 8c3aaea848..ab5ab3d022 100644 --- a/thirdparty/c-ares/docs/acountry.1 +++ b/thirdparty/c-ares/docs/acountry.1 @@ -17,7 +17,7 @@ This utility comes with the \fBc\-ares\fR asynchronous resolver library. \fB\-d\fR Print some extra debugging output. .TP -\fB\-h\fR, \fB\-\-help\fR +\fB\-h\fR, \fB\-?\fR Display this help and exit. .TP \fB\-v\fR diff --git a/thirdparty/c-ares/docs/adig.1 b/thirdparty/c-ares/docs/adig.1 index 2056b959db..52ff49b739 100644 --- a/thirdparty/c-ares/docs/adig.1 +++ b/thirdparty/c-ares/docs/adig.1 @@ -18,34 +18,40 @@ This utility comes with the \fBc\-ares\fR asynchronous resolver library. \fB\-c\fR class Set the query class. Possible values for class are -NY, CHAOS, HS, IN (default). +ANY, CHAOS, HS and IN (default). .TP \fB\-d\fR Print some extra debugging output. .TP \fB\-f\fR flag -Add a flag. +Add a behavior control flag. Possible values for flag are -igntc, noaliases, norecurse, primary, stayopen, usevc. + igntc - ignore to query in TCP to get truncated UDP answer, + noaliases - don't honor the HOSTALIASES environment variable, + norecurse - don't query upstream servers recursively, + primary - use the first server, + stayopen - don't close the communication sockets, and + usevc - always use TCP. .TP -\fB\-h\fR, \fB\-\-help\fR +\fB\-h\fR, \fB\-?\fR Display this help and exit. .TP -\fB\-T\fR port -Use specified TCP port to connect to DNS server. -.TP \fB\-s\fR server Connect to specified DNS server, instead of the system's default one(s). +Servers are tried in round-robin, if the previous one failed. .TP \fB\-t\fR type Query records of specified type. Possible values for type are A (default), AAAA, AFSDB, ANY, AXFR, CNAME, GPOS, HINFO, ISDN, KEY, LOC, MAILA, MAILB, MB, MD, MF, MG, MINFO, MR, MX, NAPTR, NS, NSAP, NSAP_PTR, NULL, -PTR, PX, RP, RT, SIG, SOA, SRV, TXT, URI, WKS, X25, +PTR, PX, RP, RT, SIG, SOA, SRV, TXT, URI, WKS and X25. +.TP +\fB\-T\fR port +Connect to the specified TCP port of DNS server. .TP \fB\-U\fR port -Use specified UDP port to connect to DNS server. +Connect to the specified UDP port of DNS server. .TP \fB\-x\fR For an IPv4 \fB-t PTR a.b.c.d\fR lookup, query for diff --git a/thirdparty/c-ares/docs/ahost.1 b/thirdparty/c-ares/docs/ahost.1 index 430af821d4..07d9d1d18a 100644 --- a/thirdparty/c-ares/docs/ahost.1 +++ b/thirdparty/c-ares/docs/ahost.1 @@ -17,7 +17,7 @@ This utility comes with the \fBc\-ares\fR asynchronous resolver library. \fB\-d\fR Print some extra debugging output. .TP -\fB\-h\fR, \fB\-\-help\fR +\fB\-h\fR, \fB\-?\fR Display this help and exit. .TP \fB\-t\fR type diff --git a/thirdparty/c-ares/docs/ares_destroy_options.3 b/thirdparty/c-ares/docs/ares_destroy_options.3 index 31e346b795..d3779b60cf 100644 --- a/thirdparty/c-ares/docs/ares_destroy_options.3 +++ b/thirdparty/c-ares/docs/ares_destroy_options.3 @@ -18,9 +18,9 @@ ares_destroy_options \- Destroy options initialized with ares_save_options .SH SYNOPSIS .nf -.B #include -.PP -.B void ares_destroy_options(struct ares_options *\fIoptions\fP) +#include + +void ares_destroy_options(struct ares_options *\fIoptions\fP) .fi .SH DESCRIPTION The \fBares_destroy_options(3)\fP function destroys the options struct diff --git a/thirdparty/c-ares/docs/ares_dup.3 b/thirdparty/c-ares/docs/ares_dup.3 index e64c10423d..925c0cdd5f 100644 --- a/thirdparty/c-ares/docs/ares_dup.3 +++ b/thirdparty/c-ares/docs/ares_dup.3 @@ -18,9 +18,9 @@ ares_dup \- Duplicate a resolver channel .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_dup(ares_channel *\fIdest\fP, ares_channel \fIsource\fP) +#include + +int ares_dup(ares_channel *\fIdest\fP, ares_channel \fIsource\fP) .fi .SH DESCRIPTION The \fBares_dup(3)\fP function duplicates an existing communications channel diff --git a/thirdparty/c-ares/docs/ares_expand_name.3 b/thirdparty/c-ares/docs/ares_expand_name.3 index fc18df3ee3..e750ab8916 100644 --- a/thirdparty/c-ares/docs/ares_expand_name.3 +++ b/thirdparty/c-ares/docs/ares_expand_name.3 @@ -18,11 +18,11 @@ ares_expand_name \- Expand a DNS-encoded domain name .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_expand_name(const unsigned char *\fIencoded\fP, -.B const unsigned char *\fIabuf\fP, int \fIalen\fP, char **\fIs\fP, -.B long *\fIenclen\fP) +#include + +int ares_expand_name(const unsigned char *\fIencoded\fP, + const unsigned char *\fIabuf\fP, int \fIalen\fP, + char **\fIs\fP, long *\fIenclen\fP) .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_expand_string.3 b/thirdparty/c-ares/docs/ares_expand_string.3 index 33dd7bdada..89037424f1 100644 --- a/thirdparty/c-ares/docs/ares_expand_string.3 +++ b/thirdparty/c-ares/docs/ares_expand_string.3 @@ -18,11 +18,11 @@ ares_expand_string \- Expand a length encoded string .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_expand_string(const unsigned char *\fIencoded\fP, -.B const unsigned char *\fIabuf\fP, int \fIalen\fP, unsigned char **\fIs\fP, -.B long *\fIenclen\fP) +#include + +int ares_expand_string(const unsigned char *\fIencoded\fP, + const unsigned char *\fIabuf\fP, int \fIalen\fP, + unsigned char **\fIs\fP, long *\fIenclen\fP) .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_free_data.3 b/thirdparty/c-ares/docs/ares_free_data.3 index 18e83ce782..a6f3938be5 100644 --- a/thirdparty/c-ares/docs/ares_free_data.3 +++ b/thirdparty/c-ares/docs/ares_free_data.3 @@ -19,11 +19,9 @@ ares_free_data \- Free data allocated by several c-ares functions .SH SYNOPSIS .nf -.B #include -.PP -.B void ares_free_data(void *\fIdataptr\fP) -.PP -.B cc file.c -lcares +#include + +void ares_free_data(void *\fIdataptr\fP) .fi .SH DESCRIPTION .PP diff --git a/thirdparty/c-ares/docs/ares_free_hostent.3 b/thirdparty/c-ares/docs/ares_free_hostent.3 index d692801baf..7c92724e70 100644 --- a/thirdparty/c-ares/docs/ares_free_hostent.3 +++ b/thirdparty/c-ares/docs/ares_free_hostent.3 @@ -18,9 +18,9 @@ ares_free_hostent \- Free host structure allocated by ares functions .SH SYNOPSIS .nf -.B #include -.PP -.B void ares_free_hostent(struct hostent *\fIhost\fP) +#include + +void ares_free_hostent(struct hostent *\fIhost\fP) .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_free_string.3 b/thirdparty/c-ares/docs/ares_free_string.3 index 61d88aa27f..d8a8e4689b 100644 --- a/thirdparty/c-ares/docs/ares_free_string.3 +++ b/thirdparty/c-ares/docs/ares_free_string.3 @@ -18,9 +18,9 @@ ares_free_string \- Free strings allocated by ares functions .SH SYNOPSIS .nf -.B #include -.PP -.B void ares_free_string(void *\fIstr\fP) +#include + +void ares_free_string(void *\fIstr\fP) .fi .SH DESCRIPTION The \fIares_free_string(3)\fP function frees a string allocated by an ares diff --git a/thirdparty/c-ares/docs/ares_get_servers.3 b/thirdparty/c-ares/docs/ares_get_servers.3 index d6064289bb..eb5861b601 100644 --- a/thirdparty/c-ares/docs/ares_get_servers.3 +++ b/thirdparty/c-ares/docs/ares_get_servers.3 @@ -19,10 +19,13 @@ ares_get_servers, ares_get_servers_ports \- Retrieve name servers from an initialized ares_channel .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_get_servers(ares_channel \fIchannel\fP, struct ares_addr_node **\fIservers\fP) -.B int ares_get_servers_ports(ares_channel \fIchannel\fP, struct ares_addr_port_node **\fIservers\fP) +#include + +int ares_get_servers(ares_channel \fIchannel\fP, + struct ares_addr_node **\fIservers\fP) + +int ares_get_servers_ports(ares_channel \fIchannel\fP, + struct ares_addr_port_node **\fIservers\fP) .fi .SH DESCRIPTION The \fBares_get_servers(3)\fP function retrieves name servers configuration diff --git a/thirdparty/c-ares/docs/ares_getaddrinfo.3 b/thirdparty/c-ares/docs/ares_getaddrinfo.3 index 33c8a50dd3..eb085e5d26 100644 --- a/thirdparty/c-ares/docs/ares_getaddrinfo.3 +++ b/thirdparty/c-ares/docs/ares_getaddrinfo.3 @@ -18,14 +18,16 @@ ares_getaddrinfo \- Initiate a host query by name and service .SH SYNOPSIS .nf -.B #include -.PP -.B typedef void (*ares_addrinfo_callback)(void *\fIarg\fP, int \fIstatus\fP, -.B int \fItimeouts\fP, struct ares_addrinfo *\fIresult\fP) -.PP -.B void ares_getaddrinfo(ares_channel \fIchannel\fP, const char *\fIname\fP, -.B const char* \fIservice\fP, const struct ares_addrinfo_hints *\fIhints\fP, -.B ares_addrinfo_callback \fIcallback\fP, void *\fIarg\fP) +#include + +typedef void (*ares_addrinfo_callback)(void *\fIarg\fP, int \fIstatus\fP, + int \fItimeouts\fP, + struct ares_addrinfo *\fIresult\fP) + +void ares_getaddrinfo(ares_channel \fIchannel\fP, const char *\fIname\fP, + const char* \fIservice\fP, + const struct ares_addrinfo_hints *\fIhints\fP, + ares_addrinfo_callback \fIcallback\fP, void *\fIarg\fP) .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_gethostbyaddr.3 b/thirdparty/c-ares/docs/ares_gethostbyaddr.3 index 7727307556..c58ca50615 100644 --- a/thirdparty/c-ares/docs/ares_gethostbyaddr.3 +++ b/thirdparty/c-ares/docs/ares_gethostbyaddr.3 @@ -18,14 +18,15 @@ ares_gethostbyaddr \- Initiate a host query by address .SH SYNOPSIS .nf -.B #include -.PP -.B typedef void (*ares_host_callback)(void *\fIarg\fP, int \fIstatus\fP, -.B int \fItimeouts\fP, struct hostent *\fIhostent\fP) -.PP -.B void ares_gethostbyaddr(ares_channel \fIchannel\fP, const void *\fIaddr\fP, -.B int \fIaddrlen\fP, int \fIfamily\fP, ares_host_callback \fIcallback\fP, -.B void *\fIarg\fP) +#include + +typedef void (*ares_host_callback)(void *\fIarg\fP, int \fIstatus\fP, + int \fItimeouts\fP, + struct hostent *\fIhostent\fP) + +void ares_gethostbyaddr(ares_channel \fIchannel\fP, const void *\fIaddr\fP, + int \fIaddrlen\fP, int \fIfamily\fP, + ares_host_callback \fIcallback\fP, void *\fIarg\fP) .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_gethostbyname.3 b/thirdparty/c-ares/docs/ares_gethostbyname.3 index cfd6a0a12a..5b4d9702b3 100644 --- a/thirdparty/c-ares/docs/ares_gethostbyname.3 +++ b/thirdparty/c-ares/docs/ares_gethostbyname.3 @@ -18,13 +18,15 @@ ares_gethostbyname \- Initiate a host query by name .SH SYNOPSIS .nf -.B #include -.PP -.B typedef void (*ares_host_callback)(void *\fIarg\fP, int \fIstatus\fP, -.B int \fItimeouts\fP, struct hostent *\fIhostent\fP) -.PP -.B void ares_gethostbyname(ares_channel \fIchannel\fP, const char *\fIname\fP, -.B int \fIfamily\fP, ares_host_callback \fIcallback\fP, void *\fIarg\fP) +#include + +typedef void (*ares_host_callback)(void *\fIarg\fP, int \fIstatus\fP, + int \fItimeouts\fP, + struct hostent *\fIhostent\fP) + +void ares_gethostbyname(ares_channel \fIchannel\fP, const char *\fIname\fP, + int \fIfamily\fP, ares_host_callback \fIcallback\fP, + void *\fIarg\fP) .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_gethostbyname_file.3 b/thirdparty/c-ares/docs/ares_gethostbyname_file.3 index 8f59b41576..ab127fce77 100644 --- a/thirdparty/c-ares/docs/ares_gethostbyname_file.3 +++ b/thirdparty/c-ares/docs/ares_gethostbyname_file.3 @@ -18,10 +18,10 @@ ares_gethostbyname_file \- Lookup a name in the system's hosts file .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_gethostbyname_file(ares_channel \fIchannel\fP, const char *\fIname\fP, -.B int \fIfamily\fP, struct hostent **host) +#include + +int ares_gethostbyname_file(ares_channel \fIchannel\fP, const char *\fIname\fP, + int \fIfamily\fP, struct hostent **host) .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_getnameinfo.3 b/thirdparty/c-ares/docs/ares_getnameinfo.3 index 1017432108..d10d841015 100644 --- a/thirdparty/c-ares/docs/ares_getnameinfo.3 +++ b/thirdparty/c-ares/docs/ares_getnameinfo.3 @@ -18,14 +18,15 @@ ares_getnameinfo \- Address-to-nodename translation in protocol-independent manner .SH SYNOPSIS .nf -.B #include -.PP -.B typedef void (*ares_nameinfo_callback)(void *\fIarg\fP, int \fIstatus\fP, -.B int \fItimeouts\fP, char *\fInode\fP, char *\fIservice\fP) -.PP -.B void ares_getnameinfo(ares_channel \fIchannel\fP, const struct sockaddr *\fIsa\fP, -.B ares_socklen_t \fIsalen\fP, int \fIflags\fP, ares_nameinfo_callback \fIcallback\fP, -.B void *\fIarg\fP) +#include + +typedef void (*ares_nameinfo_callback)(void *\fIarg\fP, int \fIstatus\fP, + int \fItimeouts\fP, char *\fInode\fP, + char *\fIservice\fP) + +void ares_getnameinfo(ares_channel \fIchannel\fP, const struct sockaddr *\fIsa\fP, + ares_socklen_t \fIsalen\fP, int \fIflags\fP, + ares_nameinfo_callback \fIcallback\fP, void *\fIarg\fP) .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_getsock.3 b/thirdparty/c-ares/docs/ares_getsock.3 index 137329115e..7908daebc1 100644 --- a/thirdparty/c-ares/docs/ares_getsock.3 +++ b/thirdparty/c-ares/docs/ares_getsock.3 @@ -18,10 +18,10 @@ ares_getsock \- get socket descriptors to wait on .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_getsock(ares_channel \fIchannel\fP, ares_socket_t *\fIsocks\fP, -.B int \fInumsocks\fP); +#include + +int ares_getsock(ares_channel \fIchannel\fP, ares_socket_t *\fIsocks\fP, + int \fInumsocks\fP); .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_inet_ntop.3 b/thirdparty/c-ares/docs/ares_inet_ntop.3 index 93ee09cf77..d0d0b5915a 100644 --- a/thirdparty/c-ares/docs/ares_inet_ntop.3 +++ b/thirdparty/c-ares/docs/ares_inet_ntop.3 @@ -18,10 +18,10 @@ ares_inet_ntop \- convert a network format address to presentation format .SH SYNOPSIS .nf -.B #include -.PP -.B const char * -.B ares_inet_ntop(int af, const void *src, char *dst, ares_socklen_t size); +#include + +const char *ares_inet_ntop(int \fIaf\fP, const void *\fIsrc\fP, char *\fIdst\fP, + ares_socklen_t \fIsize\fP); .fi .SH DESCRIPTION This is a portable version with the identical functionality of the commonly diff --git a/thirdparty/c-ares/docs/ares_inet_pton.3 b/thirdparty/c-ares/docs/ares_inet_pton.3 index b7d86bb4e0..bf7140d3b5 100644 --- a/thirdparty/c-ares/docs/ares_inet_pton.3 +++ b/thirdparty/c-ares/docs/ares_inet_pton.3 @@ -18,9 +18,9 @@ ares_inet_pton \- convert an IPv4 or IPv6 address from text to binary form .SH SYNOPSIS .nf -.B #include -.PP -.B const char *ares_inet_pton(int af, const char *src, void *dst); +#include + +const char *ares_inet_pton(int \fIaf\fP, const char *\fIsrc\fP, void *\fIdst\fP); .fi .SH DESCRIPTION This is a portable version with the identical functionality of the commonly diff --git a/thirdparty/c-ares/docs/ares_init_options.3 b/thirdparty/c-ares/docs/ares_init_options.3 index b9d52a895c..02d3072108 100644 --- a/thirdparty/c-ares/docs/ares_init_options.3 +++ b/thirdparty/c-ares/docs/ares_init_options.3 @@ -41,6 +41,7 @@ struct ares_options { int nsort; int ednspsz; char *resolvconf_path; + char *hosts_path; }; int ares_init_options(ares_channel *\fIchannelptr\fP, @@ -101,16 +102,14 @@ resolv.conf or the RES_OPTIONS environment variable. .B ARES_OPT_UDP_PORT .B unsigned short \fIudp_port\fP; .br -The port to use for queries over UDP, in network byte order. -The default value is 53 (in network byte order), the standard name -service port. +The port to use for queries over UDP, in host byte order. +The default value is 53, the standard name service port. .TP 18 .B ARES_OPT_TCP_PORT .B unsigned short \fItcp_port\fP; .br -The port to use for queries over TCP, in network byte order. -The default value is 53 (in network byte order), the standard name -service port. +The port to use for queries over TCP, in host byte order. +The default value is 53, the standard name service port. .TP 18 .B ARES_OPT_SERVERS .B struct in_addr *\fIservers\fP; @@ -195,6 +194,16 @@ should be set to a path string, and will be honoured on *nix like systems. The default is .B /etc/resolv.conf .br +.TP 18 +.B ARES_OPT_HOSTS_FILE +.B char *\fIhosts_path\fP; +.br +The path to use for reading the hosts file. The +.I hosts_path +should be set to a path string, and will be honoured on *nix like systems. The +default is +.B /etc/hosts +.br .PP The \fIoptmask\fP parameter also includes options without a corresponding field in the diff --git a/thirdparty/c-ares/docs/ares_library_init.3 b/thirdparty/c-ares/docs/ares_library_init.3 index b38cf325d4..21fc3ecab5 100644 --- a/thirdparty/c-ares/docs/ares_library_init.3 +++ b/thirdparty/c-ares/docs/ares_library_init.3 @@ -79,7 +79,8 @@ DllMain function. Doing so will produce deadlocks and other problems. Initialize everything possible. This sets all known bits. .TP .B ARES_LIB_INIT_WIN32 -Initialize Win32/64 specific libraries. +Initialize Win32/64 specific libraries. As of c-ares 1.19.0, this is ignored +as there are no currently dynamically loaded libraries. .TP .B ARES_LIB_INIT_NONE Initialize nothing extra. This sets no bit. diff --git a/thirdparty/c-ares/docs/ares_mkquery.3 b/thirdparty/c-ares/docs/ares_mkquery.3 index c8afad83cb..e394fe2847 100644 --- a/thirdparty/c-ares/docs/ares_mkquery.3 +++ b/thirdparty/c-ares/docs/ares_mkquery.3 @@ -18,11 +18,11 @@ ares_mkquery \- Compose a single-question DNS query buffer .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_mkquery(const char *\fIname\fP, int \fIdnsclass\fP, int \fItype\fP, -.B unsigned short \fIid\fP, int \fIrd\fP, unsigned char **\fIbuf\fP, -.B int *\fIbuflen\fP) +#include + +int ares_mkquery(const char *\fIname\fP, int \fIdnsclass\fP, int \fItype\fP, + unsigned short \fIid\fP, int \fIrd\fP, unsigned char **\fIbuf\fP, + int *\fIbuflen\fP) .fi .SH DESCRIPTION Deprecated function. See \fIares_create_query(3)\fP instead! diff --git a/thirdparty/c-ares/docs/ares_parse_a_reply.3 b/thirdparty/c-ares/docs/ares_parse_a_reply.3 index 8e4908a7fe..6038f68e59 100644 --- a/thirdparty/c-ares/docs/ares_parse_a_reply.3 +++ b/thirdparty/c-ares/docs/ares_parse_a_reply.3 @@ -18,11 +18,11 @@ ares_parse_a_reply \- Parse a reply to a DNS query of type A .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_parse_a_reply(const unsigned char *\fIabuf\fP, int \fIalen\fP, -.B struct hostent **\fIhost\fP, -.B struct ares_addrttl *\fIaddrttls\fP, int *\fInaddrttls\fP); +#include + +int ares_parse_a_reply(const unsigned char *\fIabuf\fP, int \fIalen\fP, + struct hostent **\fIhost\fP, + struct ares_addrttl *\fIaddrttls\fP, int *\fInaddrttls\fP); .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_parse_aaaa_reply.3 b/thirdparty/c-ares/docs/ares_parse_aaaa_reply.3 index 674acc5c46..bddd3c2f9f 100644 --- a/thirdparty/c-ares/docs/ares_parse_aaaa_reply.3 +++ b/thirdparty/c-ares/docs/ares_parse_aaaa_reply.3 @@ -18,11 +18,11 @@ ares_parse_aaaa_reply \- Parse a reply to a DNS query of type AAAA .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_parse_aaaa_reply(const unsigned char *\fIabuf\fP, int \fIalen\fP, -.B struct hostent **\fIhost\fP, -.B struct ares_addr6ttl *\fIaddrttls\fP, int *\fInaddrttls\fP); +#include + +int ares_parse_aaaa_reply(const unsigned char *\fIabuf\fP, int \fIalen\fP, + struct hostent **\fIhost\fP, + struct ares_addr6ttl *\fIaddrttls\fP, int *\fInaddrttls\fP); .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_parse_mx_reply.3 b/thirdparty/c-ares/docs/ares_parse_mx_reply.3 index 87df4592ea..6c5c902d10 100644 --- a/thirdparty/c-ares/docs/ares_parse_mx_reply.3 +++ b/thirdparty/c-ares/docs/ares_parse_mx_reply.3 @@ -17,11 +17,10 @@ .SH NAME ares_parse_mx_reply \- Parse a reply to a DNS query of type MX .SH SYNOPSIS -.nf -.B #include -.PP -.B int ares_parse_mx_reply(const unsigned char* \fIabuf\fP, int \fIalen\fP, -.B struct ares_mx_reply** \fImx_out\fP); +#include + +int ares_parse_mx_reply(const unsigned char* \fIabuf\fP, int \fIalen\fP, + struct ares_mx_reply** \fImx_out\fP); .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_parse_naptr_reply.3 b/thirdparty/c-ares/docs/ares_parse_naptr_reply.3 index 2a5f1e5bd0..b98f8fd240 100644 --- a/thirdparty/c-ares/docs/ares_parse_naptr_reply.3 +++ b/thirdparty/c-ares/docs/ares_parse_naptr_reply.3 @@ -18,10 +18,10 @@ ares_parse_naptr_reply \- Parse a reply to a DNS query of type NAPTR .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_parse_naptr_reply(const unsigned char* \fIabuf\fP, int \fIalen\fP, -.B struct ares_naptr_reply** \fInaptr_out\fP); +#include + +int ares_parse_naptr_reply(const unsigned char* \fIabuf\fP, int \fIalen\fP, + struct ares_naptr_reply** \fInaptr_out\fP); .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_parse_ns_reply.3 b/thirdparty/c-ares/docs/ares_parse_ns_reply.3 index b6340ac846..b767e04f68 100644 --- a/thirdparty/c-ares/docs/ares_parse_ns_reply.3 +++ b/thirdparty/c-ares/docs/ares_parse_ns_reply.3 @@ -18,10 +18,10 @@ ares_parse_ns_reply \- Parse a reply to a DNS query of type NS into a hostent .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_parse_ns_reply(const unsigned char *\fIabuf\fP, int \fIalen\fP, -.B struct hostent **\fIhost\fP); +#include + +int ares_parse_ns_reply(const unsigned char *\fIabuf\fP, int \fIalen\fP, + struct hostent **\fIhost\fP); .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_parse_ptr_reply.3 b/thirdparty/c-ares/docs/ares_parse_ptr_reply.3 index 1016a6823f..e3eb4d09b3 100644 --- a/thirdparty/c-ares/docs/ares_parse_ptr_reply.3 +++ b/thirdparty/c-ares/docs/ares_parse_ptr_reply.3 @@ -18,11 +18,11 @@ ares_parse_ptr_reply \- Parse a reply to a DNS query of type PTR into a hostent .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_parse_ptr_reply(const unsigned char *\fIabuf\fP, int \fIalen\fP, -.B const void *\fIaddr\fP, int \fIaddrlen\fP, int \fIfamily\fP, -.B struct hostent **\fIhost\fP); +#include + +int ares_parse_ptr_reply(const unsigned char *\fIabuf\fP, int \fIalen\fP, + const void *\fIaddr\fP, int \fIaddrlen\fP, + int \fIfamily\fP, struct hostent **\fIhost\fP); .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_parse_soa_reply.3 b/thirdparty/c-ares/docs/ares_parse_soa_reply.3 index 1c4456f0bb..c43f9be084 100644 --- a/thirdparty/c-ares/docs/ares_parse_soa_reply.3 +++ b/thirdparty/c-ares/docs/ares_parse_soa_reply.3 @@ -18,10 +18,10 @@ ares_parse_soa_reply \- Parse a reply to a DNS query of type SOA .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_parse_soa_reply(const unsigned char* \fIabuf\fP, int \fIalen\fP, -.B struct ares_soa_reply** \fIsoa_out\fP); +#include + +int ares_parse_soa_reply(const unsigned char* \fIabuf\fP, int \fIalen\fP, + struct ares_soa_reply** \fIsoa_out\fP); .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_parse_srv_reply.3 b/thirdparty/c-ares/docs/ares_parse_srv_reply.3 index 9b561ffa36..d3c26dbc28 100644 --- a/thirdparty/c-ares/docs/ares_parse_srv_reply.3 +++ b/thirdparty/c-ares/docs/ares_parse_srv_reply.3 @@ -18,10 +18,10 @@ ares_parse_srv_reply \- Parse a reply to a DNS query of type SRV .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_parse_srv_reply(const unsigned char* \fIabuf\fP, int \fIalen\fP, -.B struct ares_srv_reply** \fIsrv_out\fP); +#include + +int ares_parse_srv_reply(const unsigned char* \fIabuf\fP, int \fIalen\fP, + struct ares_srv_reply** \fIsrv_out\fP); .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_parse_txt_reply.3 b/thirdparty/c-ares/docs/ares_parse_txt_reply.3 index e15d0eace1..6eeb04c741 100644 --- a/thirdparty/c-ares/docs/ares_parse_txt_reply.3 +++ b/thirdparty/c-ares/docs/ares_parse_txt_reply.3 @@ -18,13 +18,13 @@ ares_parse_txt_reply \- Parse a reply to a DNS query of type TXT .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_parse_txt_reply(const unsigned char* \fIabuf\fP, int \fIalen\fP, -.B struct ares_txt_reply **\fItxt_out\fP); -.PP -.B int ares_parse_txt_reply_ext(const unsigned char* \fIabuf\fP, int \fIalen\fP, -.B struct ares_txt_ext **\fItxt_out\fP); +#include + +int ares_parse_txt_reply(const unsigned char* \fIabuf\fP, int \fIalen\fP, + struct ares_txt_reply **\fItxt_out\fP); + +int ares_parse_txt_reply_ext(const unsigned char* \fIabuf\fP, int \fIalen\fP, + struct ares_txt_ext **\fItxt_out\fP); .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_parse_uri_reply.3 b/thirdparty/c-ares/docs/ares_parse_uri_reply.3 index 09da1a94e5..60bc22854c 100644 --- a/thirdparty/c-ares/docs/ares_parse_uri_reply.3 +++ b/thirdparty/c-ares/docs/ares_parse_uri_reply.3 @@ -18,10 +18,10 @@ ares_parse_uri_reply \- Parse a reply to a DNS query of type URI .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_parse_uri_reply(const unsigned char* \fIabuf\fP, int \fIalen\fP, -.B struct ares_uri_reply** \fIuri_out\fP); +#include + +int ares_parse_uri_reply(const unsigned char* \fIabuf\fP, int \fIalen\fP, + struct ares_uri_reply** \fIuri_out\fP); .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_process.3 b/thirdparty/c-ares/docs/ares_process.3 index caabbf13b9..28666f2e15 100644 --- a/thirdparty/c-ares/docs/ares_process.3 +++ b/thirdparty/c-ares/docs/ares_process.3 @@ -44,7 +44,7 @@ if they complete successfully or fail. \fBares_process_fd(3)\fP works the same way but acts and operates only on the specific file descriptors (sockets) you pass in to the function. Use ARES_SOCKET_BAD for "no action". This function is provided to allow users of -c-ares to void \fIselect(3)\fP in their applications and within c-ares. +c-ares to avoid \fIselect(3)\fP in their applications and within c-ares. To only process possible timeout conditions without a socket event occurring, one may pass NULL as the values for both \fIread_fds\fP and \fIwrite_fds\fP for diff --git a/thirdparty/c-ares/docs/ares_query.3 b/thirdparty/c-ares/docs/ares_query.3 index 733fbc972b..1055baaed8 100644 --- a/thirdparty/c-ares/docs/ares_query.3 +++ b/thirdparty/c-ares/docs/ares_query.3 @@ -18,14 +18,15 @@ ares_query \- Initiate a single-question DNS query .SH SYNOPSIS .nf -.B #include -.PP -.B typedef void (*ares_callback)(void *\fIarg\fP, int \fIstatus\fP, -.B int \fItimeouts\fP, unsigned char *\fIabuf\fP, int \fIalen\fP) -.PP -.B void ares_query(ares_channel \fIchannel\fP, const char *\fIname\fP, -.B int \fIdnsclass\fP, int \fItype\fP, ares_callback \fIcallback\fP, -.B void *\fIarg\fP) +#include + +typedef void (*ares_callback)(void *\fIarg\fP, int \fIstatus\fP, + int \fItimeouts\fP, unsigned char *\fIabuf\fP, + int \fIalen\fP) + +void ares_query(ares_channel \fIchannel\fP, const char *\fIname\fP, + int \fIdnsclass\fP, int \fItype\fP, + ares_callback \fIcallback\fP, void *\fIarg\fP) .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_save_options.3 b/thirdparty/c-ares/docs/ares_save_options.3 index bddae04934..b5fcf17776 100644 --- a/thirdparty/c-ares/docs/ares_save_options.3 +++ b/thirdparty/c-ares/docs/ares_save_options.3 @@ -18,9 +18,10 @@ ares_save_options \- Save configuration values obtained from initialized ares_channel .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_save_options(ares_channel \fIchannel\fP, struct ares_options *\fIoptions\fP, int *\fIoptmask\fP) +#include + +int ares_save_options(ares_channel \fIchannel\fP, + struct ares_options *\fIoptions\fP, int *\fIoptmask\fP) .fi .SH DESCRIPTION The \fBares_save_options(3)\fP function saves the channel data identified by diff --git a/thirdparty/c-ares/docs/ares_search.3 b/thirdparty/c-ares/docs/ares_search.3 index 2c85d20f64..6b301877d0 100644 --- a/thirdparty/c-ares/docs/ares_search.3 +++ b/thirdparty/c-ares/docs/ares_search.3 @@ -18,14 +18,15 @@ ares_search \- Initiate a DNS query with domain search .SH SYNOPSIS .nf -.B #include -.PP -.B typedef void (*ares_callback)(void *\fIarg\fP, int \fIstatus\fP, -.B int \fItimeouts\fP, unsigned char *\fIabuf\fP, int \fIalen\fP) -.PP -.B void ares_search(ares_channel \fIchannel\fP, const char *\fIname\fP, -.B int \fIdnsclass\fP, int \fItype\fP, ares_callback \fIcallback\fP, -.B void *\fIarg\fP) +#include + +typedef void (*ares_callback)(void *\fIarg\fP, int \fIstatus\fP, + int \fItimeouts\fP, unsigned char *\fIabuf\fP, + int \fIalen\fP) + +void ares_search(ares_channel \fIchannel\fP, const char *\fIname\fP, + int \fIdnsclass\fP, int \fItype\fP, + ares_callback \fIcallback\fP, void *\fIarg\fP) .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_send.3 b/thirdparty/c-ares/docs/ares_send.3 index b89abfeb85..bcd55b3f4e 100644 --- a/thirdparty/c-ares/docs/ares_send.3 +++ b/thirdparty/c-ares/docs/ares_send.3 @@ -18,13 +18,14 @@ ares_send \- Initiate a DNS query .SH SYNOPSIS .nf -.B #include -.PP -.B typedef void (*ares_callback)(void *\fIarg\fP, int \fIstatus\fP, -.B int \fItimeouts\fP, unsigned char *\fIabuf\fP, int \fIalen\fP) -.PP -.B void ares_send(ares_channel \fIchannel\fP, const unsigned char *\fIqbuf\fP, -.B int \fIqlen\fP, ares_callback \fIcallback\fP, void *\fIarg\fP) +#include + +typedef void (*ares_callback)(void *\fIarg\fP, int \fIstatus\fP, + int \fItimeouts\fP, unsigned char *\fIabuf\fP, + int \fIalen\fP) + +void ares_send(ares_channel \fIchannel\fP, const unsigned char *\fIqbuf\fP, + int \fIqlen\fP, ares_callback \fIcallback\fP, void *\fIarg\fP) .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/docs/ares_set_local_dev.3 b/thirdparty/c-ares/docs/ares_set_local_dev.3 index 7d82133ac1..059eae5475 100644 --- a/thirdparty/c-ares/docs/ares_set_local_dev.3 +++ b/thirdparty/c-ares/docs/ares_set_local_dev.3 @@ -18,9 +18,9 @@ ares_set_local_dev \- Bind to a specific network device when creating sockets. .SH SYNOPSIS .nf -.B #include -.PP -.B void ares_set_local_dev(ares_channel \fIchannel\fP, const char* \fIlocal_dev_name\fP) +#include + +void ares_set_local_dev(ares_channel \fIchannel\fP, const char* \fIlocal_dev_name\fP) .fi .SH DESCRIPTION The \fBares_set_local_dev\fP function causes all future sockets diff --git a/thirdparty/c-ares/docs/ares_set_local_ip4.3 b/thirdparty/c-ares/docs/ares_set_local_ip4.3 index e68e80e76a..8425f5b67b 100644 --- a/thirdparty/c-ares/docs/ares_set_local_ip4.3 +++ b/thirdparty/c-ares/docs/ares_set_local_ip4.3 @@ -18,9 +18,9 @@ ares_set_local_ip4 \- Set local IPv4 address outgoing requests. .SH SYNOPSIS .nf -.B #include -.PP -.B void ares_set_local_ip4(ares_channel \fIchannel\fP, unsigned int \fIlocal_ip\fP) +#include + +void ares_set_local_ip4(ares_channel \fIchannel\fP, unsigned int \fIlocal_ip\fP) .fi .SH DESCRIPTION The \fBares_set_local_ip4\fP function sets the IP address for outbound diff --git a/thirdparty/c-ares/docs/ares_set_local_ip6.3 b/thirdparty/c-ares/docs/ares_set_local_ip6.3 index e659f5c930..6719ad3598 100644 --- a/thirdparty/c-ares/docs/ares_set_local_ip6.3 +++ b/thirdparty/c-ares/docs/ares_set_local_ip6.3 @@ -18,15 +18,15 @@ ares_set_local_ip6 \- Set local IPv6 address outgoing requests. .SH SYNOPSIS .nf -.B #include -.PP -.B void ares_set_local_ip6(ares_channel \fIchannel\fP, const unsigned char* \fIlocal_ip6\fP) +#include + +void ares_set_local_ip6(ares_channel \fIchannel\fP, const unsigned char* \fIlocal_ip6\fP) .fi .SH DESCRIPTION -The \fBares_set_local_ip6\fP function sets the IPv6 address for outbound -IPv6 requests. The parameter \fIlocal_ip6\fP is specified in network byte -order. This allows users to specify outbound interfaces when used on -multi-homed systems. The local_ip6 argument must be 16 bytes in length. +The \fBares_set_local_ip6\fP function sets the IPv6 address for outbound IPv6 +requests. The parameter \fIlocal_ip6\fP is specified in network byte order. +This allows users to specify outbound interfaces when used on multi-homed +systems. The \fIlocal_ip6\fP argument must be 16 bytes in length. .SH SEE ALSO .BR ares_set_local_ip4 (3) .SH NOTES diff --git a/thirdparty/c-ares/docs/ares_set_servers.3 b/thirdparty/c-ares/docs/ares_set_servers.3 index 65ad1e1642..aeed0a5156 100644 --- a/thirdparty/c-ares/docs/ares_set_servers.3 +++ b/thirdparty/c-ares/docs/ares_set_servers.3 @@ -18,10 +18,13 @@ ares_set_servers, ares_set_servers_ports \- Initialize an ares_channel name servers configuration .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_set_servers(ares_channel \fIchannel\fP, struct ares_addr_node *\fIservers\fP) -.B int ares_set_servers_ports(ares_channel \fIchannel\fP, struct ares_addr_port_node *\fIservers\fP) +#include + +int ares_set_servers(ares_channel \fIchannel\fP, + struct ares_addr_node *\fIservers\fP) + +int ares_set_servers_ports(ares_channel \fIchannel\fP, + struct ares_addr_port_node *\fIservers\fP) .fi .SH DESCRIPTION The \fBares_set_servers(3)\fP function initializes name servers configuration diff --git a/thirdparty/c-ares/docs/ares_set_servers_csv.3 b/thirdparty/c-ares/docs/ares_set_servers_csv.3 index 638d26987e..a729281b97 100644 --- a/thirdparty/c-ares/docs/ares_set_servers_csv.3 +++ b/thirdparty/c-ares/docs/ares_set_servers_csv.3 @@ -18,10 +18,11 @@ ares_set_servers_csv, ares_set_servers_ports_csv \- Set list of DNS servers to be used. .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_set_servers_csv(ares_channel \fIchannel\fP, const char* \fIservers\fP) -.B int ares_set_servers_ports_csv(ares_channel \fIchannel\fP, const char* \fIservers\fP) +#include + +int ares_set_servers_csv(ares_channel \fIchannel\fP, const char* \fIservers\fP) + +int ares_set_servers_ports_csv(ares_channel \fIchannel\fP, const char* \fIservers\fP) .fi .SH DESCRIPTION The \fBares_set_servers_csv\fP and \fBares_set_servers_ports_csv\fPfunctions set diff --git a/thirdparty/c-ares/docs/ares_set_socket_callback.3 b/thirdparty/c-ares/docs/ares_set_socket_callback.3 index 14a5ad2dcb..70d7cf7d59 100644 --- a/thirdparty/c-ares/docs/ares_set_socket_callback.3 +++ b/thirdparty/c-ares/docs/ares_set_socket_callback.3 @@ -4,15 +4,15 @@ ares_set_socket_callback \- Set a socket creation callback .SH SYNOPSIS .nf -.B #include -.PP -.B typedef int (*ares_sock_create_callback)(ares_socket_t \fIsocket_fd\fP, +#include + +typedef int (*ares_sock_create_callback)(ares_socket_t \fIsocket_fd\fP, int \fItype\fP, void *\fIuserdata\fP) -.PP -.B void ares_set_socket_callback(ares_channel \fIchannel\fP, - ares_sock_create_callback \fIcallback\fP, - void *\fIuserdata\fP) + +void ares_set_socket_callback(ares_channel \fIchannel\fP, + ares_sock_create_callback \fIcallback\fP, + void *\fIuserdata\fP) .PP .B cc file.c -lcares .fi diff --git a/thirdparty/c-ares/docs/ares_set_socket_configure_callback.3 b/thirdparty/c-ares/docs/ares_set_socket_configure_callback.3 index d3b2f93017..89188a6a9a 100644 --- a/thirdparty/c-ares/docs/ares_set_socket_configure_callback.3 +++ b/thirdparty/c-ares/docs/ares_set_socket_configure_callback.3 @@ -4,17 +4,15 @@ ares_set_socket_configure_callback \- Set a socket configuration callback .SH SYNOPSIS .nf -.B #include -.PP -.B typedef int (*ares_sock_config_callback)(ares_socket_t \fIsocket_fd\fP, +#include + +typedef int (*ares_sock_config_callback)(ares_socket_t \fIsocket_fd\fP, int \fItype\fP, void *\fIuserdata\fP) -.PP -.B void ares_set_socket_configure_callback(ares_channel \fIchannel\fP, - ares_sock_config_callback \fIcallback\fP, - void *\fIuserdata\fP) -.PP -.B cc file.c -lcares + +void ares_set_socket_configure_callback(ares_channel \fIchannel\fP, + ares_sock_config_callback \fIcallback\fP, + void *\fIuserdata\fP) .fi .SH DESCRIPTION .PP diff --git a/thirdparty/c-ares/docs/ares_set_socket_functions.3 b/thirdparty/c-ares/docs/ares_set_socket_functions.3 index 1cb0b8553b..0c33a49411 100644 --- a/thirdparty/c-ares/docs/ares_set_socket_functions.3 +++ b/thirdparty/c-ares/docs/ares_set_socket_functions.3 @@ -4,21 +4,20 @@ ares_set_socket_functions \- Set socket io callbacks .SH SYNOPSIS .nf -.B #include -.PP -.B struct ares_socket_functions { - ares_socket_t(*\fIasocket\fP)(int, int, int, void *); - int(*\fIaclose\fP)(ares_socket_t, void *); - int(*\fIaconnect\fP)(ares_socket_t, const struct sockaddr *, ares_socklen_t, void *); - ares_ssize_t(*\fIarecvfrom\fP)(ares_socket_t, void *, size_t, int, struct sockaddr *, ares_socklen_t *, void *); - ares_ssize_t(*\fIasendv\fP)(ares_socket_t, const struct iovec *, int, void *); - }; +#include -.PP -.B void ares_set_socket_functions(ares_channel \fIchannel\fP, - const struct ares_socket_functions * \fIfunctions\fP, - void *\fIuser_data\fP); +struct ares_socket_functions { + ares_socket_t (*\fIasocket\fP)(int, int, int, void *); + int (*\fIaclose\fP)(ares_socket_t, void *); + int (*\fIaconnect\fP)(ares_socket_t, const struct sockaddr *, ares_socklen_t, void *); + ares_ssize_t (*\fIarecvfrom\fP)(ares_socket_t, void *, size_t, int, + struct sockaddr *, ares_socklen_t *, void *); + ares_ssize_t (*\fIasendv\fP)(ares_socket_t, const struct iovec *, int, void *); +}; +void ares_set_socket_functions(ares_channel \fIchannel\fP, + const struct ares_socket_functions * \fIfunctions\fP, + void *\fIuser_data\fP); .fi .SH DESCRIPTION .PP diff --git a/thirdparty/c-ares/docs/ares_set_sortlist.3 b/thirdparty/c-ares/docs/ares_set_sortlist.3 index 24a97906ad..50e99e87df 100644 --- a/thirdparty/c-ares/docs/ares_set_sortlist.3 +++ b/thirdparty/c-ares/docs/ares_set_sortlist.3 @@ -16,9 +16,9 @@ ares_set_sortlist \- Initialize an ares_channel sortlist configuration .SH SYNOPSIS .nf -.B #include -.PP -.B int ares_set_sortlist(ares_channel \fIchannel\fP, const char *\fIsortstr\fP) +#include + +int ares_set_sortlist(ares_channel \fIchannel\fP, const char *\fIsortstr\fP) .fi .SH DESCRIPTION The \fBares_set_sortlist(3)\fP function initializes an address sortlist configuration diff --git a/thirdparty/c-ares/docs/ares_strerror.3 b/thirdparty/c-ares/docs/ares_strerror.3 index 4b50d5bbb5..6369fccd60 100644 --- a/thirdparty/c-ares/docs/ares_strerror.3 +++ b/thirdparty/c-ares/docs/ares_strerror.3 @@ -18,9 +18,9 @@ ares_strerror \- Get the description of an ares library error code .SH SYNOPSIS .nf -.B #include -.PP -.B const char *ares_strerror(int \fIcode\fP) +#include + +const char *ares_strerror(int \fIcode\fP) .fi .SH DESCRIPTION The diff --git a/thirdparty/c-ares/include/ares.h b/thirdparty/c-ares/include/ares.h index cf8a8553fe..8c7520eec6 100644 --- a/thirdparty/c-ares/include/ares.h +++ b/thirdparty/c-ares/include/ares.h @@ -63,6 +63,13 @@ # include # include # include +/* To aid with linking against a static c-ares build, lets tell the microsoft + * compiler to pull in needed dependencies */ +# ifdef _MSC_VER +# pragma comment(lib, "ws2_32") +# pragma comment(lib, "advapi32") +# pragma comment(lib, "iphlpapi") +# endif #else # include # include @@ -168,6 +175,7 @@ extern "C" { #define ARES_OPT_EDNSPSZ (1 << 15) #define ARES_OPT_NOROTATE (1 << 16) #define ARES_OPT_RESOLVCONF (1 << 17) +#define ARES_OPT_HOSTS_FILE (1 << 18) /* Nameinfo flag values */ #define ARES_NI_NOFQDN (1 << 0) @@ -277,6 +285,7 @@ struct ares_options { int nsort; int ednspsz; char *resolvconf_path; + char *hosts_path; }; struct hostent; diff --git a/thirdparty/c-ares/include/ares_version.h b/thirdparty/c-ares/include/ares_version.h index 22c6f62fd7..4d8d62fd18 100644 --- a/thirdparty/c-ares/include/ares_version.h +++ b/thirdparty/c-ares/include/ares_version.h @@ -6,12 +6,12 @@ #define ARES_COPYRIGHT "2004 - 2021 Daniel Stenberg, ." #define ARES_VERSION_MAJOR 1 -#define ARES_VERSION_MINOR 18 -#define ARES_VERSION_PATCH 1 +#define ARES_VERSION_MINOR 19 +#define ARES_VERSION_PATCH 0 #define ARES_VERSION ((ARES_VERSION_MAJOR<<16)|\ (ARES_VERSION_MINOR<<8)|\ (ARES_VERSION_PATCH)) -#define ARES_VERSION_STR "1.18.1" +#define ARES_VERSION_STR "1.19.0" #if (ARES_VERSION >= 0x010700) # define CARES_HAVE_ARES_LIBRARY_INIT 1 diff --git a/thirdparty/c-ares/src/lib/CMakeLists.txt b/thirdparty/c-ares/src/lib/CMakeLists.txt index 7d37be21ac..5cea076282 100644 --- a/thirdparty/c-ares/src/lib/CMakeLists.txt +++ b/thirdparty/c-ares/src/lib/CMakeLists.txt @@ -10,6 +10,11 @@ CONFIGURE_FILE (ares_config.h.cmake ${PROJECT_BINARY_DIR}/ares_config.h) IF (CARES_SHARED) ADD_LIBRARY (${PROJECT_NAME} SHARED ${CSOURCES}) + # Include resource file in windows builds for versioned DLLs + IF (WIN32) + TARGET_SOURCES (${PROJECT_NAME} PRIVATE cares.rc) + ENDIF() + # Convert CARES_LIB_VERSIONINFO libtool version format into VERSION and SOVERSION # Convert from ":" separated into CMake list format using ";" STRING (REPLACE ":" ";" CARES_LIB_VERSIONINFO ${CARES_LIB_VERSIONINFO}) diff --git a/thirdparty/c-ares/src/lib/Makefile.inc b/thirdparty/c-ares/src/lib/Makefile.inc index a3b060c289..140378d67e 100644 --- a/thirdparty/c-ares/src/lib/Makefile.inc +++ b/thirdparty/c-ares/src/lib/Makefile.inc @@ -65,7 +65,6 @@ HHEADERS = ares_android.h \ ares_inet_net_pton.h \ ares_iphlpapi.h \ ares_ipv6.h \ - ares_library_init.h \ ares_llist.h \ ares_nowarn.h \ ares_platform.h \ diff --git a/thirdparty/c-ares/src/lib/ares_data.h b/thirdparty/c-ares/src/lib/ares_data.h index 6b9dd9f8f7..54d729d0e7 100644 --- a/thirdparty/c-ares/src/lib/ares_data.h +++ b/thirdparty/c-ares/src/lib/ares_data.h @@ -1,3 +1,6 @@ +#ifndef __ARES_DATA_H +#define __ARES_DATA_H + /* Copyright (C) 2009-2013 by Daniel Stenberg * @@ -74,3 +77,5 @@ struct ares_data { void *ares_malloc_data(ares_datatype type); + +#endif // __ARES_DATA_H diff --git a/thirdparty/c-ares/src/lib/ares_destroy.c b/thirdparty/c-ares/src/lib/ares_destroy.c index fed2009ab3..7ec2bde5a4 100644 --- a/thirdparty/c-ares/src/lib/ares_destroy.c +++ b/thirdparty/c-ares/src/lib/ares_destroy.c @@ -38,6 +38,8 @@ void ares_destroy_options(struct ares_options *options) ares_free(options->lookups); if(options->resolvconf_path) ares_free(options->resolvconf_path); + if(options->hosts_path) + ares_free(options->hosts_path); } void ares_destroy(ares_channel channel) @@ -90,6 +92,9 @@ void ares_destroy(ares_channel channel) if (channel->resolvconf_path) ares_free(channel->resolvconf_path); + if (channel->hosts_path) + ares_free(channel->hosts_path); + ares_free(channel); } diff --git a/thirdparty/c-ares/src/lib/ares_expand_name.c b/thirdparty/c-ares/src/lib/ares_expand_name.c index fcd88a2a42..6c7a35a715 100644 --- a/thirdparty/c-ares/src/lib/ares_expand_name.c +++ b/thirdparty/c-ares/src/lib/ares_expand_name.c @@ -64,6 +64,8 @@ static int ares__isprint(int ch) * - underscores which are used in SRV records. * - Forward slashes such as are used for classless in-addr.arpa * delegation (CNAMEs) + * - Asterisks may be used for wildcard domains in CNAMEs as seen in the + * real world. * While RFC 2181 section 11 does state not to do validation, * that applies to servers, not clients. Vulnerabilities have been * reported when this validation is not performed. Security is more @@ -71,7 +73,7 @@ static int ares__isprint(int ch) * anyhow). */ static int is_hostnamech(int ch) { - /* [A-Za-z0-9-._/] + /* [A-Za-z0-9-*._/] * Don't use isalnum() as it is locale-specific */ if (ch >= 'A' && ch <= 'Z') @@ -80,7 +82,7 @@ static int is_hostnamech(int ch) return 1; if (ch >= '0' && ch <= '9') return 1; - if (ch == '-' || ch == '.' || ch == '_' || ch == '/') + if (ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '*') return 1; return 0; diff --git a/thirdparty/c-ares/src/lib/ares_getaddrinfo.c b/thirdparty/c-ares/src/lib/ares_getaddrinfo.c index 0a0225a59e..1c41498723 100644 --- a/thirdparty/c-ares/src/lib/ares_getaddrinfo.c +++ b/thirdparty/c-ares/src/lib/ares_getaddrinfo.c @@ -326,7 +326,7 @@ static int fake_addrinfo(const char *name, } } - if (family == AF_INET6 || family == AF_UNSPEC) + if (!result && (family == AF_INET6 || family == AF_UNSPEC)) { struct ares_in6_addr addr6; result = ares_inet_pton(AF_INET6, name, &addr6) < 1 ? 0 : 1; @@ -404,16 +404,46 @@ static void end_hquery(struct host_query *hquery, int status) ares_free(hquery); } +static int is_localhost(const char *name) +{ + /* RFC6761 6.3 says : The domain "localhost." and any names falling within ".localhost." */ + size_t len; + + if (name == NULL) + return 0; + + if (strcmp(name, "localhost") == 0) + return 1; + + len = strlen(name); + if (len < 10 /* strlen(".localhost") */) + return 0; + + if (strcmp(name + (len - 10 /* strlen(".localhost") */), ".localhost") == 0) + return 1; + + return 0; +} + static int file_lookup(struct host_query *hquery) { FILE *fp; int error; int status; - const char *path_hosts = NULL; + char *path_hosts = NULL; if (hquery->hints.ai_flags & ARES_AI_ENVHOSTS) { - path_hosts = getenv("CARES_HOSTS"); + path_hosts = ares_strdup(getenv("CARES_HOSTS")); + if (!path_hosts) + return ARES_ENOMEM; + } + + if (hquery->channel->hosts_path) + { + path_hosts = ares_strdup(hquery->channel->hosts_path); + if (!path_hosts) + return ARES_ENOMEM; } if (!path_hosts) @@ -447,15 +477,15 @@ static int file_lookup(struct host_query *hquery) return ARES_ENOTFOUND; strcat(PATH_HOSTS, WIN_PATH_HOSTS); - path_hosts = PATH_HOSTS; - #elif defined(WATT32) const char *PATH_HOSTS = _w32_GetHostsFile(); if (!PATH_HOSTS) return ARES_ENOTFOUND; #endif - path_hosts = PATH_HOSTS; + path_hosts = ares_strdup(PATH_HOSTS); + if (!path_hosts) + return ARES_ENOMEM; } fp = fopen(path_hosts, "r"); @@ -466,21 +496,29 @@ static int file_lookup(struct host_query *hquery) { case ENOENT: case ESRCH: - return ARES_ENOTFOUND; + status = ARES_ENOTFOUND; + break; default: DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error, strerror(error))); DEBUGF(fprintf(stderr, "Error opening file: %s\n", path_hosts)); - return ARES_EFILE; + status = ARES_EFILE; + break; } } - status = ares__readaddrinfo(fp, hquery->name, hquery->port, &hquery->hints, hquery->ai); - fclose(fp); + else + { + status = ares__readaddrinfo(fp, hquery->name, hquery->port, &hquery->hints, hquery->ai); + fclose(fp); + } + ares_free(path_hosts); /* RFC6761 section 6.3 #3 states that "Name resolution APIs and libraries * SHOULD recognize localhost names as special and SHOULD always return the - * IP loopback address for address queries" */ - if (status == ARES_ENOTFOUND && strcmp(hquery->name, "localhost") == 0) + * IP loopback address for address queries". + * We will also ignore ALL errors when trying to resolve localhost, such + * as permissions errors reading /etc/hosts or a malformed /etc/hosts */ + if (status != ARES_SUCCESS && is_localhost(hquery->name)) { return ares__addrinfo_localhost(hquery->name, hquery->port, &hquery->hints, hquery->ai); @@ -497,7 +535,7 @@ static void next_lookup(struct host_query *hquery, int status) /* RFC6761 section 6.3 #3 says "Name resolution APIs SHOULD NOT send * queries for localhost names to their configured caching DNS * server(s)." */ - if (strcmp(hquery->name, "localhost") != 0) + if (!is_localhost(hquery->name)) { /* DNS lookup */ if (next_dns_lookup(hquery)) @@ -543,7 +581,16 @@ static void host_callback(void *arg, int status, int timeouts, if (addinfostatus != ARES_SUCCESS && addinfostatus != ARES_ENODATA) { /* error in parsing result e.g. no memory */ - end_hquery(hquery, addinfostatus); + if (addinfostatus == ARES_EBADRESP && hquery->ai->nodes) + { + /* We got a bad response from server, but at least one query + * ended with ARES_SUCCESS */ + end_hquery(hquery, ARES_SUCCESS); + } + else + { + end_hquery(hquery, addinfostatus); + } } else if (hquery->ai->nodes) { diff --git a/thirdparty/c-ares/src/lib/ares_init.c b/thirdparty/c-ares/src/lib/ares_init.c index de5d86c9ae..3f9cec6521 100644 --- a/thirdparty/c-ares/src/lib/ares_init.c +++ b/thirdparty/c-ares/src/lib/ares_init.c @@ -47,9 +47,12 @@ #include #endif +#if defined(USE_WINSOCK) +# include +#endif + #include "ares.h" #include "ares_inet_net_pton.h" -#include "ares_library_init.h" #include "ares_nowarn.h" #include "ares_platform.h" #include "ares_private.h" @@ -58,6 +61,18 @@ #undef WIN32 /* Redefined in MingW/MSVC headers */ #endif +/* Define RtlGenRandom = SystemFunction036. This is in advapi32.dll. There is + * no need to dynamically load this, other software used widely does not. + * http://blogs.msdn.com/michael_howard/archive/2005/01/14/353379.aspx + * https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom + */ +#ifdef _WIN32 +BOOLEAN WINAPI SystemFunction036(PVOID RandomBuffer, ULONG RandomBufferLength); +# ifndef RtlGenRandom +# define RtlGenRandom(a,b) SystemFunction036(a,b) +# endif +#endif + static int init_by_options(ares_channel channel, const struct ares_options *options, int optmask); @@ -149,6 +164,7 @@ int ares_init_options(ares_channel *channelptr, struct ares_options *options, channel->sock_funcs = NULL; channel->sock_func_cb_data = NULL; channel->resolvconf_path = NULL; + channel->hosts_path = NULL; channel->last_server = 0; channel->last_timeout_processed = (time_t)now.tv_sec; @@ -217,13 +233,15 @@ done: if (channel->servers) ares_free(channel->servers); if (channel->ndomains != -1) - ares_strsplit_free(channel->domains, channel->ndomains); + ares__strsplit_free(channel->domains, channel->ndomains); if (channel->sortlist) ares_free(channel->sortlist); if(channel->lookups) ares_free(channel->lookups); if(channel->resolvconf_path) ares_free(channel->resolvconf_path); + if(channel->hosts_path) + ares_free(channel->hosts_path); ares_free(channel); return status; } @@ -335,6 +353,9 @@ int ares_save_options(ares_channel channel, struct ares_options *options, if (channel->resolvconf_path) (*optmask) |= ARES_OPT_RESOLVCONF; + if (channel->hosts_path) + (*optmask) |= ARES_OPT_HOSTS_FILE; + /* Copy easy stuff */ options->flags = channel->flags; @@ -414,6 +435,13 @@ int ares_save_options(ares_channel channel, struct ares_options *options, return ARES_ENOMEM; } + /* copy path for hosts file */ + if (channel->hosts_path) { + options->hosts_path = ares_strdup(channel->hosts_path); + if (!options->hosts_path) + return ARES_ENOMEM; + } + return ARES_SUCCESS; } @@ -530,6 +558,14 @@ static int init_by_options(ares_channel channel, return ARES_ENOMEM; } + /* Set path for hosts file, if given. */ + if ((optmask & ARES_OPT_HOSTS_FILE) && !channel->hosts_path) + { + channel->hosts_path = ares_strdup(options->hosts_path); + if (!channel->hosts_path && options->hosts_path) + return ARES_ENOMEM; + } + channel->optmask = optmask; return ARES_SUCCESS; @@ -608,227 +644,6 @@ static int get_REG_SZ(HKEY hKey, const char *leafKeyName, char **outptr) return 1; } -/* - * get_REG_SZ_9X() - * - * Functionally identical to get_REG_SZ() - * - * Supported on Windows 95, 98 and ME. - */ -static int get_REG_SZ_9X(HKEY hKey, const char *leafKeyName, char **outptr) -{ - DWORD dataType = 0; - DWORD size = 0; - int res; - - *outptr = NULL; - - /* Find out size of string stored in registry */ - res = RegQueryValueExA(hKey, leafKeyName, 0, &dataType, NULL, &size); - if ((res != ERROR_SUCCESS && res != ERROR_MORE_DATA) || !size) - return 0; - - /* Allocate buffer of indicated size plus one given that string - might have been stored without null termination */ - *outptr = ares_malloc(size+1); - if (!*outptr) - return 0; - - /* Get the value for real */ - res = RegQueryValueExA(hKey, leafKeyName, 0, &dataType, - (unsigned char *)*outptr, &size); - if ((res != ERROR_SUCCESS) || (size == 1)) - { - ares_free(*outptr); - *outptr = NULL; - return 0; - } - - /* Null terminate buffer allways */ - *(*outptr + size) = '\0'; - - return 1; -} - -/* - * get_enum_REG_SZ() - * - * Given a 'hKeyParent' handle to an open registry key and a 'leafKeyName' - * pointer to the name of the registry leaf key to be queried, parent key - * is enumerated searching in child keys for given leaf key name and its - * associated string value. When located, this returns a pointer in *outptr - * to a newly allocated memory area holding it as a null-terminated string. - * - * Returns 0 and nullifies *outptr upon inability to return a string value. - * - * Returns 1 and sets *outptr when returning a dynamically allocated string. - * - * Supported on Windows NT 3.5 and newer. - */ -static int get_enum_REG_SZ(HKEY hKeyParent, const char *leafKeyName, - char **outptr) -{ - char enumKeyName[256]; - DWORD enumKeyNameBuffSize; - DWORD enumKeyIdx = 0; - HKEY hKeyEnum; - int gotString; - int res; - - *outptr = NULL; - - for(;;) - { - enumKeyNameBuffSize = sizeof(enumKeyName); - res = RegEnumKeyExA(hKeyParent, enumKeyIdx++, enumKeyName, - &enumKeyNameBuffSize, 0, NULL, NULL, NULL); - if (res != ERROR_SUCCESS) - break; - res = RegOpenKeyExA(hKeyParent, enumKeyName, 0, KEY_QUERY_VALUE, - &hKeyEnum); - if (res != ERROR_SUCCESS) - continue; - gotString = get_REG_SZ(hKeyEnum, leafKeyName, outptr); - RegCloseKey(hKeyEnum); - if (gotString) - break; - } - - if (!*outptr) - return 0; - - return 1; -} - -/* - * get_DNS_Registry_9X() - * - * Functionally identical to get_DNS_Registry() - * - * Implementation supports Windows 95, 98 and ME. - */ -static int get_DNS_Registry_9X(char **outptr) -{ - HKEY hKey_VxD_MStcp; - int gotString; - int res; - - *outptr = NULL; - - res = RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_9X, 0, KEY_READ, - &hKey_VxD_MStcp); - if (res != ERROR_SUCCESS) - return 0; - - gotString = get_REG_SZ_9X(hKey_VxD_MStcp, NAMESERVER, outptr); - RegCloseKey(hKey_VxD_MStcp); - - if (!gotString || !*outptr) - return 0; - - return 1; -} - -/* - * get_DNS_Registry_NT() - * - * Functionally identical to get_DNS_Registry() - * - * Refs: Microsoft Knowledge Base articles KB120642 and KB314053. - * - * Implementation supports Windows NT 3.5 and newer. - */ -static int get_DNS_Registry_NT(char **outptr) -{ - HKEY hKey_Interfaces = NULL; - HKEY hKey_Tcpip_Parameters; - int gotString; - int res; - - *outptr = NULL; - - res = RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, KEY_READ, - &hKey_Tcpip_Parameters); - if (res != ERROR_SUCCESS) - return 0; - - /* - ** Global DNS settings override adapter specific parameters when both - ** are set. Additionally static DNS settings override DHCP-configured - ** parameters when both are set. - */ - - /* Global DNS static parameters */ - gotString = get_REG_SZ(hKey_Tcpip_Parameters, NAMESERVER, outptr); - if (gotString) - goto done; - - /* Global DNS DHCP-configured parameters */ - gotString = get_REG_SZ(hKey_Tcpip_Parameters, DHCPNAMESERVER, outptr); - if (gotString) - goto done; - - /* Try adapter specific parameters */ - res = RegOpenKeyExA(hKey_Tcpip_Parameters, "Interfaces", 0, - KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, - &hKey_Interfaces); - if (res != ERROR_SUCCESS) - { - hKey_Interfaces = NULL; - goto done; - } - - /* Adapter specific DNS static parameters */ - gotString = get_enum_REG_SZ(hKey_Interfaces, NAMESERVER, outptr); - if (gotString) - goto done; - - /* Adapter specific DNS DHCP-configured parameters */ - gotString = get_enum_REG_SZ(hKey_Interfaces, DHCPNAMESERVER, outptr); - -done: - if (hKey_Interfaces) - RegCloseKey(hKey_Interfaces); - - RegCloseKey(hKey_Tcpip_Parameters); - - if (!gotString || !*outptr) - return 0; - - return 1; -} - -/* - * get_DNS_Registry() - * - * Locates DNS info in the registry. When located, this returns a pointer - * in *outptr to a newly allocated memory area holding a null-terminated - * string with a space or comma seperated list of DNS IP addresses. - * - * Returns 0 and nullifies *outptr upon inability to return DNSes string. - * - * Returns 1 and sets *outptr when returning a dynamically allocated string. - */ -static int get_DNS_Registry(char **outptr) -{ - win_platform platform; - int gotString = 0; - - *outptr = NULL; - - platform = ares__getplatform(); - - if (platform == WIN_NT) - gotString = get_DNS_Registry_NT(outptr); - else if (platform == WIN_9X) - gotString = get_DNS_Registry_9X(outptr); - - if (!gotString) - return 0; - - return 1; -} - static void commanjoin(char** dst, const char* const src, const size_t len) { char *newbuf; @@ -857,106 +672,6 @@ static void commajoin(char **dst, const char *src) commanjoin(dst, src, strlen(src)); } -/* - * get_DNS_NetworkParams() - * - * Locates DNS info using GetNetworkParams() function from the Internet - * Protocol Helper (IP Helper) API. When located, this returns a pointer - * in *outptr to a newly allocated memory area holding a null-terminated - * string with a space or comma seperated list of DNS IP addresses. - * - * Returns 0 and nullifies *outptr upon inability to return DNSes string. - * - * Returns 1 and sets *outptr when returning a dynamically allocated string. - * - * Implementation supports Windows 98 and newer. - * - * Note: Ancient PSDK required in order to build a W98 target. - */ -static int get_DNS_NetworkParams(char **outptr) -{ - FIXED_INFO *fi, *newfi; - struct ares_addr namesrvr; - char *txtaddr; - IP_ADDR_STRING *ipAddr; - int res; - DWORD size = sizeof (*fi); - - *outptr = NULL; - - /* Verify run-time availability of GetNetworkParams() */ - if (ares_fpGetNetworkParams == ZERO_NULL) - return 0; - - fi = ares_malloc(size); - if (!fi) - return 0; - - res = (*ares_fpGetNetworkParams) (fi, &size); - if ((res != ERROR_BUFFER_OVERFLOW) && (res != ERROR_SUCCESS)) - goto done; - - newfi = ares_realloc(fi, size); - if (!newfi) - goto done; - - fi = newfi; - res = (*ares_fpGetNetworkParams) (fi, &size); - if (res != ERROR_SUCCESS) - goto done; - - for (ipAddr = &fi->DnsServerList; ipAddr; ipAddr = ipAddr->Next) - { - txtaddr = &ipAddr->IpAddress.String[0]; - - /* Validate converting textual address to binary format. */ - if (ares_inet_pton(AF_INET, txtaddr, &namesrvr.addrV4) == 1) - { - if ((namesrvr.addrV4.S_un.S_addr == INADDR_ANY) || - (namesrvr.addrV4.S_un.S_addr == INADDR_NONE)) - continue; - } - else if (ares_inet_pton(AF_INET6, txtaddr, &namesrvr.addrV6) == 1) - { - if (memcmp(&namesrvr.addrV6, &ares_in6addr_any, - sizeof(namesrvr.addrV6)) == 0) - continue; - } - else - continue; - - commajoin(outptr, txtaddr); - - if (!*outptr) - break; - } - -done: - if (fi) - ares_free(fi); - - if (!*outptr) - return 0; - - return 1; -} - -static BOOL ares_IsWindowsVistaOrGreater(void) -{ - OSVERSIONINFO vinfo; - memset(&vinfo, 0, sizeof(vinfo)); - vinfo.dwOSVersionInfoSize = sizeof(vinfo); -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4996) /* warning C4996: 'GetVersionExW': was declared deprecated */ -#endif - if (!GetVersionEx(&vinfo) || vinfo.dwMajorVersion < 6) - return FALSE; - return TRUE; -#ifdef _MSC_VER -#pragma warning(pop) -#endif -} /* A structure to hold the string form of IPv4 and IPv6 addresses so we can * sort them by a metric. @@ -1033,21 +748,20 @@ static ULONG getBestRouteMetric(IF_LUID * const luid, /* Can't be const :( */ /* On this interface, get the best route to that destination. */ MIB_IPFORWARD_ROW2 row; SOCKADDR_INET ignored; - if(!ares_fpGetBestRoute2 || - ares_fpGetBestRoute2(/* The interface to use. The index is ignored since we are - * passing a LUID. - */ - luid, 0, - /* No specific source address. */ - NULL, - /* Our destination address. */ - dest, - /* No options. */ - 0, - /* The route row. */ - &row, - /* The best source address, which we don't need. */ - &ignored) != NO_ERROR + if(GetBestRoute2(/* The interface to use. The index is ignored since we are + * passing a LUID. + */ + luid, 0, + /* No specific source address. */ + NULL, + /* Our destination address. */ + dest, + /* No options. */ + 0, + /* The route row. */ + &row, + /* The best source address, which we don't need. */ + &ignored) != NO_ERROR /* If the metric is "unused" (-1) or too large for us to add the two * metrics, use the worst possible, thus sorting this last. */ @@ -1067,7 +781,7 @@ static ULONG getBestRouteMetric(IF_LUID * const luid, /* Can't be const :( */ } /* - * get_DNS_AdaptersAddresses() + * get_DNS_Windows() * * Locates DNS info using GetAdaptersAddresses() function from the Internet * Protocol Helper (IP Helper) API. When located, this returns a pointer @@ -1082,7 +796,7 @@ static ULONG getBestRouteMetric(IF_LUID * const luid, /* Can't be const :( */ */ #define IPAA_INITIAL_BUF_SZ 15 * 1024 #define IPAA_MAX_TRIES 3 -static int get_DNS_AdaptersAddresses(char **outptr) +static int get_DNS_Windows(char **outptr) { IP_ADAPTER_DNS_SERVER_ADDRESS *ipaDNSAddr; IP_ADAPTER_ADDRESSES *ipaa, *newipaa, *ipaaEntry; @@ -1107,10 +821,6 @@ static int get_DNS_AdaptersAddresses(char **outptr) *outptr = NULL; - /* Verify run-time availability of GetAdaptersAddresses() */ - if (ares_fpGetAdaptersAddresses == ZERO_NULL) - return 0; - ipaa = ares_malloc(Bufsz); if (!ipaa) return 0; @@ -1127,8 +837,7 @@ static int get_DNS_AdaptersAddresses(char **outptr) } /* Usually this call suceeds with initial buffer size */ - res = (*ares_fpGetAdaptersAddresses) (AF_UNSPEC, AddrFlags, NULL, - ipaa, &ReqBufsz); + res = GetAdaptersAddresses(AF_UNSPEC, AddrFlags, NULL, ipaa, &ReqBufsz); if ((res != ERROR_BUFFER_OVERFLOW) && (res != ERROR_SUCCESS)) goto done; @@ -1142,8 +851,7 @@ static int get_DNS_AdaptersAddresses(char **outptr) Bufsz = ReqBufsz; ipaa = newipaa; } - res = (*ares_fpGetAdaptersAddresses) (AF_UNSPEC, AddrFlags, NULL, - ipaa, &ReqBufsz); + res = GetAdaptersAddresses(AF_UNSPEC, AddrFlags, NULL, ipaa, &ReqBufsz); if (res == ERROR_SUCCESS) break; } @@ -1185,26 +893,17 @@ static int get_DNS_AdaptersAddresses(char **outptr) addressesSize = newSize; } - /* Vista required for Luid or Ipv4Metric */ - if (ares_IsWindowsVistaOrGreater()) - { - /* Save the address as the next element in addresses. */ - addresses[addressesIndex].metric = - getBestRouteMetric(&ipaaEntry->Luid, - (SOCKADDR_INET*)(namesrvr.sa), - ipaaEntry->Ipv4Metric); - } - else - { - addresses[addressesIndex].metric = (ULONG)-1; - } + addresses[addressesIndex].metric = + getBestRouteMetric(&ipaaEntry->Luid, + (SOCKADDR_INET*)(namesrvr.sa), + ipaaEntry->Ipv4Metric); /* Record insertion index to make qsort stable */ addresses[addressesIndex].orig_idx = addressesIndex; - if (! ares_inet_ntop(AF_INET, &namesrvr.sa4->sin_addr, - addresses[addressesIndex].text, - sizeof(addresses[0].text))) { + if (!ares_inet_ntop(AF_INET, &namesrvr.sa4->sin_addr, + addresses[addressesIndex].text, + sizeof(addresses[0].text))) { continue; } ++addressesIndex; @@ -1227,26 +926,17 @@ static int get_DNS_AdaptersAddresses(char **outptr) addressesSize = newSize; } - /* Vista required for Luid or Ipv4Metric */ - if (ares_IsWindowsVistaOrGreater()) - { - /* Save the address as the next element in addresses. */ - addresses[addressesIndex].metric = - getBestRouteMetric(&ipaaEntry->Luid, - (SOCKADDR_INET*)(namesrvr.sa), - ipaaEntry->Ipv6Metric); - } - else - { - addresses[addressesIndex].metric = (ULONG)-1; - } + addresses[addressesIndex].metric = + getBestRouteMetric(&ipaaEntry->Luid, + (SOCKADDR_INET*)(namesrvr.sa), + ipaaEntry->Ipv6Metric); /* Record insertion index to make qsort stable */ addresses[addressesIndex].orig_idx = addressesIndex; - if (! ares_inet_ntop(AF_INET6, &namesrvr.sa6->sin6_addr, - addresses[addressesIndex].text, - sizeof(addresses[0].text))) { + if (!ares_inet_ntop(AF_INET6, &namesrvr.sa6->sin6_addr, + addresses[addressesIndex].text, + sizeof(addresses[0].text))) { continue; } ++addressesIndex; @@ -1294,35 +984,6 @@ done: return 1; } -/* - * get_DNS_Windows() - * - * Locates DNS info from Windows employing most suitable methods available at - * run-time no matter which Windows version it is. When located, this returns - * a pointer in *outptr to a newly allocated memory area holding a string with - * a space or comma seperated list of DNS IP addresses, null-terminated. - * - * Returns 0 and nullifies *outptr upon inability to return DNSes string. - * - * Returns 1 and sets *outptr when returning a dynamically allocated string. - * - * Implementation supports Windows 95 and newer. - */ -static int get_DNS_Windows(char **outptr) -{ - /* Try using IP helper API GetAdaptersAddresses(). IPv4 + IPv6, also sorts - * DNS servers by interface route metrics to try to use the best DNS server. */ - if (get_DNS_AdaptersAddresses(outptr)) - return 1; - - /* Try using IP helper API GetNetworkParams(). IPv4 only. */ - if (get_DNS_NetworkParams(outptr)) - return 1; - - /* Fall-back to registry information */ - return get_DNS_Registry(outptr); -} - /* * get_SuffixList_Windows() * @@ -1686,8 +1347,12 @@ static int init_by_resolv_conf(ares_channel channel) channel->tries = res.retry; if (channel->rotate == -1) channel->rotate = res.options & RES_ROTATE; - if (channel->timeout == -1) + if (channel->timeout == -1) { channel->timeout = res.retrans * 1000; +#ifdef __APPLE__ + channel->timeout /= (res.retry + 1) * (res.nscount > 0 ? res.nscount : 1); +#endif + } res_ndestroy(&res); } @@ -2021,6 +1686,11 @@ static int init_by_defaults(ares_channel channel) ares_free(channel->resolvconf_path); channel->resolvconf_path = NULL; } + + if(channel->hosts_path) { + ares_free(channel->hosts_path); + channel->hosts_path = NULL; + } } if(hostname) @@ -2243,6 +1913,8 @@ static int config_sortlist(struct apattern **sortlist, int *nsort, q = str; while (*q && *q != '/' && *q != ';' && !ISSPACE(*q)) q++; + if (q-str >= 16) + return ARES_EBADSTR; memcpy(ipbuf, str, q-str); ipbuf[q-str] = '\0'; /* Find the prefix */ @@ -2251,6 +1923,8 @@ static int config_sortlist(struct apattern **sortlist, int *nsort, const char *str2 = q+1; while (*q && *q != ';' && !ISSPACE(*q)) q++; + if (q-str >= 32) + return ARES_EBADSTR; memcpy(ipbufpfx, str, q-str); ipbufpfx[q-str] = '\0'; str = str2; @@ -2325,12 +1999,12 @@ static int set_search(ares_channel channel, const char *str) if(channel->ndomains != -1) { /* LCOV_EXCL_START: all callers check ndomains == -1 */ /* if we already have some domains present, free them first */ - ares_strsplit_free(channel->domains, channel->ndomains); + ares__strsplit_free(channel->domains, channel->ndomains); channel->domains = NULL; channel->ndomains = -1; } /* LCOV_EXCL_STOP */ - channel->domains = ares_strsplit(str, ", ", 1, &cnt); + channel->domains = ares__strsplit(str, ", ", &cnt); channel->ndomains = (int)cnt; if (channel->domains == NULL || channel->ndomains == 0) { channel->domains = NULL; @@ -2495,12 +2169,10 @@ static int sortlist_alloc(struct apattern **sortlist, int *nsort, return 1; } + /* initialize an rc4 key. If possible a cryptographically secure random key - is generated using a suitable function (for example win32's RtlGenRandom as - described in - http://blogs.msdn.com/michael_howard/archive/2005/01/14/353379.aspx - otherwise the code defaults to cross-platform albeit less secure mechanism - using rand + is generated using a suitable function otherwise the code defaults to + cross-platform albeit less secure mechanism using rand */ static void randomize_key(unsigned char* key,int key_data_len) { @@ -2508,21 +2180,20 @@ static void randomize_key(unsigned char* key,int key_data_len) int counter=0; #ifdef WIN32 BOOLEAN res; - if (ares_fpSystemFunction036) - { - res = (*ares_fpSystemFunction036) (key, key_data_len); - if (res) - randomized = 1; - } + + res = RtlGenRandom(key, key_data_len); + if (res) + randomized = 1; + #else /* !WIN32 */ -#ifdef CARES_RANDOM_FILE +# ifdef CARES_RANDOM_FILE FILE *f = fopen(CARES_RANDOM_FILE, "rb"); if(f) { setvbuf(f, NULL, _IONBF, 0); counter = aresx_uztosi(fread(key, 1, key_data_len, f)); fclose(f); } -#endif +# endif #endif /* WIN32 */ if (!randomized) { diff --git a/thirdparty/c-ares/src/lib/ares_library_init.c b/thirdparty/c-ares/src/lib/ares_library_init.c index e0055d44a1..bbfcbeec17 100644 --- a/thirdparty/c-ares/src/lib/ares_library_init.c +++ b/thirdparty/c-ares/src/lib/ares_library_init.c @@ -18,18 +18,10 @@ #include "ares_setup.h" #include "ares.h" -#include "ares_library_init.h" #include "ares_private.h" /* library-private global and unique instance vars */ -#ifdef USE_WINSOCK -fpGetNetworkParams_t ares_fpGetNetworkParams = ZERO_NULL; -fpSystemFunction036_t ares_fpSystemFunction036 = ZERO_NULL; -fpGetAdaptersAddresses_t ares_fpGetAdaptersAddresses = ZERO_NULL; -fpGetBestRoute2_t ares_fpGetBestRoute2 = ZERO_NULL; -#endif - #if defined(ANDROID) || defined(__ANDROID__) #include "ares_android.h" #endif @@ -59,81 +51,8 @@ void *(*ares_malloc)(size_t size) = default_malloc; void *(*ares_realloc)(void *ptr, size_t size) = default_realloc; void (*ares_free)(void *ptr) = default_free; -#ifdef USE_WINSOCK -static HMODULE hnd_iphlpapi; -static HMODULE hnd_advapi32; -#endif - - -static int ares_win32_init(void) -{ -#ifdef USE_WINSOCK - - hnd_iphlpapi = 0; - hnd_iphlpapi = LoadLibraryW(L"iphlpapi.dll"); - if (!hnd_iphlpapi) - return ARES_ELOADIPHLPAPI; - - ares_fpGetNetworkParams = (fpGetNetworkParams_t) - GetProcAddress(hnd_iphlpapi, "GetNetworkParams"); - if (!ares_fpGetNetworkParams) - { - FreeLibrary(hnd_iphlpapi); - return ARES_EADDRGETNETWORKPARAMS; - } - - ares_fpGetAdaptersAddresses = (fpGetAdaptersAddresses_t) - GetProcAddress(hnd_iphlpapi, "GetAdaptersAddresses"); - if (!ares_fpGetAdaptersAddresses) - { - /* This can happen on clients before WinXP, I don't - think it should be an error, unless we don't want to - support Windows 2000 anymore */ - } - - ares_fpGetBestRoute2 = (fpGetBestRoute2_t) - GetProcAddress(hnd_iphlpapi, "GetBestRoute2"); - if (!ares_fpGetBestRoute2) - { - /* This can happen on clients before Vista, I don't - think it should be an error, unless we don't want to - support Windows XP anymore */ - } - - /* - * When advapi32.dll is unavailable or advapi32.dll has no SystemFunction036, - * also known as RtlGenRandom, which is the case for Windows versions prior - * to WinXP then c-ares uses portable rand() function. Then don't error here. - */ - - hnd_advapi32 = 0; - hnd_advapi32 = LoadLibraryW(L"advapi32.dll"); - if (hnd_advapi32) - { - ares_fpSystemFunction036 = (fpSystemFunction036_t) - GetProcAddress(hnd_advapi32, "SystemFunction036"); - } - -#endif - return ARES_SUCCESS; -} - - -static void ares_win32_cleanup(void) -{ -#ifdef USE_WINSOCK - if (hnd_advapi32) - FreeLibrary(hnd_advapi32); - if (hnd_iphlpapi) - FreeLibrary(hnd_iphlpapi); -#endif -} - - int ares_library_init(int flags) { - int res; - if (ares_initialized) { ares_initialized++; @@ -141,12 +60,7 @@ int ares_library_init(int flags) } ares_initialized++; - if (flags & ARES_LIB_INIT_WIN32) - { - res = ares_win32_init(); - if (res != ARES_SUCCESS) - return res; /* LCOV_EXCL_LINE: can't test Win32 init failure */ - } + /* NOTE: ARES_LIB_INIT_WIN32 flag no longer used */ ares_init_flags = flags; @@ -176,8 +90,7 @@ void ares_library_cleanup(void) if (ares_initialized) return; - if (ares_init_flags & ARES_LIB_INIT_WIN32) - ares_win32_cleanup(); + /* NOTE: ARES_LIB_INIT_WIN32 flag no longer used */ #if defined(ANDROID) || defined(__ANDROID__) ares_library_cleanup_android(); diff --git a/thirdparty/c-ares/src/lib/ares_library_init.h b/thirdparty/c-ares/src/lib/ares_library_init.h deleted file mode 100644 index b3896d9f7b..0000000000 --- a/thirdparty/c-ares/src/lib/ares_library_init.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef HEADER_CARES_LIBRARY_INIT_H -#define HEADER_CARES_LIBRARY_INIT_H - - -/* Copyright 1998 by the Massachusetts Institute of Technology. - * Copyright (C) 2004-2011 by Daniel Stenberg - * - * Permission to use, copy, modify, and distribute this - * software and its documentation for any purpose and without - * fee is hereby granted, provided that the above copyright - * notice appear in all copies and that both that copyright - * notice and this permission notice appear in supporting - * documentation, and that the name of M.I.T. not be used in - * advertising or publicity pertaining to distribution of the - * software without specific, written prior permission. - * M.I.T. makes no representations about the suitability of - * this software for any purpose. It is provided "as is" - * without express or implied warranty. - */ - -#include "ares_setup.h" - -#ifdef USE_WINSOCK - -#include -#include "ares_iphlpapi.h" - -typedef DWORD (WINAPI *fpGetNetworkParams_t) (FIXED_INFO*, DWORD*); -typedef BOOLEAN (APIENTRY *fpSystemFunction036_t) (void*, ULONG); -typedef ULONG (WINAPI *fpGetAdaptersAddresses_t) ( ULONG, ULONG, void*, IP_ADAPTER_ADDRESSES*, ULONG* ); -typedef NETIO_STATUS (WINAPI *fpGetBestRoute2_t) ( NET_LUID *, NET_IFINDEX, const SOCKADDR_INET *, const SOCKADDR_INET *, ULONG, PMIB_IPFORWARD_ROW2, SOCKADDR_INET * ); -/* Forward-declaration of variables defined in ares_library_init.c */ -/* that are global and unique instances for whole c-ares library. */ - -extern fpGetNetworkParams_t ares_fpGetNetworkParams; -extern fpSystemFunction036_t ares_fpSystemFunction036; -extern fpGetAdaptersAddresses_t ares_fpGetAdaptersAddresses; -extern fpGetBestRoute2_t ares_fpGetBestRoute2; - -#endif /* USE_WINSOCK */ - -#endif /* HEADER_CARES_LIBRARY_INIT_H */ - diff --git a/thirdparty/c-ares/src/lib/ares_private.h b/thirdparty/c-ares/src/lib/ares_private.h index 60d69e08b7..53043a6513 100644 --- a/thirdparty/c-ares/src/lib/ares_private.h +++ b/thirdparty/c-ares/src/lib/ares_private.h @@ -339,6 +339,9 @@ struct ares_channeldata { /* Path for resolv.conf file, configurable via ares_options */ char *resolvconf_path; + + /* Path for hosts file, configurable via ares_options */ + char *hosts_path; }; /* Does the domain end in ".onion" or ".onion."? Case-insensitive. */ diff --git a/thirdparty/c-ares/src/lib/ares_process.c b/thirdparty/c-ares/src/lib/ares_process.c index 87329e3588..d5a6df8ba7 100644 --- a/thirdparty/c-ares/src/lib/ares_process.c +++ b/thirdparty/c-ares/src/lib/ares_process.c @@ -209,6 +209,17 @@ static void write_tcp_data(ares_channel channel, ares_ssize_t scount; ares_ssize_t wcount; size_t n; + /* From writev manpage: An implementation can advertise its limit by defining + IOV_MAX in or at run time via the return value from + sysconf(_SC_IOV_MAX). On modern Linux systems, the limit is 1024. Back in + Linux 2.0 days, this limit was 16. */ +#if defined(IOV_MAX) + const size_t maxn = IOV_MAX; /* FreeBSD */ +#elif defined(_SC_IOV_MAX) + const size_t maxn = sysconf(_SC_IOV_MAX); /* Linux */ +#else + const size_t maxn = 16; /* Safe default */ +#endif if(!write_fds && (write_fd == ARES_SOCKET_BAD)) /* no possible action */ @@ -256,6 +267,8 @@ static void write_tcp_data(ares_channel channel, vec[n].iov_base = (char *) sendreq->data; vec[n].iov_len = sendreq->len; n++; + if(n >= maxn) + break; } wcount = socket_writev(channel, server->tcp_socket, vec, (int)n); ares_free(vec); diff --git a/thirdparty/c-ares/src/lib/ares_strsplit.c b/thirdparty/c-ares/src/lib/ares_strsplit.c index 97b4e5d5bb..194375c830 100644 --- a/thirdparty/c-ares/src/lib/ares_strsplit.c +++ b/thirdparty/c-ares/src/lib/ares_strsplit.c @@ -22,47 +22,7 @@ #include "ares.h" #include "ares_private.h" -static int list_contains(char * const *list, size_t num_elem, const char *str, int insensitive) -{ - size_t len; - size_t i; - - len = strlen(str); - for (i=0; ireserve(len); #if _MSC_VER >= 1920 // VS2019+ @@ -62,7 +63,7 @@ auto prepare(_Cont& str, size_t size) { using _Elem = typename _Cont::value_type; intrusive_string<_Elem>& helper = (intrusive_string<_Elem>&)str; - return helper.resize_nofill(size); + return helper.resize_for_overwrite(size); } #if defined(_AFX) template <> inline @@ -130,12 +131,12 @@ inline wchar_t* mcbs2wdup(const char* mcb, int len, int* wbuf_len, UINT cp = NTC #if _HAS_CXX17 inline std::string from_chars(const std::wstring_view& wcb, UINT cp = NTCVT_CP_DEFAULT) { - return wcbs2a(wcb.data(), wcb.length(), cp); + return wcbs2a(wcb.data(), static_cast(wcb.length()), cp); } inline std::wstring from_chars(const std::string_view& mcb, UINT cp = NTCVT_CP_DEFAULT) { - return mcbs2w(mcb.data(), mcb.length(), cp); + return mcbs2w(mcb.data(), static_cast(mcb.length()), cp); } #else inline std::string from_chars(const std::wstring& wcb, UINT cp = NTCVT_CP_DEFAULT) @@ -170,7 +171,7 @@ namespace afx # if _HAS_CXX17 inline CStringW from_chars(std::string_view mcb, UINT cp = NTCVT_CP_DEFAULT) { - return mcbs2w(mcb.data(), mcb.length(), cp); + return mcbs2w(mcb.data(), static_cast(mcb.length()), cp); } # else inline CStringW from_chars(const char* str, UINT cp = NTCVT_CP_DEFAULT) @@ -179,7 +180,7 @@ inline CStringW from_chars(const char* str, UINT cp = NTCVT_CP_DEFAULT) } inline CStringW from_chars(const std::string& mcb, UINT cp = NTCVT_CP_DEFAULT) { - return mcbs2w(mcb.c_str(), mcb.length(), cp); + return mcbs2w(mcb.c_str(), static_cast(mcb.length()), cp); } # endif } // namespace afx diff --git a/thirdparty/openal/CMakeLists.txt b/thirdparty/openal/CMakeLists.txt index b74b89e998..379544fbc5 100644 --- a/thirdparty/openal/CMakeLists.txt +++ b/thirdparty/openal/CMakeLists.txt @@ -30,6 +30,9 @@ if(CMAKE_SYSTEM_NAME STREQUAL "iOS") endif() endif() +set(CMAKE_C_VISIBILITY_PRESET hidden) +set(CMAKE_CXX_VISIBILITY_PRESET hidden) + if(COMMAND CMAKE_POLICY) cmake_policy(SET CMP0003 NEW) cmake_policy(SET CMP0005 NEW) @@ -42,6 +45,12 @@ 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) @@ -161,8 +170,8 @@ if(NOT LIBTYPE) endif() set(LIB_MAJOR_VERSION "1") -set(LIB_MINOR_VERSION "22") -set(LIB_REVISION "2") +set(LIB_MINOR_VERSION "23") +set(LIB_REVISION "0") set(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}") set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0) @@ -170,7 +179,7 @@ set(EXPORT_DECL "") # Require C++14 -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) # Prefer C11, but support C99 and C90 too. @@ -230,7 +239,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) + 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}") @@ -258,6 +267,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() @@ -346,13 +365,6 @@ else() 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) - endif() - endif() - set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS}") endif() @@ -472,7 +484,7 @@ 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))") endif() endif() endif() @@ -1051,8 +1063,8 @@ 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_LIBRARIES}) - set(INC_PATHS ${INC_PATHS} ${PULSEAUDIO_INCLUDE_DIRS}) + add_backend_libs(${PULSEAUDIO_LIBRARY}) + set(INC_PATHS ${INC_PATHS} ${PULSEAUDIO_INCLUDE_DIR}) endif() endif() if(ALSOFT_REQUIRE_PULSEAUDIO AND NOT HAVE_PULSEAUDIO) @@ -1086,19 +1098,22 @@ 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,") + + 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}) @@ -1129,17 +1144,7 @@ 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) @@ -1168,7 +1173,7 @@ endif() # Check for SDL2 backend option(ALSOFT_REQUIRE_SDL2 "Require SDL2 backend" OFF) -find_package(SDL2) +find_package(SDL2 QUIET) if(SDL2_FOUND) # Off by default, since it adds a runtime dependency option(ALSOFT_BACKEND_SDL2 "Enable SDL2 backend" OFF) @@ -1176,9 +1181,10 @@ 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) endif() +else() + message(STATUS "Could NOT find SDL2") endif() if(ALSOFT_REQUIRE_SDL2 AND NOT SDL2_FOUND) message(FATAL_ERROR "Failed to enabled required SDL2 backend") @@ -1199,13 +1205,19 @@ set(BACKENDS "${BACKENDS} Null") find_package(Git) if(ALSOFT_UPDATE_BUILD_VERSION AND GIT_FOUND AND EXISTS "${OpenAL_SOURCE_DIR}/.git") # Get the current working branch and its latest abbreviated commit hash - add_custom_target(build_version - ${CMAKE_COMMAND} -D GIT_EXECUTABLE=${GIT_EXECUTABLE} -D LIB_VERSION=${LIB_VERSION} + 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 "${OpenAL_SOURCE_DIR}/.git/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") @@ -1219,11 +1231,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 @@ -1231,7 +1243,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() @@ -1246,7 +1258,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() @@ -1271,9 +1282,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() @@ -1352,7 +1366,8 @@ 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) @@ -1364,7 +1379,7 @@ else() # 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) @@ -1385,8 +1400,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" @@ -1438,6 +1453,11 @@ if(WIN32 AND MINGW AND ALSOFT_BUILD_IMPORT_LIB AND NOT LIBTYPE STREQUAL "STATIC" 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 ) @@ -1668,9 +1688,8 @@ 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 ex-common ${MATH_LIB}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alloopback) @@ -1681,33 +1700,32 @@ 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} ex-common) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alffplay) diff --git a/thirdparty/openal/ChangeLog b/thirdparty/openal/ChangeLog index 51f409ff30..da6698f88e 100644 --- a/thirdparty/openal/ChangeLog +++ b/thirdparty/openal/ChangeLog @@ -1,3 +1,55 @@ +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. diff --git a/thirdparty/openal/README.md b/thirdparty/openal/README.md index 0c9d30276c..50b7bfb491 100644 --- a/thirdparty/openal/README.md +++ b/thirdparty/openal/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 diff --git a/thirdparty/openal/al/auxeffectslot.cpp b/thirdparty/openal/al/auxeffectslot.cpp index d37168e752..d777bc1f1c 100644 --- a/thirdparty/openal/al/auxeffectslot.cpp +++ b/thirdparty/openal/al/auxeffectslot.cpp @@ -50,11 +50,6 @@ #include "effect.h" #include "opthelpers.h" -#ifdef ALSOFT_EAX -#include "eax/exception.h" -#include "eax/utils.h" -#endif // ALSOFT_EAX - namespace { struct FactoryItem { @@ -95,10 +90,10 @@ inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; - if UNLIKELY(lidx >= context->mEffectSlotList.size()) + 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; } @@ -108,10 +103,10 @@ inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) noexcept const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; - if UNLIKELY(lidx >= device->EffectList.size()) + 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; } @@ -121,10 +116,10 @@ inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) noexcept const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; - if UNLIKELY(lidx >= device->BufferList.size()) + 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; } @@ -148,7 +143,7 @@ void AddActiveEffectSlots(const al::span auxslots, ALCcontext *co */ EffectSlotArray *newarray = EffectSlot::CreatePtrArray(newcount); auto slotiter = std::transform(auxslots.begin(), auxslots.end(), newarray->begin(), - [](ALeffectslot *auxslot) noexcept { return &auxslot->mSlot; }); + [](ALeffectslot *auxslot) noexcept { return auxslot->mSlot; }); std::copy(curarray->begin(), curarray->end(), slotiter); /* Remove any duplicates (first instance of each will be kept). */ @@ -164,7 +159,7 @@ 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; newarray = EffectSlot::CreatePtrArray(newcount); @@ -196,13 +191,13 @@ void RemoveActiveEffectSlots(const al::span auxslots, ALCcontext 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; newarray = EffectSlot::CreatePtrArray(newsize); @@ -256,7 +251,7 @@ bool EnsureEffectSlots(ALCcontext *context, size_t needed) while(needed > count) { - if UNLIKELY(context->mEffectSlotList.size() >= 1<<25) + if(context->mEffectSlotList.size() >= 1<<25) [[unlikely]] return false; context->mEffectSlotList.emplace_back(); @@ -264,7 +259,7 @@ bool EnsureEffectSlots(ALCcontext *context, size_t needed) sublist->FreeMask = ~0_u64; sublist->EffectSlots = static_cast( al_calloc(alignof(ALeffectslot), sizeof(ALeffectslot)*64)); - if UNLIKELY(!sublist->EffectSlots) + if(!sublist->EffectSlots) [[unlikely]] { context->mEffectSlotList.pop_back(); return false; @@ -283,10 +278,10 @@ 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(sublist->EffectSlots + 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; @@ -325,13 +320,13 @@ AL_API void AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; - if UNLIKELY(n < 0) + if(n < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Generating %d effect slots", n); - if UNLIKELY(n <= 0) return; + if(n <= 0) [[unlikely]] return; - std::unique_lock slotlock{context->mEffectSlotLock}; + std::lock_guard _{context->mEffectSlotLock}; ALCdevice *device{context->mALDevice.get()}; if(static_cast(n) > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots) { @@ -349,7 +344,6 @@ START_API_FUNC if(n == 1) { ALeffectslot *slot{AllocEffectSlot(context.get())}; - if(!slot) return; effectslots[0] = slot->id; } else @@ -359,12 +353,6 @@ START_API_FUNC 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); @@ -376,22 +364,22 @@ AL_API void AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *ef START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; - if UNLIKELY(n < 0) + if(n < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Deleting %d effect slots", n); - if UNLIKELY(n <= 0) return; + if(n <= 0) [[unlikely]] return; std::lock_guard _{context->mEffectSlotLock}; if(n == 1) { ALeffectslot *slot{LookupEffectSlot(context.get(), effectslots[0])}; - if UNLIKELY(!slot) + if(!slot) [[unlikely]] { context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslots[0]); return; } - if UNLIKELY(ReadRef(slot->ref) != 0) + if(ReadRef(slot->ref) != 0) [[unlikely]] { context->setError(AL_INVALID_OPERATION, "Deleting in-use effect slot %u", effectslots[0]); @@ -406,12 +394,12 @@ START_API_FUNC for(size_t i{0};i < slots.size();++i) { ALeffectslot *slot{LookupEffectSlot(context.get(), effectslots[i])}; - if UNLIKELY(!slot) + if(!slot) [[unlikely]] { context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslots[i]); return; } - if UNLIKELY(ReadRef(slot->ref) != 0) + if(ReadRef(slot->ref) != 0) [[unlikely]] { context->setError(AL_INVALID_OPERATION, "Deleting in-use effect slot %u", effectslots[i]); @@ -440,7 +428,7 @@ AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot) START_API_FUNC { ContextRef context{GetContextRef()}; - if LIKELY(context) + if(context) [[likely]] { std::lock_guard _{context->mEffectSlotLock}; if(LookupEffectSlot(context.get(), effectslot) != nullptr) @@ -455,11 +443,11 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{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; @@ -479,18 +467,18 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint * START_API_FUNC { 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) + if(!slot) [[unlikely]] { context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotids[i]); return; @@ -514,11 +502,11 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{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; @@ -533,18 +521,18 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint * START_API_FUNC { 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) + if(!slot) [[unlikely]] { context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotids[i]); return; @@ -564,13 +552,13 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; 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); + if(!slot) [[unlikely]] + return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); ALeffectslot *target{}; ALCdevice *device{}; @@ -588,16 +576,16 @@ START_API_FUNC else { if(value != 0) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect ID %u", value); + return context->setError(AL_INVALID_VALUE, "Invalid effect ID %u", value); err = slot->initEffect(AL_EFFECT_NULL, EffectProps{}, context.get()); } } - if UNLIKELY(err != AL_NO_ERROR) + if(err != AL_NO_ERROR) [[unlikely]] { context->setError(err, "Effect initialization failed"); return; } - if UNLIKELY(slot->mState == SlotState::Initial) + if(slot->mState == SlotState::Initial) [[unlikely]] { slot->mPropsDirty = false; slot->updateProps(context.get()); @@ -610,9 +598,9 @@ START_API_FUNC case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: if(!(value == AL_TRUE || value == AL_FALSE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, + return context->setError(AL_INVALID_VALUE, "Effect slot auxiliary send auto out of range"); - if UNLIKELY(slot->AuxSendAuto == !!value) + if(slot->AuxSendAuto == !!value) [[unlikely]] return; slot->AuxSendAuto = !!value; break; @@ -620,8 +608,8 @@ START_API_FUNC case AL_EFFECTSLOT_TARGET_SOFT: target = LookupEffectSlot(context.get(), static_cast(value)); if(value && !target) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect slot target ID"); - if UNLIKELY(slot->Target == target) + return context->setError(AL_INVALID_VALUE, "Invalid effect slot target ID"); + if(slot->Target == target) [[unlikely]] return; if(target) { @@ -629,7 +617,7 @@ START_API_FUNC while(checker && checker != slot) checker = checker->Target; if(checker) - SETERR_RETURN(context, AL_INVALID_OPERATION,, + return context->setError(AL_INVALID_OPERATION, "Setting target of effect slot ID %u to %u creates circular chain", slot->id, target->id); } @@ -654,15 +642,15 @@ START_API_FUNC device = context->mALDevice.get(); if(slot->mState == SlotState::Playing) - SETERR_RETURN(context, AL_INVALID_OPERATION,, + return context->setError(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; { @@ -671,9 +659,9 @@ START_API_FUNC if(value) { buffer = LookupBuffer(device, static_cast(value)); - if(!buffer) SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid buffer ID"); + if(!buffer) return context->setError(AL_INVALID_VALUE, "Invalid buffer ID"); if(buffer->mCallback) - SETERR_RETURN(context, AL_INVALID_OPERATION,, + return context->setError(AL_INVALID_OPERATION, "Callback buffer not valid for effects"); IncrementRef(buffer->ref); @@ -690,10 +678,10 @@ START_API_FUNC break; case AL_EFFECTSLOT_STATE_SOFT: - SETERR_RETURN(context, AL_INVALID_OPERATION,, "AL_EFFECTSLOT_STATE_SOFT is read-only"); + return context->setError(AL_INVALID_OPERATION, "AL_EFFECTSLOT_STATE_SOFT is read-only"); default: - SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid effect slot integer property 0x%04x", + return context->setError(AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param); } UpdateProps(slot, context.get()); @@ -715,17 +703,17 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] 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); + if(!slot) [[unlikely]] + return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); switch(param) { default: - SETERR_RETURN(context, AL_INVALID_ENUM,, + return context->setError(AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x", param); } } @@ -735,26 +723,26 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; 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); + if(!slot) [[unlikely]] + return context->setError(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 context->setError(AL_INVALID_VALUE, "Effect slot gain out of range"); + if(slot->Gain == value) [[unlikely]] return; slot->Gain = value; break; default: - SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid effect slot float property 0x%04x", + return context->setError(AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param); } UpdateProps(slot, context.get()); @@ -772,17 +760,17 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] 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); + if(!slot) [[unlikely]] + return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); switch(param) { default: - SETERR_RETURN(context, AL_INVALID_ENUM,, + return context->setError(AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x", param); } } @@ -793,12 +781,12 @@ AL_API void AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum para START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] 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); + if(!slot) [[unlikely]] + return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); switch(param) { @@ -845,12 +833,12 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] 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); + if(!slot) [[unlikely]] + return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); switch(param) { @@ -865,12 +853,12 @@ AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum para START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] 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); + if(!slot) [[unlikely]] + return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); switch(param) { @@ -895,12 +883,12 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] 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); + if(!slot) [[unlikely]] + return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); switch(param) { @@ -912,14 +900,17 @@ START_API_FUNC 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,16 +922,15 @@ ALeffectslot::~ALeffectslot() DecrementRef(Buffer->ref); Buffer = nullptr; - EffectSlotProps *props{mSlot.Update.exchange(nullptr)}; - if(props) + if(EffectSlotProps *props{mSlot->Update.exchange(nullptr)}) { TRACE("Freed unapplied AuxiliaryEffectSlot update %p\n", decltype(std::declval()){props}); delete props; } - if(mSlot.mEffectState) - mSlot.mEffectState->release(); + mSlot->mEffectState = nullptr; + mSlot->InUse = false; } ALenum ALeffectslot::initEffect(ALenum effectType, const EffectProps &effectProps, @@ -1002,14 +992,14 @@ void ALeffectslot::updateProps(ALCcontext *context) /* 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 @@ -1057,667 +1047,521 @@ EffectSlotSubList::~EffectSlotSubList() } #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( - const EaxCall& call, - 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."); - } + mPropsDirty = true; + eax_al_context_ = &al_context; eax_fx_slot_index_ = index; - - eax_initialize_eax(); - eax_initialize_lock(); - eax_initialize_effects(call); + eax_version_ = eax_al_context_->eax_get_version(); + eax_fx_slot_set_defaults(); } -const EAX50FXSLOTPROPERTIES& ALeffectslot::eax_get_eax_fx_slot() const noexcept +void ALeffectslot::eax_commit() { - return eax_eax_fx_slot_; -} + auto df = EaxDirtyFlags{}; -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) + switch(eax_version_) { - eax_fail("Unsupported EAX effect GUID."); + 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; + default: + eax_fail_unknown_version(); } -} -void ALeffectslot::eax_validate_fx_slot_volume(long eax_volume) -{ - eax_validate_range( - "Volume", - eax_volume, - EAXFXSLOT_MINVOLUME, - EAXFXSLOT_MAXVOLUME); -} + if(df == EaxDirtyFlags{}) { + if(eax_effect_ != nullptr && eax_effect_->commit()) + eax_set_efx_slot_effect(*eax_effect_); -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(const EaxCall& call, unsigned long eax_flags) -{ - eax_validate_range( - "Flags", - eax_flags, - 0UL, - ~(call.get_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 EaxCall& call, const EAX40FXSLOTPROPERTIES& fx_slot) -{ - 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(call, fx_slot.ulFlags); -} - -void ALeffectslot::eax_validate_fx_slot_all(const EaxCall& call, const EAX50FXSLOTPROPERTIES& fx_slot) -{ - eax_validate_fx_slot_all(call, static_cast(fx_slot)); - 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 EaxCall& call, const GUID& eax_effect_id) -{ - if (eax_eax_fx_slot_.guidLoadEffect == eax_effect_id) - { return; } - eax_eax_fx_slot_.guidLoadEffect = eax_effect_id; - - eax_set_fx_slot_effect(call); -} - -void ALeffectslot::eax_set_fx_slot_volume( - long eax_volume) -{ - if (eax_eax_fx_slot_.lVolume == eax_volume) - { - return; + if((df & eax_load_effect_dirty_bit) != EaxDirtyFlags{}) + eax_fx_slot_load_effect(); + else { + if(eax_effect_ != nullptr && eax_effect_->commit()) + eax_set_efx_slot_effect(*eax_effect_); } - eax_eax_fx_slot_.lVolume = eax_volume; + if((df & eax_volume_dirty_bit) != EaxDirtyFlags{}) + eax_fx_slot_set_volume(); - eax_set_fx_slot_volume(); + if((df & eax_flags_dirty_bit) != EaxDirtyFlags{}) + eax_fx_slot_set_flags(); } -void ALeffectslot::eax_set_fx_slot_lock( - long eax_lock) +[[noreturn]] void ALeffectslot::eax_fail(const char* message) { - if (eax_eax_fx_slot_.lLock == eax_lock) + 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_.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 EaxCall& call, const EAX40FXSLOTPROPERTIES& eax_fx_slot) -{ - eax_set_fx_slot_effect(call, 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 EaxCall& call, const EAX50FXSLOTPROPERTIES& eax_fx_slot) -{ - eax_set_fx_slot_all(call, 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::eax4_fx_slot_set_current_defaults(const Eax4Props& props) noexcept { - eax_is_locked_ = (eax_fx_slot_index_ < 2); + static_cast(eax_) = props; + eax_.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION; + eax_.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO; } -void ALeffectslot::eax_initialize_effects(const EaxCall& call) +void ALeffectslot::eax5_fx_slot_set_current_defaults(const Eax5Props& props) noexcept { - eax_set_fx_slot_effect(call); + eax_ = props; } -void ALeffectslot::eax_get_fx_slot_all(const EaxCall& call) const +void ALeffectslot::eax_fx_slot_set_current_defaults() { - switch (call.get_version()) + switch(eax_version_) { - case 4: - call.set_value(eax_eax_fx_slot_); - break; + case 1: + case 2: + case 3: + eax5_fx_slot_set_current_defaults(eax123_.i); + break; + case 4: + eax4_fx_slot_set_current_defaults(eax4_.i); + break; + case 5: + eax5_fx_slot_set_current_defaults(eax5_.i); + break; + default: + eax_fail_unknown_version(); + } - case 5: - call.set_value(eax_eax_fx_slot_); - break; + eax_df_ = ~EaxDirtyFlags{}; +} - default: - eax_fail("Unsupported EAX version."); +void ALeffectslot::eax_fx_slot_set_defaults() +{ + eax5_fx_slot_set_defaults(eax123_.i); + eax4_fx_slot_set_defaults(eax4_.i); + eax5_fx_slot_set_defaults(eax5_.i); + eax_fx_slot_set_current_defaults(); +} + +void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) const +{ + switch(call.get_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; + default: + eax_fail_unknown_property_id(); } } -void ALeffectslot::eax_get_fx_slot(const EaxCall& call) const +void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) const { - switch (call.get_property_id()) + switch(call.get_property_id()) { - case EAXFXSLOT_ALLPARAMETERS: - eax_get_fx_slot_all(call); - break; - - case EAXFXSLOT_LOADEFFECT: - call.set_value(eax_eax_fx_slot_.guidLoadEffect); - break; - - case EAXFXSLOT_VOLUME: - call.set_value(eax_eax_fx_slot_.lVolume); - break; - - case EAXFXSLOT_LOCK: - call.set_value(eax_eax_fx_slot_.lLock); - break; - - case EAXFXSLOT_FLAGS: - call.set_value(eax_eax_fx_slot_.ulFlags); - break; - - case EAXFXSLOT_OCCLUSION: - call.set_value(eax_eax_fx_slot_.lOcclusion); - break; - - case EAXFXSLOT_OCCLUSIONLFRATIO: - 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(); + } +} + +void ALeffectslot::eax_fx_slot_get(const EaxCall& call) const +{ + switch(call.get_version()) + { + 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(); } } -// [[nodiscard]] bool ALeffectslot::eax_get(const EaxCall& call) -{ - switch (call.get_property_set_id()) - { - case EaxCallPropertySetId::fx_slot: - eax_get_fx_slot(call); - break; - - case EaxCallPropertySetId::fx_slot_effect: - eax_dispatch_effect(call); - break; - - default: - eax_fail("Unsupported property id."); - } - - return false; -} - -void ALeffectslot::eax_set_fx_slot_effect(const EaxCall& call, ALenum al_effect_type) -{ - if(!IsValidEffectType(al_effect_type)) - eax_fail("Unsupported effect."); - - eax_effect_ = nullptr; - eax_effect_ = eax_create_eax_effect(al_effect_type, call); - - eax_set_effect_slot_effect(*eax_effect_); -} - -void ALeffectslot::eax_set_fx_slot_effect(const EaxCall& call) -{ - 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(call, al_effect_type); -} - -void ALeffectslot::eax_set_efx_effect_slot_gain() -{ - 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); -} - -void ALeffectslot::eax_set_fx_slot_volume() -{ - eax_set_efx_effect_slot_gain(); -} - -void ALeffectslot::eax_set_effect_slot_send_auto() -{ - eax_set_effect_slot_send_auto((eax_eax_fx_slot_.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0); -} - -void ALeffectslot::eax_set_fx_slot_flags() -{ - eax_set_effect_slot_send_auto(); -} - -void ALeffectslot::eax_defer_fx_slot_effect(const EaxCall& call) -{ - const auto& eax_effect_id = - call.get_value(); - - eax_validate_fx_slot_effect(eax_effect_id); - eax_set_fx_slot_effect(call, eax_effect_id); -} - -void ALeffectslot::eax_defer_fx_slot_volume(const EaxCall& call) -{ - const auto& eax_volume = - call.get_value(); - - eax_validate_fx_slot_volume(eax_volume); - eax_set_fx_slot_volume(eax_volume); -} - -void ALeffectslot::eax_defer_fx_slot_lock(const EaxCall& call) -{ - const auto& eax_lock = - call.get_value(); - - eax_validate_fx_slot_lock(eax_lock); - eax_set_fx_slot_lock(eax_lock); -} - -void ALeffectslot::eax_defer_fx_slot_flags(const EaxCall& call) -{ - const auto& eax_flags = - call.get_value(); - - eax_validate_fx_slot_flags(call, eax_flags); - eax_set_fx_slot_flags(eax_flags); -} - -// [[nodiscard]] -bool ALeffectslot::eax_defer_fx_slot_occlusion(const EaxCall& call) -{ - const auto& eax_occlusion = - call.get_value(); - - eax_validate_fx_slot_occlusion(eax_occlusion); - - return eax_set_fx_slot_occlusion(eax_occlusion); -} - -// [[nodiscard]] -bool ALeffectslot::eax_defer_fx_slot_occlusion_lf_ratio(const EaxCall& call) -{ - const auto& eax_occlusion_lf_ratio = - 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_defer_fx_slot_all(const EaxCall& call) -{ - switch (call.get_version()) - { - case 4: - { - const auto& eax_all = - call.get_value(); - - eax_validate_fx_slot_all(call, eax_all); - eax_set_fx_slot_all(call, eax_all); - - return false; - } - - case 5: - { - const auto& eax_all = - call.get_value(); - - eax_validate_fx_slot_all(call, eax_all); - return eax_set_fx_slot_all(call, eax_all); - } - - default: - eax_fail("Unsupported EAX version."); - } -} - -bool ALeffectslot::eax_set_fx_slot(const EaxCall& call) -{ - switch (call.get_property_id()) - { - case EAXFXSLOT_NONE: - return false; - - case EAXFXSLOT_ALLPARAMETERS: - return eax_defer_fx_slot_all(call); - - case EAXFXSLOT_LOADEFFECT: - eax_defer_fx_slot_effect(call); - return false; - - case EAXFXSLOT_VOLUME: - eax_defer_fx_slot_volume(call); - return false; - - case EAXFXSLOT_LOCK: - eax_defer_fx_slot_lock(call); - return false; - - case EAXFXSLOT_FLAGS: - eax_defer_fx_slot_flags(call); - return false; - - case EAXFXSLOT_OCCLUSION: - return eax_defer_fx_slot_occlusion(call); - - case EAXFXSLOT_OCCLUSIONLFRATIO: - return eax_defer_fx_slot_occlusion_lf_ratio(call); - - - default: - eax_fail("Unsupported FX slot property id."); - } -} - -// [[nodiscard]] -bool ALeffectslot::eax_set(const EaxCall& call) { switch(call.get_property_set_id()) { - case EaxCallPropertySetId::fx_slot: - return eax_set_fx_slot(call); - - case EaxCallPropertySetId::fx_slot_effect: - eax_dispatch_effect(call); - break; - - default: - eax_fail("Unsupported property id."); + case EaxCallPropertySetId::fx_slot: + eax_fx_slot_get(call); + break; + case EaxCallPropertySetId::fx_slot_effect: + eax_dispatch_effect(call); + break; + default: + eax_fail_unknown_property_id(); } return false; } -void ALeffectslot::eax_dispatch_effect(const EaxCall& call) -{ if(eax_effect_) eax_effect_->dispatch(call); } - -void ALeffectslot::eax_apply_deferred() +void ALeffectslot::eax_fx_slot_load_effect() { - /* The other FXSlot properties (volume, effect, etc) aren't deferred? */ + eax_effect_ = nullptr; + const auto efx_effect_type = eax_get_efx_effect_type(eax_.guidLoadEffect); - auto is_changed = false; - if(eax_effect_) - is_changed = eax_effect_->commit(); - if(is_changed) - eax_set_effect_slot_effect(*eax_effect_); + if(!IsValidEffectType(efx_effect_type)) + eax_fail("Invalid effect type."); + + eax_effect_ = eax_create_eax_effect(efx_effect_type, eax_version_); + eax_set_efx_slot_effect(*eax_effect_); } +void ALeffectslot::eax_fx_slot_set_volume() +{ + const auto volume = 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_effect_slot_effect(EaxEffect &effect) +void ALeffectslot::eax_fx_slot_set_environment_flag() +{ + eax_set_efx_slot_send_auto((eax_.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0u); +} + +void ALeffectslot::eax_fx_slot_set_flags() +{ + eax_fx_slot_set_environment_flag(); +} + +void ALeffectslot::eax4_fx_slot_set_all(const EaxCall& call) +{ + 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::eax5_fx_slot_set_all(const EaxCall& call) +{ + 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; +} + +bool ALeffectslot::eax_fx_slot_should_update_sources() const noexcept +{ + const auto dirty_bits = + eax_occlusion_dirty_bit | + eax_occlusion_lf_ratio_dirty_bit | + eax_flags_dirty_bit; + + if((eax_df_ & dirty_bits) != EaxDirtyFlags{}) + return true; + + return false; +} + +// Returns `true` if all sources should be updated, or `false` otherwise. +bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call) +{ + auto& dst = eax4_.i; + + switch(call.get_property_id()) + { + case EAXFXSLOT_NONE: + break; + case EAXFXSLOT_ALLPARAMETERS: + eax4_fx_slot_set_all(call); + break; + case EAXFXSLOT_LOADEFFECT: + eax4_fx_slot_ensure_unlocked(); + eax_fx_slot_set_dirty(call, dst.guidLoadEffect, eax_df_); + 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(); + } + + return eax_fx_slot_should_update_sources(); +} + +// Returns `true` if all sources should be updated, or `false` otherwise. +bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call) +{ + auto& dst = eax5_.i; + + switch(call.get_property_id()) + { + case EAXFXSLOT_NONE: + break; + case EAXFXSLOT_ALLPARAMETERS: + eax5_fx_slot_set_all(call); + break; + case EAXFXSLOT_LOADEFFECT: + eax_fx_slot_set_dirty(call, dst.guidLoadEffect, eax_df_); + 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(); + } + + return eax_fx_slot_should_update_sources(); +} + +// 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(); + } +} + +// Returns `true` if all sources should be updated, or `false` otherwise. +bool ALeffectslot::eax_set(const EaxCall& call) +{ + const auto version = call.get_version(); + + if(eax_version_ != version) + eax_df_ = ~EaxDirtyFlags{}; + eax_version_ = version; + + switch(call.get_property_set_id()) + { + case EaxCallPropertySetId::fx_slot: return eax_fx_slot_set(call); + case EaxCallPropertySetId::fx_slot_effect: eax_dispatch_effect(call); return false; + default: eax_fail_unknown_property_id(); + } +} + +void ALeffectslot::eax4_fx_slot_commit(EaxDirtyFlags& dst_df) +{ + if(eax_df_ == EaxDirtyFlags{}) + return; + + 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); + + auto& dst_i = eax_; + + if(dst_i.lOcclusion != EAXFXSLOT_DEFAULTOCCLUSION) { + dst_df |= eax_occlusion_dirty_bit; + dst_i.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION; + } + + if(dst_i.flOcclusionLFRatio != EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO) { + dst_df |= eax_occlusion_lf_ratio_dirty_bit; + dst_i.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO; + } + + eax_df_ = EaxDirtyFlags{}; +} + +void ALeffectslot::eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df) +{ + if(eax_df_ == EaxDirtyFlags{}) + return; + + 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); + eax_df_ = EaxDirtyFlags{}; +} + +void ALeffectslot::eax_dispatch_effect(const EaxCall& call) +{ + if(eax_effect_ != nullptr) + eax_effect_->dispatch(call); +} + +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) - { + + 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] " @@ -1727,68 +1571,51 @@ void ALeffectslot::eax_set_effect_slot_gain( ERR(EAX_PREFIX "Gain out of range (%f)\n", gain); Gain = clampf(gain, 0.0f, 1.0f); - UpdateProps(this, eax_al_context_); + 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}; - 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}; - if (ReadRef(effect_slot.ref) != 0) - { + if(ReadRef(effect_slot.ref) != 0) { ERR(EAX_PREFIX "Deleting in-use effect slot %u.\n", effect_slot.id); return; } auto effect_slot_ptr = &effect_slot; - RemoveActiveEffectSlots({&effect_slot_ptr, 1}, &context); FreeEffectSlot(&context, &effect_slot); diff --git a/thirdparty/openal/al/auxeffectslot.h b/thirdparty/openal/al/auxeffectslot.h index 8371137212..9b6403f410 100644 --- a/thirdparty/openal/al/auxeffectslot.h +++ b/thirdparty/openal/al/auxeffectslot.h @@ -20,13 +20,23 @@ #include #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, @@ -53,12 +63,12 @@ struct ALeffectslot { RefCount 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(); @@ -72,169 +82,289 @@ struct ALeffectslot { #ifdef ALSOFT_EAX public: - void eax_initialize( - const EaxCall& call, - ALCcontext& al_context, - EaxFxSlotIndexValue index); + void eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index); - const EAX50FXSLOTPROPERTIES& eax_get_eax_fx_slot() const noexcept; + EaxFxSlotIndexValue eax_get_index() const noexcept { return eax_fx_slot_index_; } + const EAX50FXSLOTPROPERTIES& eax_get_eax_fx_slot() const noexcept + { return eax_; } - - // [[nodiscard]] + // Returns `true` if all sources should be updated, or `false` otherwise. bool eax_dispatch(const EaxCall& call) { return call.is_get() ? eax_get(call) : eax_set(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; + constexpr bool eax4_fx_slot_is_legacy() const noexcept + { return eax_fx_slot_index_ < 2; } + + void eax4_fx_slot_ensure_unlocked() const; + + static ALenum eax_get_efx_effect_type(const GUID& guid); + const GUID& eax_get_eax_default_effect_guid() const noexcept; long eax_get_eax_default_lock() const noexcept; - void eax_set_eax_fx_slot_defaults(); + 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(); - void eax_initialize_eax(); - - void eax_initialize_lock(); - - - void eax_initialize_effects(const EaxCall& call); - - - void eax_get_fx_slot_all(const EaxCall& call) const; - - void eax_get_fx_slot(const EaxCall& call) const; - - // [[nodiscard]] + void eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) const; + void eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) const; + 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_fx_slot_load_effect(); + void eax_fx_slot_set_volume(); + void eax_fx_slot_set_environment_flag(); + void eax_fx_slot_set_flags(); - void eax_set_fx_slot_effect(const EaxCall& call, ALenum effect_type); + void eax4_fx_slot_set_all(const EaxCall& call); + void eax5_fx_slot_set_all(const EaxCall& call); - void eax_set_fx_slot_effect(const EaxCall& call); + bool eax_fx_slot_should_update_sources() const noexcept; - - 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(const EaxCall& call, unsigned long eax_flags); - 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 EaxCall& call, const EAX40FXSLOTPROPERTIES& fx_slot); - void eax_validate_fx_slot_all(const EaxCall& call, const EAX50FXSLOTPROPERTIES& fx_slot); - - void eax_set_fx_slot_effect(const EaxCall& call, 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 EaxCall& call, const EAX40FXSLOTPROPERTIES& eax_fx_slot); - - // [[nodiscard]] - bool eax_set_fx_slot_all(const EaxCall& call, const EAX50FXSLOTPROPERTIES& eax_fx_slot); - - - void eax_defer_fx_slot_effect(const EaxCall& call); - - void eax_defer_fx_slot_volume(const EaxCall& call); - - void eax_defer_fx_slot_lock(const EaxCall& call); - - void eax_defer_fx_slot_flags(const EaxCall& call); - - // [[nodiscard]] - bool eax_defer_fx_slot_occlusion(const EaxCall& call); - - // [[nodiscard]] - bool eax_defer_fx_slot_occlusion_lf_ratio(const EaxCall& call); - - // [[nodiscard]] - bool eax_defer_fx_slot_all(const EaxCall& call); - - bool eax_set_fx_slot(const EaxCall& call); - - void eax_apply_deferred(); - - // [[nodiscard]] + // 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); + 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 eax4_fx_slot_commit(EaxDirtyFlags& dst_df); + void eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df); void eax_dispatch_effect(const EaxCall& call); - // `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 #endif diff --git a/thirdparty/openal/al/buffer.cpp b/thirdparty/openal/al/buffer.cpp index 9b455b9c7b..6bf3ecbcfa 100644 --- a/thirdparty/openal/al/buffer.cpp +++ b/thirdparty/openal/al/buffer.cpp @@ -288,8 +288,8 @@ al::optional AmbiLayoutFromEnum(ALenum layout) { 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; } @@ -307,9 +307,9 @@ al::optional AmbiScalingFromEnum(ALenum scale) { 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; } @@ -329,18 +329,18 @@ 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); + case UserFmtMono: return FmtMono; + case UserFmtStereo: return FmtStereo; + case UserFmtRear: return FmtRear; + case UserFmtQuad: return FmtQuad; + case UserFmtX51: return FmtX51; + case UserFmtX61: return FmtX61; + case UserFmtX71: return FmtX71; + case UserFmtBFormat2D: return FmtBFormat2D; + case UserFmtBFormat3D: return FmtBFormat3D; + case UserFmtUHJ2: return FmtUHJ2; + case UserFmtUHJ3: return FmtUHJ3; + case UserFmtUHJ4: return FmtUHJ4; } return al::nullopt; } @@ -348,12 +348,12 @@ 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); + case UserFmtUByte: return FmtUByte; + case UserFmtShort: return FmtShort; + case UserFmtFloat: return FmtFloat; + case UserFmtDouble: return FmtDouble; + case UserFmtMulaw: return FmtMulaw; + case UserFmtAlaw: return FmtAlaw; /* ADPCM not handled here. */ case UserFmtIMA4: break; case UserFmtMSADPCM: break; @@ -411,14 +411,14 @@ bool EnsureBuffers(ALCdevice *device, size_t needed) 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) + if(!sublist->Buffers) [[unlikely]] { device->BufferList.pop_back(); return false; @@ -467,10 +467,10 @@ inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; - if UNLIKELY(lidx >= device->BufferList.size()) + 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; } @@ -531,14 +531,14 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, UserFmtChannels SrcChannels, UserFmtType SrcType, const al::byte *SrcData, ALbitfieldSOFT access) { - if UNLIKELY(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) - SETERR_RETURN(context, AL_INVALID_OPERATION,, "Modifying storage for in-use buffer %u", - ALBuf->id); + if(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) [[unlikely]] + return context->setError(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"); + if(!DstChannels) [[unlikely]] + return context->setError(AL_INVALID_ENUM, "Invalid format"); /* IMA4 and MSADPCM convert to 16-bit short. * @@ -548,19 +548,19 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, */ if((access&MAP_READ_WRITE_FLAGS)) { - if UNLIKELY(SrcType == UserFmtIMA4 || SrcType == UserFmtMSADPCM) - SETERR_RETURN(context, AL_INVALID_VALUE,, "%s samples cannot be mapped", + if(SrcType == UserFmtIMA4 || SrcType == UserFmtMSADPCM) [[unlikely]] + return context->setError(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(!DstType) [[unlikely]] + return context->setError(AL_INVALID_ENUM, "Invalid format"); 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", + if(align < 1) [[unlikely]] + return context->setError(AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", unpackalign, NameFromUserFmtType(SrcType)); const ALuint ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder : @@ -569,12 +569,12 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, if((access&AL_PRESERVE_DATA_BIT_SOFT)) { /* Can only preserve data with the same format and alignment. */ - if 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->mAmbiOrder != ambiorder) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched order"); + if(ALBuf->mChannels != *DstChannels || ALBuf->OriginalType != SrcType) [[unlikely]] + return context->setError(AL_INVALID_VALUE, "Preserving data of mismatched format"); + if(ALBuf->OriginalAlign != align) [[unlikely]] + return context->setError(AL_INVALID_VALUE, "Preserving data of mismatched alignment"); + if(ALBuf->mAmbiOrder != ambiorder) [[unlikely]] + return context->setError(AL_INVALID_VALUE, "Preserving data of mismatched order"); } /* Convert the input/source size in bytes to sample frames using the unpack @@ -584,13 +584,13 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, ((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,, + if((size%SrcByteAlign) != 0) [[unlikely]] + return context->setError(AL_INVALID_VALUE, "Data size %d is not a multiple of frame size %d (%d unpack alignment)", size, SrcByteAlign, align); - if UNLIKELY(size/SrcByteAlign > std::numeric_limits::max()/align) - SETERR_RETURN(context, AL_OUT_OF_MEMORY,, + if(size/SrcByteAlign > std::numeric_limits::max()/align) [[unlikely]] + return context->setError(AL_OUT_OF_MEMORY, "Buffer size overflow, %d blocks x %d samples per block", size/SrcByteAlign, align); const ALuint frames{size / SrcByteAlign * align}; @@ -599,8 +599,8 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, */ 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,, + if(frames > std::numeric_limits::max()/FrameSize) [[unlikely]] + return context->setError(AL_OUT_OF_MEMORY, "Buffer size overflow, %d frames x %d bytes per frame", frames, FrameSize); size_t newsize{static_cast(frames) * FrameSize}; @@ -609,7 +609,7 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, { ALCdevice &device = *context->mALDevice; if(!eax_x_ram_check_availability(device, *ALBuf, size)) - SETERR_RETURN(context, AL_OUT_OF_MEMORY,, + return context->setError(AL_OUT_OF_MEMORY, "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, size); } #endif @@ -631,6 +631,9 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, } newdata.swap(ALBuf->mData); } +#ifdef ALSOFT_EAX + eax_x_ram_clear(*context->mALDevice, *ALBuf); +#endif if(SrcType == UserFmtIMA4) { @@ -683,19 +686,19 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, UserFmtChannels SrcChannels, UserFmtType SrcType, 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", + if(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) [[unlikely]] + return context->setError(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"); + if(!DstChannels) [[unlikely]] + return context->setError(AL_INVALID_ENUM, "Invalid format"); /* 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"); + if(!DstType) [[unlikely]] + return context->setError(AL_INVALID_ENUM, "Unsupported callback format"); const ALuint ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(*DstChannels) ? 1 : 0)}; @@ -820,11 +823,11 @@ AL_API void AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; - if UNLIKELY(n < 0) + if(n < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Generating %d buffers", n); - if UNLIKELY(n <= 0) return; + if(n <= 0) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; @@ -834,7 +837,7 @@ START_API_FUNC return; } - if LIKELY(n == 1) + if(n == 1) [[likely]] { /* Special handling for the easy and normal case. */ ALbuffer *buffer{AllocBuffer(device)}; @@ -860,11 +863,11 @@ AL_API void AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; - if UNLIKELY(n < 0) + if(n < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Deleting %d buffers", n); - if UNLIKELY(n <= 0) return; + if(n <= 0) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; @@ -874,12 +877,12 @@ START_API_FUNC { if(!bid) return true; ALbuffer *ALBuf{LookupBuffer(device, bid)}; - if UNLIKELY(!ALBuf) + if(!ALBuf) [[unlikely]] { context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", bid); return false; } - if UNLIKELY(ReadRef(ALBuf->ref) != 0) + if(ReadRef(ALBuf->ref) != 0) [[unlikely]] { context->setError(AL_INVALID_OPERATION, "Deleting in-use buffer %u", bid); return false; @@ -888,7 +891,7 @@ START_API_FUNC }; const ALuint *buffers_end = buffers + n; auto invbuf = std::find_if_not(buffers, buffers_end, validate_buffer); - if UNLIKELY(invbuf != buffers_end) return; + if(invbuf != buffers_end) [[unlikely]] return; /* All good. Delete non-0 buffer IDs. */ auto delete_buffer = [device](const ALuint bid) -> void @@ -904,7 +907,7 @@ AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer) START_API_FUNC { ContextRef context{GetContextRef()}; - if LIKELY(context) + if(context) [[likely]] { ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; @@ -925,28 +928,28 @@ AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) + if(!albuf) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(size < 0) + else if(size < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Negative storage size %d", size); - else if UNLIKELY(freq < 1) + else if(freq < 1) [[unlikely]] context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq); - else if UNLIKELY((flags&INVALID_STORAGE_MASK) != 0) + else if((flags&INVALID_STORAGE_MASK) != 0) [[unlikely]] 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)) + else if((flags&AL_MAP_PERSISTENT_BIT_SOFT) && !(flags&MAP_READ_WRITE_FLAGS)) [[unlikely]] context->setError(AL_INVALID_VALUE, "Declaring persistently mapped storage without read or write access"); else { auto usrfmt = DecomposeUserFormat(format); - if UNLIKELY(!usrfmt) + if(!usrfmt) [[unlikely]] context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); else { @@ -961,39 +964,40 @@ AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return nullptr; + if(!context) [[unlikely]] return nullptr; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) + if(!albuf) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY((access&INVALID_MAP_FLAGS) != 0) + else if((access&INVALID_MAP_FLAGS) != 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Invalid map flags 0x%x", access&INVALID_MAP_FLAGS); - else if UNLIKELY(!(access&MAP_READ_WRITE_FLAGS)) + else if(!(access&MAP_READ_WRITE_FLAGS)) [[unlikely]] context->setError(AL_INVALID_VALUE, "Mapping buffer %u without read or write access", buffer); else { ALbitfieldSOFT unavailable = (albuf->Access^access) & access; - if UNLIKELY(ReadRef(albuf->ref) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT)) + if(ReadRef(albuf->ref) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT)) [[unlikely]] context->setError(AL_INVALID_OPERATION, "Mapping in-use buffer %u without persistent mapping", buffer); - else if UNLIKELY(albuf->MappedAccess != 0) + else if(albuf->MappedAccess != 0) [[unlikely]] context->setError(AL_INVALID_OPERATION, "Mapping already-mapped buffer %u", buffer); - else if UNLIKELY((unavailable&AL_MAP_READ_BIT_SOFT)) + else if((unavailable&AL_MAP_READ_BIT_SOFT)) [[unlikely]] context->setError(AL_INVALID_VALUE, "Mapping buffer %u for reading without read access", buffer); - else if UNLIKELY((unavailable&AL_MAP_WRITE_BIT_SOFT)) + else if((unavailable&AL_MAP_WRITE_BIT_SOFT)) [[unlikely]] context->setError(AL_INVALID_VALUE, "Mapping buffer %u for writing without write access", buffer); - else if UNLIKELY((unavailable&AL_MAP_PERSISTENT_BIT_SOFT)) + else if((unavailable&AL_MAP_PERSISTENT_BIT_SOFT)) [[unlikely]] context->setError(AL_INVALID_VALUE, "Mapping buffer %u persistently without persistent access", buffer); - else if UNLIKELY(offset < 0 || length <= 0 + else if(offset < 0 || length <= 0 || static_cast(offset) >= albuf->OriginalSize || static_cast(length) > albuf->OriginalSize - static_cast(offset)) + [[unlikely]] context->setError(AL_INVALID_VALUE, "Mapping invalid range %d+%d for buffer %u", offset, length, buffer); else @@ -1014,15 +1018,15 @@ AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) + if(!albuf) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(albuf->MappedAccess == 0) + else if(albuf->MappedAccess == 0) [[unlikely]] context->setError(AL_INVALID_OPERATION, "Unmapping unmapped buffer %u", buffer); else { @@ -1037,20 +1041,20 @@ AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, A START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) + if(!albuf) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT)) + else if(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT)) [[unlikely]] context->setError(AL_INVALID_OPERATION, "Flushing buffer %u while not mapped for writing", buffer); - else if UNLIKELY(offset < albuf->MappedOffset || length <= 0 + else if(offset < albuf->MappedOffset || length <= 0 || offset >= albuf->MappedOffset+albuf->MappedSize - || length > albuf->MappedOffset+albuf->MappedSize-offset) + || length > albuf->MappedOffset+albuf->MappedSize-offset) [[unlikely]] context->setError(AL_INVALID_VALUE, "Flushing invalid range %d+%d on buffer %u", offset, length, buffer); else @@ -1069,20 +1073,20 @@ AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer, ALenum format, const START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) + if(!albuf) [[unlikely]] { context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); return; } auto usrfmt = DecomposeUserFormat(format); - if UNLIKELY(!usrfmt) + if(!usrfmt) [[unlikely]] { context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); return; @@ -1090,18 +1094,18 @@ START_API_FUNC ALuint unpack_align{albuf->UnpackAlign}; ALuint align{SanitizeAlignment(usrfmt->type, unpack_align)}; - if UNLIKELY(align < 1) + if(align < 1) [[unlikely]] context->setError(AL_INVALID_VALUE, "Invalid unpack alignment %u", unpack_align); - else if UNLIKELY(long{usrfmt->channels} != long{albuf->mChannels} - || usrfmt->type != albuf->OriginalType) + else if(al::to_underlying(usrfmt->channels) != al::to_underlying(albuf->mChannels) + || usrfmt->type != albuf->OriginalType) [[unlikely]] context->setError(AL_INVALID_ENUM, "Unpacking data with mismatched format"); - else if UNLIKELY(align != albuf->OriginalAlign) + else if(align != albuf->OriginalAlign) [[unlikely]] 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) + else if(albuf->isBFormat() && albuf->UnpackAmbiOrder != albuf->mAmbiOrder) [[unlikely]] context->setError(AL_INVALID_VALUE, "Unpacking data with mismatched ambisonic order"); - else if UNLIKELY(albuf->MappedAccess != 0) + else if(albuf->MappedAccess != 0) [[unlikely]] context->setError(AL_INVALID_OPERATION, "Unpacking data into mapped buffer %u", buffer); else { @@ -1113,15 +1117,16 @@ START_API_FUNC (align * frame_size) }; - if UNLIKELY(offset < 0 || length < 0 || static_cast(offset) > albuf->OriginalSize + if(offset < 0 || length < 0 || static_cast(offset) > albuf->OriginalSize || static_cast(length) > albuf->OriginalSize-static_cast(offset)) + [[unlikely]] 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) + else if((static_cast(offset)%byte_align) != 0) [[unlikely]] 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) + else if((static_cast(length)%byte_align) != 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Sub-range length %d is not a multiple of frame size %d (%d unpack alignment)", length, byte_align, align); @@ -1140,7 +1145,7 @@ START_API_FUNC static_cast(data), num_chans, samplen, align); else { - assert(long{usrfmt->type} == long{albuf->mType}); + assert(al::to_underlying(usrfmt->type) == al::to_underlying(albuf->mType)); memcpy(dst, data, size_t{samplen} * frame_size); } } @@ -1155,7 +1160,7 @@ AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint /*buffer*/, ALuint /*samplera START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; context->setError(AL_INVALID_OPERATION, "alBufferSamplesSOFT not supported"); } @@ -1166,7 +1171,7 @@ AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint /*buffer*/, ALsizei /*offs START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; context->setError(AL_INVALID_OPERATION, "alBufferSubSamplesSOFT not supported"); } @@ -1177,7 +1182,7 @@ AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint /*buffer*/, ALsizei /*offs START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; context->setError(AL_INVALID_OPERATION, "alGetBufferSamplesSOFT not supported"); } @@ -1187,7 +1192,7 @@ AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum /*format*/) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return AL_FALSE; + if(!context) [[unlikely]] return AL_FALSE; context->setError(AL_INVALID_OPERATION, "alIsBufferFormatSupportedSOFT not supported"); return AL_FALSE; @@ -1199,12 +1204,12 @@ AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat /*value*/ START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + if(LookupBuffer(device, buffer) == nullptr) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); else switch(param) { @@ -1219,12 +1224,12 @@ AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param, START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + if(LookupBuffer(device, buffer) == nullptr) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); else switch(param) { @@ -1238,14 +1243,14 @@ AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *v START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + if(LookupBuffer(device, buffer) == nullptr) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!values) + else if(!values) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { @@ -1260,52 +1265,53 @@ AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) + if(!albuf) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); else switch(param) { case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: - if UNLIKELY(value < 0) + if(value < 0) [[unlikely]] 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) + if(value < 0) [[unlikely]] 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) + if(ReadRef(albuf->ref) != 0) [[unlikely]] 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) + else if(value != AL_FUMA_SOFT && value != AL_ACN_SOFT) [[unlikely]] 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) + if(ReadRef(albuf->ref) != 0) [[unlikely]] 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) + else if(value != AL_FUMA_SOFT && value != AL_SN3D_SOFT && value != AL_N3D_SOFT) + [[unlikely]] 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) + if(value < 1 || value > 14) [[unlikely]] context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic order %d", value); else albuf->UnpackAmbiOrder = static_cast(value); @@ -1322,12 +1328,12 @@ AL_API void AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param, START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + if(LookupBuffer(device, buffer) == nullptr) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); else switch(param) { @@ -1355,24 +1361,24 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) + if(!albuf) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!values) + else if(!values) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { case AL_LOOP_POINTS_SOFT: - if UNLIKELY(ReadRef(albuf->ref) != 0) + if(ReadRef(albuf->ref) != 0) [[unlikely]] 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) + else if(values[0] < 0 || values[0] >= values[1] + || static_cast(values[1]) > albuf->mSampleLen) [[unlikely]] context->setError(AL_INVALID_VALUE, "Invalid loop point range %d -> %d on buffer %u", values[0], values[1], buffer); else @@ -1393,15 +1399,15 @@ AL_API void AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) + if(!albuf) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!value) + else if(!value) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { @@ -1415,14 +1421,14 @@ AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *valu START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + if(LookupBuffer(device, buffer) == nullptr) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!value1 || !value2 || !value3) + else if(!value1 || !value2 || !value3) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { @@ -1443,14 +1449,14 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + if(LookupBuffer(device, buffer) == nullptr) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!values) + else if(!values) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { @@ -1465,14 +1471,14 @@ AL_API void AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) + if(!albuf) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!value) + else if(!value) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { @@ -1522,13 +1528,13 @@ AL_API void AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1 START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + if(LookupBuffer(device, buffer) == nullptr) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!value1 || !value2 || !value3) + else if(!value1 || !value2 || !value3) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { @@ -1560,14 +1566,14 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) + if(!albuf) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!values) + else if(!values) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { @@ -1588,22 +1594,22 @@ AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsiz START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) + if(!albuf) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(freq < 1) + else if(freq < 1) [[unlikely]] context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq); - else if UNLIKELY(callback == nullptr) + else if(callback == nullptr) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL callback"); else { auto usrfmt = DecomposeUserFormat(format); - if UNLIKELY(!usrfmt) + if(!usrfmt) [[unlikely]] context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); else PrepareCallback(context.get(), albuf, freq, usrfmt->channels, usrfmt->type, callback, @@ -1616,14 +1622,14 @@ AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid * START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) + if(!albuf) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!value) + else if(!value) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { @@ -1644,13 +1650,13 @@ AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + if(LookupBuffer(device, buffer) == nullptr) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!value1 || !value2 || !value3) + else if(!value1 || !value2 || !value3) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { @@ -1672,13 +1678,13 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + if(LookupBuffer(device, buffer) == nullptr) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!values) + else if(!values) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { @@ -1763,7 +1769,7 @@ START_API_FUNC continue; const auto al_buffer = LookupBuffer(device, buffer); - if (!al_buffer) + if(!al_buffer) [[unlikely]] { ERR(EAX_PREFIX "Invalid buffer ID %u.\n", buffer); return ALC_FALSE; @@ -1779,7 +1785,7 @@ START_API_FUNC * 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)) + if(std::numeric_limits::max()-al_buffer->OriginalSize < total_needed) [[unlikely]] { context->setError(AL_OUT_OF_MEMORY, EAX_PREFIX "Buffer size overflow (%u + %zu)\n", al_buffer->OriginalSize, total_needed); diff --git a/thirdparty/openal/al/eax/api.cpp b/thirdparty/openal/al/eax/api.cpp index 1eb5b20dfd..f0809df13e 100644 --- a/thirdparty/openal/al/eax/api.cpp +++ b/thirdparty/openal/al/eax/api.cpp @@ -269,7 +269,8 @@ const GUID EAX_RINGMODULATOR_EFFECT = }; -const GUID EAXCONTEXT_DEFAULTPRIMARYFXSLOTID = EAXPROPERTYID_EAX40_FXSlot0; +const GUID EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID = EAXPROPERTYID_EAX40_FXSlot0; +const GUID EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID = EAXPROPERTYID_EAX50_FXSlot0; const EAX40ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID = EAX40ACTIVEFXSLOTS {{ diff --git a/thirdparty/openal/al/eax/api.h b/thirdparty/openal/al/eax/api.h index a436acb277..d254da1f7d 100644 --- a/thirdparty/openal/al/eax/api.h +++ b/thirdparty/openal/al/eax/api.h @@ -28,11 +28,14 @@ typedef struct _GUID { std::uint8_t Data4[8]; } GUID; +#ifndef _SYS_GUID_OPERATOR_EQ_ +#define _SYS_GUID_OPERATOR_EQ_ inline bool operator==(const GUID& lhs, const GUID& rhs) noexcept { return std::memcmp(&lhs, &rhs, sizeof(GUID)) == 0; } inline bool operator!=(const GUID& lhs, const GUID& rhs) noexcept { return !(lhs == rhs); } +#endif // _SYS_GUID_OPERATOR_EQ_ #endif // GUID_DEFINED @@ -287,21 +290,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; @@ -370,7 +371,8 @@ 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 { EAXFXSLOT_PARAMETER = 0, diff --git a/thirdparty/openal/al/eax/call.cpp b/thirdparty/openal/al/eax/call.cpp index 1fd059683f..abb279331d 100644 --- a/thirdparty/openal/al/eax/call.cpp +++ b/thirdparty/openal/al/eax/call.cpp @@ -23,6 +23,7 @@ EaxCall::EaxCall( ALvoid* property_buffer, ALuint property_size) : type_{type}, version_{0}, property_set_id_{EaxCallPropertySetId::none} + , is_deferred_{(property_id & deferred_flag) != 0} , property_id_{property_id & ~deferred_flag}, property_source_id_{property_source_id} , property_buffer_{property_buffer}, property_size_{property_size} { @@ -150,7 +151,28 @@ EaxCall::EaxCall( fail("EAX version out of range."); } - if(!(property_id&deferred_flag)) + switch(property_id) + { + 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. + is_deferred_ = false; + break; + default: + break; + } + + if(!is_deferred_) { if(property_set_id_ != EaxCallPropertySetId::fx_slot && property_id_ != 0) { diff --git a/thirdparty/openal/al/eax/call.h b/thirdparty/openal/al/eax/call.h index 9c2706c3d7..4c35551cf6 100644 --- a/thirdparty/openal/al/eax/call.h +++ b/thirdparty/openal/al/eax/call.h @@ -32,6 +32,7 @@ public: ALuint property_size); bool is_get() const noexcept { return type_ == EaxCallType::get; } + bool is_deferred() const noexcept { return is_deferred_; } int get_version() const noexcept { return version_; } EaxCallPropertySetId get_property_set_id() const noexcept { return property_set_id_; } ALuint get_property_id() const noexcept { return property_id_; } @@ -76,6 +77,7 @@ private: int version_; EaxFxSlotIndex fx_slot_index_; EaxCallPropertySetId property_set_id_; + bool is_deferred_; ALuint property_id_; ALuint property_source_id_; diff --git a/thirdparty/openal/al/eax/effect.h b/thirdparty/openal/al/eax/effect.h index b57bf24027..f09a252046 100644 --- a/thirdparty/openal/al/eax/effect.h +++ b/thirdparty/openal/al/eax/effect.h @@ -35,8 +35,8 @@ template class EaxEffect4 : public EaxEffect { public: - EaxEffect4(ALenum type, const EaxCall& call) - : EaxEffect{type}, version_{clamp(call.get_version(), 4, 5)} + EaxEffect4(ALenum type, int eax_version) + : EaxEffect{type}, version_{clamp(eax_version, 4, 5)} {} void initialize() @@ -70,10 +70,10 @@ protected: Props d; // Deferred. }; // State - int version_; - Props props_; - State state4_; - State state5_; + int version_{}; + Props props_{}; + State state4_{}; + State state5_{}; template static void defer(const EaxCall& call, TProperty& property) @@ -149,25 +149,25 @@ using EaxEffectUPtr = std::unique_ptr; // Creates EAX4+ effect. template -EaxEffectUPtr eax_create_eax4_effect(const EaxCall& call) +EaxEffectUPtr eax_create_eax4_effect(int eax_version) { - auto effect = std::make_unique(call); + auto effect = std::make_unique(eax_version); effect->initialize(); return effect; } EaxEffectUPtr eax_create_eax_null_effect(); -EaxEffectUPtr eax_create_eax_chorus_effect(const EaxCall& call); -EaxEffectUPtr eax_create_eax_distortion_effect(const EaxCall& call); -EaxEffectUPtr eax_create_eax_echo_effect(const EaxCall& call); -EaxEffectUPtr eax_create_eax_flanger_effect(const EaxCall& call); -EaxEffectUPtr eax_create_eax_frequency_shifter_effect(const EaxCall& call); -EaxEffectUPtr eax_create_eax_vocal_morpher_effect(const EaxCall& call); -EaxEffectUPtr eax_create_eax_pitch_shifter_effect(const EaxCall& call); -EaxEffectUPtr eax_create_eax_ring_modulator_effect(const EaxCall& call); -EaxEffectUPtr eax_create_eax_auto_wah_effect(const EaxCall& call); -EaxEffectUPtr eax_create_eax_compressor_effect(const EaxCall& call); -EaxEffectUPtr eax_create_eax_equalizer_effect(const EaxCall& call); -EaxEffectUPtr eax_create_eax_reverb_effect(const EaxCall& call); +EaxEffectUPtr eax_create_eax_chorus_effect(int eax_version); +EaxEffectUPtr eax_create_eax_distortion_effect(int eax_version); +EaxEffectUPtr eax_create_eax_echo_effect(int eax_version); +EaxEffectUPtr eax_create_eax_flanger_effect(int eax_version); +EaxEffectUPtr eax_create_eax_frequency_shifter_effect(int eax_version); +EaxEffectUPtr eax_create_eax_vocal_morpher_effect(int eax_version); +EaxEffectUPtr eax_create_eax_pitch_shifter_effect(int eax_version); +EaxEffectUPtr eax_create_eax_ring_modulator_effect(int eax_version); +EaxEffectUPtr eax_create_eax_auto_wah_effect(int eax_version); +EaxEffectUPtr eax_create_eax_compressor_effect(int eax_version); +EaxEffectUPtr eax_create_eax_equalizer_effect(int eax_version); +EaxEffectUPtr eax_create_eax_reverb_effect(int eax_version); #endif // !EAX_EFFECT_INCLUDED diff --git a/thirdparty/openal/al/eax/exception.cpp b/thirdparty/openal/al/eax/exception.cpp index 3b3196481c..435e74426b 100644 --- a/thirdparty/openal/al/eax/exception.cpp +++ b/thirdparty/openal/al/eax/exception.cpp @@ -6,17 +6,14 @@ #include -EaxException::EaxException( - const char* context, - const char* message) - : - std::runtime_error{make_message(context, message)} +EaxException::EaxException(const char *context, const char *message) + : std::runtime_error{make_message(context, message)} { } +EaxException::~EaxException() = default; -std::string EaxException::make_message( - const char* context, - const char* message) + +std::string EaxException::make_message(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); diff --git a/thirdparty/openal/al/eax/exception.h b/thirdparty/openal/al/eax/exception.h index 9a7acf715e..3ae88cdc6d 100644 --- a/thirdparty/openal/al/eax/exception.h +++ b/thirdparty/openal/al/eax/exception.h @@ -6,19 +6,12 @@ #include -class EaxException : - public std::runtime_error -{ +class EaxException : public std::runtime_error { + static std::string make_message(const char *context, const char *message); + public: - EaxException( - const char* context, - const char* message); - - -private: - static std::string make_message( - const char* context, - const char* message); + EaxException(const char *context, const char *message); + ~EaxException() override; }; // EaxException diff --git a/thirdparty/openal/al/eax/fx_slots.cpp b/thirdparty/openal/al/eax/fx_slots.cpp index 671d2cfb04..d04b70df3f 100644 --- a/thirdparty/openal/al/eax/fx_slots.cpp +++ b/thirdparty/openal/al/eax/fx_slots.cpp @@ -28,11 +28,9 @@ public: } // namespace -void EaxFxSlots::initialize( - const EaxCall& call, - ALCcontext& al_context) +void EaxFxSlots::initialize(ALCcontext& al_context) { - initialize_fx_slots(call, al_context); + initialize_fx_slots(al_context); } void EaxFxSlots::uninitialize() noexcept @@ -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,16 +62,14 @@ void EaxFxSlots::fail( throw EaxFxSlotsException{message}; } -void EaxFxSlots::initialize_fx_slots( - const EaxCall& call, - ALCcontext& al_context) +void EaxFxSlots::initialize_fx_slots(ALCcontext& al_context) { auto fx_slot_index = EaxFxSlotIndexValue{}; for (auto& fx_slot : fx_slots_) { fx_slot = eax_create_al_effect_slot(al_context); - fx_slot->eax_initialize(call, al_context, fx_slot_index); + fx_slot->eax_initialize(al_context, fx_slot_index); fx_slot_index += 1; } } diff --git a/thirdparty/openal/al/eax/fx_slots.h b/thirdparty/openal/al/eax/fx_slots.h index e7d1452ef4..18b2d3ad43 100644 --- a/thirdparty/openal/al/eax/fx_slots.h +++ b/thirdparty/openal/al/eax/fx_slots.h @@ -14,9 +14,7 @@ class EaxFxSlots { public: - void initialize( - const EaxCall& call, - ALCcontext& al_context); + void initialize(ALCcontext& al_context); void uninitialize() noexcept; @@ -33,9 +31,6 @@ public: ALeffectslot& get( EaxFxSlotIndex index); - void unlock_legacy() noexcept; - - private: using Items = std::array; @@ -47,9 +42,7 @@ private: static void fail( const char* message); - void initialize_fx_slots( - const EaxCall& call, - ALCcontext& al_context); + void initialize_fx_slots(ALCcontext& al_context); }; // EaxFxSlots diff --git a/thirdparty/openal/al/eax/utils.cpp b/thirdparty/openal/al/eax/utils.cpp index 9fa2871d57..b3ed6ca14f 100644 --- a/thirdparty/openal/al/eax/utils.cpp +++ b/thirdparty/openal/al/eax/utils.cpp @@ -8,29 +8,19 @@ #include "core/logging.h" -void eax_log_exception( - const char* message) noexcept +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 - { + try { std::rethrow_exception(exception_ptr); } - catch (const std::exception& ex) - { + catch(const std::exception& ex) { const auto ex_message = ex.what(); - ERR("%s\n", ex_message); + ERR("%s %s\n", message ? message : "", ex_message); } - catch (...) - { - ERR("%s\n", "Generic exception."); + catch(...) { + ERR("%s %s\n", message ? message : "", "Generic exception."); } } diff --git a/thirdparty/openal/al/eax/utils.h b/thirdparty/openal/al/eax/utils.h index 5a8fdd64da..8ff75a18fc 100644 --- a/thirdparty/openal/al/eax/utils.h +++ b/thirdparty/openal/al/eax/utils.h @@ -6,12 +6,14 @@ #include #include +using EaxDirtyFlags = unsigned int; + struct EaxAlLowPassParam { float gain; float gain_hf; }; -void eax_log_exception(const char* message = nullptr) noexcept; +void eax_log_exception(const char *message) noexcept; template void eax_validate_range( diff --git a/thirdparty/openal/al/effect.cpp b/thirdparty/openal/al/effect.cpp index 90a4dfde57..bb4e9de62d 100644 --- a/thirdparty/openal/al/effect.cpp +++ b/thirdparty/openal/al/effect.cpp @@ -86,6 +86,7 @@ effect_exception::effect_exception(ALenum code, const char *msg, ...) : mErrorCo setMessage(msg, args); va_end(args); } +effect_exception::~effect_exception() = default; namespace { @@ -139,7 +140,7 @@ const EffectPropsItem *getEffectPropsItemByType(ALenum type) 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; + return (iter != std::end(EffectPropsList)) ? al::to_address(iter) : nullptr; } void InitEffectParams(ALeffect *effect, ALenum type) @@ -166,14 +167,14 @@ bool EnsureEffects(ALCdevice *device, size_t needed) 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) + if(!sublist->Effects) [[unlikely]] { device->EffectList.pop_back(); return false; @@ -219,10 +220,10 @@ inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; - if UNLIKELY(lidx >= device->EffectList.size()) + 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; } @@ -233,11 +234,11 @@ AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; - if UNLIKELY(n < 0) + if(n < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Generating %d effects", n); - if UNLIKELY(n <= 0) return; + if(n <= 0) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; @@ -247,7 +248,7 @@ START_API_FUNC return; } - if LIKELY(n == 1) + if(n == 1) [[likely]] { /* Special handling for the easy and normal case. */ ALeffect *effect{AllocEffect(device)}; @@ -273,11 +274,11 @@ AL_API void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; - if UNLIKELY(n < 0) + if(n < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Deleting %d effects", n); - if UNLIKELY(n <= 0) return; + if(n <= 0) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; @@ -288,7 +289,7 @@ START_API_FUNC const ALuint *effects_end = effects + n; auto inveffect = std::find_if_not(effects, effects_end, validate_effect); - if UNLIKELY(inveffect != effects_end) + if(inveffect != effects_end) [[unlikely]] { context->setError(AL_INVALID_NAME, "Invalid effect ID %u", *inveffect); return; @@ -308,7 +309,7 @@ AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect) START_API_FUNC { ContextRef context{GetContextRef()}; - if LIKELY(context) + if(context) [[likely]] { ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; @@ -323,13 +324,13 @@ AL_API void AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; - if UNLIKELY(!aleffect) + if(!aleffect) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); else if(param == AL_EFFECT_TYPE) { @@ -373,13 +374,13 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; - if UNLIKELY(!aleffect) + if(!aleffect) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); else try { @@ -396,13 +397,13 @@ AL_API void AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; - if UNLIKELY(!aleffect) + if(!aleffect) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); else try { @@ -419,13 +420,13 @@ AL_API void AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *v START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; - if UNLIKELY(!aleffect) + if(!aleffect) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); else try { @@ -442,13 +443,13 @@ AL_API void AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if UNLIKELY(!aleffect) + if(!aleffect) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); else if(param == AL_EFFECT_TYPE) *value = aleffect->type; @@ -474,13 +475,13 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if UNLIKELY(!aleffect) + if(!aleffect) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); else try { @@ -497,13 +498,13 @@ AL_API void AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *value START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if UNLIKELY(!aleffect) + if(!aleffect) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); else try { @@ -520,13 +521,13 @@ AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *valu START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if UNLIKELY(!aleffect) + if(!aleffect) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); else try { diff --git a/thirdparty/openal/al/effects/autowah.cpp b/thirdparty/openal/al/effects/autowah.cpp index 7e0e34aa0b..3400f5a43f 100644 --- a/thirdparty/openal/al/effects/autowah.cpp +++ b/thirdparty/openal/al/effects/autowah.cpp @@ -127,7 +127,7 @@ public: class EaxAutoWahEffect final : public EaxEffect4 { public: - EaxAutoWahEffect(const EaxCall& call); + EaxAutoWahEffect(int eax_version); private: struct AttackTimeValidator { @@ -197,8 +197,8 @@ private: bool commit_props(const Props& props) override; }; // EaxAutoWahEffect -EaxAutoWahEffect::EaxAutoWahEffect(const EaxCall& call) - : EaxEffect4{AL_EFFECT_AUTOWAH, call} +EaxAutoWahEffect::EaxAutoWahEffect(int eax_version) + : EaxEffect4{AL_EFFECT_AUTOWAH, eax_version} {} void EaxAutoWahEffect::set_defaults(Props& props) @@ -310,9 +310,9 @@ bool EaxAutoWahEffect::commit_props(const Props& props) } // namespace -EaxEffectUPtr eax_create_eax_auto_wah_effect(const EaxCall& call) +EaxEffectUPtr eax_create_eax_auto_wah_effect(int eax_version) { - return eax_create_eax4_effect(call); + return eax_create_eax4_effect(eax_version); } #endif // ALSOFT_EAX diff --git a/thirdparty/openal/al/effects/chorus.cpp b/thirdparty/openal/al/effects/chorus.cpp index 0d4283c990..eec67d4673 100644 --- a/thirdparty/openal/al/effects/chorus.cpp +++ b/thirdparty/openal/al/effects/chorus.cpp @@ -31,8 +31,8 @@ inline al::optional WaveformFromEnum(ALenum type) { 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; } @@ -149,7 +149,7 @@ void Chorus_getParamf(const EffectProps *props, ALenum param, float *val) void Chorus_getParamfv(const EffectProps *props, ALenum param, float *vals) { Chorus_getParamf(props, param, vals); } -const EffectProps genDefaultChorusProps() noexcept +EffectProps genDefaultChorusProps() noexcept { EffectProps props{}; props.Chorus.Waveform = *WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM); @@ -433,8 +433,8 @@ public: using typename Base::State; using Base::defer; - EaxChorusFlangerEffect(const EaxCall& call) - : Base{Traits::efx_effect(), call} + EaxChorusFlangerEffect(int eax_version) + : Base{Traits::efx_effect(), eax_version} {} private: @@ -712,23 +712,23 @@ private: }; // EaxChorusFlangerEffect template -EaxEffectUPtr eax_create_eax_chorus_flanger_effect(const EaxCall& call) +EaxEffectUPtr eax_create_eax_chorus_flanger_effect(int eax_version) { - return eax_create_eax4_effect>(call); + return eax_create_eax4_effect>(eax_version); } } // namespace // ========================================================================== -EaxEffectUPtr eax_create_eax_chorus_effect(const EaxCall& call) +EaxEffectUPtr eax_create_eax_chorus_effect(int eax_version) { - return eax_create_eax_chorus_flanger_effect(call); + return eax_create_eax_chorus_flanger_effect(eax_version); } -EaxEffectUPtr eax_create_eax_flanger_effect(const EaxCall& call) +EaxEffectUPtr eax_create_eax_flanger_effect(int eax_version) { - return eax_create_eax_chorus_flanger_effect(call); + return eax_create_eax_chorus_flanger_effect(eax_version); } #endif // ALSOFT_EAX diff --git a/thirdparty/openal/al/effects/compressor.cpp b/thirdparty/openal/al/effects/compressor.cpp index 9824d11b61..df3187096d 100644 --- a/thirdparty/openal/al/effects/compressor.cpp +++ b/thirdparty/openal/al/effects/compressor.cpp @@ -91,7 +91,7 @@ public: class EaxCompressorEffect final : public EaxEffect4 { public: - EaxCompressorEffect(const EaxCall& call); + EaxCompressorEffect(int eax_version); private: struct OnOffValidator { @@ -122,8 +122,8 @@ private: bool commit_props(const Props& props) override; }; // EaxCompressorEffect -EaxCompressorEffect::EaxCompressorEffect(const EaxCall& call) - : EaxEffect4{AL_EFFECT_COMPRESSOR, call} +EaxCompressorEffect::EaxCompressorEffect(int eax_version) + : EaxEffect4{AL_EFFECT_COMPRESSOR, eax_version} {} void EaxCompressorEffect::set_defaults(Props& props) @@ -182,9 +182,9 @@ bool EaxCompressorEffect::commit_props(const Props& props) } // namespace -EaxEffectUPtr eax_create_eax_compressor_effect(const EaxCall& call) +EaxEffectUPtr eax_create_eax_compressor_effect(int eax_version) { - return eax_create_eax4_effect(call); + return eax_create_eax4_effect(eax_version); } #endif // ALSOFT_EAX diff --git a/thirdparty/openal/al/effects/distortion.cpp b/thirdparty/openal/al/effects/distortion.cpp index b58412b907..80e5f46dc4 100644 --- a/thirdparty/openal/al/effects/distortion.cpp +++ b/thirdparty/openal/al/effects/distortion.cpp @@ -133,7 +133,7 @@ public: class EaxDistortionEffect final : public EaxEffect4 { public: - EaxDistortionEffect(const EaxCall& call); + EaxDistortionEffect(int eax_version); private: struct EdgeValidator { @@ -216,8 +216,8 @@ private: bool commit_props(const Props& props) override; }; // EaxDistortionEffect -EaxDistortionEffect::EaxDistortionEffect(const EaxCall& call) - : EaxEffect4{AL_EFFECT_DISTORTION, call} +EaxDistortionEffect::EaxDistortionEffect(int eax_version) + : EaxEffect4{AL_EFFECT_DISTORTION, eax_version} {} void EaxDistortionEffect::set_defaults(Props& props) @@ -347,9 +347,9 @@ bool EaxDistortionEffect::commit_props(const Props& props) } // namespace -EaxEffectUPtr eax_create_eax_distortion_effect(const EaxCall& call) +EaxEffectUPtr eax_create_eax_distortion_effect(int eax_version) { - return eax_create_eax4_effect(call); + return eax_create_eax4_effect(eax_version); } #endif // ALSOFT_EAX diff --git a/thirdparty/openal/al/effects/echo.cpp b/thirdparty/openal/al/effects/echo.cpp index f25c94bf5a..c0968676ba 100644 --- a/thirdparty/openal/al/effects/echo.cpp +++ b/thirdparty/openal/al/effects/echo.cpp @@ -130,7 +130,7 @@ public: class EaxEchoEffect final : public EaxEffect4 { public: - EaxEchoEffect(const EaxCall& call); + EaxEchoEffect(int eax_version); private: struct DelayValidator { @@ -213,8 +213,8 @@ private: bool commit_props(const Props& props) override; }; // EaxEchoEffect -EaxEchoEffect::EaxEchoEffect(const EaxCall& call) - : EaxEffect4{AL_EFFECT_ECHO, call} +EaxEchoEffect::EaxEchoEffect(int eax_version) + : EaxEffect4{AL_EFFECT_ECHO, eax_version} {} void EaxEchoEffect::set_defaults(Props& props) @@ -344,9 +344,9 @@ bool EaxEchoEffect::commit_props(const Props& props) } // namespace -EaxEffectUPtr eax_create_eax_echo_effect(const EaxCall& call) +EaxEffectUPtr eax_create_eax_echo_effect(int eax_version) { - return eax_create_eax4_effect(call); + return eax_create_eax4_effect(eax_version); } #endif // ALSOFT_EAX diff --git a/thirdparty/openal/al/effects/effects.cpp b/thirdparty/openal/al/effects/effects.cpp index e4e61231a5..7c8447e45f 100644 --- a/thirdparty/openal/al/effects/effects.cpp +++ b/thirdparty/openal/al/effects/effects.cpp @@ -1,16 +1,12 @@ - #include "config.h" #ifdef ALSOFT_EAX +#include +#include "AL/efx.h" #include "effects.h" -#include - -#include "AL/efx.h" - - -EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type, const EaxCall& call) +EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type, int eax_version) { #define EAX_PREFIX "[EAX_MAKE_EAX_EFFECT] " @@ -20,40 +16,40 @@ EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type, const EaxCall& call) return eax_create_eax_null_effect(); case AL_EFFECT_CHORUS: - return eax_create_eax_chorus_effect(call); + return eax_create_eax_chorus_effect(eax_version); case AL_EFFECT_DISTORTION: - return eax_create_eax_distortion_effect(call); + return eax_create_eax_distortion_effect(eax_version); case AL_EFFECT_ECHO: - return eax_create_eax_echo_effect(call); + return eax_create_eax_echo_effect(eax_version); case AL_EFFECT_FLANGER: - return eax_create_eax_flanger_effect(call); + return eax_create_eax_flanger_effect(eax_version); case AL_EFFECT_FREQUENCY_SHIFTER: - return eax_create_eax_frequency_shifter_effect(call); + return eax_create_eax_frequency_shifter_effect(eax_version); case AL_EFFECT_VOCAL_MORPHER: - return eax_create_eax_vocal_morpher_effect(call); + return eax_create_eax_vocal_morpher_effect(eax_version); case AL_EFFECT_PITCH_SHIFTER: - return eax_create_eax_pitch_shifter_effect(call); + return eax_create_eax_pitch_shifter_effect(eax_version); case AL_EFFECT_RING_MODULATOR: - return eax_create_eax_ring_modulator_effect(call); + return eax_create_eax_ring_modulator_effect(eax_version); case AL_EFFECT_AUTOWAH: - return eax_create_eax_auto_wah_effect(call); + return eax_create_eax_auto_wah_effect(eax_version); case AL_EFFECT_COMPRESSOR: - return eax_create_eax_compressor_effect(call); + return eax_create_eax_compressor_effect(eax_version); case AL_EFFECT_EQUALIZER: - return eax_create_eax_equalizer_effect(call); + return eax_create_eax_equalizer_effect(eax_version); case AL_EFFECT_EAXREVERB: - return eax_create_eax_reverb_effect(call); + return eax_create_eax_reverb_effect(eax_version); default: assert(false && "Unsupported AL effect type."); diff --git a/thirdparty/openal/al/effects/effects.h b/thirdparty/openal/al/effects/effects.h index 164c0d1924..70960a7fca 100644 --- a/thirdparty/openal/al/effects/effects.h +++ b/thirdparty/openal/al/effects/effects.h @@ -6,7 +6,6 @@ #include "core/except.h" #ifdef ALSOFT_EAX -#include "al/eax/call.h" #include "al/eax/effect.h" #endif // ALSOFT_EAX @@ -23,6 +22,7 @@ public: [[gnu::format(printf, 3, 4)]] #endif effect_exception(ALenum code, const char *msg, ...); + ~effect_exception() override; ALenum errorCode() const noexcept { return mErrorCode; } }; @@ -87,7 +87,7 @@ extern const EffectVtable ConvolutionEffectVtable; #ifdef ALSOFT_EAX -EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type, const EaxCall& call); +EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type, int eax_version); #endif // ALSOFT_EAX #endif /* AL_EFFECTS_EFFECTS_H */ diff --git a/thirdparty/openal/al/effects/equalizer.cpp b/thirdparty/openal/al/effects/equalizer.cpp index 80dd1c4be9..0ee351f355 100644 --- a/thirdparty/openal/al/effects/equalizer.cpp +++ b/thirdparty/openal/al/effects/equalizer.cpp @@ -188,7 +188,7 @@ public: class EaxEqualizerEffect final : public EaxEffect4 { public: - EaxEqualizerEffect(const EaxCall& call); + EaxEqualizerEffect(int eax_version); private: struct LowGainValidator { @@ -336,8 +336,8 @@ private: bool commit_props(const Props& props) override; }; // EaxEqualizerEffect -EaxEqualizerEffect::EaxEqualizerEffect(const EaxCall& call) - : EaxEffect4{AL_EFFECT_EQUALIZER, call} +EaxEqualizerEffect::EaxEqualizerEffect(int eax_version) + : EaxEffect4{AL_EFFECT_EQUALIZER, eax_version} {} void EaxEqualizerEffect::set_defaults(Props& props) @@ -557,9 +557,9 @@ bool EaxEqualizerEffect::commit_props(const Props& props) } // namespace -EaxEffectUPtr eax_create_eax_equalizer_effect(const EaxCall& call) +EaxEffectUPtr eax_create_eax_equalizer_effect(int eax_version) { - return eax_create_eax4_effect(call); + return eax_create_eax4_effect(eax_version); } #endif // ALSOFT_EAX diff --git a/thirdparty/openal/al/effects/fshifter.cpp b/thirdparty/openal/al/effects/fshifter.cpp index 2b1710ad5b..9ca287751d 100644 --- a/thirdparty/openal/al/effects/fshifter.cpp +++ b/thirdparty/openal/al/effects/fshifter.cpp @@ -24,9 +24,9 @@ al::optional DirectionFromEmum(ALenum value) { 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; } @@ -149,7 +149,7 @@ public: class EaxFrequencyShifterEffect final : public EaxEffect4 { public: - EaxFrequencyShifterEffect(const EaxCall& call); + EaxFrequencyShifterEffect(int eax_version); private: struct FrequencyValidator { @@ -207,8 +207,8 @@ private: }; // EaxFrequencyShifterEffect -EaxFrequencyShifterEffect::EaxFrequencyShifterEffect(const EaxCall& call) - : EaxEffect4{AL_EFFECT_FREQUENCY_SHIFTER, call} +EaxFrequencyShifterEffect::EaxFrequencyShifterEffect(int eax_version) + : EaxEffect4{AL_EFFECT_FREQUENCY_SHIFTER, eax_version} {} void EaxFrequencyShifterEffect::set_defaults(Props& props) @@ -308,9 +308,9 @@ bool EaxFrequencyShifterEffect::commit_props(const Props& props) } // namespace -EaxEffectUPtr eax_create_eax_frequency_shifter_effect(const EaxCall& call) +EaxEffectUPtr eax_create_eax_frequency_shifter_effect(int eax_version) { - return eax_create_eax4_effect(call); + return eax_create_eax4_effect(eax_version); } #endif // ALSOFT_EAX diff --git a/thirdparty/openal/al/effects/modulator.cpp b/thirdparty/openal/al/effects/modulator.cpp index 774fb767db..070b09984b 100644 --- a/thirdparty/openal/al/effects/modulator.cpp +++ b/thirdparty/openal/al/effects/modulator.cpp @@ -24,9 +24,9 @@ al::optional WaveformFromEmum(ALenum value) { 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; } @@ -156,7 +156,7 @@ public: class EaxRingModulatorEffect final : public EaxEffect4 { public: - EaxRingModulatorEffect(const EaxCall& call); + EaxRingModulatorEffect(int eax_version); private: struct FrequencyValidator { @@ -213,8 +213,8 @@ private: bool commit_props(const Props& props) override; }; // EaxRingModulatorEffect -EaxRingModulatorEffect::EaxRingModulatorEffect(const EaxCall& call) - : EaxEffect4{AL_EFFECT_RING_MODULATOR, call} +EaxRingModulatorEffect::EaxRingModulatorEffect(int eax_version) + : EaxEffect4{AL_EFFECT_RING_MODULATOR, eax_version} {} void EaxRingModulatorEffect::set_defaults(Props& props) @@ -311,9 +311,9 @@ bool EaxRingModulatorEffect::commit_props(const Props& props) } // namespace -EaxEffectUPtr eax_create_eax_ring_modulator_effect(const EaxCall& call) +EaxEffectUPtr eax_create_eax_ring_modulator_effect(int eax_version) { - return eax_create_eax4_effect(call); + return eax_create_eax4_effect(eax_version); } #endif // ALSOFT_EAX diff --git a/thirdparty/openal/al/effects/pshifter.cpp b/thirdparty/openal/al/effects/pshifter.cpp index 51dbdd8f04..9711da281c 100644 --- a/thirdparty/openal/al/effects/pshifter.cpp +++ b/thirdparty/openal/al/effects/pshifter.cpp @@ -102,7 +102,7 @@ public: class EaxPitchShifterEffect final : public EaxEffect4 { public: - EaxPitchShifterEffect(const EaxCall& call); + EaxPitchShifterEffect(int eax_version); private: struct CoarseTuneValidator { @@ -146,8 +146,8 @@ private: bool commit_props(const Props& old_i) override; }; // EaxPitchShifterEffect -EaxPitchShifterEffect::EaxPitchShifterEffect(const EaxCall& call) - : EaxEffect4{AL_EFFECT_PITCH_SHIFTER, call} +EaxPitchShifterEffect::EaxPitchShifterEffect(int eax_version) + : EaxEffect4{AL_EFFECT_PITCH_SHIFTER, eax_version} {} void EaxPitchShifterEffect::set_defaults(Props& props) @@ -223,9 +223,9 @@ bool EaxPitchShifterEffect::commit_props(const Props& props) } // namespace -EaxEffectUPtr eax_create_eax_pitch_shifter_effect(const EaxCall& call) +EaxEffectUPtr eax_create_eax_pitch_shifter_effect(int eax_version) { - return eax_create_eax4_effect(call); + return eax_create_eax4_effect(eax_version); } #endif // ALSOFT_EAX diff --git a/thirdparty/openal/al/effects/reverb.cpp b/thirdparty/openal/al/effects/reverb.cpp index 07d6dfcfb7..656642e039 100644 --- a/thirdparty/openal/al/effects/reverb.cpp +++ b/thirdparty/openal/al/effects/reverb.cpp @@ -577,7 +577,7 @@ public: class EaxReverbEffect final : public EaxEffect { public: - EaxReverbEffect(const EaxCall& call) noexcept; + EaxReverbEffect(int eax_version) noexcept; void dispatch(const EaxCall& call) override; /*[[nodiscard]]*/ bool commit() override; @@ -1194,8 +1194,8 @@ private: static void translate(const Props2& src, Props3& dst) noexcept; }; // EaxReverbEffect -EaxReverbEffect::EaxReverbEffect(const EaxCall& call) noexcept - : EaxEffect{AL_EFFECT_EAXREVERB}, version_{call.get_version()} +EaxReverbEffect::EaxReverbEffect(int eax_version) noexcept + : EaxEffect{AL_EFFECT_EAXREVERB}, version_{eax_version} { set_defaults(); set_current_defaults(); @@ -1496,7 +1496,7 @@ void EaxReverbEffect::get2(const EaxCall& call, const Props2& props) 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.flReverbDelay); 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; @@ -1986,9 +1986,9 @@ void EaxReverbEffect::translate(const Props2& src, Props3& dst) noexcept } // namespace -EaxEffectUPtr eax_create_eax_reverb_effect(const EaxCall& call) +EaxEffectUPtr eax_create_eax_reverb_effect(int eax_version) { - return std::make_unique(call); + return std::make_unique(eax_version); } #endif // ALSOFT_EAX diff --git a/thirdparty/openal/al/effects/vmorpher.cpp b/thirdparty/openal/al/effects/vmorpher.cpp index 95f98db561..e26c6fe31d 100644 --- a/thirdparty/openal/al/effects/vmorpher.cpp +++ b/thirdparty/openal/al/effects/vmorpher.cpp @@ -23,7 +23,7 @@ namespace { al::optional PhenomeFromEnum(ALenum val) { #define HANDLE_PHENOME(x) case AL_VOCAL_MORPHER_PHONEME_ ## x: \ - return al::make_optional(VMorpherPhenome::x) + return VMorpherPhenome::x switch(val) { HANDLE_PHENOME(A); @@ -104,9 +104,9 @@ al::optional WaveformFromEmum(ALenum value) { 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; } @@ -267,7 +267,7 @@ public: class EaxVocalMorpherEffect final : public EaxEffect4 { public: - EaxVocalMorpherEffect(const EaxCall& call); + EaxVocalMorpherEffect(int eax_version); private: struct PhonemeAValidator { @@ -363,8 +363,8 @@ private: bool commit_props(const Props& props) override; }; // EaxVocalMorpherEffect -EaxVocalMorpherEffect::EaxVocalMorpherEffect(const EaxCall& call) - : EaxEffect4{AL_EFFECT_VOCAL_MORPHER, call} +EaxVocalMorpherEffect::EaxVocalMorpherEffect(int eax_version) + : EaxEffect4{AL_EFFECT_VOCAL_MORPHER, eax_version} {} void EaxVocalMorpherEffect::set_defaults(Props& props) @@ -570,9 +570,9 @@ bool EaxVocalMorpherEffect::commit_props(const Props& props) } // namespace -EaxEffectUPtr eax_create_eax_vocal_morpher_effect(const EaxCall& call) +EaxEffectUPtr eax_create_eax_vocal_morpher_effect(int eax_version) { - return eax_create_eax4_effect(call); + return eax_create_eax4_effect(eax_version); } #endif // ALSOFT_EAX diff --git a/thirdparty/openal/al/error.cpp b/thirdparty/openal/al/error.cpp index 906710113a..0340f430dd 100644 --- a/thirdparty/openal/al/error.cpp +++ b/thirdparty/openal/al/error.cpp @@ -85,7 +85,7 @@ AL_API ALenum AL_APIENTRY alGetError(void) START_API_FUNC { ContextRef context{GetContextRef()}; - if(unlikely(!context)) + if(!context) [[unlikely]] { static constexpr ALenum deferror{AL_INVALID_OPERATION}; WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror); diff --git a/thirdparty/openal/al/event.cpp b/thirdparty/openal/al/event.cpp index e5923c436a..2aceba3a20 100644 --- a/thirdparty/openal/al/event.cpp +++ b/thirdparty/openal/al/event.cpp @@ -35,7 +35,7 @@ static int EventThread(ALCcontext *context) { RingBuffer *ring{context->mAsyncEvents.get()}; bool quitnow{false}; - while(likely(!quitnow)) + while(!quitnow) [[likely]] { auto evt_data = ring->getReadVector().first; if(evt_data.len == 0) @@ -55,21 +55,20 @@ static int EventThread(ALCcontext *context) ring->readAdvance(1); quitnow = evt.EnumType == AsyncEvent::KillThread; - if(unlikely(quitnow)) break; + if(quitnow) [[unlikely]] break; if(evt.EnumType == AsyncEvent::ReleaseEffectState) { - evt.u.mEffectState->release(); + al::intrusive_ptr{evt.u.mEffectState}; continue; } - uint enabledevts{context->mEnabledEvts.load(std::memory_order_acquire)}; - if(!context->mEventCb) continue; + auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire); + if(!context->mEventCb || !enabledevts.test(evt.EnumType)) + continue; if(evt.EnumType == AsyncEvent::SourceStateChange) { - if(!(enabledevts&AsyncEvent::SourceStateChange)) - continue; ALuint state{}; std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)}; msg += " state has changed to "; @@ -97,8 +96,6 @@ static int EventThread(ALCcontext *context) } else if(evt.EnumType == AsyncEvent::BufferCompleted) { - if(!(enabledevts&AsyncEvent::BufferCompleted)) - continue; std::string msg{std::to_string(evt.u.bufcomp.count)}; if(evt.u.bufcomp.count == 1) msg += " buffer completed"; else msg += " buffers completed"; @@ -108,8 +105,6 @@ static int EventThread(ALCcontext *context) } else if(evt.EnumType == AsyncEvent::Disconnected) { - 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); @@ -155,34 +150,34 @@ AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, A START_API_FUNC { ContextRef context{GetContextRef()}; - if(unlikely(!context)) return; + if(!context) [[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) return context->setError(AL_INVALID_VALUE, "NULL pointer"); - uint flags{0}; + ContextBase::AsyncEventBitset flags{}; const ALenum *types_end = types+count; auto bad_type = std::find_if_not(types, types_end, [&flags](ALenum type) noexcept -> bool { if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT) - flags |= AsyncEvent::BufferCompleted; + flags.set(AsyncEvent::BufferCompleted); else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT) - flags |= AsyncEvent::SourceStateChange; + flags.set(AsyncEvent::SourceStateChange); else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT) - flags |= AsyncEvent::Disconnected; + flags.set(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); + return context->setError(AL_INVALID_ENUM, "Invalid event type 0x%04x", *bad_type); 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 +188,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) { @@ -210,7 +205,7 @@ AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *user START_API_FUNC { ContextRef context{GetContextRef()}; - if(unlikely(!context)) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; std::lock_guard __{context->mEventCbLock}; diff --git a/thirdparty/openal/al/extension.cpp b/thirdparty/openal/al/extension.cpp index 5dda2a86d5..3a84ee083a 100644 --- a/thirdparty/openal/al/extension.cpp +++ b/thirdparty/openal/al/extension.cpp @@ -37,10 +37,13 @@ AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName) START_API_FUNC { ContextRef context{GetContextRef()}; - if(unlikely(!context)) return AL_FALSE; + if(!context) [[unlikely]] return AL_FALSE; - if(!extName) - SETERR_RETURN(context, AL_INVALID_VALUE, AL_FALSE, "NULL pointer"); + if(!extName) [[unlikely]] + { + context->setError(AL_INVALID_VALUE, "NULL pointer"); + return AL_FALSE; + } size_t len{strlen(extName)}; const char *ptr{context->mExtensionList}; diff --git a/thirdparty/openal/al/filter.cpp b/thirdparty/openal/al/filter.cpp index f4b0ac9de0..68daee767b 100644 --- a/thirdparty/openal/al/filter.cpp +++ b/thirdparty/openal/al/filter.cpp @@ -57,16 +57,21 @@ public: #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); - } + filter_exception(ALenum code, const char *msg, ...); + ~filter_exception() override; + ALenum errorCode() const noexcept { return mErrorCode; } }; +filter_exception::filter_exception(ALenum code, const char* msg, ...) : mErrorCode{code} +{ + std::va_list args; + va_start(args, msg); + setMessage(msg, args); + va_end(args); +} +filter_exception::~filter_exception() = default; + #define DEFINE_ALFILTER_VTABLE(T) \ const ALfilter::Vtable T##_vtable = { \ @@ -331,14 +336,14 @@ bool EnsureFilters(ALCdevice *device, size_t needed) 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) + if(!sublist->Filters) [[unlikely]] { device->FilterList.pop_back(); return false; @@ -386,10 +391,10 @@ inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; - if UNLIKELY(lidx >= device->FilterList.size()) + 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; } @@ -400,11 +405,11 @@ AL_API void AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; - if UNLIKELY(n < 0) + if(n < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Generating %d filters", n); - if UNLIKELY(n <= 0) return; + if(n <= 0) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; @@ -414,7 +419,7 @@ START_API_FUNC return; } - if LIKELY(n == 1) + if(n == 1) [[likely]] { /* Special handling for the easy and normal case. */ ALfilter *filter{AllocFilter(device)}; @@ -440,11 +445,11 @@ AL_API void AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; - if UNLIKELY(n < 0) + if(n < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Deleting %d filters", n); - if UNLIKELY(n <= 0) return; + if(n <= 0) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; @@ -455,7 +460,7 @@ START_API_FUNC const ALuint *filters_end = filters + n; auto invflt = std::find_if_not(filters, filters_end, validate_filter); - if UNLIKELY(invflt != filters_end) + if(invflt != filters_end) [[unlikely]] { context->setError(AL_INVALID_NAME, "Invalid filter ID %u", *invflt); return; @@ -475,7 +480,7 @@ AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter) START_API_FUNC { ContextRef context{GetContextRef()}; - if LIKELY(context) + if(context) [[likely]] { ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; @@ -491,13 +496,13 @@ AL_API void AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) + if(!alfilt) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); else { @@ -532,13 +537,13 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) + if(!alfilt) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); else try { @@ -555,13 +560,13 @@ AL_API void AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) + if(!alfilt) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); else try { @@ -578,13 +583,13 @@ AL_API void AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *v START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) + if(!alfilt) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); else try { @@ -601,13 +606,13 @@ AL_API void AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) + if(!alfilt) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); else { @@ -636,13 +641,13 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) + if(!alfilt) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); else try { @@ -659,13 +664,13 @@ AL_API void AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *value START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) + if(!alfilt) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); else try { @@ -682,13 +687,13 @@ AL_API void AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *valu START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) + if(!alfilt) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); else try { diff --git a/thirdparty/openal/al/listener.cpp b/thirdparty/openal/al/listener.cpp index 9484d9b1ac..4aa261ddfd 100644 --- a/thirdparty/openal/al/listener.cpp +++ b/thirdparty/openal/al/listener.cpp @@ -81,7 +81,7 @@ AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALlistener &listener = context->mListener; std::lock_guard _{context->mPropLock}; @@ -89,14 +89,14 @@ START_API_FUNC { case AL_GAIN: if(!(value >= 0.0f && std::isfinite(value))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener gain out of range"); + return context->setError(AL_INVALID_VALUE, "Listener gain out of range"); listener.Gain = value; UpdateProps(context.get()); break; case AL_METERS_PER_UNIT: if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener meters per unit out of range"); + return context->setError(AL_INVALID_VALUE, "Listener meters per unit out of range"); listener.mMetersPerUnit = value; UpdateProps(context.get()); break; @@ -111,7 +111,7 @@ AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALlistener &listener = context->mListener; std::lock_guard _{context->mPropLock}; @@ -119,7 +119,7 @@ START_API_FUNC { case AL_POSITION: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener position out of range"); + return context->setError(AL_INVALID_VALUE, "Listener position out of range"); listener.Position[0] = value1; listener.Position[1] = value2; listener.Position[2] = value3; @@ -128,7 +128,7 @@ START_API_FUNC case AL_VELOCITY: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener velocity out of range"); + return context->setError(AL_INVALID_VALUE, "Listener velocity out of range"); listener.Velocity[0] = value1; listener.Velocity[1] = value2; listener.Velocity[2] = value3; @@ -161,17 +161,19 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; + + if(!values) [[unlikely]] + return context->setError(AL_INVALID_VALUE, "NULL pointer"); ALlistener &listener = context->mListener; std::lock_guard _{context->mPropLock}; - if(!values) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer"); switch(param) { case AL_ORIENTATION: if(!(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) && std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5]))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener orientation out of range"); + return context->setError(AL_INVALID_VALUE, "Listener orientation out of range"); /* AT then UP */ listener.OrientAt[0] = values[0]; listener.OrientAt[1] = values[1]; @@ -193,7 +195,7 @@ AL_API void AL_APIENTRY alListeneri(ALenum param, ALint /*value*/) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; switch(param) @@ -211,12 +213,13 @@ START_API_FUNC { case AL_POSITION: case AL_VELOCITY: - alListener3f(param, static_cast(value1), static_cast(value2), static_cast(value3)); + alListener3f(param, static_cast(value1), static_cast(value2), + static_cast(value3)); return; } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; switch(param) @@ -237,7 +240,8 @@ START_API_FUNC { case AL_POSITION: case AL_VELOCITY: - alListener3f(param, static_cast(values[0]), static_cast(values[1]), static_cast(values[2])); + alListener3f(param, static_cast(values[0]), static_cast(values[1]), + static_cast(values[2])); return; case AL_ORIENTATION: @@ -253,10 +257,10 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; - if(!values) + if(!values) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { @@ -271,7 +275,7 @@ AL_API void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALlistener &listener = context->mListener; std::lock_guard _{context->mPropLock}; @@ -297,7 +301,7 @@ AL_API void AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat * START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALlistener &listener = context->mListener; std::lock_guard _{context->mPropLock}; @@ -340,7 +344,7 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALlistener &listener = context->mListener; std::lock_guard _{context->mPropLock}; @@ -369,7 +373,7 @@ AL_API void AL_APIENTRY alGetListeneri(ALenum param, ALint *value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; if(!value) @@ -386,7 +390,7 @@ AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *valu START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALlistener &listener = context->mListener; std::lock_guard _{context->mPropLock}; @@ -424,7 +428,7 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; ALlistener &listener = context->mListener; std::lock_guard _{context->mPropLock}; diff --git a/thirdparty/openal/al/source.cpp b/thirdparty/openal/al/source.cpp index e68c5b1e70..8e67ac64e6 100644 --- a/thirdparty/openal/al/source.cpp +++ b/thirdparty/openal/al/source.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -109,8 +110,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; @@ -160,7 +161,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; @@ -170,7 +171,7 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context }; std::transform(source->Send.cbegin(), source->Send.cend(), props->Send, 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,9 +194,9 @@ 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(); @@ -205,9 +206,8 @@ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds { 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)); @@ -218,9 +218,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,9 +234,9 @@ 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(); @@ -244,8 +246,8 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl { 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)); @@ -255,10 +257,10 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl 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) @@ -281,9 +283,9 @@ double 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 { @@ -304,7 +306,7 @@ double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) 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; @@ -321,11 +323,12 @@ double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) switch(name) { case AL_SEC_OFFSET: - offset = (readPos + readPosFrac/double{MixerFracOne}) / BufferFmt->mSampleRate; + offset = static_cast(readPos) + readPosFrac/double{MixerFracOne}; + offset /= BufferFmt->mSampleRate; break; case AL_SAMPLE_OFFSET: - offset = readPos + readPosFrac/double{MixerFracOne}; + offset = static_cast(readPos) + readPosFrac/double{MixerFracOne}; break; case AL_BYTE_OFFSET: @@ -410,7 +413,8 @@ double GetSourceLength(const ALsource *source, ALenum name) struct VoicePos { - ALuint pos, frac; + int pos; + uint frac; ALbufferQueueItem *bufferitem; }; @@ -435,45 +439,67 @@ al::optional GetSampleOffset(al::deque &BufferList, return al::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(mind(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(mind(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; + Offset = std::floor(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; + Offset = std::floor(Offset / align / BufferFmt->channelsFromFmt()); + Offset *= BufferFmt->OriginalAlign; } else - offset /= BufferFmt->frameSizeFromFmt(); + Offset = std::floor(Offset / BufferFmt->channelsFromFmt()); + offset = static_cast(Offset); frac = 0; break; } /* Find the bufferlist item this offset belongs to. */ - ALuint totalBufferLen{0u}; + if(offset < 0) + { + if(offset < std::numeric_limits::min()) + return al::nullopt; + return VoicePos{static_cast(offset), frac, &BufferList.front()}; + } + + int64_t totalBufferLen{0}; for(auto &item : BufferList) { if(totalBufferLen > offset) @@ -481,7 +507,7 @@ 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; } @@ -525,7 +551,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; @@ -547,7 +573,7 @@ void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail) const bool connected{device->Connected.load(std::memory_order_acquire)}; device->waitForMix(); - if UNLIKELY(!connected) + if(!connected) [[unlikely]] { if(ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) { @@ -585,7 +611,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,6 +645,7 @@ 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()) newvoice->mFlags.set(VoiceIsFading); @@ -640,7 +667,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 @@ -693,14 +720,14 @@ bool EnsureSources(ALCcontext *context, size_t needed) while(needed > count) { - if UNLIKELY(context->mSourceList.size() >= 1<<25) + if(context->mSourceList.size() >= 1<<25) [[unlikely]] return false; context->mSourceList.emplace_back(); auto sublist = context->mSourceList.end() - 1; sublist->FreeMask = ~0_u64; sublist->Sources = static_cast(al_calloc(alignof(ALsource), sizeof(ALsource)*64)); - if UNLIKELY(!sublist->Sources) + if(!sublist->Sources) [[unlikely]] { context->mSourceList.pop_back(); return false; @@ -760,10 +787,10 @@ 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; } @@ -773,10 +800,10 @@ inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) noexcept const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; - if UNLIKELY(lidx >= device->BufferList.size()) + 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; } @@ -786,10 +813,10 @@ inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) noexcept const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; - if UNLIKELY(lidx >= device->FilterList.size()) + 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; } @@ -799,10 +826,10 @@ inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; - if UNLIKELY(lidx >= context->mEffectSlotList.size()) + 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; } @@ -812,8 +839,8 @@ al::optional StereoModeFromEnum(ALenum mode) { 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; @@ -832,9 +859,9 @@ al::optional SpatializeModeFromEnum(ALenum mode) { 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; @@ -854,9 +881,9 @@ al::optional DirectModeFromEnum(ALenum mode) { 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; @@ -876,13 +903,13 @@ al::optional DistanceModelFromALenum(ALenum model) { 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; } @@ -981,7 +1008,7 @@ enum SourceProp : ALenum { constexpr size_t MaxValues{6u}; -ALuint FloatValsByProp(ALenum prop) +constexpr ALuint FloatValsByProp(ALenum prop) { switch(static_cast(prop)) { @@ -1048,7 +1075,7 @@ ALuint FloatValsByProp(ALenum prop) } return 0; } -ALuint DoubleValsByProp(ALenum prop) +constexpr ALuint DoubleValsByProp(ALenum prop) { switch(static_cast(prop)) { @@ -1115,22 +1142,21 @@ 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); +void SetSourcefv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); +void SetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); +void SetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); + +struct check_exception : std::exception { +}; +struct check_size_exception final : check_exception { + const char *what() const noexcept override + { return "check_size_exception"; } +}; +struct check_value_exception final : check_exception { + const char *what() const noexcept override + { return "check_value_exception"; } +}; -#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) { @@ -1167,9 +1193,43 @@ inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) #endif -void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, - const al::span values) +/** + * Returns a pair of lambdas to check the following setters and getters. + * + * The first lambda checks the size of the span is valid for its given 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(ALCcontext *const Context, const SourceProp prop, const al::span values) { + return std::make_pair( + [=](size_t expect) -> void + { + if(values.size() == expect || values.size() == MaxValues) [[likely]] return; + Context->setError(AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu", + prop, expect, values.size()); + throw check_size_exception{}; + }, + [Context](bool passed) -> void + { + if(passed) [[likely]] return; + Context->setError(AL_INVALID_VALUE, "Value out of range"); + throw check_value_exception{}; + } + ); +} + +void SetSourcefv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, + const al::span values) +try { + /* Structured bindings would be nice (C++17). */ + auto Checkers = GetCheckers(Context, prop, values); + auto &CheckSize = Checkers.first; + auto &CheckValue = Checkers.second; int ival; switch(prop) @@ -1178,103 +1238,103 @@ void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, case AL_SEC_OFFSET_LATENCY_SOFT: case AL_SEC_OFFSET_CLOCK_SOFT: /* Query only */ - SETERR_RETURN(Context, AL_INVALID_OPERATION,, + return Context->setError(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] >= 0.0f); Source->Pitch = values[0]; return UpdateSourceProps(Source, Context); case AL_CONE_INNER_ANGLE: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f && values[0] <= 360.0f); + CheckSize(1); + CheckValue(values[0] >= 0.0f && values[0] <= 360.0f); Source->InnerAngle = 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] >= 0.0f && values[0] <= 360.0f); Source->OuterAngle = values[0]; return CommitAndUpdateSourceProps(Source, Context); case AL_GAIN: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + CheckValue(values[0] >= 0.0f); Source->Gain = values[0]; return UpdateSourceProps(Source, Context); case AL_MAX_DISTANCE: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + CheckValue(values[0] >= 0.0f); Source->MaxDistance = values[0]; return CommitAndUpdateSourceProps(Source, Context); case AL_ROLLOFF_FACTOR: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + CheckValue(values[0] >= 0.0f); Source->RolloffFactor = values[0]; return CommitAndUpdateSourceProps(Source, Context); case AL_REFERENCE_DISTANCE: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + CheckValue(values[0] >= 0.0f); Source->RefDistance = values[0]; return CommitAndUpdateSourceProps(Source, Context); case AL_MIN_GAIN: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + CheckValue(values[0] >= 0.0f); Source->MinGain = values[0]; return UpdateSourceProps(Source, Context); case AL_MAX_GAIN: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + CheckValue(values[0] >= 0.0f); Source->MaxGain = values[0]; return UpdateSourceProps(Source, Context); case AL_CONE_OUTER_GAIN: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f); + CheckSize(1); + CheckValue(values[0] >= 0.0f && values[0] <= 1.0f); Source->OuterGain = values[0]; return UpdateSourceProps(Source, Context); case AL_CONE_OUTER_GAINHF: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f); + CheckSize(1); + CheckValue(values[0] >= 0.0f && values[0] <= 1.0f); Source->OuterGainHF = values[0]; return UpdateSourceProps(Source, Context); case AL_AIR_ABSORPTION_FACTOR: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f && values[0] <= 10.0f); + CheckSize(1); + CheckValue(values[0] >= 0.0f && values[0] <= 10.0f); Source->AirAbsorptionFactor = values[0]; return UpdateSourceProps(Source, Context); case AL_ROOM_ROLLOFF_FACTOR: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f && values[0] <= 10.0f); + CheckSize(1); + CheckValue(values[0] >= 0.0f && values[0] <= 10.0f); Source->RoomRolloffFactor = values[0]; return UpdateSourceProps(Source, Context); case AL_DOPPLER_FACTOR: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f); + CheckSize(1); + CheckValue(values[0] >= 0.0f && values[0] <= 1.0f); Source->DopplerFactor = values[0]; return UpdateSourceProps(Source, Context); @@ -1282,13 +1342,13 @@ void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, case AL_SEC_OFFSET: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + 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"); + if(!vpos) return Context->setError(AL_INVALID_VALUE, "Invalid offset"); if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mALDevice.get())) return; @@ -1298,22 +1358,22 @@ void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, return; case AL_SOURCE_RADIUS: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f && std::isfinite(values[0])); + CheckSize(1); + CheckValue(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); + CheckSize(1); + CheckValue(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])); + CheckSize(2); + CheckValue(std::isfinite(values[0]) && std::isfinite(values[1])); Source->StereoPan[0] = values[0]; Source->StereoPan[1] = values[1]; @@ -1321,8 +1381,8 @@ void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, case AL_POSITION: - CHECKSIZE(values, 3); - CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); + CheckSize(3); + CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); Source->Position[0] = values[0]; Source->Position[1] = values[1]; @@ -1330,8 +1390,8 @@ void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, return CommitAndUpdateSourceProps(Source, Context); case AL_VELOCITY: - CHECKSIZE(values, 3); - CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); + CheckSize(3); + CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); Source->Velocity[0] = values[0]; Source->Velocity[1] = values[1]; @@ -1339,8 +1399,8 @@ void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, return CommitAndUpdateSourceProps(Source, Context); case AL_DIRECTION: - CHECKSIZE(values, 3); - CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); + CheckSize(3); + CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); Source->Direction[0] = values[0]; Source->Direction[1] = values[1]; @@ -1348,8 +1408,8 @@ void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, return CommitAndUpdateSourceProps(Source, Context); case AL_ORIENTATION: - CHECKSIZE(values, 6); - CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) + CheckSize(6); + CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) && std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5])); Source->OrientAt[0] = values[0]; @@ -1375,13 +1435,13 @@ void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, case AL_BYTE_LENGTH_SOFT: case AL_SAMPLE_LENGTH_SOFT: case AL_STEREO_MODE_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); ival = static_cast(values[0]); return SetSourceiv(Source, Context, prop, {&ival, 1u}); case AL_BUFFERS_QUEUED: case AL_BUFFERS_PROCESSED: - CHECKSIZE(values, 1); + CheckSize(1); ival = static_cast(static_cast(values[0])); return SetSourceiv(Source, Context, prop, {&ival, 1u}); @@ -1395,12 +1455,16 @@ void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, ERR("Unexpected property: 0x%04x\n", prop); Context->setError(AL_INVALID_ENUM, "Invalid source float property 0x%04x", prop); - return; +} +catch(check_exception&) { } -void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, +void SetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values) -{ +try { + auto Checkers = GetCheckers(Context, prop, values); + auto &CheckSize = Checkers.first; + auto &CheckValue = Checkers.second; ALCdevice *device{Context->mALDevice.get()}; ALeffectslot *slot{nullptr}; al::deque oldlist; @@ -1416,19 +1480,19 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, case AL_BYTE_LENGTH_SOFT: case AL_SAMPLE_LENGTH_SOFT: /* Query only */ - SETERR_RETURN(Context, AL_INVALID_OPERATION,, + return Context->setError(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); + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); Source->HeadRelative = values[0] != AL_FALSE; return CommitAndUpdateSourceProps(Source, Context); case AL_LOOPING: - CHECKSIZE(values, 1); - CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); Source->Looping = values[0] != AL_FALSE; if(Voice *voice{GetSourceVoice(Source, Context)}) @@ -1446,11 +1510,11 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, return; case AL_BUFFER: - CHECKSIZE(values, 1); + CheckSize(1); { const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; if(state == AL_PLAYING || state == AL_PAUSED) - SETERR_RETURN(Context, AL_INVALID_OPERATION,, + return Context->setError(AL_INVALID_OPERATION, "Setting buffer on playing or paused source %u", Source->id); } if(values[0]) @@ -1458,13 +1522,13 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, 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", + return Context->setError(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,, + return Context->setError(AL_INVALID_OPERATION, "Setting non-persistently mapped buffer %u", buffer->id); if(buffer->mCallback && ReadRef(buffer->ref) != 0) - SETERR_RETURN(Context, AL_INVALID_OPERATION,, + return Context->setError(AL_INVALID_OPERATION, "Setting already-set callback buffer %u", buffer->id); /* Add the selected buffer to a one-item queue */ @@ -1502,13 +1566,12 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, case AL_SEC_OFFSET: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0); + CheckSize(1); 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(!vpos) return Context->setError(AL_INVALID_VALUE, "Invalid source offset"); if(SetVoiceOffset(voice, *vpos, Source, Context, device)) return; @@ -1518,13 +1581,13 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, return; case AL_DIRECT_FILTER: - CHECKSIZE(values, 1); + CheckSize(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", + return Context->setError(AL_INVALID_VALUE, "Invalid filter ID %u", static_cast(values[0])); Source->Direct.Gain = filter->Gain; Source->Direct.GainHF = filter->GainHF; @@ -1543,28 +1606,28 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, return UpdateSourceProps(Source, Context); case AL_DIRECT_FILTER_GAINHF_AUTO: - CHECKSIZE(values, 1); - CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); Source->DryGainHFAuto = values[0] != AL_FALSE; return UpdateSourceProps(Source, Context); case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - CHECKSIZE(values, 1); - CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); Source->WetGainAuto = values[0] != AL_FALSE; return UpdateSourceProps(Source, Context); case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - CHECKSIZE(values, 1); - CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); Source->WetGainHFAuto = values[0] != AL_FALSE; return UpdateSourceProps(Source, Context); case AL_DIRECT_CHANNELS_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); if(auto mode = DirectModeFromEnum(values[0])) { Source->DirectChannels = *mode; @@ -1575,7 +1638,7 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, return; case AL_DISTANCE_MODEL: - CHECKSIZE(values, 1); + CheckSize(1); if(auto model = DistanceModelFromALenum(values[0])) { Source->mDistanceModel = *model; @@ -1587,14 +1650,14 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, return; case AL_SOURCE_RESAMPLER_SOFT: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0 && values[0] <= static_cast(Resampler::Max)); + CheckSize(1); + CheckValue(values[0] >= 0 && values[0] <= static_cast(Resampler::Max)); Source->mResampler = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_SOURCE_SPATIALIZE_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); if(auto mode = SpatializeModeFromEnum(values[0])) { Source->mSpatialize = *mode; @@ -1605,11 +1668,11 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, return; case AL_STEREO_MODE_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); { const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; if(state == AL_PLAYING || state == AL_PAUSED) - SETERR_RETURN(Context, AL_INVALID_OPERATION,, + return Context->setError(AL_INVALID_OPERATION, "Modifying stereo mode on playing or paused source %u", Source->id); } if(auto mode = StereoModeFromEnum(values[0])) @@ -1622,19 +1685,19 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, return; case AL_AUXILIARY_SEND_FILTER: - CHECKSIZE(values, 3); + CheckSize(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]); + return Context->setError(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]); + return Context->setError(AL_INVALID_VALUE, "Invalid send %u", values[1]); if(values[2]) { std::lock_guard _{device->FilterLock}; ALfilter *filter{LookupFilter(device, static_cast(values[2]))}; if(!filter) - SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid filter ID %u", values[2]); + return Context->setError(AL_INVALID_VALUE, "Invalid filter ID %u", values[2]); auto &send = Source->Send[static_cast(values[1])]; send.Gain = filter->Gain; @@ -1654,7 +1717,13 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, send.LFReference = HIGHPASSFREQREF; } - if(slot != Source->Send[static_cast(values[1])].Slot && IsPlayingOrPaused(Source)) + /* We must force an update if the current auxiliary slot is valid and + * about to be changed on an active source, in case the old slot is + * about to be deleted. + */ + if(Source->Send[static_cast(values[1])].Slot + && slot != Source->Send[static_cast(values[1])].Slot + && IsPlayingOrPaused(Source)) { /* Add refcount on the new slot, and release the previous slot */ if(slot) IncrementRef(slot->ref); @@ -1662,9 +1731,6 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, 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; @@ -1698,7 +1764,7 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, case AL_SOURCE_RADIUS: case AL_SEC_LENGTH_SOFT: case AL_SUPER_STEREO_WIDTH_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); fvals[0] = static_cast(values[0]); return SetSourcefv(Source, Context, prop, {fvals, 1u}); @@ -1706,7 +1772,7 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, case AL_POSITION: case AL_VELOCITY: case AL_DIRECTION: - CHECKSIZE(values, 3); + CheckSize(3); fvals[0] = static_cast(values[0]); fvals[1] = static_cast(values[1]); fvals[2] = static_cast(values[2]); @@ -1714,7 +1780,7 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, /* 6x float */ case AL_ORIENTATION: - CHECKSIZE(values, 6); + CheckSize(6); fvals[0] = static_cast(values[0]); fvals[1] = static_cast(values[1]); fvals[2] = static_cast(values[2]); @@ -1733,12 +1799,16 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, ERR("Unexpected property: 0x%04x\n", prop); Context->setError(AL_INVALID_ENUM, "Invalid source integer property 0x%04x", prop); - return; +} +catch(check_exception&) { } -void SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, +void SetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values) -{ +try { + auto Checkers = GetCheckers(Context, prop, values); + auto &CheckSize = Checkers.first; + auto &CheckValue = Checkers.second; float fvals[MaxValues]; int ivals[MaxValues]; @@ -1753,7 +1823,7 @@ void SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, case AL_SAMPLE_OFFSET_LATENCY_SOFT: case AL_SAMPLE_OFFSET_CLOCK_SOFT: /* Query only */ - SETERR_RETURN(Context, AL_INVALID_OPERATION,, + return Context->setError(AL_INVALID_OPERATION, "Setting read-only source property 0x%04x", prop); /* 1x int */ @@ -1770,8 +1840,8 @@ void SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, 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); + CheckSize(1); + CheckValue(values[0] <= INT_MAX && values[0] >= INT_MIN); ivals[0] = static_cast(values[0]); return SetSourceiv(Source, Context, prop, {ivals, 1u}); @@ -1779,17 +1849,17 @@ void SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, /* 1x uint */ case AL_BUFFER: case AL_DIRECT_FILTER: - CHECKSIZE(values, 1); - CHECKVAL(values[0] <= UINT_MAX && values[0] >= 0); + CheckSize(1); + CheckValue(values[0] <= UINT_MAX && values[0] >= 0); ivals[0] = static_cast(values[0]); return SetSourceiv(Source, Context, prop, {ivals, 1u}); /* 3x uint */ case AL_AUXILIARY_SEND_FILTER: - CHECKSIZE(values, 3); - CHECKVAL(values[0] <= UINT_MAX && values[0] >= 0 && values[1] <= UINT_MAX && values[1] >= 0 - && values[2] <= UINT_MAX && values[2] >= 0); + CheckSize(3); + CheckValue(values[0] <= UINT_MAX && values[0] >= 0 && values[1] <= UINT_MAX + && values[1] >= 0 && values[2] <= UINT_MAX && values[2] >= 0); ivals[0] = static_cast(values[0]); ivals[1] = static_cast(values[1]); @@ -1814,7 +1884,7 @@ void SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, case AL_SOURCE_RADIUS: case AL_SEC_LENGTH_SOFT: case AL_SUPER_STEREO_WIDTH_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); fvals[0] = static_cast(values[0]); return SetSourcefv(Source, Context, prop, {fvals, 1u}); @@ -1822,7 +1892,7 @@ void SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, case AL_POSITION: case AL_VELOCITY: case AL_DIRECTION: - CHECKSIZE(values, 3); + CheckSize(3); fvals[0] = static_cast(values[0]); fvals[1] = static_cast(values[1]); fvals[2] = static_cast(values[2]); @@ -1830,7 +1900,7 @@ void SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, /* 6x float */ case AL_ORIENTATION: - CHECKSIZE(values, 6); + CheckSize(6); fvals[0] = static_cast(values[0]); fvals[1] = static_cast(values[1]); fvals[2] = static_cast(values[2]); @@ -1847,26 +1917,31 @@ void SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, ERR("Unexpected property: 0x%04x\n", prop); Context->setError(AL_INVALID_ENUM, "Invalid source integer64 property 0x%04x", prop); - return; +} +catch(check_exception&) { } -#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) +template +auto GetSizeChecker(ALCcontext *const Context, const SourceProp prop, const al::span values) { + return [=](size_t expect) -> void + { + if(values.size() == expect || values.size() == MaxValues) [[likely]] return; + Context->setError(AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu", + prop, expect, values.size()); + throw check_size_exception{}; + }; +} + +bool GetSourcedv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); +bool GetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); +bool GetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); + +bool GetSourcedv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, + const al::span values) +try { + auto CheckSize = GetSizeChecker(Context, prop, values); ALCdevice *device{Context->mALDevice.get()}; ClockLatency clocktime; nanoseconds srcclock; @@ -1876,107 +1951,107 @@ bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a switch(prop) { case AL_GAIN: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->Gain; return true; case AL_PITCH: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->Pitch; return true; case AL_MAX_DISTANCE: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->MaxDistance; return true; case AL_ROLLOFF_FACTOR: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->RolloffFactor; return true; case AL_REFERENCE_DISTANCE: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->RefDistance; return true; case AL_CONE_INNER_ANGLE: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->InnerAngle; return true; case AL_CONE_OUTER_ANGLE: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->OuterAngle; return true; case AL_MIN_GAIN: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->MinGain; return true; case AL_MAX_GAIN: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->MaxGain; return true; case AL_CONE_OUTER_GAIN: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->OuterGain; return true; case AL_SEC_OFFSET: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = GetSourceOffset(Source, prop, Context); return true; case AL_CONE_OUTER_GAINHF: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->OuterGainHF; return true; case AL_AIR_ABSORPTION_FACTOR: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->AirAbsorptionFactor; return true; case AL_ROOM_ROLLOFF_FACTOR: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->RoomRolloffFactor; return true; case AL_DOPPLER_FACTOR: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->DopplerFactor; return true; case AL_SOURCE_RADIUS: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->Radius; return true; case AL_SUPER_STEREO_WIDTH_SOFT: - CHECKSIZE(values, 1); + CheckSize(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); + CheckSize(1); values[0] = GetSourceLength(Source, prop); return true; case AL_STEREO_ANGLES: - CHECKSIZE(values, 2); + CheckSize(2); values[0] = Source->StereoPan[0]; values[1] = Source->StereoPan[1]; return true; case AL_SEC_OFFSET_LATENCY_SOFT: - CHECKSIZE(values, 2); + CheckSize(2); /* Get the source offset with the clock time first. Then get the clock * time with the device latency. Order is important. */ @@ -1999,34 +2074,34 @@ bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a return true; case AL_SEC_OFFSET_CLOCK_SOFT: - CHECKSIZE(values, 2); + CheckSize(2); values[0] = GetSourceSecOffset(Source, Context, &srcclock); values[1] = static_cast(srcclock.count()) / 1000000000.0; return true; case AL_POSITION: - CHECKSIZE(values, 3); + CheckSize(3); values[0] = Source->Position[0]; values[1] = Source->Position[1]; values[2] = Source->Position[2]; return true; case AL_VELOCITY: - CHECKSIZE(values, 3); + CheckSize(3); values[0] = Source->Velocity[0]; values[1] = Source->Velocity[1]; values[2] = Source->Velocity[2]; return true; case AL_DIRECTION: - CHECKSIZE(values, 3); + CheckSize(3); values[0] = Source->Direction[0]; values[1] = Source->Direction[1]; values[2] = Source->Direction[2]; return true; case AL_ORIENTATION: - CHECKSIZE(values, 6); + CheckSize(6); values[0] = Source->OrientAt[0]; values[1] = Source->OrientAt[1]; values[2] = Source->OrientAt[2]; @@ -2050,7 +2125,7 @@ bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: case AL_STEREO_MODE_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) values[0] = static_cast(ivals[0]); return err; @@ -2067,26 +2142,31 @@ bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a Context->setError(AL_INVALID_ENUM, "Invalid source double property 0x%04x", prop); return false; } +catch(check_exception&) { + return false; +} -bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values) -{ +bool GetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, + const al::span values) +try { + auto CheckSize = GetSizeChecker(Context, prop, values); double dvals[MaxValues]; bool err; switch(prop) { case AL_SOURCE_RELATIVE: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->HeadRelative; return true; case AL_LOOPING: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->Looping; return true; case AL_BUFFER: - CHECKSIZE(values, 1); + CheckSize(1); { ALbufferQueueItem *BufferList{(Source->SourceType == AL_STATIC) ? &Source->mQueue.front() : nullptr}; @@ -2096,17 +2176,17 @@ bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a return true; case AL_SOURCE_STATE: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = GetSourceState(Source, GetSourceVoice(Source, Context)); return true; case AL_BUFFERS_QUEUED: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = static_cast(Source->mQueue.size()); return true; case AL_BUFFERS_PROCESSED: - CHECKSIZE(values, 1); + CheckSize(1); if(Source->Looping || Source->SourceType != AL_STREAMING) { /* Buffers on a looping source are in a perpetual state of PENDING, @@ -2134,55 +2214,55 @@ bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a return true; case AL_SOURCE_TYPE: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->SourceType; return true; case AL_DIRECT_FILTER_GAINHF_AUTO: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->DryGainHFAuto; return true; case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->WetGainAuto; return true; case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = Source->WetGainHFAuto; return true; case AL_DIRECT_CHANNELS_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = EnumFromDirectMode(Source->DirectChannels); return true; case AL_DISTANCE_MODEL: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = ALenumFromDistanceModel(Source->mDistanceModel); return true; case AL_BYTE_LENGTH_SOFT: case AL_SAMPLE_LENGTH_SOFT: case AL_SEC_LENGTH_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = static_cast(mind(GetSourceLength(Source, prop), std::numeric_limits::max())); return true; case AL_SOURCE_RESAMPLER_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = static_cast(Source->mResampler); return true; case AL_SOURCE_SPATIALIZE_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = EnumFromSpatializeMode(Source->mSpatialize); return true; case AL_STEREO_MODE_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = EnumFromStereoMode(Source->mStereoMode); return true; @@ -2206,7 +2286,7 @@ bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a case AL_CONE_OUTER_GAINHF: case AL_SOURCE_RADIUS: case AL_SUPER_STEREO_WIDTH_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false) values[0] = static_cast(dvals[0]); return err; @@ -2215,7 +2295,7 @@ bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a case AL_POSITION: case AL_VELOCITY: case AL_DIRECTION: - CHECKSIZE(values, 3); + CheckSize(3); if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false) { values[0] = static_cast(dvals[0]); @@ -2226,7 +2306,7 @@ bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a /* 6x float/double */ case AL_ORIENTATION: - CHECKSIZE(values, 6); + CheckSize(6); if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false) { values[0] = static_cast(dvals[0]); @@ -2256,9 +2336,14 @@ bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a Context->setError(AL_INVALID_ENUM, "Invalid source integer property 0x%04x", prop); return false; } +catch(check_exception&) { + return false; +} -bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values) -{ +bool GetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, + const al::span values) +try { + auto CheckSize = GetSizeChecker(Context, prop, values); ALCdevice *device{Context->mALDevice.get()}; ClockLatency clocktime; nanoseconds srcclock; @@ -2271,12 +2356,12 @@ bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const case AL_BYTE_LENGTH_SOFT: case AL_SAMPLE_LENGTH_SOFT: case AL_SEC_LENGTH_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); values[0] = static_cast(GetSourceLength(Source, prop)); return true; case AL_SAMPLE_OFFSET_LATENCY_SOFT: - CHECKSIZE(values, 2); + CheckSize(2); /* Get the source offset with the clock time first. Then get the clock * time with the device latency. Order is important. */ @@ -2298,7 +2383,7 @@ bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const return true; case AL_SAMPLE_OFFSET_CLOCK_SOFT: - CHECKSIZE(values, 2); + CheckSize(2); values[0] = GetSourceSampleOffset(Source, Context, &srcclock); values[1] = srcclock.count(); return true; @@ -2323,7 +2408,7 @@ bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const case AL_CONE_OUTER_GAINHF: case AL_SOURCE_RADIUS: case AL_SUPER_STEREO_WIDTH_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false) values[0] = static_cast(dvals[0]); return err; @@ -2332,7 +2417,7 @@ bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const case AL_POSITION: case AL_VELOCITY: case AL_DIRECTION: - CHECKSIZE(values, 3); + CheckSize(3); if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false) { values[0] = static_cast(dvals[0]); @@ -2343,7 +2428,7 @@ bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const /* 6x float/double */ case AL_ORIENTATION: - CHECKSIZE(values, 6); + CheckSize(6); if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false) { values[0] = static_cast(dvals[0]); @@ -2370,7 +2455,7 @@ bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: case AL_STEREO_MODE_SOFT: - CHECKSIZE(values, 1); + CheckSize(1); if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) values[0] = ivals[0]; return err; @@ -2378,14 +2463,14 @@ bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const /* 1x uint */ case AL_BUFFER: case AL_DIRECT_FILTER: - CHECKSIZE(values, 1); + CheckSize(1); if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) values[0] = static_cast(ivals[0]); return err; /* 3x uint */ case AL_AUXILIARY_SEND_FILTER: - CHECKSIZE(values, 3); + CheckSize(3); if((err=GetSourceiv(Source, Context, prop, {ivals, 3u})) != false) { values[0] = static_cast(ivals[0]); @@ -2405,6 +2490,178 @@ bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const Context->setError(AL_INVALID_ENUM, "Invalid source integer64 property 0x%04x", prop); return false; } +catch(check_exception&) { + return false; +} + + +void StartSources(ALCcontext *const context, const al::span srchandles, + const nanoseconds start_time=nanoseconds::min()) +{ + ALCdevice *device{context->mALDevice.get()}; + /* If the device is disconnected, and voices stop on disconnect, go right + * to stopped. + */ + if(!device->Connected.load(std::memory_order_acquire)) [[unlikely]] + { + if(context->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) + { + for(ALsource *source : srchandles) + { + /* TODO: Send state change event? */ + source->Offset = 0.0; + source->OffsetType = AL_NONE; + source->state = AL_STOPPED; + } + return; + } + } + + /* Count the number of reusable voices. */ + auto voicelist = context->getVoicesSpan(); + size_t free_voices{0}; + for(const Voice *voice : voicelist) + { + free_voices += (voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped + && voice->mSourceID.load(std::memory_order_relaxed) == 0u + && voice->mPendingChange.load(std::memory_order_relaxed) == false); + if(free_voices == srchandles.size()) + break; + } + if(srchandles.size() != free_voices) [[unlikely]] + { + const size_t inc_amount{srchandles.size() - free_voices}; + auto &allvoices = *context->mVoices.load(std::memory_order_relaxed); + if(inc_amount > allvoices.size() - voicelist.size()) + { + /* Increase the number of voices to handle the request. */ + context->allocVoices(inc_amount - (allvoices.size() - voicelist.size())); + } + context->mActiveVoiceCount.fetch_add(inc_amount, std::memory_order_release); + voicelist = context->getVoicesSpan(); + } + + auto voiceiter = voicelist.begin(); + ALuint vidx{0}; + VoiceChange *tail{}, *cur{}; + for(ALsource *source : srchandles) + { + /* Check that there is a queue containing at least one valid, non zero + * length buffer. + */ + 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(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 + * there's no need to look up its voice and clear the source. + */ + source->Offset = 0.0; + source->OffsetType = AL_NONE; + source->state = AL_STOPPED; + continue; + } + + if(!cur) + cur = tail = GetVoiceChanger(context); + else + { + cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed); + cur = cur->mNext.load(std::memory_order_relaxed); + } + + Voice *voice{GetSourceVoice(source, context)}; + switch(GetSourceState(source, voice)) + { + case AL_PAUSED: + /* A source that's paused simply resumes. If there's no voice, it + * was lost from a disconnect, so just start over with a new one. + */ + cur->mOldVoice = nullptr; + if(!voice) break; + cur->mVoice = voice; + cur->mSourceID = source->id; + cur->mState = VChangeState::Play; + source->state = AL_PLAYING; +#ifdef ALSOFT_EAX + if(source->eax_is_initialized()) + source->eax_commit(); +#endif // ALSOFT_EAX + continue; + + case AL_PLAYING: + /* A source that's already playing is restarted from the beginning. + * Stop the current voice and start a new one so it properly cross- + * fades back to the beginning. + */ + if(voice) + voice->mPendingChange.store(true, std::memory_order_relaxed); + cur->mOldVoice = voice; + voice = nullptr; + break; + + default: + assert(voice == nullptr); + cur->mOldVoice = nullptr; +#ifdef ALSOFT_EAX + if(source->eax_is_initialized()) + source->eax_commit(); +#endif // ALSOFT_EAX + break; + } + + /* Find the next unused voice to play this source with. */ + for(;voiceiter != voicelist.end();++voiceiter,++vidx) + { + Voice *v{*voiceiter}; + if(v->mPlayState.load(std::memory_order_acquire) == Voice::Stopped + && v->mSourceID.load(std::memory_order_relaxed) == 0u + && v->mPendingChange.load(std::memory_order_relaxed) == false) + { + voice = v; + break; + } + } + ASSUME(voice != nullptr); + + 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. + */ + if(const ALenum offsettype{source->OffsetType}) + { + const double offset{source->Offset}; + source->OffsetType = AL_NONE; + source->Offset = 0.0; + if(auto vpos = GetSampleOffset(source->mQueue, offsettype, offset)) + { + voice->mPosition.store(vpos->pos, std::memory_order_relaxed); + voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed); + voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_relaxed); + if(vpos->pos!=0 || vpos->frac!=0 || vpos->bufferitem!=&source->mQueue.front()) + voice->mFlags.set(VoiceIsFading); + } + } + InitVoice(voice, source, al::to_address(BufferList), context, device); + + source->VoiceIdx = vidx; + source->state = AL_PLAYING; + + cur->mVoice = voice; + cur->mSourceID = source->id; + cur->mState = VChangeState::Play; + } + if(tail) [[likely]] + SendVoiceChanges(context, tail); +} } // namespace @@ -2412,11 +2669,11 @@ AL_API void AL_APIENTRY alGenSources(ALsizei n, ALuint *sources) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; - if UNLIKELY(n < 0) + if(n < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Generating %d sources", n); - if UNLIKELY(n <= 0) return; + if(n <= 0) [[unlikely]] return; #ifdef ALSOFT_EAX const bool has_eax{context->has_eax()}; @@ -2481,10 +2738,11 @@ AL_API void AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; - if UNLIKELY(n < 0) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Deleting %d sources", n); + if(n < 0) [[unlikely]] + context->setError(AL_INVALID_VALUE, "Deleting %d sources", n); + if(n <= 0) [[unlikely]] return; std::lock_guard _{context->mSourceLock}; @@ -2494,11 +2752,8 @@ START_API_FUNC const ALuint *sources_end = sources + n; auto invsrc = std::find_if_not(sources, sources_end, validate_source); - if UNLIKELY(invsrc != sources_end) - { - context->setError(AL_INVALID_NAME, "Invalid source ID %u", *invsrc); - return; - } + if(invsrc != sources_end) [[unlikely]] + return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *invsrc); /* All good. Delete source IDs. */ auto delete_source = [&context](const ALuint sid) -> void @@ -2514,7 +2769,7 @@ AL_API ALboolean AL_APIENTRY alIsSource(ALuint source) START_API_FUNC { ContextRef context{GetContextRef()}; - if LIKELY(context) + if(context) [[likely]] { std::lock_guard _{context->mSourceLock}; if(LookupSource(context.get(), source) != nullptr) @@ -2529,12 +2784,12 @@ AL_API void AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; std::lock_guard __{context->mSourceLock}; ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); else SetSourcefv(Source, context.get(), static_cast(param), {&value, 1u}); @@ -2545,12 +2800,12 @@ AL_API void AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; std::lock_guard __{context->mSourceLock}; ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); else { @@ -2564,14 +2819,14 @@ AL_API void AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *v START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; std::lock_guard __{context->mSourceLock}; ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) + else if(!values) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else SetSourcefv(Source, context.get(), static_cast(param), {values, MaxValues}); @@ -2583,12 +2838,12 @@ AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble valu START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; std::lock_guard __{context->mSourceLock}; ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); else { @@ -2602,12 +2857,12 @@ AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble val START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; std::lock_guard __{context->mSourceLock}; ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); else { @@ -2622,14 +2877,14 @@ AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdoub START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; std::lock_guard __{context->mSourceLock}; ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) + else if(!values) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else { @@ -2647,12 +2902,12 @@ AL_API void AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; std::lock_guard __{context->mSourceLock}; ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); else SetSourceiv(Source, context.get(), static_cast(param), {&value, 1u}); @@ -2663,12 +2918,12 @@ AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, AL START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; std::lock_guard __{context->mSourceLock}; ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); else { @@ -2682,14 +2937,14 @@ AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *val START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; std::lock_guard __{context->mSourceLock}; ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) + else if(!values) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else SetSourceiv(Source, context.get(), static_cast(param), {values, MaxValues}); @@ -2701,12 +2956,12 @@ AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; std::lock_guard __{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); else SetSourcei64v(Source, context.get(), static_cast(param), {&value, 1u}); @@ -2717,12 +2972,12 @@ AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOF START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; std::lock_guard __{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); else { @@ -2736,14 +2991,14 @@ AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALin START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; std::lock_guard __{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) + else if(!values) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else SetSourcei64v(Source, context.get(), static_cast(param), {values, MaxValues}); @@ -2755,13 +3010,13 @@ AL_API void AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!value) + else if(!value) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else { @@ -2776,13 +3031,13 @@ AL_API void AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *valu START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!(value1 && value2 && value3)) + else if(!(value1 && value2 && value3)) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else { @@ -2801,13 +3056,13 @@ AL_API void AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *valu START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) + else if(!values) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else { @@ -2827,13 +3082,13 @@ AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble * START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!value) + else if(!value) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else GetSourcedv(Source, context.get(), static_cast(param), {value, 1u}); @@ -2844,13 +3099,13 @@ AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!(value1 && value2 && value3)) + else if(!(value1 && value2 && value3)) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else { @@ -2869,13 +3124,13 @@ AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) + else if(!values) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else GetSourcedv(Source, context.get(), static_cast(param), {values, MaxValues}); @@ -2887,13 +3142,13 @@ AL_API void AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!value) + else if(!value) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else GetSourceiv(Source, context.get(), static_cast(param), {value, 1u}); @@ -2904,13 +3159,13 @@ AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1 START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!(value1 && value2 && value3)) + else if(!(value1 && value2 && value3)) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else { @@ -2929,13 +3184,13 @@ AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) + else if(!values) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else GetSourceiv(Source, context.get(), static_cast(param), {values, MaxValues}); @@ -2947,13 +3202,13 @@ AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64S START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!value) + else if(!value) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else GetSourcei64v(Source, context.get(), static_cast(param), {value, 1u}); @@ -2964,13 +3219,13 @@ AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64 START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!(value1 && value2 && value3)) + else if(!(value1 && value2 && value3)) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else { @@ -2989,13 +3244,13 @@ AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64 START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mSourceLock}; ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) + if(!Source) [[unlikely]] context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) + else if(!values) [[unlikely]] context->setError(AL_INVALID_VALUE, "NULL pointer"); else GetSourcei64v(Source, context.get(), static_cast(param), {values, MaxValues}); @@ -3005,23 +3260,88 @@ END_API_FUNC AL_API void AL_APIENTRY alSourcePlay(ALuint source) START_API_FUNC -{ alSourcePlayv(1, &source); } +{ + ContextRef context{GetContextRef()}; + if(!context) [[unlikely]] return; + + std::lock_guard _{context->mSourceLock}; + ALsource *srchandle{LookupSource(context.get(), source)}; + if(!srchandle) + return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + + StartSources(context.get(), {&srchandle, 1}); +} +END_API_FUNC + +void AL_APIENTRY alSourcePlayAtTimeSOFT(ALuint source, ALint64SOFT start_time) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if(!context) [[unlikely]] return; + + if(start_time < 0) [[unlikely]] + return context->setError(AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time); + + std::lock_guard _{context->mSourceLock}; + ALsource *srchandle{LookupSource(context.get(), source)}; + if(!srchandle) + return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + + StartSources(context.get(), {&srchandle, 1}, nanoseconds{start_time}); +} 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(!context) [[unlikely]] return; - if UNLIKELY(n < 0) + if(n < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Playing %d sources", n); - if UNLIKELY(n <= 0) return; + if(n <= 0) [[unlikely]] return; al::vector extra_sources; std::array source_storage; al::span srchandles; - if LIKELY(static_cast(n) <= source_storage.size()) + if(static_cast(n) <= source_storage.size()) [[likely]] + 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) [[unlikely]] + return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources); + ++sources; + } + + StartSources(context.get(), srchandles); +} +END_API_FUNC + +void AL_APIENTRY alSourcePlayAtTimevSOFT(ALsizei n, const ALuint *sources, ALint64SOFT start_time) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if(!context) [[unlikely]] return; + + if(n < 0) [[unlikely]] + context->setError(AL_INVALID_VALUE, "Playing %d sources", n); + if(n <= 0) [[unlikely]] return; + + if(start_time < 0) [[unlikely]] + return context->setError(AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time); + + al::vector extra_sources; + std::array source_storage; + al::span srchandles; + if(static_cast(n) <= source_storage.size()) [[likely]] srchandles = {source_storage.data(), static_cast(n)}; else { @@ -3034,175 +3354,11 @@ START_API_FUNC { srchdl = LookupSource(context.get(), *sources); if(!srchdl) - SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources); + return context->setError(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(context->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) - { - for(ALsource *source : srchandles) - { - /* TODO: Send state change event? */ - source->Offset = 0.0; - source->OffsetType = AL_NONE; - source->state = AL_STOPPED; - } - return; - } - } - - /* Count the number of reusable voices. */ - auto voicelist = context->getVoicesSpan(); - size_t free_voices{0}; - for(const Voice *voice : voicelist) - { - free_voices += (voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped - && voice->mSourceID.load(std::memory_order_relaxed) == 0u - && voice->mPendingChange.load(std::memory_order_relaxed) == false); - if(free_voices == srchandles.size()) - break; - } - if UNLIKELY(srchandles.size() != free_voices) - { - const size_t inc_amount{srchandles.size() - free_voices}; - auto &allvoices = *context->mVoices.load(std::memory_order_relaxed); - if(inc_amount > allvoices.size() - voicelist.size()) - { - /* Increase the number of voices to handle the request. */ - context->allocVoices(inc_amount - (allvoices.size() - voicelist.size())); - } - context->mActiveVoiceCount.fetch_add(inc_amount, std::memory_order_release); - voicelist = context->getVoicesSpan(); - } - - auto voiceiter = voicelist.begin(); - ALuint vidx{0}; - VoiceChange *tail{}, *cur{}; - for(ALsource *source : srchandles) - { - /* 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; - } - - /* If there's nothing to play, go right to stopped. */ - if UNLIKELY(BufferList == source->mQueue.end()) - { - /* NOTE: A source without any playable buffers should not have a - * Voice since it shouldn't be in a playing or paused state. So - * there's no need to look up its voice and clear the source. - */ - source->Offset = 0.0; - source->OffsetType = AL_NONE; - source->state = AL_STOPPED; - continue; - } - - if(!cur) - cur = tail = GetVoiceChanger(context.get()); - else - { - cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed); - cur = cur->mNext.load(std::memory_order_relaxed); - } - - Voice *voice{GetSourceVoice(source, context.get())}; - switch(GetSourceState(source, voice)) - { - case AL_PAUSED: - /* A source that's paused simply resumes. If there's no voice, it - * was lost from a disconnect, so just start over with a new one. - */ - cur->mOldVoice = nullptr; - if(!voice) break; - cur->mVoice = voice; - cur->mSourceID = source->id; - cur->mState = VChangeState::Play; - source->state = AL_PLAYING; -#ifdef ALSOFT_EAX - if(source->eax_is_initialized()) - source->eax_commit(); -#endif // ALSOFT_EAX - continue; - - case AL_PLAYING: - /* A source that's already playing is restarted from the beginning. - * Stop the current voice and start a new one so it properly cross- - * fades back to the beginning. - */ - if(voice) - voice->mPendingChange.store(true, std::memory_order_relaxed); - cur->mOldVoice = voice; - voice = nullptr; - break; - - default: - assert(voice == nullptr); - cur->mOldVoice = nullptr; -#ifdef ALSOFT_EAX - if(source->eax_is_initialized()) - source->eax_commit(); -#endif // ALSOFT_EAX - break; - } - - /* Find the next unused voice to play this source with. */ - for(;voiceiter != voicelist.end();++voiceiter,++vidx) - { - Voice *v{*voiceiter}; - if(v->mPlayState.load(std::memory_order_acquire) == Voice::Stopped - && v->mSourceID.load(std::memory_order_relaxed) == 0u - && v->mPendingChange.load(std::memory_order_relaxed) == false) - { - voice = v; - break; - } - } - ASSUME(voice != nullptr); - - voice->mPosition.store(0u, std::memory_order_relaxed); - voice->mPositionFrac.store(0, std::memory_order_relaxed); - voice->mCurrentBuffer.store(&source->mQueue.front(), std::memory_order_relaxed); - voice->mFlags.reset(); - /* A source that's not playing or paused has any offset applied when it - * starts playing. - */ - if(const ALenum offsettype{source->OffsetType}) - { - const double offset{source->Offset}; - source->OffsetType = AL_NONE; - source->Offset = 0.0; - if(auto vpos = GetSampleOffset(source->mQueue, offsettype, offset)) - { - voice->mPosition.store(vpos->pos, std::memory_order_relaxed); - voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed); - voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_relaxed); - if(vpos->pos!=0 || vpos->frac!=0 || vpos->bufferitem!=&source->mQueue.front()) - voice->mFlags.set(VoiceIsFading); - } - } - InitVoice(voice, source, std::addressof(*BufferList), context.get(), device); - - source->VoiceIdx = vidx; - source->state = AL_PLAYING; - - cur->mVoice = voice; - cur->mSourceID = source->id; - cur->mState = VChangeState::Play; - } - if LIKELY(tail) - SendVoiceChanges(context.get(), tail); + StartSources(context.get(), srchandles, nanoseconds{start_time}); } END_API_FUNC @@ -3216,16 +3372,16 @@ AL_API void AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; - if UNLIKELY(n < 0) + if(n < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Pausing %d sources", n); - if UNLIKELY(n <= 0) return; + if(n <= 0) [[unlikely]] return; al::vector extra_sources; std::array source_storage; al::span srchandles; - if LIKELY(static_cast(n) <= source_storage.size()) + if(static_cast(n) <= source_storage.size()) [[likely]] srchandles = {source_storage.data(), static_cast(n)}; else { @@ -3238,7 +3394,7 @@ START_API_FUNC { srchdl = LookupSource(context.get(), *sources); if(!srchdl) - SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources); + return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources); ++sources; } @@ -3264,7 +3420,7 @@ START_API_FUNC cur->mState = VChangeState::Pause; } } - if LIKELY(tail) + if(tail) [[likely]] { SendVoiceChanges(context.get(), tail); /* Second, now that the voice changes have been sent, because it's @@ -3292,16 +3448,16 @@ AL_API void AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources) START_API_FUNC { 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 sources", n); - if UNLIKELY(n <= 0) return; + if(n <= 0) [[unlikely]] return; al::vector extra_sources; std::array source_storage; al::span srchandles; - if LIKELY(static_cast(n) <= source_storage.size()) + if(static_cast(n) <= source_storage.size()) [[likely]] srchandles = {source_storage.data(), static_cast(n)}; else { @@ -3314,7 +3470,7 @@ START_API_FUNC { srchdl = LookupSource(context.get(), *sources); if(!srchdl) - SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources); + return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources); ++sources; } @@ -3340,7 +3496,7 @@ START_API_FUNC source->OffsetType = AL_NONE; source->VoiceIdx = INVALID_VOICE_IDX; } - if LIKELY(tail) + if(tail) [[likely]] SendVoiceChanges(context.get(), tail); } END_API_FUNC @@ -3355,16 +3511,16 @@ AL_API void AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; - if UNLIKELY(n < 0) + if(n < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Rewinding %d sources", n); - if UNLIKELY(n <= 0) return; + if(n <= 0) [[unlikely]] return; al::vector extra_sources; std::array source_storage; al::span srchandles; - if LIKELY(static_cast(n) <= source_storage.size()) + if(static_cast(n) <= source_storage.size()) [[likely]] srchandles = {source_storage.data(), static_cast(n)}; else { @@ -3377,7 +3533,7 @@ START_API_FUNC { srchdl = LookupSource(context.get(), *sources); if(!srchdl) - SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources); + return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources); ++sources; } @@ -3405,7 +3561,7 @@ START_API_FUNC source->OffsetType = AL_NONE; source->VoiceIdx = INVALID_VOICE_IDX; } - if LIKELY(tail) + if(tail) [[likely]] SendVoiceChanges(context.get(), tail); } END_API_FUNC @@ -3415,20 +3571,20 @@ AL_API void AL_APIENTRY alSourceQueueBuffers(ALuint src, ALsizei nb, const ALuin START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; - if UNLIKELY(nb < 0) + if(nb < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Queueing %d buffers", nb); - if UNLIKELY(nb <= 0) return; + if(nb <= 0) [[unlikely]] 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); + if(!source) [[unlikely]] + return context->setError(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) [[unlikely]] + return context->setError(AL_INVALID_OPERATION, "Queueing onto static source %u", src); /* Check for a valid Buffer, for its frequency and format */ ALCdevice *device{context->mALDevice.get()}; @@ -3494,7 +3650,7 @@ START_API_FUNC fmt_mismatch |= BufferFmt->mAmbiOrder != buffer->mAmbiOrder; fmt_mismatch |= BufferFmt->OriginalType != buffer->OriginalType; } - if UNLIKELY(fmt_mismatch) + if(fmt_mismatch) [[unlikely]] { context->setError(AL_INVALID_OPERATION, "Queueing buffer with mismatched format"); @@ -3521,7 +3677,7 @@ 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 @@ -3530,26 +3686,26 @@ AL_API void AL_APIENTRY alSourceUnqueueBuffers(ALuint src, ALsizei nb, ALuint *b START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; - if UNLIKELY(nb < 0) + if(nb < 0) [[unlikely]] context->setError(AL_INVALID_VALUE, "Unqueueing %d buffers", nb); - if UNLIKELY(nb <= 0) return; + if(nb <= 0) [[unlikely]] 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); + if(!source) [[unlikely]] + return context->setError(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", + if(source->SourceType != AL_STREAMING) [[unlikely]] + return context->setError(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->Looping) [[unlikely]] + return context->setError(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) + if(source->state != AL_INITIAL) [[likely]] { VoiceBufferItem *Current{nullptr}; if(Voice *voice{GetSourceVoice(source, context.get())}) @@ -3561,8 +3717,8 @@ START_API_FUNC ++processed; } } - if UNLIKELY(processed < static_cast(nb)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Unqueueing %d buffer%s (only %u processed)", + if(processed < static_cast(nb)) [[unlikely]] + return context->setError(AL_INVALID_VALUE, "Unqueueing %d buffer%s (only %u processed)", nb, (nb==1)?"":"s", processed); do { @@ -3584,7 +3740,7 @@ AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint, ALsizei, const ALu START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; context->setError(AL_INVALID_OPERATION, "alSourceQueueBufferLayersSOFT not supported"); } @@ -4013,26 +4169,44 @@ EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept eax_.source.flOcclusionDirectRatio, eax_.source.flOcclusionLFRatio); + const auto has_source_occlusion = (eax_.source.lOcclusion != 0); + auto gain_hf_mb = static_cast(eax_.source.lDirectHF) + - static_cast(eax_.source.lObstruction) + - (static_cast(eax_.source.lOcclusion) * eax_.source.flOcclusionDirectRatio); + static_cast(eax_.source.lObstruction); for (auto i = std::size_t{}; i < EAX_MAX_FXSLOTS; ++i) { - if (!eax_active_fx_slots_[i]) - { + if(!eax_active_fx_slots_[i]) continue; + + if(has_source_occlusion) { + const auto& fx_slot = eax_al_context_->eax_get_fx_slot(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 = (eax_primary_fx_slot_id_.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( + eax_.source.lOcclusion, + eax_.source.flOcclusionDirectRatio, + eax_.source.flOcclusionLFRatio); + + gain_hf_mb += static_cast(eax_.source.lOcclusion) * eax_.source.flOcclusionDirectRatio; + } } const auto& send = eax_.sends[i]; - gain_mb += eax_calculate_dst_occlusion_mb( - send.lOcclusion, - send.flOcclusionDirectRatio, - send.flOcclusionLFRatio); + if(send.lOcclusion != 0) { + gain_mb += eax_calculate_dst_occlusion_mb( + send.lOcclusion, + send.flOcclusionDirectRatio, + send.flOcclusionLFRatio); - gain_hf_mb += static_cast(send.lOcclusion) * send.flOcclusionDirectRatio; + gain_hf_mb += static_cast(send.lOcclusion) * send.flOcclusionDirectRatio; + } } const auto al_low_pass_param = EaxAlLowPassParam{ @@ -4047,25 +4221,38 @@ 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 = (eax_primary_fx_slot_id_.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 ? eax_.source.lRoom : 0) + send.lSend) + + (is_listener_environment ? + eax_calculate_dst_occlusion_mb( + eax_.source.lOcclusion, + eax_.source.flOcclusionRoomRatio, + eax_.source.flOcclusionLFRatio) : + 0.0f) + eax_calculate_dst_occlusion_mb( send.lOcclusion, send.flOcclusionRoomRatio, send.flOcclusionLFRatio) + - (static_cast(eax_.source.lExclusion) * eax_.source.flExclusionLFRatio) + + (is_listener_environment ? + (static_cast(eax_.source.lExclusion) * eax_.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 ? eax_.source.lRoomHF : 0) + send.lSendHF) + + (is_listener_environment ? + ((static_cast(eax_.source.lOcclusion) * eax_.source.flOcclusionRoomRatio)) : + 0.0f) + (static_cast(send.lOcclusion) * send.flOcclusionRoomRatio) + - static_cast(eax_.source.lExclusion + send.lExclusion); + (is_listener_environment ? + static_cast(eax_.source.lExclusion + send.lExclusion) : + 0.0f); const auto al_low_pass_param = EaxAlLowPassParam{ level_mb_to_gain(gain_mb), @@ -4486,6 +4673,15 @@ void ALsource::eax_set(const EaxCall& call) eax_version_ = 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()) { @@ -4731,7 +4927,7 @@ void ALsource::eax4_get(const EaxCall& call, const Eax4Props& props) break; case EAXSOURCE_ACTIVEFXSLOTID: - call.set_value(props.active_fx_slots); + eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots, EAX40_MAX_ACTIVE_FXSLOTS); break; default: @@ -4803,7 +4999,7 @@ void ALsource::eax5_get(const EaxCall& call, const Eax5Props& props) break; case EAXSOURCE_ACTIVEFXSLOTID: - call.set_value(props.active_fx_slots); + eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots, EAX50_MAX_ACTIVE_FXSLOTS); break; case EAXSOURCE_MACROFXFACTOR: @@ -4858,28 +5054,36 @@ void ALsource::eax_set_al_source_send(ALeffectslot *slot, size_t sendidx, const void ALsource::eax_commit_active_fx_slots() { - // Mark all slots as non-active. + // Clear all slots to an inactive state. eax_active_fx_slots_.fill(false); - // Mark primary FX slot as active. - if (eax_primary_fx_slot_id_.has_value()) - eax_active_fx_slots_[*eax_primary_fx_slot_id_] = true; - - // Mark the other FX slots as active. - for (const auto& slot_id : eax_.active_fx_slots.guidActiveFXSlots) { - if (slot_id == EAXPROPERTYID_EAX50_FXSlot0) + // Mark the set slots as active. + for(const auto& slot_id : eax_.active_fx_slots.guidActiveFXSlots) + { + if(slot_id == EAX_NULL_GUID) + { + } + else if(slot_id == EAX_PrimaryFXSlotID) + { + // Mark primary FX slot as active. + if(eax_primary_fx_slot_id_.has_value()) + eax_active_fx_slots_[*eax_primary_fx_slot_id_] = true; + } + else if(slot_id == EAXPROPERTYID_EAX50_FXSlot0) eax_active_fx_slots_[0] = true; - else if (slot_id == EAXPROPERTYID_EAX50_FXSlot1) + else if(slot_id == EAXPROPERTYID_EAX50_FXSlot1) eax_active_fx_slots_[1] = true; - else if (slot_id == EAXPROPERTYID_EAX50_FXSlot2) + else if(slot_id == EAXPROPERTYID_EAX50_FXSlot2) eax_active_fx_slots_[2] = true; - else if (slot_id == EAXPROPERTYID_EAX50_FXSlot3) + else if(slot_id == EAXPROPERTYID_EAX50_FXSlot3) eax_active_fx_slots_[3] = true; } - // Deactivate EFX auxiliary effect slots. - for (auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i) { - if (!eax_active_fx_slots_[i]) + // Deactivate EFX auxiliary effect slots for inactive slots. Active slots + // will be updated with the room filters. + for(auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i) + { + if(!eax_active_fx_slots_[i]) eax_set_al_source_send(nullptr, i, EaxAlLowPassParam{1.0f, 1.0f}); } } diff --git a/thirdparty/openal/al/source.h b/thirdparty/openal/al/source.h index a1efd527cc..bcee0e18e8 100644 --- a/thirdparty/openal/al/source.h +++ b/thirdparty/openal/al/source.h @@ -163,6 +163,7 @@ public: void eax_dispatch(const EaxCall& call); void eax_commit() { eax_commit(EaxCommitType::normal); } void eax_commit_and_update(); + void eax_mark_as_changed() { eax_changed_ = true; } bool eax_is_initialized() const noexcept { return eax_al_context_ != nullptr; } static ALsource* eax_lookup_source(ALCcontext& al_context, ALuint source_id) noexcept; @@ -901,6 +902,7 @@ private: } } + void eax_get_active_fx_slot_id(const EaxCall& call, const GUID* ids, size_t max_count); void eax1_get(const EaxCall& call, const Eax1Props& props); void eax2_get(const EaxCall& call, const Eax2Props& props); void eax3_get_obstruction(const EaxCall& call, const Eax3Props& props); diff --git a/thirdparty/openal/al/state.cpp b/thirdparty/openal/al/state.cpp index f41016d6e4..11202374e6 100644 --- a/thirdparty/openal/al/state.cpp +++ b/thirdparty/openal/al/state.cpp @@ -107,13 +107,13 @@ al::optional DistanceModelFromALenum(ALenum model) { 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; } @@ -158,7 +158,7 @@ AL_API void AL_APIENTRY alEnable(ALenum capability) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; switch(capability) { @@ -184,7 +184,7 @@ AL_API void AL_APIENTRY alDisable(ALenum capability) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; switch(capability) { @@ -210,7 +210,7 @@ AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return AL_FALSE; + if(!context) [[unlikely]] return AL_FALSE; std::lock_guard _{context->mPropLock}; ALboolean value{AL_FALSE}; @@ -236,7 +236,7 @@ AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum pname) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return AL_FALSE; + if(!context) [[unlikely]] return AL_FALSE; std::lock_guard _{context->mPropLock}; ALboolean value{AL_FALSE}; @@ -293,7 +293,7 @@ AL_API ALdouble AL_APIENTRY alGetDouble(ALenum pname) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return 0.0; + if(!context) [[unlikely]] return 0.0; std::lock_guard _{context->mPropLock}; ALdouble value{0.0}; @@ -344,7 +344,7 @@ AL_API ALfloat AL_APIENTRY alGetFloat(ALenum pname) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return 0.0f; + if(!context) [[unlikely]] return 0.0f; std::lock_guard _{context->mPropLock}; ALfloat value{0.0f}; @@ -395,7 +395,7 @@ AL_API ALint AL_APIENTRY alGetInteger(ALenum pname) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return 0; + if(!context) [[unlikely]] return 0; std::lock_guard _{context->mPropLock}; ALint value{0}; @@ -481,7 +481,7 @@ AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return 0_i64; + if(!context) [[unlikely]] return 0_i64; std::lock_guard _{context->mPropLock}; ALint64SOFT value{0}; @@ -532,7 +532,7 @@ AL_API ALvoid* AL_APIENTRY alGetPointerSOFT(ALenum pname) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return nullptr; + if(!context) [[unlikely]] return nullptr; std::lock_guard _{context->mPropLock}; void *value{nullptr}; @@ -575,7 +575,7 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; if(!values) context->setError(AL_INVALID_VALUE, "NULL pointer"); @@ -608,7 +608,7 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; if(!values) context->setError(AL_INVALID_VALUE, "NULL pointer"); @@ -641,7 +641,7 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; if(!values) context->setError(AL_INVALID_VALUE, "NULL pointer"); @@ -674,7 +674,7 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; if(!values) context->setError(AL_INVALID_VALUE, "NULL pointer"); @@ -707,7 +707,7 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; if(!values) context->setError(AL_INVALID_VALUE, "NULL pointer"); @@ -734,7 +734,7 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; if(!values) context->setError(AL_INVALID_VALUE, "NULL pointer"); @@ -750,7 +750,7 @@ AL_API const ALchar* AL_APIENTRY alGetString(ALenum pname) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return nullptr; + if(!context) [[unlikely]] return nullptr; const ALchar *value{nullptr}; switch(pname) @@ -806,7 +806,7 @@ AL_API void AL_APIENTRY alDopplerFactor(ALfloat value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; if(!(value >= 0.0f && std::isfinite(value))) context->setError(AL_INVALID_VALUE, "Doppler factor %f out of range", value); @@ -823,7 +823,7 @@ AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; if(!(value >= 0.0f && std::isfinite(value))) context->setError(AL_INVALID_VALUE, "Doppler velocity %f out of range", value); @@ -840,7 +840,7 @@ AL_API void AL_APIENTRY alSpeedOfSound(ALfloat value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; if(!(value > 0.0f && std::isfinite(value))) context->setError(AL_INVALID_VALUE, "Speed of sound %f out of range", value); @@ -857,7 +857,7 @@ AL_API void AL_APIENTRY alDistanceModel(ALenum value) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; if(auto model = DistanceModelFromALenum(value)) { @@ -876,7 +876,7 @@ AL_API void AL_APIENTRY alDeferUpdatesSOFT(void) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; context->deferUpdates(); @@ -887,7 +887,7 @@ AL_API void AL_APIENTRY alProcessUpdatesSOFT(void) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) [[unlikely]] return; std::lock_guard _{context->mPropLock}; context->processUpdates(); @@ -899,7 +899,7 @@ AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return nullptr; + if(!context) [[unlikely]] return nullptr; const ALchar *value{nullptr}; switch(pname) diff --git a/thirdparty/openal/alc/alc.cpp b/thirdparty/openal/alc/alc.cpp index 6ee6c24a81..9915a85358 100644 --- a/thirdparty/openal/alc/alc.cpp +++ b/thirdparty/openal/alc/alc.cpp @@ -452,6 +452,9 @@ const struct { DECL(alAuxiliaryEffectSlotPlayvSOFT), DECL(alAuxiliaryEffectSlotStopSOFT), DECL(alAuxiliaryEffectSlotStopvSOFT), + + DECL(alSourcePlayAtTimeSOFT), + DECL(alSourcePlayAtTimevSOFT), #ifdef ALSOFT_EAX }, eaxFunctions[] = { DECL(EAXGet), @@ -536,6 +539,12 @@ constexpr struct { 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), @@ -663,6 +672,9 @@ constexpr struct { DECL(AL_SOURCE_RADIUS), + DECL(AL_SAMPLE_OFFSET_LATENCY_SOFT), + DECL(AL_SEC_OFFSET_LATENCY_SOFT), + DECL(AL_STEREO_ANGLES), DECL(AL_UNUSED), @@ -1124,25 +1136,56 @@ void alc_initconfig(void) if(auto limopt = ConfigValueBool(nullptr, nullptr, "rt-time-limit")) 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 char *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)) + { + 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")); - aluInit(compatflags); + aluInit(compatflags, ConfigValueFloat(nullptr, "game_compat", "nfc-scale").value_or(1.0f)); + } Voice::InitMixer(ConfigValueStr(nullptr, nullptr, "resampler")); + auto uhjfiltopt = ConfigValueStr(nullptr, "uhj", "decode-filter"); + if(!uhjfiltopt) + { + if((uhjfiltopt = ConfigValueStr(nullptr, "uhj", "filter"))) + WARN("uhj/filter is deprecated, please use uhj/decode-filter\n"); + } + if(uhjfiltopt) + { + if(al::strcasecmp(uhjfiltopt->c_str(), "fir256") == 0) + UhjDecodeQuality = UhjQualityType::FIR256; + else if(al::strcasecmp(uhjfiltopt->c_str(), "fir512") == 0) + UhjDecodeQuality = UhjQualityType::FIR512; + else if(al::strcasecmp(uhjfiltopt->c_str(), "iir") == 0) + UhjDecodeQuality = UhjQualityType::IIR; + else + WARN("Unsupported uhj/decode-filter: %s\n", uhjfiltopt->c_str()); + } + if((uhjfiltopt = ConfigValueStr(nullptr, "uhj", "encode-filter"))) + { + if(al::strcasecmp(uhjfiltopt->c_str(), "fir256") == 0) + UhjEncodeQuality = UhjQualityType::FIR256; + else if(al::strcasecmp(uhjfiltopt->c_str(), "fir512") == 0) + UhjEncodeQuality = UhjQualityType::FIR512; + else if(al::strcasecmp(uhjfiltopt->c_str(), "iir") == 0) + UhjEncodeQuality = UhjQualityType::IIR; + else + WARN("Unsupported uhj/encode-filter: %s\n", uhjfiltopt->c_str()); + } + auto traperr = al::getenv("ALSOFT_TRAP_ERROR"); if(traperr && (al::strcasecmp(traperr->c_str(), "true") == 0 || std::strtol(traperr->c_str(), nullptr, 0) == 1)) @@ -1385,7 +1428,7 @@ al::optional DecomposeDevFormat(ALenum format) for(const auto &item : list) { if(item.format == format) - return al::make_optional(DevFmtPair{item.channels, item.type}); + return al::make_optional({item.channels, item.type}); } return al::nullopt; @@ -1395,13 +1438,13 @@ al::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; @@ -1425,13 +1468,13 @@ al::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; @@ -1448,6 +1491,7 @@ ALCenum EnumFromDevFmt(DevFmtChannels channels) 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))}; @@ -1457,8 +1501,8 @@ al::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; @@ -1477,9 +1521,9 @@ al::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; @@ -1500,31 +1544,68 @@ ALCenum EnumFromDevAmbi(DevAmbiScaling scaling) * existing ones. Based on Wine's DSound downmix values, which are based on * PulseAudio's. */ +constexpr std::array FrontStereoSplit{{ + {FrontLeft, 0.5f}, {FrontRight, 0.5f} +}}; +constexpr std::array FrontLeft9{{ + {FrontLeft, 1.0f/9.0f} +}}; +constexpr std::array FrontRight9{{ + {FrontRight, 1.0f/9.0f} +}}; +constexpr std::array BackMonoToFrontSplit{{ + {FrontLeft, 0.5f/9.0f}, {FrontRight, 0.5f/9.0f} +}}; +constexpr std::array LeftStereoSplit{{ + {FrontLeft, 0.5f}, {BackLeft, 0.5f} +}}; +constexpr std::array RightStereoSplit{{ + {FrontRight, 0.5f}, {BackRight, 0.5f} +}}; +constexpr std::array BackStereoSplit{{ + {BackLeft, 0.5f}, {BackRight, 0.5f} +}}; +constexpr std::array SideStereoSplit{{ + {SideLeft, 0.5f}, {SideRight, 0.5f} +}}; +constexpr std::array ToSideLeft{{ + {SideLeft, 1.0f} +}}; +constexpr std::array ToSideRight{{ + {SideRight, 1.0f} +}}; +constexpr std::array BackLeftSplit{{ + {SideLeft, 0.5f}, {BackCenter, 0.5f} +}}; +constexpr std::array BackRightSplit{{ + {SideRight, 0.5f}, {BackCenter, 0.5f} +}}; + 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}}} }, + { FrontCenter, FrontStereoSplit }, + { SideLeft, FrontLeft9 }, + { SideRight, FrontRight9 }, + { BackLeft, FrontLeft9 }, + { BackRight, FrontRight9 }, + { BackCenter, BackMonoToFrontSplit }, }}; 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}}} }, + { FrontCenter, FrontStereoSplit }, + { SideLeft, LeftStereoSplit }, + { SideRight, RightStereoSplit }, + { BackCenter, BackStereoSplit }, }}; 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}}} }, + { BackLeft, ToSideLeft }, + { BackRight, ToSideRight }, + { BackCenter, SideStereoSplit }, }}; const std::array X61Downmix{{ - { BackLeft, {{{BackCenter, 0.5f}, {SideLeft, 0.5f}}} }, - { BackRight, {{{BackCenter, 0.5f}, {SideRight, 0.5f}}} }, + { BackLeft, BackLeftSplit }, + { BackRight, BackRightSplit }, }}; const std::array X71Downmix{{ - { BackCenter, {{{BackLeft, 0.5f}, {BackRight, 0.5f}}} }, + { BackCenter, BackStereoSplit }, }}; @@ -1577,7 +1658,7 @@ 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; @@ -1597,26 +1678,160 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) return ALC_INVALID_VALUE; } - al::optional stereomode{}; - al::optional optlimit{}; + uint numMono{device->NumMonoSources}; + uint numStereo{device->NumStereoSources}; + uint numSends{device->NumAuxSends}; + al::optional stereomode; + al::optional optlimit; + al::optional optsrate; + al::optional optchans; + al::optional opttype; + al::optional optlayout; + al::optional optscale; + uint period_size{DEFAULT_UPDATE_SIZE}; + uint buffer_size{DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES}; int hrtf_id{-1}; + uint aorder{0u}; - // Check for attributes + if(device->Type != DeviceType::Loopback) + { + /* Get default settings from the user configuration */ + + if(auto freqopt = device->configValue(nullptr, "frequency")) + { + optsrate = clampu(*freqopt, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE); + + const double scale{static_cast(*optsrate) / DEFAULT_OUTPUT_RATE}; + period_size = static_cast(period_size*scale + 0.5); + } + + if(auto persizeopt = device->configValue(nullptr, "period_size")) + period_size = clampu(*persizeopt, 64, 8192); + if(auto numperopt = device->configValue(nullptr, "periods")) + buffer_size = clampu(*numperopt, 2, 16) * period_size; + else + buffer_size = period_size * DEFAULT_NUM_UPDATES; + + if(auto typeopt = device->configValue(nullptr, "sample-type")) + { + 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 + opttype = iter->type; + } + if(auto chanopt = device->configValue(nullptr, "channels")) + { + 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 }, + { "surround714", DevFmtX714, 0 }, + { "surround3d71", DevFmtX3D71, 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 + { + optchans = iter->chans; + aorder = iter->order; + } + } + if(auto ambiopt = device->configValue(nullptr, "ambi-format")) + { + const ALCchar *fmt{ambiopt->c_str()}; + if(al::strcasecmp(fmt, "fuma") == 0) + { + optlayout = DevAmbiLayout::FuMa; + optscale = DevAmbiScaling::FuMa; + } + else if(al::strcasecmp(fmt, "acn+fuma") == 0) + { + optlayout = DevAmbiLayout::ACN; + optscale = DevAmbiScaling::FuMa; + } + else if(al::strcasecmp(fmt, "ambix") == 0 || al::strcasecmp(fmt, "acn+sn3d") == 0) + { + optlayout = DevAmbiLayout::ACN; + optscale = DevAmbiScaling::SN3D; + } + else if(al::strcasecmp(fmt, "acn+n3d") == 0) + { + optlayout = DevAmbiLayout::ACN; + optscale = DevAmbiScaling::N3D; + } + else + ERR("Unsupported ambi-format: %s\n", fmt); + } + + if(auto hrtfopt = device->configValue(nullptr, "hrtf")) + { + WARN("general/hrtf is deprecated, please use stereo-encoding instead\n"); + + const char *hrtf{hrtfopt->c_str()}; + if(al::strcasecmp(hrtf, "true") == 0) + stereomode = StereoEncoding::Hrtf; + 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); + } + } + + if(auto encopt = device->configValue(nullptr, "stereo-encoding")) + { + const char *mode{encopt->c_str()}; + if(al::strcasecmp(mode, "basic") == 0 || al::strcasecmp(mode, "panpot") == 0) + stereomode = StereoEncoding::Basic; + else if(al::strcasecmp(mode, "uhj") == 0) + stereomode = StereoEncoding::Uhj; + else if(al::strcasecmp(mode, "hrtf") == 0) + stereomode = StereoEncoding::Hrtf; + else + ERR("Unexpected stereo-encoding: %s\n", mode); + } + + // Check for app-specified attributes if(attrList && attrList[0]) { - uint numMono{device->NumMonoSources}; - uint numStereo{device->NumStereoSources}; - uint numSends{device->NumAuxSends}; - - al::optional optchans; - al::optional opttype; - al::optional optlayout; - al::optional optscale; - al::optional opthrtf; - ALenum outmode{ALC_ANY_SOFT}; - uint aorder{0u}; - uint freq{0u}; + al::optional opthrtf; + int freqAttr{}; #define ATTRIBUTE(a) a: TRACE("%s = %d\n", #a, attrList[attrIdx + 1]); size_t attrIdx{0}; @@ -1625,27 +1840,32 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) 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) @@ -1700,12 +1920,11 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) } #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 < MIN_OUTPUT_RATE || freqAttr > MAX_OUTPUT_RATE) return ALC_INVALID_VALUE; if(*optchans == DevFmtAmbi3D) { @@ -1717,6 +1936,79 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) && aorder > 3) return ALC_INVALID_VALUE; } + else if(*optchans == DevFmtStereo) + { + if(opthrtf) + { + 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; + else if(outmode == ALC_STEREO_UHJ_SOFT) + stereomode = StereoEncoding::Uhj; + else if(outmode == ALC_STEREO_HRTF_SOFT) + stereomode = StereoEncoding::Hrtf; + } + + optsrate = static_cast(freqAttr); + } + else + { + if(opthrtf) + { + if(*opthrtf) + stereomode = StereoEncoding::Hrtf; + else + { + if(stereomode.value_or(StereoEncoding::Hrtf) == StereoEncoding::Hrtf) + stereomode = StereoEncoding::Default; + } + } + + if(outmode != ALC_ANY_SOFT) + { + 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(DEFAULT_OUTPUT_RATE); + freqAttr = clampi(freqAttr, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE); + + const double scale{static_cast(freqAttr) / oldrate}; + period_size = static_cast(period_size*scale + 0.5); + buffer_size = static_cast(buffer_size*scale + 0.5); + optsrate = static_cast(freqAttr); + } } /* If a context is already running on the device, stop playback so the @@ -1727,119 +2019,6 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) 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) - { - if(opthrtf) - stereomode = *opthrtf ? StereoEncoding::Hrtf : StereoEncoding::Default; - - if(outmode == ALC_STEREO_BASIC_SOFT) - stereomode = StereoEncoding::Basic; - else if(outmode == ALC_STEREO_UHJ_SOFT) - stereomode = StereoEncoding::Uhj; - else if(outmode == ALC_STEREO_HRTF_SOFT) - stereomode = StereoEncoding::Hrtf; - } - device->Flags.set(FrequencyRequest).set(ChannelsRequest).set(SampleTypeRequest); - } - 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; - } - - using OutputMode = ALCdevice::OutputMode; - switch(OutputMode(outmode)) - { - 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; - } - } } if(device->Flags.test(DeviceRunning)) @@ -1861,7 +2040,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(); @@ -1875,142 +2054,52 @@ 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(DEFAULT_OUTPUT_RATE); + 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 }, - { "surround3d71", DevFmtX3D71, 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 = clampu(aorder, 1, 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, + (((device->mAmbiOrder%100)/10) == 1) ? "th" : + ((device->mAmbiOrder%10) == 1) ? "st" : + ((device->mAmbiOrder%10) == 2) ? "nd" : + ((device->mAmbiOrder%10) == 3) ? "rd" : "th"); + 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", @@ -2067,23 +2156,33 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) 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); - } } 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(nullptr, "sources")) + { + if(*srcsopt <= 0) numMono = 256; + else numMono = maxu(*srcsopt, 16); + } + else + { + numMono = minu(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; + TRACE("Max sources: %d (%d + %d), effect slots: %d, sends: %d\n", device->SourcesMax, device->NumMonoSources, device->NumStereoSources, device->AuxiliaryEffectSlotMax, device->NumAuxSends); @@ -2099,18 +2198,14 @@ 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; - } + if(auto *encoder{device->mUhjEncoder.get()}) + sample_delay += encoder->getDelay(); if(device->getConfigValueBool(nullptr, "dither", true)) { @@ -2146,8 +2241,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(nullptr, "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 @@ -2217,16 +2312,36 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) 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 &cluster) + { + for(size_t i{0};i < ContextBase::EffectSlotClusterSize;++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&& slots : context->mEffectSlotClusters) + { + for(size_t i{0};i < ContextBase::EffectSlotClusterSize;++i) + { + slots[i].mWetBuffer.clear(); + slots[i].mWetBuffer.shrink_to_fit(); + slots[i].Wet.Buffer = {}; + } + } if(ALeffectslot *slot{context->mDefaultSlot.get()}) { - aluInitEffectPanning(&slot->mSlot, context); + aluInitEffectPanning(slot->mSlot, context); EffectState *state{slot->Effect.State.get()}; state->mOutTarget = device->Dry.Buffer; @@ -2245,7 +2360,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) ALeffectslot *slot{sublist.EffectSlots + idx}; usemask &= ~(1_u64 << idx); - aluInitEffectPanning(&slot->mSlot, context); + aluInitEffectPanning(slot->mSlot, context); EffectState *state{slot->Effect.State.get()}; state->mOutTarget = device->Dry.Buffer; @@ -2348,7 +2463,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) bool ResetDeviceParams(ALCdevice *device, const int *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(); @@ -2380,7 +2495,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; @@ -2426,9 +2541,14 @@ ContextRef GetContextRef(void) 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}; } @@ -3319,13 +3439,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. */ - ctx = ContextRef{ALCcontext::sGlobalContext.exchange(ctx.release())}; + 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. + */ + } + 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); @@ -3386,6 +3511,7 @@ START_API_FUNC if(deviceName) { + TRACE("Opening playback device \"%s\"\n", deviceName); if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0 #ifdef _WIN32 /* Some old Windows apps hardcode these expecting OpenAL to use a @@ -3404,6 +3530,8 @@ START_API_FUNC || al::strcasecmp(deviceName, "openal-soft") == 0) deviceName = nullptr; } + else + TRACE("Opening default playback device\n"); const uint DefaultSends{ #ifdef ALSOFT_EAX @@ -3422,6 +3550,8 @@ START_API_FUNC device->BufferSize = DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES; device->SourcesMax = 256; + device->NumStereoSources = 1; + device->NumMonoSources = device->SourcesMax - device->NumStereoSources; device->AuxiliaryEffectSlotMax = 64; device->NumAuxSends = DefaultSends; @@ -3438,36 +3568,6 @@ 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")) - { - const int max_sends{clampi(*sendsopt, 0, MAX_SENDS)}; - device->NumAuxSends = minu(DefaultSends, static_cast(max_sends)); - } - - device->NumStereoSources = 1; - device->NumMonoSources = device->SourcesMax - device->NumStereoSources; - { std::lock_guard _{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); @@ -3552,10 +3652,13 @@ START_API_FUNC if(deviceName) { + TRACE("Opening capture device \"%s\"\n", deviceName); if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0 || al::strcasecmp(deviceName, "openal-soft") == 0) deviceName = nullptr; } + else + TRACE("Opening default capture device\n"); DeviceRef device{new ALCdevice{DeviceType::Capture}}; @@ -3749,18 +3852,6 @@ START_API_FUNC 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")) - { - const int max_sends{clampi(*sendsopt, 0, MAX_SENDS)}; - device->NumAuxSends = minu(DefaultSends, static_cast(max_sends)); - } - device->NumStereoSources = 1; device->NumMonoSources = device->SourcesMax - device->NumStereoSources; diff --git a/thirdparty/openal/alc/alconfig.cpp b/thirdparty/openal/alc/alconfig.cpp index 7c1eec6dbd..b0544b8904 100644 --- a/thirdparty/openal/alc/alconfig.cpp +++ b/thirdparty/openal/alc/alconfig.cpp @@ -140,7 +140,7 @@ void LoadConfigFromFile(std::istream &f) if(buffer[0] == '[') { - char *line{&buffer[0]}; + auto line = const_cast(buffer.data()); char *section = line+1; char *endsection; @@ -486,36 +486,36 @@ void ReadALConfig() al::optional ConfigValueStr(const char *devName, const char *blockName, const char *keyName) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return al::make_optional(val); + return val; return al::nullopt; } al::optional ConfigValueInt(const char *devName, const char *blockName, const char *keyName) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return al::make_optional(static_cast(std::strtol(val, nullptr, 0))); + return static_cast(std::strtol(val, nullptr, 0)); return al::nullopt; } al::optional ConfigValueUInt(const char *devName, const char *blockName, const char *keyName) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return al::make_optional(static_cast(std::strtoul(val, nullptr, 0))); + return static_cast(std::strtoul(val, nullptr, 0)); return al::nullopt; } al::optional ConfigValueFloat(const char *devName, const char *blockName, const char *keyName) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return al::make_optional(std::strtof(val, nullptr)); + return std::strtof(val, nullptr); return al::nullopt; } al::optional ConfigValueBool(const char *devName, const char *blockName, const char *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::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 + || al::strcasecmp(val, "true")==0 || atoi(val) != 0; return al::nullopt; } diff --git a/thirdparty/openal/alc/alu.cpp b/thirdparty/openal/alc/alu.cpp index a06d567538..d49b0d668b 100644 --- a/thirdparty/openal/alc/alu.cpp +++ b/thirdparty/openal/alc/alu.cpp @@ -105,6 +105,7 @@ static_assert(!(MaxResamplerPadding&1), "MaxResamplerPadding is not a multiple o namespace { using uint = unsigned int; +using namespace std::chrono; constexpr uint MaxPitch{10}; @@ -135,6 +136,9 @@ float XScale{1.0f}; float YScale{1.0f}; float ZScale{1.0f}; +/* Source distance scale for NFC filters. */ +float NfcScale{1.0f}; + struct ChanMap { Channel channel; @@ -241,12 +245,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 = clampf(nfcscale, 0.0001f, 10000.0f); } @@ -325,8 +331,6 @@ void DeviceBase::ProcessBs2b(const size_t SamplesToDo) namespace { -using AmbiRotateMatrix = std::array,MaxAmbiChannels>; - /* This RNG method was created based on the math found in opusdec. It's quick, * and starting with a seed value of 22222, is suitable for generating * whitenoise. @@ -338,6 +342,35 @@ inline uint dither_rng(uint *seed) 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 coeffs_order) +{ + const size_t num_chans{AmbiChannelsFromOrder(coeffs_order)}; + for(size_t i{0};i < upsampler.size();++i) + output[i].fill(0.0f); + for(size_t i{0};i < upsampler.size();++i) + { + for(size_t k{0};k < num_chans;++k) + { + float *RESTRICT out{output[i].data()}; + /* Write the full number of channels. The compiler will have an + * easier time optimizing if it has a fixed length. + */ + for(size_t j{0};j < MaxAmbiChannels;++j) + out[j] += upsampler[i][k] * rotator[k][j]; + } + } +} + + inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept { switch(scaletype) @@ -439,8 +472,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. @@ -450,7 +483,7 @@ 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)}; @@ -523,49 +556,48 @@ inline float WrapRadians(float r) * precomputed since they're constant. The second-order coefficients are * followed by the third-order coefficients, etc. */ +template +constexpr size_t CalcRotatorSize() +{ return (L*2 + 1)*(L*2 + 1) + CalcRotatorSize(); } + +template<> constexpr size_t CalcRotatorSize<0>() = delete; +template<> constexpr size_t CalcRotatorSize<1>() = delete; +template<> constexpr size_t CalcRotatorSize<2>() { return 5*5; } + 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 float denom{static_cast((std::abs(n) == l) ? + (2*l) * (2*l - 1) : (l*l - n*n))}; - const int abs_m{std::abs(m)}; - coeffs->u = std::sqrt(static_cast(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 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; + } } } - - 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 @@ -580,16 +612,16 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order) auto P = [](const int i, const int l, const int a, const int n, const size_t last_band, const AmbiRotateMatrix &R) { - const float ri1{ R[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)]}; + const float rim1{R[-1+2][static_cast(i+2)]}; + const float ri0{ R[ 0+2][static_cast(i+2)]}; - 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)*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)*2][y] - rim1*R[last_band][y]; + return ri0*R[last_band + static_cast(n+l-1)][y]; }; auto U = [P](const int l, const int m, const int n, const size_t last_band, @@ -629,15 +661,15 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order) }; // 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}; @@ -668,7 +700,7 @@ 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 VoiceProps *props, const ContextParams &Context, DeviceBase *Device) { static constexpr ChanMap MonoMap[1]{ { FrontCenter, 0.0f, 0.0f } @@ -787,7 +819,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{maxf(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)}; /* Only need to adjust the first channel of a B-Format source. */ @@ -808,7 +840,7 @@ 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); + return CalcDirectionCoeffs({xpos, ypos, zpos}); /* Clamp Y, in case rounding errors caused it to end up outside * of -1...+1. @@ -823,22 +855,27 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con */ return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, 0.0f); }; - auto coeffs = calc_coeffs(Device->mRenderMode); - std::transform(coeffs.begin()+1, coeffs.end(), coeffs.begin()+1, - std::bind(std::multiplies{}, _1, 1.0f-coverage)); - - /* NOTE: W needs to be scaled according to channel scaling. */ auto&& scales = GetAmbiScales(voice->mAmbiScaling); - ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base*scales[0], - voice->mChans[0].mDryParams.Gains.Target); - for(uint i{0};i < NumSends;i++) - { - if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base*scales[0], - voice->mChans[0].mWetParams[i].Gains.Target); - } + auto coeffs = calc_coeffs(Device->mRenderMode); + /* Scale the panned W signal based on the coverage (full coverage means + * no panned signal). Scale the panned W signal according to channel + * scaling. + */ + std::transform(coeffs.begin(), coeffs.end(), coeffs.begin(), + [scale=(1.0f-coverage)*scales[0]](const float c){ return c * scale; }); - if(coverage > 0.0f) + if(!(coverage > 0.0f)) + { + ComputePanGains(&Device->Dry, coeffs.data(), 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*scales[0], + voice->mChans[0].mWetParams[i].Gains.Target); + } + } + else { /* Local B-Format sources have their XYZ channels rotated according * to the orientation. @@ -861,12 +898,57 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * order elements, then construct the rotation for the higher * orders. */ - AmbiRotateMatrix 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) + { + auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ? + AmbiScale::FirstOrder2DUp : AmbiScale::FirstOrderUp; + UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); + } + else if(voice->mAmbiOrder == 2) + { + auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ? + AmbiScale::SecondOrder2DUp : AmbiScale::SecondOrderUp; + UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); + } + else if(voice->mAmbiOrder == 3) + { + auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ? + AmbiScale::ThirdOrder2DUp : AmbiScale::ThirdOrderUp; + UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); + } + else if(voice->mAmbiOrder == 4) + { + auto&& upsampler = 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. @@ -875,20 +957,17 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con GetAmbi2DLayout(voice->mAmbiLayout).data() : GetAmbiLayout(voice->mAmbiLayout).data()}; - static const uint8_t ChansPerOrder[MaxAmbiOrder+1]{1, 3, 5, 7,}; - static const uint8_t OrderOffset[MaxAmbiOrder+1]{0, 1, 4, 9,}; - for(size_t c{1};c < num_channels;c++) + 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. + */ + for(size_t x{0};x < MaxAmbiChannels;++x) + coeffs[x] += mixmatrix[acn][x] * scale; ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, voice->mChans[c].mDryParams.Gains.Target); @@ -899,6 +978,8 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, voice->mChans[c].mWetParams[i].Gains.Target); } + + coeffs = std::array{}; } } } @@ -911,8 +992,8 @@ 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) + uint idx{Device->channelIdxByName(chans[c].channel)}; + if(idx != InvalidChannelIndex) voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; else if(DirectChannels == DirectMode::RemixMismatch) { @@ -924,8 +1005,8 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con { for(const auto &target : remap->targets) { - idx = GetChannelIdxByName(Device->RealOut, target.channel); - if(idx != INVALID_CHANNEL_INDEX) + idx = Device->channelIdxByName(target.channel); + if(idx != InvalidChannelIndex) voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * target.mix; } @@ -938,6 +1019,10 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con */ for(size_t c{0};c < num_channels;c++) { + /* Skip LFE */ + if(chans[c].channel == LFE) + continue; + const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f); for(uint i{0};i < NumSends;i++) @@ -957,35 +1042,54 @@ 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)}; + const float src_ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; + const float src_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) { + Device->mHrtf->getCoeffs(src_ev, src_az, Distance*NfcScale, Spread, + voice->mChans[0].mDryParams.Hrtf.Target.Coeffs, + voice->mChans[0].mDryParams.Hrtf.Target.Delay); + voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain.Base; + + const auto coeffs = CalcAngleCoeffs(src_az, src_ev, Spread); + for(uint i{0};i < NumSends;i++) + { + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), 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; - voice->mChans[c].mDryParams.Hrtf.Target = voice->mChans[0].mDryParams.Hrtf.Target; - } - /* Calculate the directional coefficients once, which apply to all - * input channels of the source sends. - */ - const auto coeffs = CalcDirectionCoeffs({xpos, ypos, zpos}, Spread); + /* Warp the channel position toward the source position as the + * source spread decreases. With no spread, all channels are at + * the source position, at full spread (pi*2), each channel is + * left unchanged. + */ + const float ev{lerpf(src_ev, chans[c].elevation, inv_pi_v/2.0f * Spread)}; - for(size_t c{0};c < num_channels;c++) - { - /* Skip LFE */ - if(chans[c].channel == LFE) - continue; + float az{chans[c].angle - src_az}; + if(az < -pi_v) az += pi_v*2.0f; + else if(az > pi_v) az -= pi_v*2.0f; + + az *= inv_pi_v/2.0f * Spread; + + az += src_az; + if(az < -pi_v) az += pi_v*2.0f; + else if(az > pi_v) az -= pi_v*2.0f; + + 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; + + const auto coeffs = CalcAngleCoeffs(az, ev, 0.0f); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) @@ -996,6 +1100,12 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con } 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 * (voice->mFmtChannels == FmtMono)}; + /* Local sources on HRTF play with each channel panned to its * relative location around the listener, providing "virtual * speaker" responses. @@ -1009,14 +1119,14 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con /* 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, + Device->mHrtf->getCoeffs(chans[c].elevation, chans[c].angle, + 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; /* Normal panning for auxiliary sends. */ - const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread); + const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, spread); for(uint i{0};i < NumSends;i++) { @@ -1041,7 +1151,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{maxf(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)}; /* Adjust NFC filters. */ @@ -1051,40 +1161,78 @@ 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({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); ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, - voice->mChans[c].mDryParams.Gains.Target); + 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); + voice->mChans[0].mWetParams[i].Gains.Target); + } + } + else + { + using namespace al::numbers; + + const float src_ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; + const float src_az{std::atan2(xpos, -zpos)}; + + for(size_t c{0};c < num_channels;c++) + { + /* 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; + } + 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 ev{lerpf(src_ev, chans[c].elevation, + inv_pi_v/2.0f * Spread)}; + + float az{chans[c].angle - src_az}; + if(az < -pi_v) az += pi_v*2.0f; + else if(az > pi_v) az -= pi_v*2.0f; + + az *= inv_pi_v/2.0f * Spread; + + az += src_az; + if(az < -pi_v) az += pi_v*2.0f; + else if(az > pi_v) az -= pi_v*2.0f; + + if(Device->mRenderMode == RenderMode::Pairwise) + az = ScaleAzimuthFront(az, 3.0f); + const auto coeffs = CalcAngleCoeffs(az, ev, 0.0f); + + ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, + voice->mChans[c].mDryParams.Gains.Target); + for(uint i{0};i < NumSends;i++) + { + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + voice->mChans[c].mWetParams[i].Gains.Target); + } } } } @@ -1102,6 +1250,11 @@ 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 * (voice->mFmtChannels == FmtMono)}; for(size_t c{0};c < num_channels;c++) { /* Special-case LFE */ @@ -1109,8 +1262,8 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con { if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) { - const uint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)}; - if(idx != INVALID_CHANNEL_INDEX) + const uint idx{Device->channelIdxByName(chans[c].channel)}; + if(idx != InvalidChannelIndex) voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; } continue; @@ -1118,7 +1271,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con const auto coeffs = CalcAngleCoeffs((Device->mRenderMode == RenderMode::Pairwise) ? ScaleAzimuthFront(chans[c].angle, 3.0f) : chans[c].angle, - chans[c].elevation, Spread); + chans[c].elevation, spread); ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, voice->mChans[c].mDryParams.Gains.Target); @@ -1173,7 +1326,7 @@ 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}; + DeviceBase *Device{context->mDevice}; EffectSlot *SendSlots[MAX_SENDS]; voice->mDirect.Buffer = Device->Dry.Buffer; @@ -1219,7 +1372,7 @@ 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. */ @@ -1373,18 +1526,13 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa } /* 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. @@ -1407,6 +1555,9 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa 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?). @@ -1513,8 +1664,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) @@ -1564,7 +1714,7 @@ void SendSourceStateEvent(ContextBase *context, uint id, VChangeState state) break; /* Shouldn't happen. */ case VChangeState::Restart: - ASSUME(0); + al::unreachable(); } ring->writeAdvance(1); @@ -1576,7 +1726,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; @@ -1657,7 +1807,7 @@ void ProcessVoiceChanges(ContextBase *ctx) } oldvoice->mPendingChange.store(false, std::memory_order_release); } - if(sendevt && (enabledevt&AsyncEvent::SourceStateChange)) + if(sendevt && enabledevt.test(AsyncEvent::SourceStateChange)) SendSourceStateEvent(ctx, cur->mSourceID, cur->mState); next = cur->mNext.load(std::memory_order_acquire); @@ -1671,7 +1821,7 @@ void ProcessParamUpdates(ContextBase *ctx, const EffectSlotArray &slots, 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()); @@ -1692,6 +1842,9 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) { ASSUME(SamplesToDo > 0); + const nanoseconds curtime{device->ClockBase + + nanoseconds{seconds{device->SamplesDone}}/device->Frequency}; + for(ContextBase *ctx : *device->mContexts.load(std::memory_order_acquire)) { const EffectSlotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire); @@ -1712,7 +1865,7 @@ 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. */ @@ -1760,7 +1913,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; @@ -1773,7 +1926,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); } } @@ -1803,7 +1956,7 @@ void ApplyDistanceComp(const al::span Samples, const size_t Sam float *inout{al::assume_aligned<16>(chanbuffer.data())}; auto inout_end = inout + SamplesToDo; - if LIKELY(SamplesToDo >= base) + if(SamplesToDo >= base) [[likely]] { auto delay_end = std::rotate(inout, inout_end - base, inout_end); std::swap_ranges(inout, delay_end, distbuf); @@ -1813,7 +1966,7 @@ void ApplyDistanceComp(const al::span Samples, const size_t Sam auto delay_start = std::swap_ranges(inout, inout_end, distbuf); std::rotate(distbuf, delay_start, distbuf + base); } - std::transform(inout, inout_end, inout, std::bind(std::multiplies{}, _1, gain)); + std::transform(inout, inout_end, inout, [gain](auto a){ return a * gain; }); } } @@ -1978,7 +2131,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. @@ -2019,8 +2172,7 @@ void DeviceBase::handleDisconnect(const char *msg, ...) for(ContextBase *ctx : *mContexts.load()) { - const uint enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)}; - if((enabledevt&AsyncEvent::Disconnected)) + if(ctx->mEnabledEvts.load(std::memory_order_acquire).test(AsyncEvent::Disconnected)) { RingBuffer *ring{ctx->mAsyncEvents.get()}; auto evt_data = ring->getWriteVector().first; diff --git a/thirdparty/openal/alc/alu.h b/thirdparty/openal/alc/alu.h index f3796a8987..67fd09e573 100644 --- a/thirdparty/openal/alc/alu.h +++ b/thirdparty/openal/alc/alu.h @@ -24,7 +24,7 @@ enum CompatFlags : uint8_t { }; using CompatFlagBitset = std::bitset; -void aluInit(CompatFlagBitset flags); +void aluInit(CompatFlagBitset flags, const float nfcscale); /* aluInitRenderer * diff --git a/thirdparty/openal/alc/backends/alsa.cpp b/thirdparty/openal/alc/backends/alsa.cpp index 9c78b6c6a7..d620a83cc7 100644 --- a/thirdparty/openal/alc/backends/alsa.cpp +++ b/thirdparty/openal/alc/backends/alsa.cpp @@ -132,7 +132,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,6 +241,11 @@ 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; @@ -264,7 +269,7 @@ al::vector probe_devices(snd_pcm_stream_t stream) auto defname = ConfigValueStr(nullptr, "alsa", (stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture"); - devlist.emplace_back(DevMap{alsaDevice, defname ? defname->c_str() : "default"}); + devlist.emplace_back(alsaDevice, defname ? defname->c_str() : "default"); if(auto customdevs = ConfigValueStr(nullptr, "alsa", (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures")) @@ -283,8 +288,8 @@ al::vector probe_devices(snd_pcm_stream_t stream) } else { - devlist.emplace_back(DevMap{customdevs->substr(curpos, seppos-curpos), - customdevs->substr(seppos+1, nextpos-seppos-1)}); + devlist.emplace_back(customdevs->substr(curpos, seppos-curpos), + customdevs->substr(seppos+1, nextpos-seppos-1)); const auto &entry = devlist.back(); TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } @@ -325,7 +330,7 @@ al::vector probe_devices(snd_pcm_stream_t stream) ConfigValueStr(nullptr, "alsa", name.c_str()).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"); @@ -367,7 +372,7 @@ 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)}); + devlist.emplace_back(std::move(name), std::move(device)); const auto &entry = devlist.back(); TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } @@ -623,8 +628,7 @@ int AlsaPlayback::mixerNoMMapProc() void AlsaPlayback::open(const char *name) { - al::optional driveropt; - const char *driver{"default"}; + std::string driver{"default"}; if(name) { if(PlaybackDevices.empty()) @@ -635,21 +639,21 @@ void AlsaPlayback::open(const char *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(); + driver = iter->device_name; } else { name = alsaDevice; - if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "device")}) - driver = driveropt->c_str(); + if(auto driveropt = ConfigValueStr(nullptr, "alsa", "device")) + 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,7 +692,7 @@ bool AlsaPlayback::reset() break; } - bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", 1)}; + bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", 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}; @@ -746,7 +750,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.c_str(), "alsa", "allow-resampler", false) || !mDevice->Flags.test(FrequencyRequest)) { if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0) @@ -896,8 +900,7 @@ AlsaCapture::~AlsaCapture() void AlsaCapture::open(const char *name) { - al::optional driveropt; - const char *driver{"default"}; + std::string driver{"default"}; if(name) { if(CaptureDevices.empty()) @@ -908,20 +911,20 @@ void AlsaCapture::open(const char *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(); + driver = iter->device_name; } else { name = alsaDevice; - if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "capture")}) - driver = driveropt->c_str(); + if(auto driveropt = ConfigValueStr(nullptr, "alsa", "capture")) + 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)}; + TRACE("Opening device \"%s\"\n", driver.c_str()); + int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; if(err < 0) 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(); diff --git a/thirdparty/openal/alc/backends/base.cpp b/thirdparty/openal/alc/backends/base.cpp index 4abd7c0374..e5ad84943c 100644 --- a/thirdparty/openal/alc/backends/base.cpp +++ b/thirdparty/openal/alc/backends/base.cpp @@ -21,6 +21,20 @@ #include "core/devformat.h" +namespace al { + +backend_exception::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_exception::~backend_exception() = default; + +} // namespace al + + bool BackendBase::reset() { throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; } @@ -54,7 +68,7 @@ ClockLatency BackendBase::getClockLatency() void BackendBase::setDefaultWFXChannelOrder() { - mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); + mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex); switch(mDevice->FmtChans) { @@ -98,6 +112,20 @@ 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; @@ -115,7 +143,7 @@ void BackendBase::setDefaultWFXChannelOrder() void BackendBase::setDefaultChannelOrder() { - mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); + mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex); switch(mDevice->FmtChans) { @@ -137,6 +165,20 @@ 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; diff --git a/thirdparty/openal/alc/backends/base.h b/thirdparty/openal/alc/backends/base.h index 65bc636b3f..b6b3d92270 100644 --- a/thirdparty/openal/alc/backends/base.h +++ b/thirdparty/openal/alc/backends/base.h @@ -103,13 +103,9 @@ public: #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_exception(backend_error code, const char *msg, ...); + ~backend_exception() override; + backend_error errorCode() const noexcept { return mErrorCode; } }; diff --git a/thirdparty/openal/alc/backends/coreaudio.cpp b/thirdparty/openal/alc/backends/coreaudio.cpp index f779f84443..8b0e75fdfa 100644 --- a/thirdparty/openal/alc/backends/coreaudio.cpp +++ b/thirdparty/openal/alc/backends/coreaudio.cpp @@ -548,6 +548,8 @@ struct CoreAudioCapture final : public BackendBase { SampleConverterPtr mConverter; + al::vector mCaptureData; + RingBufferPtr mRing{nullptr}; DEF_NEWDEL(CoreAudioCapture) @@ -561,49 +563,29 @@ 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]; + al::byte _[maxz(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: %d\n", err); return err; } - mRing->writeAdvance(inNumberFrames); + mRing->write(mCaptureData.data(), inNumberFrames); return noErr; } @@ -766,6 +748,7 @@ 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", @@ -815,12 +798,14 @@ void CoreAudioCapture::open(const char *name) throw al::backend_exception{al::backend_error::DeviceError, "Could not get input frame count: %u", err}; + mCaptureData.resize(outputFrameCount * mFrameSize); + outputFrameCount = static_cast(maxu64(outputFrameCount, FrameCount64)); mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false); /* Set up sample converter if needed */ if(outputFormat.mSampleRate != mDevice->Frequency) - mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType, + mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, mFormat.mChannelsPerFrame, static_cast(hardwareFormat.mSampleRate), mDevice->Frequency, Resampler::FastBSinc24); diff --git a/thirdparty/openal/alc/backends/dsound.cpp b/thirdparty/openal/alc/backends/dsound.cpp index 3f2bf8df8e..f549c0fe64 100644 --- a/thirdparty/openal/alc/backends/dsound.cpp +++ b/thirdparty/openal/alc/backends/dsound.cpp @@ -115,6 +115,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 @@ -424,6 +425,7 @@ bool DSoundPlayback::reset() 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; } @@ -638,6 +640,7 @@ 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)); diff --git a/thirdparty/openal/alc/backends/jack.cpp b/thirdparty/openal/alc/backends/jack.cpp index 2b0131b3bb..9579686288 100644 --- a/thirdparty/openal/alc/backends/jack.cpp +++ b/thirdparty/openal/alc/backends/jack.cpp @@ -160,6 +160,11 @@ using JackPortsPtr = std::unique_ptr; 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; @@ -187,7 +192,7 @@ void EnumerateDevices(jack_client_t *client, al::vector &list) continue; std::string name{portdev.data(), portdev.size()}; - list.emplace_back(DeviceEntry{name, name+":"}); + list.emplace_back(name, name+":"); const auto &entry = list.back(); TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); } @@ -197,7 +202,7 @@ 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", ""); } } @@ -238,8 +243,8 @@ void EnumerateDevices(jack_client_t *client, al::vector &list) else { /* Otherwise, add a new device entry. */ - list.emplace_back(DeviceEntry{std::string{name.data(), name.size()}, - std::string{pattern.data(), pattern.size()}}); + list.emplace_back(std::string{name.data(), name.size()}, + std::string{pattern.data(), pattern.size()}); const auto &entry = list.back(); TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); } @@ -340,7 +345,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 { @@ -364,7 +369,7 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept } 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))}; @@ -492,7 +497,7 @@ void JackPlayback::open(const char *name) mPortPattern = iter->mPattern; } - mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", 1); + mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", true); jack_set_process_callback(mClient, mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); @@ -669,7 +674,7 @@ bool JackBackendFactory::init() if(!jack_load()) return false; - if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0)) + if(!GetConfigValueBool(nullptr, "jack", "spawn-server", false)) ClientOptions = static_cast(ClientOptions | JackNoStartServer); const PathNamePair &binname = GetProcBinary(); diff --git a/thirdparty/openal/alc/backends/oboe.cpp b/thirdparty/openal/alc/backends/oboe.cpp index 7b1dc966e3..32e4d4dd55 100644 --- a/thirdparty/openal/alc/backends/oboe.cpp +++ b/thirdparty/openal/alc/backends/oboe.cpp @@ -245,6 +245,7 @@ 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", diff --git a/thirdparty/openal/alc/backends/opensl.cpp b/thirdparty/openal/alc/backends/opensl.cpp index 49e5c2680d..f46438ce16 100644 --- a/thirdparty/openal/alc/backends/opensl.cpp +++ b/thirdparty/openal/alc/backends/opensl.cpp @@ -75,6 +75,11 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept 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; } @@ -142,9 +147,9 @@ 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))); \ +#define PRINTERR(x, s) do { \ + if((x) != SL_RESULT_SUCCESS) [[unlikely]] \ + ERR("%s: %s\n", (s), res_str((x))); \ } while(0) @@ -614,7 +619,7 @@ void OpenSLPlayback::stop() } while(SL_RESULT_SUCCESS == result && state.count > 0); PRINTERR(result, "bufferQueue->GetState"); - mRing.reset(); + mRing->reset(); } } @@ -911,18 +916,18 @@ 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)) + 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 @@ -937,7 +942,7 @@ 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); diff --git a/thirdparty/openal/alc/backends/pipewire.cpp b/thirdparty/openal/alc/backends/pipewire.cpp index 0048e1e251..04cc38c74c 100644 --- a/thirdparty/openal/alc/backends/pipewire.cpp +++ b/thirdparty/openal/alc/backends/pipewire.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -125,6 +126,7 @@ namespace { #endif using std::chrono::seconds; +using std::chrono::milliseconds; using std::chrono::nanoseconds; using uint = unsigned int; @@ -365,7 +367,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{}; @@ -472,7 +477,7 @@ struct EventManager { */ void waitForInit() { - if(unlikely(!mInitDone.load(std::memory_order_acquire))) + if(!mInitDone.load(std::memory_order_acquire)) [[unlikely]] { MainloopUniqueLock plock{mLoop}; plock.wait([this](){ return mInitDone.load(std::memory_order_acquire); }); @@ -549,10 +554,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{}; @@ -606,7 +613,7 @@ 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; } @@ -644,6 +651,10 @@ const spa_audio_channel MonoMap[]{ }, X71Map[]{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR +}, X714Map[]{ + 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 }; /** @@ -686,8 +697,8 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept 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]); + TRACE("Device ID %" PRIu64 " sample rate: %d (range: %d -> %d)\n", mSerial, srates[0], + srates[1], srates[2]); mSampleRate = static_cast(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)); return; } @@ -708,7 +719,7 @@ 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("Device ID %" PRIu64 " sample rate: %d (%s)\n", mSerial, srates[0], others.c_str()); /* Pick the first rate listed that's within the allowed range (default * rate if possible). */ @@ -732,7 +743,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept } auto srates = get_pod_body(value); - TRACE("Device ID %u sample rate: %d\n", mId, srates[0]); + TRACE("Device ID %" PRIu64 " sample rate: %d\n", mSerial, srates[0]); mSampleRate = static_cast(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)); return; } @@ -747,7 +758,9 @@ void DeviceNode::parsePositions(const spa_pod *value) noexcept mIs51Rear = false; - if(MatchChannelMap(chanmap, X71Map)) + if(MatchChannelMap(chanmap, X714Map)) + mChannels = DevFmtX714; + else if(MatchChannelMap(chanmap, X71Map)) mChannels = DevFmtX71; else if(MatchChannelMap(chanmap, X61Map)) mChannels = DevFmtX61; @@ -764,7 +777,7 @@ void DeviceNode::parsePositions(const spa_pod *value) noexcept mChannels = DevFmtStereo; else mChannels = DevFmtMono; - TRACE("Device ID %u got %zu position%s for %s%s\n", mId, chanmap.size(), + TRACE("Device ID %" PRIu64 " got %zu position%s for %s%s\n", mSerial, chanmap.size(), (chanmap.size()==1)?"":"s", DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":""); } @@ -780,8 +793,8 @@ void DeviceNode::parseChannelCount(const spa_pod *value) noexcept mChannels = DevFmtStereo; else if(*chancount >= 1) mChannels = DevFmtMono; - TRACE("Device ID %u got %d channel%s for %s\n", mId, *chancount, (*chancount==1)?"":"s", - DevFmtChannelsString(mChannels)); + TRACE("Device ID %" PRIu64 " got %d channel%s for %s\n", mSerial, *chancount, + (*chancount==1)?"":"s", DevFmtChannelsString(mChannels)); } @@ -789,6 +802,7 @@ constexpr char MonitorPrefix[]{"Monitor of "}; constexpr auto MonitorPrefixLen = al::size(MonitorPrefix) - 1; constexpr char AudioSinkClass[]{"Audio/Sink"}; constexpr char AudioSourceClass[]{"Audio/Source"}; +constexpr char AudioSourceVirtualClass[]{"Audio/Source/Virtual"}; constexpr char AudioDuplexClass[]{"Audio/Duplex"}; constexpr char StreamClass[]{"Stream/"}; @@ -848,12 +862,13 @@ 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; NodeType ntype{}; if(al::strcasecmp(media_class, AudioSinkClass) == 0) ntype = NodeType::Sink; - else if(al::strcasecmp(media_class, AudioSourceClass) == 0) + else if(al::strcasecmp(media_class, AudioSourceClass) == 0 + || al::strcasecmp(media_class, AudioSourceVirtualClass) == 0) ntype = NodeType::Source; else if(al::strcasecmp(media_class, AudioDuplexClass) == 0) ntype = NodeType::Duplex; @@ -869,12 +884,26 @@ 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; +#ifdef PW_KEY_OBJECT_SERIAL + char *serial_end{}; + const char *serial_str{spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL)}; + uint64_t 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; + } +#else + uint64_t serial_id{info->id}; +#endif + 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", nodeName ? nodeName : "(nil)", serial_id); DeviceNode &node = DeviceNode::Add(info->id); + node.mSerial = serial_id; if(nodeName && *nodeName) node.mName = nodeName; else node.mName = "PipeWire node #"+std::to_string(info->id); node.mDevName = devName ? devName : ""; @@ -889,7 +918,7 @@ void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_po if(id == SPA_PARAM_EnumFormat) { DeviceNode *node{DeviceNode::Find(mId)}; - if(unlikely(!node)) return; + if(!node) [[unlikely]] return; if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)}) node->parseSampleRate(&prop->value); @@ -1094,6 +1123,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t /* Specifically, audio sinks and sources (and duplexes). */ const bool isGood{al::strcasecmp(media_class, AudioSinkClass) == 0 || al::strcasecmp(media_class, AudioSourceClass) == 0 + || al::strcasecmp(media_class, AudioSourceVirtualClass) == 0 || al::strcasecmp(media_class, AudioDuplexClass) == 0}; if(!isGood) { @@ -1227,6 +1257,7 @@ 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; @@ -1262,7 +1293,7 @@ class PipeWirePlayback final : public BackendBase { void stop() override; ClockLatency getClockLatency() override; - uint32_t mTargetId{PwIdAny}; + uint64_t mTargetId{PwIdAny}; nanoseconds mTimeBase{0}; ThreadMainloop mLoop; PwContextPtr mContext; @@ -1312,7 +1343,7 @@ void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size) void PipeWirePlayback::outputCallback() { 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, minu(mNumChannels, pw_buf->buffer->n_datas)}; @@ -1328,7 +1359,7 @@ void PipeWirePlayback::outputCallback() uint length{mRateMatch ? mRateMatch->size : 0u}; #endif /* If no length is specified, use the device's update size as a fallback. */ - if(unlikely(!length)) length = mDevice->UpdateSize; + 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 @@ -1360,7 +1391,7 @@ void PipeWirePlayback::open(const char *name) { static std::atomic OpenCount{0}; - uint32_t targetid{PwIdAny}; + uint64_t targetid{PwIdAny}; std::string devname{}; gEventHandler.waitForInit(); if(!name) @@ -1385,7 +1416,7 @@ void PipeWirePlayback::open(const char *name) "No PipeWire playback device found"}; } - targetid = match->mId; + targetid = match->mSerial; devname = match->mName; } else @@ -1400,7 +1431,7 @@ void PipeWirePlayback::open(const char *name) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; - targetid = match->mId; + targetid = match->mSerial; devname = match->mName; } @@ -1465,7 +1496,7 @@ bool PipeWirePlayback::reset() 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()) { @@ -1473,10 +1504,11 @@ bool PipeWirePlayback::reset() { /* Scale the update size if the sample rate changes. */ const double scale{static_cast(match->mSampleRate) / mDevice->Frequency}; + const double numbufs{static_cast(mDevice->BufferSize)/mDevice->UpdateSize}; mDevice->Frequency = match->mSampleRate; mDevice->UpdateSize = static_cast(clampd(mDevice->UpdateSize*scale + 0.5, 64.0, 8192.0)); - mDevice->BufferSize = mDevice->UpdateSize * 2; + mDevice->BufferSize = static_cast(numbufs*mDevice->UpdateSize + 0.5); } if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig) mDevice->FmtChans = match->mChannels; @@ -1502,7 +1534,13 @@ bool PipeWirePlayback::reset() 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", @@ -1512,16 +1550,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. */ @@ -1532,9 +1568,11 @@ 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.c_str(), "pipewire", "rt-mix", true)) + 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}; @@ -1549,10 +1587,12 @@ bool PipeWirePlayback::reset() 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(); @@ -1583,11 +1623,62 @@ void PipeWirePlayback::start() return state == PW_STREAM_STATE_STREAMING; }); - if(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 + + 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) + 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() @@ -1637,7 +1728,7 @@ ClockLatency PipeWirePlayback::getClockLatency() */ 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. @@ -1703,7 +1794,7 @@ class PipeWireCapture final : public BackendBase { void captureSamples(al::byte *buffer, uint samples) override; uint availableSamples() override; - uint32_t mTargetId{PwIdAny}; + uint64_t mTargetId{PwIdAny}; ThreadMainloop mLoop; PwContextPtr mContext; PwCorePtr mCore; @@ -1735,7 +1826,7 @@ void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, con void PipeWireCapture::inputCallback() { 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)}; @@ -1751,7 +1842,7 @@ void PipeWireCapture::open(const char *name) { static std::atomic OpenCount{0}; - uint32_t targetid{PwIdAny}; + uint64_t targetid{PwIdAny}; std::string devname{}; gEventHandler.waitForInit(); if(!name) @@ -1780,7 +1871,7 @@ 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; } @@ -1803,7 +1894,7 @@ void PipeWireCapture::open(const char *name) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; - targetid = match->mId; + targetid = match->mSerial; devname = name; } @@ -1853,7 +1944,7 @@ void PipeWireCapture::open(const char *name) 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; @@ -1869,7 +1960,11 @@ void PipeWireCapture::open(const char *name) 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", @@ -1879,10 +1974,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. @@ -1890,6 +1981,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)}; @@ -1901,7 +1997,7 @@ void PipeWireCapture::open(const char *name) constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS}; - if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, mTargetId, Flags, params, 1)}) + if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, params, 1)}) throw al::backend_exception{al::backend_error::DeviceError, "Error connecting PipeWire stream (res: %d)", res}; diff --git a/thirdparty/openal/alc/backends/pulseaudio.cpp b/thirdparty/openal/alc/backends/pulseaudio.cpp index 30f486c740..9b7e2c0804 100644 --- a/thirdparty/openal/alc/backends/pulseaudio.cpp +++ b/thirdparty/openal/alc/backends/pulseaudio.cpp @@ -28,18 +28,13 @@ #include #include #include -#include #include -#include #include #include -#include -#include #include #include #include #include -#include #include #include "albyte.h" @@ -50,7 +45,6 @@ #include "alspan.h" #include "core/devformat.h" #include "core/device.h" -#include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "opthelpers.h" @@ -66,12 +60,6 @@ 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); \ @@ -112,6 +100,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,12 +131,6 @@ 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 @@ -176,6 +167,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,26 +234,39 @@ 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; } @@ -277,102 +290,53 @@ al::vector CaptureDevices; 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{}; 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() { if(mLoop) pa_threaded_mainloop_free(mLoop); } + + 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); } + 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); } + auto getApi() const { return pa_threaded_mainloop_get_api(mLoop); } - 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) 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_context *context, pa_stream *stream=nullptr); void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) noexcept { if(eol) { - mCondVar.notify_all(); + signal(); return; } @@ -398,14 +362,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 { if(eol) { - mCondVar.notify_all(); + signal(); return; } @@ -431,27 +393,62 @@ 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) { - mThread = std::thread{std::mem_fn(&PulseMainloop::mainloop_proc), this}; - mCondVar.wait(plock, [this]() noexcept { return mMainloop; }); + if(op) + { + wait([op]{ return pa_operation_get_state(op) != PA_OPERATION_RUNNING; }); + pa_operation_unref(op); + } } - pa_context *context{pa_context_new(pa_mainloop_get_api(mMainloop), 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(); + } + + pa_context *connectContext(); + pa_stream *connectStream(const char *device_name, pa_context *context, pa_stream_flags_t flags, + pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type); +}; +using MainloopLockGuard = std::lock_guard; + + +pa_context *MainloopUniqueLock::connectContext() +{ + pa_context *context{pa_context_new(mutex()->getApi(), nullptr)}; if(!context) throw al::backend_exception{al::backend_error::OutOfMemory, "pa_context_new() failed"}; - pa_context_set_state_callback(context, &contextStateCallbackC, this); + pa_context_set_state_callback(context, [](pa_context *ctx, void *pdata) noexcept + { return static_cast(pdata)->contextStateCallback(ctx); }, this); int err; if((err=pa_context_connect(context, nullptr, pulse_ctx_flags, nullptr)) >= 0) @@ -466,7 +463,7 @@ pa_context *PulseMainloop::connectContext(std::unique_lock &plock) break; } - mCondVar.wait(plock); + wait(); } } pa_context_set_state_callback(context, nullptr, nullptr); @@ -481,9 +478,9 @@ pa_context *PulseMainloop::connectContext(std::unique_lock &plock) return context; } -pa_stream *PulseMainloop::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) +pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_context *context, + pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, + BackendType type) { const char *stream_id{(type==BackendType::Playback) ? "Playback Stream" : "Capture Stream"}; pa_stream *stream{pa_stream_new(context, stream_id, spec, chanmap)}; @@ -491,7 +488,8 @@ pa_stream *PulseMainloop::connectStream(const char *device_name, throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed (%s)", pa_strerror(pa_context_errno(context))}; - pa_stream_set_state_callback(stream, &streamStateCallbackC, this); + 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) : @@ -514,7 +512,7 @@ pa_stream *PulseMainloop::connectStream(const char *device_name, "%s did not get ready (%s)", stream_id, pa_strerror(err)}; } - mCondVar.wait(plock); + wait(); } pa_stream_set_state_callback(stream, nullptr, nullptr); @@ -523,7 +521,7 @@ pa_stream *PulseMainloop::connectStream(const char *device_name, void PulseMainloop::close(pa_context *context, pa_stream *stream) { - std::lock_guard _{mMutex}; + MainloopUniqueLock _{*this}; if(stream) { pa_stream_set_state_callback(stream, nullptr, nullptr); @@ -542,19 +540,19 @@ void PulseMainloop::close(pa_context *context, pa_stream *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); + context = plock.connectContext(); + pa_operation *op{pa_context_get_sink_info_by_name(context, nullptr, sink_callback, this)}; + plock.waitForOperation(op); - op = pa_context_get_sink_info_list(context, &deviceSinkCallbackC, this); - waitForOperation(op, plock); + op = pa_context_get_sink_info_list(context, sink_callback, this); + plock.waitForOperation(op); pa_context_disconnect(context); pa_context_unref(context); @@ -562,26 +560,26 @@ void PulseMainloop::probePlaybackDevices() } catch(std::exception &e) { ERR("Error enumerating devices: %s\n", e.what()); - if(context) close(context, stream); + if(context) close(context); } } 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); + context = plock.connectContext(); + pa_operation *op{pa_context_get_source_info_by_name(context, nullptr, src_callback, this)}; + plock.waitForOperation(op); - op = pa_context_get_source_info_list(context, &deviceSourceCallbackC, this); - waitForOperation(op, plock); + op = pa_context_get_source_info_list(context, src_callback, this); + plock.waitForOperation(op); pa_context_disconnect(context); pa_context_unref(context); @@ -589,7 +587,7 @@ void PulseMainloop::probeCaptureDevices() } catch(std::exception &e) { ERR("Error enumerating devices: %s\n", e.what()); - if(context) close(context, stream); + if(context) close(context); } } @@ -603,28 +601,11 @@ 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; bool reset() override; @@ -677,7 +658,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,8 +666,8 @@ 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); @@ -699,7 +680,7 @@ void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexce 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 +692,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 +705,7 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int if(eol) { - mMainloop.getCondVar().notify_all(); + mMainloop.signal(); return; } @@ -755,7 +737,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; @@ -770,9 +752,11 @@ void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept void PulsePlayback::open(const char *name) { + mMainloop = PulseMainloop::Create(); + mMainloop.start(); + const char *pulse_name{nullptr}; const char *dev_name{nullptr}; - if(name) { if(PlaybackDevices.empty()) @@ -787,13 +771,12 @@ void PulsePlayback::open(const char *name) dev_name = iter->name.c_str(); } - auto plock = mMainloop.getUniqueLock(); - if(!mContext) - mContext = mMainloop.connectContext(plock); + MainloopUniqueLock plock{mMainloop}; + mContext = 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(nullptr, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; pa_sample_spec spec{}; @@ -807,28 +790,22 @@ 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, mContext, 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(pulse_name) mDeviceName.emplace(pulse_name); + else mDeviceName.reset(); if(!dev_name) { + auto name_callback = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept + { return static_cast(pdata)->sinkNameCallback(context, info, eol); }; pa_operation *op{pa_context_get_sink_info_by_name(mContext, - pa_stream_get_device_name(mStream), &PulsePlayback::sinkNameCallbackC, this)}; - mMainloop.waitForOperation(op, plock); + pa_stream_get_device_name(mStream), name_callback, this)}; + plock.waitForOperation(op); } else mDevice->DeviceName = dev_name; @@ -836,7 +813,7 @@ void PulsePlayback::open(const char *name) bool PulsePlayback::reset() { - auto plock = mMainloop.getUniqueLock(); + MainloopUniqueLock plock{mMainloop}; const auto deviceName = mDeviceName ? mDeviceName->c_str() : nullptr; if(mStream) @@ -850,15 +827,16 @@ 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(mContext, 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(nullptr, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "adjust-latency", 0)) + if(GetConfigValueBool(mDevice->DeviceName.c_str(), "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 +845,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.c_str(), "pulse", "fix-rate", false) || !mDevice->Flags.test(FrequencyRequest)) flags |= PA_STREAM_FIX_RATE; @@ -896,6 +874,9 @@ bool PulsePlayback::reset() case DevFmtX3D71: chanmap = X71ChanMap; break; + case DevFmtX714: + chanmap = X714ChanMap; + break; } setDefaultWFXChannelOrder(); @@ -935,11 +916,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, mContext, flags, &mAttr, &mSpec, &chanmap, + BackendType::Playback); - pa_stream_set_state_callback(mStream, &PulsePlayback::streamStateCallbackC, this); - pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this); + 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)); @@ -962,12 +945,14 @@ bool PulsePlayback::reset() 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; @@ -978,46 +963,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); } @@ -1029,12 +1001,12 @@ ClockLatency PulsePlayback::getClockLatency() int neg, err; { - auto plock = mMainloop.getUniqueLock(); + MainloopUniqueLock plock{mMainloop}; ret.ClockTime = GetDeviceClockTime(mDevice); 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 @@ -1045,7 +1017,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}; @@ -1058,16 +1030,8 @@ 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 start() override; @@ -1080,12 +1044,13 @@ struct PulseCapture final : public BackendBase { al::optional mDeviceName{al::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}; - pa_buffer_attr mAttr{}; pa_sample_spec mSpec{}; @@ -1113,14 +1078,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; @@ -1135,6 +1100,12 @@ void PulseCapture::streamMovedCallback(pa_stream *stream) noexcept void PulseCapture::open(const char *name) { + if(!mMainloop) + { + mMainloop = PulseMainloop::Create(); + mMainloop.start(); + } + const char *pulse_name{nullptr}; if(name) { @@ -1150,8 +1121,8 @@ void PulseCapture::open(const char *name) mDevice->DeviceName = iter->name; } - auto plock = mMainloop.getUniqueLock(); - mContext = mMainloop.connectContext(plock); + MainloopUniqueLock plock{mMainloop}; + mContext = plock.connectContext(); pa_channel_map chanmap{}; switch(mDevice->FmtChans) @@ -1174,6 +1145,9 @@ 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", @@ -1216,39 +1190,44 @@ void PulseCapture::open(const char *name) mAttr.fragsize = minu(samples, 50*mDevice->Frequency/1000) * frame_size; pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY}; - if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1)) + if(!GetConfigValueBool(nullptr, "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, mContext, flags, &mAttr, &mSpec, &chanmap, BackendType::Capture); - pa_stream_set_moved_callback(mStream, &PulseCapture::streamMovedCallbackC, this); - pa_stream_set_state_callback(mStream, &PulseCapture::streamStateCallbackC, this); + 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()) { + auto name_callback = [](pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept + { return static_cast(pdata)->sourceNameCallback(context, info, eol); }; pa_operation *op{pa_context_get_source_info_by_name(mContext, - pa_stream_get_device_name(mStream), &PulseCapture::sourceNameCallbackC, this)}; - mMainloop.waitForOperation(op, plock); + 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) @@ -1256,42 +1235,50 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) 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{minz(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()); + 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) + if(pa_stream_peek(mStream, &capbuf, &caplen) < 0) [[unlikely]] { mDevice->handleDisconnect("Failed retrieving capture samples: %s", pa_strerror(pa_context_errno(mContext))); @@ -1300,11 +1287,11 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) 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); @@ -1312,13 +1299,13 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) uint PulseCapture::availableSamples() { - size_t readable{mCapBuffer.size()}; + size_t readable{maxz(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); @@ -1326,11 +1313,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)); @@ -1344,18 +1337,18 @@ ClockLatency PulseCapture::getClockLatency() int neg, err; { - auto plock = mMainloop.getUniqueLock(); + MainloopUniqueLock plock{mMainloop}; ret.ClockTime = GetDeviceClockTime(mDevice); 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}; @@ -1408,12 +1401,18 @@ bool PulseBackendFactory::init() #endif /* HAVE_DYNLOAD */ pulse_ctx_flags = PA_CONTEXT_NOFLAGS; - if(!GetConfigValueBool(nullptr, "pulse", "spawn-server", 1)) + if(!GetConfigValueBool(nullptr, "pulse", "spawn-server", false)) pulse_ctx_flags |= PA_CONTEXT_NOAUTOSPAWN; try { - auto plock = gGlobalMainloop.getUniqueLock(); - pa_context *context{gGlobalMainloop.connectContext(plock)}; + if(!gGlobalMainloop) + { + gGlobalMainloop = PulseMainloop::Create(); + gGlobalMainloop.start(); + } + + MainloopUniqueLock plock{gGlobalMainloop}; + pa_context *context{plock.connectContext()}; pa_context_disconnect(context); pa_context_unref(context); return true; diff --git a/thirdparty/openal/alc/backends/sndio.cpp b/thirdparty/openal/alc/backends/sndio.cpp index 48387c651a..077e77f200 100644 --- a/thirdparty/openal/alc/backends/sndio.cpp +++ b/thirdparty/openal/alc/backends/sndio.cpp @@ -22,13 +22,13 @@ #include "sndio.h" +#include +#include #include #include #include #include - #include -#include #include "alnumeric.h" #include "core/device.h" @@ -98,9 +98,9 @@ int SndioPlayback::mixerProc() 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; } @@ -353,7 +353,14 @@ int SndioCapture::recordProc() 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); diff --git a/thirdparty/openal/alc/backends/wasapi.cpp b/thirdparty/openal/alc/backends/wasapi.cpp index 9007da1b19..e834eef427 100644 --- a/thirdparty/openal/alc/backends/wasapi.cpp +++ b/thirdparty/openal/alc/backends/wasapi.cpp @@ -57,6 +57,7 @@ #include #include "albit.h" +#include "alc/alconfig.h" #include "alnumeric.h" #include "comptr.h" #include "core/converter.h" @@ -86,6 +87,7 @@ DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x namespace { +using std::chrono::nanoseconds; using std::chrono::milliseconds; using std::chrono::seconds; @@ -102,6 +104,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,6 +122,7 @@ 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}; @@ -422,7 +426,6 @@ void TraceFormat(const char *msg, const WAVEFORMATEX *format) enum class MsgType { OpenDevice, - ReopenDevice, ResetDevice, StartDevice, StopDevice, @@ -436,7 +439,6 @@ enum class MsgType { constexpr char MessageStr[static_cast(MsgType::Count)][20]{ "Open Device", - "Reopen Device", "Reset Device", "Start Device", "Stop Device", @@ -465,9 +467,12 @@ struct WasapiProxy { explicit operator bool() const noexcept { return mType != MsgType::QuitThread; } }; + static std::thread sThread; static std::deque mMsgQueue; static std::mutex mMsgQueueLock; static std::condition_variable mMsgQueueCond; + static std::mutex sThreadLock; + static size_t sInitCount; std::future pushMessage(MsgType type, const char *param=nullptr) { @@ -503,42 +508,60 @@ struct WasapiProxy { } static int messageHandler(std::promise *promise); + + static HRESULT InitThread() + { + std::lock_guard _{sThreadLock}; + HRESULT res{S_OK}; + if(!sThread.joinable()) + { + std::promise promise; + auto future = promise.get_future(); + + sThread = std::thread{&WasapiProxy::messageHandler, &promise}; + res = future.get(); + if(FAILED(res)) + { + sThread.join(); + return res; + } + } + ++sInitCount; + return res; + } + + static void DeinitThread() + { + std::lock_guard _{sThreadLock}; + if(!--sInitCount && sThread.joinable()) + { + pushMessageStatic(MsgType::QuitThread); + sThread.join(); + } + } }; +std::thread WasapiProxy::sThread; std::deque WasapiProxy::mMsgQueue; std::mutex WasapiProxy::mMsgQueueLock; std::condition_variable WasapiProxy::mMsgQueueCond; +std::mutex WasapiProxy::sThreadLock; +size_t WasapiProxy::sInitCount{0}; int WasapiProxy::messageHandler(std::promise *promise) { TRACE("Starting message thread\n"); - HRESULT cohr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; - if(FAILED(cohr)) - { - WARN("Failed to initialize COM: 0x%08lx\n", cohr); - promise->set_value(cohr); - return 0; - } - - void *ptr{}; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr)}; + HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; if(FAILED(hr)) { - WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); + WARN("Failed to initialize COM: 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); promise = nullptr; TRACE("Starting message loop\n"); - uint deviceCount{0}; while(Msg msg{popMessage()}) { TRACE("Got message \"%s\" (0x%04x, this=%p, param=%p)\n", @@ -548,21 +571,6 @@ int WasapiProxy::messageHandler(std::promise *promise) 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,36 +593,29 @@ 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)) + { + void *ptr{}; 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(FAILED(hr)) + msg.mPromise.set_value(hr); + else + { + ComPtr devenum{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(msg.mType == MsgType::EnumeratePlayback) + probe_devices(devenum.get(), eRender, PlaybackDevices); + else if(msg.mType == MsgType::EnumerateCapture) + probe_devices(devenum.get(), eCapture, CaptureDevices); + msg.mPromise.set_value(S_OK); + } + continue; } - if(--deviceCount == 0 && SUCCEEDED(cohr)) - CoUninitialize(); - continue; - case MsgType::QuitThread: break; } @@ -622,6 +623,7 @@ int WasapiProxy::messageHandler(std::promise *promise) msg.mPromise.set_value(E_FAIL); } TRACE("Message loop finished\n"); + CoUninitialize(); return 0; } @@ -652,7 +654,12 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { ComPtr mRender{nullptr}; HANDLE mNotifyEvent{nullptr}; - UINT32 mFrameStep{0u}; + UINT32 mOrigBufferSize{}, mOrigUpdateSize{}; + std::unique_ptr mResampleBuffer{}; + uint mBufferFilled{0}; + SampleConverterPtr mResampler; + + WAVEFORMATEXTENSIBLE mFormat{}; std::atomic mPadding{0u}; std::mutex mMutex; @@ -666,7 +673,10 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { WasapiPlayback::~WasapiPlayback() { if(SUCCEEDED(mOpenStatus)) + { pushMessage(MsgType::CloseDevice).wait(); + DeinitThread(); + } mOpenStatus = E_FAIL; if(mNotifyEvent != nullptr) @@ -688,8 +698,9 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - const uint update_size{mDevice->UpdateSize}; - const UINT32 buffer_len{mDevice->BufferSize}; + const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u}; + const uint update_size{mOrigUpdateSize}; + const UINT32 buffer_len{mOrigBufferSize}; while(!mKillNow.load(std::memory_order_relaxed)) { UINT32 written; @@ -715,9 +726,37 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() hr = mRender->GetBuffer(len, &buffer); if(SUCCEEDED(hr)) { + if(mResampler) { std::lock_guard _{mMutex}; - mDevice->renderSamples(buffer, len, mFrameStep); + for(UINT32 done{0};done < len;) + { + if(mBufferFilled == 0) + { + mDevice->renderSamples(mResampleBuffer.get(), mDevice->UpdateSize, + mFormat.Format.nChannels); + mBufferFilled = mDevice->UpdateSize; + } + + const void *src{mResampleBuffer.get()}; + uint srclen{mBufferFilled}; + uint got{mResampler->convert(&src, &srclen, buffer, len-done)}; + buffer += got*frame_size; + done += got; + + mPadding.store(written + done, std::memory_order_relaxed); + if(srclen) + { + const char *bsrc{static_cast(src)}; + std::copy(bsrc, bsrc + srclen*frame_size, mResampleBuffer.get()); + } + mBufferFilled = srclen; + } + } + else + { + std::lock_guard _{mMutex}; + mDevice->renderSamples(buffer, len, mFormat.Format.nChannels); mPadding.store(written + len, std::memory_order_relaxed); } hr = mRender->ReleaseBuffer(len, 0); @@ -738,44 +777,44 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() void WasapiPlayback::open(const char *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; - } - } - - 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(SUCCEEDED(mOpenStatus)) - hr = pushMessage(MsgType::ReopenDevice, name).get(); - else - { - hr = pushMessage(MsgType::OpenDevice, name).get(); - mOpenStatus = hr; - } + ERR("Failed to create notify events: %lu\n", GetLastError()); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create notify events"}; } + HRESULT hr{InitThread()}; if(FAILED(hr)) + { + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to init COM thread: 0x%08lx", hr}; + } + + if(name) + { + if(PlaybackDevices.empty()) + pushMessage(MsgType::EnumeratePlayback); + if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) + { + name += DevNameHeadLen; + if(*name == '\0') + name = nullptr; + } + } + + mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); + if(FAILED(mOpenStatus)) + { + DeinitThread(); throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", - hr}; + mOpenStatus}; + } } HRESULT WasapiPlayback::openProxy(const char *name) @@ -888,7 +927,9 @@ HRESULT WasapiPlayback::resetProxy() */ 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 = DevFmtX71; + else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) mDevice->FmtChans = DevFmtX71; else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) mDevice->FmtChans = DevFmtX61; @@ -946,6 +987,10 @@ HRESULT WasapiPlayback::resetProxy() OutputType.Format.nChannels = 8; OutputType.dwChannelMask = X7DOT1; break; + case DevFmtX714: + OutputType.Format.nChannels = 12; + OutputType.dwChannelMask = X7DOT1DOT4; + break; } switch(mDevice->FmtType) { @@ -990,7 +1035,7 @@ HRESULT WasapiPlayback::resetProxy() hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); if(FAILED(hr)) { - ERR("Failed to check format support: 0x%08lx\n", hr); + WARN("Failed to check format support: 0x%08lx\n", hr); hr = mClient->GetMixFormat(&wfx); } if(FAILED(hr)) @@ -1010,7 +1055,11 @@ HRESULT WasapiPlayback::resetProxy() CoTaskMemFree(wfx); wfx = nullptr; - mDevice->Frequency = OutputType.Format.nSamplesPerSec; + if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "wasapi", "allow-resampler", true)) + mDevice->Frequency = OutputType.Format.nSamplesPerSec; + else + mDevice->Frequency = minu(mDevice->Frequency, OutputType.Format.nSamplesPerSec); + const uint32_t chancount{OutputType.Format.nChannels}; const DWORD chanmask{OutputType.dwChannelMask}; /* Don't update the channel format if the requested format fits what's @@ -1045,13 +1094,17 @@ HRESULT WasapiPlayback::resetProxy() 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 >= 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; @@ -1104,7 +1157,7 @@ HRESULT WasapiPlayback::resetProxy() } OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; } - mFrameStep = OutputType.Format.nChannels; + mFormat = OutputType; const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())}; mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset)); @@ -1133,8 +1186,31 @@ HRESULT WasapiPlayback::resetProxy() /* 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; + + mOrigBufferSize = buffer_len; + mOrigUpdateSize = minu(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec), buffer_len/2); + + mDevice->BufferSize = static_cast(uint64_t{buffer_len} * mDevice->Frequency / + mFormat.Format.nSamplesPerSec); + mDevice->UpdateSize = minu(RefTime2Samples(min_per, mDevice->Frequency), + mDevice->BufferSize/2); + + mResampler = nullptr; + mResampleBuffer = nullptr; + mBufferFilled = 0; + if(mDevice->Frequency != mFormat.Format.nSamplesPerSec) + { + mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, + mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec, + Resampler::FastBSinc24); + mResampleBuffer = std::make_unique(size_t{mDevice->UpdateSize} * + mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8); + + 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); + } hr = mClient->SetEventHandle(mNotifyEvent); if(FAILED(hr)) @@ -1211,8 +1287,14 @@ ClockLatency WasapiPlayback::getClockLatency() std::lock_guard _{mMutex}; ret.ClockTime = GetDeviceClockTime(mDevice); - ret.Latency = std::chrono::seconds{mPadding.load(std::memory_order_relaxed)}; - ret.Latency /= mDevice->Frequency; + 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; } @@ -1256,7 +1338,10 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { WasapiCapture::~WasapiCapture() { if(SUCCEEDED(mOpenStatus)) + { pushMessage(MsgType::CloseDevice).wait(); + DeinitThread(); + } mOpenStatus = E_FAIL; if(mNotifyEvent != nullptr) @@ -1359,35 +1444,44 @@ FORCE_ALIGN int WasapiCapture::recordProc() void WasapiCapture::open(const char *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; - + HRESULT hr{InitThread()}; if(FAILED(hr)) + { + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to init COM thread: 0x%08lx", hr}; + } + + if(name) + { + if(CaptureDevices.empty()) + pushMessage(MsgType::EnumerateCapture); + if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) + { + name += DevNameHeadLen; + if(*name == '\0') + name = nullptr; + } + } + + mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); + if(FAILED(mOpenStatus)) + { + DeinitThread(); throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", - hr}; + mOpenStatus}; + } hr = pushMessage(MsgType::ResetDevice).get(); if(FAILED(hr)) @@ -1518,6 +1612,10 @@ 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: @@ -1559,12 +1657,12 @@ HRESULT WasapiCapture::resetProxy() hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx); if(FAILED(hr)) { - WARN("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 check format support: 0x%08lx\n", hr); + ERR("Failed to find a supported capture format: 0x%08lx\n", hr); return hr; } @@ -1606,6 +1704,8 @@ HRESULT WasapiCapture::resetProxy() 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()); } @@ -1681,8 +1781,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", @@ -1806,11 +1907,31 @@ bool WasapiBackendFactory::init() if(FAILED(InitResult)) try { - std::promise promise; - auto future = promise.get_future(); + auto res = std::async(std::launch::async, []() -> HRESULT + { + HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; + if(FAILED(hr)) + { + WARN("Failed to initialize COM: 0x%08lx\n", hr); + return hr; + } - std::thread{&WasapiProxy::messageHandler, &promise}.detach(); - InitResult = future.get(); + void *ptr{}; + hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, + IID_IMMDeviceEnumerator, &ptr); + if(FAILED(hr)) + { + WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); + CoUninitialize(); + return hr; + } + static_cast(ptr)->Release(); + CoUninitialize(); + + return S_OK; + }); + + InitResult = res.get(); } catch(...) { } @@ -1823,7 +1944,17 @@ bool WasapiBackendFactory::querySupport(BackendType type) std::string WasapiBackendFactory::probe(BackendType type) { + struct ProxyControl { + HRESULT mResult{}; + ProxyControl() { mResult = WasapiProxy::InitThread(); } + ~ProxyControl() { if(SUCCEEDED(mResult)) WasapiProxy::DeinitThread(); } + }; + ProxyControl proxy; + std::string outnames; + if(FAILED(proxy.mResult)) + return outnames; + switch(type) { case BackendType::Playback: diff --git a/thirdparty/openal/alc/backends/wave.cpp b/thirdparty/openal/alc/backends/wave.cpp index 80e93f69ae..1b40640c7d 100644 --- a/thirdparty/openal/alc/backends/wave.cpp +++ b/thirdparty/openal/alc/backends/wave.cpp @@ -234,7 +234,7 @@ bool WaveBackend::reset() fseek(mFile, 0, SEEK_SET); clearerr(mFile); - if(GetConfigValueBool(nullptr, "wave", "bformat", 0)) + if(GetConfigValueBool(nullptr, "wave", "bformat", false)) { mDevice->FmtChans = DevFmtAmbi3D; mDevice->mAmbiOrder = 1; @@ -265,6 +265,10 @@ 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: diff --git a/thirdparty/openal/alc/backends/winmm.cpp b/thirdparty/openal/alc/backends/winmm.cpp index 14cc4f9ec5..38e1193f92 100644 --- a/thirdparty/openal/alc/backends/winmm.cpp +++ b/thirdparty/openal/alc/backends/winmm.cpp @@ -469,6 +469,7 @@ 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", diff --git a/thirdparty/openal/alc/context.cpp b/thirdparty/openal/alc/context.cpp index 9292926c16..b1b95321dc 100644 --- a/thirdparty/openal/alc/context.cpp +++ b/thirdparty/openal/alc/context.cpp @@ -30,11 +30,8 @@ #include "vecmat.h" #ifdef ALSOFT_EAX -#include #include - #include "alstring.h" -#include "al/eax/exception.h" #include "al/eax/globals.h" #endif // ALSOFT_EAX @@ -81,11 +78,13 @@ constexpr ALchar alExtList[] = "AL_SOFT_source_length " "AL_SOFT_source_resampler " "AL_SOFT_source_spatialize " + "AL_SOFTX_source_start_delay " "AL_SOFT_UHJ"; } // namespace +std::atomic ALCcontext::sGlobalContextLock{false}; std::atomic ALCcontext::sGlobalContext{nullptr}; thread_local ALCcontext *ALCcontext::sLocalContext{nullptr}; @@ -145,8 +144,8 @@ 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; @@ -155,7 +154,7 @@ void ALCcontext::init() else { auxslots = EffectSlot::CreatePtrArray(1); - (*auxslots)[0] = &mDefaultSlot->mSlot; + (*auxslots)[0] = mDefaultSlot->mSlot; mDefaultSlot->mState = SlotState::Playing; } mActiveAuxSlots.store(auxslots, std::memory_order_relaxed); @@ -200,12 +199,19 @@ bool ALCcontext::deinit() { 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{}; /* First make sure this context exists in the device's list. */ @@ -224,7 +230,7 @@ bool ALCcontext::deinit() * given context. */ std::copy_if(oldarray->begin(), oldarray->end(), newarray->begin(), - std::bind(std::not_equal_to<>{}, _1, this)); + [this](auto a){ return a != this; }); /* Store the new context array in the device. Wait for any current mix * to finish before deleting the old array. @@ -257,7 +263,8 @@ void ALCcontext::applyAllUpdates() } #ifdef ALSOFT_EAX - eax_apply_deferred(); + if(eax_is_initialized_) + eax_commit(); #endif if(std::exchange(mPropsDirty, false)) UpdateContextProps(this); @@ -273,19 +280,6 @@ void ALCcontext::applyAllUpdates() #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) { @@ -312,14 +306,11 @@ bool ALCcontext::eax_is_capable() const noexcept void ALCcontext::eax_uninitialize() noexcept { - if (!eax_is_initialized_) - { + if(!eax_is_initialized_) return; - } - eax_is_initialized_ = true; + eax_is_initialized_ = false; eax_is_tried_ = false; - eax_fx_slots_.uninitialize(); } @@ -337,31 +328,30 @@ ALenum ALCcontext::eax_eax_set( property_source_id, property_value, property_value_size); - eax_version_ = call.get_version(); - eax_initialize(call); - eax_unlock_legacy_fx_slots(call); - switch (call.get_property_set_id()) + const auto eax_version = call.get_version(); + if(eax_version != eax_version_) + eax_df_ = ~EaxDirtyFlags(); + eax_version_ = eax_version; + eax_initialize(); + + switch(call.get_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("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(); } - static constexpr auto deferred_flag = 0x80000000u; - if(!(property_id&deferred_flag) && !mDeferUpdates) + if(!call.is_deferred() && !mDeferUpdates) applyAllUpdates(); return AL_NO_ERROR; @@ -382,36 +372,27 @@ ALenum ALCcontext::eax_eax_get( property_value, property_value_size); eax_version_ = call.get_version(); - eax_initialize(call); - eax_unlock_legacy_fx_slots(call); + eax_initialize(); - switch (call.get_property_set_id()) + switch(call.get_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("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() -{ - ForEachSource(this, [](ALsource& source){ source.eax_commit(); }); -} - void ALCcontext::eax_commit_and_update_sources() { std::unique_lock source_lock{mSourceLock}; @@ -423,19 +404,35 @@ 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 + @@ -444,13 +441,11 @@ void ALCcontext::eax_initialize_extensions() 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; + std::strlen(eax_x_ram_ext_name) + 1; eax_extension_list_.reserve(string_max_capacity); - if (eax_is_capable()) - { + if(eax_is_capable()) { eax_extension_list_ += eax1_ext_name; eax_extension_list_ += ' '; @@ -474,33 +469,31 @@ void ALCcontext::eax_initialize_extensions() mExtensionList = eax_extension_list_.c_str(); } -void ALCcontext::eax_initialize(const EaxCall& call) +void ALCcontext::eax_initialize() { - if (eax_is_initialized_) - { + if(eax_is_initialized_) return; - } - if (eax_is_tried_) - { + if(eax_is_tried_) eax_fail("No EAX."); - } eax_is_tried_ = 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(call); + eax_initialize_fx_slots(); eax_initialize_sources(); eax_is_initialized_ = true; + mPropsDirty = true; + + if(!mDeferUpdates) + applyAllUpdates(); } bool ALCcontext::eax_has_no_default_effect_slot() const noexcept @@ -510,10 +503,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 @@ -523,10 +514,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() @@ -554,6 +543,10 @@ 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). */ @@ -580,36 +573,87 @@ void ALCcontext::eax_set_last_error_defaults() noexcept eax_last_error_ = EAX_OK; } -void ALCcontext::eax_set_session_defaults() noexcept +void ALCcontext::eax_session_set_defaults() noexcept { - eax_session_.ulEAXVersion = EAXCONTEXT_MINEAXSESSION; + eax_session_.ulEAXVersion = EAXCONTEXT_DEFAULTEAXSESSION; eax_session_.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::eax4_context_set_current_defaults(const Eax4Props& props) noexcept +{ + static_cast(eax_) = props; + eax_.flMacroFXFactor = EAXCONTEXT_DEFAULTMACROFXFACTOR; +} + +void ALCcontext::eax5_context_set_current_defaults(const Eax5Props& props) noexcept +{ + eax_ = props; +} + +void ALCcontext::eax_context_set_current_defaults() +{ + switch(eax_version_) + { + case 1: + case 2: + case 3: + eax5_context_set_current_defaults(eax123_.i); + break; + case 4: + eax4_context_set_current_defaults(eax4_.i); + break; + case 5: + eax5_context_set_current_defaults(eax5_.i); + break; + default: + eax_fail_unknown_version(); + } + + eax_df_ = ~EaxDirtyFlags{}; +} + +void ALCcontext::eax_context_set_defaults() +{ + eax5_context_set_defaults(eax123_); + eax4_context_set_defaults(eax4_); + eax5_context_set_defaults(eax5_); + eax_context_set_current_defaults(); +} + +void ALCcontext::eax_set_defaults() { eax_set_last_error_defaults(); - eax_set_session_defaults(); - eax_set_context_defaults(); - - eax_d_ = eax_; -} - -void ALCcontext::eax_unlock_legacy_fx_slots(const EaxCall& call) noexcept -{ - if (call.get_version() != 5 || eax_are_legacy_fx_slots_unlocked_) - return; - - eax_are_legacy_fx_slots_unlocked_ = true; - eax_fx_slots_.unlock_legacy(); + eax_session_set_defaults(); + eax_context_set_defaults(); } void ALCcontext::eax_dispatch_fx_slot(const EaxCall& call) @@ -622,7 +666,7 @@ void ALCcontext::eax_dispatch_fx_slot(const EaxCall& call) if(fx_slot.eax_dispatch(call)) { std::lock_guard source_lock{mSourceLock}; - eax_update_filters(); + ForEachSource(this, [](ALsource& source){ source.eax_mark_as_changed(); }); } } @@ -638,152 +682,128 @@ void ALCcontext::eax_dispatch_source(const EaxCall& call) source->eax_dispatch(call); } -void ALCcontext::eax_get_primary_fx_slot_id(const EaxCall& call) +void ALCcontext::eax_get_misc(const EaxCall& call) { - call.set_value(eax_.context.guidPrimaryFXSlotID); -} - -void ALCcontext::eax_get_distance_factor(const EaxCall& call) -{ - call.set_value(eax_.context.flDistanceFactor); -} - -void ALCcontext::eax_get_air_absorption_hf(const EaxCall& call) -{ - call.set_value(eax_.context.flAirAbsorptionHF); -} - -void ALCcontext::eax_get_hf_reference(const EaxCall& call) -{ - call.set_value(eax_.context.flHFReference); -} - -void ALCcontext::eax_get_last_error(const EaxCall& call) -{ - const auto eax_last_error = eax_last_error_; - eax_last_error_ = EAX_OK; - call.set_value(eax_last_error); -} - -void ALCcontext::eax_get_speaker_config(const EaxCall& call) -{ - call.set_value(eax_speaker_config_); -} - -void ALCcontext::eax_get_session(const EaxCall& call) -{ - call.set_value(eax_session_); -} - -void ALCcontext::eax_get_macro_fx_factor(const EaxCall& call) -{ - call.set_value(eax_.context.flMacroFXFactor); -} - -void ALCcontext::eax_get_context_all(const EaxCall& call) -{ - switch (call.get_version()) + switch(call.get_property_id()) { - case 4: - call.set_value(static_cast(eax_.context)); - break; + case EAXCONTEXT_NONE: + break; + case EAXCONTEXT_LASTERROR: + call.set_value(eax_last_error_); + break; + case EAXCONTEXT_SPEAKERCONFIG: + call.set_value(eax_speaker_config_); + break; + case EAXCONTEXT_EAXSESSION: + call.set_value(eax_session_); + break; + default: + eax_fail_unknown_property_id(); + } +} - case 5: - call.set_value(static_cast(eax_.context)); - break; +void ALCcontext::eax4_get(const EaxCall& call, const Eax4Props& props) +{ + 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; + default: + eax_get_misc(call); + break; + } +} - default: - eax_fail("Unsupported EAX version."); +void ALCcontext::eax5_get(const EaxCall& call, const Eax5Props& props) +{ + 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_get(const EaxCall& call) { - switch (call.get_property_id()) + switch(call.get_version()) { - case EAXCONTEXT_NONE: - break; - - case EAXCONTEXT_ALLPARAMETERS: - eax_get_context_all(call); - break; - - case EAXCONTEXT_PRIMARYFXSLOTID: - eax_get_primary_fx_slot_id(call); - break; - - case EAXCONTEXT_DISTANCEFACTOR: - eax_get_distance_factor(call); - break; - - case EAXCONTEXT_AIRABSORPTIONHF: - eax_get_air_absorption_hf(call); - break; - - case EAXCONTEXT_HFREFERENCE: - eax_get_hf_reference(call); - break; - - case EAXCONTEXT_LASTERROR: - eax_get_last_error(call); - break; - - case EAXCONTEXT_SPEAKERCONFIG: - eax_get_speaker_config(call); - break; - - case EAXCONTEXT_EAXSESSION: - eax_get_session(call); - break; - - case EAXCONTEXT_MACROFXFACTOR: - eax_get_macro_fx_factor(call); - break; - - default: - eax_fail("Unsupported property id."); + case 4: eax4_get(call, eax4_.i); break; + case 5: eax5_get(call, eax5_.i); break; + default: eax_fail_unknown_version(); } } -void ALCcontext::eax_set_primary_fx_slot_id() +void ALCcontext::eax_context_commit_primary_fx_slot_id() { - eax_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID; + eax_primary_fx_slot_index_ = eax_.guidPrimaryFXSlotID; } -void ALCcontext::eax_set_distance_factor() +void ALCcontext::eax_context_commit_distance_factor() { - mListener.mMetersPerUnit = eax_.context.flDistanceFactor; + if(mListener.mMetersPerUnit == eax_.flDistanceFactor) + return; + + mListener.mMetersPerUnit = eax_.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(eax_.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() +void ALCcontext::eax_initialize_fx_slots() { - 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(const EaxCall& call) -{ - eax_fx_slots_.initialize(call, *this); - eax_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID; + eax_fx_slots_.initialize(*this); + eax_primary_fx_slot_index_ = eax_.guidPrimaryFXSlotID; } void ALCcontext::eax_initialize_sources() @@ -802,408 +822,250 @@ void ALCcontext::eax_update_sources() 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 EaxCall& call) -{ - switch(call.get_version()) - { - case 4: - { - const auto& context_all = - 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 = - call.get_value(); - - eax_validate_context_all(context_all); - eax_defer_context_all(context_all); - } + case EAXCONTEXT_SPEAKERCONFIG: + eax_set(call, eax_speaker_config_); + break; + case EAXCONTEXT_EAXSESSION: + eax_set(call, eax_session_); break; - default: - eax_fail("Unsupported EAX version."); + eax_fail_unknown_property_id(); } } -void ALCcontext::eax_defer_primary_fx_slot_id(const EaxCall& call) +void ALCcontext::eax4_defer_all(const EaxCall& call, Eax4State& state) { - const auto& primary_fx_slot_id = - 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) + eax_df_ |= eax_primary_fx_slot_id_dirty_bit; + + if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) + eax_df_ |= eax_distance_factor_dirty_bit; + + if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) + eax_df_ |= eax_air_absorption_hf_dirty_bit; + + if(dst_i.flHFReference != dst_d.flHFReference) + eax_df_ |= eax_hf_reference_dirty_bit; } -void ALCcontext::eax_defer_distance_factor(const EaxCall& call) +void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state) { - const auto& distance_factor = - call.get_value(); - - eax_validate_distance_factor(distance_factor); - eax_defer_distance_factor(distance_factor); + switch(call.get_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_defer_air_absorption_hf(const EaxCall& call) +void ALCcontext::eax5_defer_all(const EaxCall& call, Eax5State& state) { - const auto& air_absorption_hf = - 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_air_absorption_hf(air_absorption_hf); - eax_defer_air_absorption_hf(air_absorption_hf); + if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) + eax_df_ |= eax_primary_fx_slot_id_dirty_bit; + + if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) + eax_df_ |= eax_distance_factor_dirty_bit; + + if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) + eax_df_ |= eax_air_absorption_hf_dirty_bit; + + if(dst_i.flHFReference != dst_d.flHFReference) + eax_df_ |= eax_hf_reference_dirty_bit; + + if(dst_i.flMacroFXFactor != dst_d.flMacroFXFactor) + eax_df_ |= eax_macro_fx_factor_dirty_bit; } -void ALCcontext::eax_defer_hf_reference(const EaxCall& call) +void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state) { - const auto& hf_reference = - call.get_value(); - - eax_validate_hf_reference(hf_reference); - eax_defer_hf_reference(hf_reference); -} - -void ALCcontext::eax_set_session(const EaxCall& call) -{ - const auto& eax_session = - call.get_value(); - - eax_validate_session(eax_session); - - eax_session_ = eax_session; -} - -void ALCcontext::eax_defer_macro_fx_factor(const EaxCall& call) -{ - const auto& macro_fx_factor = - call.get_value(); - - eax_validate_macro_fx_factor(macro_fx_factor); - eax_defer_macro_fx_factor(macro_fx_factor); + 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) { - switch (call.get_property_id()) + switch(call.get_version()) { - case EAXCONTEXT_NONE: - break; - - case EAXCONTEXT_ALLPARAMETERS: - eax_defer_context_all(call); - break; - - case EAXCONTEXT_PRIMARYFXSLOTID: - eax_defer_primary_fx_slot_id(call); - break; - - case EAXCONTEXT_DISTANCEFACTOR: - eax_defer_distance_factor(call); - break; - - case EAXCONTEXT_AIRABSORPTIONHF: - eax_defer_air_absorption_hf(call); - break; - - case EAXCONTEXT_HFREFERENCE: - eax_defer_hf_reference(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(call); - break; - - case EAXCONTEXT_MACROFXFACTOR: - eax_defer_macro_fx_factor(call); - break; - - default: - eax_fail("Unsupported property id."); + case 4: eax4_defer(call, eax4_); break; + case 5: eax5_defer(call, eax5_); break; + default: eax_fail_unknown_version(); } } -void ALCcontext::eax_apply_deferred() +void ALCcontext::eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df) { - if (eax_context_dirty_flags_ == ContextDirtyFlags{}) - { + if(eax_df_ == EaxDirtyFlags{}) return; - } - eax_ = eax_d_; + 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); - if (eax_context_dirty_flags_.guidPrimaryFXSlotID) - { - eax_set_primary_fx_slot_id(); - } - - if (eax_context_dirty_flags_.flDistanceFactor) - { - eax_set_distance_factor(); - } - - if (eax_context_dirty_flags_.flAirAbsorptionHF) - { - eax_set_air_absorbtion_hf(); - } - - if (eax_context_dirty_flags_.flHFReference) - { - eax_set_hf_reference(); - } - - if (eax_context_dirty_flags_.flMacroFXFactor) - { - eax_set_macro_fx_factor(); - } - - if (eax_context_dirty_flags_.guidPrimaryFXSlotID) - { - eax_update_sources(); - } - - eax_context_dirty_flags_ = ContextDirtyFlags{}; + eax_df_ = EaxDirtyFlags{}; } - -namespace +void ALCcontext::eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df) { + if(eax_df_ == 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); -class EaxSetException : - public EaxException + eax_df_ = EaxDirtyFlags{}; +} + +void ALCcontext::eax_context_commit() { -public: - explicit EaxSetException( - const char* message) - : - EaxException{"EAX_SET", message} + auto dst_df = EaxDirtyFlags{}; + + switch(eax_version_) { + case 1: + case 2: + case 3: + eax5_context_commit(eax123_, dst_df); + break; + case 4: + eax4_context_commit(eax4_, dst_df); + break; + case 5: + eax5_context_commit(eax5_, dst_df); + break; + default: + eax_fail_unknown_version(); } -}; // EaxSetException + if(dst_df == EaxDirtyFlags{}) + return; -[[noreturn]] -void eax_fail_set( - const char* message) + if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_primary_fx_slot_id(); + + if((dst_df & eax_distance_factor_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_distance_factor(); + + if((dst_df & eax_air_absorption_hf_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_air_absorbtion_hf(); + + if((dst_df & eax_hf_reference_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_hf_reference(); + + if((dst_df & eax_macro_fx_factor_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_macro_fx_factor(); + + if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{}) + eax_update_sources(); +} + +void ALCcontext::eax_commit() +{ + eax_context_commit(); +} + +namespace { + +class EaxSetException : public EaxException { +public: + explicit EaxSetException(const char* message) + : EaxException{"EAX_SET", message} + {} +}; + +[[noreturn]] void eax_fail_set(const char* message) { throw EaxSetException{message}; } - -class EaxGetException : - public EaxException -{ +class EaxGetException : public EaxException { public: - explicit EaxGetException( - const char* message) - : - EaxException{"EAX_GET", message} - { - } -}; // EaxGetException + explicit EaxGetException(const char* message) + : EaxException{"EAX_GET", message} + {} +}; - -[[noreturn]] -void eax_fail_get( - const char* message) +[[noreturn]] void eax_fail_get(const char* message) { throw EaxGetException{message}; } - } // namespace @@ -1217,10 +1079,8 @@ try { auto context = GetContextRef(); - if (!context) - { + if(!context) eax_fail_set("No current context."); - } std::lock_guard prop_lock{context->mPropLock}; @@ -1229,8 +1089,7 @@ try property_id, property_source_id, property_value, - property_value_size - ); + property_value_size); } catch (...) { @@ -1248,10 +1107,8 @@ try { auto context = GetContextRef(); - if (!context) - { + if(!context) eax_fail_get("No current context."); - } std::lock_guard prop_lock{context->mPropLock}; @@ -1260,8 +1117,7 @@ try property_id, property_source_id, property_value, - property_value_size - ); + property_value_size); } catch (...) { diff --git a/thirdparty/openal/alc/context.h b/thirdparty/openal/alc/context.h index 2290839ebc..d93d63d65d 100644 --- a/thirdparty/openal/alc/context.h +++ b/thirdparty/openal/alc/context.h @@ -21,30 +21,10 @@ #ifdef ALSOFT_EAX #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" - - -using ContextDirtyFlagsValue = std::uint_least8_t; - -struct ContextDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - ContextDirtyFlagsValue guidPrimaryFXSlotID : 1; - ContextDirtyFlagsValue flDistanceFactor : 1; - ContextDirtyFlagsValue flAirAbsorptionHF : 1; - ContextDirtyFlagsValue flHFReference : 1; - ContextDirtyFlagsValue flMacroFXFactor : 1; -}; // ContextDirtyFlags - - -struct EaxAlIsExtensionPresentResult -{ - ALboolean is_present; - bool is_return; -}; // EaxAlIsExtensionPresentResult #endif // ALSOFT_EAX struct ALeffect; @@ -88,9 +68,6 @@ struct EffectSlotSubList { 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}; @@ -171,6 +148,7 @@ struct ALCcontext : public al::intrusive_ref, ContextBase { void setError(ALenum errorCode, const char *msg, ...); /* Process-wide current context */ + static std::atomic sGlobalContextLock; static std::atomic sGlobalContext; private: @@ -208,14 +186,11 @@ public: #ifdef ALSOFT_EAX public: bool has_eax() const noexcept { return eax_is_initialized_; } - bool eax_is_capable() const noexcept; - + int eax_get_version() const noexcept { return eax_version_; } void eax_uninitialize() noexcept; - int eax_get_version() const noexcept { return eax_version_; } - ALenum eax_eax_set( const GUID* property_set_id, ALuint property_id, @@ -230,15 +205,9 @@ public: ALvoid* property_value, ALuint property_value_size); - - void eax_update_filters(); - void eax_commit_and_update_sources(); - - void eax_set_last_error() noexcept; - EaxFxSlotIndex eax_get_primary_fx_slot_index() const noexcept { return eax_primary_fx_slot_index_; } @@ -251,15 +220,170 @@ public: { eax_fx_slots_.commit(); } private: - struct Eax - { - EAX50CONTEXTPROPERTIES context{}; - }; // 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 + { + 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(); + } + } + }; + + struct Eax4DistanceFactorValidator { + void operator()(float flDistanceFactor) const + { + eax_validate_range( + "Distance Factor", + flDistanceFactor, + EAXCONTEXT_MINDISTANCEFACTOR, + EAXCONTEXT_MAXDISTANCEFACTOR); + } + }; + + struct Eax4AirAbsorptionHfValidator { + void operator()(float flAirAbsorptionHF) const + { + eax_validate_range( + "Air Absorption HF", + flAirAbsorptionHF, + EAXCONTEXT_MINAIRABSORPTIONHF, + EAXCONTEXT_MAXAIRABSORPTIONHF); + } + }; + + struct Eax4HfReferenceValidator { + void operator()(float flHFReference) const + { + eax_validate_range( + "HF Reference", + flHFReference, + EAXCONTEXT_MINHFREFERENCE, + EAXCONTEXT_MAXHFREFERENCE); + } + }; + + struct Eax4AllValidator { + void operator()(const EAX40CONTEXTPROPERTIES& all) const + { + Eax4PrimaryFxSlotIdValidator{}(all.guidPrimaryFXSlotID); + Eax4DistanceFactorValidator{}(all.flDistanceFactor); + Eax4AirAbsorptionHfValidator{}(all.flAirAbsorptionHF); + Eax4HfReferenceValidator{}(all.flHFReference); + } + }; + + 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(); + } + } + }; + + struct Eax5MacroFxFactorValidator { + void operator()(float flMacroFXFactor) const + { + eax_validate_range( + "Macro FX Factor", + flMacroFXFactor, + EAXCONTEXT_MINMACROFXFACTOR, + EAXCONTEXT_MAXMACROFXFACTOR); + } + }; + + 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); + } + }; + + 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 eax_is_initialized_{}; bool eax_is_tried_{}; - bool eax_are_legacy_fx_slots_unlocked_{}; long eax_last_error_{}; unsigned long eax_speaker_config_{}; @@ -267,181 +391,130 @@ private: EaxFxSlotIndex eax_primary_fx_slot_index_{}; EaxFxSlots eax_fx_slots_{}; - int eax_version_{}; - Eax eax_{}; - Eax eax_d_{}; + int eax_version_{}; // Current EAX version. + EaxDirtyFlags eax_df_{}; // Dirty flags for the current EAX version. + Eax5State eax123_{}; // EAX1/EAX2/EAX3 state. + Eax4State eax4_{}; // EAX4 state. + Eax5State eax5_{}; // EAX5 state. + Eax5Props eax_{}; // Current EAX state. EAXSESSIONPROPERTIES eax_session_{}; - ContextDirtyFlags eax_context_dirty_flags_{}; - std::string eax_extension_list_{}; + [[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(); - [[noreturn]] - static void eax_fail( - const char* message); + // 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) noexcept + { + 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) + eax_df_ |= 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((eax_df_ & TDirtyBit) != EaxDirtyFlags{}) + { + dst_df |= TDirtyBit; + const auto& src_d = state.d.*member; + state.i.*member = src_d; + eax_.*member = src_d; + } + } void eax_initialize_extensions(); - - void eax_initialize(const EaxCall& call); - + void eax_initialize(); bool eax_has_no_default_effect_slot() const noexcept; - void eax_ensure_no_default_effect_slot() const; - bool eax_has_enough_aux_sends() const noexcept; - void eax_ensure_enough_aux_sends() const; - void eax_ensure_compatibility(); - unsigned long eax_detect_speaker_configuration() const; void eax_update_speaker_configuration(); - void eax_set_last_error_defaults() noexcept; - - void eax_set_session_defaults() noexcept; - - void eax_set_context_defaults() noexcept; - - void eax_set_defaults() noexcept; + void eax_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 eax4_context_set_current_defaults(const Eax4Props& props) noexcept; + void eax5_context_set_current_defaults(const Eax5Props& props) noexcept; + void eax_context_set_current_defaults(); + void eax_context_set_defaults(); + void eax_set_defaults(); void eax_initialize_sources(); - - void eax_unlock_legacy_fx_slots(const EaxCall& call) noexcept; - - void eax_dispatch_fx_slot(const EaxCall& call); - void eax_dispatch_source(const EaxCall& call); - - void eax_get_primary_fx_slot_id(const EaxCall& call); - - void eax_get_distance_factor(const EaxCall& call); - - void eax_get_air_absorption_hf(const EaxCall& call); - - void eax_get_hf_reference(const EaxCall& call); - - void eax_get_last_error(const EaxCall& call); - - void eax_get_speaker_config(const EaxCall& call); - - void eax_get_session(const EaxCall& call); - - void eax_get_macro_fx_factor(const EaxCall& call); - - void eax_get_context_all(const EaxCall& call); - + 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_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_set_primary_fx_slot_id(); - - void eax_set_distance_factor(); - - void eax_set_air_absorbtion_hf(); - - void eax_set_hf_reference(); - - void eax_set_macro_fx_factor(); - - void eax_set_context(); - - void eax_initialize_fx_slots(const EaxCall& call); - + void eax_initialize_fx_slots(); void eax_update_sources(); - - void eax_validate_primary_fx_slot_id( - const GUID& primary_fx_slot_id); - - void eax_validate_distance_factor( - float distance_factor); - - void eax_validate_air_absorption_hf( - float air_absorption_hf); - - void eax_validate_hf_reference( - float hf_reference); - - void eax_validate_speaker_config( - unsigned long speaker_config); - - void eax_validate_session_eax_version( - unsigned long eax_version); - - void eax_validate_session_max_active_sends( - unsigned long max_active_sends); - - void eax_validate_session( - const EAXSESSIONPROPERTIES& eax_session); - - void eax_validate_macro_fx_factor( - float macro_fx_factor); - - void eax_validate_context_all( - const EAX40CONTEXTPROPERTIES& context_all); - - void eax_validate_context_all( - const EAX50CONTEXTPROPERTIES& context_all); - - - void eax_defer_primary_fx_slot_id( - const GUID& primary_fx_slot_id); - - void eax_defer_distance_factor( - float distance_factor); - - void eax_defer_air_absorption_hf( - float air_absorption_hf); - - void eax_defer_hf_reference( - float hf_reference); - - void eax_defer_macro_fx_factor( - float macro_fx_factor); - - void eax_defer_context_all( - const EAX40CONTEXTPROPERTIES& context_all); - - void eax_defer_context_all( - const EAX50CONTEXTPROPERTIES& context_all); - - - void eax_defer_context_all(const EaxCall& call); - - void eax_defer_primary_fx_slot_id(const EaxCall& call); - - void eax_defer_distance_factor(const EaxCall& call); - - void eax_defer_air_absorption_hf(const EaxCall& call); - - void eax_defer_hf_reference(const EaxCall& call); - - void eax_set_session(const EaxCall& call); - - void eax_defer_macro_fx_factor(const EaxCall& call); - + 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_apply_deferred(); + void eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df); + void eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df); + void eax_context_commit(); + void eax_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); diff --git a/thirdparty/openal/alc/device.cpp b/thirdparty/openal/alc/device.cpp index 6eeb907e25..66b13c5e40 100644 --- a/thirdparty/openal/alc/device.cpp +++ b/thirdparty/openal/alc/device.cpp @@ -84,8 +84,10 @@ auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1 case DevFmtX51: return OutputMode1::X51; case DevFmtX61: return OutputMode1::X61; case DevFmtX71: return OutputMode1::X71; + case DevFmtX714: case DevFmtX3D71: - case DevFmtAmbi3D: break; + case DevFmtAmbi3D: + break; } return OutputMode1::Any; } diff --git a/thirdparty/openal/alc/effects/autowah.cpp b/thirdparty/openal/alc/effects/autowah.cpp index 50c4a5adc6..f32c69b800 100644 --- a/thirdparty/openal/alc/effects/autowah.cpp +++ b/thirdparty/openal/alc/effects/autowah.cpp @@ -65,14 +65,16 @@ struct AutowahState final : public EffectState { } mEnv[BufferLineSize]; struct { + uint mTargetChannel{InvalidChannelIndex}; + /* Effect filters' history. */ struct { float z1, z2; - } Filter; + } mFilter; /* Effect gains for each output channel */ - float CurrentGains[MAX_OUTPUT_CHANNELS]; - float TargetGains[MAX_OUTPUT_CHANNELS]; + float mCurrentGain; + float mTargetGain; } mChans[MaxAmbiChannels]; /* Effects buffers */ @@ -108,9 +110,10 @@ 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; } } @@ -131,9 +134,12 @@ void AutowahState::update(const ContextBase *context, const EffectSlot *slot, 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, @@ -165,17 +171,24 @@ void AutowahState::process(const size_t samplesToDo, } mEnvDelay = env_delay; - auto chandata = std::addressof(mChans[0]); + auto chandata = std::begin(mChans); 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++) { @@ -197,12 +210,12 @@ void AutowahState::process(const size_t samplesToDo, 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, samplesToDo}, samplesOut[outidx].data(), chandata->mCurrentGain, + chandata->mTargetGain, samplesToDo); ++chandata; } } diff --git a/thirdparty/openal/alc/effects/chorus.cpp b/thirdparty/openal/alc/effects/chorus.cpp index 99a2a68a7a..44d8c32aa9 100644 --- a/thirdparty/openal/alc/effects/chorus.cpp +++ b/thirdparty/openal/alc/effects/chorus.cpp @@ -61,8 +61,8 @@ struct ChorusState final : public EffectState { /* Gains for left and right sides */ struct { - float Current[MAX_OUTPUT_CHANNELS]{}; - float Target[MAX_OUTPUT_CHANNELS]{}; + float Current[MaxAmbiChannels]{}; + float Target[MaxAmbiChannels]{}; } mGains[2]; /* effect parameters */ @@ -120,8 +120,8 @@ void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot, mFeedback = props->Chorus.Feedback; /* Gains for left and right sides */ - const auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f); - const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f); + static constexpr auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}); + static constexpr auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}); mOutTarget = target.Main->Buffer; ComputePanGains(target.Main, lcoeffs.data(), Slot->Gain, mGains[0].Target); @@ -172,17 +172,33 @@ 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{minz(todo-i, lfo_range-offset)}; + do { + delays[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{minz(todo-i, lfo_range-offset)}; + do { + delays[1][i++] = gen_lfo(offset++); + } while(--rem); + if(offset == lfo_range) + offset = 0; + } mLfoOffset = static_cast(mLfoOffset+todo) % lfo_range; } @@ -197,17 +213,33 @@ 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{minz(todo-i, lfo_range-offset)}; + do { + delays[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{minz(todo-i, lfo_range-offset)}; + do { + delays[1][i++] = gen_lfo(offset++); + } while(--rem); + if(offset == lfo_range) + offset = 0; + } mLfoOffset = static_cast(mLfoOffset+todo) % lfo_range; } @@ -216,7 +248,7 @@ void ChorusState::process(const size_t samplesToDo, const al::span(mDelay) + (MixerFracOne>>1)) >> MixerFracBits}; + const uint avgdelay{(static_cast(mDelay) + MixerFracHalf) >> MixerFracBits}; float *RESTRICT delaybuf{mSampleBuffer.data()}; uint offset{mOffset}; diff --git a/thirdparty/openal/alc/effects/compressor.cpp b/thirdparty/openal/alc/effects/compressor.cpp index 366f22753a..299394cffa 100644 --- a/thirdparty/openal/alc/effects/compressor.cpp +++ b/thirdparty/openal/alc/effects/compressor.cpp @@ -64,7 +64,10 @@ namespace { struct CompressorState final : public EffectState { /* Effect gains for each channel */ - float mGain[MaxAmbiChannels][MAX_OUTPUT_CHANNELS]{}; + struct { + uint mTarget{InvalidChannelIndex}; + float mGain{1.0f}; + } mChans[MaxAmbiChannels]; /* Effect parameters */ bool mEnabled{true}; @@ -103,9 +106,12 @@ void CompressorState::update(const ContextBase*, const EffectSlot *slot, mEnabled = props->Compressor.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, @@ -158,19 +164,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 = std::cbegin(mChans); 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 float *RESTRICT src{input.data() + 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/thirdparty/openal/alc/effects/convolution.cpp b/thirdparty/openal/alc/effects/convolution.cpp index 196238fcb1..e88fb0d00d 100644 --- a/thirdparty/openal/alc/effects/convolution.cpp +++ b/thirdparty/openal/alc/effects/convolution.cpp @@ -72,7 +72,7 @@ namespace { */ -void LoadSamples(double *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype, +void LoadSamples(float *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype, const size_t samples) noexcept { #define HANDLE_FMT(T) case T: al::LoadSampleArray(dst, src, srcstep, samples); break @@ -124,7 +124,7 @@ 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}; @@ -187,21 +187,21 @@ struct ConvolutionState final : public EffectState { al::vector,16> mFilter; al::vector,16> mOutput; - alignas(16) std::array mFftBuffer{}; + alignas(16) std::array mFftBuffer{}; 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]{}; }; using ChannelDataArray = al::FlexArray; std::unique_ptr mChans; - std::unique_ptr mComplexData; + std::unique_ptr mComplexData; ConvolutionState() = default; @@ -235,7 +235,7 @@ void ConvolutionState::UpsampleMix(const al::span samplesOut, for(auto &chan : *mChans) { const al::span src{chan.mBuffer.data(), samplesToDo}; - chan.mFilter.processHfScale(src, chan.mHfScale); + chan.mFilter.processScale(src, chan.mHfScale, chan.mLfScale); MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0); } } @@ -243,13 +243,16 @@ void ConvolutionState::UpsampleMix(const al::span samplesOut, void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buffer) { + using UhjDecoderType = UhjDecoder<512>; + static constexpr auto DecoderPadding = UhjDecoderType::sInputPadding; + constexpr uint MaxConvolveAmbiOrder{1u}; mFifoPos = 0; mInput.fill(0.0f); decltype(mFilter){}.swap(mFilter); decltype(mOutput){}.swap(mOutput); - mFftBuffer.fill(complex_d{}); + mFftBuffer.fill(complex_f{}); mCurrentSegment = 0; mNumConvolveSegs = 0; @@ -260,17 +263,21 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buff /* An empty buffer doesn't need a convolution filter. */ if(!buffer.storage || buffer.storage->mSampleLen < 1) return; + mChannels = buffer.storage->mChannels; + mAmbiLayout = IsUHJ(mChannels) ? AmbiLayout::FuMa : buffer.storage->mAmbiLayout; + mAmbiScaling = IsUHJ(mChannels) ? AmbiScaling::UHJ : buffer.storage->mAmbiScaling; + mAmbiOrder = minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder); + 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)); + const auto bytesPerSample = BytesFromFmt(buffer.storage->mType); + const auto realChannels = buffer.storage->channelsFromFmt(); + const auto numChannels = (mChannels == FmtUHJ2) ? 3u : ChannelsFromFmt(mChannels, mAmbiOrder); mChans = ChannelDataArray::Create(numChannels); /* The impulse response needs to have the same sample rate as the input and * output. The bsinc24 resampler is decent, but there is high-frequency - * attenation that some people may be able to pick up on. Since this is + * 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; @@ -296,43 +303,63 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buff mNumConvolveSegs = maxz(mNumConvolveSegs, 2) - 1; const size_t complex_length{mNumConvolveSegs * m * (numChannels+1)}; - mComplexData = std::make_unique(complex_length); - std::fill_n(mComplexData.get(), complex_length, complex_d{}); + mComplexData = std::make_unique(complex_length); + std::fill_n(mComplexData.get(), complex_length, complex_f{}); - 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.storage->mSampleLen+DecoderPadding, 16)}; + auto srcsamples = std::make_unique(srclinelength * numChannels); + std::fill_n(srcsamples.get(), srclinelength * numChannels, 0.0f); + for(size_t c{0};c < numChannels && c < realChannels;++c) + LoadSamples(srcsamples.get() + srclinelength*c, buffer.samples.data() + bytesPerSample*c, + realChannels, buffer.storage->mType, buffer.storage->mSampleLen); - 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] = srcsamples.get() + srclinelength*c; + decoder->decode({samples.data(), numChannels}, buffer.storage->mSampleLen, + buffer.storage->mSampleLen); + } + + auto ressamples = std::make_unique(buffer.storage->mSampleLen + + (resampler ? resampledCount : 0)); + complex_f *filteriter = mComplexData.get() + mNumConvolveSegs*m; for(size_t c{0};c < numChannels;++c) { - /* Load the samples from the buffer, and resample to match the device. */ - LoadSamples(srcsamples.get(), buffer.samples.data() + bytesPerSample*c, realChannels, - buffer.storage->mType, buffer.storage->mSampleLen); - if(device->Frequency != buffer.storage->mSampleRate) - resampler.process(buffer.storage->mSampleLen, srcsamples.get(), resampledCount, - srcsamples.get()); + /* Resample to match the device. */ + if(resampler) + { + std::copy_n(srcsamples.get() + srclinelength*c, buffer.storage->mSampleLen, + ressamples.get() + resampledCount); + resampler.process(buffer.storage->mSampleLen, ressamples.get()+resampledCount, + resampledCount, ressamples.get()); + } + else + std::copy_n(srcsamples.get() + srclinelength*c, buffer.storage->mSampleLen, + ressamples.get()); /* Store the first segment's samples in reverse in the time-domain, to * apply as a FIR filter. */ const size_t first_size{minz(resampledCount, ConvolveUpdateSamples)}; - std::transform(srcsamples.get(), srcsamples.get()+first_size, mFilter[c].rbegin(), + std::transform(ressamples.get(), ressamples.get()+first_size, mFilter[c].rbegin(), [](const double d) noexcept -> float { return static_cast(d); }); + auto fftbuffer = std::vector>(ConvolveUpdateSize); size_t done{first_size}; for(size_t s{0};s < mNumConvolveSegs;++s) { const size_t todo{minz(resampledCount-done, ConvolveUpdateSamples)}; - auto iter = std::copy_n(&srcsamples[done], todo, mFftBuffer.begin()); + auto iter = std::copy_n(&ressamples[done], todo, fftbuffer.begin()); done += todo; - std::fill(iter, mFftBuffer.end(), complex_d{}); + std::fill(iter, fftbuffer.end(), std::complex{}); - forward_fft(mFftBuffer); - filteriter = std::copy_n(mFftBuffer.cbegin(), m, filteriter); + forward_fft(al::as_span(fftbuffer)); + filteriter = std::copy_n(fftbuffer.cbegin(), m, filteriter); } } } @@ -388,7 +415,7 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) } }; - if(mNumConvolveSegs < 1) + if(mNumConvolveSegs < 1) [[unlikely]] return; mMix = &ConvolutionState::NormalMix; @@ -396,39 +423,36 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot for(auto &chan : *mChans) std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f); const float gain{slot->Gain}; - /* 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 = 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) ? + const uint8_t *index_map{Is2DAmbisonic(mChannels) ? GetAmbi2DLayout(mAmbiLayout).data() : GetAmbiLayout(mAmbiLayout).data()}; @@ -495,7 +519,7 @@ 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}; @@ -515,8 +539,7 @@ void ConvolutionState::process(const size_t samplesToDo, for(size_t c{0};c < chans.size();++c) { auto buf_iter = chans[c].mBuffer.begin() + base; - apply_fir({std::addressof(*buf_iter), todo}, mInput.data()+1 + mFifoPos, - mFilter[c].data()); + apply_fir({buf_iter, todo}, mInput.data()+1 + mFifoPos, mFilter[c].data()); auto fifo_iter = mOutput[c].begin() + mFifoPos; std::transform(fifo_iter, fifo_iter+todo, buf_iter, buf_iter, std::plus<>{}); @@ -536,20 +559,20 @@ void ConvolutionState::process(const size_t samplesToDo, * frequency bins to the FFT history. */ auto fftiter = std::copy_n(mInput.cbegin(), ConvolveUpdateSamples, mFftBuffer.begin()); - std::fill(fftiter, mFftBuffer.end(), complex_d{}); - forward_fft(mFftBuffer); + std::fill(fftiter, mFftBuffer.end(), complex_f{}); + forward_fft(al::as_span(mFftBuffer)); std::copy_n(mFftBuffer.cbegin(), m, &mComplexData[curseg*m]); - const complex_d *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m}; + const complex_f *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m}; for(size_t c{0};c < chans.size();++c) { - std::fill_n(mFftBuffer.begin(), m, complex_d{}); + std::fill_n(mFftBuffer.begin(), m, complex_f{}); /* Convolve each input segment with its IR filter counterpart * (aligned in time). */ - const complex_d *RESTRICT input{&mComplexData[curseg*m]}; + const complex_f *RESTRICT input{&mComplexData[curseg*m]}; for(size_t s{curseg};s < mNumConvolveSegs;++s) { for(size_t i{0};i < m;++i,++input,++filter) @@ -573,19 +596,17 @@ void ConvolutionState::process(const size_t samplesToDo, * second-half samples (and this output's second half is * subsequently saved for next time). */ - inverse_fft(mFftBuffer); + inverse_fft(al::as_span(mFftBuffer)); /* The iFFT'd response is scaled up by the number of bins, so apply * the inverse to normalize the output. */ for(size_t i{0};i < ConvolveUpdateSamples;++i) mOutput[c][i] = - static_cast(mFftBuffer[i].real() * (1.0/double{ConvolveUpdateSize})) + - mOutput[c][ConvolveUpdateSamples+i]; + (mFftBuffer[i].real()+mOutput[c][ConvolveUpdateSamples+i]) * + (1.0f/float{ConvolveUpdateSize}); for(size_t i{0};i < ConvolveUpdateSamples;++i) - mOutput[c][ConvolveUpdateSamples+i] = - static_cast(mFftBuffer[ConvolveUpdateSamples+i].real() * - (1.0/double{ConvolveUpdateSize})); + mOutput[c][ConvolveUpdateSamples+i] = mFftBuffer[ConvolveUpdateSamples+i].real(); } /* Shift the input history. */ diff --git a/thirdparty/openal/alc/effects/dedicated.cpp b/thirdparty/openal/alc/effects/dedicated.cpp index 671eb5ecbc..e666df9f6e 100644 --- a/thirdparty/openal/alc/effects/dedicated.cpp +++ b/thirdparty/openal/alc/effects/dedicated.cpp @@ -43,6 +43,10 @@ namespace { using uint = unsigned int; struct DedicatedState final : 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. + */ float mCurrentGains[MAX_OUTPUT_CHANNELS]; float mTargetGains[MAX_OUTPUT_CHANNELS]; @@ -70,9 +74,8 @@ void DedicatedState::update(const ContextBase*, const EffectSlot *slot, if(slot->EffectType == EffectSlotType::DedicatedLFE) { - const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX : - GetChannelIdxByName(*target.RealOut, LFE)}; - if(idx != INVALID_CHANNEL_INDEX) + const uint idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex}; + if(idx != InvalidChannelIndex) { mOutTarget = target.RealOut->Buffer; mTargetGains[idx] = Gain; @@ -82,16 +85,16 @@ void DedicatedState::update(const ContextBase*, const EffectSlot *slot, { /* 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) + const uint idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter] + : InvalidChannelIndex}; + if(idx != InvalidChannelIndex) { 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({0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; ComputePanGains(target.Main, coeffs.data(), Gain, mTargetGains); diff --git a/thirdparty/openal/alc/effects/distortion.cpp b/thirdparty/openal/alc/effects/distortion.cpp index 74cffd4a17..5e27761944 100644 --- a/thirdparty/openal/alc/effects/distortion.cpp +++ b/thirdparty/openal/alc/effects/distortion.cpp @@ -45,7 +45,7 @@ namespace { struct DistortionState final : public EffectState { /* Effect gains for each channel */ - float mGain[MAX_OUTPUT_CHANNELS]{}; + float mGain[MaxAmbiChannels]{}; /* Effect parameters */ BiquadFilter mLowpass; @@ -53,7 +53,7 @@ struct DistortionState final : public EffectState { float mAttenuation{}; float mEdgeCoeff{}; - float mBuffer[2][BufferLineSize]{}; + alignas(16) float mBuffer[2][BufferLineSize]{}; void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; @@ -95,7 +95,7 @@ void DistortionState::update(const ContextBase *context, const EffectSlot *slot, bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f); mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth); - const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f); + static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; ComputePanGains(target.Main, coeffs.data(), slot->Gain*props->Distortion.Gain, mGain); diff --git a/thirdparty/openal/alc/effects/echo.cpp b/thirdparty/openal/alc/effects/echo.cpp index 5d003718ca..7f9edf82a8 100644 --- a/thirdparty/openal/alc/effects/echo.cpp +++ b/thirdparty/openal/alc/effects/echo.cpp @@ -60,8 +60,8 @@ struct EchoState final : public EffectState { /* The panning gains for the two taps */ struct { - float Current[MAX_OUTPUT_CHANNELS]{}; - float Target[MAX_OUTPUT_CHANNELS]{}; + float Current[MaxAmbiChannels]{}; + float Target[MaxAmbiChannels]{}; } mGains[2]; BiquadFilter mFilter; diff --git a/thirdparty/openal/alc/effects/equalizer.cpp b/thirdparty/openal/alc/effects/equalizer.cpp index 67ad67b054..9d7cccef0a 100644 --- a/thirdparty/openal/alc/effects/equalizer.cpp +++ b/thirdparty/openal/alc/effects/equalizer.cpp @@ -87,15 +87,17 @@ namespace { struct EqualizerState final : public EffectState { struct { + uint mTargetChannel{InvalidChannelIndex}; + /* Effect parameters */ - BiquadFilter filter[4]; + BiquadFilter mFilter[4]; /* Effect gains for each channel */ - float CurrentGains[MAX_OUTPUT_CHANNELS]{}; - float TargetGains[MAX_OUTPUT_CHANNELS]{}; + float mCurrentGain{}; + float mTargetGain{}; } mChans[MaxAmbiChannels]; - FloatBufferLine mSampleBuffer{}; + alignas(16) FloatBufferLine mSampleBuffer{}; void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; @@ -111,8 +113,10 @@ void EqualizerState::deviceUpdate(const DeviceBase*, const Buffer&) { for(auto &e : mChans) { - std::for_each(std::begin(e.filter), std::end(e.filter), std::mem_fn(&BiquadFilter::clear)); - std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); + e.mTargetChannel = InvalidChannelIndex; + std::for_each(std::begin(e.mFilter), std::end(e.mFilter), + std::mem_fn(&BiquadFilter::clear)); + e.mCurrentGain = 0.0f; } } @@ -131,48 +135,56 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot, */ gain = std::sqrt(props->Equalizer.LowGain); f0norm = props->Equalizer.LowCutoff / frequency; - mChans[0].filter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f); + 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, + mChans[0].mFilter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, props->Equalizer.Mid1Width); gain = std::sqrt(props->Equalizer.Mid2Gain); f0norm = props->Equalizer.Mid2Center / frequency; - mChans[0].filter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, + mChans[0].mFilter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, props->Equalizer.Mid2Width); gain = std::sqrt(props->Equalizer.HighGain); f0norm = props->Equalizer.HighCutoff / frequency; - mChans[0].filter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f); + 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 = std::begin(mChans); 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.begin()); + DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer.begin()); - MixSamples(buffer, samplesOut, chan->CurrentGains, chan->TargetGains, samplesToDo, 0u); + MixSamples(buffer, samplesOut[outidx].data(), chan->mCurrentGain, chan->mTargetGain, + samplesToDo); + } ++chan; } } diff --git a/thirdparty/openal/alc/effects/fshifter.cpp b/thirdparty/openal/alc/effects/fshifter.cpp index def745c409..4bdb53a775 100644 --- a/thirdparty/openal/alc/effects/fshifter.cpp +++ b/thirdparty/openal/alc/effects/fshifter.cpp @@ -48,49 +48,52 @@ 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]{}; + float Current[MaxAmbiChannels]{}; + float Target[MaxAmbiChannels]{}; } mGains[2]; @@ -107,15 +110,15 @@ void FshifterState::deviceUpdate(const DeviceBase*, const Buffer&) { /* (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) { @@ -160,8 +163,8 @@ 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 lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}); + static constexpr auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}); mOutTarget = target.Main->Buffer; ComputePanGains(target.Main, lcoeffs.data(), slot->Gain, mGains[0].Target); @@ -172,7 +175,7 @@ void FshifterState::process(const size_t samplesToDo, const al::span(mBufferOut.data())}; + for(size_t c{0};c < 2;++c) { const uint phase_step{mPhaseStep[c]}; uint phase_idx{mPhase[c]}; diff --git a/thirdparty/openal/alc/effects/modulator.cpp b/thirdparty/openal/alc/effects/modulator.cpp index 84561f5c33..79993c0d88 100644 --- a/thirdparty/openal/alc/effects/modulator.cpp +++ b/thirdparty/openal/alc/effects/modulator.cpp @@ -84,10 +84,12 @@ struct ModulatorState final : public EffectState { uint mStep{1}; struct { - BiquadFilter Filter; + uint mTargetChannel{InvalidChannelIndex}; - float CurrentGains[MAX_OUTPUT_CHANNELS]{}; - float TargetGains[MAX_OUTPUT_CHANNELS]{}; + BiquadFilter mFilter; + + float mCurrentGain{}; + float mTargetGain{}; } mChans[MaxAmbiChannels]; @@ -104,8 +106,9 @@ void ModulatorState::deviceUpdate(const DeviceBase*, const Buffer&) { 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; } } @@ -129,14 +132,17 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot, float f0norm{props->Modulator.HighPassCutoff / static_cast(device->Frequency)}; f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f); /* Bandwidth value is constant in octaves. */ - mChans[0].Filter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f); + 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) @@ -153,14 +159,18 @@ void ModulatorState::process(const size_t samplesToDo, const al::spanmTargetChannel}; + if(outidx != InvalidChannelIndex) + { + 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]; + chandata->mFilter.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); + MixSamples({temps, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain, + chandata->mTargetGain, samplesToDo-base); + } ++chandata; } diff --git a/thirdparty/openal/alc/effects/pshifter.cpp b/thirdparty/openal/alc/effects/pshifter.cpp index aa20c660d3..731a5baf0b 100644 --- a/thirdparty/openal/alc/effects/pshifter.cpp +++ b/thirdparty/openal/alc/effects/pshifter.cpp @@ -47,34 +47,36 @@ 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; }; @@ -83,24 +85,24 @@ struct PshifterState final : public EffectState { size_t mCount; size_t mPos; uint mPitchShiftI; - double mPitchShift; + 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; + std::array mFftBuffer; - std::array mAnalysisBuffer; - std::array mSynthesisBuffer; + std::array mAnalysisBuffer; + std::array mSynthesisBuffer; alignas(16) FloatBufferLine mBufferOut; /* Effect gains for each output channel */ - float mCurrentGains[MAX_OUTPUT_CHANNELS]; - float mTargetGains[MAX_OUTPUT_CHANNELS]; + float mCurrentGains[MaxAmbiChannels]; + float mTargetGains[MaxAmbiChannels]; void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; @@ -116,17 +118,17 @@ void PshifterState::deviceUpdate(const DeviceBase*, const Buffer&) { /* (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(complex_f{}); + 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); @@ -137,16 +139,17 @@ void PshifterState::update(const ContextBase*, const EffectSlot *slot, { const int tune{props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune}; const float pitch{std::pow(2.0f, static_cast(tune) / 1200.0f)}; - mPitchShiftI = fastf2u(pitch*MixerFracOne); - mPitchShift = mPitchShiftI * double{1.0/MixerFracOne}; + mPitchShiftI = clampu(fastf2u(pitch*MixerFracOne), MixerFracHalf, MixerFracOne*2); + 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({0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; ComputePanGains(target.Main, coeffs.data(), 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,103 +158,133 @@ 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{minz(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]; + forward_fft(al::as_span(mFftBuffer)); /* 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 float magnitude{std::abs(mFftBuffer[k])}; + const float phase{std::arg(mFftBuffer[k])}; - /* 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)}; + + 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; + + mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k]); } - for(size_t k{STFT_HALF_SIZE+1};k < STFT_SIZE;++k) - mFftBuffer[k] = std::conj(mFftBuffer[STFT_SIZE-k]); + for(size_t k{StftHalfSize+1};k < StftSize;++k) + mFftBuffer[k] = std::conj(mFftBuffer[StftSize-k]); - /* Apply an inverse FFT to get the time-domain 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); + inverse_fft(al::as_span(mFftBuffer)); + + static constexpr float scale{3.0f / OversampleFactor / StftSize}; + for(size_t dst{mPos}, k{0u};dst < StftSize;++dst,++k) + mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale; + for(size_t dst{0u}, k{StftSize-mPos};dst < mPos;++dst,++k) + mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * 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. */ diff --git a/thirdparty/openal/alc/effects/reverb.cpp b/thirdparty/openal/alc/effects/reverb.cpp index e9f2e35f88..63a502d4e6 100644 --- a/thirdparty/openal/alc/effects/reverb.cpp +++ b/thirdparty/openal/alc/effects/reverb.cpp @@ -104,21 +104,21 @@ alignas(16) constexpr float B2A[NUM_LINES][NUM_LINES]{ }; /* 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 } -}; +alignas(16) constexpr std::array,NUM_LINES> EarlyA2B{{ + {{ 0.5f, 0.5f, 0.5f, 0.5f }}, + {{ 0.5f, -0.5f, 0.5f, -0.5f }}, + {{ 0.5f, -0.5f, -0.5f, 0.5f }}, + {{ 0.5f, 0.5f, -0.5f, -0.5f }} +}}; /* Converts A-Format to B-Format for late reverb. */ 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{{ + {{ 0.5f, 0.5f, 0.5f, 0.5f }}, + {{ InvSqrt2, -InvSqrt2, 0.0f, 0.0f }}, + {{ 0.0f, 0.0f, InvSqrt2, -InvSqrt2 }}, + {{ 0.5f, 0.5f, -0.5f, -0.5f }} +}}; /* The all-pass and delay lines have a variable length dependent on the * effect's density parameter, which helps alter the perceived environment @@ -302,12 +302,9 @@ struct DelayLineI { struct VecAllpass { DelayLineI Delay; float Coeff{0.0f}; - size_t Offset[NUM_LINES][2]{}; + size_t Offset[NUM_LINES]{}; - 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, + void process(const al::span samples, size_t offset, const float xCoeff, const float yCoeff, const size_t todo); }; @@ -315,7 +312,7 @@ 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, @@ -324,6 +321,8 @@ struct T60Filter { /* Applies the two T60 damping filter sections. */ void process(const al::span samples) { DualBiquad{HFFilter, LFFilter}.process(samples, samples.data()); } + + void clear() noexcept { HFFilter.clear(); LFFilter.clear(); } }; struct EarlyReflections { @@ -336,12 +335,12 @@ struct EarlyReflections { * reflections. */ DelayLineI Delay; - size_t Offset[NUM_LINES][2]{}; - float Coeff[NUM_LINES][2]{}; + size_t Offset[NUM_LINES]{}; + float Coeff[NUM_LINES]{}; /* The gain for each output channel based on 3D panning. */ - float CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; - float PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; + float CurrentGains[NUM_LINES][MaxAmbiChannels]{}; + float TargetGains[NUM_LINES][MaxAmbiChannels]{}; void updateLines(const float density_mult, const float diffusion, const float decayTime, const float frequency); @@ -355,25 +354,24 @@ struct Modulation { uint Index, Step; /* The depth of frequency change, in samples. */ - float Depth[2]; + float Depth; float ModDelays[MAX_UPDATE_SAMPLES]; 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]{}; + size_t Offset[NUM_LINES]{}; /* 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]; @@ -384,12 +382,76 @@ 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]{}; + float CurrentGains[NUM_LINES][MaxAmbiChannels]{}; + float TargetGains[NUM_LINES][MaxAmbiChannels]{}; 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 { + BiquadFilter Lp; + BiquadFilter Hp; + } mFilter[NUM_LINES]; + + /* Core delay line (early reflections and late reverb tap from this). */ + DelayLineI mEarlyDelayIn; + DelayLineI mLateDelayIn; + + /* Tap points for early reflection delay. */ + size_t mEarlyDelayTap[NUM_LINES][2]{}; + float mEarlyDelayCoeff[NUM_LINES]{}; + + /* Tap points for late reverb feed and delay. */ + 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; + + std::array,2> mAmbiSplitter; + + size_t mFadeSampleCount{1}; + + void updateDelayLine(const float earlyDelay, const float lateDelay, const float density_mult, + const float decayTime, const float frequency); + void update3DPanning(const float *ReflectionsPan, const float *LateReverbPan, + const float earlyGain, const float lateGain, const bool doUpmix, const MixParams *mainMix); + + void processEarly(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 { @@ -413,35 +475,17 @@ struct ReverbState final : public EffectState { float LFReference{250.0f}; } mParams; - /* Master effect filters */ - struct { - BiquadFilter Lp; - BiquadFilter Hp; - } mFilter[NUM_LINES]; + enum PipelineState : uint8_t { + DeviceClear, + StartFade, + Fading, + Cleanup, + Normal, + }; + PipelineState mPipelineState{DeviceClear}; + uint8_t mCurrentPipeline{0}; - /* Core delay line (early reflections and late reverb tap from this). */ - DelayLineI mDelay; - - /* Tap points for early reflection delay. */ - size_t mEarlyDelayTap[NUM_LINES][2]{}; - 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}; + ReverbPipeline mPipelines[2]; /* The current write offset for all delay lines. */ size_t mOffset{}; @@ -451,62 +495,64 @@ struct ReverbState final : public EffectState { alignas(16) FloatBufferLine mTempLine{}; alignas(16) std::array mTempSamples; }; - alignas(16) std::array mEarlySamples{}; - alignas(16) std::array mLateSamples{}; + alignas(16) std::array mEarlySamples{}; + alignas(16) std::array mLateSamples{}; + std::array mOrderScales{}; bool mUpmixOutput{false}; - std::array mOrderScales{}; - std::array,2> mAmbiSplitter; - 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) { - 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. + */ + for(size_t c{0u};c < NUM_LINES;c++) { - const float *RESTRICT input{al::assume_aligned<16>(InSamples)}; - InSamples += InStride; + const al::span tmpspan{mEarlySamples[c].data(), todo}; + MixSamples(tmpspan, samplesOut, pipeline.mEarly.CurrentGains[c], + pipeline.mEarly.TargetGains[c], todo, 0); + } + for(size_t c{0u};c < NUM_LINES;c++) + { + const al::span tmpspan{mLateSamples[c].data(), todo}; + MixSamples(tmpspan, samplesOut, pipeline.mLate.CurrentGains[c], + pipeline.mLate.TargetGains[c], todo, 0); + } + } - 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 float *InSamples, const size_t InStride) + { + 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>(InSamples)}; + InSamples += InStride; + + if(!(std::fabs(gain) > GainSilenceThreshold)) + continue; + + auto mix_sample = [gain](const float sample, const float in) noexcept -> float + { return sample + in*gain; }; + std::transform(OutBuffer.begin(), OutBuffer.end(), input, OutBuffer.begin(), + mix_sample); } - } - } - - - 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. */ - 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()); - 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); + }; + /* 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++) { @@ -516,47 +562,33 @@ struct ReverbState final : public EffectState { * higher-order output. */ const float hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; - mAmbiSplitter[0][c].processHfScale(tmpspan, hfscale); + pipeline.mAmbiSplitter[0][c].processHfScale(tmpspan, hfscale); - MixSamples(tmpspan, samplesOut, mEarly.CurrentGain[c], mEarly.PanGain[c], counter, - offset); + MixSamples(tmpspan, samplesOut, pipeline.mEarly.CurrentGains[c], + pipeline.mEarly.TargetGains[c], todo, 0); } for(size_t c{0u};c < NUM_LINES;c++) { DoMixRow(tmpspan, LateA2B[c], mLateSamples[0].data(), mLateSamples[0].size()); const float hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; - mAmbiSplitter[1][c].processHfScale(tmpspan, hfscale); + pipeline.mAmbiSplitter[1][c].processHfScale(tmpspan, hfscale); - MixSamples(tmpspan, samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], counter, - offset); + MixSamples(tmpspan, samplesOut, pipeline.mLate.CurrentGains[c], + pipeline.mLate.TargetGains[c], todo, 0); } } - void mixOut(const al::span samplesOut, const size_t counter, - const size_t offset, const size_t todo) + void mixOut(ReverbPipeline &pipeline, const al::span samplesOut, const size_t todo) { if(mUpmixOutput) - MixOutAmbiUp(samplesOut, counter, offset, todo); + MixOutAmbiUp(pipeline, samplesOut, todo); else - MixOutPlain(samplesOut, counter, offset, todo); + 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 update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; @@ -588,41 +620,51 @@ void ReverbState::allocLines(const float frequency) */ 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}; - /* 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. - */ - length = LATE_LINE_LENGTHS.back()*multiplier + max_mod_delay; - totalSamples += mLate.Delay.calcLineLength(length, totalSamples, frequency, 1); + for(auto &pipeline : mPipelines) + { + /* The main delay length includes the maximum early reflection delay, + * the largest early tap width, the maximum late reverb delay, and the + * largest late tap width. Finally, it must also be extended by the + * update size (BufferLineSize) for block processing. + */ + float length{ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier}; + totalSamples += pipeline.mEarlyDelayIn.calcLineLength(length, totalSamples, frequency, + BufferLineSize); + + constexpr float LateLineDiffAvg{(LATE_LINE_LENGTHS.back()-LATE_LINE_LENGTHS.front()) / + float{NUM_LINES}}; + length = ReverbMaxLateReverbDelay + LateLineDiffAvg*multiplier; + totalSamples += pipeline.mLateDelayIn.calcLineLength(length, totalSamples, frequency, + BufferLineSize); + + /* The early vector all-pass line. */ + length = EARLY_ALLPASS_LENGTHS.back() * multiplier; + totalSamples += pipeline.mEarly.VecAp.Delay.calcLineLength(length, totalSamples, frequency, + 0); + + /* The early reflection line. */ + length = EARLY_LINE_LENGTHS.back() * multiplier; + totalSamples += pipeline.mEarly.Delay.calcLineLength(length, totalSamples, frequency, + MAX_UPDATE_SAMPLES); + + /* The late vector all-pass line. */ + length = LATE_ALLPASS_LENGTHS.back() * multiplier; + totalSamples += pipeline.mLate.VecAp.Delay.calcLineLength(length, totalSamples, frequency, + 0); + + /* 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. + */ + length = LATE_LINE_LENGTHS.back()*multiplier + max_mod_delay; + totalSamples += pipeline.mLate.Delay.calcLineLength(length, totalSamples, frequency, 1); + } if(totalSamples != mSampleBuffer.size()) decltype(mSampleBuffer)(totalSamples).swap(mSampleBuffer); @@ -631,11 +673,15 @@ 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()); + for(auto &pipeline : mPipelines) + { + pipeline.mEarlyDelayIn.realizeLineOffset(mSampleBuffer.data()); + pipeline.mLateDelayIn.realizeLineOffset(mSampleBuffer.data()); + pipeline.mEarly.VecAp.Delay.realizeLineOffset(mSampleBuffer.data()); + pipeline.mEarly.Delay.realizeLineOffset(mSampleBuffer.data()); + pipeline.mLate.VecAp.Delay.realizeLineOffset(mSampleBuffer.data()); + pipeline.mLate.Delay.realizeLineOffset(mSampleBuffer.data()); + } } void ReverbState::deviceUpdate(const DeviceBase *device, const Buffer&) @@ -645,67 +691,64 @@ void ReverbState::deviceUpdate(const DeviceBase *device, const Buffer&) /* 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(); + } + + std::fill(std::begin(pipeline.mEarlyDelayCoeff),std::end(pipeline.mEarlyDelayCoeff), 0.0f); + std::fill(std::begin(pipeline.mEarlyDelayCoeff),std::end(pipeline.mEarlyDelayCoeff), 0.0f); + + pipeline.mLate.DensityGain = 0.0f; + for(auto &t60 : pipeline.mLate.T60) + { + t60.MidGain = 0.0f; + t60.HFFilter.clear(); + t60.LFFilter.clear(); + } + + pipeline.mLate.Mod.Index = 0; + pipeline.mLate.Mod.Step = 1; + pipeline.mLate.Mod.Depth = 0.0f; + + for(auto &gains : pipeline.mEarly.CurrentGains) + std::fill(std::begin(gains), std::end(gains), 0.0f); + for(auto &gains : pipeline.mEarly.TargetGains) + std::fill(std::begin(gains), std::end(gains), 0.0f); + for(auto &gains : pipeline.mLate.CurrentGains) + std::fill(std::begin(gains), std::end(gains), 0.0f); + for(auto &gains : pipeline.mLate.TargetGains) + std::fill(std::begin(gains), std::end(gains), 0.0f); } + 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) { mUpmixOutput = true; - mOrderScales = AmbiScale::GetHFOrderScales(1, device->mAmbiOrder); + mOrderScales = AmbiScale::GetHFOrderScales(1, device->mAmbiOrder, device->m2DMixing); } else { 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]); + } } /************************************** @@ -792,7 +835,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); } @@ -808,14 +851,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); } } @@ -849,10 +892,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. */ @@ -889,7 +932,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; @@ -898,11 +941,11 @@ 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. */ length = LATE_LINE_LENGTHS[i] * density_mult; - Offset[i][1] = float2uint(length*frequency + 0.5f); + Offset[i] = float2uint(length*frequency + 0.5f); /* Approximate the absorption that the vector all-pass would exhibit * given the current diffusion so we don't have to process a full T60 @@ -910,7 +953,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); @@ -919,7 +962,7 @@ 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, +void ReverbPipeline::updateDelayLine(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 @@ -936,11 +979,11 @@ 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] = CalcDecayCoeff(length, decayTime); 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); } } @@ -949,7 +992,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 float *vec) { /* Normalize the panning vector according to the N3D scale, which has an * extra sqrt(3) term on the directional components. Converting from OpenAL @@ -978,36 +1021,88 @@ 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 float *ReflectionsPan, const float *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 std::array,4> earlymat{GetTransformFromVector(ReflectionsPan)}; + const std::array,4> latemat{GetTransformFromVector(LateReverbPan)}; - mOutTarget = target.Main->Buffer; - for(size_t i{0u};i < NUM_LINES;i++) + if(doUpmix) { - const float coeffs[MaxAmbiChannels]{earlymat[0][i], earlymat[1][i], earlymat[2][i], - earlymat[3][i]}; - ComputePanGains(target.Main, coeffs, earlyGain, mEarly.PanGain[i]); + /* When upsampling, combine the early and late transforms with the + * first-order upsample matrix. This results in panning gains that + * apply the panning transform to first-order B-Format, which is then + * upsampled. + */ + auto mult_matrix = [](const al::span,4> mtx1) + { + auto&& mtx2 = AmbiScale::FirstOrderUp; + std::array,NUM_LINES> res{}; + + for(size_t i{0};i < mtx1[0].size();++i) + { + for(size_t j{0};j < mtx2[0].size();++j) + { + double sum{0.0}; + for(size_t k{0};k < mtx1.size();++k) + sum += double{mtx1[k][i]} * mtx2[k][j]; + res[i][j] = static_cast(sum); + } + } + + return res; + }; + auto earlycoeffs = mult_matrix(earlymat); + auto latecoeffs = mult_matrix(latemat); + + for(size_t i{0u};i < NUM_LINES;i++) + ComputePanGains(mainMix, earlycoeffs[i].data(), earlyGain, mEarly.TargetGains[i]); + for(size_t i{0u};i < NUM_LINES;i++) + ComputePanGains(mainMix, latecoeffs[i].data(), lateGain, mLate.TargetGains[i]); } - for(size_t i{0u};i < NUM_LINES;i++) + else { - const float coeffs[MaxAmbiChannels]{latemat[0][i], latemat[1][i], latemat[2][i], - latemat[3][i]}; - ComputePanGains(target.Main, coeffs, lateGain, mLate.PanGain[i]); + /* 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) + { + for(size_t j{0};j < mtx2.size();++j) + { + double sum{0.0}; + for(size_t k{0};k < mtx1.size();++k) + sum += double{mtx1[k][i]} * mtx2[j][k]; + res[i][j] = static_cast(sum); + } + } + + return res; + }; + auto earlycoeffs = mult_matrix(EarlyA2B, earlymat); + auto latecoeffs = mult_matrix(LateA2B, latemat); + + for(size_t i{0u};i < NUM_LINES;i++) + ComputePanGains(mainMix, earlycoeffs[i].data(), earlyGain, mEarly.TargetGains[i]); + for(size_t i{0u};i < NUM_LINES;i++) + ComputePanGains(mainMix, latecoeffs[i].data(), lateGain, mLate.TargetGains[i]); } } @@ -1017,30 +1112,6 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, 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. */ @@ -1055,27 +1126,12 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, MinDecayTime, MaxDecayTime)}; const float hfDecayTime{clampf(props->Reverb.DecayTime*hfRatio, MinDecayTime, MaxDecayTime)}; - /* Update the modulator rate and depth. */ - mLate.Mod.updateModulator(props->Reverb.ModulationTime, props->Reverb.ModulationDepth, - 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->Reverb.Density || /* Diffusion and decay times influences the decay rate (gain) of the * late reverb T60 filter. */ @@ -1090,8 +1146,8 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, * gain. */ mParams.HFReference != props->Reverb.HFReference || - mParams.LFReference != props->Reverb.LFReference); - if(mDoFading) + mParams.LFReference != props->Reverb.LFReference}; + if(fullUpdate) { mParams.Density = props->Reverb.Density; mParams.Diffusion = props->Reverb.Diffusion; @@ -1102,7 +1158,58 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, mParams.ModulationDepth = props->Reverb.ModulationDepth; mParams.HFReference = props->Reverb.HFReference; mParams.LFReference = props->Reverb.LFReference; + + mPipelineState = (mPipelineState != DeviceClear) ? StartFade : Normal; + mCurrentPipeline ^= 1; } + auto &pipeline = mPipelines[mCurrentPipeline]; + + /* Update early and late 3D panning. */ + mOutTarget = target.Main->Buffer; + const float gain{props->Reverb.Gain * Slot->Gain * ReverbBoost}; + pipeline.update3DPanning(props->Reverb.ReflectionsPan, props->Reverb.LateReverbPan, + props->Reverb.ReflectionsGain*gain, props->Reverb.LateReverbGain*gain, mUpmixOutput, + target.Main); + + /* Calculate the master filters */ + float hf0norm{minf(props->Reverb.HFReference/frequency, 0.49f)}; + pipeline.mFilter[0].Lp.setParamsFromSlope(BiquadType::HighShelf, hf0norm, props->Reverb.GainHF, 1.0f); + float lf0norm{minf(props->Reverb.LFReference/frequency, 0.49f)}; + pipeline.mFilter[0].Hp.setParamsFromSlope(BiquadType::LowShelf, lf0norm, props->Reverb.GainLF, 1.0f); + for(size_t i{1u};i < NUM_LINES;i++) + { + pipeline.mFilter[i].Lp.copyParamsFrom(pipeline.mFilter[0].Lp); + pipeline.mFilter[i].Hp.copyParamsFrom(pipeline.mFilter[0].Hp); + } + + /* The density-based room size (delay length) multiplier. */ + const float density_mult{CalcDelayLengthMult(props->Reverb.Density)}; + + /* Update the main effect delay and associated taps. */ + pipeline.updateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay, + density_mult, props->Reverb.DecayTime, frequency); + + if(fullUpdate) + { + /* Update the early lines. */ + pipeline.mEarly.updateLines(density_mult, props->Reverb.Diffusion, props->Reverb.DecayTime, + frequency); + + /* Get the mixing matrix coefficients. */ + CalcMatrixCoeffs(props->Reverb.Diffusion, &pipeline.mMixX, &pipeline.mMixY); + + /* Update the modulator rate and depth. */ + pipeline.mLate.Mod.updateModulator(props->Reverb.ModulationTime, + props->Reverb.ModulationDepth, frequency); + + /* Update the late lines. */ + pipeline.mLate.updateLines(density_mult, props->Reverb.Diffusion, lfDecayTime, + props->Reverb.DecayTime, hfDecayTime, lf0norm, hf0norm, frequency); + } + + const float decaySamples{(props->Reverb.ReflectionsDelay + props->Reverb.LateReverbDelay + + props->Reverb.DecayTime) * frequency}; + pipeline.mFadeSampleCount = static_cast(minf(decaySamples, 1'000'000.0f)); } @@ -1190,7 +1297,7 @@ void VectorScatterRevDelayIn(const DelayLineI delay, size_t offset, const float * 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, +void VecAllpass::process(const al::span samples, size_t offset, const float xCoeff, const float yCoeff, const size_t todo) { const DelayLineI delay{Delay}; @@ -1200,7 +1307,7 @@ void VecAllpass::processUnfaded(const al::span sampl size_t vap_offset[NUM_LINES]; 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++) @@ -1228,58 +1335,6 @@ void VecAllpass::processUnfaded(const al::span sampl } 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) -{ - const DelayLineI 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++) - { - vap_offset[j][0] &= delay.Mask; - vap_offset[j][1] &= delay.Mask; - } - offset &= delay.Mask; - - size_t maxoff{offset}; - for(size_t j{0u};j < NUM_LINES;j++) - maxoff = maxz(maxoff, maxz(vap_offset[j][0], vap_offset[j][1])); - size_t td{minz(delay.Mask+1 - maxoff, todo - i)}; - - do { - fadeCount += 1.0f; - const float fade{fadeCount * fadeStep}; - - std::array 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. * @@ -1296,148 +1351,97 @@ void VecAllpass::processFaded(const al::span samples * * Finally, the early response is reversed, scattered (based on diffusion), * and fed into the late reverb section of the main delay line. - * - * Two static specializations are used for transitional (cross-faded) delay - * line processing and non-transitional processing. */ -void ReverbState::earlyUnfaded(const size_t offset, const size_t todo) +void ReverbPipeline::processEarly(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 DelayLineI in_delay{mEarlyDelayIn}; 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{minz(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); + size_t early_delay_tap0{offset - mEarlyDelayTap[j][0]}; + size_t early_delay_tap1{offset - mEarlyDelayTap[j][1]}; + const float coeff{mEarlyDelayCoeff[j]}; + const float coeffStep{early_delay_tap0 != early_delay_tap1 ? coeff*fadeStep : 0.0f}; + float fadeCount{0.0f}; + + for(size_t i{0u};i < todo;) + { + early_delay_tap0 &= in_delay.Mask; + early_delay_tap1 &= in_delay.Mask; + const size_t max_tap{maxz(early_delay_tap0, early_delay_tap1)}; + size_t td{minz(in_delay.Mask+1 - max_tap, todo-i)}; + do { + const float fade0{coeff - coeffStep*fadeCount}; + const float fade1{coeffStep*fadeCount}; + fadeCount += 1.0f; + tempSamples[j][i++] = in_delay.Line[early_delay_tap0++][j]*fade0 + + in_delay.Line[early_delay_tap1++][j]*fade1; + } while(--td); + } + + mEarlyDelayTap[j][0] = mEarlyDelayTap[j][1]; } - } - /* Apply a vector all-pass, to help color the initial reflections based on - * the diffusion strength. - */ - mEarly.VecAp.processUnfaded(mTempSamples, offset, mixX, mixY, todo); + /* Apply a vector all-pass, to help color the initial reflections based + * on the diffusion strength. + */ + mEarly.VecAp.process(tempSamples, offset, mixX, mixY, todo); - /* Apply a delay and bounce to generate secondary reflections, combine with - * the primary reflections and write out the result for mixing. - */ - for(size_t j{0u};j < NUM_LINES;j++) - { - size_t feedb_tap{offset - mEarly.Offset[j][0]}; - const 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. + */ + for(size_t j{0u};j < NUM_LINES;j++) + early_delay.write(offset, NUM_LINES-1-j, tempSamples[j].data(), todo); + for(size_t j{0u};j < NUM_LINES;j++) { - 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); + 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{minz(early_delay.Mask+1 - feedb_tap, todo - i)}; + do { + tempSamples[j][i] += early_delay.Line[feedb_tap++][j]*feedb_coeff; + out[i] = tempSamples[j][i]; + ++i; + } while(--td); + } + } + + /* 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. + */ + VectorScatterRevDelayIn(mLateDelayIn, offset, mixX, mixY, tempSamples, 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 mod_scale{al::numbers::pi_v * 2.0f / MOD_FRACONE}; uint idx{Index}; const uint step{Step}; - const float depth{Depth[0]}; + const float depth{Depth}; for(size_t i{0};i < todo;++i) { idx += step; @@ -1447,23 +1451,6 @@ void Modulation::calcDelays(size_t todo) Index = idx; } -void Modulation::calcFadedDelays(size_t todo, float fadeCount, float fadeStep) -{ - constexpr float mod_scale{al::numbers::pi_v * 2.0f / MOD_FRACONE}; - 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) * mod_scale)}; - ModDelays[i] = (lfo+1.0f) * (depth + depthStep*fadeCount); - } - Index = idx; -} - /* This generates the reverb tail using a modified feed-back delay network * (FDN). @@ -1475,233 +1462,242 @@ 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 DelayLineI 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{minz(samplesToDo-base, minz(mLate.Offset[0], MAX_UPDATE_SAMPLES))}; + ASSUME(todo > 0); - for(size_t i{0u};i < todo;) + /* 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. + */ + const float fadeStep{1.0f / static_cast(todo)}; + 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)}; + size_t late_delay_tap0{offset - mLateDelayTap[j][0]}; + size_t late_delay_tap1{offset - mLateDelayTap[j][1]}; + size_t late_feedb_tap{offset - mLate.Offset[j]}; + const float midGain{mLate.T60[j].MidGain}; + const float densityGain{mLate.DensityGain * midGain}; + const float densityStep{late_delay_tap0 != late_delay_tap1 ? + densityGain*fadeStep : 0.0f}; + float fadeCount{0.0f}; - /* Feed the delay line with the late feedback sample, and get - * the two samples crossed by the delayed offset. - */ - 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]}; - ++late_feedb_tap; + for(size_t i{0u};i < todo;) + { + late_delay_tap0 &= in_delay.Mask; + late_delay_tap1 &= in_delay.Mask; + size_t td{minz(todo-i, in_delay.Mask+1 - maxz(late_delay_tap0, late_delay_tap1))}; + do { + /* Calculate the read offset and 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)}; - /* 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 two samples crossed by the delayed offset. */ + 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]}; + ++late_feedb_tap; + + /* The output is obtained by linearly interpolating the two + * samples that were acquired above, and combined with the + * main delay tap. + */ + const float fade0{densityGain - densityStep*fadeCount}; + const float fade1{densityStep*fadeCount}; + fadeCount += 1.0f; + tempSamples[j][i] = lerpf(out0, out1, frac)*midGain + + in_delay.Line[late_delay_tap0++][j]*fade0 + + in_delay.Line[late_delay_tap1++][j]*fade1; + ++i; + } while(--td); + } + mLateDelayTap[j][0] = mLateDelayTap[j][1]; + + 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.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. */ + VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, tempSamples, todo); + + base += todo; + offset += 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;) - { - 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 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); - } - mLate.T60[j].process({mTempSamples[j].data(), todo}); - } - - mLate.VecAp.processFaded(mTempSamples, offset, mixX, mixY, fade, fadeStep, todo); - for(size_t j{0u};j < NUM_LINES;j++) - std::copy_n(mTempSamples[j].begin(), todo, mLateSamples[j].begin()); - - VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, mTempSamples, todo); } void ReverbState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - size_t offset{mOffset}; + const size_t offset{mOffset}; ASSUME(samplesToDo > 0); - /* Convert B-Format to A-Format for processing. */ - const size_t numInput{minz(samplesIn.size(), NUM_LINES)}; - const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo}; - for(size_t c{0u};c < NUM_LINES;c++) - { - std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - for(size_t i{0};i < numInput;++i) - { - const float gain{B2A[c][i]}; - const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())}; + auto &oldpipeline = mPipelines[mCurrentPipeline^1]; + auto &pipeline = mPipelines[mCurrentPipeline]; - for(float &sample : tmpspan) + if(mPipelineState >= Fading) + { + /* Convert B-Format to A-Format for processing. */ + const size_t numInput{minz(samplesIn.size(), NUM_LINES)}; + const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo}; + for(size_t c{0u};c < NUM_LINES;c++) + { + std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); + for(size_t i{0};i < numInput;++i) { - sample += *input * gain; - ++input; + const float gain{B2A[c][i]}; + const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())}; + + auto mix_sample = [gain](const float sample, const float in) noexcept -> float + { return sample + in*gain; }; + std::transform(tmpspan.begin(), tmpspan.end(), input, tmpspan.begin(), + mix_sample); } + + /* Band-pass the incoming samples and feed the initial delay line. */ + auto&& filter = DualBiquad{pipeline.mFilter[c].Lp, pipeline.mFilter[c].Hp}; + filter.process(tmpspan, tmpspan.data()); + pipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); } - - /* 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); - } - - /* Process reverb for these samples. */ - if LIKELY(!mDoFading) - { - for(size_t base{0};base < samplesToDo;) + if(mPipelineState == Fading) { - /* Calculate the number of samples we can do this iteration. */ - size_t todo{minz(samplesToDo - base, mMaxUpdate[0])}; - /* Some mixers require maintaining a 4-sample alignment, so ensure - * that if it's not the last iteration. - */ - if(base+todo < samplesToDo) todo &= ~size_t{3}; - ASSUME(todo > 0); + /* Give the old pipeline silence if it's still fading out. */ + for(size_t c{0u};c < NUM_LINES;c++) + { + std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - /* Generate non-faded early reflections and late reverb. */ - earlyUnfaded(offset, todo); - lateUnfaded(offset, todo); - - /* Finally, mix early reflections and late reverb. */ - mixOut(samplesOut, samplesToDo-base, base, todo); - - offset += todo; - base += todo; + auto&& filter = DualBiquad{oldpipeline.mFilter[c].Lp, oldpipeline.mFilter[c].Hp}; + filter.process(tmpspan, tmpspan.data()); + oldpipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); + } } } else { + /* At the start of a fade, fade in input for the current pipeline, and + * fade out input for the old pipeline. + */ + const size_t numInput{minz(samplesIn.size(), NUM_LINES)}; + const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo}; const float fadeStep{1.0f / static_cast(samplesToDo)}; - for(size_t 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); - - mixOut(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]; + std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); + for(size_t i{0};i < numInput;++i) + { + const float gain{B2A[c][i]}; + const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())}; + + auto mix_sample = [gain](const float sample, const float in) noexcept -> float + { return sample + in*gain; }; + std::transform(tmpspan.begin(), tmpspan.end(), input, tmpspan.begin(), + mix_sample); + } + float stepCount{0.0f}; + for(float &sample : tmpspan) + { + stepCount += 1.0f; + sample *= stepCount*fadeStep; + } + + auto&& filter = DualBiquad{pipeline.mFilter[c].Lp, pipeline.mFilter[c].Hp}; + filter.process(tmpspan, tmpspan.data()); + pipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); } - mLate.DensityGain[0] = mLate.DensityGain[1]; - mLate.Mod.Depth[0] = mLate.Mod.Depth[1]; - mMaxUpdate[0] = mMaxUpdate[1]; - mDoFading = false; + for(size_t c{0u};c < NUM_LINES;c++) + { + std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); + for(size_t i{0};i < numInput;++i) + { + const float gain{B2A[c][i]}; + const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())}; + + auto mix_sample = [gain](const float sample, const float in) noexcept -> float + { return sample + in*gain; }; + std::transform(tmpspan.begin(), tmpspan.end(), input, tmpspan.begin(), + mix_sample); + } + float stepCount{0.0f}; + for(float &sample : tmpspan) + { + stepCount += 1.0f; + sample *= 1.0f - stepCount*fadeStep; + } + + auto&& filter = DualBiquad{oldpipeline.mFilter[c].Lp, oldpipeline.mFilter[c].Hp}; + filter.process(tmpspan, tmpspan.data()); + oldpipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); + } + mPipelineState = Fading; } - mOffset = offset; + + /* Process reverb for these samples. and mix them to the output. */ + pipeline.processEarly(offset, samplesToDo, mTempSamples, mEarlySamples); + pipeline.processLate(offset, samplesToDo, mTempSamples, mLateSamples); + mixOut(pipeline, samplesOut, samplesToDo); + + if(mPipelineState != Normal) + { + if(mPipelineState == Cleanup) + { + size_t numSamples{mSampleBuffer.size()/2}; + size_t pipelineOffset{numSamples * (mCurrentPipeline^1)}; + 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(samplesToDo >= oldpipeline.mFadeSampleCount) + { + for(auto &gains : oldpipeline.mEarly.TargetGains) + std::fill(std::begin(gains), std::end(gains), 0.0f); + for(auto &gains : oldpipeline.mLate.TargetGains) + std::fill(std::begin(gains), std::end(gains), 0.0f); + oldpipeline.mFadeSampleCount = 0; + mPipelineState = Cleanup; + } + else + oldpipeline.mFadeSampleCount -= samplesToDo; + + /* Process the old reverb for these samples. */ + oldpipeline.processEarly(offset, samplesToDo, mTempSamples, mEarlySamples); + oldpipeline.processLate(offset, samplesToDo, mTempSamples, mLateSamples); + mixOut(oldpipeline, samplesOut, samplesToDo); + } + } + + mOffset = offset + samplesToDo; } diff --git a/thirdparty/openal/alc/effects/vmorpher.cpp b/thirdparty/openal/alc/effects/vmorpher.cpp index edc50eb18e..97267ad9bd 100644 --- a/thirdparty/openal/alc/effects/vmorpher.cpp +++ b/thirdparty/openal/alc/effects/vmorpher.cpp @@ -143,12 +143,14 @@ struct FormantFilter struct VmorpherState final : public EffectState { struct { + uint mTargetChannel{InvalidChannelIndex}; + /* Effect parameters */ - FormantFilter Formants[NUM_FILTERS][NUM_FORMANTS]; + FormantFilter mFormants[NUM_FILTERS][NUM_FORMANTS]; /* Effect gains for each channel */ - float CurrentGains[MAX_OUTPUT_CHANNELS]{}; - float TargetGains[MAX_OUTPUT_CHANNELS]{}; + float mCurrentGain{}; + float mTargetGain{}; } mChans[MaxAmbiChannels]; void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){}; @@ -229,11 +231,12 @@ void VmorpherState::deviceUpdate(const DeviceBase*, const Buffer&) { 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(std::begin(e.mFormants[VOWEL_A_INDEX]), std::end(e.mFormants[VOWEL_A_INDEX]), std::mem_fn(&FormantFilter::clear)); - std::for_each(std::begin(e.Formants[VOWEL_B_INDEX]), std::end(e.Formants[VOWEL_B_INDEX]), + std::for_each(std::begin(e.mFormants[VOWEL_B_INDEX]), std::end(e.mFormants[VOWEL_B_INDEX]), std::mem_fn(&FormantFilter::clear)); - std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); + e.mCurrentGain = 0.0f; } } @@ -265,14 +268,17 @@ void VmorpherState::update(const ContextBase *context, const EffectSlot *slot, /* 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(), std::begin(mChans[i].mFormants[VOWEL_A_INDEX])); + std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].mFormants[VOWEL_B_INDEX])); } 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) @@ -291,8 +297,15 @@ void VmorpherState::process(const size_t samplesToDo, const al::spanFormants[VOWEL_A_INDEX]; - auto& vowelB = chandata->Formants[VOWEL_B_INDEX]; + const size_t outidx{chandata->mTargetChannel}; + if(outidx == InvalidChannelIndex) + { + ++chandata; + continue; + } + + auto& vowelA = chandata->mFormants[VOWEL_A_INDEX]; + auto& vowelB = chandata->mFormants[VOWEL_B_INDEX]; /* Process first vowel. */ std::fill_n(std::begin(mSampleBufferA), td, 0.0f); @@ -313,8 +326,8 @@ void VmorpherState::process(const size_t samplesToDo, const al::spanCurrentGains, chandata->TargetGains, - samplesToDo-base, base); + MixSamples({blended, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain, + chandata->mTargetGain, samplesToDo-base); ++chandata; } diff --git a/thirdparty/openal/alc/inprogext.h b/thirdparty/openal/alc/inprogext.h index 9af80f12ec..5dfc22cb7c 100644 --- a/thirdparty/openal/alc/inprogext.h +++ b/thirdparty/openal/alc/inprogext.h @@ -54,6 +54,15 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint * #define AL_STOP_SOURCES_ON_DISCONNECT_SOFT 0x19AB #endif +#ifndef AL_SOFT_source_start_delay +#define AL_SOFT_source_start_delay +typedef void (AL_APIENTRY*LPALSOURCEPLAYATTIMESOFT)(ALuint source, ALint64SOFT start_time); +typedef void (AL_APIENTRY*LPALSOURCEPLAYATTIMEVSOFT)(ALsizei n, const ALuint *sources, ALint64SOFT start_time); +#ifdef AL_ALEXT_PROTOTYPES +void AL_APIENTRY alSourcePlayAtTimeSOFT(ALuint source, ALint64SOFT start_time); +void AL_APIENTRY alSourcePlayAtTimevSOFT(ALsizei n, const ALuint *sources, ALint64SOFT start_time); +#endif +#endif /* Non-standard export. Not part of any extension. */ AL_API const ALchar* AL_APIENTRY alsoft_get_version(void); diff --git a/thirdparty/openal/alc/panning.cpp b/thirdparty/openal/alc/panning.cpp index d0afd5779e..d118f99cf9 100644 --- a/thirdparty/openal/alc/panning.cpp +++ b/thirdparty/openal/alc/panning.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -115,13 +116,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; } @@ -220,7 +221,7 @@ struct DecoderConfig { return *this; } - explicit operator bool() const noexcept { return mOrder != 0; } + explicit operator bool() const noexcept { return !mChannels.empty(); } }; using DecoderView = DecoderConfig; @@ -231,7 +232,7 @@ void InitNearFieldCtrl(ALCdevice *device, float ctrl_dist, uint order, bool is3d static const uint chans_per_order3d[MaxAmbiOrder+1]{ 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); @@ -251,7 +252,7 @@ void InitDistanceComp(ALCdevice *device, const al::span channels, { const float maxdist{std::accumulate(std::begin(dists), std::end(dists), 0.0f, maxf)}; - 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; @@ -262,7 +263,7 @@ void InitDistanceComp(ALCdevice *device, const al::span channels, { const Channel ch{channels[chidx]}; const uint idx{device->RealOut.ChannelIndex[ch]}; - if(idx == INVALID_CHANNEL_INDEX) + if(idx == InvalidChannelIndex) continue; const float distance{dists[chidx]}; @@ -274,11 +275,11 @@ 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}; + GetLabelFromChannel(ch), delay, DistanceComp::MaxDelay-1); + delay = float{DistanceComp::MaxDelay-1}; } ChanDelay.resize(maxz(ChanDelay.size(), idx+1)); @@ -331,12 +332,14 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, { 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; @@ -349,44 +352,10 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, std::min(al::size(conf->LFOrderGain), al::size(decoder.mOrderGainLF)), std::begin(decoder.mOrderGainLF)); - 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 ? AmbiIndex::FromACN().data() + : AmbiIndex::FromACN2D().data(); const auto hfmatrix = conf->HFMatrix; const auto lfmatrix = conf->LFMatrix; @@ -407,6 +376,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 @@ -429,6 +402,14 @@ 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 { int idx{}; @@ -443,16 +424,16 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, } 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]; } } @@ -492,21 +473,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{ @@ -542,25 +523,25 @@ 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{ @@ -568,21 +549,38 @@ constexpr DecoderConfig X3D71Config{ DevAmbiScaling::N3D, /*HF*/{{1.73205081e+0f, 1.00000000e+0f}}, {{ - {{1.66669447e-1f, 0.00000000e+0f, 2.36070520e-1f, -1.66153012e-1f}}, - {{1.66669447e-1f, 2.04127551e-1f, -1.17487922e-1f, -1.66927066e-1f}}, - {{1.66669447e-1f, 2.04127551e-1f, 1.17487922e-1f, 1.66927066e-1f}}, - {{1.66669447e-1f, -2.04127551e-1f, 1.17487922e-1f, 1.66927066e-1f}}, - {{1.66669447e-1f, -2.04127551e-1f, -1.17487922e-1f, -1.66927066e-1f}}, - {{1.66669447e-1f, 0.00000000e+0f, -2.36070520e-1f, 1.66153012e-1f}}, + {{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.66669447e-1f, 0.00000000e+0f, 2.36070520e-1f, -1.66153012e-1f}}, - {{1.66669447e-1f, 2.04127551e-1f, -1.17487922e-1f, -1.66927066e-1f}}, - {{1.66669447e-1f, 2.04127551e-1f, 1.17487922e-1f, 1.66927066e-1f}}, - {{1.66669447e-1f, -2.04127551e-1f, 1.17487922e-1f, 1.66927066e-1f}}, - {{1.66669447e-1f, -2.04127551e-1f, -1.17487922e-1f, -1.66927066e-1f}}, - {{1.66669447e-1f, 0.00000000e+0f, -2.36070520e-1f, 1.66153012e-1f}}, + {{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}}, }} }; @@ -599,6 +597,7 @@ 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); @@ -610,59 +609,59 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= [&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; 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 uint idx{device->channelIdxByName(decoder.mChannels[i])}; + if(idx == InvalidChannelIndex) { ERR("Failed to find %s channel in device\n", GetLabelFromChannel(decoder.mChannels[i])); continue; } + auto ordermap = decoder.mIs3D ? AmbiIndex::OrderFromChannel().data() + : AmbiIndex::OrderFrom2DChannel().data(); + 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; - } + 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; - } + 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); @@ -698,6 +697,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" : ""); @@ -711,10 +711,13 @@ void InitHrtfPanning(ALCdevice *device) constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/}; constexpr float Deg_45{Deg_90 / 2.0f /* 45 degrees*/}; constexpr float Deg135{Deg_45 * 3.0f /*135 degrees*/}; + constexpr float Deg_21{3.648638281e-01f /* 20~ 21 degrees*/}; + constexpr float Deg_32{5.535743589e-01f /* 31~ 32 degrees*/}; constexpr float Deg_35{6.154797087e-01f /* 35~ 36 degrees*/}; + constexpr float Deg_58{1.017221968e+00f /* 58~ 59 degrees*/}; constexpr float Deg_69{1.205932499e+00f /* 69~ 70 degrees*/}; constexpr float Deg111{1.935660155e+00f /*110~111 degrees*/}; - constexpr float Deg_21{3.648638281e-01f /* 20~ 21 degrees*/}; + constexpr float Deg122{2.124370686e+00f /*121~122 degrees*/}; static const AngularPoint AmbiPoints1O[]{ { EvRadians{ Deg_35}, AzRadians{-Deg_45} }, { EvRadians{ Deg_35}, AzRadians{-Deg135} }, @@ -725,20 +728,18 @@ void InitHrtfPanning(ALCdevice *device) { 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} }, + { EvRadians{-Deg_32}, AzRadians{ 0.0f} }, + { EvRadians{ 0.0f}, AzRadians{ Deg_58} }, + { EvRadians{ Deg_58}, AzRadians{ Deg_90} }, + { EvRadians{ Deg_32}, AzRadians{ 0.0f} }, + { EvRadians{ 0.0f}, AzRadians{ Deg122} }, + { EvRadians{-Deg_58}, AzRadians{-Deg_90} }, + { EvRadians{-Deg_32}, AzRadians{ Deg180} }, + { EvRadians{ 0.0f}, AzRadians{-Deg122} }, + { EvRadians{ Deg_58}, AzRadians{-Deg_90} }, + { EvRadians{ Deg_32}, AzRadians{ Deg180} }, + { EvRadians{ 0.0f}, AzRadians{-Deg_58} }, + { EvRadians{-Deg_58}, AzRadians{ Deg_90} }, }, AmbiPoints3O[]{ { EvRadians{ Deg_69}, AzRadians{-Deg_90} }, { EvRadians{ Deg_69}, AzRadians{ Deg_90} }, @@ -771,20 +772,18 @@ void InitHrtfPanning(ALCdevice *device) { 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, }, + { 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }, + { 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, + { 8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, + { 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }, + { 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, + { 8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, + { 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }, + { 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, + { 8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, + { 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }, + { 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, + { 8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, }, AmbiMatrix3O[][MaxAmbiChannels]{ { 5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }, { 5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }, @@ -810,13 +809,13 @@ void InitHrtfPanning(ALCdevice *device) static const float AmbiOrderHFGain1O[MaxAmbiOrder+1]{ /*ENRGY*/ 2.000000000e+00f, 1.154700538e+00f }, AmbiOrderHFGain2O[MaxAmbiOrder+1]{ - /*ENRGY 2.357022604e+00f, 1.825741858e+00f, 9.428090416e-01f*/ + /*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 + /*RMS 9.128709292e-01f, 7.071067812e-01f, 3.651483717e-01f*/ }, AmbiOrderHFGain3O[MaxAmbiOrder+1]{ /*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"); @@ -881,11 +880,13 @@ void InitHrtfPanning(ALCdevice *device) (device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "", device->mHrtfName.c_str()); + bool perHrirMin{false}; al::span AmbiPoints{AmbiPoints1O}; const float (*AmbiMatrix)[MaxAmbiChannels]{AmbiMatrix1O}; al::span AmbiOrderHFGain{AmbiOrderHFGain1O}; if(ambi_order >= 3) { + perHrirMin = true; AmbiPoints = AmbiPoints3O; AmbiMatrix = AmbiMatrix3O; AmbiOrderHFGain = AmbiOrderHFGain3O; @@ -897,6 +898,7 @@ 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, @@ -907,11 +909,11 @@ void InitHrtfPanning(ALCdevice *device) 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) @@ -920,8 +922,9 @@ void InitUhjPanning(ALCdevice *device) constexpr size_t count{Ambi2DChannelsFromOrder(1)}; device->mAmbiOrder = 1; + device->m2DMixing = true; - auto acnmap_begin = AmbiIndex::FromFuMa().begin(); + auto acnmap_begin = AmbiIndex::FromFuMa2D().begin(); std::transform(acnmap_begin, acnmap_begin + count, std::begin(device->Dry.AmbiMap), [](const uint8_t &acn) noexcept -> BFChannelConfig { return BFChannelConfig{1.0f/AmbiScale::FromUHJ()[acn], acn}; }); @@ -940,6 +943,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) @@ -955,6 +959,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalRealOut.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(nullptr, "front-stablizer", false) != 0}; + const bool hqdec{device->getConfigValueBool("decoder", "hq-mode", true) != 0}; InitPanning(device, hqdec, stablize, decoder); - if(decoder.mOrder > 0) + if(decoder) { float accum_dist{0.0f}, spkr_count{0.0f}; for(auto dist : speakerdists) @@ -1016,11 +1021,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()}) { @@ -1068,7 +1075,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalmHrtf.get()}; - device->mIrSize = hrtf->irSize; + device->mIrSize = hrtf->mIrSize; if(auto hrtfsizeopt = device->configValue(nullptr, "hrtf-size")) { if(*hrtfsizeopt > 0 && *hrtfsizeopt < device->mIrSize) @@ -1085,7 +1092,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; @@ -1121,49 +1141,12 @@ 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 = {}; - - *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; + slot->mWetBuffer.resize(count); auto acnmap_begin = AmbiIndex::FromACN().begin(); auto iter = std::transform(acnmap_begin, acnmap_begin + count, slot->Wet.AmbiMap.begin(), [](const uint8_t &acn) noexcept -> BFChannelConfig { return BFChannelConfig{1.0f, acn}; }); std::fill(iter, slot->Wet.AmbiMap.end(), BFChannelConfig{}); - slot->Wet.Buffer = wetbuffer->mBuffer; + slot->Wet.Buffer = slot->mWetBuffer; } diff --git a/thirdparty/openal/cmake/FindFFmpeg.cmake b/thirdparty/openal/cmake/FindFFmpeg.cmake index 60ca68fd08..26ed4d2faf 100644 --- a/thirdparty/openal/cmake/FindFFmpeg.cmake +++ b/thirdparty/openal/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/thirdparty/openal/cmake/FindPulseAudio.cmake b/thirdparty/openal/cmake/FindPulseAudio.cmake index 1f6f843a1d..fdcbc20ff0 100644 --- a/thirdparty/openal/cmake/FindPulseAudio.cmake +++ b/thirdparty/openal/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/thirdparty/openal/cmake/FindSDL2.cmake b/thirdparty/openal/cmake/FindSDL2.cmake deleted file mode 100644 index e808d00615..0000000000 --- a/thirdparty/openal/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/thirdparty/openal/cmake/bin2h.script.cmake b/thirdparty/openal/cmake/bin2h.script.cmake index 1438fde29f..7e74a7a1b8 100644 --- a/thirdparty/openal/cmake/bin2h.script.cmake +++ b/thirdparty/openal/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/thirdparty/openal/common/alcomplex.cpp b/thirdparty/openal/common/alcomplex.cpp index 126e2c0435..b6cac4cd68 100644 --- a/thirdparty/openal/common/alcomplex.cpp +++ b/thirdparty/openal/common/alcomplex.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "albit.h" @@ -20,12 +21,6 @@ namespace { using ushort = unsigned short; using ushort2 = std::pair; -/* 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 { /* Some magic math that calculates the number of swaps needed for a @@ -34,51 +29,54 @@ constexpr size_t BitReverseCounter(size_t log2_size) noexcept return (1u<<(log2_size-1)) - (1u<<((log2_size-1u)/2u)); } + template -constexpr auto GetBitReverser() noexcept -{ +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}; + ushort2 mData[BitReverseCounter(N)]{}; - /* 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 size_t fftsize{1u << N}; + size_t ret_i{0}; - if(idx < revidx) + /* Bit-reversal permutation applied to a sequence of fftsize items. */ + for(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; + size_t revidx{0u}, imask{idx}; + for(size_t i{0};i < N;++i) + { + revidx = (revidx<<1) | (imask&1); + imask >>= 1; + } + + if(idx < revidx) + { + mData[ret_i].first = static_cast(idx); + mData[ret_i].second = static_cast(revidx); + ++ret_i; + } } + assert(ret_i == al::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. */ -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 std::array,11> gBitReverses{{ {}, {}, BitReverser2.mData, BitReverser3.mData, @@ -89,11 +87,13 @@ constexpr al::span gBitReverses[11]{ BitReverser8.mData, BitReverser9.mData, BitReverser10.mData -}; +}}; } // namespace -void complex_fft(const al::span> buffer, const double sign) +template +std::enable_if_t::value> +complex_fft(const al::span> buffer, const al::type_identity_t sign) { const size_t fftsize{buffer.size()}; /* Get the number of bits used for indexing. Simplifies bit-reversal and @@ -101,7 +101,7 @@ void complex_fft(const al::span> buffer, const double sign) */ const size_t log2_size{static_cast(al::countr_zero(fftsize))}; - if(unlikely(log2_size >= al::size(gBitReverses))) + if(log2_size >= gBitReverses.size()) [[unlikely]] { for(size_t idx{1u};idx < fftsize-1;++idx) { @@ -116,25 +116,25 @@ void complex_fft(const al::span> buffer, const double sign) std::swap(buffer[idx], buffer[revidx]); } } - else for(auto &rev : gBitReverses[log2_size]) + else for(auto &rev : gBitReverses[log2_size]) [[likely]] std::swap(buffer[rev.first], buffer[rev.second]); /* Iterative form of Danielson-Lanczos lemma */ - const double pi{al::numbers::pi * sign}; + const Real pi{al::numbers::pi_v * sign}; size_t step2{1u}; for(size_t i{0};i < log2_size;++i) { - const double arg{pi / static_cast(step2)}; + const Real arg{pi / static_cast(step2)}; /* TODO: Would std::polar(1.0, arg) be any better? */ - const std::complex w{std::cos(arg), std::sin(arg)}; - std::complex u{1.0, 0.0}; + const 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}; + std::complex temp{buffer[k+step2] * u}; buffer[k+step2] = buffer[k] - temp; buffer[k] += temp; } @@ -148,6 +148,8 @@ void complex_fft(const al::span> buffer, const double sign) void complex_hilbert(const al::span> buffer) { + using namespace std::placeholders; + inverse_fft(buffer); const double inverse_size = 1.0/static_cast(buffer.size()); @@ -156,11 +158,14 @@ void complex_hilbert(const al::span> buffer) *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](auto a){ return a * scale; }); *bufiter *= inverse_size; ++bufiter; std::fill(bufiter, buffer.end(), std::complex{}); forward_fft(buffer); } + + +template void complex_fft<>(const al::span> buffer, const float sign); +template void complex_fft<>(const al::span> buffer, const double sign); diff --git a/thirdparty/openal/common/alcomplex.h b/thirdparty/openal/common/alcomplex.h index 23b8114a34..794c352689 100644 --- a/thirdparty/openal/common/alcomplex.h +++ b/thirdparty/openal/common/alcomplex.h @@ -2,6 +2,7 @@ #define ALCOMPLEX_H #include +#include #include "alspan.h" @@ -10,21 +11,27 @@ * FFT and 1 is inverse FFT. Applies the Discrete Fourier Transform (DFT) to * the data supplied in the buffer, which MUST BE power of two. */ -void complex_fft(const al::span> buffer, const double sign); +template +std::enable_if_t::value> +complex_fft(const al::span> buffer, const al::type_identity_t sign); /** * Calculate the frequency-domain response of the time-domain signal in the * provided buffer, which MUST BE power of two. */ -inline void forward_fft(const al::span> buffer) -{ complex_fft(buffer, -1.0); } +template +std::enable_if_t::value> +forward_fft(const al::span,N> buffer) +{ complex_fft(buffer.subspan(0), -1); } /** * Calculate the time-domain signal of the frequency-domain response in the * provided buffer, which MUST BE power of two. */ -inline void inverse_fft(const al::span> buffer) -{ complex_fft(buffer, 1.0); } +template +std::enable_if_t::value> +inverse_fft(const al::span,N> buffer) +{ complex_fft(buffer.subspan(0), 1); } /** * Calculate the complex helical sequence (discrete-time analytical signal) of diff --git a/thirdparty/openal/common/alfstream.cpp b/thirdparty/openal/common/alfstream.cpp index 3beda83301..8991ce0352 100644 --- a/thirdparty/openal/common/alfstream.cpp +++ b/thirdparty/openal/common/alfstream.cpp @@ -9,141 +9,17 @@ 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); + : std::ifstream{utf8_to_wstr(filename).c_str(), mode} +{ } - // 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); +void ifstream::open(const char *filename, std::ios_base::openmode mode) +{ + std::wstring wstr{utf8_to_wstr(filename)}; + std::ifstream::open(wstr.c_str(), mode); } -/* 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() { } +ifstream::~ifstream() = default; } // namespace al diff --git a/thirdparty/openal/common/alfstream.h b/thirdparty/openal/common/alfstream.h index 353fd2de69..62b2e12c75 100644 --- a/thirdparty/openal/common/alfstream.h +++ b/thirdparty/openal/common/alfstream.h @@ -3,57 +3,29 @@ #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; - +// Inherit from std::ifstream to accept UTF-8 filenames +class ifstream final : public std::ifstream { public: - filebuf() = default; - ~filebuf() override; + explicit ifstream(const char *filename, std::ios_base::openmode mode=std::ios_base::in); + explicit ifstream(const std::string &filename, std::ios_base::openmode mode=std::ios_base::in) + : ifstream{filename.c_str(), mode} { } - bool open(const wchar_t *filename, std::ios_base::openmode mode); - bool open(const char *filename, std::ios_base::openmode mode); + explicit ifstream(const wchar_t *filename, std::ios_base::openmode mode=std::ios_base::in) + : std::ifstream{filename, mode} { } + explicit ifstream(const std::wstring &filename, std::ios_base::openmode mode=std::ios_base::in) + : ifstream{filename.c_str(), mode} { } - bool is_open() const noexcept { return mFile != INVALID_HANDLE_VALUE; } + void open(const char *filename, std::ios_base::openmode mode=std::ios_base::in); + void open(const std::string &filename, std::ios_base::openmode mode=std::ios_base::in) + { open(filename.c_str(), mode); } - 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 @@ -64,7 +36,6 @@ public: namespace al { -using filebuf = std::filebuf; using ifstream = std::ifstream; } // namespace al diff --git a/thirdparty/openal/common/almalloc.h b/thirdparty/openal/common/almalloc.h index 295107dc67..a795fc3b76 100644 --- a/thirdparty/openal/common/almalloc.h +++ b/thirdparty/openal/common/almalloc.h @@ -72,8 +72,10 @@ enum FamCount : size_t { }; namespace al { -template +template struct allocator { + static constexpr std::size_t alignment{std::max(Align, alignof(T))}; + using value_type = T; using reference = T&; using const_reference = const T&; @@ -85,7 +87,7 @@ struct allocator { template struct rebind { - using other = allocator; + using other = allocator; }; constexpr explicit allocator() noexcept = default; @@ -106,6 +108,18 @@ template constexpr bool operator!=(const allocator&, const allocator&) noexcept { return false; } +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 to_address(p.operator->()); } + + template constexpr T* construct_at(T *ptr, Args&& ...args) noexcept(std::is_nothrow_constructible::value) diff --git a/thirdparty/openal/common/alnumeric.h b/thirdparty/openal/common/alnumeric.h index 9e7a7f0740..13e61645e7 100644 --- a/thirdparty/openal/common/alnumeric.h +++ b/thirdparty/openal/common/alnumeric.h @@ -161,11 +161,11 @@ inline int float2int(float f) noexcept 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) + if(shift < 0) [[likely]] return (mant >> -shift) * sign; return (mant << shift) * sign; @@ -198,11 +198,11 @@ inline int double2int(double d) noexcept shift = ((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) + if(shift < 0) [[likely]] return (int)(mant >> -shift) * sign; return (int)(mant << shift) * sign; @@ -251,7 +251,7 @@ inline float fast_roundf(float f) noexcept sign = (conv.i>>31)&0x01; 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 diff --git a/thirdparty/openal/common/alspan.h b/thirdparty/openal/common/alspan.h index 4a0e0430ff..519f22e450 100644 --- a/thirdparty/openal/common/alspan.h +++ b/thirdparty/openal/common/alspan.h @@ -7,6 +7,8 @@ #include #include +#include "almalloc.h" + namespace al { template @@ -35,6 +37,13 @@ constexpr const T* data(std::initializer_list list) noexcept { return list.begin(); } +template +struct type_identity { using type = T; }; + +template +using type_identity_t = typename type_identity::type; + + constexpr size_t dynamic_extent{static_cast(-1)}; template @@ -42,37 +51,39 @@ class span; namespace detail_ { template - struct make_void { using type = void; }; - template - using void_t = typename make_void::type; + using void_t = void; template struct is_span_ : std::false_type { }; template struct is_span_> : std::true_type { }; template - using is_span = is_span_>; + constexpr bool is_span_v = is_span_>::value; template struct is_std_array_ : std::false_type { }; template struct is_std_array_> : std::true_type { }; template - using is_std_array = is_std_array_>; + constexpr bool is_std_array_v = is_std_array_>::value; template - struct has_size_and_data : std::false_type { }; + constexpr bool has_size_and_data = false; template - struct has_size_and_data())), decltype(al::data(std::declval()))>> - : std::true_type { }; + = true; + + template + constexpr bool is_array_compatible = std::is_convertible::value; + + template + constexpr bool is_valid_container = !is_span_v && !is_std_array_v + && !std::is_array::value && has_size_and_data + && is_array_compatible()))>,T>; } // 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 class span { @@ -94,23 +105,33 @@ public: static constexpr 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)} { } + template + constexpr explicit span(U iter, index_type) : mData{to_address(iter)} { } + template::value)> + constexpr explicit span(U first, V) : mData{to_address(first)} { } + + constexpr span(type_identity_t (&arr)[E]) noexcept + : span{al::data(arr), al::size(arr)} + { } constexpr span(std::array &arr) noexcept : span{al::data(arr), al::size(arr)} { } - template::value)> + 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&& cont) : span{al::data(cont), al::size(cont)} { } + + template::value + && detail_::is_array_compatible && N == dynamic_extent)> + constexpr explicit span(const span &span_) noexcept + : span{al::data(span_), al::size(span_)} + { } + template::value + && detail_::is_array_compatible && N == extent)> + constexpr span(const span &span_) noexcept : span{al::data(span_), al::size(span_)} { } constexpr span(const span&) noexcept = default; constexpr span& operator=(const span &rhs) noexcept = default; @@ -199,22 +220,30 @@ public: static constexpr 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(U iter, index_type count) + : mData{to_address(iter)}, mDataEnd{to_address(iter)+count} + { } + template::value)> + constexpr span(U first, V last) : span{to_address(first), static_cast(last-first)} + { } + template - constexpr span(element_type (&arr)[N]) noexcept : span{al::data(arr), al::size(arr)} { } + constexpr span(type_identity_t (&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)> + 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)> + constexpr span(U&& cont) : span{al::data(cont), al::size(cont)} { } + template::value || extent != N) - && std::is_convertible::value)> + && detail_::is_array_compatible)> constexpr span(const span &span_) noexcept : span{al::data(span_), al::size(span_)} { } constexpr span(const span&) noexcept = default; @@ -299,7 +328,31 @@ constexpr inline auto span::subspan(size_t offset, size_t count) const span{mData+offset, mData+offset+count}; } -#undef IS_VALID_CONTAINER +/* Helpers to deal with the lack of user-defined deduction guides (C++17). */ +template +constexpr auto as_span(T ptr, U count_or_end) +{ + using value_type = typename std::pointer_traits::element_type; + return span{ptr, count_or_end}; +} +template +constexpr auto as_span(T (&arr)[N]) noexcept { return span{al::data(arr), al::size(arr)}; } +template +constexpr auto as_span(std::array &arr) noexcept +{ return span{al::data(arr), al::size(arr)}; } +template +constexpr auto as_span(const std::array &arr) noexcept +{ return span,N>{al::data(arr), al::size(arr)}; } +template && !detail_::is_std_array_v + && !std::is_array::value && detail_::has_size_and_data)> +constexpr auto as_span(U&& cont) +{ + using value_type = std::remove_pointer_t()))>; + return span{al::data(cont), al::size(cont)}; +} +template +constexpr auto as_span(span span_) noexcept { return span_; } + #undef REQUIRES } // namespace al diff --git a/thirdparty/openal/common/comptr.h b/thirdparty/openal/common/comptr.h index 3dc574e803..f2355d058d 100644 --- a/thirdparty/openal/common/comptr.h +++ b/thirdparty/openal/common/comptr.h @@ -44,7 +44,7 @@ public: } ComPtr& operator=(ComPtr&& rhs) { - if(likely(&rhs != this)) + if(&rhs != this) [[likely]] { if(mPtr) mPtr->Release(); mPtr = std::exchange(rhs.mPtr, nullptr); diff --git a/thirdparty/openal/common/intrusive_ptr.h b/thirdparty/openal/common/intrusive_ptr.h index 9e206a6b62..84d18aa0ea 100644 --- a/thirdparty/openal/common/intrusive_ptr.h +++ b/thirdparty/openal/common/intrusive_ptr.h @@ -15,10 +15,10 @@ class intrusive_ref { 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 +58,22 @@ 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(); } 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; } 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; @@ -88,7 +88,7 @@ public: void reset(T *ptr=nullptr) noexcept { if(mPtr) - mPtr->release(); + mPtr->dec_ref(); mPtr = ptr; } diff --git a/thirdparty/openal/common/opthelpers.h b/thirdparty/openal/common/opthelpers.h index e46c0f3a9b..95ff781aa4 100644 --- a/thirdparty/openal/common/opthelpers.h +++ b/thirdparty/openal/common/opthelpers.h @@ -3,7 +3,7 @@ #include #include - +#include #ifdef __has_builtin #define HAS_BUILTIN __has_builtin @@ -12,49 +12,25 @@ #endif #ifdef __GNUC__ -#define force_inline [[gnu::always_inline]] +#define force_inline [[gnu::always_inline]] inline #elif defined(_MSC_VER) #define force_inline __forceinline #else #define force_inline inline #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) @@ -62,12 +38,25 @@ force_inline constexpr bool unlikely(T&& expr) noexcept 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< dst, const float *RESTRICT src) const; - void processAccum(al::span dst, const float *RESTRICT src) const; private: #if defined(HAVE_NEON) @@ -212,103 +211,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/thirdparty/openal/common/polyphase_resampler.cpp b/thirdparty/openal/common/polyphase_resampler.cpp index bb8f69a41d..5475cff851 100644 --- a/thirdparty/openal/common/polyphase_resampler.cpp +++ b/thirdparty/openal/common/polyphase_resampler.cpp @@ -21,7 +21,7 @@ using uint = unsigned int; */ double Sinc(const double x) { - if(unlikely(std::abs(x) < Epsilon)) + if(std::abs(x) < Epsilon) [[unlikely]] return 1.0; return std::sin(al::numbers::pi*x) / (al::numbers::pi*x); } @@ -96,7 +96,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 +104,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)) + @@ -171,13 +171,13 @@ void PPhaseResampler::init(const uint srcRate, const uint dstRate) // polyphase filter implementation. void PPhaseResampler::process(const uint inN, const double *in, const uint outN, double *out) { - if UNLIKELY(outN == 0) + if(outN == 0) [[unlikely]] return; // Handle in-place operation. std::vector workspace; double *work{out}; - if UNLIKELY(work == in) + if(work == in) [[unlikely]] { workspace.resize(outN); work = workspace.data(); @@ -195,17 +195,17 @@ void PPhaseResampler::process(const uint inN, const double *in, const uint outN, // Only take input when 0 <= j_s < inN. 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) + if(j_s+1 > inN) [[likely]] { size_t skip{std::min(j_s+1 - inN, filt_len)}; j_f += p*skip; j_s -= skip; filt_len -= skip; } - if(size_t todo{std::min(j_s+1, filt_len)}) + if(size_t todo{std::min(j_s+1, filt_len)}) [[likely]] { do { r += f[j_f] * in[j_s]; diff --git a/thirdparty/openal/common/polyphase_resampler.h b/thirdparty/openal/common/polyphase_resampler.h index 0b343d5ac1..557485bb2f 100644 --- a/thirdparty/openal/common/polyphase_resampler.h +++ b/thirdparty/openal/common/polyphase_resampler.h @@ -37,6 +37,8 @@ struct PPhaseResampler { void init(const uint srcRate, const uint dstRate); void process(const uint inN, const double *in, const uint outN, double *out); + explicit operator bool() const noexcept { return !mF.empty(); } + private: uint mP, mQ, mM, mL; std::vector mF; diff --git a/thirdparty/openal/common/strutils.cpp b/thirdparty/openal/common/strutils.cpp index 18c4947ad2..d0418eff2e 100644 --- a/thirdparty/openal/common/strutils.cpp +++ b/thirdparty/openal/common/strutils.cpp @@ -47,7 +47,7 @@ al::optional getenv(const char *envname) { const char *str{std::getenv(envname)}; if(str && str[0] != '\0') - return al::make_optional(str); + return str; return al::nullopt; } @@ -56,7 +56,7 @@ al::optional getenv(const WCHAR *envname) { const WCHAR *str{_wgetenv(envname)}; if(str && str[0] != L'\0') - return al::make_optional(str); + return str; return al::nullopt; } #endif diff --git a/thirdparty/openal/common/threads.cpp b/thirdparty/openal/common/threads.cpp index c782dc35cd..19a6bbf04c 100644 --- a/thirdparty/openal/common/threads.cpp +++ b/thirdparty/openal/common/threads.cpp @@ -34,7 +34,8 @@ void althrd_setname(const char *name) { -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(_M_ARM) + #define MS_VC_EXCEPTION 0x406D1388 #pragma pack(push,8) struct { @@ -55,7 +56,9 @@ void althrd_setname(const char *name) __except(EXCEPTION_CONTINUE_EXECUTION) { } #undef MS_VC_EXCEPTION + #else + (void)name; #endif } @@ -76,7 +79,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)); } @@ -100,7 +103,8 @@ namespace { using setname_t1 = int(*)(const char*); using setname_t2 = int(*)(pthread_t, const char*); -using setname_t3 = int(*)(pthread_t, const char*, void*); +using setname_t3 = void(*)(pthread_t, const char*); +using setname_t4 = int(*)(pthread_t, const char*, void*); void setname_caller(setname_t1 func, const char *name) { func(name); } @@ -109,6 +113,9 @@ void setname_caller(setname_t2 func, const char *name) { func(pthread_self(), name); } void setname_caller(setname_t3 func, const char *name) +{ func(pthread_self(), name); } + +void setname_caller(setname_t4 func, const char *name) { func(pthread_self(), "%s", static_cast(const_cast(name))); } } // namespace @@ -125,6 +132,7 @@ void althrd_setname(const char *name) std::ignore = static_cast(&setname_caller); std::ignore = static_cast(&setname_caller); std::ignore = static_cast(&setname_caller); + std::ignore = static_cast(&setname_caller); } #ifdef __APPLE__ diff --git a/thirdparty/openal/common/vecmat.h b/thirdparty/openal/common/vecmat.h index 78fd806e7c..a45f262f6d 100644 --- a/thirdparty/openal/common/vecmat.h +++ b/thirdparty/openal/common/vecmat.h @@ -14,19 +14,19 @@ namespace alu { template class VectorR { static_assert(std::is_floating_point::value, "Must use floating-point types"); - alignas(16) std::array mVals; + alignas(16) T mVals[4]; 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 +35,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,38 +58,39 @@ public: return T{0}; } - constexpr VectorR cross_product(const alu::VectorR &rhs) const + constexpr VectorR cross_product(const alu::VectorR &rhs) const noexcept { 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]; } + constexpr T dot_product(const alu::VectorR &rhs) const noexcept + { return mVals[0]*rhs.mVals[0] + mVals[1]*rhs.mVals[1] + mVals[2]*rhs.mVals[2]; } }; using Vector = VectorR; template class MatrixR { static_assert(std::is_floating_point::value, "Must use floating-point types"); - alignas(16) std::array mVals; + alignas(16) T mVals[16]; 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 +106,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 +115,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/thirdparty/openal/common/win_main_utf8.h b/thirdparty/openal/common/win_main_utf8.h index e40c2158a4..077af533d5 100644 --- a/thirdparty/openal/common/win_main_utf8.h +++ b/thirdparty/openal/common/win_main_utf8.h @@ -44,10 +44,10 @@ 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)+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 + modelen); #endif wmode = wname + namelen; MultiByteToWideChar(CP_UTF8, 0, fname, -1, wname, namelen); diff --git a/thirdparty/openal/core/ambdec.cpp b/thirdparty/openal/core/ambdec.cpp index 0df22bc954..5e2de6af72 100644 --- a/thirdparty/openal/core/ambdec.cpp +++ b/thirdparty/openal/core/ambdec.cpp @@ -5,51 +5,21 @@ #include #include +#include #include +#include #include #include #include +#include "albit.h" #include "alfstream.h" -#include "core/logging.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 +31,45 @@ 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 __USE_MINGW_ANSI_STDIO +[[gnu::format(gnu_printf,2,3)]] +#else +[[gnu::format(printf,2,3)]] +#endif +al::optional make_error(size_t linenum, const char *fmt, ...) { - size_t cur_speaker{0}; - while(cur_speaker < num_speakers) + al::optional ret; + auto &str = ret.emplace(); + + str.resize(256); + int printed{std::snprintf(const_cast(str.data()), str.length(), "Line %zu: ", linenum)}; + if(printed < 0) printed = 0; + auto plen = std::min(static_cast(printed), str.length()); + + 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) [[unlikely]] { - 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); - 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 @@ -186,96 +81,162 @@ al::optional AmbDecConf::load(const char *fname) noexcept { al::ifstream f{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 == NumSpeakers) + 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 < al::size(gains)) + gains[curgain++] = value; + } + } + else if(command == "add_row") + { + if(pos == NumSpeakers) + 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"); - + return make_error(linenum, "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)); + return make_error(linenum, "Invalid speakers: %zu", NumSpeakers); Speakers = std::make_unique(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); @@ -283,33 +244,15 @@ al::optional AmbDecConf::load(const char *fname) noexcept 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); + 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(); + return make_error(linenum, "Matrix defined without a speaker count"); + if(!ChanMask) + return make_error(linenum, "Matrix defined without a channel mask"); if(!Matrix) { @@ -321,58 +264,43 @@ al::optional AmbDecConf::load(const char *fname) noexcept 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 < NumSpeakers || hfmatrix_pos < NumSpeakers + || (FreqBands == 2 && lfmatrix_pos < NumSpeakers)) + return make_error(linenum, "Incomplete decoder definition"); + if(CoeffScale == AmbDecScale::Unset) + return make_error(linenum, "No coefficient scaling defined"); return al::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/thirdparty/openal/core/ambdec.h b/thirdparty/openal/core/ambdec.h index e1bcde26be..7f7397817c 100644 --- a/thirdparty/openal/core/ambdec.h +++ b/thirdparty/openal/core/ambdec.h @@ -11,6 +11,7 @@ /* Helpers to read .ambdec configuration files. */ enum class AmbDecScale { + Unset, N3D, SN3D, FuMa, @@ -21,7 +22,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}; diff --git a/thirdparty/openal/core/ambidefs.cpp b/thirdparty/openal/core/ambidefs.cpp index 2725748e60..70d6f356e6 100644 --- a/thirdparty/openal/core/ambidefs.cpp +++ b/thirdparty/openal/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; + +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,MaxAmbiOrder+1> HFScales{{ + {{ 4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }}, + {{ 4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }}, + {{ 2.981423970e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }}, + {{ 2.359168820e+00f, 2.031565936e+00f, 1.444598386e+00f, 7.189495850e-01f }}, + /* 1.947005434e+00f, 1.764337084e+00f, 1.424707344e+00f, 9.755104127e-01f, 4.784482742e-01f */ }}; -inline auto& GetDecoderHFScales(uint order) noexcept +/* Same as above, but using a 10-point horizontal-only speaker array. Should + * only be used when the device is mixing in 2D B-Format for horizontal-only + * output. + */ +constexpr std::array,MaxAmbiOrder+1> HFScales2D{{ + {{ 2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }}, + {{ 2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }}, + {{ 1.825741858e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }}, + {{ 1.581138830e+00f, 1.460781803e+00f, 1.118033989e+00f, 6.050756345e-01f }}, + /* 1.414213562e+00f, 1.344997024e+00f, 1.144122806e+00f, 8.312538756e-01f, 4.370160244e-01f */ +}}; + + +/* 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,8> FirstOrderDecoder{{ + {{ 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, }}, + {{ 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, }}, + {{ 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, }}, + {{ 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, }}, + {{ 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, }}, + {{ 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, }}, + {{ 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, }}, + {{ 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, }}, +}}; +constexpr std::array FirstOrderEncoder{{ + 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,4> FirstOrder2DDecoder{{ + {{ 2.500000000e-01f, 2.041241452e-01f, 0.0f, 2.041241452e-01f, }}, + {{ 2.500000000e-01f, 2.041241452e-01f, 0.0f, -2.041241452e-01f, }}, + {{ 2.500000000e-01f, -2.041241452e-01f, 0.0f, 2.041241452e-01f, }}, + {{ 2.500000000e-01f, -2.041241452e-01f, 0.0f, -2.041241452e-01f, }}, +}}; +constexpr std::array FirstOrder2DEncoder{{ + 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,12> SecondOrderDecoder{{ + {{ 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }}, + {{ 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }}, + {{ 8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }}, + {{ 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }}, + {{ 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }}, + {{ 8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }}, + {{ 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }}, + {{ 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }}, + {{ 8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }}, + {{ 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }}, + {{ 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }}, + {{ 8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }}, +}}; +constexpr std::array SecondOrderEncoder{{ + 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,6> SecondOrder2DDecoder{{ + {{ 1.666666667e-01f, -9.622504486e-02f, 0.0f, 1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }}, + {{ 1.666666667e-01f, -1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f, }}, + {{ 1.666666667e-01f, -9.622504486e-02f, 0.0f, -1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }}, + {{ 1.666666667e-01f, 9.622504486e-02f, 0.0f, -1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }}, + {{ 1.666666667e-01f, 1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f, }}, + {{ 1.666666667e-01f, 9.622504486e-02f, 0.0f, 1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }}, +}}; +constexpr std::array SecondOrder2DEncoder{{ + 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,20> ThirdOrderDecoder{{ + {{ 5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }}, + {{ 5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }}, + {{ 5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }}, + {{ 5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }}, + {{ 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }}, + {{ 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }}, + {{ 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }}, + {{ 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }}, + {{ 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f, }}, + {{ 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f, }}, + {{ 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f, }}, + {{ 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f, }}, + {{ 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }}, + {{ 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }}, + {{ 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }}, + {{ 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }}, + {{ 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }}, + {{ 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }}, + {{ 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }}, + {{ 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }}, +}}; +constexpr std::array ThirdOrderEncoder{{ + 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,8> ThirdOrder2DDecoder{{ + {{ 1.250000000e-01f, -5.523559567e-02f, 0.0f, 1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f, }}, + {{ 1.250000000e-01f, -1.333505242e-01f, 0.0f, 5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f, }}, + {{ 1.250000000e-01f, -1.333505242e-01f, 0.0f, -5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f, }}, + {{ 1.250000000e-01f, -5.523559567e-02f, 0.0f, -1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f, }}, + {{ 1.250000000e-01f, 5.523559567e-02f, 0.0f, -1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f, }}, + {{ 1.250000000e-01f, 1.333505242e-01f, 0.0f, -5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f, }}, + {{ 1.250000000e-01f, 1.333505242e-01f, 0.0f, 5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f, }}, + {{ 1.250000000e-01f, 5.523559567e-02f, 0.0f, 1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f, }}, +}}; +constexpr std::array ThirdOrder2DEncoder{{ + 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,10> FourthOrder2DDecoder{{ + {{ 1.000000000e-01f, 3.568220898e-02f, 0.0f, 1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }}, + {{ 1.000000000e-01f, 9.341723590e-02f, 0.0f, 6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }}, + {{ 1.000000000e-01f, 1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, -9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f, }}, + {{ 1.000000000e-01f, 9.341723590e-02f, 0.0f, -6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }}, + {{ 1.000000000e-01f, 3.568220898e-02f, 0.0f, -1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }}, + {{ 1.000000000e-01f, -3.568220898e-02f, 0.0f, -1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }}, + {{ 1.000000000e-01f, -9.341723590e-02f, 0.0f, -6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }}, + {{ 1.000000000e-01f, -1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, 9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f, }}, + {{ 1.000000000e-01f, -9.341723590e-02f, 0.0f, 6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }}, + {{ 1.000000000e-01f, -3.568220898e-02f, 0.0f, 1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }}, +}}; +constexpr std::array FourthOrder2DEncoder{{ + 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 +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 AmbiScale::FirstOrderUp{CalcAmbiUpsampler(FirstOrderDecoder, FirstOrderEncoder)}; +const std::array AmbiScale::FirstOrder2DUp{CalcAmbiUpsampler(FirstOrder2DDecoder, FirstOrder2DEncoder)}; +const std::array AmbiScale::SecondOrderUp{CalcAmbiUpsampler(SecondOrderDecoder, SecondOrderEncoder)}; +const std::array AmbiScale::SecondOrder2DUp{CalcAmbiUpsampler(SecondOrder2DDecoder, SecondOrder2DEncoder)}; +const std::array AmbiScale::ThirdOrderUp{CalcAmbiUpsampler(ThirdOrderDecoder, ThirdOrderEncoder)}; +const std::array AmbiScale::ThirdOrder2DUp{CalcAmbiUpsampler(ThirdOrder2DDecoder, ThirdOrder2DEncoder)}; +const std::array AmbiScale::FourthOrder2DUp{CalcAmbiUpsampler(FourthOrder2DDecoder, FourthOrder2DEncoder)}; + + +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/thirdparty/openal/core/ambidefs.h b/thirdparty/openal/core/ambidefs.h index 82a1a4e5b9..b7d2bcd1f9 100644 --- a/thirdparty/openal/core/ambidefs.h +++ b/thirdparty/openal/core/ambidefs.h @@ -5,6 +5,9 @@ #include #include +#include "alnumbers.h" + + using uint = unsigned int; /* The maximum number of Ambisonics channels. For a given order (o), the size @@ -111,8 +114,16 @@ struct AmbiScale { } /* 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 { @@ -184,4 +195,56 @@ struct AmbiIndex { } }; + +/** + * 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/thirdparty/openal/core/async_event.h b/thirdparty/openal/core/async_event.h index 750f38c9c0..5a2f5f91ac 100644 --- a/thirdparty/openal/core/async_event.h +++ b/thirdparty/openal/core/async_event.h @@ -10,16 +10,17 @@ 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, + SourceStateChange, + BufferCompleted, + Disconnected, + UserEventCount, - /* Internal events. */ - ReleaseEffectState = 65536, + /* Internal events, always processed. */ + ReleaseEffectState = 128, + + /* End event thread processing. */ + KillThread, }; enum class SrcState { @@ -29,7 +30,7 @@ struct AsyncEvent { Pause }; - uint EnumType{0u}; + const uint EnumType; union { char dummy; struct { @@ -46,7 +47,6 @@ struct AsyncEvent { EffectState *mEffectState; } u{}; - AsyncEvent() noexcept = default; constexpr AsyncEvent(uint type) noexcept : EnumType{type} { } DISABLE_ALLOC() diff --git a/thirdparty/openal/core/bformatdec.cpp b/thirdparty/openal/core/bformatdec.cpp index 606093c07f..129b99762b 100644 --- a/thirdparty/openal/core/bformatdec.cpp +++ b/thirdparty/openal/core/bformatdec.cpp @@ -88,15 +88,14 @@ void BFormatDec::processStablize(const al::span OutBuffer, 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]; + mid[i] = OutBuffer[lidx][i] + OutBuffer[ridx][i]; + side[i] = OutBuffer[lidx][i] - OutBuffer[ridx][i]; } std::fill_n(OutBuffer[lidx].begin(), SamplesToDo, 0.0f); std::fill_n(OutBuffer[ridx].begin(), SamplesToDo, 0.0f); @@ -104,55 +103,36 @@ void BFormatDec::processStablize(const al::span OutBuffer, /* 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] += OutBuffer[lidx][i] - OutBuffer[ridx][i]; + + /* Get the decoded mid signal and band-split it. */ + std::transform(OutBuffer[lidx].cbegin(), OutBuffer[lidx].cbegin()+SamplesToDo, + OutBuffer[ridx].cbegin(), mStablizer->Temp.begin(), + [](const float l, const float r) noexcept { return l + r; }); + + mStablizer->MidFilter.process({mStablizer->Temp.data(), SamplesToDo}, mStablizer->MidHF.data(), + mStablizer->MidLF.data()); + + /* 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, SamplesToDo}); + else if(i == ridx) + mStablizer->ChannelFilters[i].processAllPass({side, SamplesToDo}); 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 @@ -164,6 +144,9 @@ void BFormatDec::processStablize(const al::span OutBuffer, const float sin_hf{std::sin(1.0f/4.0f * (al::numbers::pi_v*0.5f))}; 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]}; @@ -175,11 +158,6 @@ void BFormatDec::processStablize(const al::span OutBuffer, OutBuffer[ridx][i] = (m - s) * 0.5f; OutBuffer[cidx][i] += c * 0.5f; } - /* Move the delayed mid/side samples to the front for next time. */ - auto mid_end = mStablizer->MidDirect.cbegin() + SamplesToDo; - std::copy(mid_end, mid_end+FrontStablizer::DelayLength, mStablizer->MidDirect.begin()); - auto side_end = mStablizer->Side.cbegin() + SamplesToDo; - std::copy(side_end, side_end+FrontStablizer::DelayLength, mStablizer->Side.begin()); } diff --git a/thirdparty/openal/core/context.cpp b/thirdparty/openal/core/context.cpp index 39fd852215..d68d8327dd 100644 --- a/thirdparty/openal/core/context.cpp +++ b/thirdparty/openal/core/context.cpp @@ -1,6 +1,7 @@ #include "config.h" +#include #include #include "async_event.h" @@ -13,8 +14,12 @@ #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() { @@ -136,3 +141,24 @@ void ContextBase::allocVoices(size_t addcount) delete oldvoices; } } + + +EffectSlot *ContextBase::getEffectSlot() +{ + for(auto& cluster : mEffectSlotClusters) + { + for(size_t i{0};i < EffectSlotClusterSize;++i) + { + if(!cluster[i].InUse) + return &cluster[i]; + } + } + + if(1 >= std::numeric_limits::max()/EffectSlotClusterSize - mEffectSlotClusters.size()) + throw std::runtime_error{"Allocating too many effect slots"}; + const size_t totalcount{(mEffectSlotClusters.size()+1) * EffectSlotClusterSize}; + TRACE("Increasing allocated effect slots to %zu\n", totalcount); + + mEffectSlotClusters.emplace_back(std::make_unique(EffectSlotClusterSize)); + return getEffectSlot(); +} diff --git a/thirdparty/openal/core/context.h b/thirdparty/openal/core/context.h index f5768629cb..9723eac38c 100644 --- a/thirdparty/openal/core/context.h +++ b/thirdparty/openal/core/context.h @@ -3,12 +3,14 @@ #include #include +#include #include #include #include #include "almalloc.h" #include "alspan.h" +#include "async_event.h" #include "atomic.h" #include "bufferline.h" #include "threads.h" @@ -40,17 +42,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; @@ -146,7 +137,8 @@ struct ContextBase { 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. @@ -163,6 +155,13 @@ struct ContextBase { al::vector mVoicePropClusters; + static constexpr size_t EffectSlotClusterSize{4}; + EffectSlot *getEffectSlot(); + + using EffectSlotCluster = std::unique_ptr; + al::vector mEffectSlotClusters; + + ContextBase(DeviceBase *device); ContextBase(const ContextBase&) = delete; ContextBase& operator=(const ContextBase&) = delete; diff --git a/thirdparty/openal/core/converter.cpp b/thirdparty/openal/core/converter.cpp index 6a06b464f6..35b1f289e6 100644 --- a/thirdparty/openal/core/converter.cpp +++ b/thirdparty/openal/core/converter.cpp @@ -4,6 +4,7 @@ #include "converter.h" #include +#include #include #include #include @@ -142,7 +143,7 @@ void Multi2Mono(uint chanmask, const size_t step, const float scale, float *REST std::fill_n(dst, frames, 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]); @@ -155,7 +156,7 @@ void Multi2Mono(uint chanmask, const size_t step, const float scale, float *REST } // 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,8 +168,13 @@ 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) + { + const al::span buffer{chan.PrevSamples}; + std::fill(buffer.begin(), buffer.end(), 0.0f); + } /* Have to set the mixer FPU mode since that's what the resampler code expects. */ FPUCtl mixer_mode{}; @@ -186,30 +192,20 @@ 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; @@ -232,34 +228,19 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint 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{minu(NumSrcSamples, 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); + mChan.size(), mSrcType, readable); - mSrcPrepCount = prepcount + static_cast(toread); + mSrcPrepCount = prepcount + readable; NumSrcSamples = 0; break; } @@ -267,8 +248,8 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint float *RESTRICT SrcData{mSrcSamples}; float *RESTRICT DstData{mDstSamples}; uint DataPosFrac{mFracOffset}; - auto DataSize64 = static_cast(prepcount); - DataSize64 += toread; + uint64_t DataSize64{prepcount}; + DataSize64 += readable; DataSize64 -= MaxResamplerPadding; DataSize64 <<= MixerFracBits; DataSize64 -= DataPosFrac; @@ -278,6 +259,12 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint clampu64((DataSize64 + increment-1)/increment, 1, BufferLineSize)); DstSize = minu(DstSize, dstframes-pos); + const uint DataPosEnd{DstSize*increment + DataPosFrac}; + const uint SrcDataEnd{DataPosEnd>>MixerFracBits}; + + assert(prepcount+readable >= SrcDataEnd); + const uint nextprep{minu(prepcount + readable - SrcDataEnd, MaxResamplerPadding)}; + for(size_t chan{0u};chan < mChan.size();chan++) { const al::byte *SrcSamples{SamplesIn + mSrcTypeSize*chan}; @@ -287,23 +274,14 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint * new samples from the input buffer. */ std::copy_n(mChan[chan].PrevSamples, prepcount, SrcData); - LoadSamples(SrcData + prepcount, SrcSamples, mChan.size(), mSrcType, toread); + LoadSamples(SrcData + prepcount, SrcSamples, mChan.size(), mSrcType, readable); /* 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); - } + std::copy_n(SrcData+SrcDataEnd, nextprep, mChan[chan].PrevSamples); + std::fill(std::begin(mChan[chan].PrevSamples)+nextprep, + std::end(mChan[chan].PrevSamples), 0.0f); /* Now resample, and store the result in the output buffer. */ const float *ResampledData{mResample(&mState, SrcData+(MaxResamplerPadding>>1), @@ -315,14 +293,13 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint /* 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{minu(NumSrcSamples, SrcDataEnd + mSrcPrepCount - prepcount)}; + SamplesIn += SrcFrameSize*srcread; + NumSrcSamples -= srcread; dst = static_cast(dst) + DstFrameSize*DstSize; pos += DstSize; diff --git a/thirdparty/openal/core/converter.h b/thirdparty/openal/core/converter.h index 2d22ae387b..01becea28f 100644 --- a/thirdparty/openal/core/converter.h +++ b/thirdparty/openal/core/converter.h @@ -1,6 +1,7 @@ #ifndef CORE_CONVERTER_H #define CORE_CONVERTER_H +#include #include #include @@ -17,7 +18,7 @@ struct SampleConverter { uint mSrcTypeSize{}; uint mDstTypeSize{}; - int mSrcPrepCount{}; + uint mSrcPrepCount{}; uint mFracOffset{}; uint mIncrement{}; @@ -37,14 +38,20 @@ struct SampleConverter { uint convert(const void **src, uint *srcframes, void *dst, uint dstframes); uint availableOut(uint srcframes) const; + using SampleOffset = std::chrono::duration>; + SampleOffset currentInputDelay() const noexcept + { + 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{}; diff --git a/thirdparty/openal/core/cpu_caps.cpp b/thirdparty/openal/core/cpu_caps.cpp index 0325cd4910..d4b4d86c87 100644 --- a/thirdparty/openal/core/cpu_caps.cpp +++ b/thirdparty/openal/core/cpu_caps.cpp @@ -32,7 +32,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 @@ -137,5 +137,5 @@ al::optional GetCPUInfo() #endif #endif - return al::make_optional(ret); + return ret; } diff --git a/thirdparty/openal/core/devformat.cpp b/thirdparty/openal/core/devformat.cpp index cbe8eaf310..acdabc4f1f 100644 --- a/thirdparty/openal/core/devformat.cpp +++ b/thirdparty/openal/core/devformat.cpp @@ -28,6 +28,7 @@ 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); } @@ -58,6 +59,7 @@ 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"; } diff --git a/thirdparty/openal/core/devformat.h b/thirdparty/openal/core/devformat.h index f2a372c1c4..485826a397 100644 --- a/thirdparty/openal/core/devformat.h +++ b/thirdparty/openal/core/devformat.h @@ -65,6 +65,7 @@ enum DevFmtChannels : unsigned char { DevFmtX51, DevFmtX61, DevFmtX71, + DevFmtX714, DevFmtX3D71, DevFmtAmbi3D, diff --git a/thirdparty/openal/core/device.h b/thirdparty/openal/core/device.h index 58a30f1b52..e4c64a6dcd 100644 --- a/thirdparty/openal/core/device.h +++ b/thirdparty/openal/core/device.h @@ -37,9 +37,9 @@ using uint = unsigned int; #define MIN_OUTPUT_RATE 8000 #define MAX_OUTPUT_RATE 192000 -#define DEFAULT_OUTPUT_RATE 44100 +#define DEFAULT_OUTPUT_RATE 48000 -#define DEFAULT_UPDATE_SIZE 882 /* 20ms */ +#define DEFAULT_UPDATE_SIZE 960 /* 20ms */ #define DEFAULT_NUM_UPDATES 3 @@ -69,17 +69,17 @@ 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 { float Gain{1.0f}; - uint Length{0u}; /* Valid range is [0...MAX_DELAY_LENGTH). */ + uint Length{0u}; /* Valid range is [0...MaxDelay). */ float *Buffer{nullptr}; }; @@ -95,17 +95,49 @@ struct DistanceComp { }; +constexpr uint InvalidChannelIndex{~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 INVALID_CHANNEL_INDEX, 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) + { + auto idx = InvalidChannelIndex; + auto 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 { @@ -115,10 +147,12 @@ struct RealMixParams { 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, @@ -152,6 +186,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. */ @@ -182,9 +218,12 @@ struct DeviceBase { std::chrono::nanoseconds ClockBase{0}; std::chrono::nanoseconds FixedLatency{0}; + AmbiRotateMatrix mAmbiRotateMatrix{}; + AmbiRotateMatrix mAmbiRotateMatrix2{}; + /* Temp storage used for mixer processing. */ static constexpr size_t MixerLineSize{BufferLineSize + MaxResamplerPadding + - UhjDecoder::sFilterDelay}; + DecoderBase::sMaxPadding}; static constexpr size_t MixerChannelsMax{16}; using MixerBufferLine = std::array; alignas(16) std::array mSampleData; @@ -217,7 +256,7 @@ 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; @@ -272,7 +311,7 @@ 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); @@ -285,26 +324,23 @@ struct DeviceBase { #endif void handleDisconnect(const char *msg, ...); + /** + * Returns the index for the given channel name (e.g. FrontCenter), or + * INVALID_CHANNEL_INDEX if it doesn't exist. + */ + uint channelIdxByName(Channel chan) const noexcept + { return RealOut.ChannelIndex[chan]; } + DISABLE_ALLOC() private: uint renderSamples(const uint numSamples); }; - /* Must be less than 15 characters (16 including terminating null) for * compatibility with pthread_setname_np limitations. */ #define MIXER_THREAD_NAME "alsoft-mixer" #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 - #endif /* CORE_DEVICE_H */ diff --git a/thirdparty/openal/core/effectslot.cpp b/thirdparty/openal/core/effectslot.cpp index 51fb8d468f..db8aa078c2 100644 --- a/thirdparty/openal/core/effectslot.cpp +++ b/thirdparty/openal/core/effectslot.cpp @@ -17,9 +17,3 @@ EffectSlotArray *EffectSlot::CreatePtrArray(size_t count) noexcept 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; -} diff --git a/thirdparty/openal/core/effectslot.h b/thirdparty/openal/core/effectslot.h index 8b7b977ca3..2624ae5fd3 100644 --- a/thirdparty/openal/core/effectslot.h +++ b/thirdparty/openal/core/effectslot.h @@ -51,6 +51,8 @@ struct 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,12 @@ 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() + DEF_NEWDEL(EffectSlot) }; #endif /* CORE_EFFECTSLOT_H */ diff --git a/thirdparty/openal/core/except.cpp b/thirdparty/openal/core/except.cpp index 07bb410a44..ba4759a39b 100644 --- a/thirdparty/openal/core/except.cpp +++ b/thirdparty/openal/core/except.cpp @@ -11,18 +11,17 @@ 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) { 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(const_cast(mMessage.data()), mMessage.length(), msg, args2); mMessage.pop_back(); } va_end(args2); diff --git a/thirdparty/openal/core/filters/biquad.cpp b/thirdparty/openal/core/filters/biquad.cpp index 470b1cd3c9..a0a62eb8bd 100644 --- a/thirdparty/openal/core/filters/biquad.cpp +++ b/thirdparty/openal/core/filters/biquad.cpp @@ -14,8 +14,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)}; diff --git a/thirdparty/openal/core/filters/splitter.cpp b/thirdparty/openal/core/filters/splitter.cpp index e7f03756f4..983ba36f15 100644 --- a/thirdparty/openal/core/filters/splitter.cpp +++ b/thirdparty/openal/core/filters/splitter.cpp @@ -160,17 +160,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/thirdparty/openal/core/filters/splitter.h b/thirdparty/openal/core/filters/splitter.h index fa15bd509a..e853eb385b 100644 --- a/thirdparty/openal/core/filters/splitter.h +++ b/thirdparty/openal/core/filters/splitter.h @@ -31,12 +31,9 @@ public: /** * 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/thirdparty/openal/core/front_stablizer.h b/thirdparty/openal/core/front_stablizer.h index 3d328a8d55..6825111a7c 100644 --- a/thirdparty/openal/core/front_stablizer.h +++ b/thirdparty/openal/core/front_stablizer.h @@ -10,27 +10,22 @@ 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/thirdparty/openal/core/helpers.cpp b/thirdparty/openal/core/helpers.cpp index 6d0863cac7..99cf009c88 100644 --- a/thirdparty/openal/core/helpers.cpp +++ b/thirdparty/openal/core/helpers.cpp @@ -51,7 +51,7 @@ const PathNamePair &GetProcBinary() if(len == 0) { ERR("Failed to get process name: error %lu\n", GetLastError()); - procbin = al::make_optional(); + procbin.emplace(); return *procbin; } @@ -59,16 +59,15 @@ const PathNamePair &GetProcBinary() if(fullpath.back() != 0) fullpath.push_back(0); + std::replace(fullpath.begin(), fullpath.end(), '/', '\\'); 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)); + procbin.emplace(wstr_to_utf8(fullpath.data()), wstr_to_utf8(al::to_address(sep.base()))); } else - procbin = al::make_optional(std::string{}, wstr_to_utf8(fullpath.data())); + procbin.emplace(std::string{}, wstr_to_utf8(fullpath.data())); TRACE("Got binary: %s, %s\n", procbin->path.c_str(), procbin->fname.c_str()); return *procbin; @@ -285,11 +284,10 @@ const PathNamePair &GetProcBinary() auto sep = std::find(pathname.crbegin(), pathname.crend(), '/'); if(sep != pathname.crend()) - procbin = al::make_optional(std::string(pathname.cbegin(), sep.base()-1), + procbin.emplace(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())); + procbin.emplace(std::string{}, std::string(pathname.cbegin(), pathname.cend())); TRACE("Got binary: \"%s\", \"%s\"\n", procbin->path.c_str(), procbin->fname.c_str()); return *procbin; @@ -497,11 +495,12 @@ 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; diff --git a/thirdparty/openal/core/hrtf.cpp b/thirdparty/openal/core/hrtf.cpp index d4d6981597..d5c7573a18 100644 --- a/thirdparty/openal/core/hrtf.cpp +++ b/thirdparty/openal/core/hrtf.cpp @@ -42,12 +42,28 @@ namespace { struct HrtfEntry { std::string mDispName; std::string mFilename; + + /* GCC warns when it tries to inline this. */ + ~HrtfEntry(); }; +HrtfEntry::~HrtfEntry() = default; struct LoadedHrtf { std::string mFilename; std::unique_ptr mEntry; + + template + LoadedHrtf(T&& name, U&& entry) + : mFilename{std::forward(name)}, 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. @@ -164,8 +180,8 @@ 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)}; @@ -176,8 +192,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,36 +205,37 @@ 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 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 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]{ 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 @@ -232,21 +249,21 @@ void GetHrtfCoeffs(const HrtfStore *Hrtf, float elevation, float azimuth, float }; /* 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{mDelays[idx[0]][0]*blend[0] + mDelays[idx[1]][0]*blend[1] + mDelays[idx[2]][0]*blend[2] + + 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 = mDelays[idx[0]][1]*blend[0] + mDelays[idx[1]][1]*blend[1] + mDelays[idx[2]][1]*blend[2] + + 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; }; @@ -258,7 +275,7 @@ 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, +void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool perHrirMin, const al::span AmbiPoints, const float (*AmbiMatrix)[MaxAmbiChannels], const float XOverFreq, const al::span AmbiOrderHFGain) { @@ -268,11 +285,12 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, uint ldelay, rdelay; }; - const double xover_norm{double{XOverFreq} / Hrtf->sampleRate}; + const double xover_norm{double{XOverFreq} / Hrtf->mSampleRate}; + mChannels[0].mSplitter.init(static_cast(xover_norm)); for(size_t i{0};i < mChannels.size();++i) { const size_t order{AmbiIndex::OrderFromChannel()[i]}; - mChannels[i].mSplitter.init(static_cast(xover_norm)); + mChannels[i].mSplitter = mChannels[0].mSplitter; mChannels[i].mHfScale = AmbiOrderHFGain[order]; } @@ -280,33 +298,26 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, al::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 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]{ 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)); @@ -320,13 +331,12 @@ 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()); max_delay = 0; for(size_t c{0u};c < AmbiPoints.size();++c) { const ConstHrirSpan hrir{impres[c].hrir}; - const uint base_delay{per_hrir_min ? minu(impres[c].ldelay, impres[c].rdelay) : min_delay}; + const uint base_delay{perHrirMin ? minu(impres[c].ldelay, impres[c].rdelay) : min_delay}; const uint ldelay{hrir_delay_round(impres[c].ldelay - base_delay)}; const uint rdelay{hrir_delay_round(impres[c].rdelay - base_delay)}; max_delay = maxu(max_delay, maxu(impres[c].ldelay, impres[c].rdelay) - base_delay); @@ -363,7 +373,7 @@ 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) @@ -371,23 +381,20 @@ std::unique_ptr CreateHrtfStore(uint rate, ushort irSize, 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 + std::unique_ptr Hrtf{}; + if(void *ptr{al_calloc(16, total)}) { + Hrtf.reset(al::construct_at(static_cast(ptr))); InitRef(Hrtf->mRef, 1u); - Hrtf->sampleRate = rate; - Hrtf->irSize = irSize; - Hrtf->fdCount = static_cast(fields.size()); + Hrtf->mSampleRate = rate; + Hrtf->mIrSize = irSize; /* Set up pointers to storage following the main HRTF struct. */ char *base = reinterpret_cast(Hrtf.get()); @@ -408,7 +415,7 @@ std::unique_ptr CreateHrtfStore(uint rate, ushort irSize, auto delays_ = reinterpret_cast(base + offset); offset += sizeof(delays_[0])*irCount; - if(unlikely(offset != total)) + if(offset != total) throw std::runtime_error{"HrtfStore allocation size mismatch"}; /* Copy input data to storage. */ @@ -418,11 +425,13 @@ std::unique_ptr CreateHrtfStore(uint rate, ushort irSize, std::uninitialized_copy_n(delays, irCount, delays_); /* Finally, assign the storage pointers. */ - Hrtf->field = field_; - Hrtf->elev = elev_; - Hrtf->coeffs = coeffs_; - Hrtf->delays = delays_; + Hrtf->mFields = al::as_span(field_, fields.size()); + Hrtf->mElev = elev_; + Hrtf->mCoeffs = coeffs_; + Hrtf->mDelays = delays_; } + else + ERR("Out of memory allocating storage for %s.\n", filename); return Hrtf; } @@ -590,14 +599,14 @@ std::unique_ptr LoadHrtf00(std::istream &data, const char *filename) 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); + return CreateHrtfStore(rate, static_cast(irSize), field, {elevs.data(), elevs.size()}, + coeffs.data(), delays.data(), filename); } std::unique_ptr LoadHrtf01(std::istream &data, const char *filename) { uint rate{readle(data)}; - ushort irSize{readle(data)}; + uint8_t irSize{readle(data)}; ubyte evCount{readle(data)}; if(!data || data.eof()) { @@ -682,7 +691,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) 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()) { @@ -948,7 +957,7 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) 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()) { @@ -1197,7 +1206,9 @@ al::span GetResource(int /*name*/) #else -#include "hrtf_default.h" +constexpr unsigned char hrtf_default[]{ +#include "default_hrtf.txt" +}; al::span GetResource(int name) { @@ -1283,7 +1294,7 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) while(handle != LoadedHrtfs.end() && handle->mFilename == fname) { HrtfStore *hrtf{handle->mEntry.get()}; - if(hrtf && hrtf->sampleRate == devrate) + if(hrtf && hrtf->mSampleRate == devrate) { hrtf->add_ref(); return HrtfStorePtr{hrtf}; @@ -1352,24 +1363,24 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) 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", name.c_str(), 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(), size_t{0}, [](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]); + HrirArray &coeffs = const_cast(hrtf->mCoeffs[i]); for(size_t j{0};j < 2;++j) { std::transform(coeffs.cbegin(), coeffs.cend(), inout[0].begin(), @@ -1384,12 +1395,12 @@ 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)}; + 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(hrtf->mDelays[i][j] * rate_scale) / float{HrirDelayFracOne}}; max_delay = maxf(max_delay, new_delay); new_delays[i][j] = new_delay; @@ -1409,7 +1420,7 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) for(size_t i{0};i < irCount;++i) { - ubyte2 &delays = const_cast(hrtf->delays[i]); + ubyte2 &delays = const_cast(hrtf->mDelays[i]); for(size_t j{0};j < 2;++j) delays[j] = static_cast(float2int(new_delays[i][j]*delay_scale + 0.5f)); } @@ -1417,14 +1428,14 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) /* 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(minf(HrirLength, newIrSize)); + hrtf->mSampleRate = devrate; } 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)}); + hrtf->mSampleRate, hrtf->mIrSize); + handle = LoadedHrtfs.emplace(handle, fname, std::move(hrtf)); return HrtfStorePtr{handle->mEntry.get()}; } @@ -1436,7 +1447,7 @@ 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); diff --git a/thirdparty/openal/core/hrtf.h b/thirdparty/openal/core/hrtf.h index 9cf11efb84..eb18682a52 100644 --- a/thirdparty/openal/core/hrtf.h +++ b/thirdparty/openal/core/hrtf.h @@ -20,8 +20,8 @@ struct HrtfStore { RefCount mRef; - uint sampleRate; - uint irSize; + uint mSampleRate : 24; + uint mIrSize : 8; struct Field { float distance; @@ -30,19 +30,21 @@ 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); void add_ref(); - void release(); + void dec_ref(); DEF_PLACE_NEWDEL() }; @@ -71,7 +73,7 @@ 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, + void build(const HrtfStore *Hrtf, const uint irSize, const bool perHrirMin, const al::span AmbiPoints, const float (*AmbiMatrix)[MaxAmbiChannels], const float XOverFreq, const al::span AmbiOrderHFGain); @@ -84,7 +86,4 @@ 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); - #endif /* CORE_HRTF_H */ diff --git a/thirdparty/openal/core/logging.cpp b/thirdparty/openal/core/logging.cpp index 7ee7ff23a1..ec53e5f6c0 100644 --- a/thirdparty/openal/core/logging.cpp +++ b/thirdparty/openal/core/logging.cpp @@ -7,30 +7,53 @@ #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, ...) { + /* Kind of ugly since string literals are const char arrays with a size + * that includes the null terminator, which we want to exclude from the + * span. + */ + auto prefix = al::as_span("[ALSOFT] (--) ").first<14>(); + switch(level) + { + case LogLevel::Disable: break; + case LogLevel::Error: prefix = al::as_span("[ALSOFT] (EE) ").first<14>(); break; + case LogLevel::Warning: prefix = al::as_span("[ALSOFT] (WW) ").first<14>(); break; + case LogLevel::Trace: prefix = al::as_span("[ALSOFT] (II) ").first<14>(); break; + } + al::vector dynmsg; - char stcmsg[256]; - char *str{stcmsg}; + std::array stcmsg{}; + + char *str{stcmsg.data()}; + auto prefend1 = std::copy_n(prefix.begin(), prefix.size(), stcmsg.begin()); + al::span msg{prefend1, stcmsg.end()}; 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 && static_cast(msglen) >= msg.size()) [[unlikely]] { - dynmsg.resize(static_cast(msglen) + 1u); + dynmsg.resize(static_cast(msglen)+prefix.size() + 1u); + str = dynmsg.data(); - std::vsnprintf(str, dynmsg.size(), fmt, args2); + auto prefend2 = std::copy_n(prefix.begin(), prefix.size(), dynmsg.begin()); + msg = {prefend2, dynmsg.end()}; + + std::vsnprintf(msg.data(), msg.size(), fmt, args2); } va_end(args2); va_end(args); @@ -40,47 +63,14 @@ void al_print(LogLevel level, FILE *logfile, const char *fmt, ...) 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) @@ -97,5 +87,3 @@ void al_print(LogLevel level, FILE *logfile, const char *fmt, ...) __android_log_print(android_severity(level), "openal", "%s", str); #endif } - -#endif diff --git a/thirdparty/openal/core/logging.h b/thirdparty/openal/core/logging.h index 81465929c8..c729ef4e50 100644 --- a/thirdparty/openal/core/logging.h +++ b/thirdparty/openal/core/logging.h @@ -16,25 +16,6 @@ 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) - -#define WARN(...) do { \ - if UNLIKELY(gLogLevel >= LogLevel::Warning) \ - fprintf(gLogFile, "[ALSOFT] (WW) " __VA_ARGS__); \ -} while(0) - -#define ERR(...) do { \ - if UNLIKELY(gLogLevel >= LogLevel::Error) \ - fprintf(gLogFile, "[ALSOFT] (EE) " __VA_ARGS__); \ -} while(0) - -#else - #ifdef __USE_MINGW_ANSI_STDIO [[gnu::format(gnu_printf,3,4)]] #else @@ -42,11 +23,29 @@ extern FILE *gLogFile; #endif void al_print(LogLevel level, FILE *logfile, const char *fmt, ...); -#define TRACE(...) al_print(LogLevel::Trace, gLogFile, "[ALSOFT] (II) " __VA_ARGS__) +#if (!defined(_WIN32) || defined(NDEBUG)) && !defined(__ANDROID__) +#define TRACE(...) do { \ + if(gLogLevel >= LogLevel::Trace) [[unlikely]] \ + al_print(LogLevel::Trace, gLogFile, __VA_ARGS__); \ +} while(0) -#define WARN(...) al_print(LogLevel::Warning, gLogFile, "[ALSOFT] (WW) " __VA_ARGS__) +#define WARN(...) do { \ + if(gLogLevel >= LogLevel::Warning) [[unlikely]] \ + al_print(LogLevel::Warning, gLogFile, __VA_ARGS__); \ +} while(0) -#define ERR(...) al_print(LogLevel::Error, gLogFile, "[ALSOFT] (EE) " __VA_ARGS__) +#define ERR(...) do { \ + if(gLogLevel >= LogLevel::Error) [[unlikely]] \ + al_print(LogLevel::Error, gLogFile, __VA_ARGS__); \ +} while(0) + +#else + +#define TRACE(...) al_print(LogLevel::Trace, gLogFile, __VA_ARGS__) + +#define WARN(...) al_print(LogLevel::Warning, gLogFile, __VA_ARGS__) + +#define ERR(...) al_print(LogLevel::Error, gLogFile, __VA_ARGS__) #endif #endif /* CORE_LOGGING_H */ diff --git a/thirdparty/openal/core/mastering.cpp b/thirdparty/openal/core/mastering.cpp index 998504772b..88a0b5e0be 100644 --- a/thirdparty/openal/core/mastering.cpp +++ b/thirdparty/openal/core/mastering.cpp @@ -66,7 +66,7 @@ float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in) goto found_place; } while(lowerIndex--); lowerIndex = mask; - } while(1); + } while(true); found_place: lowerIndex = (lowerIndex + 1) & mask; @@ -87,10 +87,10 @@ void ShiftSlidingHold(SlidingHold *Hold, const uint n) if(exp_last-exp_begin < 0) { std::transform(exp_begin, std::end(Hold->mExpiries), exp_begin, - std::bind(std::minus<>{}, _1, n)); + [n](auto a){ return a - n; }); exp_begin = std::begin(Hold->mExpiries); } - std::transform(exp_begin, exp_last+1, exp_begin, std::bind(std::minus<>{}, _1, n)); + std::transform(exp_begin, exp_last+1, exp_begin, [n](auto a){ return a - n; }); } @@ -121,7 +121,7 @@ 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 CrestDetector(Compressor *Comp, const uint SamplesToDo) { const float a_crest{Comp->mCrestCoeff}; float y2_peak{Comp->mLastPeakSq}; @@ -155,7 +155,7 @@ void PeakDetector(Compressor *Comp, const uint SamplesToDo) /* Clamp the minimum amplitude to near-zero and convert to logarithm. */ auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; std::transform(side_begin, side_begin+SamplesToDo, side_begin, - [](const float s) -> float { return std::log(maxf(0.000001f, s)); }); + [](auto s) { return std::log(maxf(0.000001f, s)); }); } /* An optional hold can be used to extend the peak detector so it can more @@ -295,7 +295,7 @@ void SignalDelay(Compressor *Comp, const uint SamplesToDo, FloatBufferLine *OutB float *delaybuf{al::assume_aligned<16>(Comp->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); @@ -404,7 +404,7 @@ 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](auto a){ return a * preGain; }); }; std::for_each(OutBuffer, OutBuffer+numChans, apply_gain); } @@ -430,7 +430,7 @@ void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer) float *buffer{al::assume_aligned<16>(input.data())}; const float *gains{al::assume_aligned<16>(&sideChain[0])}; std::transform(gains, gains+SamplesToDo, buffer, buffer, - std::bind(std::multiplies{}, _1, _2)); + [](auto a, auto b){ return a * b; }); }; std::for_each(OutBuffer, OutBuffer+numChans, apply_comp); diff --git a/thirdparty/openal/core/mixer.cpp b/thirdparty/openal/core/mixer.cpp index 4618406bef..066c57bd76 100644 --- a/thirdparty/openal/core/mixer.cpp +++ b/thirdparty/openal/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) { @@ -114,7 +83,7 @@ std::array CalcAmbiCoeffs(const float y, const float z, c } void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain, - const al::span gains) + const al::span gains) { auto ambimap = mix->AmbiMap.cbegin(); diff --git a/thirdparty/openal/core/mixer.h b/thirdparty/openal/core/mixer.h index 309f422498..aa7597bbae 100644 --- a/thirdparty/openal/core/mixer.h +++ b/thirdparty/openal/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); } /** @@ -51,6 +65,18 @@ inline std::array CalcDirectionCoeffs(const float (&dir)[ 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 float (&dir)[3]) +{ + /* Convert from OpenAL coords to Ambisonics. */ + return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2]); +} + /** * CalcAngleCoeffs * @@ -78,24 +104,6 @@ inline std::array CalcAngleCoeffs(const float azimuth, * 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); - } -} + const al::span gains); #endif /* CORE_MIXER_H */ diff --git a/thirdparty/openal/core/mixer/defs.h b/thirdparty/openal/core/mixer/defs.h index ba304f2227..80d9fc7fcd 100644 --- a/thirdparty/openal/core/mixer/defs.h +++ b/thirdparty/openal/core/mixer/defs.h @@ -16,9 +16,10 @@ using uint = unsigned int; using float2 = std::array; -constexpr int MixerFracBits{12}; +constexpr int MixerFracBits{16}; constexpr int MixerFracOne{1 << MixerFracBits}; constexpr int MixerFracMask{MixerFracOne - 1}; +constexpr int MixerFracHalf{MixerFracOne >> 1}; constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */ @@ -67,6 +68,9 @@ float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, uint 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, diff --git a/thirdparty/openal/core/mixer/hrtfbase.h b/thirdparty/openal/core/mixer/hrtfbase.h index 606f9d4ee3..656b9445ec 100644 --- a/thirdparty/openal/core/mixer/hrtfbase.h +++ b/thirdparty/openal/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]}; diff --git a/thirdparty/openal/core/mixer/mixer_c.cpp b/thirdparty/openal/core/mixer/mixer_c.cpp index f3e6aa6ad5..9ac2a9c421 100644 --- a/thirdparty/openal/core/mixer/mixer_c.cpp +++ b/thirdparty/openal/core/mixer/mixer_c.cpp @@ -96,6 +96,37 @@ 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<> @@ -166,35 +197,19 @@ 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()); + 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 = minz(Counter, InSamples.size()); + + MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, + TargetGain, delta, min_len, Counter); } diff --git a/thirdparty/openal/core/mixer/mixer_neon.cpp b/thirdparty/openal/core/mixer/mixer_neon.cpp index a34689269b..5a91da6070 100644 --- a/thirdparty/openal/core/mixer/mixer_neon.cpp +++ b/thirdparty/openal/core/mixer/mixer_neon.cpp @@ -56,6 +56,78 @@ 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<> @@ -233,75 +305,18 @@ void Mix_(const al::span InSamples, const al::span(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 = minz(Counter, InSamples.size()); + const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len; + + MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len, + aligned_len, Counter); } diff --git a/thirdparty/openal/core/mixer/mixer_sse.cpp b/thirdparty/openal/core/mixer/mixer_sse.cpp index fc4d8cc63c..1b0d1386c2 100644 --- a/thirdparty/openal/core/mixer/mixer_sse.cpp +++ b/thirdparty/openal/core/mixer/mixer_sse.cpp @@ -40,39 +40,110 @@ 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])}; + const __m128 coeffs{_mm_load_ps(Coeffs[i].data())}; + __m128 vals{_mm_load_ps(Values[i].data())}; vals = MLA4(vals, lrlr, coeffs); - _mm_store_ps(&Values[i][0], vals); + _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 = 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 = 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<> @@ -199,74 +270,18 @@ void Mix_(const al::span InSamples, const al::span(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 = minz(Counter, InSamples.size()); + const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len; + + MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len, + aligned_len, Counter); } diff --git a/thirdparty/openal/core/rtkit.cpp b/thirdparty/openal/core/rtkit.cpp index 9210220ef8..ff944ebf2b 100644 --- a/thirdparty/openal/core/rtkit.cpp +++ b/thirdparty/openal/core/rtkit.cpp @@ -40,7 +40,11 @@ #include #include #include +#ifdef __linux__ #include +#elif defined(__FreeBSD__) +#include +#endif namespace dbus { diff --git a/thirdparty/openal/core/uhjfilter.cpp b/thirdparty/openal/core/uhjfilter.cpp index 6cdc29446d..ac5f2d4094 100644 --- a/thirdparty/openal/core/uhjfilter.cpp +++ b/thirdparty/openal/core/uhjfilter.cpp @@ -12,12 +12,71 @@ #include "phase_shifter.h" +UhjQualityType UhjDecodeQuality{UhjQualityType::Default}; +UhjQualityType UhjEncodeQuality{UhjQualityType::Default}; + + namespace { -const PhaseShifterT PShift{}; +const PhaseShifterT PShiftLq{}; +const PhaseShifterT PShiftHq{}; + +template +struct GetPhaseShifter; +template<> +struct GetPhaseShifter { static auto& Get() noexcept { return PShiftLq; } }; +template<> +struct GetPhaseShifter { static auto& Get() noexcept { return PShiftHq; } }; + + +constexpr float square(float x) noexcept +{ return x*x; } + +/* Filter coefficients for the 'base' all-pass IIR, which applies a frequency- + * dependent phase-shift of N degrees. The output of the filter requires a 1- + * sample delay. + */ +constexpr std::array Filter1Coeff{{ + square(0.6923878f), square(0.9360654322959f), square(0.9882295226860f), + square(0.9987488452737f) +}}; +/* Filter coefficients for the offset all-pass IIR, which applies a frequency- + * dependent phase-shift of N+90 degrees. + */ +constexpr std::array Filter2Coeff{{ + square(0.4021921162426f), square(0.8561710882420f), square(0.9722909545651f), + square(0.9952884791278f) +}}; } // namespace +void UhjAllPassFilter::process(const al::span coeffs, + const al::span src, const size_t forwardSamples, float *RESTRICT dst) +{ + float z[4][2]{{state[0].z[0], state[0].z[1]}, {state[1].z[0], state[1].z[1]}, + {state[2].z[0], state[2].z[1]}, {state[3].z[0], state[3].z[1]}}; + + auto proc_sample = [&z,coeffs](float x) noexcept -> float + { + for(size_t i{0};i < 4;++i) + { + const float y{x*coeffs[i] + z[i][0]}; + z[i][0] = z[i][1]; + z[i][1] = y*coeffs[i] - x; + x = y; + } + return x; + }; + auto dstiter = std::transform(src.begin(), src.begin()+forwardSamples, dst, proc_sample); + for(size_t i{0};i < 4;++i) + { + state[i].z[0] = z[i][0]; + state[i].z[1] = z[i][1]; + } + + std::transform(src.begin()+forwardSamples, src.end(), dstiter, proc_sample); +} + /* Encoding UHJ from B-Format is done as: * @@ -36,55 +95,136 @@ const PhaseShifterT PShift{}; * impulse with the desired shift. */ -void UhjEncoder::encode(float *LeftOut, float *RightOut, +template +void UhjEncoder::encode(float *LeftOut, float *RightOut, const al::span InSamples, const size_t SamplesToDo) { - ASSUME(SamplesToDo > 0); + const auto &PShift = GetPhaseShifter::Get(); - float *RESTRICT left{al::assume_aligned<16>(LeftOut)}; - float *RESTRICT right{al::assume_aligned<16>(RightOut)}; + 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])}; - /* Combine the previously delayed S/D signal with the input. Include any - * existing direct signal with it. - */ + 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 */ - 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]; + for(size_t i{0};i < SamplesToDo;++i) + mS[i] = 0.9396926f*mW[i] + 0.1855740f*mX[i]; - /* 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]; - - /* 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, + /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */ + std::transform(winput, winput+SamplesToDo, xinput, mWX.begin() + sWXInOffset, [](const float w, const float x) noexcept -> float { return -0.3420201f*w + 0.5098604f*x; }); - std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory.size(), mWXHistory.begin()); - PShift.processAccum({mD.data(), SamplesToDo}, mTemp.data()); + PShift.process({mD.data(), SamplesToDo}, mWX.data()); - /* Left = (S + D)/2.0 */ - 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; + /* 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(mS.cbegin()+SamplesToDo, mS.cbegin()+SamplesToDo+sFilterDelay, mS.begin()); - std::copy(mD.cbegin()+SamplesToDo, mD.cbegin()+SamplesToDo+sFilterDelay, mD.begin()); + std::copy(mW.cbegin()+SamplesToDo, mW.cbegin()+SamplesToDo+sFilterDelay, mW.begin()); + std::copy(mX.cbegin()+SamplesToDo, mX.cbegin()+SamplesToDo+sFilterDelay, mX.begin()); + std::copy(mY.cbegin()+SamplesToDo, mY.cbegin()+SamplesToDo+sFilterDelay, mY.begin()); + std::copy(mWX.cbegin()+SamplesToDo, mWX.cbegin()+SamplesToDo+sWXInOffset, mWX.begin()); + + /* Apply a delay to the existing output to align with the input delay. */ + auto *delayBuffer = mDirectDelay.data(); + 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) [[likely]] + { + 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); + + 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 */ + 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}, SamplesToDo, mS.data()+1); + mS[0] = mDelayWX; mDelayWX = mS[SamplesToDo]; + + /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mWX. */ + std::transform(winput, winput+SamplesToDo, xinput, mTemp.begin(), + [](const float w, const float x) noexcept { return -0.3420201f*w + 0.5098604f*x; }); + mFilter2WX.process(Filter2Coeff, {mTemp.data(), SamplesToDo}, SamplesToDo, mWX.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}, SamplesToDo, 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 + mTemp[i]; + + mFilter1Direct[1].process(Filter1Coeff, {RightOut, SamplesToDo}, SamplesToDo, 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 +241,14 @@ void UhjEncoder::encode(float *LeftOut, float *RightOut, * 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, +template +void UhjDecoder::decode(const al::span samples, const size_t samplesToDo, const size_t forwardSamples) { + static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); + + const auto &PShift = GetPhaseShifter::Get(); + ASSUME(samplesToDo > 0); { @@ -112,15 +257,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,7 +275,7 @@ 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()); PShift.process({xoutput, samplesToDo}, mTemp.data()); @@ -144,7 +289,7 @@ 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(mS.cbegin(), samplesToDo+sInputPadding, tmpiter); std::copy_n(mTemp.cbegin()+forwardSamples, mSHistory.size(), mSHistory.begin()); PShift.process({youtput, samplesToDo}, mTemp.data()); @@ -161,6 +306,75 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesToDo } } +void UhjDecoderIIR::decode(const al::span samples, const size_t samplesToDo, + const size_t forwardSamples) +{ + 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;++i) + mS[i] = left[i] + right[i]; + + /* D = Left - Right */ + for(size_t i{0};i < samplesToDo;++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()+samplesToDo, youtput, mTemp.begin(), + [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); + mFilter2DT.process(Filter2Coeff, {mTemp.data(), samplesToDo}, forwardSamples, xoutput); + + /* Apply filter1 to S and store in mTemp. */ + mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo-1}, forwardSamples, mTemp.data()+1); + mTemp[0] = mDelayS; mDelayS = mTemp[forwardSamples]; + + /* 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 youtput. */ + std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, mTemp.begin(), + [](const float d, const float t) noexcept { return 0.795968f*d - 0.676392f*t; }); + mFilter1DT.process(Filter1Coeff, {mTemp.data(), samplesToDo-1}, forwardSamples, youtput+1); + youtput[0] = mDelayDT; mDelayDT = youtput[forwardSamples]; + + /* Precompute j*S and store in mTemp. */ + mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, forwardSamples, mTemp.data()); + + /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ + for(size_t i{0};i < samplesToDo;++i) + youtput[i] = youtput[i] + 0.186633f*mTemp[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-1}, forwardSamples, mTemp.data()+1); + mTemp[0] = mDelayQ; mDelayQ = mTemp[forwardSamples]; + + /* Z = 1.023332*Q */ + for(size_t i{0};i < samplesToDo;++i) + zoutput[i] = 1.023332f*mTemp[i]; + } +} + /* Super Stereo processing is done as: * @@ -174,27 +388,33 @@ 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 UhjStereoDecoder::decode(const al::span samples, const size_t samplesToDo, +template +void UhjStereoDecoder::decode(const al::span samples, const size_t samplesToDo, const size_t forwardSamples) { + static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); + + const auto &PShift = GetPhaseShifter::Get(); + 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 || forwardSamples == 0) { - 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 { @@ -206,7 +426,7 @@ void UhjStereoDecoder::decode(const al::span samples, const size_t sampl mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi); fi += 1.0f; } - for(;i < samplesToDo+sFilterDelay;++i) + for(;i < samplesToDo+sInputPadding;++i) mD[i] = (left[i] - right[i]) * wtarget; mCurrentWidth = wtarget; } @@ -218,7 +438,7 @@ void UhjStereoDecoder::decode(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(mD.cbegin(), samplesToDo+sInputPadding, tmpiter); std::copy_n(mTemp.cbegin()+forwardSamples, mDTHistory.size(), mDTHistory.begin()); PShift.process({xoutput, samplesToDo}, mTemp.data()); @@ -231,7 +451,7 @@ void UhjStereoDecoder::decode(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(mS.cbegin(), samplesToDo+sInputPadding, tmpiter); std::copy_n(mTemp.cbegin()+forwardSamples, mSHistory.size(), mSHistory.begin()); PShift.process({youtput, samplesToDo}, mTemp.data()); @@ -239,3 +459,83 @@ void UhjStereoDecoder::decode(const al::span samples, const size_t sampl 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 size_t forwardSamples) +{ + 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;++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 || forwardSamples == 0) + { + for(size_t i{0};i < samplesToDo;++i) + mD[i] = (left[i] - right[i]) * wcurrent; + mCurrentWidth = wcurrent; + } + else + { + const float wstep{(wtarget - wcurrent) / static_cast(forwardSamples)}; + float fi{0.0f}; + size_t i{0}; + for(;i < forwardSamples;++i) + { + mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi); + fi += 1.0f; + } + for(;i < samplesToDo;++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-1}, forwardSamples, mTemp.data()+1); + mTemp[0] = mDelayS; mDelayS = mTemp[forwardSamples]; + + /* Precompute j*D and store in xoutput. */ + mFilter2D.process(Filter2Coeff, {mD.data(), samplesToDo}, forwardSamples, 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. */ + mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, forwardSamples, youtput); + + /* Apply filter1 to D and store in mTemp. */ + mFilter1D.process(Filter1Coeff, {mD.data(), samplesToDo-1}, forwardSamples, mTemp.data()+1); + mTemp[0] = mDelayD; mDelayD = mTemp[forwardSamples]; + + /* 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]; +} + + +template struct UhjEncoder; +template struct UhjDecoder; +template struct UhjStereoDecoder; + +template struct UhjEncoder; +template struct UhjDecoder; +template struct UhjStereoDecoder; diff --git a/thirdparty/openal/core/uhjfilter.h b/thirdparty/openal/core/uhjfilter.h index eeabb6d2df..0cdd0f901f 100644 --- a/thirdparty/openal/core/uhjfilter.h +++ b/thirdparty/openal/core/uhjfilter.h @@ -9,7 +9,117 @@ #include "resampler_limits.h" +static constexpr size_t UhjLength256{256}; +static constexpr size_t UhjLength512{512}; + +enum class UhjQualityType : uint8_t { + IIR = 0, + FIR256, + FIR512, + Default = IIR +}; + +extern UhjQualityType UhjDecodeQuality; +extern UhjQualityType UhjEncodeQuality; + + +struct UhjAllPassFilter { + struct AllPassState { + /* Last two delayed components for direct form II. */ + float z[2]; + }; + std::array state; + + void process(const al::span coeffs, const al::span src, + const size_t forwardSamples, float *RESTRICT dst); +}; + + +struct UhjEncoderBase { + virtual ~UhjEncoderBase() = default; + + virtual 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). + */ + virtual void encode(float *LeftOut, float *RightOut, + const al::span InSamples, const size_t SamplesToDo) = 0; +}; + +template +struct UhjEncoder final : public UhjEncoderBase { + static constexpr size_t sFilterDelay{N/2}; + + /* 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 FIR filter. New samples should be + * written to index sFilterDelay*2 - 1. + */ + static constexpr size_t sWXInOffset{sFilterDelay*2 - 1}; + alignas(16) std::array mWX{}; + + alignas(16) std::array,2> mDirectDelay{}; + + 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 size_t SamplesToDo) override; + + DEF_NEWDEL(UhjEncoder) +}; + +struct UhjEncoderIIR final : public UhjEncoderBase { + static constexpr 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{}; + + 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 size_t SamplesToDo) override; + + DEF_NEWDEL(UhjEncoderIIR) +}; + + struct DecoderBase { + static constexpr size_t sMaxPadding{256}; + + /* For 2-channel UHJ, shelf filters should use these LF responses. */ + static constexpr float sWLFScale{0.661f}; + static constexpr float sXYLFScale{1.293f}; + virtual ~DecoderBase() = default; virtual void decode(const al::span samples, const size_t samplesToDo, @@ -20,53 +130,21 @@ struct DecoderBase { * calls to decode, with valid values being between 0...0.7. */ float mWidthControl{0.593f}; - - float mCurrentWidth{-1.0f}; }; +template +struct UhjDecoder final : public DecoderBase { + /* The number of extra sample frames needed for input. */ + static constexpr size_t sInputPadding{N/2}; -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}; -}; + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mT{}; -struct UhjEncoder : public UhjFilterBase { - /* Delays and processing storage for the unfiltered signal. */ - alignas(16) std::array mS{}; - alignas(16) std::array mD{}; + alignas(16) std::array mDTHistory{}; + alignas(16) std::array mSHistory{}; - /* History for the FIR filter. */ - alignas(16) std::array mWXHistory{}; - - alignas(16) std::array mTemp{}; - - /** - * 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 size_t SamplesToDo); - - DEF_NEWDEL(UhjEncoder) -}; - - -struct UhjDecoder : public DecoderBase, public UhjFilterBase { - /* 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 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{}; + alignas(16) std::array mTemp{}; /** * Decodes a 3- or 4-channel UHJ signal into a B-Format signal with FuMa @@ -82,7 +160,45 @@ struct UhjDecoder : public DecoderBase, public UhjFilterBase { DEF_NEWDEL(UhjDecoder) }; -struct UhjStereoDecoder : public UhjDecoder { +struct UhjDecoderIIR final : public DecoderBase { + /* FIXME: These IIR decoder filters actually have a 1-sample delay on the + * non-filtered components, which is not reflected in the source latency + * value. sInputPadding is 0, however, because it doesn't need any extra + * input samples as long as 'forwardSamples' is less than 'samplesToDo'. + */ + static constexpr size_t sInputPadding{0}; + + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mTemp{}; + float mDelayS{}, mDelayDT{}, mDelayQ{}; + + UhjAllPassFilter mFilter1S; + UhjAllPassFilter mFilter2DT; + UhjAllPassFilter mFilter1DT; + UhjAllPassFilter mFilter2S; + UhjAllPassFilter mFilter1Q; + + void decode(const al::span samples, const size_t samplesToDo, + const size_t forwardSamples) override; + + DEF_NEWDEL(UhjDecoderIIR) +}; + +template +struct UhjStereoDecoder final : public DecoderBase { + static constexpr 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 * signal with FuMa channel ordering and UHJ scaling. The samples span @@ -95,4 +211,25 @@ struct UhjStereoDecoder : public UhjDecoder { DEF_NEWDEL(UhjStereoDecoder) }; +struct UhjStereoDecoderIIR final : public DecoderBase { + static constexpr size_t sInputPadding{0}; + + float mCurrentWidth{-1.0f}; + + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mTemp{}; + float mDelayS{}, mDelayD{}; + + UhjAllPassFilter mFilter1S; + UhjAllPassFilter mFilter2D; + UhjAllPassFilter mFilter1D; + UhjAllPassFilter mFilter2S; + + void decode(const al::span samples, const size_t samplesToDo, + const size_t forwardSamples) override; + + DEF_NEWDEL(UhjStereoDecoderIIR) +}; + #endif /* CORE_UHJFILTER_H */ diff --git a/thirdparty/openal/core/voice.cpp b/thirdparty/openal/core/voice.cpp index ed6c9bf8da..972628a534 100644 --- a/thirdparty/openal/core/voice.cpp +++ b/thirdparty/openal/core/voice.cpp @@ -55,11 +55,12 @@ static_assert(!(sizeof(DeviceBase::MixerBufferLine)&15), "DeviceBase::MixerBufferLine must be a multiple of 16 bytes"); static_assert(!(MaxResamplerEdge&3), "MaxResamplerEdge is not a multiple of 4"); -Resampler ResamplerDefault{Resampler::Linear}; +Resampler ResamplerDefault{Resampler::Cubic}; namespace { using uint = unsigned int; +using namespace std::chrono; using HrtfMixerFunc = void(*)(const float *InSamples, float2 *AccumSamples, const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t BufferSize); @@ -70,7 +71,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)) @@ -151,7 +165,8 @@ void Voice::InitMixer(al::optional resampler) ResamplerDefault = iter->resampler; } - MixSamples = SelectMixer(); + MixSamplesOut = SelectMixer(); + MixSamplesOne = SelectMixerOne(); MixHrtfBlendSamples = SelectHrtfBlendMixer(); MixHrtfSamples = SelectHrtfMixer(); } @@ -245,41 +260,46 @@ void LoadSamples(const al::span dstSamples, const size_t dstOffset, cons #undef HANDLE_FMT } -void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, +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 srcStep, size_t samplesLoaded, const size_t samplesToLoad, + const al::span voiceSamples) { - const uint loopStart{buffer->mLoopStart}; - const uint loopEnd{buffer->mLoopEnd}; - ASSUME(loopEnd > loopStart); + const size_t loopStart{buffer->mLoopStart}; + const size_t loopEnd{buffer->mLoopEnd}; /* If current pos is beyond the loop range, do not loop */ if(!bufferLoopItem || dataPosInt >= loopEnd) { - /* 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); + bufferLoopItem = nullptr; - if(const size_t toFill{samplesToLoad - remaining}) + /* Load what's left to play from the buffer */ + const size_t remaining{minz(samplesToLoad-samplesLoaded, buffer->mSampleLen-dataPosInt)}; + LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, dataPosInt, sampleType, + sampleChannels, srcStep, remaining); + samplesLoaded += remaining; + + if(const size_t toFill{samplesToLoad - samplesLoaded}) { for(auto *chanbuffer : voiceSamples) { - auto srcsamples = chanbuffer + remaining - 1; + auto srcsamples = chanbuffer + samplesLoaded - 1; std::fill_n(srcsamples + 1, toFill, *srcsamples); } } } else { + ASSUME(loopEnd > 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{minz(samplesToLoad-samplesLoaded, loopEnd-dataPosInt)}; + LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, dataPosInt, sampleType, + sampleChannels, srcStep, remaining); + samplesLoaded += remaining; /* Load repeats of the loop to fill the buffer. */ - const auto loopSize = static_cast(loopEnd - loopStart); - size_t samplesLoaded{remaining}; + const size_t loopSize{loopEnd - loopStart}; while(const size_t toFill{minz(samplesToLoad - samplesLoaded, loopSize)}) { LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, loopStart, sampleType, @@ -291,29 +311,30 @@ void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, 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) + size_t samplesLoaded, const size_t samplesToLoad, const 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); + const size_t remaining{minz(samplesToLoad-samplesLoaded, numCallbackSamples)}; + LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, 0, sampleType, sampleChannels, + srcStep, remaining); + samplesLoaded += remaining; - if(const size_t toFill{samplesToLoad - remaining}) + if(const size_t toFill{samplesToLoad - samplesLoaded}) { for(auto *chanbuffer : voiceSamples) { - auto srcsamples = chanbuffer + remaining - 1; - std::fill_n(srcsamples + 1, toFill, *srcsamples); + auto srcsamples = chanbuffer + remaining; + std::fill_n(srcsamples, toFill, *(srcsamples-1)); } } } 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) + const size_t srcStep, size_t samplesLoaded, const size_t samplesToLoad, + const al::span voiceSamples) { /* Crawl the buffer queue to fill in the temp buffer */ - size_t samplesLoaded{0}; while(buffer && samplesLoaded != samplesToLoad) { if(dataPosInt >= buffer->mSampleLen) @@ -338,12 +359,10 @@ void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *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; + auto srcsamples = chanbuffer + samplesLoaded; + std::fill_n(srcsamples, toFill, *(srcsamples-1)); } } } @@ -362,7 +381,7 @@ void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &par std::begin(HrtfSamples)); std::copy_n(samples, DstBufferSize, src_iter); /* Copy the last used samples back into the history buffer for later. */ - if(likely(IsPlaying)) + if(IsPlaying) [[likely]] std::copy_n(std::begin(HrtfSamples) + DstBufferSize, parms.Hrtf.History.size(), parms.Hrtf.History.begin()); @@ -453,19 +472,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{}; 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 +498,33 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo return; } - DeviceBase *Device{Context->mDevice}; - const uint NumSends{Device->NumAuxSends}; + uint Counter{mFlags.test(VoiceIsFading) ? minu(SamplesToDo, 64u) : 0u}; + uint OutPos{0u}; - ResamplerFunc Resample{(increment == MixerFracOne && DataPosFrac == 0) ? - Resample_ : mResampler}; + /* Check if we're doing a delayed start, and we start in this update. */ + if(mStartTime > deviceTime) + { + /* 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); + } - uint Counter{mFlags.test(VoiceIsFading) ? SamplesToDo : 0}; if(!Counter) { /* No fading, just overwrite the old/current params. */ @@ -504,8 +547,6 @@ 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()}; @@ -514,9 +555,10 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo std::transform(Device->mSampleData.end() - mChans.size(), Device->mSampleData.end(), MixingSamples.begin(), offset_bufferline); + const ResamplerFunc Resample{(increment == MixerFracOne && DataPosFrac == 0) ? + Resample_ : mResampler}; const uint PostPadding{MaxResamplerEdge + mDecoderPadding}; uint buffers_done{0u}; - uint OutPos{0u}; do { /* Figure out how many buffer samples will be needed */ uint DstBufferSize{SamplesToDo - OutPos}; @@ -563,14 +605,15 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo /* If the voice is stopping, only one mixing iteration will * be done, so ensure it fades out completely this mix. */ - if(unlikely(vstate == Stopping)) + if(vstate == Stopping) [[unlikely]] Counter = std::min(Counter, DstBufferSize); } ASSUME(DstBufferSize > 0); } } - if(unlikely(!BufferListItem)) + float **voiceSamples{}; + if(!BufferListItem) [[unlikely]] { const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits}; auto prevSamples = mPrevSamples.data(); @@ -603,15 +646,29 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo std::copy_n(prevSamples->data(), MaxResamplerEdge, chanbuffer-MaxResamplerEdge); ++prevSamples; } + + size_t samplesLoaded{0}; + if(DataPosInt < 0) [[unlikely]] + { + if(static_cast(-DataPosInt) >= SrcBufferSize) + goto skip_mix; + + samplesLoaded = static_cast(-DataPosInt); + for(auto *chanbuffer : MixingSamples) + std::fill_n(chanbuffer, samplesLoaded, 0.0f); + } + const uint DataPosUInt{static_cast(maxi(DataPosInt, 0))}; + if(mFlags.test(VoiceIsStatic)) - LoadBufferStatic(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, - mFmtChannels, mFrameStep, SrcBufferSize, MixingSamples); + LoadBufferStatic(BufferListItem, BufferLoopItem, DataPosUInt, mFmtType, + mFmtChannels, mFrameStep, samplesLoaded, SrcBufferSize, MixingSamples); else if(mFlags.test(VoiceIsCallback)) { - if(!mFlags.test(VoiceCallbackStopped) && SrcBufferSize > mNumCallbackSamples) + const size_t remaining{SrcBufferSize - samplesLoaded}; + if(!mFlags.test(VoiceCallbackStopped) && remaining > mNumCallbackSamples) { const size_t byteOffset{mNumCallbackSamples*mFrameSize}; - const size_t needBytes{SrcBufferSize*mFrameSize - byteOffset}; + const size_t needBytes{remaining*mFrameSize - byteOffset}; const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData, &BufferListItem->mSamples[byteOffset], static_cast(needBytes))}; @@ -623,29 +680,29 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo mNumCallbackSamples += static_cast(gotBytes) / mFrameSize; } else - mNumCallbackSamples = SrcBufferSize; + mNumCallbackSamples = static_cast(remaining); } LoadBufferCallback(BufferListItem, mNumCallbackSamples, mFmtType, mFmtChannels, - mFrameStep, SrcBufferSize, MixingSamples); + mFrameStep, samplesLoaded, SrcBufferSize, MixingSamples); } else - LoadBufferQueue(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, mFmtChannels, - mFrameStep, SrcBufferSize, MixingSamples); + LoadBufferQueue(BufferListItem, BufferLoopItem, DataPosUInt, mFmtType, mFmtChannels, + mFrameStep, samplesLoaded, SrcBufferSize, MixingSamples); const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits}; if(mDecoder) { SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerEdge; mDecoder->decode(MixingSamples, SrcBufferSize, - likely(vstate == Playing) ? srcOffset : 0); + (vstate == Playing) ? srcOffset : 0); } + /* Store the last source samples used for next time. */ - if(likely(vstate == Playing)) + if(vstate == Playing) [[likely]] { 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; @@ -653,7 +710,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo } } - auto voiceSamples = MixingSamples.begin(); + voiceSamples = MixingSamples.begin(); for(auto &chandata : mChans) { /* Resample, then apply ambisonic upsampling as needed. */ @@ -674,13 +731,13 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo if(mFlags.test(VoiceHasHrtf)) { - const float TargetGain{parms.Hrtf.Target.Gain * likely(vstate == Playing)}; + const float TargetGain{parms.Hrtf.Target.Gain * (vstate == Playing)}; DoHrtfMix(samples, DstBufferSize, parms, TargetGain, Counter, OutPos, (vstate == Playing), Device); } else { - const float *TargetGains{likely(vstate == Playing) ? parms.Gains.Target.data() + const float *TargetGains{(vstate == Playing) ? parms.Gains.Target.data() : SilentTarget.data()}; if(mFlags.test(VoiceHasNfc)) DoNfcMix({samples, DstBufferSize}, mDirect.Buffer.data(), parms, @@ -700,14 +757,15 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo 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); } } + skip_mix: /* If the voice is stopping, we're now done. */ - if(unlikely(vstate == Stopping)) + if(vstate == Stopping) [[unlikely]] break; /* Update positions */ @@ -719,27 +777,31 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo OutPos += DstBufferSize; Counter = maxu(DstBufferSize, Counter) - DstBufferSize; - if(unlikely(!BufferListItem)) - { - /* Do nothing extra when there's no buffers. */ - } - else if(mFlags.test(VoiceIsStatic)) + /* Do nothing extra when there's no buffers, or if the voice position + * is still negative. + */ + if(!BufferListItem || DataPosInt < 0) [[unlikely]] + continue; + + 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; @@ -767,7 +829,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo { /* Handle streaming source */ do { - if(BufferListItem->mSampleLen > DataPosInt) + if(BufferListItem->mSampleLen > static_cast(DataPosInt)) break; DataPosInt -= BufferListItem->mSampleLen; @@ -782,7 +844,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo mFlags.set(VoiceIsFading); /* Don't update positions and buffers if we were stopping. */ - if(unlikely(vstate == Stopping)) + if(vstate == Stopping) [[unlikely]] { mPlayState.store(Stopped, std::memory_order_release); return; @@ -803,8 +865,8 @@ 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(AsyncEvent::BufferCompleted)) { RingBuffer *ring{Context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); @@ -824,7 +886,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(AsyncEvent::SourceStateChange)) SendSourceStoppedEvent(Context, SourceID); } } @@ -836,7 +898,7 @@ void Voice::prepare(DeviceBase *device) */ uint num_channels{(mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 3 : ChannelsFromFmt(mFmtChannels, minu(mAmbiOrder, device->mAmbiOrder))}; - if(unlikely(num_channels > device->mSampleData.size())) + if(num_channels > device->mSampleData.size()) [[unlikely]] { ERR("Unexpected channel count: %u (limit: %zu, %d:%d)\n", num_channels, device->mSampleData.size(), mFmtChannels, mAmbiOrder); @@ -852,20 +914,43 @@ void Voice::prepare(DeviceBase *device) mPrevSamples.reserve(maxu(2, num_channels)); mPrevSamples.resize(num_channels); + mDecoder = nullptr; + mDecoderPadding = 0; if(mFmtChannels == FmtSuperStereo) { - mDecoder = std::make_unique(); - mDecoderPadding = UhjStereoDecoder::sFilterDelay; + 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 if(IsUHJ(mFmtChannels)) { - mDecoder = std::make_unique(); - mDecoderPadding = UhjDecoder::sFilterDelay; - } - else - { - mDecoder = nullptr; - mDecoderPadding = 0; + 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 @@ -876,48 +961,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 = UhjDecoder::sWLFScale; - mChans[1].mAmbiLFScale = UhjDecoder::sXYLFScale; - mChans[2].mAmbiLFScale = UhjDecoder::sXYLFScale; - } - 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)}; @@ -930,9 +985,31 @@ void Voice::prepare(DeviceBase *device) chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); } - mChans[0].mAmbiLFScale = UhjDecoder::sWLFScale; - mChans[1].mAmbiLFScale = UhjDecoder::sXYLFScale; - mChans[2].mAmbiLFScale = UhjDecoder::sXYLFScale; + 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) + { + const uint8_t *OrderFromChan{Is2DAmbisonic(mFmtChannels) ? + AmbiIndex::OrderFrom2DChannel().data() : AmbiIndex::OrderFromChannel().data()}; + 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/thirdparty/openal/core/voice.h b/thirdparty/openal/core/voice.h index 25560cb491..df0c8c9e53 100644 --- a/thirdparty/openal/core/voice.h +++ b/thirdparty/openal/core/voice.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -51,7 +52,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}; +constexpr uint MaxPostVoiceLoad{MaxResamplerEdge + DecoderBase::sMaxPadding}; enum { @@ -85,8 +86,8 @@ struct SendParams { BiquadFilter HighPass; struct { - std::array Current; - std::array Target; + std::array Current; + std::array Target; } Gains; }; @@ -197,7 +198,7 @@ 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; @@ -209,6 +210,8 @@ struct Voice { */ std::atomic mLoopBuffer; + std::chrono::nanoseconds mStartTime{}; + /* Properties for the attached buffer(s). */ FmtChannels mFmtChannels; FmtType mFmtType; @@ -262,7 +265,8 @@ 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); diff --git a/thirdparty/openal/examples/common/alhelpers.c b/thirdparty/openal/examples/common/alhelpers.c index 1d498ffa61..6627f70420 100644 --- a/thirdparty/openal/examples/common/alhelpers.c +++ b/thirdparty/openal/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/thirdparty/openal/hrtf/Default HRTF.mhr b/thirdparty/openal/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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 \ + 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( \ diff --git a/thirdparty/pugixml/LICENSE.md b/thirdparty/pugixml/LICENSE.md index 2503b81ebc..63042bd91b 100644 --- a/thirdparty/pugixml/LICENSE.md +++ b/thirdparty/pugixml/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2006-2020 Arseny Kapoulkine +Copyright (c) 2006-2022 Arseny Kapoulkine Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/thirdparty/pugixml/README.md b/thirdparty/pugixml/README.md index 2982ec2c38..1d49def009 100644 --- a/thirdparty/pugixml/README.md +++ b/thirdparty/pugixml/README.md @@ -1,4 +1,4 @@ -pugixml [![Build Status](https://travis-ci.org/zeux/pugixml.svg?branch=master)](https://travis-ci.org/zeux/pugixml) [![Build status](https://ci.appveyor.com/api/projects/status/9hdks1doqvq8pwe7/branch/master?svg=true)](https://ci.appveyor.com/project/zeux/pugixml) [![codecov.io](https://codecov.io/github/zeux/pugixml/coverage.svg?branch=master)](https://codecov.io/github/zeux/pugixml?branch=master) ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) +pugixml [![Actions Status](https://github.com/zeux/pugixml/workflows/build/badge.svg)](https://github.com/zeux/pugixml/actions) [![Build status](https://ci.appveyor.com/api/projects/status/9hdks1doqvq8pwe7/branch/master?svg=true)](https://ci.appveyor.com/project/zeux/pugixml) [![codecov.io](https://codecov.io/github/zeux/pugixml/coverage.svg?branch=master)](https://codecov.io/github/zeux/pugixml?branch=master) ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) ======= pugixml is a C++ XML processing library, which consists of a DOM-like interface with rich traversal/modification diff --git a/thirdparty/pugixml/pugiconfig.hpp b/thirdparty/pugixml/pugiconfig.hpp index 0713b0efbb..88b2f2aee0 100644 --- a/thirdparty/pugixml/pugiconfig.hpp +++ b/thirdparty/pugixml/pugiconfig.hpp @@ -1,5 +1,5 @@ /** - * pugixml parser - version 1.12 + * pugixml parser - version 1.13 * -------------------------------------------------------- * Copyright (C) 2006-2022, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://pugixml.org/ diff --git a/thirdparty/pugixml/pugixml.cpp b/thirdparty/pugixml/pugixml.cpp index d76d6ec675..f316b29993 100644 --- a/thirdparty/pugixml/pugixml.cpp +++ b/thirdparty/pugixml/pugixml.cpp @@ -1,5 +1,5 @@ /** - * pugixml parser - version 1.12 + * pugixml parser - version 1.13 * -------------------------------------------------------- * Copyright (C) 2006-2022, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://pugixml.org/ @@ -143,6 +143,8 @@ using std::memset; # define PUGI__SNPRINTF(buf, ...) snprintf(buf, sizeof(buf), __VA_ARGS__) #elif defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 # define PUGI__SNPRINTF(buf, ...) _snprintf_s(buf, _countof(buf), _TRUNCATE, __VA_ARGS__) +#elif defined(__APPLE__) && __clang_major__ >= 14 // Xcode 14 marks sprintf as deprecated while still using C++98 by default +# define PUGI__SNPRINTF(buf, fmt, arg1, arg2) snprintf(buf, sizeof(buf), fmt, arg1, arg2) #else # define PUGI__SNPRINTF sprintf #endif @@ -1361,12 +1363,14 @@ PUGI__NS_BEGIN child->parent = parent; - if (node->next_sibling) - node->next_sibling->prev_sibling_c = child; + xml_node_struct* next = node->next_sibling; + + if (next) + next->prev_sibling_c = child; else parent->first_child->prev_sibling_c = child; - child->next_sibling = node->next_sibling; + child->next_sibling = next; child->prev_sibling_c = node; node->next_sibling = child; @@ -1378,12 +1382,14 @@ PUGI__NS_BEGIN child->parent = parent; - if (node->prev_sibling_c->next_sibling) - node->prev_sibling_c->next_sibling = child; + xml_node_struct* prev = node->prev_sibling_c; + + if (prev->next_sibling) + prev->next_sibling = child; else parent->first_child = child; - child->prev_sibling_c = node->prev_sibling_c; + child->prev_sibling_c = prev; child->next_sibling = node; node->prev_sibling_c = child; @@ -1393,15 +1399,18 @@ PUGI__NS_BEGIN { xml_node_struct* parent = node->parent; - if (node->next_sibling) - node->next_sibling->prev_sibling_c = node->prev_sibling_c; - else - parent->first_child->prev_sibling_c = node->prev_sibling_c; + xml_node_struct* next = node->next_sibling; + xml_node_struct* prev = node->prev_sibling_c; - if (node->prev_sibling_c->next_sibling) - node->prev_sibling_c->next_sibling = node->next_sibling; + if (next) + next->prev_sibling_c = prev; else - parent->first_child = node->next_sibling; + parent->first_child->prev_sibling_c = prev; + + if (prev->next_sibling) + prev->next_sibling = next; + else + parent->first_child = next; node->parent = 0; node->prev_sibling_c = 0; @@ -1445,39 +1454,46 @@ PUGI__NS_BEGIN inline void insert_attribute_after(xml_attribute_struct* attr, xml_attribute_struct* place, xml_node_struct* node) { - if (place->next_attribute) - place->next_attribute->prev_attribute_c = attr; + xml_attribute_struct* next = place->next_attribute; + + if (next) + next->prev_attribute_c = attr; else node->first_attribute->prev_attribute_c = attr; - attr->next_attribute = place->next_attribute; + attr->next_attribute = next; attr->prev_attribute_c = place; place->next_attribute = attr; } inline void insert_attribute_before(xml_attribute_struct* attr, xml_attribute_struct* place, xml_node_struct* node) { - if (place->prev_attribute_c->next_attribute) - place->prev_attribute_c->next_attribute = attr; + xml_attribute_struct* prev = place->prev_attribute_c; + + if (prev->next_attribute) + prev->next_attribute = attr; else node->first_attribute = attr; - attr->prev_attribute_c = place->prev_attribute_c; + attr->prev_attribute_c = prev; attr->next_attribute = place; place->prev_attribute_c = attr; } inline void remove_attribute(xml_attribute_struct* attr, xml_node_struct* node) { - if (attr->next_attribute) - attr->next_attribute->prev_attribute_c = attr->prev_attribute_c; - else - node->first_attribute->prev_attribute_c = attr->prev_attribute_c; + xml_attribute_struct* next = attr->next_attribute; + xml_attribute_struct* prev = attr->prev_attribute_c; - if (attr->prev_attribute_c->next_attribute) - attr->prev_attribute_c->next_attribute = attr->next_attribute; + if (next) + next->prev_attribute_c = prev; else - node->first_attribute = attr->next_attribute; + node->first_attribute->prev_attribute_c = prev; + + if (prev->next_attribute) + prev->next_attribute = next; + else + node->first_attribute = next; attr->prev_attribute_c = 0; attr->next_attribute = 0; @@ -2441,7 +2457,7 @@ PUGI__NS_BEGIN } template - PUGI__FN bool strcpy_insitu(String& dest, int& dest_len, Header& header, uintptr_t header_mask, const char_t* source, size_t source_length, bool shallow_copy = false) + PUGI__FN bool strcpy_insitu(String& dest, int& dest_len, Header& header, uintptr_t header_mask, const char_t* source, size_t source_length, boolean shallow_copy = pugi::false_value) { dest_len = static_cast(source_length); @@ -4820,7 +4836,7 @@ PUGI__NS_BEGIN template PUGI__FN bool set_value_bool(String& dest, int& dest_len, Header& header, uintptr_t header_mask, bool value) { - return strcpy_insitu(dest, dest_len, header, header_mask, value ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false"), value ? 4 : 5, true); + return strcpy_insitu(dest, dest_len, header, header_mask, value ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false"), value ? 4 : 5, true_value); } PUGI__FN xml_parse_result load_buffer_impl(xml_document_struct* doc, xml_node_struct* root, void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own, char_t** out_buffer) @@ -5180,7 +5196,7 @@ PUGI__NS_BEGIN xml_writer_file writer(file); doc.save(writer, indent, flags, encoding); - return ferror(file) == 0; + return fflush(file) == 0 && ferror(file) == 0; } struct name_null_sentry @@ -5315,12 +5331,15 @@ namespace pugi PUGI__FN xml_attribute xml_attribute::next_attribute() const { - return _attr ? xml_attribute(_attr->next_attribute) : xml_attribute(); + if (!_attr) return xml_attribute(); + return xml_attribute(_attr->next_attribute); } PUGI__FN xml_attribute xml_attribute::previous_attribute() const { - return _attr && _attr->prev_attribute_c->next_attribute ? xml_attribute(_attr->prev_attribute_c) : xml_attribute(); + if (!_attr) return xml_attribute(); + xml_attribute_struct* prev = _attr->prev_attribute_c; + return prev->next_attribute ? xml_attribute(prev) : xml_attribute(); } PUGI__FN string_view_t xml_attribute::as_string(string_view_t def) const @@ -5330,38 +5349,52 @@ namespace pugi PUGI__FN int xml_attribute::as_int(int def) const { - return (_attr && _attr->value) ? impl::get_value_int(_attr->value) : def; + if (!_attr) return def; + const char_t* value = _attr->value; + return value ? impl::get_value_int(value) : def; } PUGI__FN unsigned int xml_attribute::as_uint(unsigned int def) const { - return (_attr && _attr->value) ? impl::get_value_uint(_attr->value) : def; + if (!_attr) return def; + const char_t* value = _attr->value; + return value ? impl::get_value_uint(value) : def; } PUGI__FN double xml_attribute::as_double(double def) const { - return (_attr && _attr->value) ? impl::get_value_double(_attr->value) : def; + if (!_attr) return def; + const char_t* value = _attr->value; + return value ? impl::get_value_double(value) : def; } PUGI__FN float xml_attribute::as_float(float def) const { - return (_attr && _attr->value) ? impl::get_value_float(_attr->value) : def; + if (!_attr) return def; + const char_t* value = _attr->value; + return value ? impl::get_value_float(value) : def; } PUGI__FN bool xml_attribute::as_bool(bool def) const { - return (_attr && _attr->value) ? impl::get_value_bool(_attr->value) : def; + if (!_attr) return def; + const char_t* value = _attr->value; + return value ? impl::get_value_bool(value) : def; } #ifdef PUGIXML_HAS_LONG_LONG PUGI__FN long long xml_attribute::as_llong(long long def) const { - return (_attr && _attr->value) ? impl::get_value_llong(_attr->value) : def; + if (!_attr) return def; + const char_t* value = _attr->value; + return value ? impl::get_value_llong(value) : def; } PUGI__FN unsigned long long xml_attribute::as_ullong(unsigned long long def) const { - return (_attr && _attr->value) ? impl::get_value_ullong(_attr->value) : def; + if (!_attr) return def; + const char_t* value = _attr->value; + return value ? impl::get_value_ullong(value) : def; } #endif @@ -5452,20 +5485,25 @@ namespace pugi } #endif - PUGI__FN bool xml_attribute::set_name(string_view_t rhs, bool shallow_copy) + PUGI__FN bool xml_attribute::set_name(string_view_t rhs, boolean shallow_copy) { if (!_attr) return false; return impl::strcpy_insitu(_attr->name, _attr->name_len, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs.data(), rhs.length(), shallow_copy); } - PUGI__FN bool xml_attribute::set_value(string_view_t rhs, bool shallow_copy) + PUGI__FN bool xml_attribute::set_value(string_view_t rhs, boolean shallow_copy) { if (!_attr) return false; return impl::strcpy_insitu(_attr->value, _attr->value_len, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs.data(), rhs.length(), shallow_copy); } + PUGI__FN bool xml_attribute::set_value(const char_t* rhs, size_t sz) + { + return set_value(string_view_t(rhs, sz )); + } + PUGI__FN bool xml_attribute::set_value(int rhs) { if (!_attr) return false; @@ -5678,9 +5716,10 @@ namespace pugi { if (!_root) return xml_attribute(); - for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute) + for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute) { if (i->equals_name(name_)) return xml_attribute(i); + } return xml_attribute(); } @@ -5720,7 +5759,7 @@ namespace pugi if (!_root) return xml_attribute(); // optimistically search from hint up until the end - for (xml_attribute_struct* i = hint; i; i = i->next_attribute) + for (xml_attribute_struct* i = hint; i; i = i->next_attribute) { if (i->equals_name(name_)) { // update hint to maximize efficiency of searching for consecutive attributes @@ -5728,10 +5767,11 @@ namespace pugi return xml_attribute(i); } + } // wrap around and search from the first attribute until the hint // 'j' null pointer check is technically redundant, but it prevents a crash in case the assertion above fails - for (xml_attribute_struct* j = _root->first_attribute; j && j != hint; j = j->next_attribute) + for (xml_attribute_struct* j = _root->first_attribute; j && j != hint; j = j->next_attribute) { if (j->equals_name(name_)) { // update hint to maximize efficiency of searching for consecutive attributes @@ -5739,6 +5779,7 @@ namespace pugi return xml_attribute(j); } + } return xml_attribute(); } @@ -5746,9 +5787,8 @@ namespace pugi PUGI__FN xml_node xml_node::previous_sibling() const { if (!_root) return xml_node(); - - if (_root->prev_sibling_c->next_sibling) return xml_node(_root->prev_sibling_c); - else return xml_node(); + xml_node_struct* prev = _root->prev_sibling_c; + return prev->next_sibling ? xml_node(prev) : xml_node(); } PUGI__FN xml_node xml_node::parent() const @@ -5788,25 +5828,31 @@ namespace pugi PUGI__FN xml_attribute xml_node::first_attribute() const { - return _root ? xml_attribute(_root->first_attribute) : xml_attribute(); + if (!_root) return xml_attribute(); + return xml_attribute(_root->first_attribute); } PUGI__FN xml_attribute xml_node::last_attribute() const { - return _root && _root->first_attribute ? xml_attribute(_root->first_attribute->prev_attribute_c) : xml_attribute(); + if (!_root) return xml_attribute(); + xml_attribute_struct* first = _root->first_attribute; + return first ? xml_attribute(first->prev_attribute_c) : xml_attribute(); } PUGI__FN xml_node xml_node::first_child() const { - return _root ? xml_node(_root->first_child) : xml_node(); + if (!_root) return xml_node(); + return xml_node(_root->first_child); } PUGI__FN xml_node xml_node::last_child() const { - return _root && _root->first_child ? xml_node(_root->first_child->prev_sibling_c) : xml_node(); + if (!_root) return xml_node(); + xml_node_struct* first = _root->first_child; + return first ? xml_node(first->prev_sibling_c) : xml_node(); } - PUGI__FN bool xml_node::set_name(string_view_t rhs, bool shallow_copy) + PUGI__FN bool xml_node::set_name(string_view_t rhs, boolean shallow_copy) { xml_node_type type_ = _root ? PUGI__NODETYPE(_root) : node_null; @@ -5816,7 +5862,7 @@ namespace pugi return impl::strcpy_insitu(_root->name, _root->name_len, _root->header, impl::xml_memory_page_name_allocated_mask, rhs.data(), rhs.length(), shallow_copy); } - PUGI__FN bool xml_node::set_value(string_view_t rhs, bool shallow_copy) + PUGI__FN bool xml_node::set_value(string_view_t rhs, boolean shallow_copy) { xml_node_type type_ = _root ? PUGI__NODETYPE(_root) : node_null; @@ -5826,7 +5872,12 @@ namespace pugi return impl::strcpy_insitu(_root->value, _root->value_len, _root->header, impl::xml_memory_page_value_allocated_mask, rhs.data(), rhs.length(), shallow_copy); } - PUGI__FN xml_attribute xml_node::append_attribute(string_view_t name_, bool shallow_copy) + PUGI__FN bool xml_node::set_value(const char_t* rhs, size_t sz) + { + return set_value(string_view_t( rhs, sz )); + } + + PUGI__FN xml_attribute xml_node::append_attribute(string_view_t name_, boolean shallow_copy) { if (!impl::allow_insert_attribute(type())) return xml_attribute(); @@ -5843,7 +5894,7 @@ namespace pugi return a; } - PUGI__FN xml_attribute xml_node::prepend_attribute(string_view_t name_, bool shallow_copy) + PUGI__FN xml_attribute xml_node::prepend_attribute(string_view_t name_, boolean shallow_copy) { if (!impl::allow_insert_attribute(type())) return xml_attribute(); @@ -5860,7 +5911,7 @@ namespace pugi return a; } - PUGI__FN xml_attribute xml_node::insert_attribute_after(string_view_t name_, const xml_attribute& attr, bool shallow_copy) + PUGI__FN xml_attribute xml_node::insert_attribute_after(string_view_t name_, const xml_attribute& attr, boolean shallow_copy) { if (!impl::allow_insert_attribute(type())) return xml_attribute(); if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); @@ -5878,7 +5929,7 @@ namespace pugi return a; } - PUGI__FN xml_attribute xml_node::insert_attribute_before(string_view_t name_, const xml_attribute& attr, bool shallow_copy) + PUGI__FN xml_attribute xml_node::insert_attribute_before(string_view_t name_, const xml_attribute& attr, boolean shallow_copy) { if (!impl::allow_insert_attribute(type())) return xml_attribute(); if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); @@ -5978,7 +6029,7 @@ namespace pugi impl::append_node(n._root, _root); - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"), true); + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"), true_value); return n; } @@ -5995,7 +6046,7 @@ namespace pugi impl::prepend_node(n._root, _root); - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"), true); + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"), true_value); return n; } @@ -6013,7 +6064,7 @@ namespace pugi impl::insert_node_before(n._root, node._root); - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"), true); + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"), true_value); return n; } @@ -6031,12 +6082,12 @@ namespace pugi impl::insert_node_after(n._root, node._root); - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"), true); + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"), true_value); return n; } - PUGI__FN xml_node xml_node::append_child(string_view_t name_, bool shallow_copy) + PUGI__FN xml_node xml_node::append_child(string_view_t name_, boolean shallow_copy) { xml_node result = append_child(node_element); @@ -6045,7 +6096,7 @@ namespace pugi return result; } - PUGI__FN xml_node xml_node::prepend_child(string_view_t name_, bool shallow_copy) + PUGI__FN xml_node xml_node::prepend_child(string_view_t name_, boolean shallow_copy) { xml_node result = prepend_child(node_element); @@ -6054,7 +6105,7 @@ namespace pugi return result; } - PUGI__FN xml_node xml_node::insert_child_after(string_view_t name_, const xml_node& node, bool shallow_copy) + PUGI__FN xml_node xml_node::insert_child_after(string_view_t name_, const xml_node& node, boolean shallow_copy) { xml_node result = insert_child_after(node_element, node); @@ -6063,7 +6114,7 @@ namespace pugi return result; } - PUGI__FN xml_node xml_node::insert_child_before(string_view_t name_, const xml_node& node, bool shallow_copy) + PUGI__FN xml_node xml_node::insert_child_before(string_view_t name_, const xml_node& node, boolean shallow_copy) { xml_node result = insert_child_before(node_element, node); @@ -6360,8 +6411,9 @@ namespace pugi for (xml_node_struct* i = _root; i; i = i->parent) { + const char_t* iname = i->name; offset += (i != _root); - offset += i->name ? impl::strlength(i->name) : 0; + offset += iname ? impl::strlength(iname) : 0; } string_t result; @@ -6372,12 +6424,13 @@ namespace pugi if (j != _root) result[--offset] = delimiter; - if (j->name) + const char_t* jname = j->name; + if (jname) { - size_t length = impl::strlength(j->name); + size_t length = impl::strlength(jname); offset -= length; - memcpy(&result[offset], j->name, length * sizeof(char_t)); + memcpy(&result[offset], jname, length * sizeof(char_t)); } } @@ -6415,7 +6468,8 @@ namespace pugi { for (xml_node_struct* j = context._root->first_child; j; j = j->next_sibling) { - if (j->name && impl::strequalrange(j->name, path_segment, static_cast(path_segment_end - path_segment))) + const char_t* jname = j->name; + if (jname && impl::strequalrange(jname, path_segment, static_cast(path_segment_end - path_segment))) { xml_node subsearch = xml_node(j).first_element_by_path(next_segment, delimiter); @@ -6621,61 +6675,73 @@ namespace pugi PUGI__FN int xml_text::as_int(int def) const { xml_node_struct* d = _data(); - - return (d && d->value) ? impl::get_value_int(d->value) : def; + if (!d) return def; + const char_t* value = d->value; + return value ? impl::get_value_int(value) : def; } PUGI__FN unsigned int xml_text::as_uint(unsigned int def) const { xml_node_struct* d = _data(); - - return (d && d->value) ? impl::get_value_uint(d->value) : def; + if (!d) return def; + const char_t* value = d->value; + return value ? impl::get_value_uint(value) : def; } PUGI__FN double xml_text::as_double(double def) const { xml_node_struct* d = _data(); - - return (d && d->value) ? impl::get_value_double(d->value) : def; + if (!d) return def; + const char_t* value = d->value; + return value ? impl::get_value_double(value) : def; } PUGI__FN float xml_text::as_float(float def) const { xml_node_struct* d = _data(); - - return (d && d->value) ? impl::get_value_float(d->value) : def; + if (!d) return def; + const char_t* value = d->value; + return value ? impl::get_value_float(value) : def; } PUGI__FN bool xml_text::as_bool(bool def) const { xml_node_struct* d = _data(); - - return (d && d->value) ? impl::get_value_bool(d->value) : def; + if (!d) return def; + const char_t* value = d->value; + return value ? impl::get_value_bool(value) : def; } #ifdef PUGIXML_HAS_LONG_LONG PUGI__FN long long xml_text::as_llong(long long def) const { xml_node_struct* d = _data(); - - return (d && d->value) ? impl::get_value_llong(d->value) : def; + if (!d) return def; + const char_t* value = d->value; + return value ? impl::get_value_llong(value) : def; } PUGI__FN unsigned long long xml_text::as_ullong(unsigned long long def) const { xml_node_struct* d = _data(); - - return (d && d->value) ? impl::get_value_ullong(d->value) : def; + if (!d) return def; + const char_t* value = d->value; + return value ? impl::get_value_ullong(value) : def; } #endif - PUGI__FN bool xml_text::set(string_view_t rhs, bool shallow_copy) + PUGI__FN bool xml_text::set(string_view_t rhs, boolean shallow_copy) { xml_node_struct* dn = _data_new(); return dn ? impl::strcpy_insitu(dn->value, dn->value_len, dn->header, impl::xml_memory_page_value_allocated_mask, rhs.data(), rhs.length(), shallow_copy) : false; } + PUGI__FN bool xml_text::set(const char_t* rhs, size_t sz) + { + return set(string_view_t( rhs, sz )); + } + PUGI__FN bool xml_text::set(int rhs) { xml_node_struct* dn = _data_new(); @@ -7425,7 +7491,7 @@ namespace pugi using impl::auto_deleter; // MSVC7 workaround auto_deleter file(impl::open_file(path_, (flags & format_save_file_text) ? "w" : "wb"), impl::close_file); - return impl::save_file_impl(*this, file.data, indent, flags, encoding); + return impl::save_file_impl(*this, file.data, indent, flags, encoding) && fclose(file.release()) == 0; } PUGI__FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const @@ -7433,7 +7499,7 @@ namespace pugi using impl::auto_deleter; // MSVC7 workaround auto_deleter file(impl::open_file_wide(path_, (flags & format_save_file_text) ? L"w" : L"wb"), impl::close_file); - return impl::save_file_impl(*this, file.data, indent, flags, encoding); + return impl::save_file_impl(*this, file.data, indent, flags, encoding) && fclose(file.release()) == 0; } PUGI__FN xml_node xml_document::document_element() const diff --git a/thirdparty/pugixml/pugixml.hpp b/thirdparty/pugixml/pugixml.hpp index 2452bcf95b..8adbb6fcd7 100644 --- a/thirdparty/pugixml/pugixml.hpp +++ b/thirdparty/pugixml/pugixml.hpp @@ -1,5 +1,5 @@ /** - * pugixml parser - version 1.12 + * pugixml parser - version 1.13 * -------------------------------------------------------- * Copyright (C) 2006-2022, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://pugixml.org/ @@ -14,7 +14,7 @@ // Define version macro; evaluates to major * 1000 + minor * 10 + patch so that it's safe to use in less-than comparisons // Note: pugixml used major * 100 + minor * 10 + patch format up until 1.9 (which had version identifier 190); starting from pugixml 1.10, the minor version number is two digits #ifndef PUGIXML_VERSION -# define PUGIXML_VERSION 1120 // 1.12 +# define PUGIXML_VERSION 1130 // 1.13 #endif // Include user configuration file (this can define various configuration macros) @@ -59,14 +59,6 @@ # define PUGI_CXX_STD 20 #endif // C++20 features check -#if (defined(_MSC_VER) && _MSC_VER > 1900 && \ - ((defined(_HAS_CXX23) && _HAS_CXX23 == 1))) -# ifdef PUGI_CXX_STD -# undef PUGI_CXX_STD -# endif -# define PUGI_CXX_STD 23 -#endif - #if !defined(PUGI_CXX_STD) # define PUGI_CXX_STD 11 #endif @@ -148,6 +140,8 @@ #ifndef PUGIXML_NULL # if __cplusplus >= 201103 # define PUGIXML_NULL nullptr +# elif defined(_MSC_VER) && _MSC_VER >= 1600 +# define PUGIXML_NULL nullptr # else # define PUGIXML_NULL 0 # endif @@ -574,8 +568,9 @@ namespace pugi bool as_bool(bool def = false) const; // Set attribute name/value (returns false if attribute is empty or there is not enough memory) - bool set_name(string_view_t rhs, bool shallow_copy = false); - bool set_value(string_view_t rhs, bool shallow_copy = false); + bool set_name(string_view_t rhs, boolean shallow_copy = pugi::false_value); + bool set_value(string_view_t rhs, boolean shallow_copy = pugi::false_value); + bool set_value(const char_t*, size_t sz); // 1.13 ABI compatible // Set attribute value with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") bool set_value(int rhs); @@ -708,14 +703,15 @@ namespace pugi string_view_t child_value(string_view_t name) const; // Set node name/value (returns false if node is empty, there is not enough memory, or node can not have name/value) - bool set_name(string_view_t rhs, bool shallow_copy = false); - bool set_value(string_view_t rhs, bool shallow_copy = false); + bool set_name(string_view_t rhs, boolean shallow_copy = pugi::false_value); + bool set_value(string_view_t rhs, boolean shallow_copy = pugi::false_value); + bool set_value(const char_t* rhs, size_t sz); // 1.13 ABI compatible // Add attribute with specified name. Returns added attribute, or empty attribute on errors. - xml_attribute append_attribute(string_view_t name, bool shallow_copy = false); - xml_attribute prepend_attribute(string_view_t name, bool shallow_copy = false); - xml_attribute insert_attribute_after(string_view_t name, const xml_attribute& attr, bool shallow_copy = false); - xml_attribute insert_attribute_before(string_view_t name, const xml_attribute& attr, bool shallow_copy = false); + xml_attribute append_attribute(string_view_t name, boolean shallow_copy = pugi::false_value); + xml_attribute prepend_attribute(string_view_t name, boolean shallow_copy = pugi::false_value); + xml_attribute insert_attribute_after(string_view_t name, const xml_attribute& attr, boolean shallow_copy = pugi::false_value); + xml_attribute insert_attribute_before(string_view_t name, const xml_attribute& attr, boolean shallow_copy = pugi::false_value); // Add a copy of the specified attribute. Returns added attribute, or empty attribute on errors. xml_attribute append_copy(const xml_attribute& proto); @@ -730,10 +726,10 @@ namespace pugi xml_node insert_child_before(xml_node_type type, const xml_node& node); // Add child element with specified name. Returns added node, or empty node on errors. - xml_node append_child(string_view_t name, bool shallow_copy = false); - xml_node prepend_child(string_view_t name, bool shallow_copy = false); - xml_node insert_child_after(string_view_t name, const xml_node& node, bool shallow_copy = false); - xml_node insert_child_before(string_view_t name, const xml_node& node, bool shallow_copy = false); + xml_node append_child(string_view_t name, boolean shallow_copy = pugi::false_value); + xml_node prepend_child(string_view_t name, boolean shallow_copy = pugi::false_value); + xml_node insert_child_after(string_view_t name, const xml_node& node, boolean shallow_copy = pugi::false_value); + xml_node insert_child_before(string_view_t name, const xml_node& node, boolean shallow_copy = pugi::false_value); // Add a copy of the specified node as a child. Returns added node, or empty node on errors. xml_node append_copy(const xml_node& proto); @@ -867,9 +863,12 @@ namespace pugi // Range-based for support xml_object_range children() const; - xml_object_range children(string_view_t name) const; xml_object_range attributes() const; + // Range-based for support for all children with the specified name + // Note: name pointer must have a longer lifetime than the returned object; be careful with passing temporaries! + xml_object_range children(string_view_t name) const; + // Get node offset in parsed file/string (in char_t units) for debugging purposes ptrdiff_t offset_debug() const; @@ -934,7 +933,8 @@ namespace pugi bool as_bool(bool def = false) const; // Set text (returns false if object is empty or there is not enough memory) - bool set(string_view_t rhs, bool shallow_copy = false); + bool set(string_view_t rhs, boolean shallow_copy = pugi::false_value); + bool set(const char_t* rhs, size_t sz); // 1.13 ABI compatible // Set text with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") bool set(int rhs); @@ -1081,6 +1081,7 @@ namespace pugi xml_named_node_iterator(); // Construct an iterator which points to the specified node + // Note: name pointer is stored in the iterator and must have a longer lifetime than iterator itself xml_named_node_iterator(const xml_node& node, string_view_t name); // Iterator operators diff --git a/thirdparty/stb/stb_image.h b/thirdparty/stb/stb_image.h index 0f8459a799..5e807a0a6e 100644 --- a/thirdparty/stb/stb_image.h +++ b/thirdparty/stb/stb_image.h @@ -1,4 +1,4 @@ -/* stb_image - v2.27 - public domain image loader - http://nothings.org/stb +/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb no warranty implied; use at your own risk Do this: @@ -48,6 +48,7 @@ LICENSE RECENT REVISION HISTORY: + 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes 2.26 (2020-07-13) many minor fixes 2.25 (2020-02-02) fix warnings @@ -108,7 +109,7 @@ RECENT REVISION HISTORY: Cass Everitt Ryamond Barbiero github:grim210 Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus - Josh Tobin Matthew Gregan github:poppolopoppo + Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo Julian Raschke Gregory Mullen Christian Floisand github:darealshinji Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 Brad Weinberger Matvey Cherevko github:mosra @@ -140,7 +141,7 @@ RECENT REVISION HISTORY: // // ... x = width, y = height, n = # 8-bit components per pixel ... // // ... replace '0' with '1'..'4' to force that many components per pixel // // ... but 'n' will always be the number that it would have been if you said 0 -// stbi_image_free(data) +// stbi_image_free(data); // // Standard parameters: // int *x -- outputs image width in pixels @@ -635,7 +636,7 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch #endif #endif -#ifdef _MSC_VER +#if defined(_MSC_VER) || defined(__SYMBIAN32__) typedef unsigned short stbi__uint16; typedef signed short stbi__int16; typedef unsigned int stbi__uint32; @@ -1032,7 +1033,7 @@ static int stbi__mad3sizes_valid(int a, int b, int c, int add) } // returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) { return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && @@ -1055,7 +1056,7 @@ static void *stbi__malloc_mad3(int a, int b, int c, int add) return stbi__malloc(a*b*c + add); } -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) { if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; @@ -1063,6 +1064,23 @@ static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) } #endif +// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. +static int stbi__addints_valid(int a, int b) +{ + if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow + if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. + return a <= INT_MAX - b; +} + +// returns 1 if the product of two signed shorts is valid, 0 on overflow. +static int stbi__mul2shorts_valid(short a, short b) +{ + if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow + if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid + if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN + return a >= SHRT_MIN / b; +} + // stbi__err - error // stbi__errpf - error returning pointer to float // stbi__errpuc - error returning pointer to unsigned char @@ -1985,9 +2003,12 @@ static int stbi__build_huffman(stbi__huffman *h, int *count) int i,j,k=0; unsigned int code; // build size list for each symbol (from JPEG spec) - for (i=0; i < 16; ++i) - for (j=0; j < count[i]; ++j) + for (i=0; i < 16; ++i) { + for (j=0; j < count[i]; ++j) { h->size[k++] = (stbi_uc) (i+1); + if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); + } + } h->size[k] = 0; // compute actual symbols (from jpeg spec) @@ -2112,6 +2133,8 @@ stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) // convert the huffman code to the symbol id c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + if(c < 0 || c >= 256) // symbol id out of bounds! + return -1; STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); // convert the id to a symbol @@ -2130,6 +2153,7 @@ stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n) unsigned int k; int sgn; if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) k = stbi_lrot(j->code_buffer, n); @@ -2144,6 +2168,7 @@ stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) { unsigned int k; if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing k = stbi_lrot(j->code_buffer, n); j->code_buffer = k & ~stbi__bmask[n]; k &= stbi__bmask[n]; @@ -2155,6 +2180,7 @@ stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) { unsigned int k; if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing k = j->code_buffer; j->code_buffer <<= 1; --j->code_bits; @@ -2192,8 +2218,10 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman memset(data,0,64*sizeof(data[0])); diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); data[0] = (short) (dc * dequant[0]); // decode AC components, see JPEG spec @@ -2207,6 +2235,7 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman if (r) { // fast-AC path k += (r >> 4) & 15; // run s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); j->code_buffer <<= s; j->code_bits -= s; // decode into unzigzag'd location @@ -2246,8 +2275,10 @@ static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__ if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); data[0] = (short) (dc * (1 << j->succ_low)); } else { // refinement scan for DC coefficient @@ -2282,6 +2313,7 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__ if (r) { // fast-AC path k += (r >> 4) & 15; // run s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); j->code_buffer <<= s; j->code_bits -= s; zig = stbi__jpeg_dezigzag[k++]; @@ -3102,6 +3134,7 @@ static int stbi__process_marker(stbi__jpeg *z, int m) sizes[i] = stbi__get8(z->s); n += sizes[i]; } + if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! L -= 17; if (tc == 0) { if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; @@ -3267,6 +3300,13 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan) if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; } + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + // compute interleaved mcu info z->img_h_max = h_max; z->img_v_max = v_max; @@ -3344,6 +3384,28 @@ static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) return 1; } +static int stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +{ + // some JPEGs have junk at end, skip over it but if we find what looks + // like a valid marker, resume there + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + while (x == 255) { // might be a marker + if (stbi__at_eof(j->s)) return STBI__MARKER_none; + x = stbi__get8(j->s); + if (x != 0x00 && x != 0xff) { + // not a stuffed zero or lead-in to another marker, looks + // like an actual marker, return it + return x; + } + // stuffed zero has x=0 now which ends the loop, meaning we go + // back to regular scan loop. + // repeated 0xff keeps trying to read the next byte of the marker. + } + } + return STBI__MARKER_none; +} + // decode image to YCbCr format static int stbi__decode_jpeg_image(stbi__jpeg *j) { @@ -3360,25 +3422,22 @@ static int stbi__decode_jpeg_image(stbi__jpeg *j) if (!stbi__process_scan_header(j)) return 0; if (!stbi__parse_entropy_coded_data(j)) return 0; if (j->marker == STBI__MARKER_none ) { - // handle 0s at the end of image data from IP Kamera 9060 - while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - if (x == 255) { - j->marker = stbi__get8(j->s); - break; - } - } + j->marker = stbi__skip_jpeg_junk_at_end(j); // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 } + m = stbi__get_marker(j); + if (STBI__RESTART(m)) + m = stbi__get_marker(j); } else if (stbi__DNL(m)) { int Ld = stbi__get16be(j->s); stbi__uint32 NL = stbi__get16be(j->s); if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + m = stbi__get_marker(j); } else { - if (!stbi__process_marker(j, m)) return 0; + if (!stbi__process_marker(j, m)) return 1; + m = stbi__get_marker(j); } - m = stbi__get_marker(j); } if (j->progressive) stbi__jpeg_finish(j); @@ -3969,6 +4028,7 @@ static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int re unsigned char* result; stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); if (!j) return stbi__errpuc("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); STBI_NOTUSED(ri); j->s = s; stbi__setup_jpeg(j); @@ -3982,6 +4042,7 @@ static int stbi__jpeg_test(stbi__context *s) int r; stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); j->s = s; stbi__setup_jpeg(j); r = stbi__decode_jpeg_header(j, STBI__SCAN_type); @@ -4007,6 +4068,7 @@ static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) int result; stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); j->s = s; result = stbi__jpeg_info_raw(j, x, y, comp); STBI_FREE(j); @@ -4249,11 +4311,12 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) a->zout = zout; return 1; } + if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data z -= 257; len = stbi__zlength_base[z]; if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); z = stbi__zhuffman_decode(a, &a->z_distance); - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data dist = stbi__zdist_base[z]; if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); @@ -4948,7 +5011,7 @@ STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; -STBIDEF void stbi__unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) { stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; stbi__unpremultiply_on_load_set = 1; @@ -5057,14 +5120,13 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (!pal_img_n) { s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); - if (scan == STBI__SCAN_header) return 1; } else { // if paletted, then pal_n is our final components, and // img_n is # components to decompress/filter. s->img_n = 1; if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); - // if SCAN_header, have to scan to see if we have a tRNS } + // even with SCAN_header, have to scan to see if we have a tRNS break; } @@ -5096,6 +5158,8 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); has_trans = 1; + // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. + if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } if (z->depth == 16) { for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is } else { @@ -5108,7 +5172,13 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) case STBI__PNG_TYPE('I','D','A','T'): { if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); - if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if (scan == STBI__SCAN_header) { + // header scan definitely stops at first IDAT + if (pal_img_n) + s->img_n = pal_img_n; + return 1; + } + if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); if ((int)(ioff + c.length) < (int)ioff) return 0; if (ioff + c.length > idata_limit) { stbi__uint32 idata_limit_old = idata_limit; @@ -5491,8 +5561,22 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req psize = (info.offset - info.extra_read - info.hsz) >> 2; } if (psize == 0) { - if (info.offset != s->callback_already_read + (s->img_buffer - s->img_buffer_original)) { - return stbi__errpuc("bad offset", "Corrupt BMP"); + // accept some number of extra bytes after the header, but if the offset points either to before + // the header ends or implies a large amount of extra data, reject the file as malformed + int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); + int header_limit = 1024; // max we actually read is below 256 bytes currently. + int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. + if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { + return stbi__errpuc("bad header", "Corrupt BMP"); + } + // we established that bytes_read_so_far is positive and sensible. + // the first half of this test rejects offsets that are either too small positives, or + // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn + // ensures the number computed in the second half of the test can't overflow. + if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } else { + stbi__skip(s, info.offset - bytes_read_so_far); } } @@ -7180,12 +7264,12 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re // Run value = stbi__get8(s); count -= 128; - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } for (z = 0; z < count; ++z) scanline[i++ * 4 + k] = value; } else { // Dump - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } for (z = 0; z < count; ++z) scanline[i++ * 4 + k] = stbi__get8(s); } @@ -7439,10 +7523,17 @@ static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); if (!out) return stbi__errpuc("outofmem", "Out of memory"); - stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8)); + if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { + STBI_FREE(out); + return stbi__errpuc("bad PNM", "PNM file truncated"); + } if (req_comp && req_comp != s->img_n) { - out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (ri->bits_per_channel == 16) { + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); + } else { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + } if (out == NULL) return out; // stbi__convert_format frees input on failure } return out; @@ -7479,6 +7570,8 @@ static int stbi__pnm_getinteger(stbi__context *s, char *c) while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { value = value*10 + (*c - '0'); *c = (char) stbi__get8(s); + if((value > 214748364) || (value == 214748364 && *c > '7')) + return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); } return value; @@ -7509,9 +7602,13 @@ static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) stbi__pnm_skip_whitespace(s, &c); *x = stbi__pnm_getinteger(s, &c); // read width + if(*x == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); stbi__pnm_skip_whitespace(s, &c); *y = stbi__pnm_getinteger(s, &c); // read height + if (*y == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); stbi__pnm_skip_whitespace(s, &c); maxv = stbi__pnm_getinteger(s, &c); // read max value diff --git a/thirdparty/webp/sharpyuv/sharpyuv.c b/thirdparty/webp/sharpyuv/sharpyuv.c index 8b3ab7216b..7de34fb0b2 100644 --- a/thirdparty/webp/sharpyuv/sharpyuv.c +++ b/thirdparty/webp/sharpyuv/sharpyuv.c @@ -15,15 +15,21 @@ #include #include -#include +#include #include #include #include "src/webp/types.h" -#include "src/dsp/cpu.h" +#include "sharpyuv/sharpyuv_cpu.h" #include "sharpyuv/sharpyuv_dsp.h" #include "sharpyuv/sharpyuv_gamma.h" +//------------------------------------------------------------------------------ + +int SharpYuvGetVersion(void) { + return SHARPYUV_VERSION; +} + //------------------------------------------------------------------------------ // Sharp RGB->YUV conversion @@ -414,24 +420,45 @@ static int DoSharpArgbToYuv(const uint8_t* r_ptr, const uint8_t* g_ptr, } #undef SAFE_ALLOC +#if defined(WEBP_USE_THREAD) && !defined(_WIN32) +#include // NOLINT + +#define LOCK_ACCESS \ + static pthread_mutex_t sharpyuv_lock = PTHREAD_MUTEX_INITIALIZER; \ + if (pthread_mutex_lock(&sharpyuv_lock)) return +#define UNLOCK_ACCESS_AND_RETURN \ + do { \ + (void)pthread_mutex_unlock(&sharpyuv_lock); \ + return; \ + } while (0) +#else // !(defined(WEBP_USE_THREAD) && !defined(_WIN32)) +#define LOCK_ACCESS do {} while (0) +#define UNLOCK_ACCESS_AND_RETURN return +#endif // defined(WEBP_USE_THREAD) && !defined(_WIN32) + // Hidden exported init function. -// By default SharpYuvConvert calls it with NULL. If needed, users can declare -// it as extern and call it with a VP8CPUInfo function. -extern void SharpYuvInit(VP8CPUInfo cpu_info_func); +// By default SharpYuvConvert calls it with SharpYuvGetCPUInfo. If needed, +// users can declare it as extern and call it with an alternate VP8CPUInfo +// function. +SHARPYUV_EXTERN void SharpYuvInit(VP8CPUInfo cpu_info_func); void SharpYuvInit(VP8CPUInfo cpu_info_func) { static volatile VP8CPUInfo sharpyuv_last_cpuinfo_used = (VP8CPUInfo)&sharpyuv_last_cpuinfo_used; - const int initialized = - (sharpyuv_last_cpuinfo_used != (VP8CPUInfo)&sharpyuv_last_cpuinfo_used); - if (cpu_info_func == NULL && initialized) return; - if (sharpyuv_last_cpuinfo_used == cpu_info_func) return; - - SharpYuvInitDsp(cpu_info_func); - if (!initialized) { - SharpYuvInitGammaTables(); + LOCK_ACCESS; + // Only update SharpYuvGetCPUInfo when called from external code to avoid a + // race on reading the value in SharpYuvConvert(). + if (cpu_info_func != (VP8CPUInfo)&SharpYuvGetCPUInfo) { + SharpYuvGetCPUInfo = cpu_info_func; + } + if (sharpyuv_last_cpuinfo_used == SharpYuvGetCPUInfo) { + UNLOCK_ACCESS_AND_RETURN; } - sharpyuv_last_cpuinfo_used = cpu_info_func; + SharpYuvInitDsp(); + SharpYuvInitGammaTables(); + + sharpyuv_last_cpuinfo_used = SharpYuvGetCPUInfo; + UNLOCK_ACCESS_AND_RETURN; } int SharpYuvConvert(const void* r_ptr, const void* g_ptr, @@ -467,7 +494,8 @@ int SharpYuvConvert(const void* r_ptr, const void* g_ptr, // Stride should be even for uint16_t buffers. return 0; } - SharpYuvInit(NULL); + // The address of the function pointer is used to avoid a read race. + SharpYuvInit((VP8CPUInfo)&SharpYuvGetCPUInfo); // Add scaling factor to go from rgb_bit_depth to yuv_bit_depth, to the // rgb->yuv conversion matrix. diff --git a/thirdparty/webp/sharpyuv/sharpyuv.h b/thirdparty/webp/sharpyuv/sharpyuv.h index 9386ea2185..181b20a0bc 100644 --- a/thirdparty/webp/sharpyuv/sharpyuv.h +++ b/thirdparty/webp/sharpyuv/sharpyuv.h @@ -12,15 +12,31 @@ #ifndef WEBP_SHARPYUV_SHARPYUV_H_ #define WEBP_SHARPYUV_SHARPYUV_H_ -#include - #ifdef __cplusplus extern "C" { #endif +#ifndef SHARPYUV_EXTERN +#ifdef WEBP_EXTERN +#define SHARPYUV_EXTERN WEBP_EXTERN +#else +// This explicitly marks library functions and allows for changing the +// signature for e.g., Windows DLL builds. +#if defined(__GNUC__) && __GNUC__ >= 4 +#define SHARPYUV_EXTERN extern __attribute__((visibility("default"))) +#else +#if defined(_MSC_VER) && defined(WEBP_DLL) +#define SHARPYUV_EXTERN __declspec(dllexport) +#else +#define SHARPYUV_EXTERN extern +#endif /* _MSC_VER && WEBP_DLL */ +#endif /* __GNUC__ >= 4 */ +#endif /* WEBP_EXTERN */ +#endif /* SHARPYUV_EXTERN */ + // SharpYUV API version following the convention from semver.org #define SHARPYUV_VERSION_MAJOR 0 -#define SHARPYUV_VERSION_MINOR 1 +#define SHARPYUV_VERSION_MINOR 2 #define SHARPYUV_VERSION_PATCH 0 // Version as a uint32_t. The major number is the high 8 bits. // The minor number is the middle 8 bits. The patch number is the low 16 bits. @@ -30,6 +46,10 @@ extern "C" { SHARPYUV_MAKE_VERSION(SHARPYUV_VERSION_MAJOR, SHARPYUV_VERSION_MINOR, \ SHARPYUV_VERSION_PATCH) +// Returns the library's version number, packed in hexadecimal. See +// SHARPYUV_VERSION. +SHARPYUV_EXTERN int SharpYuvGetVersion(void); + // RGB to YUV conversion matrix, in 16 bit fixed point. // y = rgb_to_y[0] * r + rgb_to_y[1] * g + rgb_to_y[2] * b + rgb_to_y[3] // u = rgb_to_u[0] * r + rgb_to_u[1] * g + rgb_to_u[2] * b + rgb_to_u[3] @@ -65,11 +85,13 @@ typedef struct { // adjacent pixels on the y, u and v channels. If yuv_bit_depth > 8, they // should be multiples of 2. // width, height: width and height of the image in pixels -int SharpYuvConvert(const void* r_ptr, const void* g_ptr, const void* b_ptr, - int rgb_step, int rgb_stride, int rgb_bit_depth, - void* y_ptr, int y_stride, void* u_ptr, int u_stride, - void* v_ptr, int v_stride, int yuv_bit_depth, int width, - int height, const SharpYuvConversionMatrix* yuv_matrix); +SHARPYUV_EXTERN int SharpYuvConvert(const void* r_ptr, const void* g_ptr, + const void* b_ptr, int rgb_step, + int rgb_stride, int rgb_bit_depth, + void* y_ptr, int y_stride, void* u_ptr, + int u_stride, void* v_ptr, int v_stride, + int yuv_bit_depth, int width, int height, + const SharpYuvConversionMatrix* yuv_matrix); // TODO(b/194336375): Add YUV444 to YUV420 conversion. Maybe also add 422 // support (it's rarely used in practice, especially for images). diff --git a/thirdparty/webp/sharpyuv/sharpyuv_cpu.c b/thirdparty/webp/sharpyuv/sharpyuv_cpu.c new file mode 100644 index 0000000000..29425a0c49 --- /dev/null +++ b/thirdparty/webp/sharpyuv/sharpyuv_cpu.c @@ -0,0 +1,14 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +#include "sharpyuv/sharpyuv_cpu.h" + +// Include src/dsp/cpu.c to create SharpYuvGetCPUInfo from VP8GetCPUInfo. The +// function pointer is renamed in sharpyuv_cpu.h. +#include "src/dsp/cpu.c" diff --git a/thirdparty/webp/sharpyuv/sharpyuv_cpu.h b/thirdparty/webp/sharpyuv/sharpyuv_cpu.h new file mode 100644 index 0000000000..176ca3eb16 --- /dev/null +++ b/thirdparty/webp/sharpyuv/sharpyuv_cpu.h @@ -0,0 +1,22 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +#ifndef WEBP_SHARPYUV_SHARPYUV_CPU_H_ +#define WEBP_SHARPYUV_SHARPYUV_CPU_H_ + +#include "sharpyuv/sharpyuv.h" + +// Avoid exporting SharpYuvGetCPUInfo in shared object / DLL builds. +// SharpYuvInit() replaces the use of the function pointer. +#undef WEBP_EXTERN +#define WEBP_EXTERN extern +#define VP8GetCPUInfo SharpYuvGetCPUInfo +#include "src/dsp/cpu.h" + +#endif // WEBP_SHARPYUV_SHARPYUV_CPU_H_ diff --git a/thirdparty/webp/sharpyuv/sharpyuv_csp.c b/thirdparty/webp/sharpyuv/sharpyuv_csp.c index 5334fa64fa..0ad22be945 100644 --- a/thirdparty/webp/sharpyuv/sharpyuv_csp.c +++ b/thirdparty/webp/sharpyuv/sharpyuv_csp.c @@ -13,7 +13,7 @@ #include #include -#include +#include static int ToFixed16(float f) { return (int)floor(f * (1 << 16) + 0.5f); } diff --git a/thirdparty/webp/sharpyuv/sharpyuv_csp.h b/thirdparty/webp/sharpyuv/sharpyuv_csp.h index 63c99ef5cd..3214e3ac60 100644 --- a/thirdparty/webp/sharpyuv/sharpyuv_csp.h +++ b/thirdparty/webp/sharpyuv/sharpyuv_csp.h @@ -35,8 +35,9 @@ typedef struct { } SharpYuvColorSpace; // Fills in 'matrix' for the given YUVColorSpace. -void SharpYuvComputeConversionMatrix(const SharpYuvColorSpace* yuv_color_space, - SharpYuvConversionMatrix* matrix); +SHARPYUV_EXTERN void SharpYuvComputeConversionMatrix( + const SharpYuvColorSpace* yuv_color_space, + SharpYuvConversionMatrix* matrix); // Enums for precomputed conversion matrices. typedef enum { @@ -49,7 +50,7 @@ typedef enum { } SharpYuvMatrixType; // Returns a pointer to a matrix for one of the predefined colorspaces. -const SharpYuvConversionMatrix* SharpYuvGetConversionMatrix( +SHARPYUV_EXTERN const SharpYuvConversionMatrix* SharpYuvGetConversionMatrix( SharpYuvMatrixType matrix_type); #ifdef __cplusplus diff --git a/thirdparty/webp/sharpyuv/sharpyuv_dsp.c b/thirdparty/webp/sharpyuv/sharpyuv_dsp.c index 956fa7ce55..31c272c408 100644 --- a/thirdparty/webp/sharpyuv/sharpyuv_dsp.c +++ b/thirdparty/webp/sharpyuv/sharpyuv_dsp.c @@ -16,7 +16,7 @@ #include #include -#include "src/dsp/cpu.h" +#include "sharpyuv/sharpyuv_cpu.h" //----------------------------------------------------------------------------- @@ -75,23 +75,24 @@ void (*SharpYuvFilterRow)(const int16_t* A, const int16_t* B, int len, extern void InitSharpYuvSSE2(void); extern void InitSharpYuvNEON(void); -void SharpYuvInitDsp(VP8CPUInfo cpu_info_func) { - (void)cpu_info_func; - +void SharpYuvInitDsp(void) { #if !WEBP_NEON_OMIT_C_CODE SharpYuvUpdateY = SharpYuvUpdateY_C; SharpYuvUpdateRGB = SharpYuvUpdateRGB_C; SharpYuvFilterRow = SharpYuvFilterRow_C; #endif + if (SharpYuvGetCPUInfo != NULL) { #if defined(WEBP_HAVE_SSE2) - if (cpu_info_func == NULL || cpu_info_func(kSSE2)) { - InitSharpYuvSSE2(); - } + if (SharpYuvGetCPUInfo(kSSE2)) { + InitSharpYuvSSE2(); + } #endif // WEBP_HAVE_SSE2 + } #if defined(WEBP_HAVE_NEON) - if (WEBP_NEON_OMIT_C_CODE || cpu_info_func == NULL || cpu_info_func(kNEON)) { + if (WEBP_NEON_OMIT_C_CODE || + (SharpYuvGetCPUInfo != NULL && SharpYuvGetCPUInfo(kNEON))) { InitSharpYuvNEON(); } #endif // WEBP_HAVE_NEON diff --git a/thirdparty/webp/sharpyuv/sharpyuv_dsp.h b/thirdparty/webp/sharpyuv/sharpyuv_dsp.h index e561d8d3d0..805fbadbf6 100644 --- a/thirdparty/webp/sharpyuv/sharpyuv_dsp.h +++ b/thirdparty/webp/sharpyuv/sharpyuv_dsp.h @@ -12,9 +12,8 @@ #ifndef WEBP_SHARPYUV_SHARPYUV_DSP_H_ #define WEBP_SHARPYUV_SHARPYUV_DSP_H_ -#include - -#include "src/dsp/cpu.h" +#include "sharpyuv/sharpyuv_cpu.h" +#include "src/webp/types.h" extern uint64_t (*SharpYuvUpdateY)(const uint16_t* src, const uint16_t* ref, uint16_t* dst, int len, int bit_depth); @@ -24,6 +23,6 @@ extern void (*SharpYuvFilterRow)(const int16_t* A, const int16_t* B, int len, const uint16_t* best_y, uint16_t* out, int bit_depth); -void SharpYuvInitDsp(VP8CPUInfo cpu_info_func); +void SharpYuvInitDsp(void); #endif // WEBP_SHARPYUV_SHARPYUV_DSP_H_ diff --git a/thirdparty/webp/sharpyuv/sharpyuv_gamma.c b/thirdparty/webp/sharpyuv/sharpyuv_gamma.c index 05b5436f83..20ab2da6bc 100644 --- a/thirdparty/webp/sharpyuv/sharpyuv_gamma.c +++ b/thirdparty/webp/sharpyuv/sharpyuv_gamma.c @@ -13,7 +13,6 @@ #include #include -#include #include "src/webp/types.h" diff --git a/thirdparty/webp/sharpyuv/sharpyuv_gamma.h b/thirdparty/webp/sharpyuv/sharpyuv_gamma.h index 2f1a3ff4a0..d13aff59e1 100644 --- a/thirdparty/webp/sharpyuv/sharpyuv_gamma.h +++ b/thirdparty/webp/sharpyuv/sharpyuv_gamma.h @@ -12,7 +12,7 @@ #ifndef WEBP_SHARPYUV_SHARPYUV_GAMMA_H_ #define WEBP_SHARPYUV_SHARPYUV_GAMMA_H_ -#include +#include "src/webp/types.h" #ifdef __cplusplus extern "C" { diff --git a/thirdparty/webp/sharpyuv/sharpyuv_neon.c b/thirdparty/webp/sharpyuv/sharpyuv_neon.c index 5cf6aaffb0..5840914865 100644 --- a/thirdparty/webp/sharpyuv/sharpyuv_neon.c +++ b/thirdparty/webp/sharpyuv/sharpyuv_neon.c @@ -17,11 +17,6 @@ #include #include #include -#endif - -extern void InitSharpYuvNEON(void); - -#if defined(WEBP_USE_NEON) static uint16_t clip_NEON(int v, int max) { return (v < 0) ? 0 : (v > max) ? max : (uint16_t)v; @@ -169,6 +164,8 @@ static void SharpYuvFilterRow_NEON(const int16_t* A, const int16_t* B, int len, //------------------------------------------------------------------------------ +extern void InitSharpYuvNEON(void); + WEBP_TSAN_IGNORE_FUNCTION void InitSharpYuvNEON(void) { SharpYuvUpdateY = SharpYuvUpdateY_NEON; SharpYuvUpdateRGB = SharpYuvUpdateRGB_NEON; @@ -177,6 +174,8 @@ WEBP_TSAN_IGNORE_FUNCTION void InitSharpYuvNEON(void) { #else // !WEBP_USE_NEON +extern void InitSharpYuvNEON(void); + void InitSharpYuvNEON(void) {} #endif // WEBP_USE_NEON diff --git a/thirdparty/webp/sharpyuv/sharpyuv_sse2.c b/thirdparty/webp/sharpyuv/sharpyuv_sse2.c index 1943873748..9744d1bb6c 100644 --- a/thirdparty/webp/sharpyuv/sharpyuv_sse2.c +++ b/thirdparty/webp/sharpyuv/sharpyuv_sse2.c @@ -16,11 +16,6 @@ #if defined(WEBP_USE_SSE2) #include #include -#endif - -extern void InitSharpYuvSSE2(void); - -#if defined(WEBP_USE_SSE2) static uint16_t clip_SSE2(int v, int max) { return (v < 0) ? 0 : (v > max) ? max : (uint16_t)v; @@ -199,6 +194,8 @@ WEBP_TSAN_IGNORE_FUNCTION void InitSharpYuvSSE2(void) { } #else // !WEBP_USE_SSE2 +extern void InitSharpYuvSSE2(void); + void InitSharpYuvSSE2(void) {} #endif // WEBP_USE_SSE2 diff --git a/thirdparty/webp/src/dec/Makefile.am b/thirdparty/webp/src/dec/Makefile.am new file mode 100644 index 0000000000..f8c6398d99 --- /dev/null +++ b/thirdparty/webp/src/dec/Makefile.am @@ -0,0 +1,29 @@ +AM_CPPFLAGS += -I$(top_builddir) -I$(top_srcdir) +noinst_LTLIBRARIES = libwebpdecode.la + +libwebpdecode_la_SOURCES = +libwebpdecode_la_SOURCES += alpha_dec.c +libwebpdecode_la_SOURCES += alphai_dec.h +libwebpdecode_la_SOURCES += buffer_dec.c +libwebpdecode_la_SOURCES += common_dec.h +libwebpdecode_la_SOURCES += vp8_dec.h +libwebpdecode_la_SOURCES += frame_dec.c +libwebpdecode_la_SOURCES += idec_dec.c +libwebpdecode_la_SOURCES += io_dec.c +libwebpdecode_la_SOURCES += quant_dec.c +libwebpdecode_la_SOURCES += tree_dec.c +libwebpdecode_la_SOURCES += vp8_dec.c +libwebpdecode_la_SOURCES += vp8i_dec.h +libwebpdecode_la_SOURCES += vp8l_dec.c +libwebpdecode_la_SOURCES += vp8li_dec.h +libwebpdecode_la_SOURCES += webp_dec.c +libwebpdecode_la_SOURCES += webpi_dec.h + +libwebpdecodeinclude_HEADERS = +libwebpdecodeinclude_HEADERS += ../webp/decode.h +libwebpdecodeinclude_HEADERS += ../webp/types.h +noinst_HEADERS = +noinst_HEADERS += ../webp/format_constants.h + +libwebpdecode_la_CPPFLAGS = $(AM_CPPFLAGS) +libwebpdecodeincludedir = $(includedir)/webp diff --git a/thirdparty/webp/src/dec/vp8i_dec.h b/thirdparty/webp/src/dec/vp8i_dec.h index 30c1bd3ef9..83791ecd25 100644 --- a/thirdparty/webp/src/dec/vp8i_dec.h +++ b/thirdparty/webp/src/dec/vp8i_dec.h @@ -31,8 +31,8 @@ extern "C" { // version numbers #define DEC_MAJ_VERSION 1 -#define DEC_MIN_VERSION 2 -#define DEC_REV_VERSION 4 +#define DEC_MIN_VERSION 3 +#define DEC_REV_VERSION 0 // YUV-cache parameters. Cache is 32-bytes wide (= one cacheline). // Constraints are: We need to store one 16x16 block of luma samples (y), diff --git a/thirdparty/webp/src/dec/vp8l_dec.c b/thirdparty/webp/src/dec/vp8l_dec.c index 1348055128..c0ea0181e5 100644 --- a/thirdparty/webp/src/dec/vp8l_dec.c +++ b/thirdparty/webp/src/dec/vp8l_dec.c @@ -1336,7 +1336,7 @@ static int ReadTransform(int* const xsize, int const* ysize, ok = ok && ExpandColorMap(num_colors, transform); break; } - case SUBTRACT_GREEN: + case SUBTRACT_GREEN_TRANSFORM: break; default: assert(0); // can't happen diff --git a/thirdparty/webp/src/dec/webp_dec.c b/thirdparty/webp/src/dec/webp_dec.c index 77a54c55d2..3f4f7bb659 100644 --- a/thirdparty/webp/src/dec/webp_dec.c +++ b/thirdparty/webp/src/dec/webp_dec.c @@ -179,7 +179,7 @@ static VP8StatusCode ParseOptionalChunks(const uint8_t** const data, return VP8_STATUS_BITSTREAM_ERROR; // Not a valid chunk size. } // For odd-sized chunk-payload, there's one byte padding at the end. - disk_chunk_size = (CHUNK_HEADER_SIZE + chunk_size + 1) & ~1; + disk_chunk_size = (CHUNK_HEADER_SIZE + chunk_size + 1) & ~1u; total_size += disk_chunk_size; // Check that total bytes skipped so far does not exceed riff_size. diff --git a/thirdparty/webp/src/demux/Makefile.am b/thirdparty/webp/src/demux/Makefile.am new file mode 100644 index 0000000000..d7392b3ea0 --- /dev/null +++ b/thirdparty/webp/src/demux/Makefile.am @@ -0,0 +1,18 @@ +AM_CPPFLAGS += -I$(top_builddir) -I$(top_srcdir) +lib_LTLIBRARIES = libwebpdemux.la + +libwebpdemux_la_SOURCES = +libwebpdemux_la_SOURCES += anim_decode.c demux.c + +libwebpdemuxinclude_HEADERS = +libwebpdemuxinclude_HEADERS += ../webp/decode.h +libwebpdemuxinclude_HEADERS += ../webp/demux.h +libwebpdemuxinclude_HEADERS += ../webp/mux_types.h +libwebpdemuxinclude_HEADERS += ../webp/types.h +noinst_HEADERS = +noinst_HEADERS += ../webp/format_constants.h + +libwebpdemux_la_LIBADD = ../libwebp.la +libwebpdemux_la_LDFLAGS = -no-undefined -version-info 2:12:0 +libwebpdemuxincludedir = $(includedir)/webp +pkgconfig_DATA = libwebpdemux.pc diff --git a/thirdparty/webp/src/demux/demux.c b/thirdparty/webp/src/demux/demux.c index 41387ec2d6..324e5eb993 100644 --- a/thirdparty/webp/src/demux/demux.c +++ b/thirdparty/webp/src/demux/demux.c @@ -24,8 +24,8 @@ #include "src/webp/format_constants.h" #define DMUX_MAJ_VERSION 1 -#define DMUX_MIN_VERSION 2 -#define DMUX_REV_VERSION 4 +#define DMUX_MIN_VERSION 3 +#define DMUX_REV_VERSION 0 typedef struct { size_t start_; // start location of the data diff --git a/thirdparty/webp/src/demux/libwebpdemux.pc.in b/thirdparty/webp/src/demux/libwebpdemux.pc.in new file mode 100644 index 0000000000..15ed176388 --- /dev/null +++ b/thirdparty/webp/src/demux/libwebpdemux.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libwebpdemux +Description: Library for parsing the WebP graphics format container +Version: @PACKAGE_VERSION@ +Requires: libwebp >= 0.2.0 +Cflags: -I${includedir} +Libs: -L${libdir} -l@webp_libname_prefix@webpdemux diff --git a/thirdparty/webp/src/demux/libwebpdemux.rc b/thirdparty/webp/src/demux/libwebpdemux.rc new file mode 100644 index 0000000000..18353e5900 --- /dev/null +++ b/thirdparty/webp/src/demux/libwebpdemux.rc @@ -0,0 +1,41 @@ +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,3,0 + PRODUCTVERSION 1,0,3,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Google, Inc." + VALUE "FileDescription", "libwebpdemux DLL" + VALUE "FileVersion", "1.3.0" + VALUE "InternalName", "libwebpdemux.dll" + VALUE "LegalCopyright", "Copyright (C) 2022" + VALUE "OriginalFilename", "libwebpdemux.dll" + VALUE "ProductName", "WebP Image Demuxer" + VALUE "ProductVersion", "1.3.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources diff --git a/thirdparty/webp/src/dsp/Makefile.am b/thirdparty/webp/src/dsp/Makefile.am new file mode 100644 index 0000000000..7db4ef0f07 --- /dev/null +++ b/thirdparty/webp/src/dsp/Makefile.am @@ -0,0 +1,187 @@ +AM_CPPFLAGS += -I$(top_builddir) -I$(top_srcdir) +noinst_LTLIBRARIES = +noinst_LTLIBRARIES += libwebpdsp.la +noinst_LTLIBRARIES += libwebpdsp_sse2.la +noinst_LTLIBRARIES += libwebpdspdecode_sse2.la +noinst_LTLIBRARIES += libwebpdsp_sse41.la +noinst_LTLIBRARIES += libwebpdspdecode_sse41.la +noinst_LTLIBRARIES += libwebpdsp_neon.la +noinst_LTLIBRARIES += libwebpdspdecode_neon.la +noinst_LTLIBRARIES += libwebpdsp_msa.la +noinst_LTLIBRARIES += libwebpdspdecode_msa.la +noinst_LTLIBRARIES += libwebpdsp_mips32.la +noinst_LTLIBRARIES += libwebpdspdecode_mips32.la +noinst_LTLIBRARIES += libwebpdsp_mips_dsp_r2.la +noinst_LTLIBRARIES += libwebpdspdecode_mips_dsp_r2.la + +if BUILD_LIBWEBPDECODER + noinst_LTLIBRARIES += libwebpdspdecode.la +endif + +common_HEADERS = ../webp/types.h +commondir = $(includedir)/webp + +COMMON_SOURCES = +COMMON_SOURCES += alpha_processing.c +COMMON_SOURCES += cpu.c +COMMON_SOURCES += cpu.h +COMMON_SOURCES += dec.c +COMMON_SOURCES += dec_clip_tables.c +COMMON_SOURCES += dsp.h +COMMON_SOURCES += filters.c +COMMON_SOURCES += lossless.c +COMMON_SOURCES += lossless.h +COMMON_SOURCES += lossless_common.h +COMMON_SOURCES += rescaler.c +COMMON_SOURCES += upsampling.c +COMMON_SOURCES += yuv.c +COMMON_SOURCES += yuv.h + +ENC_SOURCES = +ENC_SOURCES += cost.c +ENC_SOURCES += enc.c +ENC_SOURCES += lossless_enc.c +ENC_SOURCES += quant.h +ENC_SOURCES += ssim.c + +libwebpdspdecode_sse41_la_SOURCES = +libwebpdspdecode_sse41_la_SOURCES += alpha_processing_sse41.c +libwebpdspdecode_sse41_la_SOURCES += dec_sse41.c +libwebpdspdecode_sse41_la_SOURCES += lossless_sse41.c +libwebpdspdecode_sse41_la_SOURCES += upsampling_sse41.c +libwebpdspdecode_sse41_la_SOURCES += yuv_sse41.c +libwebpdspdecode_sse41_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) +libwebpdspdecode_sse41_la_CFLAGS = $(AM_CFLAGS) $(SSE41_FLAGS) + +libwebpdspdecode_sse2_la_SOURCES = +libwebpdspdecode_sse2_la_SOURCES += alpha_processing_sse2.c +libwebpdspdecode_sse2_la_SOURCES += common_sse2.h +libwebpdspdecode_sse2_la_SOURCES += dec_sse2.c +libwebpdspdecode_sse2_la_SOURCES += filters_sse2.c +libwebpdspdecode_sse2_la_SOURCES += lossless_sse2.c +libwebpdspdecode_sse2_la_SOURCES += rescaler_sse2.c +libwebpdspdecode_sse2_la_SOURCES += upsampling_sse2.c +libwebpdspdecode_sse2_la_SOURCES += yuv_sse2.c +libwebpdspdecode_sse2_la_CPPFLAGS = $(libwebpdsp_sse2_la_CPPFLAGS) +libwebpdspdecode_sse2_la_CFLAGS = $(libwebpdsp_sse2_la_CFLAGS) + +libwebpdspdecode_neon_la_SOURCES = +libwebpdspdecode_neon_la_SOURCES += alpha_processing_neon.c +libwebpdspdecode_neon_la_SOURCES += dec_neon.c +libwebpdspdecode_neon_la_SOURCES += filters_neon.c +libwebpdspdecode_neon_la_SOURCES += lossless_neon.c +libwebpdspdecode_neon_la_SOURCES += neon.h +libwebpdspdecode_neon_la_SOURCES += rescaler_neon.c +libwebpdspdecode_neon_la_SOURCES += upsampling_neon.c +libwebpdspdecode_neon_la_SOURCES += yuv_neon.c +libwebpdspdecode_neon_la_CPPFLAGS = $(libwebpdsp_neon_la_CPPFLAGS) +libwebpdspdecode_neon_la_CFLAGS = $(libwebpdsp_neon_la_CFLAGS) + +libwebpdspdecode_msa_la_SOURCES = +libwebpdspdecode_msa_la_SOURCES += dec_msa.c +libwebpdspdecode_msa_la_SOURCES += filters_msa.c +libwebpdspdecode_msa_la_SOURCES += lossless_msa.c +libwebpdspdecode_msa_la_SOURCES += msa_macro.h +libwebpdspdecode_msa_la_SOURCES += rescaler_msa.c +libwebpdspdecode_msa_la_SOURCES += upsampling_msa.c +libwebpdspdecode_msa_la_CPPFLAGS = $(libwebpdsp_msa_la_CPPFLAGS) +libwebpdspdecode_msa_la_CFLAGS = $(libwebpdsp_msa_la_CFLAGS) + +libwebpdspdecode_mips32_la_SOURCES = +libwebpdspdecode_mips32_la_SOURCES += dec_mips32.c +libwebpdspdecode_mips32_la_SOURCES += mips_macro.h +libwebpdspdecode_mips32_la_SOURCES += rescaler_mips32.c +libwebpdspdecode_mips32_la_SOURCES += yuv_mips32.c +libwebpdspdecode_mips32_la_CPPFLAGS = $(libwebpdsp_mips32_la_CPPFLAGS) +libwebpdspdecode_mips32_la_CFLAGS = $(libwebpdsp_mips32_la_CFLAGS) + +libwebpdspdecode_mips_dsp_r2_la_SOURCES = +libwebpdspdecode_mips_dsp_r2_la_SOURCES += alpha_processing_mips_dsp_r2.c +libwebpdspdecode_mips_dsp_r2_la_SOURCES += dec_mips_dsp_r2.c +libwebpdspdecode_mips_dsp_r2_la_SOURCES += filters_mips_dsp_r2.c +libwebpdspdecode_mips_dsp_r2_la_SOURCES += lossless_mips_dsp_r2.c +libwebpdspdecode_mips_dsp_r2_la_SOURCES += mips_macro.h +libwebpdspdecode_mips_dsp_r2_la_SOURCES += rescaler_mips_dsp_r2.c +libwebpdspdecode_mips_dsp_r2_la_SOURCES += upsampling_mips_dsp_r2.c +libwebpdspdecode_mips_dsp_r2_la_SOURCES += yuv_mips_dsp_r2.c +libwebpdspdecode_mips_dsp_r2_la_CPPFLAGS = $(libwebpdsp_mips_dsp_r2_la_CPPFLAGS) +libwebpdspdecode_mips_dsp_r2_la_CFLAGS = $(libwebpdsp_mips_dsp_r2_la_CFLAGS) + +libwebpdsp_sse2_la_SOURCES = +libwebpdsp_sse2_la_SOURCES += cost_sse2.c +libwebpdsp_sse2_la_SOURCES += enc_sse2.c +libwebpdsp_sse2_la_SOURCES += lossless_enc_sse2.c +libwebpdsp_sse2_la_SOURCES += ssim_sse2.c +libwebpdsp_sse2_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) +libwebpdsp_sse2_la_CFLAGS = $(AM_CFLAGS) $(SSE2_FLAGS) +libwebpdsp_sse2_la_LIBADD = libwebpdspdecode_sse2.la + +libwebpdsp_sse41_la_SOURCES = +libwebpdsp_sse41_la_SOURCES += enc_sse41.c +libwebpdsp_sse41_la_SOURCES += lossless_enc_sse41.c +libwebpdsp_sse41_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) +libwebpdsp_sse41_la_CFLAGS = $(AM_CFLAGS) $(SSE41_FLAGS) +libwebpdsp_sse41_la_LIBADD = libwebpdspdecode_sse41.la + +libwebpdsp_neon_la_SOURCES = +libwebpdsp_neon_la_SOURCES += cost_neon.c +libwebpdsp_neon_la_SOURCES += enc_neon.c +libwebpdsp_neon_la_SOURCES += lossless_enc_neon.c +libwebpdsp_neon_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) +libwebpdsp_neon_la_CFLAGS = $(AM_CFLAGS) $(NEON_FLAGS) +libwebpdsp_neon_la_LIBADD = libwebpdspdecode_neon.la + +libwebpdsp_msa_la_SOURCES = +libwebpdsp_msa_la_SOURCES += enc_msa.c +libwebpdsp_msa_la_SOURCES += lossless_enc_msa.c +libwebpdsp_msa_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) +libwebpdsp_msa_la_CFLAGS = $(AM_CFLAGS) +libwebpdsp_msa_la_LIBADD = libwebpdspdecode_msa.la + +libwebpdsp_mips32_la_SOURCES = +libwebpdsp_mips32_la_SOURCES += cost_mips32.c +libwebpdsp_mips32_la_SOURCES += enc_mips32.c +libwebpdsp_mips32_la_SOURCES += lossless_enc_mips32.c +libwebpdsp_mips32_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) +libwebpdsp_mips32_la_CFLAGS = $(AM_CFLAGS) +libwebpdsp_mips32_la_LIBADD = libwebpdspdecode_mips32.la + +libwebpdsp_mips_dsp_r2_la_SOURCES = +libwebpdsp_mips_dsp_r2_la_SOURCES += cost_mips_dsp_r2.c +libwebpdsp_mips_dsp_r2_la_SOURCES += enc_mips_dsp_r2.c +libwebpdsp_mips_dsp_r2_la_SOURCES += lossless_enc_mips_dsp_r2.c +libwebpdsp_mips_dsp_r2_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) +libwebpdsp_mips_dsp_r2_la_CFLAGS = $(AM_CFLAGS) +libwebpdsp_mips_dsp_r2_la_LIBADD = libwebpdspdecode_mips_dsp_r2.la + +libwebpdsp_la_SOURCES = $(COMMON_SOURCES) $(ENC_SOURCES) + +noinst_HEADERS = +noinst_HEADERS += ../dec/vp8_dec.h +noinst_HEADERS += ../webp/decode.h + +libwebpdsp_la_CPPFLAGS = +libwebpdsp_la_CPPFLAGS += $(AM_CPPFLAGS) +libwebpdsp_la_CPPFLAGS += $(USE_SWAP_16BIT_CSP) +libwebpdsp_la_LDFLAGS = -lm +libwebpdsp_la_LIBADD = +libwebpdsp_la_LIBADD += libwebpdsp_sse2.la +libwebpdsp_la_LIBADD += libwebpdsp_sse41.la +libwebpdsp_la_LIBADD += libwebpdsp_neon.la +libwebpdsp_la_LIBADD += libwebpdsp_msa.la +libwebpdsp_la_LIBADD += libwebpdsp_mips32.la +libwebpdsp_la_LIBADD += libwebpdsp_mips_dsp_r2.la + +if BUILD_LIBWEBPDECODER + libwebpdspdecode_la_SOURCES = $(COMMON_SOURCES) + + libwebpdspdecode_la_CPPFLAGS = $(libwebpdsp_la_CPPFLAGS) + libwebpdspdecode_la_LDFLAGS = $(libwebpdsp_la_LDFLAGS) + libwebpdspdecode_la_LIBADD = + libwebpdspdecode_la_LIBADD += libwebpdspdecode_sse2.la + libwebpdspdecode_la_LIBADD += libwebpdspdecode_sse41.la + libwebpdspdecode_la_LIBADD += libwebpdspdecode_neon.la + libwebpdspdecode_la_LIBADD += libwebpdspdecode_msa.la + libwebpdspdecode_la_LIBADD += libwebpdspdecode_mips32.la + libwebpdspdecode_la_LIBADD += libwebpdspdecode_mips_dsp_r2.la +endif diff --git a/thirdparty/webp/src/dsp/alpha_processing_sse2.c b/thirdparty/webp/src/dsp/alpha_processing_sse2.c index a5f8c9f7c7..f0843d0feb 100644 --- a/thirdparty/webp/src/dsp/alpha_processing_sse2.c +++ b/thirdparty/webp/src/dsp/alpha_processing_sse2.c @@ -26,8 +26,8 @@ static int DispatchAlpha_SSE2(const uint8_t* WEBP_RESTRICT alpha, uint32_t alpha_and = 0xff; int i, j; const __m128i zero = _mm_setzero_si128(); - const __m128i rgb_mask = _mm_set1_epi32(0xffffff00u); // to preserve RGB - const __m128i all_0xff = _mm_set_epi32(0, 0, ~0u, ~0u); + const __m128i rgb_mask = _mm_set1_epi32((int)0xffffff00); // to preserve RGB + const __m128i all_0xff = _mm_set_epi32(0, 0, ~0, ~0); __m128i all_alphas = all_0xff; // We must be able to access 3 extra bytes after the last written byte @@ -106,8 +106,8 @@ static int ExtractAlpha_SSE2(const uint8_t* WEBP_RESTRICT argb, int argb_stride, // value is not 0xff if any of the alpha[] is not equal to 0xff. uint32_t alpha_and = 0xff; int i, j; - const __m128i a_mask = _mm_set1_epi32(0xffu); // to preserve alpha - const __m128i all_0xff = _mm_set_epi32(0, 0, ~0u, ~0u); + const __m128i a_mask = _mm_set1_epi32(0xff); // to preserve alpha + const __m128i all_0xff = _mm_set_epi32(0, 0, ~0, ~0); __m128i all_alphas = all_0xff; // We must be able to access 3 extra bytes after the last written byte @@ -178,7 +178,7 @@ static int ExtractAlpha_SSE2(const uint8_t* WEBP_RESTRICT argb, int argb_stride, static void ApplyAlphaMultiply_SSE2(uint8_t* rgba, int alpha_first, int w, int h, int stride) { const __m128i zero = _mm_setzero_si128(); - const __m128i kMult = _mm_set1_epi16(0x8081u); + const __m128i kMult = _mm_set1_epi16((short)0x8081); const __m128i kMask = _mm_set_epi16(0, 0xff, 0xff, 0, 0, 0xff, 0xff, 0); const int kSpan = 4; while (h-- > 0) { @@ -267,7 +267,7 @@ static int HasAlpha32b_SSE2(const uint8_t* src, int length) { } static void AlphaReplace_SSE2(uint32_t* src, int length, uint32_t color) { - const __m128i m_color = _mm_set1_epi32(color); + const __m128i m_color = _mm_set1_epi32((int)color); const __m128i zero = _mm_setzero_si128(); int i = 0; for (; i + 8 <= length; i += 8) { diff --git a/thirdparty/webp/src/dsp/alpha_processing_sse41.c b/thirdparty/webp/src/dsp/alpha_processing_sse41.c index cdf877ce49..1156ac3417 100644 --- a/thirdparty/webp/src/dsp/alpha_processing_sse41.c +++ b/thirdparty/webp/src/dsp/alpha_processing_sse41.c @@ -26,7 +26,7 @@ static int ExtractAlpha_SSE41(const uint8_t* WEBP_RESTRICT argb, // value is not 0xff if any of the alpha[] is not equal to 0xff. uint32_t alpha_and = 0xff; int i, j; - const __m128i all_0xff = _mm_set1_epi32(~0u); + const __m128i all_0xff = _mm_set1_epi32(~0); __m128i all_alphas = all_0xff; // We must be able to access 3 extra bytes after the last written byte diff --git a/thirdparty/webp/src/dsp/cpu.c b/thirdparty/webp/src/dsp/cpu.c index a4ba7f2cb7..62de73f750 100644 --- a/thirdparty/webp/src/dsp/cpu.c +++ b/thirdparty/webp/src/dsp/cpu.c @@ -212,7 +212,7 @@ VP8CPUInfo VP8GetCPUInfo = wasmCPUInfo; #elif defined(WEBP_HAVE_NEON) // In most cases this function doesn't check for NEON support (it's assumed by // the configuration), but enables turning off NEON at runtime, for testing -// purposes, by setting VP8DecGetCPUInfo = NULL. +// purposes, by setting VP8GetCPUInfo = NULL. static int armCPUInfo(CPUFeature feature) { if (feature != kNEON) return 0; #if defined(__linux__) && defined(WEBP_HAVE_NEON_RTCD) diff --git a/thirdparty/webp/src/dsp/cpu.h b/thirdparty/webp/src/dsp/cpu.h index 57a40d87d4..be80727c0d 100644 --- a/thirdparty/webp/src/dsp/cpu.h +++ b/thirdparty/webp/src/dsp/cpu.h @@ -14,6 +14,8 @@ #ifndef WEBP_DSP_CPU_H_ #define WEBP_DSP_CPU_H_ +#include + #ifdef HAVE_CONFIG_H #include "src/webp/config.h" #endif diff --git a/thirdparty/webp/src/dsp/dec_sse2.c b/thirdparty/webp/src/dsp/dec_sse2.c index 873aa59e8a..01e6bcb636 100644 --- a/thirdparty/webp/src/dsp/dec_sse2.c +++ b/thirdparty/webp/src/dsp/dec_sse2.c @@ -158,10 +158,10 @@ static void Transform_SSE2(const int16_t* in, uint8_t* dst, int do_two) { dst3 = _mm_loadl_epi64((__m128i*)(dst + 3 * BPS)); } else { // Load four bytes/pixels per line. - dst0 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 0 * BPS)); - dst1 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 1 * BPS)); - dst2 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 2 * BPS)); - dst3 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 3 * BPS)); + dst0 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 0 * BPS)); + dst1 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 1 * BPS)); + dst2 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 2 * BPS)); + dst3 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 3 * BPS)); } // Convert to 16b. dst0 = _mm_unpacklo_epi8(dst0, zero); @@ -187,10 +187,10 @@ static void Transform_SSE2(const int16_t* in, uint8_t* dst, int do_two) { _mm_storel_epi64((__m128i*)(dst + 3 * BPS), dst3); } else { // Store four bytes/pixels per line. - WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(dst0)); - WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(dst1)); - WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(dst2)); - WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(dst3)); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(dst0)); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(dst1)); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(dst2)); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(dst3)); } } } @@ -213,10 +213,10 @@ static void TransformAC3(const int16_t* in, uint8_t* dst) { const __m128i m3 = _mm_subs_epi16(B, d4); const __m128i zero = _mm_setzero_si128(); // Load the source pixels. - __m128i dst0 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 0 * BPS)); - __m128i dst1 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 1 * BPS)); - __m128i dst2 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 2 * BPS)); - __m128i dst3 = _mm_cvtsi32_si128(WebPMemToUint32(dst + 3 * BPS)); + __m128i dst0 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 0 * BPS)); + __m128i dst1 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 1 * BPS)); + __m128i dst2 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 2 * BPS)); + __m128i dst3 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 3 * BPS)); // Convert to 16b. dst0 = _mm_unpacklo_epi8(dst0, zero); dst1 = _mm_unpacklo_epi8(dst1, zero); @@ -233,10 +233,10 @@ static void TransformAC3(const int16_t* in, uint8_t* dst) { dst2 = _mm_packus_epi16(dst2, dst2); dst3 = _mm_packus_epi16(dst3, dst3); // Store the results. - WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(dst0)); - WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(dst1)); - WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(dst2)); - WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(dst3)); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(dst0)); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(dst1)); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(dst2)); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(dst3)); } #undef MUL #endif // USE_TRANSFORM_AC3 @@ -477,11 +477,11 @@ static WEBP_INLINE void Load8x4_SSE2(const uint8_t* const b, int stride, // A0 = 63 62 61 60 23 22 21 20 43 42 41 40 03 02 01 00 // A1 = 73 72 71 70 33 32 31 30 53 52 51 50 13 12 11 10 const __m128i A0 = _mm_set_epi32( - WebPMemToUint32(&b[6 * stride]), WebPMemToUint32(&b[2 * stride]), - WebPMemToUint32(&b[4 * stride]), WebPMemToUint32(&b[0 * stride])); + WebPMemToInt32(&b[6 * stride]), WebPMemToInt32(&b[2 * stride]), + WebPMemToInt32(&b[4 * stride]), WebPMemToInt32(&b[0 * stride])); const __m128i A1 = _mm_set_epi32( - WebPMemToUint32(&b[7 * stride]), WebPMemToUint32(&b[3 * stride]), - WebPMemToUint32(&b[5 * stride]), WebPMemToUint32(&b[1 * stride])); + WebPMemToInt32(&b[7 * stride]), WebPMemToInt32(&b[3 * stride]), + WebPMemToInt32(&b[5 * stride]), WebPMemToInt32(&b[1 * stride])); // B0 = 53 43 52 42 51 41 50 40 13 03 12 02 11 01 10 00 // B1 = 73 63 72 62 71 61 70 60 33 23 32 22 31 21 30 20 @@ -540,7 +540,7 @@ static WEBP_INLINE void Store4x4_SSE2(__m128i* const x, uint8_t* dst, int stride) { int i; for (i = 0; i < 4; ++i, dst += stride) { - WebPUint32ToMem(dst, _mm_cvtsi128_si32(*x)); + WebPInt32ToMem(dst, _mm_cvtsi128_si32(*x)); *x = _mm_srli_si128(*x, 4); } } @@ -908,10 +908,10 @@ static void VE4_SSE2(uint8_t* dst) { // vertical const __m128i lsb = _mm_and_si128(_mm_xor_si128(ABCDEFGH, CDEFGH00), one); const __m128i b = _mm_subs_epu8(a, lsb); const __m128i avg = _mm_avg_epu8(b, BCDEFGH0); - const uint32_t vals = _mm_cvtsi128_si32(avg); + const int vals = _mm_cvtsi128_si32(avg); int i; for (i = 0; i < 4; ++i) { - WebPUint32ToMem(dst + i * BPS, vals); + WebPInt32ToMem(dst + i * BPS, vals); } } @@ -925,10 +925,10 @@ static void LD4_SSE2(uint8_t* dst) { // Down-Left const __m128i lsb = _mm_and_si128(_mm_xor_si128(ABCDEFGH, CDEFGHH0), one); const __m128i avg2 = _mm_subs_epu8(avg1, lsb); const __m128i abcdefg = _mm_avg_epu8(avg2, BCDEFGH0); - WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcdefg )); - WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); - WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); - WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcdefg )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); } static void VR4_SSE2(uint8_t* dst) { // Vertical-Right @@ -946,10 +946,10 @@ static void VR4_SSE2(uint8_t* dst) { // Vertical-Right const __m128i lsb = _mm_and_si128(_mm_xor_si128(IXABCD, ABCD0), one); const __m128i avg2 = _mm_subs_epu8(avg1, lsb); const __m128i efgh = _mm_avg_epu8(avg2, XABCD); - WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcd )); - WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( efgh )); - WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(abcd, 1))); - WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(efgh, 1))); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcd )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( efgh )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(abcd, 1))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(efgh, 1))); // these two are hard to implement in SSE2, so we keep the C-version: DST(0, 2) = AVG3(J, I, X); @@ -970,11 +970,12 @@ static void VL4_SSE2(uint8_t* dst) { // Vertical-Left const __m128i abbc = _mm_or_si128(ab, bc); const __m128i lsb2 = _mm_and_si128(abbc, lsb1); const __m128i avg4 = _mm_subs_epu8(avg3, lsb2); - const uint32_t extra_out = _mm_cvtsi128_si32(_mm_srli_si128(avg4, 4)); - WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( avg1 )); - WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( avg4 )); - WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg1, 1))); - WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg4, 1))); + const uint32_t extra_out = + (uint32_t)_mm_cvtsi128_si32(_mm_srli_si128(avg4, 4)); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( avg1 )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( avg4 )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg1, 1))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg4, 1))); // these two are hard to get and irregular DST(3, 2) = (extra_out >> 0) & 0xff; @@ -990,7 +991,7 @@ static void RD4_SSE2(uint8_t* dst) { // Down-right const uint32_t K = dst[-1 + 2 * BPS]; const uint32_t L = dst[-1 + 3 * BPS]; const __m128i LKJI_____ = - _mm_cvtsi32_si128(L | (K << 8) | (J << 16) | (I << 24)); + _mm_cvtsi32_si128((int)(L | (K << 8) | (J << 16) | (I << 24))); const __m128i LKJIXABCD = _mm_or_si128(LKJI_____, ____XABCD); const __m128i KJIXABCD_ = _mm_srli_si128(LKJIXABCD, 1); const __m128i JIXABCD__ = _mm_srli_si128(LKJIXABCD, 2); @@ -998,10 +999,10 @@ static void RD4_SSE2(uint8_t* dst) { // Down-right const __m128i lsb = _mm_and_si128(_mm_xor_si128(JIXABCD__, LKJIXABCD), one); const __m128i avg2 = _mm_subs_epu8(avg1, lsb); const __m128i abcdefg = _mm_avg_epu8(avg2, KJIXABCD_); - WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32( abcdefg )); - WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); - WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); - WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32( abcdefg )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); } #undef DST @@ -1015,13 +1016,13 @@ static WEBP_INLINE void TrueMotion_SSE2(uint8_t* dst, int size) { const __m128i zero = _mm_setzero_si128(); int y; if (size == 4) { - const __m128i top_values = _mm_cvtsi32_si128(WebPMemToUint32(top)); + const __m128i top_values = _mm_cvtsi32_si128(WebPMemToInt32(top)); const __m128i top_base = _mm_unpacklo_epi8(top_values, zero); for (y = 0; y < 4; ++y, dst += BPS) { const int val = dst[-1] - top[-1]; const __m128i base = _mm_set1_epi16(val); const __m128i out = _mm_packus_epi16(_mm_add_epi16(base, top_base), zero); - WebPUint32ToMem(dst, _mm_cvtsi128_si32(out)); + WebPInt32ToMem(dst, _mm_cvtsi128_si32(out)); } } else if (size == 8) { const __m128i top_values = _mm_loadl_epi64((const __m128i*)top); @@ -1062,7 +1063,7 @@ static void VE16_SSE2(uint8_t* dst) { static void HE16_SSE2(uint8_t* dst) { // horizontal int j; for (j = 16; j > 0; --j) { - const __m128i values = _mm_set1_epi8(dst[-1]); + const __m128i values = _mm_set1_epi8((char)dst[-1]); _mm_storeu_si128((__m128i*)dst, values); dst += BPS; } @@ -1070,7 +1071,7 @@ static void HE16_SSE2(uint8_t* dst) { // horizontal static WEBP_INLINE void Put16_SSE2(uint8_t v, uint8_t* dst) { int j; - const __m128i values = _mm_set1_epi8(v); + const __m128i values = _mm_set1_epi8((char)v); for (j = 0; j < 16; ++j) { _mm_storeu_si128((__m128i*)(dst + j * BPS), values); } @@ -1130,7 +1131,7 @@ static void VE8uv_SSE2(uint8_t* dst) { // vertical // helper for chroma-DC predictions static WEBP_INLINE void Put8x8uv_SSE2(uint8_t v, uint8_t* dst) { int j; - const __m128i values = _mm_set1_epi8(v); + const __m128i values = _mm_set1_epi8((char)v); for (j = 0; j < 8; ++j) { _mm_storel_epi64((__m128i*)(dst + j * BPS), values); } diff --git a/thirdparty/webp/src/dsp/dec_sse41.c b/thirdparty/webp/src/dsp/dec_sse41.c index 8f18506d54..08a3630272 100644 --- a/thirdparty/webp/src/dsp/dec_sse41.c +++ b/thirdparty/webp/src/dsp/dec_sse41.c @@ -23,7 +23,7 @@ static void HE16_SSE41(uint8_t* dst) { // horizontal int j; const __m128i kShuffle3 = _mm_set1_epi8(3); for (j = 16; j > 0; --j) { - const __m128i in = _mm_cvtsi32_si128(WebPMemToUint32(dst - 4)); + const __m128i in = _mm_cvtsi32_si128(WebPMemToInt32(dst - 4)); const __m128i values = _mm_shuffle_epi8(in, kShuffle3); _mm_storeu_si128((__m128i*)dst, values); dst += BPS; diff --git a/thirdparty/webp/src/dsp/enc_neon.c b/thirdparty/webp/src/dsp/enc_neon.c index 601962ba76..3a04111c55 100644 --- a/thirdparty/webp/src/dsp/enc_neon.c +++ b/thirdparty/webp/src/dsp/enc_neon.c @@ -764,9 +764,14 @@ static WEBP_INLINE void AccumulateSSE16_NEON(const uint8_t* const a, // Horizontal sum of all four uint32_t values in 'sum'. static int SumToInt_NEON(uint32x4_t sum) { +#if defined(__aarch64__) + return (int)vaddvq_u32(sum); +#else const uint64x2_t sum2 = vpaddlq_u32(sum); - const uint64_t sum3 = vgetq_lane_u64(sum2, 0) + vgetq_lane_u64(sum2, 1); - return (int)sum3; + const uint32x2_t sum3 = vadd_u32(vreinterpret_u32_u64(vget_low_u64(sum2)), + vreinterpret_u32_u64(vget_high_u64(sum2))); + return (int)vget_lane_u32(sum3, 0); +#endif } static int SSE16x16_NEON(const uint8_t* a, const uint8_t* b) { diff --git a/thirdparty/webp/src/dsp/enc_sse2.c b/thirdparty/webp/src/dsp/enc_sse2.c index b2e78ed941..1d1055668f 100644 --- a/thirdparty/webp/src/dsp/enc_sse2.c +++ b/thirdparty/webp/src/dsp/enc_sse2.c @@ -156,10 +156,10 @@ static void ITransform_SSE2(const uint8_t* ref, const int16_t* in, uint8_t* dst, ref3 = _mm_loadl_epi64((const __m128i*)&ref[3 * BPS]); } else { // Load four bytes/pixels per line. - ref0 = _mm_cvtsi32_si128(WebPMemToUint32(&ref[0 * BPS])); - ref1 = _mm_cvtsi32_si128(WebPMemToUint32(&ref[1 * BPS])); - ref2 = _mm_cvtsi32_si128(WebPMemToUint32(&ref[2 * BPS])); - ref3 = _mm_cvtsi32_si128(WebPMemToUint32(&ref[3 * BPS])); + ref0 = _mm_cvtsi32_si128(WebPMemToInt32(&ref[0 * BPS])); + ref1 = _mm_cvtsi32_si128(WebPMemToInt32(&ref[1 * BPS])); + ref2 = _mm_cvtsi32_si128(WebPMemToInt32(&ref[2 * BPS])); + ref3 = _mm_cvtsi32_si128(WebPMemToInt32(&ref[3 * BPS])); } // Convert to 16b. ref0 = _mm_unpacklo_epi8(ref0, zero); @@ -185,10 +185,10 @@ static void ITransform_SSE2(const uint8_t* ref, const int16_t* in, uint8_t* dst, _mm_storel_epi64((__m128i*)&dst[3 * BPS], ref3); } else { // Store four bytes/pixels per line. - WebPUint32ToMem(&dst[0 * BPS], _mm_cvtsi128_si32(ref0)); - WebPUint32ToMem(&dst[1 * BPS], _mm_cvtsi128_si32(ref1)); - WebPUint32ToMem(&dst[2 * BPS], _mm_cvtsi128_si32(ref2)); - WebPUint32ToMem(&dst[3 * BPS], _mm_cvtsi128_si32(ref3)); + WebPInt32ToMem(&dst[0 * BPS], _mm_cvtsi128_si32(ref0)); + WebPInt32ToMem(&dst[1 * BPS], _mm_cvtsi128_si32(ref1)); + WebPInt32ToMem(&dst[2 * BPS], _mm_cvtsi128_si32(ref2)); + WebPInt32ToMem(&dst[3 * BPS], _mm_cvtsi128_si32(ref3)); } } } @@ -481,7 +481,7 @@ static void CollectHistogram_SSE2(const uint8_t* ref, const uint8_t* pred, // helper for chroma-DC predictions static WEBP_INLINE void Put8x8uv_SSE2(uint8_t v, uint8_t* dst) { int j; - const __m128i values = _mm_set1_epi8(v); + const __m128i values = _mm_set1_epi8((char)v); for (j = 0; j < 8; ++j) { _mm_storel_epi64((__m128i*)(dst + j * BPS), values); } @@ -489,7 +489,7 @@ static WEBP_INLINE void Put8x8uv_SSE2(uint8_t v, uint8_t* dst) { static WEBP_INLINE void Put16_SSE2(uint8_t v, uint8_t* dst) { int j; - const __m128i values = _mm_set1_epi8(v); + const __m128i values = _mm_set1_epi8((char)v); for (j = 0; j < 16; ++j) { _mm_store_si128((__m128i*)(dst + j * BPS), values); } @@ -540,7 +540,7 @@ static WEBP_INLINE void VerticalPred_SSE2(uint8_t* dst, static WEBP_INLINE void HE8uv_SSE2(uint8_t* dst, const uint8_t* left) { int j; for (j = 0; j < 8; ++j) { - const __m128i values = _mm_set1_epi8(left[j]); + const __m128i values = _mm_set1_epi8((char)left[j]); _mm_storel_epi64((__m128i*)dst, values); dst += BPS; } @@ -549,7 +549,7 @@ static WEBP_INLINE void HE8uv_SSE2(uint8_t* dst, const uint8_t* left) { static WEBP_INLINE void HE16_SSE2(uint8_t* dst, const uint8_t* left) { int j; for (j = 0; j < 16; ++j) { - const __m128i values = _mm_set1_epi8(left[j]); + const __m128i values = _mm_set1_epi8((char)left[j]); _mm_store_si128((__m128i*)dst, values); dst += BPS; } @@ -722,10 +722,10 @@ static WEBP_INLINE void VE4_SSE2(uint8_t* dst, const __m128i lsb = _mm_and_si128(_mm_xor_si128(ABCDEFGH, CDEFGH00), one); const __m128i b = _mm_subs_epu8(a, lsb); const __m128i avg = _mm_avg_epu8(b, BCDEFGH0); - const uint32_t vals = _mm_cvtsi128_si32(avg); + const int vals = _mm_cvtsi128_si32(avg); int i; for (i = 0; i < 4; ++i) { - WebPUint32ToMem(dst + i * BPS, vals); + WebPInt32ToMem(dst + i * BPS, vals); } } @@ -760,10 +760,10 @@ static WEBP_INLINE void LD4_SSE2(uint8_t* dst, const __m128i lsb = _mm_and_si128(_mm_xor_si128(ABCDEFGH, CDEFGHH0), one); const __m128i avg2 = _mm_subs_epu8(avg1, lsb); const __m128i abcdefg = _mm_avg_epu8(avg2, BCDEFGH0); - WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcdefg )); - WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); - WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); - WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcdefg )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); } static WEBP_INLINE void VR4_SSE2(uint8_t* dst, @@ -782,10 +782,10 @@ static WEBP_INLINE void VR4_SSE2(uint8_t* dst, const __m128i lsb = _mm_and_si128(_mm_xor_si128(IXABCD, ABCD0), one); const __m128i avg2 = _mm_subs_epu8(avg1, lsb); const __m128i efgh = _mm_avg_epu8(avg2, XABCD); - WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcd )); - WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( efgh )); - WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(abcd, 1))); - WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(efgh, 1))); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcd )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( efgh )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(abcd, 1))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(efgh, 1))); // these two are hard to implement in SSE2, so we keep the C-version: DST(0, 2) = AVG3(J, I, X); @@ -807,11 +807,12 @@ static WEBP_INLINE void VL4_SSE2(uint8_t* dst, const __m128i abbc = _mm_or_si128(ab, bc); const __m128i lsb2 = _mm_and_si128(abbc, lsb1); const __m128i avg4 = _mm_subs_epu8(avg3, lsb2); - const uint32_t extra_out = _mm_cvtsi128_si32(_mm_srli_si128(avg4, 4)); - WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( avg1 )); - WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( avg4 )); - WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg1, 1))); - WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg4, 1))); + const uint32_t extra_out = + (uint32_t)_mm_cvtsi128_si32(_mm_srli_si128(avg4, 4)); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( avg1 )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( avg4 )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg1, 1))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg4, 1))); // these two are hard to get and irregular DST(3, 2) = (extra_out >> 0) & 0xff; @@ -829,10 +830,10 @@ static WEBP_INLINE void RD4_SSE2(uint8_t* dst, const __m128i lsb = _mm_and_si128(_mm_xor_si128(JIXABCD__, LKJIXABCD), one); const __m128i avg2 = _mm_subs_epu8(avg1, lsb); const __m128i abcdefg = _mm_avg_epu8(avg2, KJIXABCD_); - WebPUint32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32( abcdefg )); - WebPUint32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); - WebPUint32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); - WebPUint32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32( abcdefg )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); } static WEBP_INLINE void HU4_SSE2(uint8_t* dst, const uint8_t* top) { @@ -875,14 +876,14 @@ static WEBP_INLINE void HD4_SSE2(uint8_t* dst, const uint8_t* top) { static WEBP_INLINE void TM4_SSE2(uint8_t* dst, const uint8_t* top) { const __m128i zero = _mm_setzero_si128(); - const __m128i top_values = _mm_cvtsi32_si128(WebPMemToUint32(top)); + const __m128i top_values = _mm_cvtsi32_si128(WebPMemToInt32(top)); const __m128i top_base = _mm_unpacklo_epi8(top_values, zero); int y; for (y = 0; y < 4; ++y, dst += BPS) { const int val = top[-2 - y] - top[-1]; const __m128i base = _mm_set1_epi16(val); const __m128i out = _mm_packus_epi16(_mm_add_epi16(base, top_base), zero); - WebPUint32ToMem(dst, _mm_cvtsi128_si32(out)); + WebPInt32ToMem(dst, _mm_cvtsi128_si32(out)); } } diff --git a/thirdparty/webp/src/dsp/lossless.c b/thirdparty/webp/src/dsp/lossless.c index 84a54296fd..fb86e58d4a 100644 --- a/thirdparty/webp/src/dsp/lossless.c +++ b/thirdparty/webp/src/dsp/lossless.c @@ -49,7 +49,7 @@ static WEBP_INLINE uint32_t Clip255(uint32_t a) { } static WEBP_INLINE int AddSubtractComponentFull(int a, int b, int c) { - return Clip255(a + b - c); + return Clip255((uint32_t)(a + b - c)); } static WEBP_INLINE uint32_t ClampedAddSubtractFull(uint32_t c0, uint32_t c1, @@ -66,7 +66,7 @@ static WEBP_INLINE uint32_t ClampedAddSubtractFull(uint32_t c0, uint32_t c1, } static WEBP_INLINE int AddSubtractComponentHalf(int a, int b) { - return Clip255(a + (a - b) / 2); + return Clip255((uint32_t)(a + (a - b) / 2)); } static WEBP_INLINE uint32_t ClampedAddSubtractHalf(uint32_t c0, uint32_t c1, @@ -293,10 +293,10 @@ void VP8LTransformColorInverse_C(const VP8LMultipliers* const m, const uint32_t red = argb >> 16; int new_red = red & 0xff; int new_blue = argb & 0xff; - new_red += ColorTransformDelta(m->green_to_red_, green); + new_red += ColorTransformDelta((int8_t)m->green_to_red_, green); new_red &= 0xff; - new_blue += ColorTransformDelta(m->green_to_blue_, green); - new_blue += ColorTransformDelta(m->red_to_blue_, (int8_t)new_red); + new_blue += ColorTransformDelta((int8_t)m->green_to_blue_, green); + new_blue += ColorTransformDelta((int8_t)m->red_to_blue_, (int8_t)new_red); new_blue &= 0xff; dst[i] = (argb & 0xff00ff00u) | (new_red << 16) | (new_blue); } @@ -395,7 +395,7 @@ void VP8LInverseTransform(const VP8LTransform* const transform, assert(row_start < row_end); assert(row_end <= transform->ysize_); switch (transform->type_) { - case SUBTRACT_GREEN: + case SUBTRACT_GREEN_TRANSFORM: VP8LAddGreenToBlueAndRed(in, (row_end - row_start) * width, out); break; case PREDICTOR_TRANSFORM: diff --git a/thirdparty/webp/src/dsp/lossless_enc.c b/thirdparty/webp/src/dsp/lossless_enc.c index de6c4ace5f..b1f9f26d72 100644 --- a/thirdparty/webp/src/dsp/lossless_enc.c +++ b/thirdparty/webp/src/dsp/lossless_enc.c @@ -522,11 +522,11 @@ static void GetCombinedEntropyUnrefined_C(const uint32_t X[], void VP8LSubtractGreenFromBlueAndRed_C(uint32_t* argb_data, int num_pixels) { int i; for (i = 0; i < num_pixels; ++i) { - const int argb = argb_data[i]; + const int argb = (int)argb_data[i]; const int green = (argb >> 8) & 0xff; const uint32_t new_r = (((argb >> 16) & 0xff) - green) & 0xff; const uint32_t new_b = (((argb >> 0) & 0xff) - green) & 0xff; - argb_data[i] = (argb & 0xff00ff00u) | (new_r << 16) | new_b; + argb_data[i] = ((uint32_t)argb & 0xff00ff00u) | (new_r << 16) | new_b; } } @@ -547,10 +547,10 @@ void VP8LTransformColor_C(const VP8LMultipliers* const m, uint32_t* data, const int8_t red = U32ToS8(argb >> 16); int new_red = red & 0xff; int new_blue = argb & 0xff; - new_red -= ColorTransformDelta(m->green_to_red_, green); + new_red -= ColorTransformDelta((int8_t)m->green_to_red_, green); new_red &= 0xff; - new_blue -= ColorTransformDelta(m->green_to_blue_, green); - new_blue -= ColorTransformDelta(m->red_to_blue_, red); + new_blue -= ColorTransformDelta((int8_t)m->green_to_blue_, green); + new_blue -= ColorTransformDelta((int8_t)m->red_to_blue_, red); new_blue &= 0xff; data[i] = (argb & 0xff00ff00u) | (new_red << 16) | (new_blue); } @@ -560,7 +560,7 @@ static WEBP_INLINE uint8_t TransformColorRed(uint8_t green_to_red, uint32_t argb) { const int8_t green = U32ToS8(argb >> 8); int new_red = argb >> 16; - new_red -= ColorTransformDelta(green_to_red, green); + new_red -= ColorTransformDelta((int8_t)green_to_red, green); return (new_red & 0xff); } @@ -569,9 +569,9 @@ static WEBP_INLINE uint8_t TransformColorBlue(uint8_t green_to_blue, uint32_t argb) { const int8_t green = U32ToS8(argb >> 8); const int8_t red = U32ToS8(argb >> 16); - uint8_t new_blue = argb & 0xff; - new_blue -= ColorTransformDelta(green_to_blue, green); - new_blue -= ColorTransformDelta(red_to_blue, red); + int new_blue = argb & 0xff; + new_blue -= ColorTransformDelta((int8_t)green_to_blue, green); + new_blue -= ColorTransformDelta((int8_t)red_to_blue, red); return (new_blue & 0xff); } diff --git a/thirdparty/webp/src/dsp/lossless_enc_sse2.c b/thirdparty/webp/src/dsp/lossless_enc_sse2.c index 948001a3d5..66cbaab772 100644 --- a/thirdparty/webp/src/dsp/lossless_enc_sse2.c +++ b/thirdparty/webp/src/dsp/lossless_enc_sse2.c @@ -54,8 +54,8 @@ static void TransformColor_SSE2(const VP8LMultipliers* const m, const __m128i mults_rb = MK_CST_16(CST_5b(m->green_to_red_), CST_5b(m->green_to_blue_)); const __m128i mults_b2 = MK_CST_16(CST_5b(m->red_to_blue_), 0); - const __m128i mask_ag = _mm_set1_epi32(0xff00ff00); // alpha-green masks - const __m128i mask_rb = _mm_set1_epi32(0x00ff00ff); // red-blue masks + const __m128i mask_ag = _mm_set1_epi32((int)0xff00ff00); // alpha-green masks + const __m128i mask_rb = _mm_set1_epi32(0x00ff00ff); // red-blue masks int i; for (i = 0; i + 4 <= num_pixels; i += 4) { const __m128i in = _mm_loadu_si128((__m128i*)&argb_data[i]); // argb @@ -376,7 +376,7 @@ static void BundleColorMap_SSE2(const uint8_t* const row, int width, int xbits, break; } case 2: { - const __m128i mask_or = _mm_set1_epi32(0xff000000); + const __m128i mask_or = _mm_set1_epi32((int)0xff000000); const __m128i mul_cst = _mm_set1_epi16(0x0104); const __m128i mask_mul = _mm_set1_epi16(0x0f00); for (x = 0; x + 16 <= width; x += 16, dst += 4) { @@ -427,7 +427,7 @@ static WEBP_INLINE void Average2_m128i(const __m128i* const a0, static void PredictorSub0_SSE2(const uint32_t* in, const uint32_t* upper, int num_pixels, uint32_t* out) { int i; - const __m128i black = _mm_set1_epi32(ARGB_BLACK); + const __m128i black = _mm_set1_epi32((int)ARGB_BLACK); for (i = 0; i + 4 <= num_pixels; i += 4) { const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); const __m128i res = _mm_sub_epi8(src, black); diff --git a/thirdparty/webp/src/dsp/lossless_sse2.c b/thirdparty/webp/src/dsp/lossless_sse2.c index 396cb0bdfc..4b6a532c23 100644 --- a/thirdparty/webp/src/dsp/lossless_sse2.c +++ b/thirdparty/webp/src/dsp/lossless_sse2.c @@ -27,23 +27,22 @@ static WEBP_INLINE uint32_t ClampedAddSubtractFull_SSE2(uint32_t c0, uint32_t c1, uint32_t c2) { const __m128i zero = _mm_setzero_si128(); - const __m128i C0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c0), zero); - const __m128i C1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c1), zero); - const __m128i C2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c2), zero); + const __m128i C0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c0), zero); + const __m128i C1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c1), zero); + const __m128i C2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c2), zero); const __m128i V1 = _mm_add_epi16(C0, C1); const __m128i V2 = _mm_sub_epi16(V1, C2); const __m128i b = _mm_packus_epi16(V2, V2); - const uint32_t output = _mm_cvtsi128_si32(b); - return output; + return (uint32_t)_mm_cvtsi128_si32(b); } static WEBP_INLINE uint32_t ClampedAddSubtractHalf_SSE2(uint32_t c0, uint32_t c1, uint32_t c2) { const __m128i zero = _mm_setzero_si128(); - const __m128i C0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c0), zero); - const __m128i C1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c1), zero); - const __m128i B0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(c2), zero); + const __m128i C0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c0), zero); + const __m128i C1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c1), zero); + const __m128i B0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c2), zero); const __m128i avg = _mm_add_epi16(C1, C0); const __m128i A0 = _mm_srli_epi16(avg, 1); const __m128i A1 = _mm_sub_epi16(A0, B0); @@ -52,16 +51,15 @@ static WEBP_INLINE uint32_t ClampedAddSubtractHalf_SSE2(uint32_t c0, const __m128i A3 = _mm_srai_epi16(A2, 1); const __m128i A4 = _mm_add_epi16(A0, A3); const __m128i A5 = _mm_packus_epi16(A4, A4); - const uint32_t output = _mm_cvtsi128_si32(A5); - return output; + return (uint32_t)_mm_cvtsi128_si32(A5); } static WEBP_INLINE uint32_t Select_SSE2(uint32_t a, uint32_t b, uint32_t c) { int pa_minus_pb; const __m128i zero = _mm_setzero_si128(); - const __m128i A0 = _mm_cvtsi32_si128(a); - const __m128i B0 = _mm_cvtsi32_si128(b); - const __m128i C0 = _mm_cvtsi32_si128(c); + const __m128i A0 = _mm_cvtsi32_si128((int)a); + const __m128i B0 = _mm_cvtsi32_si128((int)b); + const __m128i C0 = _mm_cvtsi32_si128((int)c); const __m128i AC0 = _mm_subs_epu8(A0, C0); const __m128i CA0 = _mm_subs_epu8(C0, A0); const __m128i BC0 = _mm_subs_epu8(B0, C0); @@ -94,8 +92,8 @@ static WEBP_INLINE void Average2_uint32_SSE2(const uint32_t a0, __m128i* const avg) { // (a + b) >> 1 = ((a + b + 1) >> 1) - ((a ^ b) & 1) const __m128i ones = _mm_set1_epi8(1); - const __m128i A0 = _mm_cvtsi32_si128(a0); - const __m128i A1 = _mm_cvtsi32_si128(a1); + const __m128i A0 = _mm_cvtsi32_si128((int)a0); + const __m128i A1 = _mm_cvtsi32_si128((int)a1); const __m128i avg1 = _mm_avg_epu8(A0, A1); const __m128i one = _mm_and_si128(_mm_xor_si128(A0, A1), ones); *avg = _mm_sub_epi8(avg1, one); @@ -103,8 +101,8 @@ static WEBP_INLINE void Average2_uint32_SSE2(const uint32_t a0, static WEBP_INLINE __m128i Average2_uint32_16_SSE2(uint32_t a0, uint32_t a1) { const __m128i zero = _mm_setzero_si128(); - const __m128i A0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(a0), zero); - const __m128i A1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(a1), zero); + const __m128i A0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)a0), zero); + const __m128i A1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)a1), zero); const __m128i sum = _mm_add_epi16(A1, A0); return _mm_srli_epi16(sum, 1); } @@ -112,19 +110,18 @@ static WEBP_INLINE __m128i Average2_uint32_16_SSE2(uint32_t a0, uint32_t a1) { static WEBP_INLINE uint32_t Average2_SSE2(uint32_t a0, uint32_t a1) { __m128i output; Average2_uint32_SSE2(a0, a1, &output); - return _mm_cvtsi128_si32(output); + return (uint32_t)_mm_cvtsi128_si32(output); } static WEBP_INLINE uint32_t Average3_SSE2(uint32_t a0, uint32_t a1, uint32_t a2) { const __m128i zero = _mm_setzero_si128(); const __m128i avg1 = Average2_uint32_16_SSE2(a0, a2); - const __m128i A1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(a1), zero); + const __m128i A1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)a1), zero); const __m128i sum = _mm_add_epi16(avg1, A1); const __m128i avg2 = _mm_srli_epi16(sum, 1); const __m128i A2 = _mm_packus_epi16(avg2, avg2); - const uint32_t output = _mm_cvtsi128_si32(A2); - return output; + return (uint32_t)_mm_cvtsi128_si32(A2); } static WEBP_INLINE uint32_t Average4_SSE2(uint32_t a0, uint32_t a1, @@ -134,8 +131,7 @@ static WEBP_INLINE uint32_t Average4_SSE2(uint32_t a0, uint32_t a1, const __m128i sum = _mm_add_epi16(avg2, avg1); const __m128i avg3 = _mm_srli_epi16(sum, 1); const __m128i A0 = _mm_packus_epi16(avg3, avg3); - const uint32_t output = _mm_cvtsi128_si32(A0); - return output; + return (uint32_t)_mm_cvtsi128_si32(A0); } static uint32_t Predictor5_SSE2(const uint32_t* const left, @@ -192,7 +188,7 @@ static uint32_t Predictor13_SSE2(const uint32_t* const left, static void PredictorAdd0_SSE2(const uint32_t* in, const uint32_t* upper, int num_pixels, uint32_t* out) { int i; - const __m128i black = _mm_set1_epi32(ARGB_BLACK); + const __m128i black = _mm_set1_epi32((int)ARGB_BLACK); for (i = 0; i + 4 <= num_pixels; i += 4) { const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); const __m128i res = _mm_add_epi8(src, black); @@ -208,7 +204,7 @@ static void PredictorAdd0_SSE2(const uint32_t* in, const uint32_t* upper, static void PredictorAdd1_SSE2(const uint32_t* in, const uint32_t* upper, int num_pixels, uint32_t* out) { int i; - __m128i prev = _mm_set1_epi32(out[-1]); + __m128i prev = _mm_set1_epi32((int)out[-1]); for (i = 0; i + 4 <= num_pixels; i += 4) { // a | b | c | d const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); @@ -285,12 +281,12 @@ GENERATE_PREDICTOR_2(9, upper[i + 1]) #undef GENERATE_PREDICTOR_2 // Predictor10: average of (average of (L,TL), average of (T, TR)). -#define DO_PRED10(OUT) do { \ - __m128i avgLTL, avg; \ - Average2_m128i(&L, &TL, &avgLTL); \ - Average2_m128i(&avgTTR, &avgLTL, &avg); \ - L = _mm_add_epi8(avg, src); \ - out[i + (OUT)] = _mm_cvtsi128_si32(L); \ +#define DO_PRED10(OUT) do { \ + __m128i avgLTL, avg; \ + Average2_m128i(&L, &TL, &avgLTL); \ + Average2_m128i(&avgTTR, &avgLTL, &avg); \ + L = _mm_add_epi8(avg, src); \ + out[i + (OUT)] = (uint32_t)_mm_cvtsi128_si32(L); \ } while (0) #define DO_PRED10_SHIFT do { \ @@ -303,7 +299,7 @@ GENERATE_PREDICTOR_2(9, upper[i + 1]) static void PredictorAdd10_SSE2(const uint32_t* in, const uint32_t* upper, int num_pixels, uint32_t* out) { int i; - __m128i L = _mm_cvtsi32_si128(out[-1]); + __m128i L = _mm_cvtsi32_si128((int)out[-1]); for (i = 0; i + 4 <= num_pixels; i += 4) { __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); @@ -336,7 +332,7 @@ static void PredictorAdd10_SSE2(const uint32_t* in, const uint32_t* upper, const __m128i B = _mm_andnot_si128(mask, T); \ const __m128i pred = _mm_or_si128(A, B); /* pred = (pa > b)? L : T*/ \ L = _mm_add_epi8(src, pred); \ - out[i + (OUT)] = _mm_cvtsi128_si32(L); \ + out[i + (OUT)] = (uint32_t)_mm_cvtsi128_si32(L); \ } while (0) #define DO_PRED11_SHIFT do { \ @@ -351,7 +347,7 @@ static void PredictorAdd11_SSE2(const uint32_t* in, const uint32_t* upper, int num_pixels, uint32_t* out) { int i; __m128i pa; - __m128i L = _mm_cvtsi32_si128(out[-1]); + __m128i L = _mm_cvtsi32_si128((int)out[-1]); for (i = 0; i + 4 <= num_pixels; i += 4) { __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); @@ -384,12 +380,12 @@ static void PredictorAdd11_SSE2(const uint32_t* in, const uint32_t* upper, #undef DO_PRED11_SHIFT // Predictor12: ClampedAddSubtractFull. -#define DO_PRED12(DIFF, LANE, OUT) do { \ - const __m128i all = _mm_add_epi16(L, (DIFF)); \ - const __m128i alls = _mm_packus_epi16(all, all); \ - const __m128i res = _mm_add_epi8(src, alls); \ - out[i + (OUT)] = _mm_cvtsi128_si32(res); \ - L = _mm_unpacklo_epi8(res, zero); \ +#define DO_PRED12(DIFF, LANE, OUT) do { \ + const __m128i all = _mm_add_epi16(L, (DIFF)); \ + const __m128i alls = _mm_packus_epi16(all, all); \ + const __m128i res = _mm_add_epi8(src, alls); \ + out[i + (OUT)] = (uint32_t)_mm_cvtsi128_si32(res); \ + L = _mm_unpacklo_epi8(res, zero); \ } while (0) #define DO_PRED12_SHIFT(DIFF, LANE) do { \ @@ -402,7 +398,7 @@ static void PredictorAdd12_SSE2(const uint32_t* in, const uint32_t* upper, int num_pixels, uint32_t* out) { int i; const __m128i zero = _mm_setzero_si128(); - const __m128i L8 = _mm_cvtsi32_si128(out[-1]); + const __m128i L8 = _mm_cvtsi32_si128((int)out[-1]); __m128i L = _mm_unpacklo_epi8(L8, zero); for (i = 0; i + 4 <= num_pixels; i += 4) { // Load 4 pixels at a time. @@ -468,7 +464,7 @@ static void TransformColorInverse_SSE2(const VP8LMultipliers* const m, const __m128i mults_b2 = MK_CST_16(CST(red_to_blue_), 0); #undef MK_CST_16 #undef CST - const __m128i mask_ag = _mm_set1_epi32(0xff00ff00); // alpha-green masks + const __m128i mask_ag = _mm_set1_epi32((int)0xff00ff00); // alpha-green masks int i; for (i = 0; i + 4 <= num_pixels; i += 4) { const __m128i in = _mm_loadu_si128((const __m128i*)&src[i]); // argb @@ -532,7 +528,7 @@ static void ConvertBGRAToRGB_SSE2(const uint32_t* src, int num_pixels, static void ConvertBGRAToRGBA_SSE2(const uint32_t* src, int num_pixels, uint8_t* dst) { - const __m128i red_blue_mask = _mm_set1_epi32(0x00ff00ffu); + const __m128i red_blue_mask = _mm_set1_epi32(0x00ff00ff); const __m128i* in = (const __m128i*)src; __m128i* out = (__m128i*)dst; while (num_pixels >= 8) { @@ -561,7 +557,7 @@ static void ConvertBGRAToRGBA_SSE2(const uint32_t* src, static void ConvertBGRAToRGBA4444_SSE2(const uint32_t* src, int num_pixels, uint8_t* dst) { const __m128i mask_0x0f = _mm_set1_epi8(0x0f); - const __m128i mask_0xf0 = _mm_set1_epi8(0xf0); + const __m128i mask_0xf0 = _mm_set1_epi8((char)0xf0); const __m128i* in = (const __m128i*)src; __m128i* out = (__m128i*)dst; while (num_pixels >= 8) { @@ -596,8 +592,8 @@ static void ConvertBGRAToRGBA4444_SSE2(const uint32_t* src, static void ConvertBGRAToRGB565_SSE2(const uint32_t* src, int num_pixels, uint8_t* dst) { - const __m128i mask_0xe0 = _mm_set1_epi8(0xe0); - const __m128i mask_0xf8 = _mm_set1_epi8(0xf8); + const __m128i mask_0xe0 = _mm_set1_epi8((char)0xe0); + const __m128i mask_0xf8 = _mm_set1_epi8((char)0xf8); const __m128i mask_0x07 = _mm_set1_epi8(0x07); const __m128i* in = (const __m128i*)src; __m128i* out = (__m128i*)dst; diff --git a/thirdparty/webp/src/dsp/lossless_sse41.c b/thirdparty/webp/src/dsp/lossless_sse41.c index b0d6daa7fe..bb7ce7611f 100644 --- a/thirdparty/webp/src/dsp/lossless_sse41.c +++ b/thirdparty/webp/src/dsp/lossless_sse41.c @@ -25,11 +25,12 @@ static void TransformColorInverse_SSE41(const VP8LMultipliers* const m, int num_pixels, uint32_t* dst) { // sign-extended multiplying constants, pre-shifted by 5. #define CST(X) (((int16_t)(m->X << 8)) >> 5) // sign-extend - const __m128i mults_rb = _mm_set1_epi32((uint32_t)CST(green_to_red_) << 16 | - (CST(green_to_blue_) & 0xffff)); + const __m128i mults_rb = + _mm_set1_epi32((int)((uint32_t)CST(green_to_red_) << 16 | + (CST(green_to_blue_) & 0xffff))); const __m128i mults_b2 = _mm_set1_epi32(CST(red_to_blue_)); #undef CST - const __m128i mask_ag = _mm_set1_epi32(0xff00ff00); + const __m128i mask_ag = _mm_set1_epi32((int)0xff00ff00); const __m128i perm1 = _mm_setr_epi8(-1, 1, -1, 1, -1, 5, -1, 5, -1, 9, -1, 9, -1, 13, -1, 13); const __m128i perm2 = _mm_setr_epi8(-1, 2, -1, -1, -1, 6, -1, -1, diff --git a/thirdparty/webp/src/dsp/quant.h b/thirdparty/webp/src/dsp/quant.h index 5e8dba8d19..fc099bf9d6 100644 --- a/thirdparty/webp/src/dsp/quant.h +++ b/thirdparty/webp/src/dsp/quant.h @@ -21,10 +21,15 @@ #define IsFlat IsFlat_NEON -static uint32x2_t horizontal_add_uint32x4(const uint32x4_t a) { +static uint32_t horizontal_add_uint32x4(const uint32x4_t a) { +#if defined(__aarch64__) + return vaddvq_u32(a); +#else const uint64x2_t b = vpaddlq_u32(a); - return vadd_u32(vreinterpret_u32_u64(vget_low_u64(b)), - vreinterpret_u32_u64(vget_high_u64(b))); + const uint32x2_t c = vadd_u32(vreinterpret_u32_u64(vget_low_u64(b)), + vreinterpret_u32_u64(vget_high_u64(b))); + return vget_lane_u32(c, 0); +#endif } static WEBP_INLINE int IsFlat(const int16_t* levels, int num_blocks, @@ -45,7 +50,7 @@ static WEBP_INLINE int IsFlat(const int16_t* levels, int num_blocks, levels += 16; } - return thresh >= (int32_t)vget_lane_u32(horizontal_add_uint32x4(sum), 0); + return thresh >= (int)horizontal_add_uint32x4(sum); } #else diff --git a/thirdparty/webp/src/dsp/rescaler_sse2.c b/thirdparty/webp/src/dsp/rescaler_sse2.c index d7effea16e..3f18e94e93 100644 --- a/thirdparty/webp/src/dsp/rescaler_sse2.c +++ b/thirdparty/webp/src/dsp/rescaler_sse2.c @@ -85,7 +85,7 @@ static void RescalerImportRowExpand_SSE2(WebPRescaler* const wrk, const __m128i mult = _mm_cvtsi32_si128(((x_add - accum) << 16) | accum); const __m128i out = _mm_madd_epi16(cur_pixels, mult); assert(sizeof(*frow) == sizeof(uint32_t)); - WebPUint32ToMem((uint8_t*)frow, _mm_cvtsi128_si32(out)); + WebPInt32ToMem((uint8_t*)frow, _mm_cvtsi128_si32(out)); frow += 1; if (frow >= frow_end) break; accum -= wrk->x_sub; @@ -132,7 +132,7 @@ static void RescalerImportRowShrink_SSE2(WebPRescaler* const wrk, __m128i base = zero; accum += wrk->x_add; while (accum > 0) { - const __m128i A = _mm_cvtsi32_si128(WebPMemToUint32(src)); + const __m128i A = _mm_cvtsi32_si128(WebPMemToInt32(src)); src += 4; base = _mm_unpacklo_epi8(A, zero); // To avoid overflow, we need: base * x_add / x_sub < 32768 @@ -198,7 +198,7 @@ static WEBP_INLINE void ProcessRow_SSE2(const __m128i* const A0, const __m128i* const mult, uint8_t* const dst) { const __m128i rounder = _mm_set_epi32(0, ROUNDER, 0, ROUNDER); - const __m128i mask = _mm_set_epi32(0xffffffffu, 0, 0xffffffffu, 0); + const __m128i mask = _mm_set_epi32(~0, 0, ~0, 0); const __m128i B0 = _mm_mul_epu32(*A0, *mult); const __m128i B1 = _mm_mul_epu32(*A1, *mult); const __m128i B2 = _mm_mul_epu32(*A2, *mult); diff --git a/thirdparty/webp/src/dsp/upsampling_sse2.c b/thirdparty/webp/src/dsp/upsampling_sse2.c index 340f1e2ac2..08b6d0b1cf 100644 --- a/thirdparty/webp/src/dsp/upsampling_sse2.c +++ b/thirdparty/webp/src/dsp/upsampling_sse2.c @@ -121,7 +121,7 @@ static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ int uv_pos, pos; \ /* 16byte-aligned array to cache reconstructed u and v */ \ uint8_t uv_buf[14 * 32 + 15] = { 0 }; \ - uint8_t* const r_u = (uint8_t*)((uintptr_t)(uv_buf + 15) & ~15); \ + uint8_t* const r_u = (uint8_t*)((uintptr_t)(uv_buf + 15) & ~(uintptr_t)15); \ uint8_t* const r_v = r_u + 32; \ \ assert(top_y != NULL); \ diff --git a/thirdparty/webp/src/dsp/yuv_sse2.c b/thirdparty/webp/src/dsp/yuv_sse2.c index 970bbb7884..01a48f9af2 100644 --- a/thirdparty/webp/src/dsp/yuv_sse2.c +++ b/thirdparty/webp/src/dsp/yuv_sse2.c @@ -15,10 +15,12 @@ #if defined(WEBP_USE_SSE2) -#include "src/dsp/common_sse2.h" #include #include +#include "src/dsp/common_sse2.h" +#include "src/utils/utils.h" + //----------------------------------------------------------------------------- // Convert spans of 32 pixels to various RGB formats for the fancy upsampler. @@ -74,7 +76,7 @@ static WEBP_INLINE __m128i Load_HI_16_SSE2(const uint8_t* src) { // Load and replicate the U/V samples static WEBP_INLINE __m128i Load_UV_HI_8_SSE2(const uint8_t* src) { const __m128i zero = _mm_setzero_si128(); - const __m128i tmp0 = _mm_cvtsi32_si128(*(const uint32_t*)src); + const __m128i tmp0 = _mm_cvtsi32_si128(WebPMemToInt32(src)); const __m128i tmp1 = _mm_unpacklo_epi8(zero, tmp0); return _mm_unpacklo_epi16(tmp1, tmp1); // replicate samples } @@ -130,7 +132,7 @@ static WEBP_INLINE void PackAndStore4444_SSE2(const __m128i* const R, const __m128i rg0 = _mm_packus_epi16(*B, *A); const __m128i ba0 = _mm_packus_epi16(*R, *G); #endif - const __m128i mask_0xf0 = _mm_set1_epi8(0xf0); + const __m128i mask_0xf0 = _mm_set1_epi8((char)0xf0); const __m128i rb1 = _mm_unpacklo_epi8(rg0, ba0); // rbrbrbrbrb... const __m128i ga1 = _mm_unpackhi_epi8(rg0, ba0); // gagagagaga... const __m128i rb2 = _mm_and_si128(rb1, mask_0xf0); @@ -147,9 +149,10 @@ static WEBP_INLINE void PackAndStore565_SSE2(const __m128i* const R, const __m128i r0 = _mm_packus_epi16(*R, *R); const __m128i g0 = _mm_packus_epi16(*G, *G); const __m128i b0 = _mm_packus_epi16(*B, *B); - const __m128i r1 = _mm_and_si128(r0, _mm_set1_epi8(0xf8)); + const __m128i r1 = _mm_and_si128(r0, _mm_set1_epi8((char)0xf8)); const __m128i b1 = _mm_and_si128(_mm_srli_epi16(b0, 3), _mm_set1_epi8(0x1f)); - const __m128i g1 = _mm_srli_epi16(_mm_and_si128(g0, _mm_set1_epi8(0xe0)), 5); + const __m128i g1 = + _mm_srli_epi16(_mm_and_si128(g0, _mm_set1_epi8((char)0xe0)), 5); const __m128i g2 = _mm_slli_epi16(_mm_and_si128(g0, _mm_set1_epi8(0x1c)), 3); const __m128i rg = _mm_or_si128(r1, g1); const __m128i gb = _mm_or_si128(g2, b1); diff --git a/thirdparty/webp/src/dsp/yuv_sse41.c b/thirdparty/webp/src/dsp/yuv_sse41.c index 579d1f7402..f79b802e47 100644 --- a/thirdparty/webp/src/dsp/yuv_sse41.c +++ b/thirdparty/webp/src/dsp/yuv_sse41.c @@ -15,10 +15,12 @@ #if defined(WEBP_USE_SSE41) -#include "src/dsp/common_sse41.h" #include #include +#include "src/dsp/common_sse41.h" +#include "src/utils/utils.h" + //----------------------------------------------------------------------------- // Convert spans of 32 pixels to various RGB formats for the fancy upsampler. @@ -74,7 +76,7 @@ static WEBP_INLINE __m128i Load_HI_16_SSE41(const uint8_t* src) { // Load and replicate the U/V samples static WEBP_INLINE __m128i Load_UV_HI_8_SSE41(const uint8_t* src) { const __m128i zero = _mm_setzero_si128(); - const __m128i tmp0 = _mm_cvtsi32_si128(*(const uint32_t*)src); + const __m128i tmp0 = _mm_cvtsi32_si128(WebPMemToInt32(src)); const __m128i tmp1 = _mm_unpacklo_epi8(zero, tmp0); return _mm_unpacklo_epi16(tmp1, tmp1); // replicate samples } diff --git a/thirdparty/webp/src/enc/Makefile.am b/thirdparty/webp/src/enc/Makefile.am new file mode 100644 index 0000000000..2fec804cb7 --- /dev/null +++ b/thirdparty/webp/src/enc/Makefile.am @@ -0,0 +1,43 @@ +AM_CPPFLAGS += -I$(top_builddir) -I$(top_srcdir) +noinst_LTLIBRARIES = libwebpencode.la + +libwebpencode_la_SOURCES = +libwebpencode_la_SOURCES += alpha_enc.c +libwebpencode_la_SOURCES += analysis_enc.c +libwebpencode_la_SOURCES += backward_references_cost_enc.c +libwebpencode_la_SOURCES += backward_references_enc.c +libwebpencode_la_SOURCES += backward_references_enc.h +libwebpencode_la_SOURCES += config_enc.c +libwebpencode_la_SOURCES += cost_enc.c +libwebpencode_la_SOURCES += cost_enc.h +libwebpencode_la_SOURCES += filter_enc.c +libwebpencode_la_SOURCES += frame_enc.c +libwebpencode_la_SOURCES += histogram_enc.c +libwebpencode_la_SOURCES += histogram_enc.h +libwebpencode_la_SOURCES += iterator_enc.c +libwebpencode_la_SOURCES += near_lossless_enc.c +libwebpencode_la_SOURCES += picture_enc.c +libwebpencode_la_SOURCES += picture_csp_enc.c +libwebpencode_la_SOURCES += picture_psnr_enc.c +libwebpencode_la_SOURCES += picture_rescale_enc.c +libwebpencode_la_SOURCES += picture_tools_enc.c +libwebpencode_la_SOURCES += predictor_enc.c +libwebpencode_la_SOURCES += quant_enc.c +libwebpencode_la_SOURCES += syntax_enc.c +libwebpencode_la_SOURCES += token_enc.c +libwebpencode_la_SOURCES += tree_enc.c +libwebpencode_la_SOURCES += vp8i_enc.h +libwebpencode_la_SOURCES += vp8l_enc.c +libwebpencode_la_SOURCES += vp8li_enc.h +libwebpencode_la_SOURCES += webp_enc.c + +libwebpencodeinclude_HEADERS = +libwebpencodeinclude_HEADERS += ../webp/encode.h +libwebpencodeinclude_HEADERS += ../webp/types.h +noinst_HEADERS = +noinst_HEADERS += ../webp/format_constants.h + +libwebpencode_la_LIBADD = ../../sharpyuv/libsharpyuv.la +libwebpencode_la_LDFLAGS = -lm +libwebpencode_la_CPPFLAGS = $(AM_CPPFLAGS) +libwebpencodeincludedir = $(includedir)/webp diff --git a/thirdparty/webp/src/enc/analysis_enc.c b/thirdparty/webp/src/enc/analysis_enc.c index ebb784261c..a0001ac034 100644 --- a/thirdparty/webp/src/enc/analysis_enc.c +++ b/thirdparty/webp/src/enc/analysis_enc.c @@ -391,12 +391,14 @@ static int DoSegmentsJob(void* arg1, void* arg2) { return ok; } +#ifdef WEBP_USE_THREAD static void MergeJobs(const SegmentJob* const src, SegmentJob* const dst) { int i; for (i = 0; i <= MAX_ALPHA; ++i) dst->alphas[i] += src->alphas[i]; dst->alpha += src->alpha; dst->uv_alpha += src->uv_alpha; } +#endif // initialize the job struct with some tasks to perform static void InitSegmentJob(VP8Encoder* const enc, SegmentJob* const job, @@ -425,10 +427,10 @@ int VP8EncAnalyze(VP8Encoder* const enc) { (enc->method_ <= 1); // for method 0 - 1, we need preds_[] to be filled. if (do_segments) { const int last_row = enc->mb_h_; - // We give a little more than a half work to the main thread. - const int split_row = (9 * last_row + 15) >> 4; const int total_mb = last_row * enc->mb_w_; #ifdef WEBP_USE_THREAD + // We give a little more than a half work to the main thread. + const int split_row = (9 * last_row + 15) >> 4; const int kMinSplitRow = 2; // minimal rows needed for mt to be worth it const int do_mt = (enc->thread_level_ > 0) && (split_row >= kMinSplitRow); #else @@ -438,6 +440,7 @@ int VP8EncAnalyze(VP8Encoder* const enc) { WebPGetWorkerInterface(); SegmentJob main_job; if (do_mt) { +#ifdef WEBP_USE_THREAD SegmentJob side_job; // Note the use of '&' instead of '&&' because we must call the functions // no matter what. @@ -455,6 +458,7 @@ int VP8EncAnalyze(VP8Encoder* const enc) { } worker_interface->End(&side_job.worker); if (ok) MergeJobs(&side_job, &main_job); // merge results together +#endif // WEBP_USE_THREAD } else { // Even for single-thread case, we use the generic Worker tools. InitSegmentJob(enc, &main_job, 0, last_row); diff --git a/thirdparty/webp/src/enc/picture_csp_enc.c b/thirdparty/webp/src/enc/picture_csp_enc.c index fabebcf202..78c8ca479b 100644 --- a/thirdparty/webp/src/enc/picture_csp_enc.c +++ b/thirdparty/webp/src/enc/picture_csp_enc.c @@ -69,10 +69,12 @@ static int CheckNonOpaque(const uint8_t* alpha, int width, int height, int WebPPictureHasTransparency(const WebPPicture* picture) { if (picture == NULL) return 0; if (picture->use_argb) { - const int alpha_offset = ALPHA_OFFSET; - return CheckNonOpaque((const uint8_t*)picture->argb + alpha_offset, - picture->width, picture->height, - 4, picture->argb_stride * sizeof(*picture->argb)); + if (picture->argb != NULL) { + return CheckNonOpaque((const uint8_t*)picture->argb + ALPHA_OFFSET, + picture->width, picture->height, + 4, picture->argb_stride * sizeof(*picture->argb)); + } + return 0; } return CheckNonOpaque(picture->a, picture->width, picture->height, 1, picture->a_stride); @@ -170,21 +172,6 @@ static const int kMinDimensionIterativeConversion = 4; //------------------------------------------------------------------------------ // Main function -extern void SharpYuvInit(VP8CPUInfo cpu_info_func); - -static void SafeInitSharpYuv(void) { -#if defined(WEBP_USE_THREAD) && !defined(_WIN32) - static pthread_mutex_t initsharpyuv_lock = PTHREAD_MUTEX_INITIALIZER; - if (pthread_mutex_lock(&initsharpyuv_lock)) return; -#endif - - SharpYuvInit(VP8GetCPUInfo); - -#if defined(WEBP_USE_THREAD) && !defined(_WIN32) - (void)pthread_mutex_unlock(&initsharpyuv_lock); -#endif -} - static int PreprocessARGB(const uint8_t* r_ptr, const uint8_t* g_ptr, const uint8_t* b_ptr, @@ -481,6 +468,8 @@ static WEBP_INLINE void ConvertRowsToUV(const uint16_t* rgb, } } +extern void SharpYuvInit(VP8CPUInfo cpu_info_func); + static int ImportYUVAFromRGBA(const uint8_t* r_ptr, const uint8_t* g_ptr, const uint8_t* b_ptr, @@ -516,7 +505,7 @@ static int ImportYUVAFromRGBA(const uint8_t* r_ptr, } if (use_iterative_conversion) { - SafeInitSharpYuv(); + SharpYuvInit(VP8GetCPUInfo); if (!PreprocessARGB(r_ptr, g_ptr, b_ptr, step, rgb_stride, picture)) { return 0; } diff --git a/thirdparty/webp/src/enc/vp8i_enc.h b/thirdparty/webp/src/enc/vp8i_enc.h index 71f76702ae..c9927c47d8 100644 --- a/thirdparty/webp/src/enc/vp8i_enc.h +++ b/thirdparty/webp/src/enc/vp8i_enc.h @@ -31,8 +31,8 @@ extern "C" { // version numbers #define ENC_MAJ_VERSION 1 -#define ENC_MIN_VERSION 2 -#define ENC_REV_VERSION 4 +#define ENC_MIN_VERSION 3 +#define ENC_REV_VERSION 0 enum { MAX_LF_LEVELS = 64, // Maximum loop filter level MAX_VARIABLE_LEVEL = 67, // last (inclusive) level with variable cost diff --git a/thirdparty/webp/src/enc/vp8l_enc.c b/thirdparty/webp/src/enc/vp8l_enc.c index 2b345df610..0b07e529a9 100644 --- a/thirdparty/webp/src/enc/vp8l_enc.c +++ b/thirdparty/webp/src/enc/vp8l_enc.c @@ -361,10 +361,11 @@ typedef enum { kHistoTotal // Must be last. } HistoIx; -static void AddSingleSubGreen(int p, uint32_t* const r, uint32_t* const b) { - const int green = p >> 8; // The upper bits are masked away later. - ++r[((p >> 16) - green) & 0xff]; - ++b[((p >> 0) - green) & 0xff]; +static void AddSingleSubGreen(uint32_t p, + uint32_t* const r, uint32_t* const b) { + const int green = (int)p >> 8; // The upper bits are masked away later. + ++r[(((int)p >> 16) - green) & 0xff]; + ++b[(((int)p >> 0) - green) & 0xff]; } static void AddSingle(uint32_t p, @@ -1354,7 +1355,7 @@ static int EncodeImageInternal( static void ApplySubtractGreen(VP8LEncoder* const enc, int width, int height, VP8LBitWriter* const bw) { VP8LPutBits(bw, TRANSFORM_PRESENT, 1); - VP8LPutBits(bw, SUBTRACT_GREEN, 2); + VP8LPutBits(bw, SUBTRACT_GREEN_TRANSFORM, 2); VP8LSubtractGreenFromBlueAndRed(enc->argb_, width * height); } diff --git a/thirdparty/webp/src/mux/Makefile.am b/thirdparty/webp/src/mux/Makefile.am new file mode 100644 index 0000000000..8f7f7395b7 --- /dev/null +++ b/thirdparty/webp/src/mux/Makefile.am @@ -0,0 +1,22 @@ +AM_CPPFLAGS += -I$(top_builddir) -I$(top_srcdir) +lib_LTLIBRARIES = libwebpmux.la + +libwebpmux_la_SOURCES = +libwebpmux_la_SOURCES += anim_encode.c +libwebpmux_la_SOURCES += animi.h +libwebpmux_la_SOURCES += muxedit.c +libwebpmux_la_SOURCES += muxi.h +libwebpmux_la_SOURCES += muxinternal.c +libwebpmux_la_SOURCES += muxread.c + +libwebpmuxinclude_HEADERS = +libwebpmuxinclude_HEADERS += ../webp/mux.h +libwebpmuxinclude_HEADERS += ../webp/mux_types.h +libwebpmuxinclude_HEADERS += ../webp/types.h +noinst_HEADERS = +noinst_HEADERS += ../webp/format_constants.h + +libwebpmux_la_LIBADD = ../libwebp.la +libwebpmux_la_LDFLAGS = -no-undefined -version-info 3:11:0 -lm +libwebpmuxincludedir = $(includedir)/webp +pkgconfig_DATA = libwebpmux.pc diff --git a/thirdparty/webp/src/mux/libwebpmux.pc.in b/thirdparty/webp/src/mux/libwebpmux.pc.in new file mode 100644 index 0000000000..9d4d6e1e18 --- /dev/null +++ b/thirdparty/webp/src/mux/libwebpmux.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libwebpmux +Description: Library for manipulating the WebP graphics format container +Version: @PACKAGE_VERSION@ +Requires: libwebp >= 0.2.0 +Cflags: -I${includedir} +Libs: -L${libdir} -l@webp_libname_prefix@webpmux +Libs.private: -lm diff --git a/thirdparty/webp/src/mux/libwebpmux.rc b/thirdparty/webp/src/mux/libwebpmux.rc new file mode 100644 index 0000000000..a61f5371b1 --- /dev/null +++ b/thirdparty/webp/src/mux/libwebpmux.rc @@ -0,0 +1,41 @@ +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,3,0 + PRODUCTVERSION 1,0,3,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Google, Inc." + VALUE "FileDescription", "libwebpmux DLL" + VALUE "FileVersion", "1.3.0" + VALUE "InternalName", "libwebpmux.dll" + VALUE "LegalCopyright", "Copyright (C) 2022" + VALUE "OriginalFilename", "libwebpmux.dll" + VALUE "ProductName", "WebP Image Muxer" + VALUE "ProductVersion", "1.3.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources diff --git a/thirdparty/webp/src/mux/muxi.h b/thirdparty/webp/src/mux/muxi.h index 0f4af1784d..7929138c44 100644 --- a/thirdparty/webp/src/mux/muxi.h +++ b/thirdparty/webp/src/mux/muxi.h @@ -28,8 +28,8 @@ extern "C" { // Defines and constants. #define MUX_MAJ_VERSION 1 -#define MUX_MIN_VERSION 2 -#define MUX_REV_VERSION 4 +#define MUX_MIN_VERSION 3 +#define MUX_REV_VERSION 0 // Chunk object. typedef struct WebPChunk WebPChunk; diff --git a/thirdparty/webp/src/utils/Makefile.am b/thirdparty/webp/src/utils/Makefile.am new file mode 100644 index 0000000000..a4bff8b9b7 --- /dev/null +++ b/thirdparty/webp/src/utils/Makefile.am @@ -0,0 +1,52 @@ +AM_CPPFLAGS += -I$(top_builddir) -I$(top_srcdir) +noinst_LTLIBRARIES = libwebputils.la + +if BUILD_LIBWEBPDECODER + noinst_LTLIBRARIES += libwebputilsdecode.la +endif + +common_HEADERS = ../webp/types.h +commondir = $(includedir)/webp + +noinst_HEADERS = +noinst_HEADERS += ../dsp/cpu.h +noinst_HEADERS += ../dsp/dsp.h +noinst_HEADERS += ../webp/decode.h +noinst_HEADERS += ../webp/encode.h +noinst_HEADERS += ../webp/format_constants.h + +COMMON_SOURCES = +COMMON_SOURCES += bit_reader_utils.c +COMMON_SOURCES += bit_reader_utils.h +COMMON_SOURCES += bit_reader_inl_utils.h +COMMON_SOURCES += color_cache_utils.c +COMMON_SOURCES += color_cache_utils.h +COMMON_SOURCES += endian_inl_utils.h +COMMON_SOURCES += filters_utils.c +COMMON_SOURCES += filters_utils.h +COMMON_SOURCES += huffman_utils.c +COMMON_SOURCES += huffman_utils.h +COMMON_SOURCES += quant_levels_dec_utils.c +COMMON_SOURCES += quant_levels_dec_utils.h +COMMON_SOURCES += rescaler_utils.c +COMMON_SOURCES += rescaler_utils.h +COMMON_SOURCES += random_utils.c +COMMON_SOURCES += random_utils.h +COMMON_SOURCES += thread_utils.c +COMMON_SOURCES += thread_utils.h +COMMON_SOURCES += utils.c +COMMON_SOURCES += utils.h + +ENC_SOURCES = +ENC_SOURCES += bit_writer_utils.c +ENC_SOURCES += bit_writer_utils.h +ENC_SOURCES += huffman_encode_utils.c +ENC_SOURCES += huffman_encode_utils.h +ENC_SOURCES += quant_levels_utils.c +ENC_SOURCES += quant_levels_utils.h + +libwebputils_la_SOURCES = $(COMMON_SOURCES) $(ENC_SOURCES) + +if BUILD_LIBWEBPDECODER + libwebputilsdecode_la_SOURCES = $(COMMON_SOURCES) +endif diff --git a/thirdparty/webp/src/utils/bit_reader_inl_utils.h b/thirdparty/webp/src/utils/bit_reader_inl_utils.h index 404b9a6d8c..24f3af7b54 100644 --- a/thirdparty/webp/src/utils/bit_reader_inl_utils.h +++ b/thirdparty/webp/src/utils/bit_reader_inl_utils.h @@ -148,9 +148,9 @@ int VP8GetSigned(VP8BitReader* WEBP_RESTRICT const br, int v, const range_t value = (range_t)(br->value_ >> pos); const int32_t mask = (int32_t)(split - value) >> 31; // -1 or 0 br->bits_ -= 1; - br->range_ += mask; + br->range_ += (range_t)mask; br->range_ |= 1; - br->value_ -= (bit_t)((split + 1) & mask) << pos; + br->value_ -= (bit_t)((split + 1) & (uint32_t)mask) << pos; BT_TRACK(br); return (v ^ mask) - mask; } diff --git a/thirdparty/webp/src/utils/huffman_utils.c b/thirdparty/webp/src/utils/huffman_utils.c index 0cba0fbb7d..90c2fbf7c1 100644 --- a/thirdparty/webp/src/utils/huffman_utils.c +++ b/thirdparty/webp/src/utils/huffman_utils.c @@ -142,7 +142,7 @@ static int BuildHuffmanTable(HuffmanCode* const root_table, int root_bits, { int step; // step size to replicate values in current table - uint32_t low = -1; // low bits for current root entry + uint32_t low = 0xffffffffu; // low bits for current root entry uint32_t mask = total_size - 1; // mask for low bits uint32_t key = 0; // reversed prefix code int num_nodes = 1; // number of Huffman tree nodes diff --git a/thirdparty/webp/src/utils/utils.h b/thirdparty/webp/src/utils/utils.h index ef04f108fe..c5ee873357 100644 --- a/thirdparty/webp/src/utils/utils.h +++ b/thirdparty/webp/src/utils/utils.h @@ -64,7 +64,8 @@ WEBP_EXTERN void WebPSafeFree(void* const ptr); // Alignment #define WEBP_ALIGN_CST 31 -#define WEBP_ALIGN(PTR) (((uintptr_t)(PTR) + WEBP_ALIGN_CST) & ~WEBP_ALIGN_CST) +#define WEBP_ALIGN(PTR) (((uintptr_t)(PTR) + WEBP_ALIGN_CST) & \ + ~(uintptr_t)WEBP_ALIGN_CST) #include // memcpy() is the safe way of moving potentially unaligned 32b memory. @@ -73,10 +74,19 @@ static WEBP_INLINE uint32_t WebPMemToUint32(const uint8_t* const ptr) { memcpy(&A, ptr, sizeof(A)); return A; } + +static WEBP_INLINE int32_t WebPMemToInt32(const uint8_t* const ptr) { + return (int32_t)WebPMemToUint32(ptr); +} + static WEBP_INLINE void WebPUint32ToMem(uint8_t* const ptr, uint32_t val) { memcpy(ptr, &val, sizeof(val)); } +static WEBP_INLINE void WebPInt32ToMem(uint8_t* const ptr, int val) { + WebPUint32ToMem(ptr, (uint32_t)val); +} + //------------------------------------------------------------------------------ // Reading/writing data. diff --git a/thirdparty/webp/src/webp/format_constants.h b/thirdparty/webp/src/webp/format_constants.h index eca6981a47..999035c5d2 100644 --- a/thirdparty/webp/src/webp/format_constants.h +++ b/thirdparty/webp/src/webp/format_constants.h @@ -55,7 +55,7 @@ typedef enum { PREDICTOR_TRANSFORM = 0, CROSS_COLOR_TRANSFORM = 1, - SUBTRACT_GREEN = 2, + SUBTRACT_GREEN_TRANSFORM = 2, COLOR_INDEXING_TRANSFORM = 3 } VP8LImageTransformType; diff --git a/thirdparty/webp/src/webp/types.h b/thirdparty/webp/src/webp/types.h index 47f7f2b007..f255432e41 100644 --- a/thirdparty/webp/src/webp/types.h +++ b/thirdparty/webp/src/webp/types.h @@ -42,7 +42,11 @@ typedef long long int int64_t; # if defined(__GNUC__) && __GNUC__ >= 4 # define WEBP_EXTERN extern __attribute__ ((visibility ("default"))) # else -# define WEBP_EXTERN extern +# if defined(_MSC_VER) && defined(WEBP_DLL) +# define WEBP_EXTERN __declspec(dllexport) +# else +# define WEBP_EXTERN extern +# endif # endif /* __GNUC__ >= 4 */ #endif /* WEBP_EXTERN */