From ba32094b7b287e1f9995d9d3fec1c4ed1217a6c2 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Wed, 3 Sep 2025 11:09:27 -0500 Subject: [PATCH] update openal-soft to 1.24.3 keeping the alt https://github.com/TorqueGameEngines/Torque3D/commit/87514151c48ebed95a42bea9c4a62d9888032e71#diff-73a8dc1ce58605f6c5ea53548454c3bae516ec5132a29c9d7ff7edf9730c75be --- Engine/lib/openal-soft/.clang-tidy | 180 + .../lib/openal-soft/.github/workflows/ci.yml | 106 +- .../workflows/{makemhr.yml => utils.yml} | 43 +- Engine/lib/openal-soft/CMakeLists.txt | 410 +- Engine/lib/openal-soft/ChangeLog | 134 + Engine/lib/openal-soft/README.md | 11 + Engine/lib/openal-soft/al/auxeffectslot.cpp | 434 +- Engine/lib/openal-soft/al/auxeffectslot.h | 105 +- Engine/lib/openal-soft/al/buffer.cpp | 754 +- Engine/lib/openal-soft/al/buffer.h | 6 +- Engine/lib/openal-soft/al/debug.cpp | 282 +- Engine/lib/openal-soft/al/eax/api.h | 96 +- Engine/lib/openal-soft/al/eax/call.cpp | 52 +- Engine/lib/openal-soft/al/eax/effect.h | 55 +- Engine/lib/openal-soft/al/eax/utils.cpp | 5 +- Engine/lib/openal-soft/al/eax/utils.h | 72 +- Engine/lib/openal-soft/al/effect.cpp | 193 +- Engine/lib/openal-soft/al/effect.h | 5 +- Engine/lib/openal-soft/al/effects/autowah.cpp | 79 +- Engine/lib/openal-soft/al/effects/chorus.cpp | 195 +- .../lib/openal-soft/al/effects/compressor.cpp | 63 +- .../openal-soft/al/effects/convolution.cpp | 95 +- .../lib/openal-soft/al/effects/dedicated.cpp | 105 +- .../lib/openal-soft/al/effects/distortion.cpp | 86 +- Engine/lib/openal-soft/al/effects/echo.cpp | 80 +- Engine/lib/openal-soft/al/effects/effects.h | 56 +- .../lib/openal-soft/al/effects/equalizer.cpp | 114 +- .../lib/openal-soft/al/effects/fshifter.cpp | 93 +- .../lib/openal-soft/al/effects/modulator.cpp | 95 +- Engine/lib/openal-soft/al/effects/null.cpp | 79 +- .../lib/openal-soft/al/effects/pshifter.cpp | 68 +- Engine/lib/openal-soft/al/effects/reverb.cpp | 361 +- .../lib/openal-soft/al/effects/vmorpher.cpp | 116 +- Engine/lib/openal-soft/al/error.cpp | 78 +- Engine/lib/openal-soft/al/error.h | 27 - Engine/lib/openal-soft/al/event.cpp | 57 +- Engine/lib/openal-soft/al/extension.cpp | 2 - Engine/lib/openal-soft/al/filter.cpp | 343 +- Engine/lib/openal-soft/al/filter.h | 22 +- Engine/lib/openal-soft/al/listener.cpp | 145 +- Engine/lib/openal-soft/al/source.cpp | 964 +-- Engine/lib/openal-soft/al/source.h | 67 +- Engine/lib/openal-soft/al/state.cpp | 130 +- Engine/lib/openal-soft/alc/alc.cpp | 796 ++- Engine/lib/openal-soft/alc/alconfig.cpp | 152 +- Engine/lib/openal-soft/alc/alu.cpp | 468 +- Engine/lib/openal-soft/alc/alu.h | 5 +- Engine/lib/openal-soft/alc/backends/alsa.cpp | 210 +- Engine/lib/openal-soft/alc/backends/base.cpp | 18 +- Engine/lib/openal-soft/alc/backends/base.h | 24 +- .../openal-soft/alc/backends/coreaudio.cpp | 255 +- .../lib/openal-soft/alc/backends/dsound.cpp | 140 +- Engine/lib/openal-soft/alc/backends/jack.cpp | 127 +- .../lib/openal-soft/alc/backends/loopback.cpp | 4 +- Engine/lib/openal-soft/alc/backends/null.cpp | 34 +- Engine/lib/openal-soft/alc/backends/oboe.cpp | 61 +- .../lib/openal-soft/alc/backends/opensl.cpp | 195 +- Engine/lib/openal-soft/alc/backends/oss.cpp | 136 +- .../lib/openal-soft/alc/backends/otherio.cpp | 700 ++ Engine/lib/openal-soft/alc/backends/otherio.h | 21 + .../lib/openal-soft/alc/backends/pipewire.cpp | 276 +- .../openal-soft/alc/backends/portaudio.cpp | 253 +- .../backends/{portaudio.h => portaudio.hpp} | 6 +- .../openal-soft/alc/backends/pulseaudio.cpp | 431 +- Engine/lib/openal-soft/alc/backends/sdl2.cpp | 178 +- Engine/lib/openal-soft/alc/backends/sdl3.cpp | 393 ++ Engine/lib/openal-soft/alc/backends/sdl3.h | 19 + Engine/lib/openal-soft/alc/backends/sndio.cpp | 101 +- .../alc/backends/{sndio.h => sndio.hpp} | 6 +- .../lib/openal-soft/alc/backends/solaris.cpp | 46 +- .../lib/openal-soft/alc/backends/wasapi.cpp | 2688 ++++---- Engine/lib/openal-soft/alc/backends/wave.cpp | 85 +- Engine/lib/openal-soft/alc/backends/winmm.cpp | 95 +- Engine/lib/openal-soft/alc/context.cpp | 216 +- Engine/lib/openal-soft/alc/context.h | 102 +- Engine/lib/openal-soft/alc/device.cpp | 21 +- Engine/lib/openal-soft/alc/device.h | 58 +- .../lib/openal-soft/alc/effects/autowah.cpp | 2 +- Engine/lib/openal-soft/alc/effects/chorus.cpp | 4 +- .../openal-soft/alc/effects/compressor.cpp | 4 +- .../openal-soft/alc/effects/convolution.cpp | 22 +- .../openal-soft/alc/effects/distortion.cpp | 2 +- Engine/lib/openal-soft/alc/effects/echo.cpp | 4 +- .../lib/openal-soft/alc/effects/equalizer.cpp | 2 +- .../lib/openal-soft/alc/effects/fshifter.cpp | 2 +- .../lib/openal-soft/alc/effects/modulator.cpp | 6 +- Engine/lib/openal-soft/alc/effects/reverb.cpp | 89 +- .../lib/openal-soft/alc/effects/vmorpher.cpp | 2 +- Engine/lib/openal-soft/alc/events.cpp | 6 +- Engine/lib/openal-soft/alc/export_list.h | 20 +- Engine/lib/openal-soft/alc/inprogext.h | 23 +- Engine/lib/openal-soft/alc/panning.cpp | 214 +- Engine/lib/openal-soft/alsoftrc.sample | 30 +- Engine/lib/openal-soft/appveyor.yml | 2 +- Engine/lib/openal-soft/cmake/FindFFmpeg.cmake | 33 +- Engine/lib/openal-soft/cmake/FindJACK.cmake | 2 +- Engine/lib/openal-soft/common/alassert.cpp | 42 +- Engine/lib/openal-soft/common/albit.h | 11 + Engine/lib/openal-soft/common/alcomplex.cpp | 8 +- Engine/lib/openal-soft/common/almalloc.h | 69 +- Engine/lib/openal-soft/common/alnumeric.h | 38 +- Engine/lib/openal-soft/common/alsem.h | 2 +- Engine/lib/openal-soft/common/alspan.h | 17 +- Engine/lib/openal-soft/common/alstring.cpp | 10 - Engine/lib/openal-soft/common/alstring.h | 28 +- Engine/lib/openal-soft/common/althreads.h | 4 +- Engine/lib/openal-soft/common/comptr.h | 11 +- Engine/lib/openal-soft/common/dynload.h | 6 +- Engine/lib/openal-soft/common/filesystem.cpp | 61 + Engine/lib/openal-soft/common/filesystem.h | 81 + Engine/lib/openal-soft/common/flexarray.h | 7 +- .../lib/openal-soft/common/ghc_filesystem.h | 6076 +++++++++++++++++ Engine/lib/openal-soft/common/intrusive_ptr.h | 7 +- Engine/lib/openal-soft/common/opthelpers.h | 24 + Engine/lib/openal-soft/common/pffft.cpp | 52 +- Engine/lib/openal-soft/common/pffft.h | 5 +- Engine/lib/openal-soft/common/phase_shifter.h | 35 +- .../common/polyphase_resampler.cpp | 103 +- .../openal-soft/common/polyphase_resampler.h | 2 +- Engine/lib/openal-soft/common/ringbuffer.cpp | 27 +- Engine/lib/openal-soft/common/ringbuffer.h | 3 +- Engine/lib/openal-soft/common/strutils.cpp | 2 + Engine/lib/openal-soft/common/vecmat.h | 7 +- Engine/lib/openal-soft/config.h.in | 73 +- Engine/lib/openal-soft/config_backends.h.in | 37 + Engine/lib/openal-soft/config_simd.h.in | 10 + .../configs/HRTF+WASAPI Exclusive/alsoft.ini | 8 + .../lib/openal-soft/configs/HRTF/alsoft.ini | 4 + .../configs/WASAPI Exclusive/alsoft.ini | 5 + Engine/lib/openal-soft/core/ambdec.cpp | 70 +- Engine/lib/openal-soft/core/ambidefs.cpp | 21 +- Engine/lib/openal-soft/core/ambidefs.h | 48 +- Engine/lib/openal-soft/core/bformatdec.cpp | 4 +- Engine/lib/openal-soft/core/bformatdec.h | 3 +- Engine/lib/openal-soft/core/bs2b.cpp | 56 +- Engine/lib/openal-soft/core/bs2b.h | 4 +- Engine/lib/openal-soft/core/bsinc_tables.cpp | 144 +- Engine/lib/openal-soft/core/bsinc_tables.h | 7 +- Engine/lib/openal-soft/core/buffer_storage.h | 4 +- Engine/lib/openal-soft/core/context.cpp | 29 +- Engine/lib/openal-soft/core/context.h | 18 +- Engine/lib/openal-soft/core/converter.cpp | 5 +- Engine/lib/openal-soft/core/converter.h | 4 +- Engine/lib/openal-soft/core/cpu_caps.cpp | 13 +- Engine/lib/openal-soft/core/cpu_caps.h | 2 +- Engine/lib/openal-soft/core/cubic_tables.cpp | 25 +- Engine/lib/openal-soft/core/cubic_tables.h | 3 +- Engine/lib/openal-soft/core/dbus_wrap.cpp | 6 +- Engine/lib/openal-soft/core/dbus_wrap.h | 4 +- Engine/lib/openal-soft/core/devformat.cpp | 47 +- Engine/lib/openal-soft/core/devformat.h | 5 +- Engine/lib/openal-soft/core/device.cpp | 3 - Engine/lib/openal-soft/core/device.h | 65 +- Engine/lib/openal-soft/core/effects/base.h | 3 +- Engine/lib/openal-soft/core/effectslot.h | 4 +- Engine/lib/openal-soft/core/except.cpp | 21 - Engine/lib/openal-soft/core/except.h | 8 +- Engine/lib/openal-soft/core/filters/biquad.h | 2 +- Engine/lib/openal-soft/core/filters/nfc.cpp | 121 +- .../lib/openal-soft/core/filters/splitter.h | 2 +- Engine/lib/openal-soft/core/fmt_traits.cpp | 79 - Engine/lib/openal-soft/core/fmt_traits.h | 71 +- Engine/lib/openal-soft/core/fpu_ctrl.cpp | 23 +- Engine/lib/openal-soft/core/front_stablizer.h | 2 +- Engine/lib/openal-soft/core/helpers.cpp | 157 +- Engine/lib/openal-soft/core/helpers.h | 4 +- Engine/lib/openal-soft/core/hrtf.cpp | 213 +- Engine/lib/openal-soft/core/hrtf.h | 5 +- Engine/lib/openal-soft/core/logging.cpp | 80 +- Engine/lib/openal-soft/core/logging.h | 20 +- Engine/lib/openal-soft/core/mastering.cpp | 42 +- Engine/lib/openal-soft/core/mastering.h | 22 +- Engine/lib/openal-soft/core/mixer.h | 5 +- Engine/lib/openal-soft/core/mixer/defs.h | 6 +- .../lib/openal-soft/core/mixer/mixer_neon.cpp | 22 +- .../lib/openal-soft/core/mixer/mixer_sse.cpp | 11 +- Engine/lib/openal-soft/core/rtkit.cpp | 1 + .../lib/openal-soft/core/storage_formats.cpp | 58 +- Engine/lib/openal-soft/core/storage_formats.h | 6 +- Engine/lib/openal-soft/core/uhjfilter.cpp | 102 +- Engine/lib/openal-soft/core/uhjfilter.h | 9 +- Engine/lib/openal-soft/core/uiddefs.cpp | 4 +- Engine/lib/openal-soft/core/voice.cpp | 263 +- Engine/lib/openal-soft/core/voice.h | 15 +- Engine/lib/openal-soft/docs/env-vars.txt | 16 + Engine/lib/openal-soft/examples/aldebug.cpp | 311 + Engine/lib/openal-soft/examples/aldirect.cpp | 32 +- Engine/lib/openal-soft/examples/alffplay.cpp | 830 ++- Engine/lib/openal-soft/examples/allafplay.cpp | 1012 +++ Engine/lib/openal-soft/examples/alloopback.c | 119 +- .../lib/openal-soft/examples/alstreamcb.cpp | 192 +- Engine/lib/openal-soft/examples/altonegen.c | 32 +- .../openal-soft/examples/common/alhelpers.h | 12 +- .../lib/openal-soft/fmt-11.1.1/.clang-format | 14 + .../fmt-11.1.1/.github/dependabot.yml | 8 + .../fmt-11.1.1/.github/issue_template.md | 6 + .../.github/pull_request_template.md | 7 + .../fmt-11.1.1/.github/workflows/cifuzz.yml | 32 + .../fmt-11.1.1/.github/workflows/doc.yml | 43 + .../fmt-11.1.1/.github/workflows/lint.yml | 28 + .../fmt-11.1.1/.github/workflows/linux.yml | 118 + .../fmt-11.1.1/.github/workflows/macos.yml | 58 + .../.github/workflows/scorecard.yml | 65 + .../fmt-11.1.1/.github/workflows/windows.yml | 95 + Engine/lib/openal-soft/fmt-11.1.1/.gitignore | 20 + .../lib/openal-soft/fmt-11.1.1/CMakeLists.txt | 84 + .../openal-soft/fmt-11.1.1/CONTRIBUTING.md | 20 + .../lib/openal-soft/fmt-11.1.1/ChangeLog.md | 2866 ++++++++ Engine/lib/openal-soft/fmt-11.1.1/LICENSE | 27 + Engine/lib/openal-soft/fmt-11.1.1/README.md | 485 ++ .../fmt-11.1.1/doc/ChangeLog-old.md | 3290 +++++++++ Engine/lib/openal-soft/fmt-11.1.1/doc/api.md | 673 ++ Engine/lib/openal-soft/fmt-11.1.1/doc/fmt.css | 61 + Engine/lib/openal-soft/fmt-11.1.1/doc/fmt.js | 4 + .../openal-soft/fmt-11.1.1/doc/get-started.md | 222 + .../lib/openal-soft/fmt-11.1.1/doc/index.md | 151 + .../lib/openal-soft/fmt-11.1.1/doc/perf.svg | 1 + .../fmt-11.1.1/doc/python-license.txt | 290 + .../lib/openal-soft/fmt-11.1.1/doc/syntax.md | 886 +++ .../openal-soft/fmt-11.1.1/include/fmt/args.h | 220 + .../openal-soft/fmt-11.1.1/include/fmt/base.h | 2954 ++++++++ .../fmt-11.1.1/include/fmt/chrono.h | 2338 +++++++ .../fmt-11.1.1/include/fmt/color.h | 610 ++ .../fmt-11.1.1/include/fmt/compile.h | 551 ++ .../openal-soft/fmt-11.1.1/include/fmt/core.h | 5 + .../fmt-11.1.1/include/fmt/format-inl.h | 1947 ++++++ .../fmt-11.1.1/include/fmt/format.h | 4220 ++++++++++++ .../openal-soft/fmt-11.1.1/include/fmt/os.h | 427 ++ .../fmt-11.1.1/include/fmt/ostream.h | 166 + .../fmt-11.1.1/include/fmt/printf.h | 633 ++ .../fmt-11.1.1/include/fmt/ranges.h | 848 +++ .../openal-soft/fmt-11.1.1/include/fmt/std.h | 727 ++ .../fmt-11.1.1/include/fmt/xchar.h | 368 + Engine/lib/openal-soft/fmt-11.1.1/src/fmt.cc | 151 + .../lib/openal-soft/fmt-11.1.1/src/format.cc | 46 + Engine/lib/openal-soft/fmt-11.1.1/src/os.cc | 398 ++ .../openal-soft/fmt-11.1.1/support/Android.mk | 15 + .../fmt-11.1.1/support/AndroidManifest.xml | 1 + .../fmt-11.1.1/support/C++.sublime-syntax | 2061 ++++++ .../lib/openal-soft/fmt-11.1.1/support/README | 4 + .../fmt-11.1.1/support/Vagrantfile | 19 + .../fmt-11.1.1/support/bazel/.bazelversion | 1 + .../fmt-11.1.1/support/bazel/BUILD.bazel | 20 + .../fmt-11.1.1/support/bazel/MODULE.bazel | 6 + .../fmt-11.1.1/support/bazel/README.md | 28 + .../fmt-11.1.1/support/bazel/WORKSPACE.bazel | 2 + .../fmt-11.1.1/support/build.gradle | 132 + .../fmt-11.1.1/support/check-commits | 43 + .../fmt-11.1.1/support/cmake/FindSetEnv.cmake | 7 + .../fmt-11.1.1/support/cmake/JoinPaths.cmake | 26 + .../support/cmake/fmt-config.cmake.in | 7 + .../fmt-11.1.1/support/cmake/fmt.pc.in | 11 + .../openal-soft/fmt-11.1.1/support/docopt.py | 581 ++ .../lib/openal-soft/fmt-11.1.1/support/mkdocs | 76 + .../openal-soft/fmt-11.1.1/support/mkdocs.yml | 48 + .../fmt-11.1.1/support/printable.py | 201 + .../mkdocstrings_handlers/cxx/__init__.py | 338 + .../cxx/templates/README | 1 + .../openal-soft/fmt-11.1.1/support/release.py | 188 + Engine/lib/openal-soft/include/AL/al.h | 14 +- Engine/lib/openal-soft/include/AL/alc.h | 14 +- Engine/lib/openal-soft/include/AL/alext.h | 27 +- Engine/lib/openal-soft/router/alc.cpp | 42 +- Engine/lib/openal-soft/router/router.cpp | 99 +- Engine/lib/openal-soft/router/router.h | 50 +- .../utils/alsoft-config/CMakeLists.txt | 6 +- .../utils/alsoft-config/mainwindow.cpp | 153 +- .../lib/openal-soft/utils/makemhr/loaddef.cpp | 486 +- .../openal-soft/utils/makemhr/loadsofa.cpp | 85 +- .../lib/openal-soft/utils/makemhr/makemhr.cpp | 161 +- .../lib/openal-soft/utils/makemhr/makemhr.h | 32 +- Engine/lib/openal-soft/utils/openal-info.c | 4 +- Engine/lib/openal-soft/utils/sofa-info.cpp | 47 +- Engine/lib/openal-soft/utils/sofa-support.cpp | 17 +- Engine/lib/openal-soft/utils/uhjdecoder.cpp | 33 +- Engine/lib/openal-soft/utils/uhjencoder.cpp | 113 +- 276 files changed, 49304 insertions(+), 8712 deletions(-) create mode 100644 Engine/lib/openal-soft/.clang-tidy rename Engine/lib/openal-soft/.github/workflows/{makemhr.yml => utils.yml} (56%) delete mode 100644 Engine/lib/openal-soft/al/error.h create mode 100644 Engine/lib/openal-soft/alc/backends/otherio.cpp create mode 100644 Engine/lib/openal-soft/alc/backends/otherio.h rename Engine/lib/openal-soft/alc/backends/{portaudio.h => portaudio.hpp} (79%) create mode 100644 Engine/lib/openal-soft/alc/backends/sdl3.cpp create mode 100644 Engine/lib/openal-soft/alc/backends/sdl3.h rename Engine/lib/openal-soft/alc/backends/{sndio.h => sndio.hpp} (81%) create mode 100644 Engine/lib/openal-soft/common/filesystem.cpp create mode 100644 Engine/lib/openal-soft/common/filesystem.h create mode 100644 Engine/lib/openal-soft/common/ghc_filesystem.h create mode 100644 Engine/lib/openal-soft/config_backends.h.in create mode 100644 Engine/lib/openal-soft/config_simd.h.in create mode 100644 Engine/lib/openal-soft/configs/HRTF+WASAPI Exclusive/alsoft.ini create mode 100644 Engine/lib/openal-soft/configs/HRTF/alsoft.ini create mode 100644 Engine/lib/openal-soft/configs/WASAPI Exclusive/alsoft.ini delete mode 100644 Engine/lib/openal-soft/core/fmt_traits.cpp create mode 100644 Engine/lib/openal-soft/examples/aldebug.cpp create mode 100644 Engine/lib/openal-soft/examples/allafplay.cpp create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/.clang-format create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/.github/dependabot.yml create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/.github/issue_template.md create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/.github/pull_request_template.md create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/cifuzz.yml create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/doc.yml create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/lint.yml create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/linux.yml create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/macos.yml create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/scorecard.yml create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/windows.yml create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/.gitignore create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/CMakeLists.txt create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/CONTRIBUTING.md create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/ChangeLog.md create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/LICENSE create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/README.md create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/doc/ChangeLog-old.md create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/doc/api.md create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/doc/fmt.css create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/doc/fmt.js create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/doc/get-started.md create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/doc/index.md create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/doc/perf.svg create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/doc/python-license.txt create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/doc/syntax.md create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/include/fmt/args.h create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/include/fmt/base.h create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/include/fmt/chrono.h create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/include/fmt/color.h create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/include/fmt/compile.h create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/include/fmt/core.h create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/include/fmt/format-inl.h create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/include/fmt/format.h create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/include/fmt/os.h create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/include/fmt/ostream.h create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/include/fmt/printf.h create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/include/fmt/ranges.h create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/include/fmt/std.h create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/include/fmt/xchar.h create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/src/fmt.cc create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/src/format.cc create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/src/os.cc create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/Android.mk create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/AndroidManifest.xml create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/C++.sublime-syntax create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/README create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/Vagrantfile create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/bazel/.bazelversion create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/bazel/BUILD.bazel create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/bazel/MODULE.bazel create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/bazel/README.md create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/bazel/WORKSPACE.bazel create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/build.gradle create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/check-commits create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/cmake/FindSetEnv.cmake create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/cmake/JoinPaths.cmake create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/cmake/fmt-config.cmake.in create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/cmake/fmt.pc.in create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/docopt.py create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/mkdocs create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/mkdocs.yml create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/printable.py create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/__init__.py create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/templates/README create mode 100644 Engine/lib/openal-soft/fmt-11.1.1/support/release.py diff --git a/Engine/lib/openal-soft/.clang-tidy b/Engine/lib/openal-soft/.clang-tidy new file mode 100644 index 000000000..3769fb87d --- /dev/null +++ b/Engine/lib/openal-soft/.clang-tidy @@ -0,0 +1,180 @@ +--- + Checks: '-*, + bugprone-argument-comment, + bugprone-assert-side-effect, + bugprone-assignment-in-if-condition, + bugprone-bad-signal-to-kill-thread, + bugprone-bool-pointer-implicit-conversion, + bugprone-casting-through-void, + bugprone-chained-comparison, + bugprone-compare-pointer-to-member-virtual-function, + bugprone-copy-constructor-init, + bugprone-crtp-constructor-accessibility, + bugprone-dangling-handle, + bugprone-dynamic-static-initializers, + bugprone-fold-init-type, + bugprone-forward-declaration-namespace, + bugprone-forwarding-reference-overload, + bugprone-implicit-widening-of-multiplication-result, + bugprone-inaccurate-erase, + bugprone-incorrect-*, + bugprone-infinite-loop, + bugprone-integer-division, + bugprone-lambda-function-name, + bugprone-macro-repeated-side-effects, + bugprone-misplaced-*, + bugprone-move-forwarding-reference, + bugprone-multi-level-implicit-pointer-conversion, + bugprone-multiple-*, + bugprone-narrowing-conversions, + bugprone-no-escape, + bugprone-non-zero-enum-to-bool-conversion, + bugprone-not-null-terminated-result, + bugprone-optional-value-conversion, + bugprone-parent-virtual-call, + bugprone-pointer-arithmetic-on-polymorphic-object, + bugprone-posix-return, + bugprone-redundant-branch-condition, + bugprone-reserved-identifier, + bugprone-return-const-ref-from-parameter, + bugprone-shared-ptr-array-mismatch, + bugprone-signal-handler, + bugprone-signed-char-misuse, + bugprone-sizeof-*, + bugprone-spuriously-wake-up-functions, + bugprone-standalone-empty, + bugprone-string-*, + bugprone-stringview-nullptr, + bugprone-suspicious-*, + bugprone-swapped-arguments, + bugprone-terminating-continue, + bugprone-throw-keyword-missing, + bugprone-too-small-loop-variable, + bugprone-undefined-memory-manipulation, + bugprone-undelegated-constructor, + bugprone-unhandled-*, + bugprone-unique-ptr-array-mismatch, + bugprone-unsafe-functions, + bugprone-unused-*, + bugprone-use-after-move, + bugprone-virtual-near-miss, + cert-dcl50-cpp, + cert-dcl58-cpp, + cert-env33-c, + cert-err34-c, + cert-err52-cpp, + cert-err60-cpp, + cert-flp30-c, + cert-mem57-cpp, + clang-analyzer-apiModeling.*, + clang-analyzer-core.*, + clang-analyzer-cplusplus.*, + clang-analyzer-deadcode.DeadStores, + clang-analyzer-fuchsia.HandleChecker, + clang-analyzer-nullability.*, + clang-analyzer-optin.*, + clang-analyzer-osx.*, + clang-analyzer-security.FloatLoopCounter, + clang-analyzer-security.PutenvStackArray, + clang-analyzer-security.SetgidSetuidOrder, + clang-analyzer-security.cert.env.InvalidPtr, + clang-analyzer-security.insecureAPI.SecuritySyntaxChecker, + clang-analyzer-security.insecureAPI.UncheckedReturn, + clang-analyzer-security.insecureAPI.bcmp, + clang-analyzer-security.insecureAPI.bcopy, + clang-analyzer-security.insecureAPI.bzero, + clang-analyzer-security.insecureAPI.decodeValueOfObjCType, + clang-analyzer-security.insecureAPI.getpw, + clang-analyzer-security.insecureAPI.gets, + clang-analyzer-security.insecureAPI.mkstemp, + clang-analyzer-security.insecureAPI.mktemp, + clang-analyzer-security.insecureAPI.rand, + clang-analyzer-security.insecureAPI.strcpy, + clang-analyzer-security.insecureAPI.vfork, + clang-analyzer-unix.*, + clang-analyzer-valist.*, + clang-analyzer-webkit.*, + concurrency-thread-canceltype-asynchronous, + cppcoreguidelines-avoid-capturing-lambda-coroutines, + cppcoreguidelines-avoid-c-arrays, + cppcoreguidelines-avoid-goto, + cppcoreguidelines-avoid-reference-coroutine-parameters, + cppcoreguidelines-c-copy-assignment-signature, + cppcoreguidelines-explicit-virtual-functions, + cppcoreguidelines-interfaces-global-init, + cppcoreguidelines-narrowing-conversions, + cppcoreguidelines-no-malloc, + cppcoreguidelines-no-suspend-with-lock, + cppcoreguidelines-owning-memory, + cppcoreguidelines-prefer-member-initializer, + cppcoreguidelines-pro-bounds-array-to-pointer-decay, + cppcoreguidelines-pro-bounds-pointer-arithmetic, + cppcoreguidelines-pro-type-const-cast, + cppcoreguidelines-pro-type-cstyle-cast, + cppcoreguidelines-pro-type-member-init, + cppcoreguidelines-pro-type-static-cast-downcast, + cppcoreguidelines-pro-type-union-access, + cppcoreguidelines-pro-type-vararg, + cppcoreguidelines-slicing, + cppcoreguidelines-virtual-class-destructor, + google-build-explicit-make-pair, + google-default-arguments, + google-explicit-constructor, + hicpp-exception-baseclass, + misc-confusable-identifiers, + misc-coroutine-hostile-raii, + misc-misleading-*, + misc-non-copyable-objects, + misc-throw-by-value-catch-by-reference, + misc-uniqueptr-reset-release, + modernize-avoid-*, + modernize-concat-nested-namespaces, + modernize-deprecated-*, + modernize-loop-convert, + modernize-macro-to-enum, + modernize-make-*, + modernize-pass-by-value, + modernize-raw-string-literal, + modernize-redundant-void-arg, + modernize-replace-*, + modernize-return-braced-init-list, + modernize-shrink-to-fit, + modernize-unary-static-assert, + modernize-use-auto, + modernize-use-bool-literals, + modernize-use-default-member-init, + modernize-use-emplace, + modernize-use-equals-*, + modernize-use-nodiscard, + modernize-use-noexcept, + modernize-use-nullptr, + modernize-use-override, + modernize-use-transparent-functors, + modernize-use-uncaught-exceptions, + modernize-use-using, + performance-faster-string-find, + performance-for-range-copy, + performance-inefficient-*, + performance-move-constructor-init, + performance-noexcept-destructor, + performance-noexcept-swap, + performance-unnecessary-copy-initialization, + portability-restrict-system-includes, + portability-std-allocator-const, + readability-const-return-type, + readability-container-contains, + readability-container-size-empty, + readability-convert-member-functions-to-static, + readability-delete-null-pointer, + readability-duplicate-include, + readability-else-after-return, + readability-inconsistent-declaration-parameter-name, + readability-make-member-function-const, + readability-misleading-indentation, + readability-misplaced-array-index, + readability-redundant-*, + readability-simplify-subscript-expr, + readability-static-definition-in-anonymous-namespace, + readability-string-compare, + readability-uniqueptr-delete-release, + readability-use-*' diff --git a/Engine/lib/openal-soft/.github/workflows/ci.yml b/Engine/lib/openal-soft/.github/workflows/ci.yml index b1c3b413b..071c93e5f 100644 --- a/Engine/lib/openal-soft/.github/workflows/ci.yml +++ b/Engine/lib/openal-soft/.github/workflows/ci.yml @@ -106,9 +106,35 @@ jobs: libdbus-1-dev", build_type: "Release" } - + - { + name: "Android_armeabi-v7a-Release", + os: ubuntu-latest, + cmake_opts: "-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ + -DALSOFT_EMBED_HRTF_DATA=TRUE \ + -DALSOFT_REQUIRE_OPENSL=ON", + build_type: "Release" + } + - { + name: "Android_arm64-v8a-Release", + os: ubuntu-latest, + cmake_opts: "-DANDRIOD_ABI=arm64-v8a \ + -DANDROID_PLATFORM=25 \ + -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ + -DALSOFT_EMBED_HRTF_DATA=TRUE \ + -DALSOFT_REQUIRE_OPENSL=ON", + build_type: "Release" + } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + fetch-depth: '0' + + - name: Get current commit tag, short hash, count and date + run: | + echo "CommitTag=$(git describe --tags --abbrev=0 --match *.*.*)" >> $env:GITHUB_ENV + echo "CommitHashShort=$(git rev-parse --short=8 HEAD)" >> $env:GITHUB_ENV + echo "CommitCount=$(git rev-list --count $env:GITHUB_REF_NAME)" >> $env:GITHUB_ENV + echo "CommitDate=$(git show -s --date=iso-local --format=%cd)" >> $env:GITHUB_ENV - name: Install Dependencies shell: bash @@ -133,8 +159,8 @@ jobs: cd build ctest - - name: Create Archive - if: ${{ matrix.config.os == 'windows-latest' }} + - name: Set up Windows artifacts + if: ${{ contains(matrix.config.name, 'Win') }} shell: bash run: | cd build @@ -143,10 +169,74 @@ jobs: cp ${{matrix.config.build_type}}/soft_oal.dll archive cp ${{matrix.config.build_type}}/OpenAL32.dll archive/router - - name: Upload Archive - # Upload package as an artifact of this workflow. - uses: actions/upload-artifact@v3.1.2 - if: ${{ matrix.config.os == 'windows-latest' }} + - name: Set up Android artifacts + if: ${{ contains(matrix.config.name, 'Android') }} + shell: bash + run: | + cd build + mkdir archive + cp ${{github.workspace}}/build/libopenal.so archive/ + + - name: Upload build as a workflow artifact + uses: actions/upload-artifact@v4 + if: ${{ contains(matrix.config.name, 'Win') || contains(matrix.config.name, 'Android') }} with: name: soft_oal-${{matrix.config.name}} path: build/archive + + outputs: + CommitTag: ${{env.CommitTag}} + CommitHashShort: ${{env.CommitHashShort}} + CommitCount: ${{env.CommitCount}} + CommitDate: ${{env.CommitDate}} + + release: + if: github.event_name != 'pull_request' + needs: build + runs-on: ubuntu-latest + steps: + + - name: Download build artifacts + uses: actions/download-artifact@v4.1.7 + with: + path: "build" + pattern: "*-Win??-Release" + github-token: "${{secrets.GITHUB_TOKEN}}" + + - name: Set up build folders + run: | + mkdir -p build/release/OpenALSoft/Documentation + mkdir -p build/release/OpenALSoft/Win32 + mkdir -p build/release/OpenALSoft/Win64 + echo "${{github.repository}}" >> "build/release/OpenALSoft/Documentation/Version.txt" + echo "v${{needs.build.outputs.CommitTag}}-${{needs.build.outputs.CommitHashShort}} ${{github.ref_name}}" >> "build/release/OpenALSoft/Documentation/Version.txt" + echo "Commit #${{needs.build.outputs.CommitCount}}" >> "build/release/OpenALSoft/Documentation/Version.txt" + echo "${{needs.build.outputs.CommitDate}}" >> "build/release/OpenALSoft/Documentation/Version.txt" + curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/README.md -o "build/release/OpenALSoft/Documentation/ReadMe.txt" + curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/ChangeLog -o "build/release/OpenALSoft/Documentation/ChangeLog.txt" + curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/COPYING -o "build/release/OpenALSoft/Documentation/License.txt" + curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/BSD-3Clause -o "build/release/OpenALSoft/Documentation/License_BSD-3Clause.txt" + curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/LICENSE-pffft -o "build/release/OpenALSoft/Documentation/License_PFFFT.txt" + curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/alsoftrc.sample -o "build/release/OpenALSoft/Win32/alsoft.ini" + curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/alsoftrc.sample -o "build/release/OpenALSoft/Win64/alsoft.ini" + cp "build/soft_oal-Win32-Release/soft_oal.dll" "build/release/OpenALSoft/Win32/OpenAL32.dll" + cp "build/soft_oal-Win64-Release/soft_oal.dll" "build/release/OpenALSoft/Win64/OpenAL32.dll" + cp -r "build/release/OpenALSoft" "build/release/OpenALSoft+HRTF" + cp "build/release/OpenALSoft+HRTF/Win32/alsoft.ini" "build/release/OpenALSoft+HRTF/Documentation/alsoft.ini" + curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/configs/HRTF/alsoft.ini -o "build/release/OpenALSoft+HRTF/Win32/alsoft.ini" + cp "build/release/OpenALSoft+HRTF/Win32/alsoft.ini" "build/release/OpenALSoft+HRTF/Win64/alsoft.ini" + + - name: Compress artifacts + run: | + cd build/release + 7z a OpenALSoft.zip ./OpenALSoft/* + 7z a OpenALSoft+HRTF.zip ./OpenALSoft+HRTF/* + + - name: GitHub pre-release + uses: "Sweeistaken/sweelease@v1.1" + with: + repo_token: "${{secrets.GITHUB_TOKEN}}" + automatic_release_tag: "latest" + prerelease: true + title: "OpenAL Soft v${{needs.build.outputs.CommitTag}}-${{needs.build.outputs.CommitHashShort}}" + files: "build/release/*" diff --git a/Engine/lib/openal-soft/.github/workflows/makemhr.yml b/Engine/lib/openal-soft/.github/workflows/utils.yml similarity index 56% rename from Engine/lib/openal-soft/.github/workflows/makemhr.yml rename to Engine/lib/openal-soft/.github/workflows/utils.yml index 654861500..4683a4e32 100644 --- a/Engine/lib/openal-soft/.github/workflows/makemhr.yml +++ b/Engine/lib/openal-soft/.github/workflows/utils.yml @@ -1,31 +1,35 @@ -name: makemhr +name: utils on: push: paths: - - 'utils/makemhr/**' - - '.github/workflows/makemhr.yml' + - 'utils/**' + - 'examples/**' + - '.github/workflows/utils.yml' workflow_dispatch: env: BUILD_TYPE: Release + Branch: ${{github.ref_name}} jobs: Win64: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - name: Clone repo and submodules + run: git clone https://github.com/${{github.repository}}.git . --branch ${{env.Branch}} - - name: Get current date - run: echo "CurrentDate=$(date +'%Y-%m-%d')" >> $env:GITHUB_ENV - - - name: Get commit hash - run: echo "CommitHash=$(git rev-parse --short=7 HEAD)" >> $env:GITHUB_ENV + - name: Get current date, commit hash and count + run: | + echo "CommitDate=$(git show -s --date=format:'%Y-%m-%d' --format=%cd)" >> $env:GITHUB_ENV + echo "CommitHashShort=$(git rev-parse --short=7 HEAD)" >> $env:GITHUB_ENV + echo "CommitCount=$(git rev-list --count ${{env.Branch}} --)" >> $env:GITHUB_ENV - name: Clone libmysofa - run: git clone --depth 1 --branch v1.3.1 https://github.com/hoene/libmysofa.git libmysofa + run: | + git clone https://github.com/hoene/libmysofa.git --branch v1.3.3 - name: Add MSBuild to PATH uses: microsoft/setup-msbuild@v1.1.3 @@ -52,25 +56,30 @@ jobs: - name: Collect artifacts run: | copy "build/Release/makemhr.exe" "Artifacts/makemhr.exe" + copy "build/Release/sofa-info.exe" "Artifacts/sofa-info.exe" + copy "build/Release/alrecord.exe" "Artifacts/alrecord.exe" + copy "build/Release/altonegen.exe" "Artifacts/altonegen.exe" + copy "build/Release/openal-info.exe" "Artifacts/openal-info.exe" + copy "build/Release/allafplay.exe" "Artifacts/allafplay.exe" copy "libmysofa/windows/third-party/zlib-1.2.11/bin/zlib.dll" "Artifacts/zlib.dll" - - name: Upload makemhr artifact - uses: actions/upload-artifact@v3.1.2 + - name: Upload artifacts + uses: actions/upload-artifact@v4 with: - name: makemhr + name: ${{env.CommitDate}}_utils-r${{env.CommitCount}}@${{env.CommitHashShort}} path: "Artifacts/" - name: Compress artifacts uses: papeloto/action-zip@v1 with: files: Artifacts/ - dest: "Release/makemhr.zip" + dest: "Release/utils.zip" - name: GitHub pre-release uses: "marvinpinto/action-automatic-releases@latest" with: repo_token: "${{secrets.GITHUB_TOKEN}}" - automatic_release_tag: "makemhr" + automatic_release_tag: "utils" prerelease: true - title: "[${{env.CurrentDate}}] makemhr-${{env.CommitHash}}" - files: "Release/makemhr.zip" + title: "[${{env.CommitDate}}] utils-r${{env.CommitCount}}@${{env.CommitHashShort}}" + files: "Release/utils.zip" diff --git a/Engine/lib/openal-soft/CMakeLists.txt b/Engine/lib/openal-soft/CMakeLists.txt index a45488a36..925512f7c 100644 --- a/Engine/lib/openal-soft/CMakeLists.txt +++ b/Engine/lib/openal-soft/CMakeLists.txt @@ -75,12 +75,12 @@ if(NOT CMAKE_DEBUG_POSTFIX) FORCE) endif() -set(DEFAULT_TARGET_PROPS +set(ALSOFT_STD_VERSION_PROPS # Require C++17. CXX_STANDARD 17 CXX_STANDARD_REQUIRED TRUE - # Prefer C11, but support C99 and earlier when possible. - C_STANDARD 11) + # Prefer C17, but support earlier when necessary. + C_STANDARD 17) list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") @@ -93,13 +93,13 @@ include(CheckCCompilerFlag) include(CheckCXXCompilerFlag) include(CheckCSourceCompiles) include(CheckCXXSourceCompiles) +include(CheckLinkerFlag) include(CheckStructHasMember) include(CMakePackageConfigHelpers) include(GNUInstallDirs) find_package(PkgConfig) -find_package(SDL2 QUIET) - +find_package(SDL3 QUIET) option(ALSOFT_DLOPEN "Check for the dlopen API for loading optional libs" ON) @@ -144,6 +144,23 @@ if(DEFINED ALSOFT_AMBDEC_PRESETS) message(WARNING "ALSOFT_AMBDEC_PRESETS is deprecated. Use ALSOFT_INSTALL_AMBDEC_PRESETS instead") endif() +if(MSVC) + option(FORCE_STATIC_VCRT "Force /MT for static VC runtimes" OFF) + if(FORCE_STATIC_VCRT) + foreach(flag_var + CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if(${flag_var} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + endif() + endforeach(flag_var) + endif() +endif() + +add_subdirectory(fmt-11.1.1 EXCLUDE_FROM_ALL) + set(CPP_DEFS ) # C pre-processor, not C++ set(INC_PATHS ) @@ -163,6 +180,21 @@ if(WIN32) if(MINGW) option(ALSOFT_BUILD_IMPORT_LIB "Build an import .lib using dlltool (requires sed)" ON) endif() + + if(NOT ALSOFT_UWP) + # Some systems may need NTDDI_VERSION defined to NTDDI_VISTA or later + check_c_source_compiles("#define INITGUID + #include + #include + int main() + { + SHGetKnownFolderPath(&FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, NULL, NULL); + return 0; + }" HAVE_SHGETKNOWNFOLDERPATH_NO_NTDDI) + if(NOT HAVE_SHGETKNOWNFOLDERPATH_NO_NTDDI) + set(CPP_DEFS ${CPP_DEFS} NTDDI_VERSION=NTDDI_VISTA) + endif() + endif() elseif(APPLE) option(ALSOFT_OSX_FRAMEWORK "Build as macOS framework" OFF) endif() @@ -181,8 +213,8 @@ if(NOT LIBTYPE) endif() set(LIB_MAJOR_VERSION "1") -set(LIB_MINOR_VERSION "23") -set(LIB_REVISION "1") +set(LIB_MINOR_VERSION "24") +set(LIB_REVISION "3") set(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}") set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0) @@ -227,12 +259,18 @@ if(ANDROID) endif() if(MSVC) - set(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS) + # NOTE: _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR is temporary. When building on + # VS 2022 17.10 or newer, but using an older runtime, mutexes can crash + # when locked. Ideally the runtime should be updated on the system, but + # until the update becomes more widespread, this helps avoid some pain + # points. + set(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR) check_cxx_compiler_flag(/permissive- HAVE_PERMISSIVE_SWITCH) if(HAVE_PERMISSIVE_SWITCH) set(C_FLAGS ${C_FLAGS} $<$:/permissive->) endif() - set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4127 /wd4268 /wd4324 /wd5030 /wd5051) + set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4127 /wd4268 /wd4324 /wd5030 /wd5051 + $<$:/EHsc> /utf-8) if(NOT DXSDK_DIR) string(REGEX REPLACE "\\\\" "/" DXSDK_DIR "$ENV{DXSDK_DIR}") @@ -242,19 +280,6 @@ if(MSVC) if(DXSDK_DIR) message(STATUS "Using DirectX SDK directory: ${DXSDK_DIR}") endif() - - option(FORCE_STATIC_VCRT "Force /MT for static VC runtimes" OFF) - if(FORCE_STATIC_VCRT) - foreach(flag_var - CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE - CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO - CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") - endif() - endforeach(flag_var) - endif() else() set(C_FLAGS ${C_FLAGS} -Winline -Wunused -Wall -Wextra -Wshadow -Wconversion -Wcast-align -Wpedantic @@ -277,15 +302,18 @@ else() if(ALSOFT_WERROR) set(C_FLAGS ${C_FLAGS} -Werror) + else() + set(C_FLAGS ${C_FLAGS} -Werror=undef) endif() - # We want RelWithDebInfo to actually include debug stuff (define _DEBUG - # instead of NDEBUG) - foreach(flag_var CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "-DNDEBUG") - string(REGEX REPLACE "-DNDEBUG" "-D_DEBUG" ${flag_var} "${${flag_var}}") - endif() - endforeach() + # NOTE: This essentially provides the equivalent of the C++26 feature to + # initialize all local variables with a non-0 bit pattern. Until C++26 is + # adopted, the [[gnu::uninitialized]] attribute will avoid the auto- + # initialization where necessary. + check_c_compiler_flag(-ftrivial-auto-var-init=pattern HAVE_FTRIVIAL_AUTO_VAR_INIT) + if(HAVE_FTRIVIAL_AUTO_VAR_INIT) + set(C_FLAGS ${C_FLAGS} -ftrivial-auto-var-init=pattern) + endif() check_c_compiler_flag(-fno-math-errno HAVE_FNO_MATH_ERRNO) if(HAVE_FNO_MATH_ERRNO) @@ -600,6 +628,8 @@ set(COMMON_OBJS common/comptr.h common/dynload.cpp common/dynload.h + common/filesystem.cpp + common/filesystem.h common/flexarray.h common/intrusive_ptr.h common/opthelpers.h @@ -657,7 +687,6 @@ set(CORE_OBJS core/filters/nfc.h core/filters/splitter.cpp core/filters/splitter.h - core/fmt_traits.cpp core/fmt_traits.h core/fpu_ctrl.cpp core/fpu_ctrl.h @@ -707,17 +736,17 @@ if(NOT WIN32) else() set(EXTRA_LIBS ${EXTRA_LIBS} ${DBus1_LIBRARIES}) endif() + else() + set(MISSING_VARS "") + if(NOT DBus1_INCLUDE_DIRS) + set(MISSING_VARS "${MISSING_VARS} DBus1_INCLUDE_DIRS") + endif() + if(NOT DBus1_LIBRARIES) + set(MISSING_VARS "${MISSING_VARS} DBus1_LIBRARIES") + endif() + message(STATUS "Could NOT find DBus1 (missing:${MISSING_VARS})") + unset(MISSING_VARS) endif() - else() - set(MISSING_VARS "") - if(NOT DBus1_INCLUDE_DIRS) - set(MISSING_VARS "${MISSING_VARS} DBus1_INCLUDE_DIRS") - endif() - if(NOT DBus1_LIBRARIES) - set(MISSING_VARS "${MISSING_VARS} DBus1_LIBRARIES") - endif() - message(STATUS "Could NOT find DBus1 (missing:${MISSING_VARS})") - unset(MISSING_VARS) endif() endif() if(ALSOFT_REQUIRE_RTKIT AND NOT HAVE_RTKIT) @@ -759,7 +788,6 @@ set(OPENAL_OBJS al/effects/reverb.cpp al/effects/vmorpher.cpp al/error.cpp - al/error.h al/event.cpp al/event.h al/extension.cpp @@ -861,8 +889,10 @@ set(HAVE_PULSEAUDIO 0) set(HAVE_COREAUDIO 0) set(HAVE_OPENSL 0) set(HAVE_OBOE 0) +set(HAVE_OTHERIO 0) set(HAVE_WAVE 0) set(HAVE_SDL2 0) +set(HAVE_SDL3 0) if(WIN32 OR HAVE_DLFCN_H) set(IS_LINKED "") @@ -976,7 +1006,7 @@ if(NOT WIN32) if(SNDIO_FOUND) set(HAVE_SNDIO 1) set(BACKENDS "${BACKENDS} SndIO (linked),") - set(ALC_OBJS ${ALC_OBJS} alc/backends/sndio.cpp alc/backends/sndio.h) + set(ALC_OBJS ${ALC_OBJS} alc/backends/sndio.cpp alc/backends/sndio.hpp) set(EXTRA_LIBS ${SNDIO_LIBRARIES} ${EXTRA_LIBS}) set(INC_PATHS ${INC_PATHS} ${SNDIO_INCLUDE_DIRS}) endif() @@ -1043,8 +1073,20 @@ if(WIN32) set(HAVE_WASAPI 1) set(BACKENDS "${BACKENDS} WASAPI,") set(ALC_OBJS ${ALC_OBJS} alc/backends/wasapi.cpp alc/backends/wasapi.h) + + if(NOT ALSOFT_UWP) + set(EXTRA_LIBS avrt ${EXTRA_LIBS}) + endif() endif() endif() + + option(ALSOFT_BACKEND_OTHERIO "Enable OtherIO backend" OFF) + option(ALSOFT_REQUIRE_OTHERIO "Require OtherIO backend" OFF) + if(ALSOFT_BACKEND_OTHERIO) + set(HAVE_OTHERIO 1) + set(BACKENDS "${BACKENDS} OtherIO,") + set(ALC_OBJS ${ALC_OBJS} alc/backends/otherio.cpp alc/backends/otherio.h) + endif() endif() if(ALSOFT_REQUIRE_WINMM AND NOT HAVE_WINMM) message(FATAL_ERROR "Failed to enable required WinMM backend") @@ -1055,6 +1097,9 @@ endif() if(ALSOFT_REQUIRE_WASAPI AND NOT HAVE_WASAPI) message(FATAL_ERROR "Failed to enable required WASAPI backend") endif() +if(ALSOFT_REQUIRE_OTHERIO AND NOT HAVE_OTHERIO) + message(FATAL_ERROR "Failed to enable required OtherIO backend") +endif() # Check JACK backend option(ALSOFT_BACKEND_JACK "Enable JACK backend" ON) @@ -1116,7 +1161,7 @@ if(ALSOFT_BACKEND_OBOE) if(ANDROID) set(OBOE_SOURCE "" CACHE STRING "Source directory for Oboe.") if(OBOE_SOURCE) - add_subdirectory(${OBOE_SOURCE} ./oboe) + add_subdirectory(${OBOE_SOURCE} ./oboe EXCLUDE_FROM_ALL) set(OBOE_TARGET oboe) else() find_package(oboe CONFIG) @@ -1148,8 +1193,8 @@ if(ALSOFT_BACKEND_OPENSL) if(OPENSL_FOUND) set(HAVE_OPENSL 1) set(ALC_OBJS ${ALC_OBJS} alc/backends/opensl.cpp alc/backends/opensl.h) - set(BACKENDS "${BACKENDS} OpenSL,") - set(EXTRA_LIBS ${OPENSL_LIBRARIES} ${EXTRA_LIBS}) + set(BACKENDS "${BACKENDS} OpenSL${IS_LINKED},") + add_backend_libs(${OPENSL_LIBRARIES}) set(INC_PATHS ${INC_PATHS} ${OPENSL_INCLUDE_DIRS}) endif() endif() @@ -1165,7 +1210,7 @@ if(ALSOFT_BACKEND_PORTAUDIO) if(PORTAUDIO_FOUND) set(HAVE_PORTAUDIO 1) set(BACKENDS "${BACKENDS} PortAudio${IS_LINKED},") - set(ALC_OBJS ${ALC_OBJS} alc/backends/portaudio.cpp alc/backends/portaudio.h) + set(ALC_OBJS ${ALC_OBJS} alc/backends/portaudio.cpp alc/backends/portaudio.hpp) add_backend_libs(${PORTAUDIO_LIBRARIES}) set(INC_PATHS ${INC_PATHS} ${PORTAUDIO_INCLUDE_DIRS}) endif() @@ -1174,11 +1219,29 @@ if(ALSOFT_REQUIRE_PORTAUDIO AND NOT HAVE_PORTAUDIO) message(FATAL_ERROR "Failed to enable required PortAudio backend") endif() -# Check for SDL2 backend -# Off by default, since it adds a runtime dependency +# Check for SDL2 or SDL3 backend +# Off by default, since it adds a runtime dependency. Additionally, both SDL2 +# and SDL3 can't be enabled simultaneously. +option(ALSOFT_BACKEND_SDL3 "Enable SDL3 backend" OFF) +option(ALSOFT_REQUIRE_SDL3 "Require SDL3 backend" OFF) +if(ALSOFT_BACKEND_SDL3) + if(SDL3_FOUND) + set(HAVE_SDL3 1) + set(ALC_OBJS ${ALC_OBJS} alc/backends/sdl3.cpp alc/backends/sdl3.h) + set(BACKENDS "${BACKENDS} SDL3,") + set(EXTRA_LIBS ${EXTRA_LIBS} SDL3::SDL3) + else() + message(STATUS "Could NOT find SDL3") + endif() +endif() +if(ALSOFT_REQUIRE_SDL3 AND NOT HAVE_SDL3) + message(FATAL_ERROR "Failed to enable required SDL3 backend") +endif() + option(ALSOFT_BACKEND_SDL2 "Enable SDL2 backend" OFF) option(ALSOFT_REQUIRE_SDL2 "Require SDL2 backend" OFF) -if(ALSOFT_BACKEND_SDL2) +if(ALSOFT_BACKEND_SDL2 AND NOT HAVE_SDL3) + find_package(SDL2) if(SDL2_FOUND) set(HAVE_SDL2 1) set(ALC_OBJS ${ALC_OBJS} alc/backends/sdl2.cpp alc/backends/sdl2.h) @@ -1188,7 +1251,7 @@ if(ALSOFT_BACKEND_SDL2) message(STATUS "Could NOT find SDL2") endif() endif() -if(ALSOFT_REQUIRE_SDL2 AND NOT SDL2_FOUND) +if(ALSOFT_REQUIRE_SDL2 AND NOT HAVE_SDL2) message(FATAL_ERROR "Failed to enable required SDL2 backend") endif() @@ -1230,7 +1293,7 @@ if(ALSOFT_UPDATE_BUILD_VERSION AND GIT_FOUND AND EXISTS "${OpenAL_SOURCE_DIR}/.g VERBATIM ) - add_custom_target(build_version DEPENDS "${OpenAL_BINARY_DIR}/version_witness.txt") + add_custom_target(alsoft.build_version DEPENDS "${OpenAL_BINARY_DIR}/version_witness.txt") else() set(GIT_BRANCH "UNKNOWN") set(GIT_COMMIT_HASH "unknown") @@ -1259,6 +1322,15 @@ if(ALSOFT_EMBED_HRTF_DATA) make_hrtf_header("Default HRTF.mhr" "default_hrtf") endif() +# Set a 16KB page size for Android +if(ANDROID) + set(CPP_DEFS ${CPP_DEFS} __BIONIC_NO_PAGE_SIZE_MACRO) + check_linker_flag(C "-Wl,-z,max-page-size=16384" HAS_MAX_PAGE_SIZE_16384) + if(HAS_MAX_PAGE_SIZE_16384) + set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,-z,max-page-size=16384") + endif() +endif() + if(ALSOFT_UTILS) find_package(MySOFA) @@ -1271,9 +1343,9 @@ if(ALSOFT_UTILS) endif() if(ALSOFT_UTILS OR ALSOFT_EXAMPLES) find_package(SndFile) - if(SDL2_FOUND) - find_package(FFmpeg COMPONENTS AVFORMAT AVCODEC AVUTIL SWSCALE SWRESAMPLE) - endif() +endif() +if(ALSOFT_EXAMPLES AND SDL3_FOUND) + find_package(FFmpeg COMPONENTS AVFORMAT AVCODEC AVUTIL SWSCALE SWRESAMPLE) endif() if(NOT WIN32) @@ -1295,12 +1367,14 @@ 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. If it's an SDL2 target, add the link flag for it. Otherwise, - # it's a name intended to be dressed as -lname. + # as-is. If it's an SDL2 or SDL3 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") + elseif(FLAG MATCHES "^SDL3::SDL3") + set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} -lSDL3") else() set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} -l${FLAG}") endif() @@ -1311,28 +1385,53 @@ endif() configure_file( "${OpenAL_SOURCE_DIR}/config.h.in" "${OpenAL_BINARY_DIR}/config.h") +configure_file( + "${OpenAL_SOURCE_DIR}/config_backends.h.in" + "${OpenAL_BINARY_DIR}/config_backends.h") +configure_file( + "${OpenAL_SOURCE_DIR}/config_simd.h.in" + "${OpenAL_BINARY_DIR}/config_simd.h") configure_file( "${OpenAL_SOURCE_DIR}/openal.pc.in" "${OpenAL_BINARY_DIR}/openal.pc" @ONLY) -add_library(alcommon STATIC EXCLUDE_FROM_ALL ${COMMON_OBJS}) -target_include_directories(alcommon PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/include - PUBLIC ${OpenAL_SOURCE_DIR}/common) -target_compile_definitions(alcommon PRIVATE ${CPP_DEFS}) -target_compile_options(alcommon PRIVATE ${C_FLAGS}) -set_target_properties(alcommon PROPERTIES ${DEFAULT_TARGET_PROPS} POSITION_INDEPENDENT_CODE TRUE) +add_library(alsoft.common STATIC EXCLUDE_FROM_ALL ${COMMON_OBJS}) +target_include_directories(alsoft.common PRIVATE ${OpenAL_SOURCE_DIR}/include + PUBLIC ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) +target_compile_definitions(alsoft.common PRIVATE ${CPP_DEFS}) +target_compile_options(alsoft.common PRIVATE ${C_FLAGS}) +target_link_libraries(alsoft.common PRIVATE alsoft::fmt) +set_target_properties(alsoft.common PROPERTIES ${ALSOFT_STD_VERSION_PROPS} + POSITION_INDEPENDENT_CODE TRUE) unset(HAS_ROUTER) set(IMPL_TARGET OpenAL) # Either OpenAL or soft_oal. + +set(NEED_ANALYZE_SOURCE_FILES "") +foreach(obj ${CORE_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${COMMON_OBJS}) + IF (NOT ${obj} MATCHES "${CMAKE_BINARY_DIR}/default_hrtf.txt") + list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${obj}") + endif() +endforeach() +IF (ALSOFT_UTILS) + list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/utils/openal-info.c") +endif() +SET(CLANG_TIDY_EXECUTABLE "clang-tidy") +if(DEFINED ENV{CLANG_TIDY_EXECUTABLE}) + SET(CLANG_TIDY_EXECUTABLE $ENV{CLANG_TIDY_EXECUTABLE}) +endif() +add_custom_target(clang-tidy-check ${CLANG_TIDY_EXECUTABLE} -format-style=file -p ${CMAKE_BINARY_DIR}/compile_commands.json ${NEED_ANALYZE_SOURCE_FILES} DEPENDS ${NEED_ANALYZE_SOURCE_FILES}) + # Build main library if(LIBTYPE STREQUAL "STATIC") add_library(${IMPL_TARGET} STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS}) target_compile_definitions(${IMPL_TARGET} PUBLIC AL_LIBTYPE_STATIC) - target_link_libraries(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) + target_link_libraries(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB} + $) if(WIN32) # This option is for static linking OpenAL Soft into another project @@ -1357,7 +1456,7 @@ else() PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" ${CPP_DEFS}) target_compile_options(OpenAL PRIVATE ${C_FLAGS}) - target_link_libraries(OpenAL PRIVATE alcommon ${LINKER_FLAGS}) + target_link_libraries(OpenAL PRIVATE alsoft.common ${LINKER_FLAGS} alsoft::fmt) target_include_directories(OpenAL PUBLIC $ @@ -1366,10 +1465,10 @@ else() ${OpenAL_SOURCE_DIR}/common ${OpenAL_BINARY_DIR} ) - set_target_properties(OpenAL PROPERTIES ${DEFAULT_TARGET_PROPS} PREFIX "" + set_target_properties(OpenAL PROPERTIES ${ALSOFT_STD_VERSION_PROPS} PREFIX "" OUTPUT_NAME ${LIBNAME}) - if(TARGET build_version) - add_dependencies(OpenAL build_version) + if(TARGET alsoft.build_version) + add_dependencies(OpenAL alsoft.build_version) endif() set(HAS_ROUTER 1) @@ -1388,24 +1487,30 @@ else() if(WIN32) set_target_properties(${IMPL_TARGET} PROPERTIES PREFIX "") endif() - target_link_libraries(${IMPL_TARGET} PRIVATE alcommon ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) + target_link_libraries(${IMPL_TARGET} PRIVATE alsoft.common ${LINKER_FLAGS} ${EXTRA_LIBS} + ${MATH_LIB} alsoft::fmt) if(ALSOFT_UWP) - set(ALSOFT_CPPWINRT_VERSION "2.0.230706.1" CACHE STRING "The soft-oal default cppwinrt version") - - find_program(NUGET_EXE NAMES nuget) - if(NOT NUGET_EXE) - message("NUGET.EXE not found.") - message(FATAL_ERROR "Please install this executable, and run CMake again.") + find_package(cppwinrt CONFIG) + if (TARGET Microsoft::CppWinRT) + target_link_libraries(${IMPL_TARGET} PRIVATE Microsoft::CppWinRT) + else() + set(ALSOFT_CPPWINRT_VERSION "2.0.230706.1" CACHE STRING "The soft-oal default cppwinrt version") + + find_program(NUGET_EXE NAMES nuget) + if(NOT NUGET_EXE) + message("NUGET.EXE not found.") + message(FATAL_ERROR "Please install this executable, and run CMake again.") + endif() + + exec_program(${NUGET_EXE} + ARGS install "Microsoft.Windows.CppWinRT" -Version ${ALSOFT_CPPWINRT_VERSION} -ExcludeVersion -OutputDirectory "\"${CMAKE_BINARY_DIR}/packages\"") + + set_target_properties(${IMPL_TARGET} PROPERTIES + VS_PROJECT_IMPORT ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.props + ) + target_link_libraries(${IMPL_TARGET} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.targets) endif() - - exec_program(${NUGET_EXE} - ARGS install "Microsoft.Windows.CppWinRT" -Version ${ALSOFT_CPPWINRT_VERSION} -ExcludeVersion -OutputDirectory "\"${CMAKE_BINARY_DIR}/packages\"") - - set_target_properties(${IMPL_TARGET} PROPERTIES - VS_PROJECT_IMPORT ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.props - ) - target_link_libraries(${IMPL_TARGET} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.targets) endif() if(NOT WIN32 AND NOT APPLE) @@ -1464,18 +1569,18 @@ target_include_directories(${IMPL_TARGET} ${OpenAL_SOURCE_DIR}/common ) -set_target_properties(${IMPL_TARGET} PROPERTIES ${DEFAULT_TARGET_PROPS} +set_target_properties(${IMPL_TARGET} PROPERTIES ${ALSOFT_STD_VERSION_PROPS} OUTPUT_NAME ${LIBNAME} VERSION ${LIB_VERSION} SOVERSION ${LIB_MAJOR_VERSION} ) target_compile_definitions(${IMPL_TARGET} - PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES $<$:ALSOFT_EAX> - "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" ${CPP_DEFS}) + PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" + ${CPP_DEFS}) target_compile_options(${IMPL_TARGET} PRIVATE ${C_FLAGS}) -if(TARGET build_version) - add_dependencies(${IMPL_TARGET} build_version) +if(TARGET alsoft.build_version) + add_dependencies(${IMPL_TARGET} alsoft.build_version) endif() if(WIN32 AND MINGW AND ALSOFT_BUILD_IMPORT_LIB AND NOT LIBTYPE STREQUAL "STATIC") @@ -1598,7 +1703,7 @@ if(ALSOFT_UTILS) target_include_directories(openal-info PRIVATE ${OpenAL_SOURCE_DIR}/common) target_compile_options(openal-info PRIVATE ${C_FLAGS}) target_link_libraries(openal-info PRIVATE ${LINKER_FLAGS} OpenAL ${UNICODE_FLAG}) - set_target_properties(openal-info PROPERTIES ${DEFAULT_TARGET_PROPS}) + set_target_properties(openal-info PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} openal-info) endif() @@ -1609,30 +1714,31 @@ if(ALSOFT_UTILS) target_include_directories(uhjdecoder PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) target_compile_options(uhjdecoder PRIVATE ${C_FLAGS}) - target_link_libraries(uhjdecoder PUBLIC alcommon - PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG}) - set_target_properties(uhjdecoder PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(uhjdecoder PUBLIC alsoft.common + PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG} alsoft::fmt) + set_target_properties(uhjdecoder PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(uhjencoder utils/uhjencoder.cpp) target_compile_definitions(uhjencoder PRIVATE ${CPP_DEFS}) target_include_directories(uhjencoder PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) target_compile_options(uhjencoder PRIVATE ${C_FLAGS}) - target_link_libraries(uhjencoder PUBLIC alcommon - PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG}) - set_target_properties(uhjencoder PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(uhjencoder PUBLIC alsoft.common + PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG} alsoft::fmt) + set_target_properties(uhjencoder PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) endif() if(MYSOFA_FOUND) set(SOFA_SUPPORT_SRCS utils/sofa-support.cpp utils/sofa-support.h) - add_library(sofa-support STATIC EXCLUDE_FROM_ALL ${SOFA_SUPPORT_SRCS}) - target_compile_definitions(sofa-support PRIVATE ${CPP_DEFS}) - target_include_directories(sofa-support PUBLIC ${OpenAL_SOURCE_DIR}/common) - target_compile_options(sofa-support PRIVATE ${C_FLAGS}) - target_link_libraries(sofa-support PUBLIC alcommon MySOFA::MySOFA PRIVATE ${LINKER_FLAGS}) - set_target_properties(sofa-support PROPERTIES ${DEFAULT_TARGET_PROPS}) + add_library(alsoft.sofa-support STATIC EXCLUDE_FROM_ALL ${SOFA_SUPPORT_SRCS}) + target_compile_definitions(alsoft.sofa-support PRIVATE ${CPP_DEFS}) + target_include_directories(alsoft.sofa-support PUBLIC ${OpenAL_SOURCE_DIR}/common) + target_compile_options(alsoft.sofa-support PRIVATE ${C_FLAGS}) + target_link_libraries(alsoft.sofa-support PUBLIC alsoft.common MySOFA::MySOFA + PRIVATE ${LINKER_FLAGS} alsoft::fmt) + set_target_properties(alsoft.sofa-support PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) set(MAKEMHR_SRCS utils/makemhr/loaddef.cpp @@ -1646,8 +1752,9 @@ if(ALSOFT_UTILS) target_include_directories(makemhr PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/utils) target_compile_options(makemhr PRIVATE ${C_FLAGS}) - target_link_libraries(makemhr PRIVATE ${LINKER_FLAGS} sofa-support ${UNICODE_FLAG}) - set_target_properties(makemhr PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(makemhr PRIVATE ${LINKER_FLAGS} alsoft.sofa-support ${UNICODE_FLAG} + alsoft::fmt) + set_target_properties(makemhr PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} makemhr) endif() @@ -1657,8 +1764,9 @@ if(ALSOFT_UTILS) target_compile_definitions(sofa-info PRIVATE ${CPP_DEFS}) target_include_directories(sofa-info PRIVATE ${OpenAL_SOURCE_DIR}/utils) target_compile_options(sofa-info PRIVATE ${C_FLAGS}) - target_link_libraries(sofa-info PRIVATE ${LINKER_FLAGS} sofa-support ${UNICODE_FLAG}) - set_target_properties(sofa-info PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(sofa-info PRIVATE ${LINKER_FLAGS} alsoft.sofa-support + ${UNICODE_FLAG} alsoft::fmt) + set_target_properties(sofa-info PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) endif() message(STATUS "Building utility programs") @@ -1670,75 +1778,86 @@ endif() # Add a static library with common functions used by multiple example targets -add_library(al-excommon STATIC EXCLUDE_FROM_ALL +add_library(alsoft.excommon STATIC EXCLUDE_FROM_ALL examples/common/alhelpers.c examples/common/alhelpers.h) -target_compile_definitions(al-excommon PUBLIC ${CPP_DEFS}) -target_include_directories(al-excommon PUBLIC ${OpenAL_SOURCE_DIR}/common) -target_compile_options(al-excommon PUBLIC ${C_FLAGS}) -target_link_libraries(al-excommon PUBLIC OpenAL PRIVATE ${RT_LIB}) -set_target_properties(al-excommon PROPERTIES ${DEFAULT_TARGET_PROPS}) +target_compile_definitions(alsoft.excommon PUBLIC ${CPP_DEFS}) +target_include_directories(alsoft.excommon PUBLIC ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) +target_compile_options(alsoft.excommon PUBLIC ${C_FLAGS}) +target_link_libraries(alsoft.excommon PUBLIC OpenAL PRIVATE ${RT_LIB}) +set_target_properties(alsoft.excommon PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_EXAMPLES) add_executable(altonegen examples/altonegen.c) - target_link_libraries(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} al-excommon ${UNICODE_FLAG}) - set_target_properties(altonegen PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} alsoft.excommon + ${UNICODE_FLAG}) + set_target_properties(altonegen PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alrecord examples/alrecord.c) - target_link_libraries(alrecord PRIVATE ${LINKER_FLAGS} al-excommon ${UNICODE_FLAG}) - set_target_properties(alrecord PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(alrecord PRIVATE ${LINKER_FLAGS} alsoft.excommon ${UNICODE_FLAG}) + set_target_properties(alrecord PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) + + add_executable(aldebug examples/aldebug.cpp) + target_link_libraries(aldebug PRIVATE ${LINKER_FLAGS} alsoft.excommon ${UNICODE_FLAG} + alsoft::fmt) + set_target_properties(aldebug PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) + + add_executable(allafplay examples/allafplay.cpp) + target_link_libraries(allafplay PRIVATE ${LINKER_FLAGS} alsoft.common alsoft.excommon + ${UNICODE_FLAG} alsoft::fmt) + set_target_properties(allafplay PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) - set(EXTRA_INSTALLS ${EXTRA_INSTALLS} altonegen alrecord) + set(EXTRA_INSTALLS ${EXTRA_INSTALLS} altonegen alrecord aldebug allafplay) endif() message(STATUS "Building example programs") if(SNDFILE_FOUND) add_executable(alplay examples/alplay.c) - target_link_libraries(alplay PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon + target_link_libraries(alplay PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${UNICODE_FLAG}) - set_target_properties(alplay PROPERTIES ${DEFAULT_TARGET_PROPS}) + set_target_properties(alplay PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alstream examples/alstream.c) - target_link_libraries(alstream PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon + target_link_libraries(alstream PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${UNICODE_FLAG}) - set_target_properties(alstream PROPERTIES ${DEFAULT_TARGET_PROPS}) + set_target_properties(alstream PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alreverb examples/alreverb.c) - target_link_libraries(alreverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon + target_link_libraries(alreverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${UNICODE_FLAG}) - set_target_properties(alreverb PROPERTIES ${DEFAULT_TARGET_PROPS}) + set_target_properties(alreverb PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(almultireverb examples/almultireverb.c) target_link_libraries(almultireverb - PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${MATH_LIB} ${UNICODE_FLAG}) - set_target_properties(almultireverb PROPERTIES ${DEFAULT_TARGET_PROPS}) + PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${MATH_LIB} ${UNICODE_FLAG}) + set_target_properties(almultireverb PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(allatency examples/allatency.c) - target_link_libraries(allatency PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon + target_link_libraries(allatency PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${UNICODE_FLAG}) - set_target_properties(allatency PROPERTIES ${DEFAULT_TARGET_PROPS}) + set_target_properties(allatency PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alhrtf examples/alhrtf.c) target_link_libraries(alhrtf - PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${MATH_LIB} ${UNICODE_FLAG}) - set_target_properties(alhrtf PROPERTIES ${DEFAULT_TARGET_PROPS}) + PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${MATH_LIB} ${UNICODE_FLAG}) + set_target_properties(alhrtf PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alstreamcb examples/alstreamcb.cpp) - target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon - ${UNICODE_FLAG}) - set_target_properties(alstreamcb PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon + ${UNICODE_FLAG} alsoft::fmt) + set_target_properties(alstreamcb PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(aldirect examples/aldirect.cpp) - target_link_libraries(aldirect PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon - ${UNICODE_FLAG}) - set_target_properties(aldirect PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(aldirect PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon + ${UNICODE_FLAG} alsoft::fmt) + set_target_properties(aldirect PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alconvolve examples/alconvolve.c) - target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} alcommon SndFile::SndFile - al-excommon ${UNICODE_FLAG}) - set_target_properties(alconvolve PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} alsoft.common SndFile::SndFile + alsoft.excommon ${UNICODE_FLAG}) + set_target_properties(alconvolve PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alplay alstream alreverb almultireverb allatency @@ -1748,18 +1867,19 @@ if(ALSOFT_EXAMPLES) message(STATUS "Building SndFile example programs") endif() - if(SDL2_FOUND) + # Can't safely use SDL3 and SDL2 together + if(SDL3_FOUND AND NOT HAVE_SDL2) + message(STATUS "Building SDL3 example programs") + add_executable(alloopback examples/alloopback.c) target_link_libraries(alloopback - PRIVATE ${LINKER_FLAGS} SDL2::SDL2 al-excommon ${MATH_LIB}) - set_target_properties(alloopback PROPERTIES ${DEFAULT_TARGET_PROPS}) + PRIVATE ${LINKER_FLAGS} SDL3::SDL3 alsoft.excommon ${MATH_LIB}) + set_target_properties(alloopback PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alloopback) endif() - message(STATUS "Building SDL example programs") - set(FFVER_OK FALSE) if(FFMPEG_FOUND) set(FFVER_OK TRUE) @@ -1788,19 +1908,19 @@ if(ALSOFT_EXAMPLES) add_executable(alffplay examples/alffplay.cpp) target_include_directories(alffplay PRIVATE ${FFMPEG_INCLUDE_DIRS}) target_link_libraries(alffplay - PRIVATE ${LINKER_FLAGS} SDL2::SDL2 ${FFMPEG_LIBRARIES} al-excommon) - set_target_properties(alffplay PROPERTIES ${DEFAULT_TARGET_PROPS}) + PRIVATE ${LINKER_FLAGS} SDL3::SDL3 ${FFMPEG_LIBRARIES} alsoft.excommon alsoft::fmt) + set_target_properties(alffplay PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alffplay) endif() - message(STATUS "Building SDL+FFmpeg example programs") + message(STATUS "Building SDL3+FFmpeg example programs") endif() endif() message(STATUS "") endif() -if (ALSOFT_TESTS) +if(ALSOFT_TESTS) add_subdirectory(tests) endif() diff --git a/Engine/lib/openal-soft/ChangeLog b/Engine/lib/openal-soft/ChangeLog index e4236f85d..c3a0ab6b1 100644 --- a/Engine/lib/openal-soft/ChangeLog +++ b/Engine/lib/openal-soft/ChangeLog @@ -1,3 +1,137 @@ +openal-soft-1.24.3: + + Fixed using as a static library when linked into another project that uses + fmtlib. + + Fixed building with static VC runtimes. + + Fixed building with Windows headers that default to older targets. + + Fixed building on 32-bit targets that use 32-bit file offsets. + + Fixed handling WASAPI enumerated device changes. + + Fixed a crash with UWP builds when __wargv is null. + + Fixed using AL_FORMAT_BFORMAT3D_I32. + + Improved the bsinc resamplers' cutoff frequencies. + + Slightly reduced the aliasing noise in the cubic spline resampler. + + Added new bsinc48 and fast_bsinc48 resampler options. + + Added support for 16KB page sizes on Android. + + Added support for using NFC filters with UHJ output. + +openal-soft-1.24.2: + + Implemented the AL_SOFT_bformat_hoa extension. + + Implemented default device change events for the PulseAudio backend. + + Implemented an option for WASAPI exclusive mode playback. + + Fixed reverb being too quiet for sounds from different directions. + + Fixed compiling with certain versions of Clang. + + Fixed compiling for some older macOS versions. + + Fixed building alffplay on systems without pkg-config. + + Improved output format detection for CoreAudio. + + Changed the default resampler back to Cubic Spline. + + Added an SDL3 playback backend. Disabled by default to avoid a runtime + dependency and for compatibility; a single process can't safely use SDL2 + and SDL3 together on some OSs, so enable with care. + + Converted examples from SDL2 to SDL3. + + Integrated fmtlib into the main library and router for logging and string + formatting. + +openal-soft-1.24.1: + + Fixed compilation on PowerPC. + + Fixed compilation on some targets that lack lock-free 64-bit atomics. + + Fixed a crash when parsing certain option values. + + Fixed applying noexcept in the public headers with MSVC. + + Fixed building for UWP with vcpkg. + + Improved compatibility when compiling as C++20 or later. + + Integrated fmtlib for some examples and utilities. + +openal-soft-1.24.0: + + Updated library codebase to C++17. + + Implemented the ALC_SOFT_system_events extension. + + Implemented the AL_EXT_debug extension. + + Implemented the AL_EXT_direct_context extension. + + Implemented speaker configuration and headphones detection on CoreAudio. + + Fixed a potential crash with some extension functions on 32-bit Windows. + + Fixed a crash that can occur when stopping playback with the Oboe backend. + + Fixed calculating the reverb room rolloff. + + Fixed EAX occlusion, obstruction, and exclusion low-pass filter strength. + + Fixed EAX distance factor calculations. + + Fixed querying AL_EFFECTSLOT_EFFECT on auxiliary effect slots. + + Fixed compilation on some macOS systems that lack libdispatch. + + Fixed compilation as a subproject with MinGW. + + Changed the context error state to be thread-local. This is technically out + of spec, but necessary to avoid race conditions with multi-threaded use. + + Split the cubic resampler into 4-point spline and gaussian variants. The + latter prioritizing the suppression of aliasing distortion and harmonics, + the former not reducing high frequencies as much. + + Improved timing precision of starting delayed sources. + + Improved ring modulator quality. + + Improved performance of convolution reverb. + + Improved WASAPI device enumeration performance. + + Added UWP support. + + Added 'noexcept' to functions and function types when compiled as C++. As a + C API, OpenAL can't be expected to throw C++ exceptions, nor can it handle + them if they leave a callback. + + Added an experimental config option for using WASAPI spatial audio output. + + Added enumeration support to the PortAudio backend. + + Added compatibility options to override the AL_VENDOR, AL_VERSION, and + AL_RENDERER strings. + + Added an example to play LAF files. + + Disabled real-time mixing by default for PipeWire playback. + + Disabled the SndIO backend by default on non-BSD targets. + openal-soft-1.23.1: Implemented the AL_SOFT_UHJ_ex extension. diff --git a/Engine/lib/openal-soft/README.md b/Engine/lib/openal-soft/README.md index dac53e71e..09f9ec24d 100644 --- a/Engine/lib/openal-soft/README.md +++ b/Engine/lib/openal-soft/README.md @@ -78,15 +78,26 @@ API, including some extensions. It also includes utility libraries for math and linear algebra, which can be useful for 3D calculations. Java Bindings: +* [LWJGL](https://github.com/LWJGL/lwjgl3), the Lightweight Java Game Library, +includes Java bindings for the OpenAL API, usable with OpenAL Soft. * [JOAL](https://jogamp.org/joal/www/), part of the JogAmp project, includes Java bindings for the OpenAL API, usable with OpenAL Soft. It also includes a higher level Sound3D Toolkit API and utility functions to make easier use of OpenAL features and capabilities. +Kotlin Bindings: +* [Multiplatform OpenAL](https://git.karmakrafts.dev/kk/multiplatform-openal), developed for the Kleaver project, +includes Kotlin/Native bindings for the OpenAL API, based on OpenAL Soft with support +for Windows, Linux, macOS, iOS and Android. + Python Bindings: * [PyOpenAL](https://pypi.org/project/PyOpenAL/). Also includes methods to play wave files and, with PyOgg, also Vorbis, Opus, and FLAC. +FreePascal/Lazarus Bindings: +* [ALSound](https://github.com/Lulu04/ALSound). Also includes a higher level +API and libsndfile support to simplify loading and playing sounds. + Other bindings for these and other languages also exist. This list will grow as more bindings are found. diff --git a/Engine/lib/openal-soft/al/auxeffectslot.cpp b/Engine/lib/openal-soft/al/auxeffectslot.cpp index 243f8fcbf..d405f18e2 100644 --- a/Engine/lib/openal-soft/al/auxeffectslot.cpp +++ b/Engine/lib/openal-soft/al/auxeffectslot.cpp @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -54,27 +53,27 @@ #include "buffer.h" #include "core/buffer_storage.h" #include "core/device.h" +#include "core/except.h" #include "core/fpu_ctrl.h" #include "core/logging.h" #include "direct_defs.h" #include "effect.h" -#include "error.h" #include "flexarray.h" #include "opthelpers.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include "eax/api.h" #include "eax/call.h" #include "eax/effect.h" #include "eax/fx_slot_index.h" -#include "eax/utils.h" #endif namespace { using SubListAllocator = al::allocator>; -EffectStateFactory *getFactoryByType(EffectSlotType type) +[[nodiscard]] +auto getFactoryByType(EffectSlotType type) -> EffectStateFactory* { switch(type) { @@ -98,6 +97,7 @@ EffectStateFactory *getFactoryByType(EffectSlotType type) } +[[nodiscard]] auto LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -> ALeffectslot* { const size_t lidx{(id-1) >> 6}; @@ -111,7 +111,8 @@ auto LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -> ALeffectslot* return al::to_address(sublist.EffectSlots->begin() + slidx); } -inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect* +[[nodiscard]] +inline auto LookupEffect(al::Device *device, ALuint id) noexcept -> ALeffect* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -124,7 +125,8 @@ inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect* return al::to_address(sublist.Effects->begin() + slidx); } -inline auto LookupBuffer(ALCdevice *device, ALuint id) noexcept -> ALbuffer* +[[nodiscard]] +inline auto LookupBuffer(al::Device *device, ALuint id) noexcept -> ALbuffer* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -217,6 +219,7 @@ void RemoveActiveEffectSlots(const al::span auxslots, ALCcontext } +[[nodiscard]] constexpr auto EffectSlotTypeFromEnum(ALenum type) noexcept -> EffectSlotType { switch(type) @@ -239,10 +242,11 @@ constexpr auto EffectSlotTypeFromEnum(ALenum type) noexcept -> EffectSlotType case AL_EFFECT_DEDICATED_DIALOGUE: return EffectSlotType::Dedicated; case AL_EFFECT_CONVOLUTION_SOFT: return EffectSlotType::Convolution; } - ERR("Unhandled effect enum: 0x%04x\n", type); + ERR("Unhandled effect enum: {:#04x}", as_unsigned(type)); return EffectSlotType::None; } +[[nodiscard]] auto EnsureEffectSlots(ALCcontext *context, size_t needed) noexcept -> bool try { size_t count{std::accumulate(context->mEffectSlotList.cbegin(), @@ -267,7 +271,8 @@ catch(...) { return false; } -ALeffectslot *AllocEffectSlot(ALCcontext *context) +[[nodiscard]] +auto AllocEffectSlot(ALCcontext *context) -> ALeffectslot* { auto sublist = std::find_if(context->mEffectSlotList.begin(), context->mEffectSlotList.end(), [](const EffectSlotSubList &entry) noexcept -> bool @@ -322,20 +327,21 @@ FORCE_ALIGN void AL_APIENTRY alGenAuxiliaryEffectSlotsDirect(ALCcontext *context ALuint *effectslots) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Generating %d effect slots", n}; + context->throw_error(AL_INVALID_VALUE, "Generating {} effect slots", n); if(n <= 0) UNLIKELY return; - std::lock_guard slotlock{context->mEffectSlotLock}; - ALCdevice *device{context->mALDevice.get()}; + auto slotlock = std::lock_guard{context->mEffectSlotLock}; + auto *device = context->mALDevice.get(); const al::span eids{effectslots, static_cast(n)}; - if(eids.size() > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots) - throw al::context_error{AL_OUT_OF_MEMORY, "Exceeding %u effect slot limit (%u + %d)", - device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, n}; + if(context->mNumEffectSlots > device->AuxiliaryEffectSlotMax + || eids.size() > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots) + context->throw_error(AL_OUT_OF_MEMORY, "Exceeding {} effect slot limit ({} + {})", + device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, n); if(!EnsureEffectSlots(context, eids.size())) - throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d effectslot%s", n, - (n == 1) ? "" : "s"}; + context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} effectslot{}", n, + (n==1) ? "" : "s"); std::vector slots; try { @@ -355,16 +361,18 @@ try { } } catch(std::exception& e) { - ERR("Exception allocating effectslot %zu of %d: %s\n", slots.size()+1, n, e.what()); + ERR("Exception allocating effectslot {} of {}: {}", slots.size()+1, n, e.what()); auto delete_effectslot = [context](ALeffectslot *slot) -> void { FreeEffectSlot(context, slot); }; std::for_each(slots.begin(), slots.end(), delete_effectslot); - throw al::context_error{AL_INVALID_OPERATION, "Exception allocating %d effectslots: %s", n, - e.what()}; + context->throw_error(AL_INVALID_OPERATION, "Exception allocating {} effectslots: {}", n, + e.what()); } } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteAuxiliaryEffectSlots, ALsizei,n, const ALuint*,effectslots) @@ -372,7 +380,7 @@ FORCE_ALIGN void AL_APIENTRY alDeleteAuxiliaryEffectSlotsDirect(ALCcontext *cont const ALuint *effectslots) noexcept try { if(n < 0) UNLIKELY - throw al::context_error{AL_INVALID_VALUE, "Deleting %d effect slots", n}; + context->throw_error(AL_INVALID_VALUE, "Deleting {} effect slots", n); if(n <= 0) UNLIKELY return; std::lock_guard slotlock{context->mEffectSlotLock}; @@ -380,10 +388,10 @@ try { { ALeffectslot *slot{LookupEffectSlot(context, *effectslots)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", *effectslots}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", *effectslots); if(slot->ref.load(std::memory_order_relaxed) != 0) - throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use effect slot %u", - *effectslots}; + context->throw_error(AL_INVALID_OPERATION, "Deleting in-use effect slot {}", + *effectslots); RemoveActiveEffectSlots({&slot, 1u}, context); FreeEffectSlot(context, slot); @@ -398,10 +406,9 @@ try { { ALeffectslot *slot{LookupEffectSlot(context, eid)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", eid}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", eid); if(slot->ref.load(std::memory_order_relaxed) != 0) - throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use effect slot %u", - eid}; + context->throw_error(AL_INVALID_OPERATION, "Deleting in-use effect slot {}", eid); return slot; }; std::transform(eids.cbegin(), eids.cend(), std::back_inserter(slots), lookupslot); @@ -417,8 +424,10 @@ try { std::for_each(eids.begin(), eids.end(), delete_effectslot); } } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsAuxiliaryEffectSlot, ALuint,effectslot) @@ -436,7 +445,6 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; - context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlaySOFT not supported"); } @@ -444,7 +452,6 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei, const ALuint*) n { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; - context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlayvSOFT not supported"); } @@ -452,7 +459,6 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; - context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopSOFT not supported"); } @@ -460,7 +466,6 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei, const ALuint*) n { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; - context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopvSOFT not supported"); } @@ -474,7 +479,7 @@ try { ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) UNLIKELY - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); ALeffectslot *target{}; ALenum err{}; @@ -482,20 +487,20 @@ try { { case AL_EFFECTSLOT_EFFECT: { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; - ALeffect *effect{value ? LookupEffect(device, static_cast(value)) : nullptr}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; + auto *effect = value ? LookupEffect(device, static_cast(value)) : nullptr; if(effect) err = slot->initEffect(effect->id, effect->type, effect->Props, context); else { if(value != 0) - throw al::context_error{AL_INVALID_VALUE, "Invalid effect ID %u", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid effect ID {}", value); err = slot->initEffect(0, AL_EFFECT_NULL, EffectProps{}, context); } } if(err != AL_NO_ERROR) - throw al::context_error{err, "Effect initialization failed"}; + context->throw_error(err, "Effect initialization failed"); if(slot->mState == SlotState::Initial) UNLIKELY { @@ -511,8 +516,7 @@ try { case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: if(!(value == AL_TRUE || value == AL_FALSE)) - throw al::context_error{AL_INVALID_VALUE, - "Effect slot auxiliary send auto out of range"}; + context->throw_error(AL_INVALID_VALUE, "Effect slot auxiliary send auto out of range"); if(!(slot->AuxSendAuto == !!value)) LIKELY { slot->AuxSendAuto = !!value; @@ -523,7 +527,7 @@ try { case AL_EFFECTSLOT_TARGET_SOFT: target = LookupEffectSlot(context, static_cast(value)); if(value && !target) - throw al::context_error{AL_INVALID_VALUE, "Invalid effect slot target ID"}; + context->throw_error(AL_INVALID_VALUE, "Invalid effect slot target ID {}", value); if(slot->Target == target) UNLIKELY return; if(target) @@ -532,9 +536,9 @@ try { while(checker && checker != slot) checker = checker->Target; if(checker) - throw al::context_error{AL_INVALID_OPERATION, - "Setting target of effect slot ID %u to %u creates circular chain", slot->id, - target->id}; + context->throw_error(AL_INVALID_OPERATION, + "Setting target of effect slot ID {} to {} creates circular chain", slot->id, + target->id); } if(ALeffectslot *oldtarget{slot->Target}) @@ -569,17 +573,17 @@ try { assert(factory); al::intrusive_ptr state{factory->create()}; - ALCdevice *device{context->mALDevice.get()}; + auto *device = context->mALDevice.get(); auto bufferlock = std::unique_lock{device->BufferLock}; ALbuffer *buffer{}; if(value) { buffer = LookupBuffer(device, static_cast(value)); if(!buffer) - throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %u", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid buffer ID {}", value); if(buffer->mCallback) - throw al::context_error{AL_INVALID_OPERATION, - "Callback buffer not valid for effects"}; + context->throw_error(AL_INVALID_OPERATION, + "Callback buffer not valid for effects"); IncrementRef(buffer->ref); } @@ -605,17 +609,17 @@ try { } else { - ALCdevice *device{context->mALDevice.get()}; + auto *device = context->mALDevice.get(); auto bufferlock = std::unique_lock{device->BufferLock}; ALbuffer *buffer{}; if(value) { buffer = LookupBuffer(device, static_cast(value)); if(!buffer) - throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %u", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid buffer ID {}", value); if(buffer->mCallback) - throw al::context_error{AL_INVALID_OPERATION, - "Callback buffer not valid for effects"}; + context->throw_error(AL_INVALID_OPERATION, + "Callback buffer not valid for effects"); IncrementRef(buffer->ref); } @@ -633,13 +637,16 @@ try { return; case AL_EFFECTSLOT_STATE_SOFT: - throw al::context_error{AL_INVALID_OPERATION, "AL_EFFECTSLOT_STATE_SOFT is read-only"}; + context->throw_error(AL_INVALID_OPERATION, "AL_EFFECTSLOT_STATE_SOFT is read-only"); } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, const ALint*,values) @@ -660,16 +667,15 @@ try { std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat,value) @@ -681,13 +687,13 @@ try { ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); switch(param) { case AL_EFFECTSLOT_GAIN: if(!(value >= 0.0f && value <= 1.0f)) - throw al::context_error{AL_INVALID_VALUE, "Effect slot gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Effect slot gain {} out of range", value); if(!(slot->Gain == value)) LIKELY { slot->Gain = value; @@ -696,10 +702,13 @@ try { return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, const ALfloat*,values) @@ -716,16 +725,15 @@ try { std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -736,7 +744,7 @@ try { std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); switch(param) { @@ -767,10 +775,13 @@ try { return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, ALint*,values) @@ -791,16 +802,15 @@ try { std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot = LookupEffectSlot(context, effectslot); if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat*,value) @@ -810,19 +820,20 @@ try { std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); switch(param) { - case AL_EFFECTSLOT_GAIN: - *value = slot->Gain; - return; + case AL_EFFECTSLOT_GAIN: *value = slot->Gain; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, ALfloat*,values) @@ -839,16 +850,15 @@ try { std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -890,12 +900,13 @@ ALenum ALeffectslot::initEffect(ALuint effectId, ALenum effectType, const Effect EffectStateFactory *factory{getFactoryByType(newtype)}; if(!factory) { - ERR("Failed to find factory for effect slot type %d\n", static_cast(newtype)); + ERR("Failed to find factory for effect slot type {}", + int{al::to_underlying(newtype)}); return AL_INVALID_ENUM; } al::intrusive_ptr state{factory->create()}; - ALCdevice *device{context->mALDevice.get()}; + auto *device = context->mALDevice.get(); state->mOutTarget = device->Dry.Buffer; { FPUCtl mixer_mode{}; @@ -964,7 +975,7 @@ void ALeffectslot::SetName(ALCcontext* context, ALuint id, std::string_view name auto slot = LookupEffectSlot(context, id); if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", id}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", id); context->mEffectSlotNames.insert_or_assign(id, name); } @@ -1004,51 +1015,51 @@ EffectSlotSubList::~EffectSlotSubList() EffectSlots = nullptr; } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX void ALeffectslot::eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index) { if(index >= EAX_MAX_FXSLOTS) eax_fail("Index out of range."); - eax_al_context_ = &al_context; - eax_fx_slot_index_ = index; + mEaxALContext = &al_context; + mEaxFXSlotIndex = index; eax_fx_slot_set_defaults(); - eax_effect_ = std::make_unique(); - if(index == 0) eax_effect_->init(); - else if(index == 1) eax_effect_->init(); - else eax_effect_->init(); + mEaxEffect = std::make_unique(); + if(index == 0) mEaxEffect->init(); + else if(index == 1) mEaxEffect->init(); + else mEaxEffect->init(); } void ALeffectslot::eax_commit() { - if(eax_df_ != EaxDirtyFlags{}) + if(mEaxDf.any()) { - auto df = EaxDirtyFlags{}; - switch(eax_version_) + auto df = std::bitset{}; + switch(mEaxVersion) { case 1: case 2: case 3: - eax5_fx_slot_commit(eax123_, df); + eax5_fx_slot_commit(mEax123, df); break; case 4: eax4_fx_slot_commit(df); break; case 5: - eax5_fx_slot_commit(eax5_, df); + eax5_fx_slot_commit(mEax5, df); break; } - eax_df_ = EaxDirtyFlags{}; + mEaxDf.reset(); - if((df & eax_volume_dirty_bit) != EaxDirtyFlags{}) + if(df.test(eax_volume_dirty_bit)) eax_fx_slot_set_volume(); - if((df & eax_flags_dirty_bit) != EaxDirtyFlags{}) + if(df.test(eax_flags_dirty_bit)) eax_fx_slot_set_flags(); } - if(eax_effect_->commit(eax_version_)) - eax_set_efx_slot_effect(*eax_effect_); + if(mEaxEffect->commit(mEaxVersion)) + eax_set_efx_slot_effect(*mEaxEffect); } [[noreturn]] void ALeffectslot::eax_fail(const char* message) @@ -1111,7 +1122,7 @@ ALenum ALeffectslot::eax_get_efx_effect_type(const GUID& guid) const GUID& ALeffectslot::eax_get_eax_default_effect_guid() const noexcept { - switch(eax_fx_slot_index_) + switch(mEaxFXSlotIndex) { case 0: return EAX_REVERB_EFFECT; case 1: return EAX_CHORUS_EFFECT; @@ -1124,7 +1135,7 @@ long ALeffectslot::eax_get_eax_default_lock() const noexcept return eax4_fx_slot_is_legacy() ? EAXFXSLOT_LOCKED : EAXFXSLOT_UNLOCKED; } -void ALeffectslot::eax4_fx_slot_set_defaults(Eax4Props& props) noexcept +void ALeffectslot::eax4_fx_slot_set_defaults(EAX40FXSLOTPROPERTIES& props) noexcept { props.guidLoadEffect = eax_get_eax_default_effect_guid(); props.lVolume = EAXFXSLOT_DEFAULTVOLUME; @@ -1132,7 +1143,7 @@ void ALeffectslot::eax4_fx_slot_set_defaults(Eax4Props& props) noexcept props.ulFlags = EAX40FXSLOT_DEFAULTFLAGS; } -void ALeffectslot::eax5_fx_slot_set_defaults(Eax5Props& props) noexcept +void ALeffectslot::eax5_fx_slot_set_defaults(EAX50FXSLOTPROPERTIES& props) noexcept { props.guidLoadEffect = eax_get_eax_default_effect_guid(); props.lVolume = EAXFXSLOT_DEFAULTVOLUME; @@ -1144,14 +1155,14 @@ void ALeffectslot::eax5_fx_slot_set_defaults(Eax5Props& props) noexcept 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_ = eax5_.i; - eax_df_ = EaxDirtyFlags{}; + eax5_fx_slot_set_defaults(mEax123.i); + eax4_fx_slot_set_defaults(mEax4.i); + eax5_fx_slot_set_defaults(mEax5.i); + mEax = mEax5.i; + mEaxDf.reset(); } -void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) +void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const EAX40FXSLOTPROPERTIES& props) { switch(call.get_property_id()) { @@ -1175,7 +1186,7 @@ void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) } } -void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) +void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const EAX50FXSLOTPROPERTIES& props) { switch(call.get_property_id()) { @@ -1209,8 +1220,8 @@ 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; + case 4: eax4_fx_slot_get(call, mEax4.i); break; + case 5: eax5_fx_slot_get(call, mEax5.i); break; default: eax_fail_unknown_version(); } } @@ -1223,7 +1234,7 @@ bool ALeffectslot::eax_get(const EaxCall& call) eax_fx_slot_get(call); break; case EaxCallPropertySetId::fx_slot_effect: - eax_effect_->get(call); + mEaxEffect->get(call); break; default: eax_fail_unknown_property_id(); @@ -1236,19 +1247,19 @@ void ALeffectslot::eax_fx_slot_load_effect(int version, ALenum altype) { if(!IsValidEffectType(altype)) altype = AL_EFFECT_NULL; - eax_effect_->set_defaults(version, altype); + mEaxEffect->set_defaults(version, altype); } void ALeffectslot::eax_fx_slot_set_volume() { - const auto volume = std::clamp(eax_.lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME); + const auto volume = std::clamp(mEax.lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME); const auto gain = level_mb_to_gain(static_cast(volume)); eax_set_efx_slot_gain(gain); } void ALeffectslot::eax_fx_slot_set_environment_flag() { - eax_set_efx_slot_send_auto((eax_.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0u); + eax_set_efx_slot_send_auto((mEax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0u); } void ALeffectslot::eax_fx_slot_set_flags() @@ -1261,11 +1272,11 @@ 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{}); + auto& dst = mEax4.i; + mEaxDf.set(eax_load_effect_dirty_bit); // Always reset the effect. + if(dst.lVolume != src.lVolume) mEaxDf.set(eax_volume_dirty_bit); + if(dst.lLock != src.lLock) mEaxDf.set(eax_lock_dirty_bit); + if(dst.ulFlags != src.ulFlags) mEaxDf.set(eax_flags_dirty_bit); dst = src; } @@ -1273,30 +1284,30 @@ 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{}); + auto& dst = mEax5.i; + mEaxDf.set(eax_load_effect_dirty_bit); // Always reset the effect. + if(dst.lVolume != src.lVolume) mEaxDf.set(eax_volume_dirty_bit); + if(dst.lLock != src.lLock) mEaxDf.set(eax_lock_dirty_bit); + if(dst.ulFlags != src.ulFlags) mEaxDf.set(eax_flags_dirty_bit); + if(dst.lOcclusion != src.lOcclusion) mEaxDf.set(eax_flags_dirty_bit); + if(dst.flOcclusionLFRatio != src.flOcclusionLFRatio) mEaxDf.set(eax_flags_dirty_bit); dst = src; } bool ALeffectslot::eax_fx_slot_should_update_sources() const noexcept { - static constexpr auto dirty_bits = - eax_occlusion_dirty_bit | - eax_occlusion_lf_ratio_dirty_bit | - eax_flags_dirty_bit; - - return (eax_df_ & dirty_bits) != EaxDirtyFlags{}; + static constexpr auto dirty_bits = std::bitset{ + (1u << eax_occlusion_dirty_bit) + | (1u << eax_occlusion_lf_ratio_dirty_bit) + | (1u << eax_flags_dirty_bit) + }; + return (mEaxDf & dirty_bits).any(); } // Returns `true` if all sources should be updated, or `false` otherwise. bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call) { - auto& dst = eax4_.i; + auto& dst = mEax4.i; switch(call.get_property_id()) { @@ -1304,24 +1315,25 @@ bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call) break; case EAXFXSLOT_ALLPARAMETERS: eax4_fx_slot_set_all(call); - if((eax_df_ & eax_load_effect_dirty_bit)) + if(mEaxDf.test(eax_load_effect_dirty_bit)) eax_fx_slot_load_effect(4, eax_get_efx_effect_type(dst.guidLoadEffect)); break; case EAXFXSLOT_LOADEFFECT: eax4_fx_slot_ensure_unlocked(); - eax_fx_slot_set_dirty(call, dst.guidLoadEffect, eax_df_); - if((eax_df_ & eax_load_effect_dirty_bit)) + eax_fx_slot_set_dirty(call, + dst.guidLoadEffect, mEaxDf); + if(mEaxDf.test(eax_load_effect_dirty_bit)) eax_fx_slot_load_effect(4, eax_get_efx_effect_type(dst.guidLoadEffect)); break; case EAXFXSLOT_VOLUME: - eax_fx_slot_set(call, dst.lVolume, eax_df_); + eax_fx_slot_set(call, dst.lVolume, mEaxDf); break; case EAXFXSLOT_LOCK: eax4_fx_slot_ensure_unlocked(); - eax_fx_slot_set(call, dst.lLock, eax_df_); + eax_fx_slot_set(call, dst.lLock, mEaxDf); break; case EAXFXSLOT_FLAGS: - eax_fx_slot_set(call, dst.ulFlags, eax_df_); + eax_fx_slot_set(call, dst.ulFlags, mEaxDf); break; default: eax_fail_unknown_property_id(); @@ -1333,7 +1345,7 @@ bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call) // Returns `true` if all sources should be updated, or `false` otherwise. bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call) { - auto& dst = eax5_.i; + auto& dst = mEax5.i; switch(call.get_property_id()) { @@ -1341,28 +1353,31 @@ bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call) break; case EAXFXSLOT_ALLPARAMETERS: eax5_fx_slot_set_all(call); - if((eax_df_ & eax_load_effect_dirty_bit)) + if(mEaxDf.test(eax_load_effect_dirty_bit)) eax_fx_slot_load_effect(5, eax_get_efx_effect_type(dst.guidLoadEffect)); break; case EAXFXSLOT_LOADEFFECT: - eax_fx_slot_set_dirty(call, dst.guidLoadEffect, eax_df_); - if((eax_df_ & eax_load_effect_dirty_bit)) + eax_fx_slot_set_dirty(call, + dst.guidLoadEffect, mEaxDf); + if(mEaxDf.test(eax_load_effect_dirty_bit)) eax_fx_slot_load_effect(5, eax_get_efx_effect_type(dst.guidLoadEffect)); break; case EAXFXSLOT_VOLUME: - eax_fx_slot_set(call, dst.lVolume, eax_df_); + eax_fx_slot_set(call, dst.lVolume, mEaxDf); break; case EAXFXSLOT_LOCK: - eax_fx_slot_set(call, dst.lLock, eax_df_); + eax_fx_slot_set(call, dst.lLock, mEaxDf); break; case EAXFXSLOT_FLAGS: - eax_fx_slot_set(call, dst.ulFlags, eax_df_); + eax_fx_slot_set(call, dst.ulFlags, mEaxDf); break; case EAXFXSLOT_OCCLUSION: - eax_fx_slot_set(call, dst.lOcclusion, eax_df_); + eax_fx_slot_set(call, dst.lOcclusion, + mEaxDf); break; case EAXFXSLOT_OCCLUSIONLFRATIO: - eax_fx_slot_set(call, dst.flOcclusionLFRatio, eax_df_); + eax_fx_slot_set(call, + dst.flOcclusionLFRatio, mEaxDf); break; default: eax_fail_unknown_property_id(); @@ -1374,7 +1389,7 @@ bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call) // Returns `true` if all sources should be updated, or `false` otherwise. bool ALeffectslot::eax_fx_slot_set(const EaxCall& call) { - switch (call.get_version()) + switch(call.get_version()) { case 4: return eax4_fx_slot_set(call); case 5: return eax5_fx_slot_set(call); @@ -1390,39 +1405,41 @@ bool ALeffectslot::eax_set(const EaxCall& call) switch(call.get_property_set_id()) { case EaxCallPropertySetId::fx_slot: ret = eax_fx_slot_set(call); break; - case EaxCallPropertySetId::fx_slot_effect: eax_effect_->set(call); break; + case EaxCallPropertySetId::fx_slot_effect: mEaxEffect->set(call); break; default: eax_fail_unknown_property_id(); } const auto version = call.get_version(); - if(eax_version_ != version) - eax_df_ = ~EaxDirtyFlags{}; - eax_version_ = version; + if(mEaxVersion != version) + mEaxDf.set(); + mEaxVersion = version; return ret; } -void ALeffectslot::eax4_fx_slot_commit(EaxDirtyFlags& dst_df) +void ALeffectslot::eax4_fx_slot_commit(std::bitset& dst_df) { - eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::guidLoadEffect); - eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::lVolume); - eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::lLock); - eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::ulFlags); + eax_fx_slot_commit_property(mEax4, dst_df, &EAX40FXSLOTPROPERTIES::guidLoadEffect); + eax_fx_slot_commit_property(mEax4, dst_df, &EAX40FXSLOTPROPERTIES::lVolume); + eax_fx_slot_commit_property(mEax4, dst_df, &EAX40FXSLOTPROPERTIES::lLock); + eax_fx_slot_commit_property(mEax4, dst_df, &EAX40FXSLOTPROPERTIES::ulFlags); - auto& dst_i = eax_; + auto& dst_i = mEax; - if(dst_i.lOcclusion != EAXFXSLOT_DEFAULTOCCLUSION) { - dst_df |= eax_occlusion_dirty_bit; + if(dst_i.lOcclusion != EAXFXSLOT_DEFAULTOCCLUSION) + { + dst_df.set(eax_occlusion_dirty_bit); dst_i.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION; } - if(dst_i.flOcclusionLFRatio != EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO) { - dst_df |= eax_occlusion_lf_ratio_dirty_bit; + if(dst_i.flOcclusionLFRatio != EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO) + { + dst_df.set(eax_occlusion_lf_ratio_dirty_bit); dst_i.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO; } } -void ALeffectslot::eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df) +void ALeffectslot::eax5_fx_slot_commit(Eax5State& state, std::bitset& dst_df) { eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::guidLoadEffect); eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::lVolume); @@ -1437,18 +1454,19 @@ void ALeffectslot::eax_set_efx_slot_effect(EaxEffect &effect) #define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_EFFECT] " const auto error = initEffect(0, effect.al_effect_type_, effect.al_effect_props_, - eax_al_context_); - - if(error != AL_NO_ERROR) { - ERR(EAX_PREFIX "%s\n", "Failed to initialize an effect."); + mEaxALContext); + if(error != AL_NO_ERROR) + { + ERR(EAX_PREFIX "Failed to initialize an effect."); return; } - if(mState == SlotState::Initial) { + if(mState == SlotState::Initial) + { mPropsDirty = false; - updateProps(eax_al_context_); + updateProps(mEaxALContext); auto effect_slot_ptr = this; - AddActiveEffectSlots({&effect_slot_ptr, 1}, eax_al_context_); + AddActiveEffectSlots({&effect_slot_ptr, 1}, mEaxALContext); mState = SlotState::Playing; return; } @@ -1474,7 +1492,7 @@ void ALeffectslot::eax_set_efx_slot_gain(ALfloat gain) if(gain == Gain) return; if(gain < 0.0f || gain > 1.0f) - ERR(EAX_PREFIX "Gain out of range (%f)\n", gain); + ERR(EAX_PREFIX "Slot gain out of range ({:f})", gain); Gain = std::clamp(gain, 0.0f, 1.0f); mPropsDirty = true; @@ -1484,7 +1502,7 @@ void ALeffectslot::eax_set_efx_slot_gain(ALfloat gain) void ALeffectslot::EaxDeleter::operator()(ALeffectslot* effect_slot) { - eax_delete_al_effect_slot(*effect_slot->eax_al_context_, *effect_slot); + eax_delete_al_effect_slot(*effect_slot->mEaxALContext, *effect_slot); } EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context) @@ -1494,13 +1512,15 @@ EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context) std::lock_guard slotlock{context.mEffectSlotLock}; auto& device = *context.mALDevice; - if(context.mNumEffectSlots == device.AuxiliaryEffectSlotMax) { - ERR(EAX_PREFIX "%s\n", "Out of memory."); + if(context.mNumEffectSlots == device.AuxiliaryEffectSlotMax) + { + ERR(EAX_PREFIX "Out of memory."); return nullptr; } - if(!EnsureEffectSlots(&context, 1)) { - ERR(EAX_PREFIX "%s\n", "Failed to ensure."); + if(!EnsureEffectSlots(&context, 1)) + { + ERR(EAX_PREFIX "Failed to ensure."); return nullptr; } @@ -1517,7 +1537,7 @@ void eax_delete_al_effect_slot(ALCcontext& context, ALeffectslot& effect_slot) if(effect_slot.ref.load(std::memory_order_relaxed) != 0) { - ERR(EAX_PREFIX "Deleting in-use effect slot %u.\n", effect_slot.id); + ERR(EAX_PREFIX "Deleting in-use effect slot {}.", effect_slot.id); return; } diff --git a/Engine/lib/openal-soft/al/auxeffectslot.h b/Engine/lib/openal-soft/al/auxeffectslot.h index 5f9913a02..9c903cf5a 100644 --- a/Engine/lib/openal-soft/al/auxeffectslot.h +++ b/Engine/lib/openal-soft/al/auxeffectslot.h @@ -1,8 +1,11 @@ #ifndef AL_AUXEFFECTSLOT_H #define AL_AUXEFFECTSLOT_H +#include "config.h" + #include #include +#include #include #include #include @@ -16,7 +19,7 @@ #include "core/effectslot.h" #include "intrusive_ptr.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include #include "eax/api.h" #include "eax/call.h" @@ -28,7 +31,7 @@ struct ALbuffer; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX class EaxFxSlotException : public EaxException { public: explicit EaxFxSlotException(const char* message) @@ -50,7 +53,7 @@ struct ALeffectslot { struct EffectData { EffectSlotType Type{EffectSlotType::None}; - EffectProps Props{}; + EffectProps Props; al::intrusive_ptr State; }; @@ -67,7 +70,7 @@ struct ALeffectslot { /* Self ID */ ALuint id{}; - ALeffectslot(ALCcontext *context); + explicit ALeffectslot(ALCcontext *context); ALeffectslot(const ALeffectslot&) = delete; ALeffectslot& operator=(const ALeffectslot&) = delete; ~ALeffectslot(); @@ -79,13 +82,14 @@ struct ALeffectslot { static void SetName(ALCcontext *context, ALuint id, std::string_view name); -#ifdef ALSOFT_EAX +#if ALSOFT_EAX public: void eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index); - [[nodiscard]] auto eax_get_index() const noexcept -> EaxFxSlotIndexValue { return eax_fx_slot_index_; } - [[nodiscard]] auto eax_get_eax_fx_slot() const noexcept -> const EAX50FXSLOTPROPERTIES& - { return eax_; } + [[nodiscard]] + auto eax_get_index() const noexcept -> EaxFxSlotIndexValue { return mEaxFXSlotIndex; } + [[nodiscard]] + auto eax_get_eax_fx_slot() const noexcept -> const EAX50FXSLOTPROPERTIES& { return mEax; } // Returns `true` if all sources should be updated, or `false` otherwise. [[nodiscard]] auto eax_dispatch(const EaxCall& call) -> bool @@ -94,25 +98,24 @@ public: 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; + enum { + eax_load_effect_dirty_bit, + eax_volume_dirty_bit, + eax_lock_dirty_bit, + eax_flags_dirty_bit, + eax_occlusion_dirty_bit, + eax_occlusion_lf_ratio_dirty_bit, + eax_dirty_bit_count + }; using Exception = EaxFxSlotException; - using Eax4Props = EAX40FXSLOTPROPERTIES; - struct Eax4State { - Eax4Props i; // Immediate. + EAX40FXSLOTPROPERTIES i; // Immediate. }; - using Eax5Props = EAX50FXSLOTPROPERTIES; - struct Eax5State { - Eax5Props i; // Immediate. + EAX50FXSLOTPROPERTIES i; // Immediate. }; struct EaxRangeValidator { @@ -237,15 +240,15 @@ private: } }; - ALCcontext* eax_al_context_{}; - EaxFxSlotIndexValue eax_fx_slot_index_{}; - int eax_version_{}; // Current EAX version. - EaxDirtyFlags eax_df_{}; // Dirty flags for the current EAX version. - EaxEffectUPtr eax_effect_{}; - Eax5State eax123_{}; // EAX1/EAX2/EAX3 state. - Eax4State eax4_{}; // EAX4 state. - Eax5State eax5_{}; // EAX5 state. - Eax5Props eax_{}; // Current EAX state. + ALCcontext* mEaxALContext{}; + EaxFxSlotIndexValue mEaxFXSlotIndex{}; + int mEaxVersion{}; // Current EAX version. + std::bitset mEaxDf; // Dirty flags for the current EAX version. + EaxEffectUPtr mEaxEffect; + Eax5State mEax123{}; // EAX1/EAX2/EAX3 state. + Eax4State mEax4{}; // EAX4 state. + Eax5State mEax5{}; // EAX5 state. + EAX50FXSLOTPROPERTIES mEax{}; // Current EAX state. [[noreturn]] static void eax_fail(const char* message); [[noreturn]] static void eax_fail_unknown_effect_id(); @@ -256,12 +259,14 @@ private: // 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) + template + static void eax_fx_slot_set(const EaxCall& call, TProperties& dst, + std::bitset& dirty_flags) { const auto& src = call.get_value(); TValidator{}(src); - dirty_flags |= (dst != src ? TDirtyBit : EaxDirtyFlags{}); + if(dst != src) + dirty_flags.set(DirtyBit); dst = src; } @@ -269,18 +274,18 @@ private: // validates it, // sets a dirty flag without comparing the values, // and assigns the new value. - template + template static void eax_fx_slot_set_dirty(const EaxCall& call, TProperties& dst, - EaxDirtyFlags& dirty_flags) + std::bitset& dirty_flags) { const auto& src = call.get_value(); TValidator{}(src); - dirty_flags |= TDirtyBit; + dirty_flags.set(DirtyBit); dst = src; } [[nodiscard]] constexpr auto eax4_fx_slot_is_legacy() const noexcept -> bool - { return eax_fx_slot_index_ < 2; } + { return mEaxFXSlotIndex < 2; } void eax4_fx_slot_ensure_unlocked() const; @@ -288,15 +293,15 @@ private: [[nodiscard]] auto eax_get_eax_default_effect_guid() const noexcept -> const GUID&; [[nodiscard]] auto eax_get_eax_default_lock() const noexcept -> long; - void eax4_fx_slot_set_defaults(Eax4Props& props) noexcept; - void eax5_fx_slot_set_defaults(Eax5Props& props) noexcept; - void eax4_fx_slot_set_current_defaults(const Eax4Props& props) noexcept; - void eax5_fx_slot_set_current_defaults(const Eax5Props& props) noexcept; + void eax4_fx_slot_set_defaults(EAX40FXSLOTPROPERTIES& props) noexcept; + void eax5_fx_slot_set_defaults(EAX50FXSLOTPROPERTIES& props) noexcept; + void eax4_fx_slot_set_current_defaults(const EAX40FXSLOTPROPERTIES& props) noexcept; + void eax5_fx_slot_set_current_defaults(const EAX50FXSLOTPROPERTIES& props) noexcept; void eax_fx_slot_set_current_defaults(); void eax_fx_slot_set_defaults(); - static void eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props); - static void eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props); + static void eax4_fx_slot_get(const EaxCall& call, const EAX40FXSLOTPROPERTIES& props); + static void eax5_fx_slot_get(const EaxCall& call, const EAX50FXSLOTPROPERTIES& props); void eax_fx_slot_get(const EaxCall& call) const; // Returns `true` if all sources should be updated, or `false` otherwise. bool eax_get(const EaxCall& call); @@ -321,25 +326,25 @@ private: bool eax_set(const EaxCall& call); template< - EaxDirtyFlags TDirtyBit, + size_t DirtyBit, typename TMemberResult, typename TProps, typename TState> - void eax_fx_slot_commit_property(TState& state, EaxDirtyFlags& dst_df, + void eax_fx_slot_commit_property(TState& state, std::bitset& dst_df, TMemberResult TProps::*member) noexcept { auto& src_i = state.i; - auto& dst_i = eax_; + auto& dst_i = mEax; - if((eax_df_ & TDirtyBit) != EaxDirtyFlags{}) + if(mEaxDf.test(DirtyBit)) { - dst_df |= TDirtyBit; + dst_df.set(DirtyBit); 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 eax4_fx_slot_commit(std::bitset& dst_df); + void eax5_fx_slot_commit(Eax5State& state, std::bitset& dst_df); // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_EFFECT, effect)` void eax_set_efx_slot_effect(EaxEffect &effect); @@ -360,7 +365,7 @@ public: void UpdateAllEffectSlotProps(ALCcontext *context); -#ifdef ALSOFT_EAX +#if ALSOFT_EAX using EaxAlEffectSlotUPtr = std::unique_ptr; EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context); diff --git a/Engine/lib/openal-soft/al/buffer.cpp b/Engine/lib/openal-soft/al/buffer.cpp index 4812e415a..2450c7029 100644 --- a/Engine/lib/openal-soft/al/buffer.cpp +++ b/Engine/lib/openal-soft/al/buffer.cpp @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -53,14 +52,15 @@ #include "alnumeric.h" #include "alspan.h" #include "core/device.h" +#include "core/except.h" +#include "core/logging.h" #include "core/resampler_limits.h" #include "core/voice.h" #include "direct_defs.h" -#include "error.h" #include "intrusive_ptr.h" #include "opthelpers.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include #include "eax/globals.h" @@ -88,7 +88,8 @@ constexpr auto EnumFromAmbiLayout(AmbiLayout layout) -> ALenum case AmbiLayout::FuMa: return AL_FUMA_SOFT; case AmbiLayout::ACN: return AL_ACN_SOFT; } - throw std::runtime_error{"Invalid AmbiLayout: "+std::to_string(int(layout))}; + throw std::runtime_error{fmt::format("Invalid AmbiLayout: {}", + int{al::to_underlying(layout)})}; } constexpr auto AmbiScalingFromEnum(ALenum scale) noexcept -> std::optional @@ -110,10 +111,11 @@ constexpr auto EnumFromAmbiScaling(AmbiScaling scale) -> ALenum case AmbiScaling::N3D: return AL_N3D_SOFT; case AmbiScaling::UHJ: break; } - throw std::runtime_error{"Invalid AmbiScaling: "+std::to_string(int(scale))}; + throw std::runtime_error{fmt::format("Invalid AmbiScaling: {}", + int{al::to_underlying(scale)})}; } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX constexpr auto EaxStorageFromEnum(ALenum scale) noexcept -> std::optional { switch(scale) @@ -132,12 +134,13 @@ constexpr auto EnumFromEaxStorage(EaxStorage storage) -> ALenum case EaxStorage::Accessible: return AL_STORAGE_ACCESSIBLE; case EaxStorage::Hardware: return AL_STORAGE_HARDWARE; } - throw std::runtime_error{"Invalid EaxStorage: "+std::to_string(int(storage))}; + throw std::runtime_error{fmt::format("Invalid EaxStorage: {}", + int{al::to_underlying(storage)})}; } -bool eax_x_ram_check_availability(const ALCdevice &device, const ALbuffer &buffer, - const ALuint newsize) noexcept +auto eax_x_ram_check_availability(const al::Device &device, const ALbuffer &buffer, + const ALuint newsize) noexcept -> bool { ALuint freemem{device.eax_x_ram_free_size}; /* If the buffer is currently in "hardware", add its memory to the free @@ -148,7 +151,7 @@ bool eax_x_ram_check_availability(const ALCdevice &device, const ALbuffer &buffe return freemem >= newsize; } -void eax_x_ram_apply(ALCdevice &device, ALbuffer &buffer) noexcept +void eax_x_ram_apply(al::Device &device, ALbuffer &buffer) noexcept { if(buffer.eax_x_ram_is_hardware) return; @@ -160,7 +163,7 @@ void eax_x_ram_apply(ALCdevice &device, ALbuffer &buffer) noexcept } } -void eax_x_ram_clear(ALCdevice& al_device, ALbuffer& al_buffer) noexcept +void eax_x_ram_clear(al::Device& al_device, ALbuffer& al_buffer) noexcept { if(al_buffer.eax_x_ram_is_hardware) al_device.eax_x_ram_free_size += al_buffer.OriginalSize; @@ -176,7 +179,8 @@ constexpr ALbitfieldSOFT INVALID_MAP_FLAGS{~unsigned(AL_MAP_READ_BIT_SOFT | AL_M AL_MAP_PERSISTENT_BIT_SOFT)}; -auto EnsureBuffers(ALCdevice *device, size_t needed) noexcept -> bool +[[nodiscard]] +auto EnsureBuffers(al::Device *device, size_t needed) noexcept -> bool try { size_t count{std::accumulate(device->BufferList.cbegin(), device->BufferList.cend(), 0_uz, [](size_t cur, const BufferSubList &sublist) noexcept -> size_t @@ -199,7 +203,8 @@ catch(...) { return false; } -ALbuffer *AllocBuffer(ALCdevice *device) noexcept +[[nodiscard]] +auto AllocBuffer(al::Device *device) noexcept -> ALbuffer* { auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(), [](const BufferSubList &entry) noexcept -> bool @@ -218,9 +223,9 @@ ALbuffer *AllocBuffer(ALCdevice *device) noexcept return buffer; } -void FreeBuffer(ALCdevice *device, ALbuffer *buffer) +void FreeBuffer(al::Device *device, ALbuffer *buffer) { -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eax_x_ram_clear(*device, *buffer); #endif // ALSOFT_EAX @@ -235,7 +240,8 @@ void FreeBuffer(ALCdevice *device, ALbuffer *buffer) device->BufferList[lidx].FreeMask |= 1_u64 << slidx; } -auto LookupBuffer(ALCdevice *device, ALuint id) noexcept -> ALbuffer* +[[nodiscard]] +auto LookupBuffer(al::Device *device, ALuint id) noexcept -> ALbuffer* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -248,7 +254,7 @@ auto LookupBuffer(ALCdevice *device, ALuint id) noexcept -> ALbuffer* return al::to_address(sublist.Buffers->begin() + slidx); } - +[[nodiscard]] constexpr auto SanitizeAlignment(FmtType type, ALuint align) noexcept -> ALuint { if(align == 0) @@ -269,47 +275,58 @@ constexpr auto SanitizeAlignment(FmtType type, ALuint align) noexcept -> ALuint if(type == FmtIMA4) { /* IMA4 block alignment must be a multiple of 8, plus 1. */ - if((align&7) == 1) return static_cast(align); + if((align&7) == 1) return align; return 0; } if(type == FmtMSADPCM) { /* MSADPCM block alignment must be a multiple of 2. */ - if((align&1) == 0) return static_cast(align); + if((align&1) == 0) return align; return 0; } - return static_cast(align); + return align; } /** Loads the specified data into the buffer, using the specified format. */ -void LoadData(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei freq, ALuint size, - const FmtChannels DstChannels, const FmtType DstType, const std::byte *SrcData, +void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, + const FmtChannels DstChannels, const FmtType DstType, const al::span SrcData, ALbitfieldSOFT access) { if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) - throw al::context_error{AL_INVALID_OPERATION, "Modifying storage for in-use buffer %u", - ALBuf->id}; + context->throw_error(AL_INVALID_OPERATION, "Modifying storage for in-use buffer {}", + ALBuf->id); const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; if(align < 1) - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", - unpackalign, NameFromFormat(DstType)}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {} for {} samples", + unpackalign, NameFromFormat(DstType)); const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; + if(ambiorder > 3) + { + if(ALBuf->mAmbiLayout == AmbiLayout::FuMa) + context->throw_error(AL_INVALID_OPERATION, + "Cannot load {}{} order B-Format data with FuMa layout", ALBuf->mAmbiOrder, + GetCounterSuffix(ALBuf->mAmbiOrder)); + if(ALBuf->mAmbiScaling == AmbiScaling::FuMa) + context->throw_error(AL_INVALID_OPERATION, + "Cannot load {}{} order B-Format data with FuMa scaling", ALBuf->mAmbiOrder, + GetCounterSuffix(ALBuf->mAmbiOrder)); + } if((access&AL_PRESERVE_DATA_BIT_SOFT)) { /* Can only preserve data with the same format and alignment. */ if(ALBuf->mChannels != DstChannels || ALBuf->mType != DstType) - throw al::context_error{AL_INVALID_VALUE, "Preserving data of mismatched format"}; + context->throw_error(AL_INVALID_VALUE, "Preserving data of mismatched format"); if(ALBuf->mBlockAlign != align) - throw al::context_error{AL_INVALID_VALUE, "Preserving data of mismatched alignment"}; + context->throw_error(AL_INVALID_VALUE, "Preserving data of mismatched alignment"); if(ALBuf->mAmbiOrder != ambiorder) - throw al::context_error{AL_INVALID_VALUE, "Preserving data of mismatched order"}; + context->throw_error(AL_INVALID_VALUE, "Preserving data of mismatched order"); } /* Convert the size in bytes to blocks using the unpack block alignment. */ @@ -319,27 +336,27 @@ void LoadData(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei fre (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : (align * BytesFromFmt(DstType)))}; if((size%BlockSize) != 0) - throw al::context_error{AL_INVALID_VALUE, - "Data size %d is not a multiple of frame size %d (%d unpack alignment)", - size, BlockSize, align}; + context->throw_error(AL_INVALID_VALUE, + "Data size {} is not a multiple of frame size {} ({} unpack alignment)", + size, BlockSize, align); const ALuint blocks{size / BlockSize}; if(blocks > std::numeric_limits::max()/align) - throw al::context_error{AL_OUT_OF_MEMORY, - "Buffer size overflow, %d blocks x %d samples per block", blocks, align}; + context->throw_error(AL_OUT_OF_MEMORY, + "Buffer size overflow, {} blocks x {} samples per block", blocks, align); if(blocks > std::numeric_limits::max()/BlockSize) - throw al::context_error{AL_OUT_OF_MEMORY, - "Buffer size overflow, %d frames x %d bytes per frame", blocks, BlockSize}; + context->throw_error(AL_OUT_OF_MEMORY, + "Buffer size overflow, {} frames x {} bytes per frame", blocks, BlockSize); const size_t newsize{static_cast(blocks) * BlockSize}; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware) { - ALCdevice &device = *context->mALDevice; + auto &device = *context->mALDevice; if(!eax_x_ram_check_availability(device, *ALBuf, size)) - throw al::context_error{AL_OUT_OF_MEMORY, - "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, size}; + context->throw_error(AL_OUT_OF_MEMORY, "Out of X-RAM memory (avail: {}, needed: {})", + device.eax_x_ram_free_size, size); } #endif @@ -360,12 +377,12 @@ void LoadData(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei fre newdata.swap(ALBuf->mDataStorage); } ALBuf->mData = ALBuf->mDataStorage; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); #endif - if(SrcData != nullptr && !ALBuf->mData.empty()) - std::copy_n(SrcData, blocks*BlockSize, ALBuf->mData.begin()); + if(!SrcData.empty() && !ALBuf->mData.empty()) + std::copy_n(SrcData.begin(), blocks*BlockSize, ALBuf->mData.begin()); ALBuf->mBlockAlign = (DstType == FmtIMA4 || DstType == FmtMSADPCM) ? align : 1; ALBuf->OriginalSize = size; @@ -384,20 +401,20 @@ void LoadData(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei fre ALBuf->mLoopStart = 0; ALBuf->mLoopEnd = ALBuf->mSampleLen; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(eax_g_is_enabled && ALBuf->eax_x_ram_mode == EaxStorage::Hardware) eax_x_ram_apply(*context->mALDevice, *ALBuf); #endif } /** Prepares the buffer to use the specified callback, using the specified format. */ -void PrepareCallback(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei freq, +void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, const FmtChannels DstChannels, const FmtType DstType, ALBUFFERCALLBACKTYPESOFT callback, void *userptr) { if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) - throw al::context_error{AL_INVALID_OPERATION, "Modifying callback for in-use buffer %u", - ALBuf->id}; + context->throw_error(AL_INVALID_OPERATION, "Modifying callback for in-use buffer {}", + ALBuf->id); const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; @@ -405,8 +422,8 @@ void PrepareCallback(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsi const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; if(align < 1) - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", - unpackalign, NameFromFormat(DstType)}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {} for {} samples", + unpackalign, NameFromFormat(DstType)); const ALuint BlockSize{ChannelsFromFmt(DstChannels, ambiorder) * ((DstType == FmtIMA4) ? (align-1)/2 + 4 : @@ -426,7 +443,7 @@ void PrepareCallback(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsi BufferVectorType(line_blocks*BlockSize).swap(ALBuf->mDataStorage); ALBuf->mData = ALBuf->mDataStorage; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); #endif @@ -452,14 +469,14 @@ void PrepareUserPtr(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsiz const FmtChannels DstChannels, const FmtType DstType, std::byte *sdata, const ALuint sdatalen) { if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) - throw al::context_error{AL_INVALID_OPERATION, "Modifying storage for in-use buffer %u", - ALBuf->id}; + context->throw_error(AL_INVALID_OPERATION, "Modifying storage for in-use buffer {}", + ALBuf->id); const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; if(align < 1) - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", - unpackalign, NameFromFormat(DstType)}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {} for {} samples", + unpackalign, NameFromFormat(DstType)); auto get_type_alignment = [](const FmtType type) noexcept -> ALuint { @@ -482,8 +499,8 @@ void PrepareUserPtr(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsiz }; const auto typealign = get_type_alignment(DstType); if((reinterpret_cast(sdata) & (typealign-1)) != 0) - throw al::context_error{AL_INVALID_VALUE, "Pointer %p is misaligned for %s samples (%u)", - static_cast(sdata), NameFromFormat(DstType), typealign}; + context->throw_error(AL_INVALID_VALUE, "Pointer {} is misaligned for {} samples ({})", + static_cast(sdata), NameFromFormat(DstType), typealign); const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; @@ -495,33 +512,32 @@ void PrepareUserPtr(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsiz (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : (align * BytesFromFmt(DstType)))}; if((sdatalen%BlockSize) != 0) - throw al::context_error{AL_INVALID_VALUE, - "Data size %u is not a multiple of frame size %u (%u unpack alignment)", - sdatalen, BlockSize, align}; + context->throw_error(AL_INVALID_VALUE, + "Data size {} is not a multiple of frame size {} ({} unpack alignment)", + sdatalen, BlockSize, align); const ALuint blocks{sdatalen / BlockSize}; if(blocks > std::numeric_limits::max()/align) - throw al::context_error{AL_OUT_OF_MEMORY, - "Buffer size overflow, %d blocks x %d samples per block", blocks, align}; + context->throw_error(AL_OUT_OF_MEMORY, + "Buffer size overflow, {} blocks x {} samples per block", blocks, align); if(blocks > std::numeric_limits::max()/BlockSize) - throw al::context_error{AL_OUT_OF_MEMORY, - "Buffer size overflow, %d frames x %d bytes per frame", blocks, BlockSize}; + context->throw_error(AL_OUT_OF_MEMORY, + "Buffer size overflow, {} frames x {} bytes per frame", blocks, BlockSize); -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware) { - ALCdevice &device = *context->mALDevice; + auto &device = *context->mALDevice; if(!eax_x_ram_check_availability(device, *ALBuf, sdatalen)) - throw al::context_error{AL_OUT_OF_MEMORY, - "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, - sdatalen}; + context->throw_error(AL_OUT_OF_MEMORY, "Out of X-RAM memory (avail: {}, needed: {})", + device.eax_x_ram_free_size, sdatalen); } #endif decltype(ALBuf->mDataStorage){}.swap(ALBuf->mDataStorage); - ALBuf->mData = {static_cast(sdata), sdatalen}; + ALBuf->mData = al::span{sdata, sdatalen}; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); #endif @@ -541,7 +557,7 @@ void PrepareUserPtr(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsiz ALBuf->mLoopStart = 0; ALBuf->mLoopEnd = ALBuf->mSampleLen; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware) eax_x_ram_apply(*context->mALDevice, *ALBuf); #endif @@ -616,11 +632,13 @@ auto DecomposeUserFormat(ALenum format) noexcept -> std::optional FormatMap{AL_FORMAT_BFORMAT2D_8, {FmtBFormat2D, FmtUByte}}, FormatMap{AL_FORMAT_BFORMAT2D_16, {FmtBFormat2D, FmtShort}}, + FormatMap{AL_FORMAT_BFORMAT2D_I32, {FmtBFormat2D, FmtInt} }, FormatMap{AL_FORMAT_BFORMAT2D_FLOAT32, {FmtBFormat2D, FmtFloat}}, FormatMap{AL_FORMAT_BFORMAT2D_MULAW, {FmtBFormat2D, FmtMulaw}}, FormatMap{AL_FORMAT_BFORMAT3D_8, {FmtBFormat3D, FmtUByte}}, FormatMap{AL_FORMAT_BFORMAT3D_16, {FmtBFormat3D, FmtShort}}, + FormatMap{AL_FORMAT_BFORMAT3D_I32, {FmtBFormat3D, FmtInt} }, FormatMap{AL_FORMAT_BFORMAT3D_FLOAT32, {FmtBFormat3D, FmtFloat}}, FormatMap{AL_FORMAT_BFORMAT3D_MULAW, {FmtBFormat3D, FmtMulaw}}, @@ -662,21 +680,23 @@ AL_API DECL_FUNC2(void, alGenBuffers, ALsizei,n, ALuint*,buffers) FORCE_ALIGN void AL_APIENTRY alGenBuffersDirect(ALCcontext *context, ALsizei n, ALuint *buffers) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Generating %d buffers", n}; + context->throw_error(AL_INVALID_VALUE, "Generating {} buffers", n); if(n <= 0) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; const al::span bids{buffers, static_cast(n)}; if(!EnsureBuffers(device, bids.size())) - throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d buffer%s", n, - (n == 1) ? "" : "s"}; + context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} buffer{}", n, + (n==1) ? "" : "s"); std::generate(bids.begin(), bids.end(), [device]{ return AllocBuffer(device)->id; }); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteBuffers, ALsizei,n, const ALuint*,buffers) @@ -684,21 +704,21 @@ FORCE_ALIGN void AL_APIENTRY alDeleteBuffersDirect(ALCcontext *context, ALsizei const ALuint *buffers) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Deleting %d buffers", n}; + context->throw_error(AL_INVALID_VALUE, "Deleting {} buffers", n); if(n <= 0) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; /* First try to find any buffers that are invalid or in-use. */ - auto validate_buffer = [device](const ALuint bid) + auto validate_buffer = [context,device](const ALuint bid) { if(!bid) return; ALbuffer *ALBuf{LookupBuffer(device, bid)}; if(!ALBuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", bid}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", bid); if(ALBuf->ref.load(std::memory_order_relaxed) != 0) - throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use buffer %u", bid}; + context->throw_error(AL_INVALID_OPERATION, "Deleting in-use buffer {}", bid); }; const al::span bids{buffers, static_cast(n)}; @@ -712,15 +732,17 @@ try { }; std::for_each(bids.begin(), bids.end(), delete_buffer); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsBuffer, ALuint,buffer) FORCE_ALIGN ALboolean AL_APIENTRY alIsBufferDirect(ALCcontext *context, ALuint buffer) noexcept { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(!buffer || LookupBuffer(device, buffer)) return AL_TRUE; return AL_FALSE; @@ -741,96 +763,101 @@ AL_API DECL_FUNCEXT6(void, alBufferStorage,SOFT, ALuint,buffer, ALenum,format, c FORCE_ALIGN void AL_APIENTRY alBufferStorageDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(size < 0) - throw al::context_error{AL_INVALID_VALUE, "Negative storage size %d", size}; + context->throw_error(AL_INVALID_VALUE, "Negative storage size {}", size); if(freq < 1) - throw al::context_error{AL_INVALID_VALUE, "Invalid sample rate %d", freq}; + context->throw_error(AL_INVALID_VALUE, "Invalid sample rate {}", freq); if((flags&INVALID_STORAGE_MASK) != 0) - throw al::context_error{AL_INVALID_VALUE, "Invalid storage flags 0x%x", - flags&INVALID_STORAGE_MASK}; + context->throw_error(AL_INVALID_VALUE, "Invalid storage flags {:#x}", + flags&INVALID_STORAGE_MASK); if((flags&AL_MAP_PERSISTENT_BIT_SOFT) && !(flags&MAP_READ_WRITE_FLAGS)) - throw al::context_error{AL_INVALID_VALUE, - "Declaring persistently mapped storage without read or write access"}; + context->throw_error(AL_INVALID_VALUE, + "Declaring persistently mapped storage without read or write access"); auto usrfmt = DecomposeUserFormat(format); if(!usrfmt) - throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; + context->throw_error(AL_INVALID_ENUM, "Invalid format {:#04x}", as_unsigned(format)); + auto bdata = static_cast(data); LoadData(context, albuf, freq, static_cast(size), usrfmt->channels, usrfmt->type, - static_cast(data), flags); + al::span{bdata, bdata ? static_cast(size) : 0u}, flags); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNC5(void, alBufferDataStatic, ALuint,buffer, ALenum,format, ALvoid*,data, ALsizei,size, ALsizei,freq) FORCE_ALIGN void AL_APIENTRY alBufferDataStaticDirect(ALCcontext *context, const ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(size < 0) - throw al::context_error{AL_INVALID_VALUE, "Negative storage size %d", size}; + context->throw_error(AL_INVALID_VALUE, "Negative storage size {}", size); if(freq < 1) - throw al::context_error{AL_INVALID_VALUE, "Invalid sample rate %d", freq}; + context->throw_error(AL_INVALID_VALUE, "Invalid sample rate {}", freq); auto usrfmt = DecomposeUserFormat(format); if(!usrfmt) - throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; + context->throw_error(AL_INVALID_ENUM, "Invalid format {:#04x}", as_unsigned(format)); PrepareUserPtr(context, albuf, freq, usrfmt->channels, usrfmt->type, static_cast(data), static_cast(size)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT4(void*, alMapBuffer,SOFT, ALuint,buffer, ALsizei,offset, ALsizei,length, ALbitfieldSOFT,access) FORCE_ALIGN void* AL_APIENTRY alMapBufferDirectSOFT(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if((access&INVALID_MAP_FLAGS) != 0) - throw al::context_error{AL_INVALID_VALUE, "Invalid map flags 0x%x", - access&INVALID_MAP_FLAGS}; + context->throw_error(AL_INVALID_VALUE, "Invalid map flags {:#x}", + access&INVALID_MAP_FLAGS); if(!(access&MAP_READ_WRITE_FLAGS)) - throw al::context_error{AL_INVALID_VALUE, "Mapping buffer %u without read or write access", - buffer}; + context->throw_error(AL_INVALID_VALUE, "Mapping buffer {} without read or write access", + buffer); const ALbitfieldSOFT unavailable{(albuf->Access^access) & access}; if(albuf->ref.load(std::memory_order_relaxed) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT)) - throw al::context_error{AL_INVALID_OPERATION, - "Mapping in-use buffer %u without persistent mapping", buffer}; + context->throw_error(AL_INVALID_OPERATION, + "Mapping in-use buffer {} without persistent mapping", buffer); if(albuf->MappedAccess != 0) - throw al::context_error{AL_INVALID_OPERATION, "Mapping already-mapped buffer %u", buffer}; + context->throw_error(AL_INVALID_OPERATION, "Mapping already-mapped buffer {}", buffer); if((unavailable&AL_MAP_READ_BIT_SOFT)) - throw al::context_error{AL_INVALID_VALUE, - "Mapping buffer %u for reading without read access", buffer}; + context->throw_error(AL_INVALID_VALUE, "Mapping buffer {} for reading without read access", + buffer); if((unavailable&AL_MAP_WRITE_BIT_SOFT)) - throw al::context_error{AL_INVALID_VALUE, - "Mapping buffer %u for writing without write access", buffer}; + context->throw_error(AL_INVALID_VALUE, + "Mapping buffer {} for writing without write access", buffer); if((unavailable&AL_MAP_PERSISTENT_BIT_SOFT)) - throw al::context_error{AL_INVALID_VALUE, - "Mapping buffer %u persistently without persistent access", buffer}; + context->throw_error(AL_INVALID_VALUE, + "Mapping buffer {} persistently without persistent access", buffer); if(offset < 0 || length <= 0 || static_cast(offset) >= albuf->OriginalSize || static_cast(length) > albuf->OriginalSize - static_cast(offset)) - throw al::context_error{AL_INVALID_VALUE, "Mapping invalid range %d+%d for buffer %u", - offset, length, buffer}; + context->throw_error(AL_INVALID_VALUE, "Mapping invalid range {}+{} for buffer {}", offset, + length, buffer); void *retval{albuf->mData.data() + offset}; albuf->MappedAccess = access; @@ -838,49 +865,54 @@ try { albuf->MappedSize = length; return retval; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { + return nullptr; +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); return nullptr; } AL_API DECL_FUNCEXT1(void, alUnmapBuffer,SOFT, ALuint,buffer) FORCE_ALIGN void AL_APIENTRY alUnmapBufferDirectSOFT(ALCcontext *context, ALuint buffer) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(albuf->MappedAccess == 0) - throw al::context_error{AL_INVALID_OPERATION, "Unmapping unmapped buffer %u", buffer}; + context->throw_error(AL_INVALID_OPERATION, "Unmapping unmapped buffer {}", buffer); albuf->MappedAccess = 0; albuf->MappedOffset = 0; albuf->MappedSize = 0; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alFlushMappedBuffer,SOFT, ALuint,buffer, ALsizei,offset, ALsizei,length) FORCE_ALIGN void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT)) - throw al::context_error{AL_INVALID_OPERATION, - "Flushing buffer %u while not mapped for writing", buffer}; + context->throw_error(AL_INVALID_OPERATION, + "Flushing buffer {} while not mapped for writing", buffer); if(offset < albuf->MappedOffset || length <= 0 || offset >= albuf->MappedOffset+albuf->MappedSize || length > albuf->MappedOffset+albuf->MappedSize-offset) - throw al::context_error{AL_INVALID_VALUE, "Flushing invalid range %d+%d on buffer %u", - offset, length, buffer}; + context->throw_error(AL_INVALID_VALUE, "Flushing invalid range {}+{} on buffer {}", offset, + length, buffer); /* FIXME: Need to use some method of double-buffering for the mixer and app * to hold separate memory, which can be safely transferred asynchronously. @@ -889,41 +921,41 @@ try { */ std::atomic_thread_fence(std::memory_order_seq_cst); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alBufferSubData,SOFT, ALuint,buffer, ALenum,format, const ALvoid*,data, ALsizei,offset, ALsizei,length) FORCE_ALIGN void AL_APIENTRY alBufferSubDataDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); auto usrfmt = DecomposeUserFormat(format); if(!usrfmt) - throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; + context->throw_error(AL_INVALID_ENUM, "Invalid format {:#04x}", as_unsigned(format)); const ALuint unpack_align{albuf->UnpackAlign}; const ALuint align{SanitizeAlignment(usrfmt->type, unpack_align)}; if(align < 1) - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u", unpack_align}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {}", unpack_align); if(usrfmt->channels != albuf->mChannels || usrfmt->type != albuf->mType) - throw al::context_error{AL_INVALID_ENUM, "Unpacking data with mismatched format"}; + context->throw_error(AL_INVALID_ENUM, "Unpacking data with mismatched format"); if(align != albuf->mBlockAlign) - throw al::context_error{AL_INVALID_VALUE, - "Unpacking data with alignment %u does not match original alignment %u", align, - albuf->mBlockAlign}; + context->throw_error(AL_INVALID_VALUE, + "Unpacking data with alignment {} does not match original alignment {}", align, + albuf->mBlockAlign); if(albuf->isBFormat() && albuf->UnpackAmbiOrder != albuf->mAmbiOrder) - throw al::context_error{AL_INVALID_VALUE, - "Unpacking data with mismatched ambisonic order"}; + context->throw_error(AL_INVALID_VALUE, "Unpacking data with mismatched ambisonic order"); if(albuf->MappedAccess != 0) - throw al::context_error{AL_INVALID_OPERATION, "Unpacking data into mapped buffer %u", - buffer}; + context->throw_error(AL_INVALID_OPERATION, "Unpacking data into mapped buffer {}", buffer); const ALuint num_chans{albuf->channelsFromFmt()}; const ALuint byte_align{ @@ -933,21 +965,23 @@ try { if(offset < 0 || length < 0 || static_cast(offset) > albuf->OriginalSize || static_cast(length) > albuf->OriginalSize-static_cast(offset)) - throw al::context_error{AL_INVALID_VALUE, "Invalid data sub-range %d+%d on buffer %u", - offset, length, buffer}; + context->throw_error(AL_INVALID_VALUE, "Invalid data sub-range {}+{} on buffer {}", offset, + length, buffer); if((static_cast(offset)%byte_align) != 0) - throw al::context_error{AL_INVALID_VALUE, - "Sub-range offset %d is not a multiple of frame size %d (%d unpack alignment)", - offset, byte_align, align}; + context->throw_error(AL_INVALID_VALUE, + "Sub-range offset {} is not a multiple of frame size {} ({} unpack alignment)", + offset, byte_align, align); if((static_cast(length)%byte_align) != 0) - throw al::context_error{AL_INVALID_VALUE, - "Sub-range length %d is not a multiple of frame size %d (%d unpack alignment)", - length, byte_align, align}; + context->throw_error(AL_INVALID_VALUE, + "Sub-range length {} is not a multiple of frame size {} ({} unpack alignment)", + length, byte_align, align); std::memcpy(albuf->mData.data()+offset, data, static_cast(length)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -955,19 +989,19 @@ AL_API DECL_FUNC3(void, alBufferf, ALuint,buffer, ALenum,param, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value [[maybe_unused]]) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alBuffer3f, ALuint,buffer, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) @@ -975,40 +1009,40 @@ FORCE_ALIGN void AL_APIENTRY alBuffer3fDirect(ALCcontext *context, ALuint buffer ALfloat value1 [[maybe_unused]], ALfloat value2 [[maybe_unused]], ALfloat value3 [[maybe_unused]]) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alBufferfv, ALuint,buffer, ALenum,param, const ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, const ALfloat *values) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer float-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -1016,79 +1050,92 @@ AL_API DECL_FUNC3(void, alBufferi, ALuint,buffer, ALenum,param, ALint,value) FORCE_ALIGN void AL_APIENTRY alBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); switch(param) { case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: if(value < 0) - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack block alignment %d", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack block alignment {}", value); albuf->UnpackAlign = static_cast(value); return; case AL_PACK_BLOCK_ALIGNMENT_SOFT: if(value < 0) - throw al::context_error{AL_INVALID_VALUE, "Invalid pack block alignment %d", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid pack block alignment {}", value); albuf->PackAlign = static_cast(value); return; case AL_AMBISONIC_LAYOUT_SOFT: if(albuf->ref.load(std::memory_order_relaxed) != 0) - throw al::context_error{AL_INVALID_OPERATION, - "Modifying in-use buffer %u's ambisonic layout", buffer}; + context->throw_error(AL_INVALID_OPERATION, + "Modifying in-use buffer {}'s ambisonic layout", buffer); if(const auto layout = AmbiLayoutFromEnum(value)) { + if(layout.value() == AmbiLayout::FuMa && albuf->mAmbiOrder > 3) + context->throw_error(AL_INVALID_OPERATION, + "Cannot set FuMa layout for {}{} order B-Format data", albuf->mAmbiOrder, + GetCounterSuffix(albuf->mAmbiOrder)); albuf->mAmbiLayout = layout.value(); return; } - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack ambisonic layout %04x", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack ambisonic layout {:#04x}", + as_unsigned(value)); case AL_AMBISONIC_SCALING_SOFT: if(albuf->ref.load(std::memory_order_relaxed) != 0) - throw al::context_error{AL_INVALID_OPERATION, - "Modifying in-use buffer %u's ambisonic scaling", buffer}; + context->throw_error(AL_INVALID_OPERATION, + "Modifying in-use buffer {}'s ambisonic scaling", buffer); if(const auto scaling = AmbiScalingFromEnum(value)) { + if(scaling.value() == AmbiScaling::FuMa && albuf->mAmbiOrder > 3) + context->throw_error(AL_INVALID_OPERATION, + "Cannot set FuMa scaling for {}{} order B-Format data", albuf->mAmbiOrder, + GetCounterSuffix(albuf->mAmbiOrder)); albuf->mAmbiScaling = scaling.value(); return; } - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack ambisonic scaling %04x", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack ambisonic scaling {:#04x}", + as_unsigned(value)); case AL_UNPACK_AMBISONIC_ORDER_SOFT: if(value < 1 || value > 14) - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack ambisonic order %d", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack ambisonic order {}", value); albuf->UnpackAmbiOrder = static_cast(value); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alBuffer3i, ALuint,buffer, ALenum,param, ALint,value1, ALint,value2, ALint,value3) FORCE_ALIGN void AL_APIENTRY alBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint value1 [[maybe_unused]], ALint value2 [[maybe_unused]], ALint value3 [[maybe_unused]]) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alBufferiv, ALuint,buffer, ALenum,param, const ALint*,values) @@ -1096,7 +1143,7 @@ FORCE_ALIGN void AL_APIENTRY alBufferivDirect(ALCcontext *context, ALuint buffer const ALint *values) noexcept try { if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -1109,34 +1156,36 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); switch(param) { case AL_LOOP_POINTS_SOFT: auto vals = al::span{values, 2_uz}; if(albuf->ref.load(std::memory_order_relaxed) != 0) - throw al::context_error{AL_INVALID_OPERATION, - "Modifying in-use buffer %u's loop points", buffer}; + context->throw_error(AL_INVALID_OPERATION, "Modifying in-use buffer {}'s loop points", + buffer); if(vals[0] < 0 || vals[0] >= vals[1] || static_cast(vals[1]) > albuf->mSampleLen) - throw al::context_error{AL_INVALID_VALUE, - "Invalid loop point range %d -> %d on buffer %u", vals[0], vals[1], buffer}; + context->throw_error(AL_INVALID_VALUE, + "Invalid loop point range {} -> {} on buffer {}", vals[0], vals[1], buffer); albuf->mLoopStart = static_cast(vals[0]); albuf->mLoopEnd = static_cast(vals[1]); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer integer-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -1144,14 +1193,14 @@ AL_API DECL_FUNC3(void, alGetBufferf, ALuint,buffer, ALenum,param, ALfloat*,valu FORCE_ALIGN void AL_APIENTRY alGetBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -1161,31 +1210,34 @@ try { return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alGetBuffer3f, ALuint,buffer, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) FORCE_ALIGN void AL_APIENTRY alGetBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value1 || !value2 || !value3) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetBufferfv, ALuint,buffer, ALenum,param, ALfloat*,values) @@ -1199,21 +1251,21 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer float-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -1221,14 +1273,14 @@ AL_API DECL_FUNC3(void, alGetBufferi, ALuint,buffer, ALenum,param, ALint*,value) FORCE_ALIGN void AL_APIENTRY alGetBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -1279,31 +1331,34 @@ try { return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alGetBuffer3i, ALuint,buffer, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) FORCE_ALIGN void AL_APIENTRY alGetBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value1 || !value2 || !value3) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetBufferiv, ALuint,buffer, ALenum,param, ALint*,values) @@ -1328,14 +1383,14 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -1346,11 +1401,13 @@ try { return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer integer-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -1358,39 +1415,41 @@ AL_API DECL_FUNCEXT5(void, alBufferCallback,SOFT, ALuint,buffer, ALenum,format, FORCE_ALIGN void AL_APIENTRY alBufferCallbackDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(freq < 1) - throw al::context_error{AL_INVALID_VALUE, "Invalid sample rate %d", freq}; + context->throw_error(AL_INVALID_VALUE, "Invalid sample rate {}", freq); if(callback == nullptr) - throw al::context_error{AL_INVALID_VALUE, "NULL callback"}; + context->throw_error(AL_INVALID_VALUE, "NULL callback"); auto usrfmt = DecomposeUserFormat(format); if(!usrfmt) - throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; + context->throw_error(AL_INVALID_ENUM, "Invalid format {:#04x}", as_unsigned(format)); PrepareCallback(context, albuf, freq, usrfmt->channels, usrfmt->type, callback, userptr); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alGetBufferPtr,SOFT, ALuint,buffer, ALenum,param, ALvoid**,value) FORCE_ALIGN void AL_APIENTRY alGetBufferPtrDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -1402,31 +1461,34 @@ try { return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer pointer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer pointer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alGetBuffer3Ptr,SOFT, ALuint,buffer, ALenum,param, ALvoid**,value1, ALvoid**,value2, ALvoid**,value3) FORCE_ALIGN void AL_APIENTRY alGetBuffer3PtrDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value1 || !value2 || !value3) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-pointer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-pointer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alGetBufferPtrv,SOFT, ALuint,buffer, ALenum,param, ALvoid**,values) @@ -1441,22 +1503,21 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer pointer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer pointer-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -1500,12 +1561,12 @@ AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum /*format*/) no void ALbuffer::SetName(ALCcontext *context, ALuint id, std::string_view name) { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; auto buffer = LookupBuffer(device, id); if(!buffer) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", id}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", id); device->mBufferNames.insert_or_assign(id, name); } @@ -1529,25 +1590,25 @@ BufferSubList::~BufferSubList() } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX FORCE_ALIGN DECL_FUNC3(ALboolean, EAXSetBufferMode, ALsizei,n, const ALuint*,buffers, ALint,value) FORCE_ALIGN ALboolean AL_APIENTRY EAXSetBufferModeDirect(ALCcontext *context, ALsizei n, const ALuint *buffers, ALint value) noexcept try { if(!eax_g_is_enabled) - throw al::context_error{AL_INVALID_OPERATION, "EAX not enabled"}; + context->throw_error(AL_INVALID_OPERATION, "EAX not enabled"); const auto storage = EaxStorageFromEnum(value); if(!storage) - throw al::context_error{AL_INVALID_ENUM, "Unsupported X-RAM mode 0x%x", value}; + context->throw_error(AL_INVALID_ENUM, "Unsupported X-RAM mode {:#x}", as_unsigned(value)); if(n == 0) return AL_TRUE; if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Buffer count %d out of range", n}; + context->throw_error(AL_INVALID_VALUE, "Buffer count {} out of range", n); if(!buffers) - throw al::context_error{AL_INVALID_VALUE, "Null AL buffers"}; + context->throw_error(AL_INVALID_VALUE, "Null AL buffers"); auto device = context->mALDevice.get(); std::lock_guard devlock{device->BufferLock}; @@ -1561,7 +1622,7 @@ try { const auto buffer = LookupBuffer(device, bufid); if(!buffer) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", bufid}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", bufid); /* TODO: Is the store location allowed to change for in-use buffers, or * only when not set/queued on a source? @@ -1571,9 +1632,9 @@ try { { if(!buffer->eax_x_ram_is_hardware && buffer->OriginalSize > device->eax_x_ram_free_size) - throw al::context_error{AL_OUT_OF_MEMORY, - "Out of X-RAM memory (need: %u, avail: %u)", buffer->OriginalSize, - device->eax_x_ram_free_size}; + context->throw_error(AL_OUT_OF_MEMORY, + "Out of X-RAM memory (need: {}, avail: {})", buffer->OriginalSize, + device->eax_x_ram_free_size); eax_x_ram_apply(*device, *buffer); } @@ -1592,7 +1653,7 @@ try { const auto buffer = LookupBuffer(device, bufid); if(!buffer) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", bufid}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", bufid); /* TODO: Is the store location allowed to change for in-use buffers, or * only when not set/queued on a source? @@ -1609,15 +1670,15 @@ try { if(!buffer->eax_x_ram_is_hardware) { if(std::numeric_limits::max() - buffer->OriginalSize < total_needed) - throw al::context_error{AL_OUT_OF_MEMORY, "Size overflow (%u + %zu)", - buffer->OriginalSize, total_needed}; + context->throw_error(AL_OUT_OF_MEMORY, "Size overflow ({} + {})", + buffer->OriginalSize, total_needed); total_needed += buffer->OriginalSize; } } if(total_needed > device->eax_x_ram_free_size) - throw al::context_error{AL_OUT_OF_MEMORY, "Out of X-RAM memory (need: %zu, avail: %u)", - total_needed, device->eax_x_ram_free_size}; + context->throw_error(AL_OUT_OF_MEMORY, "Out of X-RAM memory (need: {}, avail: {})", + total_needed, device->eax_x_ram_free_size); } /* Update the mode. */ @@ -1632,8 +1693,11 @@ try { return AL_TRUE; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "[EAXSetBufferMode] %s", e.what()); +catch(al::base_exception&) { + return AL_FALSE; +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); return AL_FALSE; } @@ -1641,24 +1705,26 @@ FORCE_ALIGN DECL_FUNC2(ALenum, EAXGetBufferMode, ALuint,buffer, ALint*,pReserved FORCE_ALIGN ALenum AL_APIENTRY EAXGetBufferModeDirect(ALCcontext *context, ALuint buffer, ALint *pReserved) noexcept try { - if(!eax_g_is_enabled) - throw al::context_error{AL_INVALID_OPERATION, "EAX not enabled."}; + context->throw_error(AL_INVALID_OPERATION, "EAX not enabled."); if(pReserved) - throw al::context_error{AL_INVALID_VALUE, "Non-null reserved parameter"}; + context->throw_error(AL_INVALID_VALUE, "Non-null reserved parameter"); auto device = context->mALDevice.get(); std::lock_guard devlock{device->BufferLock}; const auto al_buffer = LookupBuffer(device, buffer); if(!al_buffer) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); return EnumFromEaxStorage(al_buffer->eax_x_ram_mode); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "[EAXGetBufferMode] %s", e.what()); +catch(al::base_exception&) { + return AL_NONE; +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); return AL_NONE; } diff --git a/Engine/lib/openal-soft/al/buffer.h b/Engine/lib/openal-soft/al/buffer.h index f7661ec88..187bc6bd1 100644 --- a/Engine/lib/openal-soft/al/buffer.h +++ b/Engine/lib/openal-soft/al/buffer.h @@ -1,6 +1,8 @@ #ifndef AL_BUFFER_H #define AL_BUFFER_H +#include "config.h" + #include #include #include @@ -17,7 +19,7 @@ #include "core/buffer_storage.h" #include "vector.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX enum class EaxStorage : uint8_t { Automatic, Accessible, @@ -54,7 +56,7 @@ struct ALbuffer : public BufferStorage { DISABLE_ALLOC -#ifdef ALSOFT_EAX +#if ALSOFT_EAX EaxStorage eax_x_ram_mode{EaxStorage::Automatic}; bool eax_x_ram_is_hardware{}; #endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/debug.cpp b/Engine/lib/openal-soft/al/debug.cpp index e91dc29c2..bb742e746 100644 --- a/Engine/lib/openal-soft/al/debug.cpp +++ b/Engine/lib/openal-soft/al/debug.cpp @@ -21,18 +21,17 @@ #include "alc/context.h" #include "alc/device.h" -#include "alc/inprogext.h" #include "alnumeric.h" #include "alspan.h" -#include "alstring.h" #include "auxeffectslot.h" #include "buffer.h" +#include "core/except.h" #include "core/logging.h" #include "core/voice.h" #include "direct_defs.h" #include "effect.h" -#include "error.h" #include "filter.h" +#include "fmt/core.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "source.h" @@ -45,6 +44,8 @@ DebugGroup::~DebugGroup() = default; namespace { +using namespace std::string_view_literals; + static_assert(DebugSeverityBase+DebugSeverityCount <= 32, "Too many debug bits"); template @@ -109,7 +110,8 @@ constexpr auto GetDebugSourceEnum(DebugSource source) -> ALenum case DebugSource::Application: return AL_DEBUG_SOURCE_APPLICATION_EXT; case DebugSource::Other: return AL_DEBUG_SOURCE_OTHER_EXT; } - throw std::runtime_error{"Unexpected debug source value "+std::to_string(al::to_underlying(source))}; + throw std::runtime_error{fmt::format("Unexpected debug source value: {}", + int{al::to_underlying(source)})}; } constexpr auto GetDebugTypeEnum(DebugType type) -> ALenum @@ -126,7 +128,8 @@ constexpr auto GetDebugTypeEnum(DebugType type) -> ALenum case DebugType::PopGroup: return AL_DEBUG_TYPE_POP_GROUP_EXT; case DebugType::Other: return AL_DEBUG_TYPE_OTHER_EXT; } - throw std::runtime_error{"Unexpected debug type value "+std::to_string(al::to_underlying(type))}; + throw std::runtime_error{fmt::format("Unexpected debug type value: {}", + int{al::to_underlying(type)})}; } constexpr auto GetDebugSeverityEnum(DebugSeverity severity) -> ALenum @@ -138,50 +141,51 @@ constexpr auto GetDebugSeverityEnum(DebugSeverity severity) -> ALenum case DebugSeverity::Low: return AL_DEBUG_SEVERITY_LOW_EXT; case DebugSeverity::Notification: return AL_DEBUG_SEVERITY_NOTIFICATION_EXT; } - throw std::runtime_error{"Unexpected debug severity value "+std::to_string(al::to_underlying(severity))}; + throw std::runtime_error{fmt::format("Unexpected debug severity value: {}", + int{al::to_underlying(severity)})}; } -constexpr auto GetDebugSourceName(DebugSource source) noexcept -> const char* +constexpr auto GetDebugSourceName(DebugSource source) noexcept -> std::string_view { switch(source) { - case DebugSource::API: return "API"; - case DebugSource::System: return "Audio System"; - case DebugSource::ThirdParty: return "Third Party"; - case DebugSource::Application: return "Application"; - case DebugSource::Other: return "Other"; + case DebugSource::API: return "API"sv; + case DebugSource::System: return "Audio System"sv; + case DebugSource::ThirdParty: return "Third Party"sv; + case DebugSource::Application: return "Application"sv; + case DebugSource::Other: return "Other"sv; } - return ""; + return ""sv; } -constexpr auto GetDebugTypeName(DebugType type) noexcept -> const char* +constexpr auto GetDebugTypeName(DebugType type) noexcept -> std::string_view { switch(type) { - case DebugType::Error: return "Error"; - case DebugType::DeprecatedBehavior: return "Deprecated Behavior"; - case DebugType::UndefinedBehavior: return "Undefined Behavior"; - case DebugType::Portability: return "Portability"; - case DebugType::Performance: return "Performance"; - case DebugType::Marker: return "Marker"; - case DebugType::PushGroup: return "Push Group"; - case DebugType::PopGroup: return "Pop Group"; - case DebugType::Other: return "Other"; + case DebugType::Error: return "Error"sv; + case DebugType::DeprecatedBehavior: return "Deprecated Behavior"sv; + case DebugType::UndefinedBehavior: return "Undefined Behavior"sv; + case DebugType::Portability: return "Portability"sv; + case DebugType::Performance: return "Performance"sv; + case DebugType::Marker: return "Marker"sv; + case DebugType::PushGroup: return "Push Group"sv; + case DebugType::PopGroup: return "Pop Group"sv; + case DebugType::Other: return "Other"sv; } - return ""; + return ""sv; } -constexpr auto GetDebugSeverityName(DebugSeverity severity) noexcept -> const char* +constexpr auto GetDebugSeverityName(DebugSeverity severity) noexcept -> std::string_view { switch(severity) { - case DebugSeverity::High: return "High"; - case DebugSeverity::Medium: return "Medium"; - case DebugSeverity::Low: return "Low"; - case DebugSeverity::Notification: return "Notification"; + case DebugSeverity::High: return "High"sv; + case DebugSeverity::Medium: return "Medium"sv; + case DebugSeverity::Low: return "Low"sv; + case DebugSeverity::Notification: return "Notification"sv; } - return ""; + return ""sv; } } // namespace @@ -195,8 +199,8 @@ void ALCcontext::sendDebugMessage(std::unique_lock &debuglock, Debug if(message.length() >= MaxDebugMessageLength) UNLIKELY { - ERR("Debug message too long (%zu >= %d):\n-> %.*s\n", message.length(), - MaxDebugMessageLength, al::sizei(message), message.data()); + ERR("Debug message too long ({} >= {}):\n-> {}", message.length(), + MaxDebugMessageLength, message); return; } @@ -231,13 +235,13 @@ void ALCcontext::sendDebugMessage(std::unique_lock &debuglock, Debug mDebugLog.emplace_back(source, type, id, severity, message); else UNLIKELY ERR("Debug message log overflow. Lost message:\n" - " Source: %s\n" - " Type: %s\n" - " ID: %u\n" - " Severity: %s\n" - " Message: \"%.*s\"\n", + " Source: {}\n" + " Type: {}\n" + " ID: {}\n" + " Severity: {}\n" + " Message: \"{}\"", GetDebugSourceName(source), GetDebugTypeName(type), id, - GetDebugSeverityName(severity), al::sizei(message), message.data()); + GetDebugSeverityName(severity), message); } } @@ -260,32 +264,36 @@ try { return; if(!message) - throw al::context_error{AL_INVALID_VALUE, "Null message pointer"}; + context->throw_error(AL_INVALID_VALUE, "Null message pointer"); auto msgview = (length < 0) ? std::string_view{message} : std::string_view{message, static_cast(length)}; if(msgview.size() >= MaxDebugMessageLength) - throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%zu >= %d)", - msgview.size(), MaxDebugMessageLength}; + context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", msgview.size(), + MaxDebugMessageLength); auto dsource = GetDebugSource(source); if(!dsource) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}", as_unsigned(source)); if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application) - throw al::context_error{AL_INVALID_ENUM, "Debug source 0x%04x not allowed", source}; + context->throw_error(AL_INVALID_ENUM, "Debug source {:#04x} not allowed", + as_unsigned(source)); auto dtype = GetDebugType(type); if(!dtype) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug type 0x%04x", type}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug type {:#04x}", as_unsigned(type)); auto dseverity = GetDebugSeverity(severity); if(!dseverity) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug severity 0x%04x", severity}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug severity {:#04x}", + as_unsigned(severity)); context->debugMessage(*dsource, *dtype, id, *dseverity, msgview); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -296,20 +304,20 @@ try { if(count > 0) { if(!ids) - throw al::context_error{AL_INVALID_VALUE, "IDs is null with non-0 count"}; + context->throw_error(AL_INVALID_VALUE, "IDs is null with non-0 count"); if(source == AL_DONT_CARE_EXT) - throw al::context_error{AL_INVALID_OPERATION, - "Debug source cannot be AL_DONT_CARE_EXT with IDs"}; + context->throw_error(AL_INVALID_OPERATION, + "Debug source cannot be AL_DONT_CARE_EXT with IDs"); if(type == AL_DONT_CARE_EXT) - throw al::context_error{AL_INVALID_OPERATION, - "Debug type cannot be AL_DONT_CARE_EXT with IDs"}; + context->throw_error(AL_INVALID_OPERATION, + "Debug type cannot be AL_DONT_CARE_EXT with IDs"); if(severity != AL_DONT_CARE_EXT) - throw al::context_error{AL_INVALID_OPERATION, - "Debug severity must be AL_DONT_CARE_EXT with IDs"}; + context->throw_error(AL_INVALID_OPERATION, + "Debug severity must be AL_DONT_CARE_EXT with IDs"); } if(enable != AL_TRUE && enable != AL_FALSE) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug enable %d", enable}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug enable {}", enable); static constexpr size_t ElemCount{DebugSourceCount + DebugTypeCount + DebugSeverityCount}; static constexpr auto Values = make_array_sequence(); @@ -319,7 +327,8 @@ try { { auto dsource = GetDebugSource(source); if(!dsource) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}", + as_unsigned(source)); srcIndices = srcIndices.subspan(al::to_underlying(*dsource), 1); } @@ -328,7 +337,7 @@ try { { auto dtype = GetDebugType(type); if(!dtype) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug type 0x%04x", type}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug type {:#04x}", as_unsigned(type)); typeIndices = typeIndices.subspan(al::to_underlying(*dtype), 1); } @@ -337,7 +346,8 @@ try { { auto dseverity = GetDebugSeverity(severity); if(!dseverity) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug severity 0x%04x", severity}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug severity {:#04x}", + as_unsigned(severity)); svrIndices = svrIndices.subspan(al::to_underlying(*dseverity), 1); } @@ -383,8 +393,10 @@ try { [apply_type](const uint idx){ apply_type(1<setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -396,23 +408,24 @@ try { { size_t newlen{std::strlen(message)}; if(newlen >= MaxDebugMessageLength) - throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%zu >= %d)", newlen, - MaxDebugMessageLength}; + context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", newlen, + MaxDebugMessageLength); length = static_cast(newlen); } else if(length >= MaxDebugMessageLength) - throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%d >= %d)", length, - MaxDebugMessageLength}; + context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", length, + MaxDebugMessageLength); auto dsource = GetDebugSource(source); if(!dsource) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}", as_unsigned(source)); if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application) - throw al::context_error{AL_INVALID_ENUM, "Debug source 0x%04x not allowed", source}; + context->throw_error(AL_INVALID_ENUM, "Debug source {:#04x} not allowed", + as_unsigned(source)); std::unique_lock debuglock{context->mDebugCbLock}; if(context->mDebugGroups.size() >= MaxDebugGroupDepth) - throw al::context_error{AL_STACK_OVERFLOW_EXT, "Pushing too many debug groups"}; + context->throw_error(AL_STACK_OVERFLOW_EXT, "Pushing too many debug groups"); context->mDebugGroups.emplace_back(*dsource, id, std::string_view{message, static_cast(length)}); @@ -426,8 +439,10 @@ try { context->sendDebugMessage(debuglock, newback.mSource, DebugType::PushGroup, newback.mId, DebugSeverity::Notification, newback.mMessage); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT(void, alPopDebugGroup,EXT) @@ -435,8 +450,7 @@ FORCE_ALIGN void AL_APIENTRY alPopDebugGroupDirectEXT(ALCcontext *context) noexc try { std::unique_lock debuglock{context->mDebugCbLock}; if(context->mDebugGroups.size() <= 1) - throw al::context_error{AL_STACK_UNDERFLOW_EXT, - "Attempting to pop the default debug group"}; + context->throw_error(AL_STACK_UNDERFLOW_EXT, "Attempting to pop the default debug group"); DebugGroup &debug = context->mDebugGroups.back(); const auto source = debug.mSource; @@ -448,8 +462,10 @@ try { context->sendDebugMessage(debuglock, source, DebugType::PopGroup, id, DebugSeverity::Notification, message); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -458,66 +474,60 @@ FORCE_ALIGN ALuint AL_APIENTRY alGetDebugMessageLogDirectEXT(ALCcontext *context ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) noexcept try { - if(logBufSize < 0) - throw al::context_error{AL_INVALID_VALUE, "Negative debug log buffer size"}; + if(logBuf && logBufSize < 0) + context->throw_error(AL_INVALID_VALUE, "Negative debug log buffer size"); - auto sourcesOut = al::span{sources, sources ? count : 0u}; - auto typesOut = al::span{types, types ? count : 0u}; - auto idsOut = al::span{ids, ids ? count : 0u}; - auto severitiesOut = al::span{severities, severities ? count : 0u}; - auto lengthsOut = al::span{lengths, lengths ? count : 0u}; - auto logOut = al::span{logBuf, logBuf ? static_cast(logBufSize) : 0u}; + const auto sourcesSpan = al::span{sources, sources ? count : 0u}; + const auto typesSpan = al::span{types, types ? count : 0u}; + const auto idsSpan = al::span{ids, ids ? count : 0u}; + const auto severitiesSpan = al::span{severities, severities ? count : 0u}; + const auto lengthsSpan = al::span{lengths, lengths ? count : 0u}; + const auto logSpan = al::span{logBuf, logBuf ? static_cast(logBufSize) : 0u}; - std::lock_guard debuglock{context->mDebugCbLock}; + auto sourceiter = sourcesSpan.begin(); + auto typeiter = typesSpan.begin(); + auto iditer = idsSpan.begin(); + auto severityiter = severitiesSpan.begin(); + auto lengthiter = lengthsSpan.begin(); + auto logiter = logSpan.begin(); + + auto debuglock = std::lock_guard{context->mDebugCbLock}; for(ALuint i{0};i < count;++i) { if(context->mDebugLog.empty()) return i; auto &entry = context->mDebugLog.front(); - const size_t tocopy{entry.mMessage.size() + 1}; - if(logOut.data() != nullptr) + const auto tocopy = size_t{entry.mMessage.size() + 1}; + if(al::to_address(logiter) != nullptr) { - if(logOut.size() < tocopy) + if(static_cast(std::distance(logiter, logSpan.end())) < tocopy) return i; - auto oiter = std::copy(entry.mMessage.cbegin(), entry.mMessage.cend(), logOut.begin()); - *oiter = '\0'; - logOut = {oiter+1, logOut.end()}; + logiter = std::copy(entry.mMessage.cbegin(), entry.mMessage.cend(), logiter); + *(logiter++) = '\0'; } - if(!sourcesOut.empty()) - { - sourcesOut.front() = GetDebugSourceEnum(entry.mSource); - sourcesOut = sourcesOut.subspan<1>(); - } - if(!typesOut.empty()) - { - typesOut.front() = GetDebugTypeEnum(entry.mType); - typesOut = typesOut.subspan<1>(); - } - if(!idsOut.empty()) - { - idsOut.front() = entry.mId; - idsOut = idsOut.subspan<1>(); - } - if(!severitiesOut.empty()) - { - severitiesOut.front() = GetDebugSeverityEnum(entry.mSeverity); - severitiesOut = severitiesOut.subspan<1>(); - } - if(!lengthsOut.empty()) - { - lengthsOut.front() = static_cast(tocopy); - lengthsOut = lengthsOut.subspan<1>(); - } + if(al::to_address(sourceiter) != nullptr) + *(sourceiter++) = GetDebugSourceEnum(entry.mSource); + if(al::to_address(typeiter) != nullptr) + *(typeiter++) = GetDebugTypeEnum(entry.mType); + if(al::to_address(iditer) != nullptr) + *(iditer++) = entry.mId; + if(al::to_address(severityiter) != nullptr) + *(severityiter++) = GetDebugSeverityEnum(entry.mSeverity); + if(al::to_address(lengthiter) != nullptr) + *(lengthiter++) = static_cast(tocopy); context->mDebugLog.pop_front(); } return count; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { + return 0; +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); return 0; } @@ -526,13 +536,13 @@ FORCE_ALIGN void AL_APIENTRY alObjectLabelDirectEXT(ALCcontext *context, ALenum ALuint name, ALsizei length, const ALchar *label) noexcept try { if(!label && length != 0) - throw al::context_error{AL_INVALID_VALUE, "Null label pointer"}; + context->throw_error(AL_INVALID_VALUE, "Null label pointer"); auto objname = (length < 0) ? std::string_view{label} : std::string_view{label, static_cast(length)}; if(objname.size() >= MaxObjectLabelLength) - throw al::context_error{AL_INVALID_VALUE, "Object label length too long (%zu >= %d)", - objname.size(), MaxObjectLabelLength}; + context->throw_error(AL_INVALID_VALUE, "Object label length too long ({} >= {})", + objname.size(), MaxObjectLabelLength); switch(identifier) { @@ -543,10 +553,13 @@ try { case AL_AUXILIARY_EFFECT_SLOT_EXT: ALeffectslot::SetName(context, name, objname); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid name identifier 0x%04x", identifier}; + context->throw_error(AL_INVALID_ENUM, "Invalid name identifier {:#04x}", + as_unsigned(identifier)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT5(void, alGetObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,bufSize, ALsizei*,length, ALchar*,label) @@ -554,12 +567,12 @@ FORCE_ALIGN void AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALen ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) noexcept try { if(bufSize < 0) - throw al::context_error{AL_INVALID_VALUE, "Negative label bufSize"}; + context->throw_error(AL_INVALID_VALUE, "Negative label bufSize"); if(!label && !length) - throw al::context_error{AL_INVALID_VALUE, "Null length and label"}; + context->throw_error(AL_INVALID_VALUE, "Null length and label"); if(label && bufSize == 0) - throw al::context_error{AL_INVALID_VALUE, "Zero label bufSize"}; + context->throw_error(AL_INVALID_VALUE, "Zero label bufSize"); const auto labelOut = al::span{label, label ? static_cast(bufSize) : 0u}; auto copy_name = [name,length,labelOut](std::unordered_map &names) @@ -589,20 +602,20 @@ try { } else if(identifier == AL_BUFFER) { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; copy_name(device->mBufferNames); } else if(identifier == AL_FILTER_EXT) { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->FilterLock}; copy_name(device->mFilterNames); } else if(identifier == AL_EFFECT_EXT) { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->EffectLock}; copy_name(device->mEffectNames); } else if(identifier == AL_AUXILIARY_EFFECT_SLOT_EXT) @@ -611,8 +624,11 @@ try { copy_name(context->mEffectSlotNames); } else - throw al::context_error{AL_INVALID_ENUM, "Invalid name identifier 0x%04x", identifier}; + context->throw_error(AL_INVALID_ENUM, "Invalid name identifier {:#04x}", + as_unsigned(identifier)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } diff --git a/Engine/lib/openal-soft/al/eax/api.h b/Engine/lib/openal-soft/al/eax/api.h index 3e621d30f..925e7b4c7 100644 --- a/Engine/lib/openal-soft/al/eax/api.h +++ b/Engine/lib/openal-soft/al/eax/api.h @@ -21,6 +21,8 @@ #include "AL/al.h" +#include "opthelpers.h" + #ifndef _WIN32 using GUID = struct _GUID { /* NOLINT(*-reserved-identifier) */ @@ -37,14 +39,16 @@ inline bool operator!=(const GUID& lhs, const GUID& rhs) noexcept { return !(lhs == rhs); } #endif // _WIN32 +/* TODO: This seems to create very inefficient comparisons. C++20 should allow + * creating default comparison operators, avoiding the need for this. + */ #define DECL_EQOP(T, ...) \ [[nodiscard]] auto get_members() const noexcept { return std::forward_as_tuple(__VA_ARGS__); } \ [[nodiscard]] friend bool operator==(const T &lhs, const T &rhs) noexcept \ -{ return lhs.get_members() == rhs.get_members(); } \ -[[nodiscard]] friend bool operator!=(const T &lhs, const T &rhs) noexcept \ -{ return !(lhs == rhs); } +{ return lhs.get_members() == rhs.get_members(); } \ +[[nodiscard]] friend bool operator!=(const T &lhs, const T &rhs) noexcept { return !(lhs == rhs); } -extern const GUID DSPROPSETID_EAX_ReverbProperties; +DECL_HIDDEN extern const GUID DSPROPSETID_EAX_ReverbProperties; enum DSPROPERTY_EAX_REVERBPROPERTY : unsigned int { DSPROPERTY_EAX_ALL, @@ -62,7 +66,7 @@ struct EAX_REVERBPROPERTIES { }; // EAX_REVERBPROPERTIES -extern const GUID DSPROPSETID_EAXBUFFER_ReverbProperties; +DECL_HIDDEN extern const GUID DSPROPSETID_EAXBUFFER_ReverbProperties; enum DSPROPERTY_EAXBUFFER_REVERBPROPERTY : unsigned int { DSPROPERTY_EAXBUFFER_ALL, @@ -78,7 +82,7 @@ constexpr auto EAX_BUFFER_MAXREVERBMIX = 1.0F; constexpr auto EAX_REVERBMIX_USEDISTANCE = -1.0F; -extern const GUID DSPROPSETID_EAX20_ListenerProperties; +DECL_HIDDEN extern const GUID DSPROPSETID_EAX20_ListenerProperties; enum DSPROPERTY_EAX20_LISTENERPROPERTY : unsigned int { DSPROPERTY_EAX20LISTENER_NONE, @@ -216,7 +220,7 @@ constexpr auto EAX2LISTENER_DEFAULTFLAGS = EAX2LISTENERFLAGS_DECAYHFLIMIT; -extern const GUID DSPROPSETID_EAX20_BufferProperties; +DECL_HIDDEN extern const GUID DSPROPSETID_EAX20_BufferProperties; enum DSPROPERTY_EAX20_BUFFERPROPERTY : unsigned int { DSPROPERTY_EAX20BUFFER_NONE, @@ -252,9 +256,9 @@ struct EAX20BUFFERPROPERTIES { unsigned long dwFlags; // modifies the behavior of properties }; // EAX20BUFFERPROPERTIES -extern const GUID DSPROPSETID_EAX30_ListenerProperties; +DECL_HIDDEN extern const GUID DSPROPSETID_EAX30_ListenerProperties; -extern const GUID DSPROPSETID_EAX30_BufferProperties; +DECL_HIDDEN extern const GUID DSPROPSETID_EAX30_BufferProperties; constexpr auto EAX_MAX_FXSLOTS = 4; @@ -272,31 +276,29 @@ constexpr auto EAXERR_INCOMPATIBLE_SOURCE_TYPE = -5L; constexpr auto EAXERR_INCOMPATIBLE_EAX_VERSION = -6L; -extern const GUID EAX_NULL_GUID; +DECL_HIDDEN extern const GUID EAX_NULL_GUID; -extern const GUID EAX_PrimaryFXSlotID; +DECL_HIDDEN extern const GUID EAX_PrimaryFXSlotID; struct EAXVECTOR { float x; float y; float z; - [[nodiscard]] - auto get_members() const noexcept { return std::forward_as_tuple(x, y, z); } -}; // EAXVECTOR +}; [[nodiscard]] inline bool operator==(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept -{ return lhs.get_members() == rhs.get_members(); } +{ return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z; } [[nodiscard]] inline bool operator!=(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept { return !(lhs == rhs); } -extern const GUID EAXPROPERTYID_EAX40_Context; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_Context; -extern const GUID EAXPROPERTYID_EAX50_Context; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_Context; // EAX50 constexpr auto HEADPHONES = 0UL; @@ -372,17 +374,17 @@ constexpr auto EAXCONTEXT_DEFAULTMACROFXFACTOR = 0.0F; constexpr auto EAXCONTEXT_DEFAULTLASTERROR = EAX_OK; -extern const GUID EAXPROPERTYID_EAX40_FXSlot0; -extern const GUID EAXPROPERTYID_EAX50_FXSlot0; -extern const GUID EAXPROPERTYID_EAX40_FXSlot1; -extern const GUID EAXPROPERTYID_EAX50_FXSlot1; -extern const GUID EAXPROPERTYID_EAX40_FXSlot2; -extern const GUID EAXPROPERTYID_EAX50_FXSlot2; -extern const GUID EAXPROPERTYID_EAX40_FXSlot3; -extern const GUID EAXPROPERTYID_EAX50_FXSlot3; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_FXSlot0; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_FXSlot0; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_FXSlot1; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_FXSlot1; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_FXSlot2; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_FXSlot2; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_FXSlot3; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_FXSlot3; -extern const GUID EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID; -extern const GUID EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID; +DECL_HIDDEN extern const GUID EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID; +DECL_HIDDEN extern const GUID EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID; enum EAXFXSLOT_PROPERTY : unsigned int { EAXFXSLOT_PARAMETER = 0, @@ -445,8 +447,8 @@ struct EAX50FXSLOTPROPERTIES : public EAX40FXSLOTPROPERTIES { float flOcclusionLFRatio; }; // EAX50FXSLOTPROPERTIES -extern const GUID EAXPROPERTYID_EAX40_Source; -extern const GUID EAXPROPERTYID_EAX50_Source; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_Source; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_Source; // Source object properties enum EAXSOURCE_PROPERTY : unsigned int { @@ -713,16 +715,16 @@ struct EAXSOURCEEXCLUSIONSENDPROPERTIES { float flExclusionLFRatio; }; // EAXSOURCEEXCLUSIONSENDPROPERTIES -extern const EAX40ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID; +DECL_HIDDEN extern const EAX40ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID; -extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; +DECL_HIDDEN extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; -extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID; +DECL_HIDDEN extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID; // EAX Reverb Effect -extern const GUID EAX_REVERB_EFFECT; +DECL_HIDDEN extern const GUID EAX_REVERB_EFFECT; // Reverb effect properties enum EAXREVERB_PROPERTY : unsigned int { @@ -959,18 +961,18 @@ constexpr auto EAXREVERB_DEFAULTFLAGS = using Eax1ReverbPresets = std::array; -extern const Eax1ReverbPresets EAX1REVERB_PRESETS; +DECL_HIDDEN extern const Eax1ReverbPresets EAX1REVERB_PRESETS; using Eax2ReverbPresets = std::array; -extern const Eax2ReverbPresets EAX2REVERB_PRESETS; +DECL_HIDDEN extern const Eax2ReverbPresets EAX2REVERB_PRESETS; using EaxReverbPresets = std::array; -extern const EaxReverbPresets EAXREVERB_PRESETS; +DECL_HIDDEN extern const EaxReverbPresets EAXREVERB_PRESETS; // AGC Compressor Effect -extern const GUID EAX_AGCCOMPRESSOR_EFFECT; +DECL_HIDDEN extern const GUID EAX_AGCCOMPRESSOR_EFFECT; enum EAXAGCCOMPRESSOR_PROPERTY : unsigned int { EAXAGCCOMPRESSOR_NONE, @@ -991,7 +993,7 @@ constexpr auto EAXAGCCOMPRESSOR_DEFAULTONOFF = EAXAGCCOMPRESSOR_MAXONOFF; // Autowah Effect -extern const GUID EAX_AUTOWAH_EFFECT; +DECL_HIDDEN extern const GUID EAX_AUTOWAH_EFFECT; enum EAXAUTOWAH_PROPERTY : unsigned int { EAXAUTOWAH_NONE, @@ -1030,7 +1032,7 @@ constexpr auto EAXAUTOWAH_DEFAULTPEAKLEVEL = 2100L; // Chorus Effect -extern const GUID EAX_CHORUS_EFFECT; +DECL_HIDDEN extern const GUID EAX_CHORUS_EFFECT; enum EAXCHORUS_PROPERTY : unsigned int { EAXCHORUS_NONE, @@ -1086,7 +1088,7 @@ constexpr auto EAXCHORUS_DEFAULTDELAY = 0.016F; // Distortion Effect -extern const GUID EAX_DISTORTION_EFFECT; +DECL_HIDDEN extern const GUID EAX_DISTORTION_EFFECT; enum EAXDISTORTION_PROPERTY : unsigned int { EAXDISTORTION_NONE, @@ -1131,7 +1133,7 @@ constexpr auto EAXDISTORTION_DEFAULTEQBANDWIDTH = 3600.0F; // Echo Effect -extern const GUID EAX_ECHO_EFFECT; +DECL_HIDDEN extern const GUID EAX_ECHO_EFFECT; enum EAXECHO_PROPERTY : unsigned int { EAXECHO_NONE, @@ -1176,7 +1178,7 @@ constexpr auto EAXECHO_DEFAULTSPREAD = -1.0F; // Equalizer Effect -extern const GUID EAX_EQUALIZER_EFFECT; +DECL_HIDDEN extern const GUID EAX_EQUALIZER_EFFECT; enum EAXEQUALIZER_PROPERTY : unsigned int { EAXEQUALIZER_NONE, @@ -1252,7 +1254,7 @@ constexpr auto EAXEQUALIZER_DEFAULTHIGHCUTOFF = 6000.0F; // Flanger Effect -extern const GUID EAX_FLANGER_EFFECT; +DECL_HIDDEN extern const GUID EAX_FLANGER_EFFECT; enum EAXFLANGER_PROPERTY : unsigned int { EAXFLANGER_NONE, @@ -1308,7 +1310,7 @@ constexpr auto EAXFLANGER_DEFAULTDELAY = 0.002F; // Frequency Shifter Effect -extern const GUID EAX_FREQUENCYSHIFTER_EFFECT; +DECL_HIDDEN extern const GUID EAX_FREQUENCYSHIFTER_EFFECT; enum EAXFREQUENCYSHIFTER_PROPERTY : unsigned int { EAXFREQUENCYSHIFTER_NONE, @@ -1347,7 +1349,7 @@ constexpr auto EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION = EAXFREQUENCYSHIFTER_M // Vocal Morpher Effect -extern const GUID EAX_VOCALMORPHER_EFFECT; +DECL_HIDDEN extern const GUID EAX_VOCALMORPHER_EFFECT; enum EAXVOCALMORPHER_PROPERTY : unsigned int { EAXVOCALMORPHER_NONE, @@ -1439,7 +1441,7 @@ constexpr auto EAXVOCALMORPHER_DEFAULTRATE = 1.41F; // Pitch Shifter Effect -extern const GUID EAX_PITCHSHIFTER_EFFECT; +DECL_HIDDEN extern const GUID EAX_PITCHSHIFTER_EFFECT; enum EAXPITCHSHIFTER_PROPERTY : unsigned int { EAXPITCHSHIFTER_NONE, @@ -1466,7 +1468,7 @@ constexpr auto EAXPITCHSHIFTER_DEFAULTFINETUNE = 0L; // Ring Modulator Effect -extern const GUID EAX_RINGMODULATOR_EFFECT; +DECL_HIDDEN extern const GUID EAX_RINGMODULATOR_EFFECT; enum EAXRINGMODULATOR_PROPERTY : unsigned int { EAXRINGMODULATOR_NONE, diff --git a/Engine/lib/openal-soft/al/eax/call.cpp b/Engine/lib/openal-soft/al/eax/call.cpp index 013a39928..5c69d2e5e 100644 --- a/Engine/lib/openal-soft/al/eax/call.cpp +++ b/Engine/lib/openal-soft/al/eax/call.cpp @@ -15,13 +15,8 @@ public: } // namespace -EaxCall::EaxCall( - EaxCallType type, - const GUID& property_set_guid, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_buffer, - ALuint property_size) +EaxCall::EaxCall(EaxCallType type, const GUID &property_set_guid, ALuint property_id, + ALuint property_source_id, ALvoid *property_buffer, ALuint property_size) : mCallType{type}, mIsDeferred{(property_id & deferred_flag) != 0} , mPropertyId{property_id & ~deferred_flag}, mPropertySourceId{property_source_id} , mPropertyBuffer{property_buffer}, mPropertyBufferSize{property_size} @@ -145,23 +140,34 @@ EaxCall::EaxCall( fail("Unsupported property set id."); } - switch(mPropertyId) + if(mPropertySetId == EaxCallPropertySetId::context) { - case EAXCONTEXT_LASTERROR: - case EAXCONTEXT_SPEAKERCONFIG: - case EAXCONTEXT_EAXSESSION: - case EAXFXSLOT_NONE: - case EAXFXSLOT_ALLPARAMETERS: - case EAXFXSLOT_LOADEFFECT: - case EAXFXSLOT_VOLUME: - case EAXFXSLOT_LOCK: - case EAXFXSLOT_FLAGS: - case EAXFXSLOT_OCCLUSION: - case EAXFXSLOT_OCCLUSIONLFRATIO: - // EAX allow to set "defer" flag on immediate-only properties. - // If we don't clear our flag then "applyAllUpdates" in EAX context won't be called. - mIsDeferred = false; - break; + switch(mPropertyId) + { + case EAXCONTEXT_LASTERROR: + case EAXCONTEXT_SPEAKERCONFIG: + case EAXCONTEXT_EAXSESSION: + // EAX allow to set "defer" flag on immediate-only properties. + // If we don't clear our flag then "applyAllUpdates" in EAX context won't be called. + mIsDeferred = false; + break; + } + } + else if(mPropertySetId == EaxCallPropertySetId::fx_slot) + { + switch(mPropertyId) + { + case EAXFXSLOT_NONE: + case EAXFXSLOT_ALLPARAMETERS: + case EAXFXSLOT_LOADEFFECT: + case EAXFXSLOT_VOLUME: + case EAXFXSLOT_LOCK: + case EAXFXSLOT_FLAGS: + case EAXFXSLOT_OCCLUSION: + case EAXFXSLOT_OCCLUSIONLFRATIO: + mIsDeferred = false; + break; + } } if(!mIsDeferred) diff --git a/Engine/lib/openal-soft/al/eax/effect.h b/Engine/lib/openal-soft/al/eax/effect.h index 9b1f8bd35..eead5b813 100644 --- a/Engine/lib/openal-soft/al/eax/effect.h +++ b/Engine/lib/openal-soft/al/eax/effect.h @@ -1,17 +1,17 @@ #ifndef EAX_EFFECT_INCLUDED #define EAX_EFFECT_INCLUDED - #include #include #include -#include "alnumeric.h" #include "AL/al.h" #include "AL/alext.h" #include "core/effects/base.h" #include "call.h" +inline bool EaxTraceCommits{false}; + struct EaxEffectErrorMessages { static constexpr auto unknown_property_id() noexcept { return "Unknown property id."; } static constexpr auto unknown_version() noexcept { return "Unknown version."; } @@ -123,10 +123,6 @@ template struct EaxCommitter { struct Exception; - EaxCommitter(EaxEffectProps &eaxprops, EffectProps &alprops) - : mEaxProps{eaxprops}, mAlProps{alprops} - { } - EaxEffectProps &mEaxProps; EffectProps &mAlProps; @@ -141,10 +137,18 @@ struct EaxCommitter { [[noreturn]] static void fail(const char *message); [[noreturn]] static void fail_unknown_property_id() { fail(EaxEffectErrorMessages::unknown_property_id()); } + +private: + EaxCommitter(EaxEffectProps &eaxprops, EffectProps &alprops) + : mEaxProps{eaxprops}, mAlProps{alprops} + { } + + friend T; }; struct EaxAutowahCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxAutowahCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXAUTOWAHPROPERTIES &props); @@ -153,7 +157,8 @@ struct EaxAutowahCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props); }; struct EaxChorusCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxChorusCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXCHORUSPROPERTIES &props); @@ -162,7 +167,8 @@ struct EaxChorusCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXCHORUSPROPERTIES &props); }; struct EaxCompressorCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxCompressorCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXAGCCOMPRESSORPROPERTIES &props); @@ -171,7 +177,8 @@ struct EaxCompressorCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props); }; struct EaxDistortionCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxDistortionCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXDISTORTIONPROPERTIES &props); @@ -180,7 +187,8 @@ struct EaxDistortionCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props); }; struct EaxEchoCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxEchoCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXECHOPROPERTIES &props); @@ -189,7 +197,8 @@ struct EaxEchoCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXECHOPROPERTIES &props); }; struct EaxEqualizerCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxEqualizerCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXEQUALIZERPROPERTIES &props); @@ -198,7 +207,8 @@ struct EaxEqualizerCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props); }; struct EaxFlangerCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxFlangerCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXFLANGERPROPERTIES &props); @@ -207,7 +217,8 @@ struct EaxFlangerCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXFLANGERPROPERTIES &props); }; struct EaxFrequencyShifterCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxFrequencyShifterCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXFREQUENCYSHIFTERPROPERTIES &props); @@ -216,7 +227,8 @@ struct EaxFrequencyShifterCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxModulatorCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXRINGMODULATORPROPERTIES &props); @@ -225,7 +237,8 @@ struct EaxModulatorCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props); }; struct EaxPitchShifterCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxPitchShifterCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXPITCHSHIFTERPROPERTIES &props); @@ -234,7 +247,8 @@ struct EaxPitchShifterCommitter : public EaxCommitter static void Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props); }; struct EaxVocalMorpherCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxVocalMorpherCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXVOCALMORPHERPROPERTIES &props); @@ -243,7 +257,8 @@ struct EaxVocalMorpherCommitter : public EaxCommitter static void Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props); }; struct EaxNullCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxNullCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const std::monostate &props); @@ -279,7 +294,7 @@ public: ~EaxEffect() = default; ALenum al_effect_type_{AL_EFFECT_NULL}; - EffectProps al_effect_props_{}; + EffectProps al_effect_props_; using Props1 = EAX_REVERBPROPERTIES; using Props2 = EAX20LISTENERPROPERTIES; @@ -308,7 +323,7 @@ public: int version_{}; bool changed_{}; - Props4 props_{}; + Props4 props_; State1 state1_{}; State2 state2_{}; State3 state3_{}; diff --git a/Engine/lib/openal-soft/al/eax/utils.cpp b/Engine/lib/openal-soft/al/eax/utils.cpp index 871ab764b..2eec42772 100644 --- a/Engine/lib/openal-soft/al/eax/utils.cpp +++ b/Engine/lib/openal-soft/al/eax/utils.cpp @@ -5,7 +5,6 @@ #include #include -#include "alstring.h" #include "core/logging.h" @@ -18,9 +17,9 @@ void eax_log_exception(std::string_view message) noexcept std::rethrow_exception(exception_ptr); } catch(const std::exception& ex) { - ERR("%.*s %s\n", al::sizei(message), message.data(), ex.what()); + ERR("{} {}", message, ex.what()); } catch(...) { - ERR("%.*s %s\n", al::sizei(message), message.data(), "Generic exception."); + ERR("{} {}", message, "Generic exception."); } } diff --git a/Engine/lib/openal-soft/al/eax/utils.h b/Engine/lib/openal-soft/al/eax/utils.h index 8e0f975f1..fb0d87642 100644 --- a/Engine/lib/openal-soft/al/eax/utils.h +++ b/Engine/lib/openal-soft/al/eax/utils.h @@ -1,15 +1,11 @@ #ifndef EAX_UTILS_INCLUDED #define EAX_UTILS_INCLUDED -#include -#include -#include #include -#include +#include "fmt/core.h" #include "opthelpers.h" -using EaxDirtyFlags = unsigned int; struct EaxAlLowPassParam { float gain; @@ -25,71 +21,9 @@ void eax_validate_range(std::string_view value_name, const TValue& value, const if(value >= min_value && value <= max_value) LIKELY return; - const auto message = - std::string{value_name} + - " out of range (value: " + - std::to_string(value) + "; min: " + - std::to_string(min_value) + "; max: " + - std::to_string(max_value) + ")."; - + const auto message = fmt::format("{} out of range (value: {}; min: {}; max: {}).", value_name, + value, min_value, max_value); throw TException{message.c_str()}; } -namespace detail { - -template -struct EaxIsBitFieldStruct { -private: - using yes = std::true_type; - using no = std::false_type; - - template - static auto test(int) -> decltype(std::declval(), yes{}); - - template - static no test(...); - -public: - static constexpr auto value = std::is_same(0)), yes>::value; -}; - -template -inline bool eax_bit_fields_are_equal(const T& lhs, const T& rhs) noexcept -{ - static_assert(sizeof(T) == sizeof(TValue), "Invalid type size."); - return reinterpret_cast(lhs) == reinterpret_cast(rhs); -} - -} // namespace detail - -template< - typename T, - std::enable_if_t::value, int> = 0 -> -inline bool operator==(const T& lhs, const T& rhs) noexcept -{ - using Value = std::conditional_t< - sizeof(T) == 1, - std::uint8_t, - std::conditional_t< - sizeof(T) == 2, - std::uint16_t, - std::conditional_t< - sizeof(T) == 4, - std::uint32_t, - void>>>; - - static_assert(!std::is_same::value, "Unsupported type."); - return detail::eax_bit_fields_are_equal(lhs, rhs); -} - -template< - typename T, - std::enable_if_t::value, int> = 0 -> -inline bool operator!=(const T& lhs, const T& rhs) noexcept -{ - return !(lhs == rhs); -} - #endif // !EAX_UTILS_INCLUDED diff --git a/Engine/lib/openal-soft/al/effect.cpp b/Engine/lib/openal-soft/al/effect.cpp index 6509d755d..0c549d8ab 100644 --- a/Engine/lib/openal-soft/al/effect.cpp +++ b/Engine/lib/openal-soft/al/effect.cpp @@ -51,9 +51,9 @@ #include "alnumeric.h" #include "alspan.h" #include "alstring.h" +#include "core/except.h" #include "core/logging.h" #include "direct_defs.h" -#include "error.h" #include "intrusive_ptr.h" #include "opthelpers.h" @@ -139,7 +139,8 @@ void InitEffectParams(ALeffect *effect, ALenum type) noexcept effect->type = type; } -auto EnsureEffects(ALCdevice *device, size_t needed) noexcept -> bool +[[nodiscard]] +auto EnsureEffects(al::Device *device, size_t needed) noexcept -> bool try { size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), 0_uz, [](size_t cur, const EffectSubList &sublist) noexcept -> size_t @@ -162,7 +163,8 @@ catch(...) { return false; } -ALeffect *AllocEffect(ALCdevice *device) noexcept +[[nodiscard]] +auto AllocEffect(al::Device *device) noexcept -> ALeffect* { auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(), [](const EffectSubList &entry) noexcept -> bool @@ -182,7 +184,7 @@ ALeffect *AllocEffect(ALCdevice *device) noexcept return effect; } -void FreeEffect(ALCdevice *device, ALeffect *effect) +void FreeEffect(al::Device *device, ALeffect *effect) { device->mEffectNames.erase(effect->id); @@ -195,7 +197,8 @@ void FreeEffect(ALCdevice *device, ALeffect *effect) device->EffectList[lidx].FreeMask |= 1_u64 << slidx; } -inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect* +[[nodiscard]] inline +auto LookupEffect(al::Device *device, ALuint id) noexcept -> ALeffect* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -214,21 +217,23 @@ AL_API DECL_FUNC2(void, alGenEffects, ALsizei,n, ALuint*,effects) FORCE_ALIGN void AL_APIENTRY alGenEffectsDirect(ALCcontext *context, ALsizei n, ALuint *effects) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Generating %d effects", n}; + context->throw_error(AL_INVALID_VALUE, "Generating {} effects", n); if(n <= 0) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; const al::span eids{effects, static_cast(n)}; if(!EnsureEffects(device, eids.size())) - throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, - (n == 1) ? "" : "s"}; + context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} effect{}", n, + (n==1) ? "" : "s"); std::generate(eids.begin(), eids.end(), [device]{ return AllocEffect(device)->id; }); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteEffects, ALsizei,n, const ALuint*,effects) @@ -236,11 +241,11 @@ FORCE_ALIGN void AL_APIENTRY alDeleteEffectsDirect(ALCcontext *context, ALsizei const ALuint *effects) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Deleting %d effects", n}; + context->throw_error(AL_INVALID_VALUE, "Deleting {} effects", n); if(n <= 0) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; /* First try to find any effects that are invalid. */ auto validate_effect = [device](const ALuint eid) -> bool @@ -249,7 +254,7 @@ try { const al::span eids{effects, static_cast(n)}; auto inveffect = std::find_if_not(eids.begin(), eids.end(), validate_effect); if(inveffect != eids.end()) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", *inveffect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", *inveffect); /* All good. Delete non-0 effect IDs. */ auto delete_effect = [device](ALuint eid) -> void @@ -259,15 +264,17 @@ try { }; std::for_each(eids.begin(), eids.end(), delete_effect); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsEffect, ALuint,effect) FORCE_ALIGN ALboolean AL_APIENTRY alIsEffectDirect(ALCcontext *context, ALuint effect) noexcept { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; if(!effect || LookupEffect(device, effect)) return AL_TRUE; return AL_FALSE; @@ -277,12 +284,12 @@ AL_API DECL_FUNC3(void, alEffecti, ALuint,effect, ALenum,param, ALint,value) FORCE_ALIGN void AL_APIENTRY alEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, ALint value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); switch(param) { @@ -292,8 +299,8 @@ try { auto check_effect = [value](const EffectList &item) -> bool { return value == item.val && !DisabledEffects.test(item.type); }; if(!std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect)) - throw al::context_error{AL_INVALID_VALUE, "Effect type 0x%04x not supported", - value}; + context->throw_error(AL_INVALID_VALUE, "Effect type {:#04x} not supported", + as_unsigned(value)); } InitEffectParams(aleffect, value); @@ -301,15 +308,17 @@ try { } /* Call the appropriate handler */ - std::visit([aleffect,param,value](auto &arg) + std::visit([context,aleffect,param,value](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; - return arg.SetParami(std::get(aleffect->Props), param, value); + return arg.SetParami(context, std::get(aleffect->Props), param, value); }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alEffectiv, ALuint,effect, ALenum,param, const ALint*,values) @@ -323,81 +332,87 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ - std::visit([aleffect,param,values](auto &arg) + std::visit([context,aleffect,param,values](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; - return arg.SetParamiv(std::get(aleffect->Props), param, values); + return arg.SetParamiv(context, std::get(aleffect->Props), param, values); }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alEffectf, ALuint,effect, ALenum,param, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + if(!aleffect) + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ - std::visit([aleffect,param,value](auto &arg) + std::visit([context,aleffect,param,value](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; - return arg.SetParamf(std::get(aleffect->Props), param, value); + return arg.SetParamf(context, std::get(aleffect->Props), param, value); }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alEffectfv, ALuint,effect, ALenum,param, const ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, const ALfloat *values) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ - std::visit([aleffect,param,values](auto &arg) + std::visit([context,aleffect,param,values](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; - return arg.SetParamfv(std::get(aleffect->Props), param, values); + return arg.SetParamfv(context, std::get(aleffect->Props), param, values); }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetEffecti, ALuint,effect, ALenum,param, ALint*,value) FORCE_ALIGN void AL_APIENTRY alGetEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, ALint *value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); switch(param) { @@ -407,15 +422,17 @@ try { } /* Call the appropriate handler */ - std::visit([aleffect,param,value](auto &arg) + std::visit([context,aleffect,param,value](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; - return arg.GetParami(std::get(aleffect->Props), param, value); + return arg.GetParami(context, std::get(aleffect->Props), param, value); }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetEffectiv, ALuint,effect, ALenum,param, ALint*,values) @@ -429,69 +446,75 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ - std::visit([aleffect,param,values](auto &arg) + std::visit([context,aleffect,param,values](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; - return arg.GetParamiv(std::get(aleffect->Props), param, values); + return arg.GetParamiv(context, std::get(aleffect->Props), param, values); }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetEffectf, ALuint,effect, ALenum,param, ALfloat*,value) FORCE_ALIGN void AL_APIENTRY alGetEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat *value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ - std::visit([aleffect,param,value](auto &arg) + std::visit([context,aleffect,param,value](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; - return arg.GetParamf(std::get(aleffect->Props), param, value); + return arg.GetParamf(context, std::get(aleffect->Props), param, value); }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetEffectfv, ALuint,effect, ALenum,param, ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alGetEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat *values) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ - std::visit([aleffect,param,values](auto &arg) + std::visit([context,aleffect,param,values](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; - return arg.GetParamfv(std::get(aleffect->Props), param, values); + return arg.GetParamfv(context, std::get(aleffect->Props), param, values); }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -502,12 +525,12 @@ void InitEffect(ALeffect *effect) void ALeffect::SetName(ALCcontext* context, ALuint id, std::string_view name) { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; auto effect = LookupEffect(device, id); if(!effect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", id}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", id); device->mEffectNames.insert_or_assign(id, name); } @@ -673,7 +696,7 @@ void LoadReverbPreset(const std::string_view name, ALeffect *effect) if(al::case_compare(name, "NONE"sv) == 0) { InitEffectParams(effect, AL_EFFECT_NULL); - TRACE("Loading reverb '%s'\n", "NONE"); + TRACE("Loading reverb '{}'", "NONE"); return; } @@ -688,7 +711,7 @@ void LoadReverbPreset(const std::string_view name, ALeffect *effect) if(al::case_compare(name, std::data(reverbitem.name)) != 0) continue; - TRACE("Loading reverb '%s'\n", std::data(reverbitem.name)); + TRACE("Loading reverb '{}'", std::data(reverbitem.name)); const auto &props = reverbitem.props; auto &dst = std::get(effect->Props); dst.Density = props.flDensity; @@ -721,7 +744,7 @@ void LoadReverbPreset(const std::string_view name, ALeffect *effect) return; } - WARN("Reverb preset '%.*s' not found\n", al::sizei(name), name.data()); + WARN("Reverb preset '{}' not found", name); } bool IsValidEffectType(ALenum type) noexcept diff --git a/Engine/lib/openal-soft/al/effect.h b/Engine/lib/openal-soft/al/effect.h index 842566578..c7db522ab 100644 --- a/Engine/lib/openal-soft/al/effect.h +++ b/Engine/lib/openal-soft/al/effect.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "AL/al.h" #include "AL/alc.h" @@ -43,7 +44,7 @@ struct EffectList { ALuint type; ALenum val; }; -extern const std::array gEffectList; +DECL_HIDDEN extern const std::array gEffectList; using EffectHandlerVariant = std::variant #include -#include - #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX -#include "alnumeric.h" +#if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -35,75 +33,68 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps AutowahEffectProps{genDefaultProps()}; -void AutowahEffectHandler::SetParami(AutowahProps&, ALenum param, int) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } -void AutowahEffectHandler::SetParamiv(AutowahProps&, ALenum param, const int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", - param}; -} +void AutowahEffectHandler::SetParami(ALCcontext *context, AutowahProps&, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer property {:#04x}", as_unsigned(param)); } +void AutowahEffectHandler::SetParamiv(ALCcontext *context, AutowahProps&, ALenum param, const int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer vector property {:#04x}", as_unsigned(param)); } -void AutowahEffectHandler::SetParamf(AutowahProps &props, ALenum param, float val) +void AutowahEffectHandler::SetParamf(ALCcontext *context, AutowahProps &props, ALenum param, float val) { switch(param) { case AL_AUTOWAH_ATTACK_TIME: if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME)) - throw effect_exception{AL_INVALID_VALUE, "Autowah attack time out of range"}; + context->throw_error(AL_INVALID_VALUE, "Autowah attack time out of range"); props.AttackTime = val; - break; + return; case AL_AUTOWAH_RELEASE_TIME: if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME)) - throw effect_exception{AL_INVALID_VALUE, "Autowah release time out of range"}; + context->throw_error(AL_INVALID_VALUE, "Autowah release time out of range"); props.ReleaseTime = val; - break; + return; case AL_AUTOWAH_RESONANCE: if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE)) - throw effect_exception{AL_INVALID_VALUE, "Autowah resonance out of range"}; + context->throw_error(AL_INVALID_VALUE, "Autowah resonance out of range"); props.Resonance = val; - break; + return; case AL_AUTOWAH_PEAK_GAIN: if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Autowah peak gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Autowah peak gain out of range"); props.PeakGain = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param}; + return; } -} -void AutowahEffectHandler::SetParamfv(AutowahProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void AutowahEffectHandler::GetParami(const AutowahProps&, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } -void AutowahEffectHandler::GetParamiv(const AutowahProps&, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid autowah float property {:#04x}", + as_unsigned(param)); } +void AutowahEffectHandler::SetParamfv(ALCcontext *context, AutowahProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } -void AutowahEffectHandler::GetParamf(const AutowahProps &props, ALenum param, float *val) +void AutowahEffectHandler::GetParami(ALCcontext *context, const AutowahProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer property {:#04x}", as_unsigned(param)); } +void AutowahEffectHandler::GetParamiv(ALCcontext *context, const AutowahProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer vector property {:#04x}", as_unsigned(param)); } + +void AutowahEffectHandler::GetParamf(ALCcontext *context, const AutowahProps &props, ALenum param, float *val) { switch(param) { - case AL_AUTOWAH_ATTACK_TIME: *val = props.AttackTime; break; - case AL_AUTOWAH_RELEASE_TIME: *val = props.ReleaseTime; break; - case AL_AUTOWAH_RESONANCE: *val = props.Resonance; break; - case AL_AUTOWAH_PEAK_GAIN: *val = props.PeakGain; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param}; + case AL_AUTOWAH_ATTACK_TIME: *val = props.AttackTime; return; + case AL_AUTOWAH_RELEASE_TIME: *val = props.ReleaseTime; return; + case AL_AUTOWAH_RESONANCE: *val = props.Resonance; return; + case AL_AUTOWAH_PEAK_GAIN: *val = props.PeakGain; return; } + context->throw_error(AL_INVALID_ENUM, "Invalid autowah float property {:#04x}", + as_unsigned(param)); } -void AutowahEffectHandler::GetParamfv(const AutowahProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void AutowahEffectHandler::GetParamfv(ALCcontext *context, const AutowahProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using AutowahCommitter = EaxCommitter; diff --git a/Engine/lib/openal-soft/al/effects/chorus.cpp b/Engine/lib/openal-soft/al/effects/chorus.cpp index 5a5013ce4..bb9600bf0 100644 --- a/Engine/lib/openal-soft/al/effects/chorus.cpp +++ b/Engine/lib/openal-soft/al/effects/chorus.cpp @@ -7,9 +7,12 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/context.h" +#include "alnumeric.h" +#include "core/logging.h" #include "effects.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include #include "al/eax/effect.h" #include "al/eax/exception.h" @@ -41,7 +44,8 @@ constexpr ALenum EnumFromWaveform(ChorusWaveform type) case ChorusWaveform::Sinusoid: return AL_CHORUS_WAVEFORM_SINUSOID; case ChorusWaveform::Triangle: return AL_CHORUS_WAVEFORM_TRIANGLE; } - throw std::runtime_error{"Invalid chorus waveform: "+std::to_string(static_cast(type))}; + throw std::runtime_error{fmt::format("Invalid chorus waveform: {}", + int{al::to_underlying(type)})}; } constexpr EffectProps genDefaultChorusProps() noexcept @@ -72,7 +76,7 @@ constexpr EffectProps genDefaultFlangerProps() noexcept const EffectProps ChorusEffectProps{genDefaultChorusProps()}; -void ChorusEffectHandler::SetParami(ChorusProps &props, ALenum param, int val) +void ChorusEffectHandler::SetParami(ALCcontext *context, ChorusProps &props, ALenum param, int val) { switch(param) { @@ -80,89 +84,90 @@ void ChorusEffectHandler::SetParami(ChorusProps &props, ALenum param, int val) if(auto formopt = WaveformFromEnum(val)) props.Waveform = *formopt; else - throw effect_exception{AL_INVALID_VALUE, "Invalid chorus waveform: 0x%04x", val}; - break; + context->throw_error(AL_INVALID_VALUE, "Invalid chorus waveform: {:#04x}", + as_unsigned(val)); + return; case AL_CHORUS_PHASE: if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE)) - throw effect_exception{AL_INVALID_VALUE, "Chorus phase out of range: %d", val}; + context->throw_error(AL_INVALID_VALUE, "Chorus phase out of range: {}", val); props.Phase = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; + return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid chorus integer property {:#04x}", + as_unsigned(param)); } -void ChorusEffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void ChorusEffectHandler::SetParamf(ChorusProps &props, ALenum param, float val) +void ChorusEffectHandler::SetParamiv(ALCcontext *context, ChorusProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } +void ChorusEffectHandler::SetParamf(ALCcontext *context, ChorusProps &props, ALenum param, float val) { switch(param) { case AL_CHORUS_RATE: if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE)) - throw effect_exception{AL_INVALID_VALUE, "Chorus rate out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Chorus rate out of range: {:f}", val); props.Rate = val; - break; + return; case AL_CHORUS_DEPTH: if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH)) - throw effect_exception{AL_INVALID_VALUE, "Chorus depth out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Chorus depth out of range: {:f}", val); props.Depth = val; - break; + return; case AL_CHORUS_FEEDBACK: if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK)) - throw effect_exception{AL_INVALID_VALUE, "Chorus feedback out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Chorus feedback out of range: {:f}", val); props.Feedback = val; - break; + return; case AL_CHORUS_DELAY: if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "Chorus delay out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Chorus delay out of range: {:f}", val); props.Delay = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; + return; } -} -void ChorusEffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void ChorusEffectHandler::GetParami(const ChorusProps &props, ALenum param, int *val) + context->throw_error(AL_INVALID_ENUM, "Invalid chorus float property {:#04x}", + as_unsigned(param)); +} +void ChorusEffectHandler::SetParamfv(ALCcontext *context, ChorusProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void ChorusEffectHandler::GetParami(ALCcontext *context, const ChorusProps &props, ALenum param, int *val) { switch(param) { - case AL_CHORUS_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; - case AL_CHORUS_PHASE: *val = props.Phase; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; + case AL_CHORUS_WAVEFORM: *val = EnumFromWaveform(props.Waveform); return; + case AL_CHORUS_PHASE: *val = props.Phase; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid chorus integer property {:#04x}", + as_unsigned(param)); } -void ChorusEffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } -void ChorusEffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val) +void ChorusEffectHandler::GetParamiv(ALCcontext *context, const ChorusProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } +void ChorusEffectHandler::GetParamf(ALCcontext *context, const ChorusProps &props, ALenum param, float *val) { switch(param) { - case AL_CHORUS_RATE: *val = props.Rate; break; - case AL_CHORUS_DEPTH: *val = props.Depth; break; - case AL_CHORUS_FEEDBACK: *val = props.Feedback; break; - case AL_CHORUS_DELAY: *val = props.Delay; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; + case AL_CHORUS_RATE: *val = props.Rate; return; + case AL_CHORUS_DEPTH: *val = props.Depth; return; + case AL_CHORUS_FEEDBACK: *val = props.Feedback; return; + case AL_CHORUS_DELAY: *val = props.Delay; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid chorus float property {:#04x}", + as_unsigned(param)); } -void ChorusEffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void ChorusEffectHandler::GetParamfv(ALCcontext *context, const ChorusProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } const EffectProps FlangerEffectProps{genDefaultFlangerProps()}; -void FlangerEffectHandler::SetParami(ChorusProps &props, ALenum param, int val) +void FlangerEffectHandler::SetParami(ALCcontext *context, ChorusProps &props, ALenum param, int val) { switch(param) { @@ -170,87 +175,88 @@ void FlangerEffectHandler::SetParami(ChorusProps &props, ALenum param, int val) if(auto formopt = WaveformFromEnum(val)) props.Waveform = *formopt; else - throw effect_exception{AL_INVALID_VALUE, "Invalid flanger waveform: 0x%04x", val}; - break; + context->throw_error(AL_INVALID_VALUE, "Invalid flanger waveform: {:#04x}", + as_unsigned(val)); + return; case AL_FLANGER_PHASE: if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE)) - throw effect_exception{AL_INVALID_VALUE, "Flanger phase out of range: %d", val}; + context->throw_error(AL_INVALID_VALUE, "Flanger phase out of range: {}", val); props.Phase = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; + return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid flanger integer property {:#04x}", + as_unsigned(param)); } -void FlangerEffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void FlangerEffectHandler::SetParamf(ChorusProps &props, ALenum param, float val) +void FlangerEffectHandler::SetParamiv(ALCcontext *context, ChorusProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } +void FlangerEffectHandler::SetParamf(ALCcontext *context, ChorusProps &props, ALenum param, float val) { switch(param) { case AL_FLANGER_RATE: if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE)) - throw effect_exception{AL_INVALID_VALUE, "Flanger rate out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Flanger rate out of range: {:f}", val); props.Rate = val; - break; + return; case AL_FLANGER_DEPTH: if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH)) - throw effect_exception{AL_INVALID_VALUE, "Flanger depth out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Flanger depth out of range: {:f}", val); props.Depth = val; - break; + return; case AL_FLANGER_FEEDBACK: if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK)) - throw effect_exception{AL_INVALID_VALUE, "Flanger feedback out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Flanger feedback out of range: {:f}", val); props.Feedback = val; - break; + return; case AL_FLANGER_DELAY: if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "Flanger delay out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Flanger delay out of range: {:f}", val); props.Delay = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; + return; } -} -void FlangerEffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void FlangerEffectHandler::GetParami(const ChorusProps &props, ALenum param, int *val) + context->throw_error(AL_INVALID_ENUM, "Invalid flanger float property {:#04x}", + as_unsigned(param)); +} +void FlangerEffectHandler::SetParamfv(ALCcontext *context, ChorusProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void FlangerEffectHandler::GetParami(ALCcontext *context, const ChorusProps &props, ALenum param, int *val) { switch(param) { - case AL_FLANGER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; - case AL_FLANGER_PHASE: *val = props.Phase; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; + case AL_FLANGER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); return; + case AL_FLANGER_PHASE: *val = props.Phase; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid flanger integer property {:#04x}", + as_unsigned(param)); } -void FlangerEffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } -void FlangerEffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val) +void FlangerEffectHandler::GetParamiv(ALCcontext *context, const ChorusProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } +void FlangerEffectHandler::GetParamf(ALCcontext *context, const ChorusProps &props, ALenum param, float *val) { switch(param) { - case AL_FLANGER_RATE: *val = props.Rate; break; - case AL_FLANGER_DEPTH: *val = props.Depth; break; - case AL_FLANGER_FEEDBACK: *val = props.Feedback; break; - case AL_FLANGER_DELAY: *val = props.Delay; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; + case AL_FLANGER_RATE: *val = props.Rate; return; + case AL_FLANGER_DEPTH: *val = props.Depth; return; + case AL_FLANGER_FEEDBACK: *val = props.Feedback; return; + case AL_FLANGER_DELAY: *val = props.Delay; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid flanger float property {:#04x}", + as_unsigned(param)); } -void FlangerEffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void FlangerEffectHandler::GetParamfv(ALCcontext *context, const ChorusProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { struct EaxChorusTraits { @@ -558,6 +564,17 @@ public: al_props_.Depth = props.flDepth; al_props_.Feedback = props.flFeedback; al_props_.Delay = props.flDelay; + if(EaxTraceCommits) UNLIKELY + { + TRACE("Chorus/flanger commit:\n" + " Waveform: {}\n" + " Phase: {}\n" + " Rate: {:f}\n" + " Depth: {:f}\n" + " Feedback: {:f}\n" + " Delay: {:f}", al::to_underlying(al_props_.Waveform), al_props_.Phase, + al_props_.Rate, al_props_.Depth, al_props_.Feedback, al_props_.Delay); + } return true; } diff --git a/Engine/lib/openal-soft/al/effects/compressor.cpp b/Engine/lib/openal-soft/al/effects/compressor.cpp index 8c9f5b79a..f5c6786af 100644 --- a/Engine/lib/openal-soft/al/effects/compressor.cpp +++ b/Engine/lib/openal-soft/al/effects/compressor.cpp @@ -4,11 +4,11 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX -#include "alnumeric.h" +#if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -28,53 +28,46 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps CompressorEffectProps{genDefaultProps()}; -void CompressorEffectHandler::SetParami(CompressorProps &props, ALenum param, int val) +void CompressorEffectHandler::SetParami(ALCcontext *context, CompressorProps &props, ALenum param, int val) { switch(param) { case AL_COMPRESSOR_ONOFF: if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF)) - throw effect_exception{AL_INVALID_VALUE, "Compressor state out of range"}; + context->throw_error(AL_INVALID_VALUE, "Compressor state out of range"); props.OnOff = (val != AL_FALSE); - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", - param}; + return; } -} -void CompressorEffectHandler::SetParamiv(CompressorProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void CompressorEffectHandler::SetParamf(CompressorProps&, ALenum param, float) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } -void CompressorEffectHandler::SetParamfv(CompressorProps&, ALenum param, const float*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", - param}; -} -void CompressorEffectHandler::GetParami(const CompressorProps &props, ALenum param, int *val) + context->throw_error(AL_INVALID_ENUM, "Invalid compressor integer property {:#04x}", + as_unsigned(param)); +} +void CompressorEffectHandler::SetParamiv(ALCcontext *context, CompressorProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } +void CompressorEffectHandler::SetParamf(ALCcontext *context, CompressorProps&, ALenum param, float) +{ context->throw_error(AL_INVALID_ENUM, "Invalid compressor float property {:#04x}", as_unsigned(param)); } +void CompressorEffectHandler::SetParamfv(ALCcontext *context, CompressorProps&, ALenum param, const float*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid compressor float-vector property {:#04x}", as_unsigned(param)); } + +void CompressorEffectHandler::GetParami(ALCcontext *context, const CompressorProps &props, ALenum param, int *val) { switch(param) { - case AL_COMPRESSOR_ONOFF: *val = props.OnOff; break; - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", - param}; + case AL_COMPRESSOR_ONOFF: *val = props.OnOff; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid compressor integer property {:#04x}", + as_unsigned(param)); } -void CompressorEffectHandler::GetParamiv(const CompressorProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } -void CompressorEffectHandler::GetParamf(const CompressorProps&, ALenum param, float*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } -void CompressorEffectHandler::GetParamfv(const CompressorProps&, ALenum param, float*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", - param}; -} +void CompressorEffectHandler::GetParamiv(ALCcontext *context, const CompressorProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } +void CompressorEffectHandler::GetParamf(ALCcontext *context, const CompressorProps&, ALenum param, float*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid compressor float property {:#04x}", as_unsigned(param)); } +void CompressorEffectHandler::GetParamfv(ALCcontext *context, const CompressorProps&, ALenum param, float*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid compressor float-vector property {:#04x}", as_unsigned(param)); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using CompressorCommitter = EaxCommitter; diff --git a/Engine/lib/openal-soft/al/effects/convolution.cpp b/Engine/lib/openal-soft/al/effects/convolution.cpp index 618d5aac5..84de06c18 100644 --- a/Engine/lib/openal-soft/al/effects/convolution.cpp +++ b/Engine/lib/openal-soft/al/effects/convolution.cpp @@ -7,10 +7,10 @@ #include "AL/al.h" +#include "alc/context.h" #include "alc/inprogext.h" #include "alnumeric.h" #include "alspan.h" -#include "core/effects/base.h" #include "effects.h" @@ -28,90 +28,49 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps ConvolutionEffectProps{genDefaultProps()}; -void ConvolutionEffectHandler::SetParami(ConvolutionProps& /*props*/, ALenum param, int /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x", - param}; - } -} -void ConvolutionEffectHandler::SetParamiv(ConvolutionProps &props, ALenum param, const int *vals) -{ - switch(param) - { - default: - SetParami(props, param, *vals); - } -} -void ConvolutionEffectHandler::SetParamf(ConvolutionProps& /*props*/, ALenum param, float /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect float property 0x%04x", - param}; - } -} -void ConvolutionEffectHandler::SetParamfv(ConvolutionProps &props, ALenum param, const float *values) +void ConvolutionEffectHandler::SetParami(ALCcontext *context, ConvolutionProps& /*props*/, ALenum param, int /*val*/) +{ context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect integer property {:#04x}", as_unsigned(param)); } +void ConvolutionEffectHandler::SetParamiv(ALCcontext *context, ConvolutionProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } + +void ConvolutionEffectHandler::SetParamf(ALCcontext *context, ConvolutionProps& /*props*/, ALenum param, float /*val*/) +{ context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect float property {:#04x}", as_unsigned(param)); } +void ConvolutionEffectHandler::SetParamfv(ALCcontext *context, ConvolutionProps &props, ALenum param, const float *values) { static constexpr auto finite_checker = [](float val) -> bool { return std::isfinite(val); }; - al::span vals; + switch(param) { case AL_CONVOLUTION_ORIENTATION_SOFT: - vals = {values, 6_uz}; + auto vals = al::span{values, 6_uz}; if(!std::all_of(vals.cbegin(), vals.cend(), finite_checker)) - throw effect_exception{AL_INVALID_VALUE, "Property 0x%04x value out of range", param}; + context->throw_error(AL_INVALID_VALUE, "Convolution orientation out of range", param); std::copy_n(vals.cbegin(), props.OrientAt.size(), props.OrientAt.begin()); std::copy_n(vals.cbegin()+3, props.OrientUp.size(), props.OrientUp.begin()); - break; - - default: - SetParamf(props, param, *values); + return; } + + SetParamf(context, props, param, *values); } -void ConvolutionEffectHandler::GetParami(const ConvolutionProps& /*props*/, ALenum param, int* /*val*/) +void ConvolutionEffectHandler::GetParami(ALCcontext *context, const ConvolutionProps& /*props*/, ALenum param, int* /*val*/) +{ context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect integer property {:#04x}", as_unsigned(param)); } +void ConvolutionEffectHandler::GetParamiv(ALCcontext *context, const ConvolutionProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } + +void ConvolutionEffectHandler::GetParamf(ALCcontext *context, const ConvolutionProps& /*props*/, ALenum param, float* /*val*/) +{ context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect float property {:#04x}", as_unsigned(param)); } +void ConvolutionEffectHandler::GetParamfv(ALCcontext *context, const ConvolutionProps &props, ALenum param, float *values) { - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x", - param}; - } -} -void ConvolutionEffectHandler::GetParamiv(const ConvolutionProps &props, ALenum param, int *vals) -{ - switch(param) - { - default: - GetParami(props, param, vals); - } -} -void ConvolutionEffectHandler::GetParamf(const ConvolutionProps& /*props*/, ALenum param, float* /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect float property 0x%04x", - param}; - } -} -void ConvolutionEffectHandler::GetParamfv(const ConvolutionProps &props, ALenum param, float *values) -{ - al::span vals; switch(param) { case AL_CONVOLUTION_ORIENTATION_SOFT: - vals = {values, 6_uz}; + auto vals = al::span{values, 6_uz}; std::copy(props.OrientAt.cbegin(), props.OrientAt.cend(), vals.begin()); std::copy(props.OrientUp.cbegin(), props.OrientUp.cend(), vals.begin()+3); - break; - - default: - GetParamf(props, param, values); + return; } + + GetParamf(context, props, param, values); } diff --git a/Engine/lib/openal-soft/al/effects/dedicated.cpp b/Engine/lib/openal-soft/al/effects/dedicated.cpp index f032a72ec..5776b42be 100644 --- a/Engine/lib/openal-soft/al/effects/dedicated.cpp +++ b/Engine/lib/openal-soft/al/effects/dedicated.cpp @@ -6,7 +6,8 @@ #include "AL/al.h" #include "AL/alext.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" @@ -32,91 +33,81 @@ constexpr EffectProps genDefaultLfeProps() noexcept const EffectProps DedicatedDialogEffectProps{genDefaultDialogProps()}; -void DedicatedDialogEffectHandler::SetParami(DedicatedProps&, ALenum param, int) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } -void DedicatedDialogEffectHandler::SetParamiv(DedicatedProps&, ALenum param, const int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", - param}; -} -void DedicatedDialogEffectHandler::SetParamf(DedicatedProps &props, ALenum param, float val) +void DedicatedDialogEffectHandler::SetParami(ALCcontext *context, DedicatedProps&, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } +void DedicatedDialogEffectHandler::SetParamiv(ALCcontext *context, DedicatedProps&, ALenum param, const int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } +void DedicatedDialogEffectHandler::SetParamf(ALCcontext *context, DedicatedProps &props, ALenum param, float val) { switch(param) { case AL_DEDICATED_GAIN: if(!(val >= 0.0f && std::isfinite(val))) - throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Dedicated gain out of range"); props.Gain = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + return; } -} -void DedicatedDialogEffectHandler::SetParamfv(DedicatedProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void DedicatedDialogEffectHandler::GetParami(const DedicatedProps&, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } -void DedicatedDialogEffectHandler::GetParamiv(const DedicatedProps&, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}", + as_unsigned(param)); } -void DedicatedDialogEffectHandler::GetParamf(const DedicatedProps &props, ALenum param, float *val) +void DedicatedDialogEffectHandler::SetParamfv(ALCcontext *context, DedicatedProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void DedicatedDialogEffectHandler::GetParami(ALCcontext *context, const DedicatedProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } +void DedicatedDialogEffectHandler::GetParamiv(ALCcontext *context, const DedicatedProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } +void DedicatedDialogEffectHandler::GetParamf(ALCcontext *context, const DedicatedProps &props, ALenum param, float *val) { switch(param) { - case AL_DEDICATED_GAIN: *val = props.Gain; break; - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + case AL_DEDICATED_GAIN: *val = props.Gain; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}", + as_unsigned(param)); } -void DedicatedDialogEffectHandler::GetParamfv(const DedicatedProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void DedicatedDialogEffectHandler::GetParamfv(ALCcontext *context, const DedicatedProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } const EffectProps DedicatedLfeEffectProps{genDefaultLfeProps()}; -void DedicatedLfeEffectHandler::SetParami(DedicatedProps&, ALenum param, int) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } -void DedicatedLfeEffectHandler::SetParamiv(DedicatedProps&, ALenum param, const int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", - param}; -} -void DedicatedLfeEffectHandler::SetParamf(DedicatedProps &props, ALenum param, float val) +void DedicatedLfeEffectHandler::SetParami(ALCcontext *context, DedicatedProps&, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } +void DedicatedLfeEffectHandler::SetParamiv(ALCcontext *context, DedicatedProps&, ALenum param, const int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } +void DedicatedLfeEffectHandler::SetParamf(ALCcontext *context, DedicatedProps &props, ALenum param, float val) { switch(param) { case AL_DEDICATED_GAIN: if(!(val >= 0.0f && std::isfinite(val))) - throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Dedicated gain out of range"); props.Gain = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + return; } -} -void DedicatedLfeEffectHandler::SetParamfv(DedicatedProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void DedicatedLfeEffectHandler::GetParami(const DedicatedProps&, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } -void DedicatedLfeEffectHandler::GetParamiv(const DedicatedProps&, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}", + as_unsigned(param)); } -void DedicatedLfeEffectHandler::GetParamf(const DedicatedProps &props, ALenum param, float *val) +void DedicatedLfeEffectHandler::SetParamfv(ALCcontext *context, DedicatedProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void DedicatedLfeEffectHandler::GetParami(ALCcontext *context, const DedicatedProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } +void DedicatedLfeEffectHandler::GetParamiv(ALCcontext *context, const DedicatedProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } +void DedicatedLfeEffectHandler::GetParamf(ALCcontext *context, const DedicatedProps &props, ALenum param, float *val) { switch(param) { - case AL_DEDICATED_GAIN: *val = props.Gain; break; - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + case AL_DEDICATED_GAIN: *val = props.Gain; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}", + as_unsigned(param)); } -void DedicatedLfeEffectHandler::GetParamfv(const DedicatedProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void DedicatedLfeEffectHandler::GetParamfv(ALCcontext *context, const DedicatedProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } diff --git a/Engine/lib/openal-soft/al/effects/distortion.cpp b/Engine/lib/openal-soft/al/effects/distortion.cpp index 7d47fef2d..2109e384b 100644 --- a/Engine/lib/openal-soft/al/effects/distortion.cpp +++ b/Engine/lib/openal-soft/al/effects/distortion.cpp @@ -4,11 +4,11 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX -#include "alnumeric.h" +#if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -32,80 +32,76 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps DistortionEffectProps{genDefaultProps()}; -void DistortionEffectHandler::SetParami(DistortionProps&, ALenum param, int) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } -void DistortionEffectHandler::SetParamiv(DistortionProps&, ALenum param, const int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", - param}; -} -void DistortionEffectHandler::SetParamf(DistortionProps &props, ALenum param, float val) +void DistortionEffectHandler::SetParami(ALCcontext *context, DistortionProps&, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer property {:#04x}", as_unsigned(param)); } +void DistortionEffectHandler::SetParamiv(ALCcontext *context, DistortionProps&, ALenum param, const int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer-vector property {:#04x}", as_unsigned(param)); } + +void DistortionEffectHandler::SetParamf(ALCcontext *context, DistortionProps &props, ALenum param, float val) { switch(param) { case AL_DISTORTION_EDGE: if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE)) - throw effect_exception{AL_INVALID_VALUE, "Distortion edge out of range"}; + context->throw_error(AL_INVALID_VALUE, "Distortion edge out of range"); props.Edge = val; - break; + return; case AL_DISTORTION_GAIN: if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Distortion gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Distortion gain out of range"); props.Gain = val; - break; + return; case AL_DISTORTION_LOWPASS_CUTOFF: if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF)) - throw effect_exception{AL_INVALID_VALUE, "Distortion low-pass cutoff out of range"}; + context->throw_error(AL_INVALID_VALUE, "Distortion low-pass cutoff out of range"); props.LowpassCutoff = val; - break; + return; case AL_DISTORTION_EQCENTER: if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER)) - throw effect_exception{AL_INVALID_VALUE, "Distortion EQ center out of range"}; + context->throw_error(AL_INVALID_VALUE, "Distortion EQ center out of range"); props.EQCenter = val; - break; + return; case AL_DISTORTION_EQBANDWIDTH: if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH)) - throw effect_exception{AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"}; + context->throw_error(AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"); props.EQBandwidth = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; + return; } -} -void DistortionEffectHandler::SetParamfv(DistortionProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void DistortionEffectHandler::GetParami(const DistortionProps&, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } -void DistortionEffectHandler::GetParamiv(const DistortionProps&, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid distortion float property {:#04x}", + as_unsigned(param)); } -void DistortionEffectHandler::GetParamf(const DistortionProps &props, ALenum param, float *val) +void DistortionEffectHandler::SetParamfv(ALCcontext *context, DistortionProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void DistortionEffectHandler::GetParami(ALCcontext *context, const DistortionProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer property {:#04x}", as_unsigned(param)); } +void DistortionEffectHandler::GetParamiv(ALCcontext *context, const DistortionProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer-vector property {:#04x}", as_unsigned(param)); } + +void DistortionEffectHandler::GetParamf(ALCcontext *context, const DistortionProps &props, ALenum param, float *val) { switch(param) { - case AL_DISTORTION_EDGE: *val = props.Edge; break; - case AL_DISTORTION_GAIN: *val = props.Gain; break; - case AL_DISTORTION_LOWPASS_CUTOFF: *val = props.LowpassCutoff; break; - case AL_DISTORTION_EQCENTER: *val = props.EQCenter; break; - case AL_DISTORTION_EQBANDWIDTH: *val = props.EQBandwidth; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; + case AL_DISTORTION_EDGE: *val = props.Edge; return; + case AL_DISTORTION_GAIN: *val = props.Gain; return; + case AL_DISTORTION_LOWPASS_CUTOFF: *val = props.LowpassCutoff; return; + case AL_DISTORTION_EQCENTER: *val = props.EQCenter; return; + case AL_DISTORTION_EQBANDWIDTH: *val = props.EQBandwidth; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid distortion float property {:#04x}", + as_unsigned(param)); } -void DistortionEffectHandler::GetParamfv(const DistortionProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void DistortionEffectHandler::GetParamfv(ALCcontext *context, const DistortionProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using DistortionCommitter = EaxCommitter; diff --git a/Engine/lib/openal-soft/al/effects/echo.cpp b/Engine/lib/openal-soft/al/effects/echo.cpp index 96ed7be27..2a5faf0d0 100644 --- a/Engine/lib/openal-soft/al/effects/echo.cpp +++ b/Engine/lib/openal-soft/al/effects/echo.cpp @@ -4,11 +4,11 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX -#include "alnumeric.h" +#if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -35,74 +35,74 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps EchoEffectProps{genDefaultProps()}; -void EchoEffectHandler::SetParami(EchoProps&, ALenum param, int) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } -void EchoEffectHandler::SetParamiv(EchoProps&, ALenum param, const int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } -void EchoEffectHandler::SetParamf(EchoProps &props, ALenum param, float val) +void EchoEffectHandler::SetParami(ALCcontext *context, EchoProps&, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid echo integer property {:#04x}", as_unsigned(param)); } +void EchoEffectHandler::SetParamiv(ALCcontext *context, EchoProps&, ALenum param, const int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid echo integer-vector property {:#04x}", as_unsigned(param)); } +void EchoEffectHandler::SetParamf(ALCcontext *context, EchoProps &props, ALenum param, float val) { switch(param) { case AL_ECHO_DELAY: if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "Echo delay out of range"}; + context->throw_error(AL_INVALID_VALUE, "Echo delay out of range"); props.Delay = val; - break; + return; case AL_ECHO_LRDELAY: if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY)) - throw effect_exception{AL_INVALID_VALUE, "Echo LR delay out of range"}; + context->throw_error(AL_INVALID_VALUE, "Echo LR delay out of range"); props.LRDelay = val; - break; + return; case AL_ECHO_DAMPING: if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING)) - throw effect_exception{AL_INVALID_VALUE, "Echo damping out of range"}; + context->throw_error(AL_INVALID_VALUE, "Echo damping out of range"); props.Damping = val; - break; + return; case AL_ECHO_FEEDBACK: if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK)) - throw effect_exception{AL_INVALID_VALUE, "Echo feedback out of range"}; + context->throw_error(AL_INVALID_VALUE, "Echo feedback out of range"); props.Feedback = val; - break; + return; case AL_ECHO_SPREAD: if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD)) - throw effect_exception{AL_INVALID_VALUE, "Echo spread out of range"}; + context->throw_error(AL_INVALID_VALUE, "Echo spread out of range"); props.Spread = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param}; + return; } -} -void EchoEffectHandler::SetParamfv(EchoProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void EchoEffectHandler::GetParami(const EchoProps&, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } -void EchoEffectHandler::GetParamiv(const EchoProps&, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } -void EchoEffectHandler::GetParamf(const EchoProps &props, ALenum param, float *val) + context->throw_error(AL_INVALID_ENUM, "Invalid echo float property {:#04x}", + as_unsigned(param)); +} +void EchoEffectHandler::SetParamfv(ALCcontext *context, EchoProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void EchoEffectHandler::GetParami(ALCcontext *context, const EchoProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid echo integer property {:#04x}", as_unsigned(param)); } +void EchoEffectHandler::GetParamiv(ALCcontext *context, const EchoProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid echo integer-vector property {:#04x}", as_unsigned(param)); } +void EchoEffectHandler::GetParamf(ALCcontext *context, const EchoProps &props, ALenum param, float *val) { switch(param) { - case AL_ECHO_DELAY: *val = props.Delay; break; - case AL_ECHO_LRDELAY: *val = props.LRDelay; break; - case AL_ECHO_DAMPING: *val = props.Damping; break; - case AL_ECHO_FEEDBACK: *val = props.Feedback; break; - case AL_ECHO_SPREAD: *val = props.Spread; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param}; + case AL_ECHO_DELAY: *val = props.Delay; return; + case AL_ECHO_LRDELAY: *val = props.LRDelay; return; + case AL_ECHO_DAMPING: *val = props.Damping; return; + case AL_ECHO_FEEDBACK: *val = props.Feedback; return; + case AL_ECHO_SPREAD: *val = props.Spread; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid echo float property {:#04x}", + as_unsigned(param)); } -void EchoEffectHandler::GetParamfv(const EchoProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void EchoEffectHandler::GetParamfv(ALCcontext *context, const EchoProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using EchoCommitter = EaxCommitter; diff --git a/Engine/lib/openal-soft/al/effects/effects.h b/Engine/lib/openal-soft/al/effects/effects.h index 3c4b8f93f..76e5c9105 100644 --- a/Engine/lib/openal-soft/al/effects/effects.h +++ b/Engine/lib/openal-soft/al/effects/effects.h @@ -3,23 +3,24 @@ #include +#include "AL/alc.h" #include "AL/al.h" -#include "al/error.h" #include "core/effects/base.h" +#include "opthelpers.h" #define DECL_HANDLER(N, T) \ struct N { \ using prop_type = T; \ \ - static void SetParami(prop_type &props, ALenum param, int val); \ - static void SetParamiv(prop_type &props, ALenum param, const int *vals); \ - static void SetParamf(prop_type &props, ALenum param, float val); \ - static void SetParamfv(prop_type &props, ALenum param, const float *vals);\ - static void GetParami(const prop_type &props, ALenum param, int *val); \ - static void GetParamiv(const prop_type &props, ALenum param, int *vals); \ - static void GetParamf(const prop_type &props, ALenum param, float *val); \ - static void GetParamfv(const prop_type &props, ALenum param, float *vals);\ + static void SetParami(ALCcontext *context, prop_type &props, ALenum param, int val); \ + static void SetParamiv(ALCcontext *context, prop_type &props, ALenum param, const int *vals); \ + static void SetParamf(ALCcontext *context, prop_type &props, ALenum param, float val); \ + static void SetParamfv(ALCcontext *context, prop_type &props, ALenum param, const float *vals);\ + static void GetParami(ALCcontext *context, const prop_type &props, ALenum param, int *val); \ + static void GetParamiv(ALCcontext *context, const prop_type &props, ALenum param, int *vals); \ + static void GetParamf(ALCcontext *context, const prop_type &props, ALenum param, float *val); \ + static void GetParamfv(ALCcontext *context, const prop_type &props, ALenum param, float *vals);\ }; DECL_HANDLER(NullEffectHandler, std::monostate) DECL_HANDLER(ReverbEffectHandler, ReverbProps) @@ -41,26 +42,23 @@ DECL_HANDLER(ConvolutionEffectHandler, ConvolutionProps) #undef DECL_HANDLER -using effect_exception = al::context_error; - - /* Default properties for the given effect types. */ -extern const EffectProps NullEffectProps; -extern const EffectProps ReverbEffectProps; -extern const EffectProps StdReverbEffectProps; -extern const EffectProps AutowahEffectProps; -extern const EffectProps ChorusEffectProps; -extern const EffectProps CompressorEffectProps; -extern const EffectProps DistortionEffectProps; -extern const EffectProps EchoEffectProps; -extern const EffectProps EqualizerEffectProps; -extern const EffectProps FlangerEffectProps; -extern const EffectProps FshifterEffectProps; -extern const EffectProps ModulatorEffectProps; -extern const EffectProps PshifterEffectProps; -extern const EffectProps VmorpherEffectProps; -extern const EffectProps DedicatedDialogEffectProps; -extern const EffectProps DedicatedLfeEffectProps; -extern const EffectProps ConvolutionEffectProps; +DECL_HIDDEN extern const EffectProps NullEffectProps; +DECL_HIDDEN extern const EffectProps ReverbEffectProps; +DECL_HIDDEN extern const EffectProps StdReverbEffectProps; +DECL_HIDDEN extern const EffectProps AutowahEffectProps; +DECL_HIDDEN extern const EffectProps ChorusEffectProps; +DECL_HIDDEN extern const EffectProps CompressorEffectProps; +DECL_HIDDEN extern const EffectProps DistortionEffectProps; +DECL_HIDDEN extern const EffectProps EchoEffectProps; +DECL_HIDDEN extern const EffectProps EqualizerEffectProps; +DECL_HIDDEN extern const EffectProps FlangerEffectProps; +DECL_HIDDEN extern const EffectProps FshifterEffectProps; +DECL_HIDDEN extern const EffectProps ModulatorEffectProps; +DECL_HIDDEN extern const EffectProps PshifterEffectProps; +DECL_HIDDEN extern const EffectProps VmorpherEffectProps; +DECL_HIDDEN extern const EffectProps DedicatedDialogEffectProps; +DECL_HIDDEN extern const EffectProps DedicatedLfeEffectProps; +DECL_HIDDEN extern const EffectProps ConvolutionEffectProps; #endif /* AL_EFFECTS_EFFECTS_H */ diff --git a/Engine/lib/openal-soft/al/effects/equalizer.cpp b/Engine/lib/openal-soft/al/effects/equalizer.cpp index ba7353078..360fd5d8f 100644 --- a/Engine/lib/openal-soft/al/effects/equalizer.cpp +++ b/Engine/lib/openal-soft/al/effects/equalizer.cpp @@ -4,11 +4,11 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX -#include "alnumeric.h" +#if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -37,115 +37,109 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps EqualizerEffectProps{genDefaultProps()}; -void EqualizerEffectHandler::SetParami(EqualizerProps&, ALenum param, int) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } -void EqualizerEffectHandler::SetParamiv(EqualizerProps&, ALenum param, const int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", - param}; -} -void EqualizerEffectHandler::SetParamf(EqualizerProps &props, ALenum param, float val) +void EqualizerEffectHandler::SetParami(ALCcontext *context, EqualizerProps&, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer property {:#04x}", as_unsigned(param)); } +void EqualizerEffectHandler::SetParamiv(ALCcontext *context, EqualizerProps&, ALenum param, const int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer-vector property {:#04x}", as_unsigned(param)); } +void EqualizerEffectHandler::SetParamf(ALCcontext *context, EqualizerProps &props, ALenum param, float val) { switch(param) { case AL_EQUALIZER_LOW_GAIN: if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer low-band gain out of range"); props.LowGain = val; - break; + return; case AL_EQUALIZER_LOW_CUTOFF: if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band cutoff out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer low-band cutoff out of range"); props.LowCutoff = val; - break; + return; case AL_EQUALIZER_MID1_GAIN: if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer mid1-band gain out of range"); props.Mid1Gain = val; - break; + return; case AL_EQUALIZER_MID1_CENTER: if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band center out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer mid1-band center out of range"); props.Mid1Center = val; - break; + return; case AL_EQUALIZER_MID1_WIDTH: if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band width out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer mid1-band width out of range"); props.Mid1Width = val; - break; + return; case AL_EQUALIZER_MID2_GAIN: if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer mid2-band gain out of range"); props.Mid2Gain = val; - break; + return; case AL_EQUALIZER_MID2_CENTER: if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band center out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer mid2-band center out of range"); props.Mid2Center = val; - break; + return; case AL_EQUALIZER_MID2_WIDTH: if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band width out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer mid2-band width out of range"); props.Mid2Width = val; - break; + return; case AL_EQUALIZER_HIGH_GAIN: if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer high-band gain out of range"); props.HighGain = val; - break; + return; case AL_EQUALIZER_HIGH_CUTOFF: if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band cutoff out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer high-band cutoff out of range"); props.HighCutoff = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; + return; } -} -void EqualizerEffectHandler::SetParamfv(EqualizerProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void EqualizerEffectHandler::GetParami(const EqualizerProps&, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } -void EqualizerEffectHandler::GetParamiv(const EqualizerProps&, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid equalizer float property {:#04x}", + as_unsigned(param)); } -void EqualizerEffectHandler::GetParamf(const EqualizerProps &props, ALenum param, float *val) +void EqualizerEffectHandler::SetParamfv(ALCcontext *context, EqualizerProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void EqualizerEffectHandler::GetParami(ALCcontext *context, const EqualizerProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer property {:#04x}", as_unsigned(param)); } +void EqualizerEffectHandler::GetParamiv(ALCcontext *context, const EqualizerProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer-vector property {:#04x}", as_unsigned(param)); } +void EqualizerEffectHandler::GetParamf(ALCcontext *context, const EqualizerProps &props, ALenum param, float *val) { switch(param) { - case AL_EQUALIZER_LOW_GAIN: *val = props.LowGain; break; - case AL_EQUALIZER_LOW_CUTOFF: *val = props.LowCutoff; break; - case AL_EQUALIZER_MID1_GAIN: *val = props.Mid1Gain; break; - case AL_EQUALIZER_MID1_CENTER: *val = props.Mid1Center; break; - case AL_EQUALIZER_MID1_WIDTH: *val = props.Mid1Width; break; - case AL_EQUALIZER_MID2_GAIN: *val = props.Mid2Gain; break; - case AL_EQUALIZER_MID2_CENTER: *val = props.Mid2Center; break; - case AL_EQUALIZER_MID2_WIDTH: *val = props.Mid2Width; break; - case AL_EQUALIZER_HIGH_GAIN: *val = props.HighGain; break; - case AL_EQUALIZER_HIGH_CUTOFF: *val = props.HighCutoff; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; + case AL_EQUALIZER_LOW_GAIN: *val = props.LowGain; return; + case AL_EQUALIZER_LOW_CUTOFF: *val = props.LowCutoff; return; + case AL_EQUALIZER_MID1_GAIN: *val = props.Mid1Gain; return; + case AL_EQUALIZER_MID1_CENTER: *val = props.Mid1Center; return; + case AL_EQUALIZER_MID1_WIDTH: *val = props.Mid1Width; return; + case AL_EQUALIZER_MID2_GAIN: *val = props.Mid2Gain; return; + case AL_EQUALIZER_MID2_CENTER: *val = props.Mid2Center; return; + case AL_EQUALIZER_MID2_WIDTH: *val = props.Mid2Width; return; + case AL_EQUALIZER_HIGH_GAIN: *val = props.HighGain; return; + case AL_EQUALIZER_HIGH_CUTOFF: *val = props.HighCutoff; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid equalizer float property {:#04x}", + as_unsigned(param)); } -void EqualizerEffectHandler::GetParamfv(const EqualizerProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void EqualizerEffectHandler::GetParamfv(ALCcontext *context, const EqualizerProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using EqualizerCommitter = EaxCommitter; diff --git a/Engine/lib/openal-soft/al/effects/fshifter.cpp b/Engine/lib/openal-soft/al/effects/fshifter.cpp index a23213952..d1c93921f 100644 --- a/Engine/lib/openal-soft/al/effects/fshifter.cpp +++ b/Engine/lib/openal-soft/al/effects/fshifter.cpp @@ -7,12 +7,13 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include -#include "alnumeric.h" + #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -39,7 +40,7 @@ constexpr ALenum EnumFromDirection(FShifterDirection dir) case FShifterDirection::Up: return AL_FREQUENCY_SHIFTER_DIRECTION_UP; case FShifterDirection::Off: return AL_FREQUENCY_SHIFTER_DIRECTION_OFF; } - throw std::runtime_error{"Invalid direction: "+std::to_string(static_cast(dir))}; + throw std::runtime_error{fmt::format("Invalid direction: {}", int{al::to_underlying(dir)})}; } constexpr EffectProps genDefaultProps() noexcept @@ -55,7 +56,7 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps FshifterEffectProps{genDefaultProps()}; -void FshifterEffectHandler::SetParami(FshifterProps &props, ALenum param, int val) +void FshifterEffectHandler::SetParami(ALCcontext *context, FshifterProps &props, ALenum param, int val) { switch(param) { @@ -63,81 +64,75 @@ void FshifterEffectHandler::SetParami(FshifterProps &props, ALenum param, int va if(auto diropt = DirectionFromEmum(val)) props.LeftDirection = *diropt; else - throw effect_exception{AL_INVALID_VALUE, - "Unsupported frequency shifter left direction: 0x%04x", val}; - break; + context->throw_error(AL_INVALID_VALUE, + "Unsupported frequency shifter left direction: {:#04x}", as_unsigned(val)); + return; case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: if(auto diropt = DirectionFromEmum(val)) props.RightDirection = *diropt; else - throw effect_exception{AL_INVALID_VALUE, - "Unsupported frequency shifter right direction: 0x%04x", val}; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, - "Invalid frequency shifter integer property 0x%04x", param}; + context->throw_error(AL_INVALID_VALUE, + "Unsupported frequency shifter right direction: {:#04x}", as_unsigned(val)); + return; } -} -void FshifterEffectHandler::SetParamiv(FshifterProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void FshifterEffectHandler::SetParamf(FshifterProps &props, ALenum param, float val) + context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter integer property {:#04x}", + as_unsigned(param)); +} +void FshifterEffectHandler::SetParamiv(ALCcontext *context, FshifterProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } + +void FshifterEffectHandler::SetParamf(ALCcontext *context, FshifterProps &props, ALenum param, float val) { switch(param) { case AL_FREQUENCY_SHIFTER_FREQUENCY: if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY)) - throw effect_exception{AL_INVALID_VALUE, "Frequency shifter frequency out of range"}; + context->throw_error(AL_INVALID_VALUE, "Frequency shifter frequency out of range"); props.Frequency = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", - param}; + return; } -} -void FshifterEffectHandler::SetParamfv(FshifterProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void FshifterEffectHandler::GetParami(const FshifterProps &props, ALenum param, int *val) + context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter float property {:#04x}", + as_unsigned(param)); +} +void FshifterEffectHandler::SetParamfv(ALCcontext *context, FshifterProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void FshifterEffectHandler::GetParami(ALCcontext *context, const FshifterProps &props, ALenum param, int *val) { switch(param) { case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: *val = EnumFromDirection(props.LeftDirection); - break; + return; case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: *val = EnumFromDirection(props.RightDirection); - break; - - default: - throw effect_exception{AL_INVALID_ENUM, - "Invalid frequency shifter integer property 0x%04x", param}; + return; } -} -void FshifterEffectHandler::GetParamiv(const FshifterProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } -void FshifterEffectHandler::GetParamf(const FshifterProps &props, ALenum param, float *val) + context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter integer property {:#04x}", + as_unsigned(param)); +} +void FshifterEffectHandler::GetParamiv(ALCcontext *context, const FshifterProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } + +void FshifterEffectHandler::GetParamf(ALCcontext *context, const FshifterProps &props, ALenum param, float *val) { switch(param) { - case AL_FREQUENCY_SHIFTER_FREQUENCY: - *val = props.Frequency; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", - param}; + case AL_FREQUENCY_SHIFTER_FREQUENCY: *val = props.Frequency; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter float property {:#04x}", + as_unsigned(param)); } -void FshifterEffectHandler::GetParamfv(const FshifterProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void FshifterEffectHandler::GetParamfv(ALCcontext *context, const FshifterProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using FrequencyShifterCommitter = EaxCommitter; diff --git a/Engine/lib/openal-soft/al/effects/modulator.cpp b/Engine/lib/openal-soft/al/effects/modulator.cpp index 856c85a97..b3ca22138 100644 --- a/Engine/lib/openal-soft/al/effects/modulator.cpp +++ b/Engine/lib/openal-soft/al/effects/modulator.cpp @@ -7,12 +7,13 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include -#include "alnumeric.h" + #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -39,8 +40,8 @@ constexpr ALenum EnumFromWaveform(ModulatorWaveform type) case ModulatorWaveform::Sawtooth: return AL_RING_MODULATOR_SAWTOOTH; case ModulatorWaveform::Square: return AL_RING_MODULATOR_SQUARE; } - throw std::runtime_error{"Invalid modulator waveform: " + - std::to_string(static_cast(type))}; + throw std::runtime_error{fmt::format("Invalid modulator waveform: {}", + int{al::to_underlying(type)})}; } constexpr EffectProps genDefaultProps() noexcept @@ -56,84 +57,84 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps ModulatorEffectProps{genDefaultProps()}; -void ModulatorEffectHandler::SetParami(ModulatorProps &props, ALenum param, int val) +void ModulatorEffectHandler::SetParami(ALCcontext *context, ModulatorProps &props, ALenum param, int val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - SetParamf(props, param, static_cast(val)); - break; + SetParamf(context, props, param, static_cast(val)); + return; case AL_RING_MODULATOR_WAVEFORM: if(auto formopt = WaveformFromEmum(val)) props.Waveform = *formopt; else - throw effect_exception{AL_INVALID_VALUE, "Invalid modulator waveform: 0x%04x", val}; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", - param}; + context->throw_error(AL_INVALID_VALUE, "Invalid modulator waveform: {:#04x}", + as_unsigned(val)); + return; } -} -void ModulatorEffectHandler::SetParamiv(ModulatorProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void ModulatorEffectHandler::SetParamf(ModulatorProps &props, ALenum param, float val) + context->throw_error(AL_INVALID_ENUM, "Invalid modulator integer property {:#04x}", + as_unsigned(param)); +} +void ModulatorEffectHandler::SetParamiv(ALCcontext *context, ModulatorProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } + +void ModulatorEffectHandler::SetParamf(ALCcontext *context, ModulatorProps &props, ALenum param, float val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY)) - throw effect_exception{AL_INVALID_VALUE, "Modulator frequency out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Modulator frequency out of range: {:f}", val); props.Frequency = val; - break; + return; case AL_RING_MODULATOR_HIGHPASS_CUTOFF: if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)) - throw effect_exception{AL_INVALID_VALUE, "Modulator high-pass cutoff out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Modulator high-pass cutoff out of range: {:f}", + val); props.HighPassCutoff = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param}; + return; } -} -void ModulatorEffectHandler::SetParamfv(ModulatorProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void ModulatorEffectHandler::GetParami(const ModulatorProps &props, ALenum param, int *val) + context->throw_error(AL_INVALID_ENUM, "Invalid modulator float property {:#04x}", + as_unsigned(param)); +} +void ModulatorEffectHandler::SetParamfv(ALCcontext *context, ModulatorProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void ModulatorEffectHandler::GetParami(ALCcontext *context, const ModulatorProps &props, ALenum param, int *val) { switch(param) { - case AL_RING_MODULATOR_FREQUENCY: *val = static_cast(props.Frequency); break; - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = static_cast(props.HighPassCutoff); break; - case AL_RING_MODULATOR_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", - param}; + case AL_RING_MODULATOR_FREQUENCY: *val = static_cast(props.Frequency); return; + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = static_cast(props.HighPassCutoff); return; + case AL_RING_MODULATOR_WAVEFORM: *val = EnumFromWaveform(props.Waveform); return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid modulator integer property {:#04x}", + as_unsigned(param)); } -void ModulatorEffectHandler::GetParamiv(const ModulatorProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } -void ModulatorEffectHandler::GetParamf(const ModulatorProps &props, ALenum param, float *val) +void ModulatorEffectHandler::GetParamiv(ALCcontext *context, const ModulatorProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } +void ModulatorEffectHandler::GetParamf(ALCcontext *context, const ModulatorProps &props, ALenum param, float *val) { switch(param) { - case AL_RING_MODULATOR_FREQUENCY: *val = props.Frequency; break; - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = props.HighPassCutoff; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param}; + case AL_RING_MODULATOR_FREQUENCY: *val = props.Frequency; return; + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = props.HighPassCutoff; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid modulator float property {:#04x}", + as_unsigned(param)); } -void ModulatorEffectHandler::GetParamfv(const ModulatorProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void ModulatorEffectHandler::GetParamfv(ALCcontext *context, const ModulatorProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using ModulatorCommitter = EaxCommitter; diff --git a/Engine/lib/openal-soft/al/effects/null.cpp b/Engine/lib/openal-soft/al/effects/null.cpp index 2fa9cb6d4..ac522313f 100644 --- a/Engine/lib/openal-soft/al/effects/null.cpp +++ b/Engine/lib/openal-soft/al/effects/null.cpp @@ -4,10 +4,11 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #endif // ALSOFT_EAX @@ -24,78 +25,46 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps NullEffectProps{genDefaultProps()}; -void NullEffectHandler::SetParami(std::monostate& /*props*/, ALenum param, int /*val*/) +void NullEffectHandler::SetParami(ALCcontext *context, std::monostate& /*props*/, ALenum param, int /*val*/) { - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", - param}; - } + context->throw_error(AL_INVALID_ENUM, "Invalid null effect integer property {:#04x}", + as_unsigned(param)); } -void NullEffectHandler::SetParamiv(std::monostate &props, ALenum param, const int *vals) +void NullEffectHandler::SetParamiv(ALCcontext *context, std::monostate &props, ALenum param, const int *vals) { - switch(param) - { - default: - SetParami(props, param, *vals); - } + SetParami(context, props, param, *vals); } -void NullEffectHandler::SetParamf(std::monostate& /*props*/, ALenum param, float /*val*/) +void NullEffectHandler::SetParamf(ALCcontext *context, std::monostate& /*props*/, ALenum param, float /*val*/) { - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", - param}; - } + context->throw_error(AL_INVALID_ENUM, "Invalid null effect float property {:#04x}", + as_unsigned(param)); } -void NullEffectHandler::SetParamfv(std::monostate &props, ALenum param, const float *vals) +void NullEffectHandler::SetParamfv(ALCcontext *context, std::monostate &props, ALenum param, const float *vals) { - switch(param) - { - default: - SetParamf(props, param, *vals); - } + SetParamf(context, props, param, *vals); } -void NullEffectHandler::GetParami(const std::monostate& /*props*/, ALenum param, int* /*val*/) +void NullEffectHandler::GetParami(ALCcontext *context, const std::monostate& /*props*/, ALenum param, int* /*val*/) { - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", - param}; - } + context->throw_error(AL_INVALID_ENUM, "Invalid null effect integer property {:#04x}", + as_unsigned(param)); } -void NullEffectHandler::GetParamiv(const std::monostate &props, ALenum param, int *vals) +void NullEffectHandler::GetParamiv(ALCcontext *context, const std::monostate &props, ALenum param, int *vals) { - switch(param) - { - default: - GetParami(props, param, vals); - } + GetParami(context, props, param, vals); } -void NullEffectHandler::GetParamf(const std::monostate& /*props*/, ALenum param, float* /*val*/) +void NullEffectHandler::GetParamf(ALCcontext *context, const std::monostate& /*props*/, ALenum param, float* /*val*/) { - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", - param}; - } + context->throw_error(AL_INVALID_ENUM, "Invalid null effect float property {:#04x}", + as_unsigned(param)); } -void NullEffectHandler::GetParamfv(const std::monostate &props, ALenum param, float *vals) +void NullEffectHandler::GetParamfv(ALCcontext *context, const std::monostate &props, ALenum param, float *vals) { - switch(param) - { - default: - GetParamf(props, param, vals); - } + GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using NullCommitter = EaxCommitter; diff --git a/Engine/lib/openal-soft/al/effects/pshifter.cpp b/Engine/lib/openal-soft/al/effects/pshifter.cpp index c408eddce..c07ef2753 100644 --- a/Engine/lib/openal-soft/al/effects/pshifter.cpp +++ b/Engine/lib/openal-soft/al/effects/pshifter.cpp @@ -4,11 +4,11 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX -#include "alnumeric.h" +#if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -29,63 +29,55 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps PshifterEffectProps{genDefaultProps()}; -void PshifterEffectHandler::SetParami(PshifterProps &props, ALenum param, int val) +void PshifterEffectHandler::SetParami(ALCcontext *context, PshifterProps &props, ALenum param, int val) { switch(param) { case AL_PITCH_SHIFTER_COARSE_TUNE: if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE)) - throw effect_exception{AL_INVALID_VALUE, "Pitch shifter coarse tune out of range"}; + context->throw_error(AL_INVALID_VALUE, "Pitch shifter coarse tune out of range"); props.CoarseTune = val; - break; + return; case AL_PITCH_SHIFTER_FINE_TUNE: if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE)) - throw effect_exception{AL_INVALID_VALUE, "Pitch shifter fine tune out of range"}; + context->throw_error(AL_INVALID_VALUE, "Pitch shifter fine tune out of range"); props.FineTune = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", - param}; + return; } -} -void PshifterEffectHandler::SetParamiv(PshifterProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void PshifterEffectHandler::SetParamf(PshifterProps&, ALenum param, float) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } -void PshifterEffectHandler::SetParamfv(PshifterProps&, ALenum param, const float*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter integer property {:#04x}", + as_unsigned(param)); } +void PshifterEffectHandler::SetParamiv(ALCcontext *context, PshifterProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } -void PshifterEffectHandler::GetParami(const PshifterProps &props, ALenum param, int *val) +void PshifterEffectHandler::SetParamf(ALCcontext *context, PshifterProps&, ALenum param, float) +{ context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter float property {:#04x}", as_unsigned(param)); } +void PshifterEffectHandler::SetParamfv(ALCcontext *context, PshifterProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void PshifterEffectHandler::GetParami(ALCcontext *context, const PshifterProps &props, ALenum param, int *val) { switch(param) { - case AL_PITCH_SHIFTER_COARSE_TUNE: *val = props.CoarseTune; break; - case AL_PITCH_SHIFTER_FINE_TUNE: *val = props.FineTune; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", - param}; + case AL_PITCH_SHIFTER_COARSE_TUNE: *val = props.CoarseTune; return; + case AL_PITCH_SHIFTER_FINE_TUNE: *val = props.FineTune; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter integer property {:#04x}", + as_unsigned(param)); } -void PshifterEffectHandler::GetParamiv(const PshifterProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } +void PshifterEffectHandler::GetParamiv(ALCcontext *context, const PshifterProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } -void PshifterEffectHandler::GetParamf(const PshifterProps&, ALenum param, float*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } -void PshifterEffectHandler::GetParamfv(const PshifterProps&, ALenum param, float*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", - param}; -} +void PshifterEffectHandler::GetParamf(ALCcontext *context, const PshifterProps&, ALenum param, float*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter float property {:#04x}", as_unsigned(param)); } +void PshifterEffectHandler::GetParamfv(ALCcontext *context, const PshifterProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using PitchShifterCommitter = EaxCommitter; diff --git a/Engine/lib/openal-soft/al/effects/reverb.cpp b/Engine/lib/openal-soft/al/effects/reverb.cpp index 7954e6179..d3b83a089 100644 --- a/Engine/lib/openal-soft/al/effects/reverb.cpp +++ b/Engine/lib/openal-soft/al/effects/reverb.cpp @@ -9,13 +9,17 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/context.h" #include "alnumeric.h" #include "alspan.h" -#include "core/effects/base.h" +#include "core/logging.h" #include "effects.h" +#include "fmt/ranges.h" +#include "opthelpers.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include + #include "al/eax/api.h" #include "al/eax/call.h" #include "al/eax/effect.h" @@ -92,152 +96,149 @@ constexpr EffectProps genDefaultStdProps() noexcept const EffectProps ReverbEffectProps{genDefaultProps()}; -void ReverbEffectHandler::SetParami(ReverbProps &props, ALenum param, int val) +void ReverbEffectHandler::SetParami(ALCcontext *context, ReverbProps &props, ALenum param, int val) { switch(param) { case AL_EAXREVERB_DECAY_HFLIMIT: if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"); props.DecayHFLimit = val != AL_FALSE; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", - param}; + return; } + context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb integer property {:#04x}", + as_unsigned(param)); } -void ReverbEffectHandler::SetParamiv(ReverbProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void ReverbEffectHandler::SetParamf(ReverbProps &props, ALenum param, float val) +void ReverbEffectHandler::SetParamiv(ALCcontext *context, ReverbProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } +void ReverbEffectHandler::SetParamf(ALCcontext *context, ReverbProps &props, ALenum param, float val) { switch(param) { case AL_EAXREVERB_DENSITY: if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb density out of range"); props.Density = val; - break; + return; case AL_EAXREVERB_DIFFUSION: if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb diffusion out of range"); props.Diffusion = val; - break; + return; case AL_EAXREVERB_GAIN: if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb gain out of range"); props.Gain = val; - break; + return; case AL_EAXREVERB_GAINHF: if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb gainhf out of range"); props.GainHF = val; - break; + return; case AL_EAXREVERB_GAINLF: if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainlf out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb gainlf out of range"); props.GainLF = val; - break; + return; case AL_EAXREVERB_DECAY_TIME: if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay time out of range"); props.DecayTime = val; - break; + return; case AL_EAXREVERB_DECAY_HFRATIO: if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"); props.DecayHFRatio = val; - break; + return; case AL_EAXREVERB_DECAY_LFRATIO: if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay lfratio out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay lfratio out of range"); props.DecayLFRatio = val; - break; + return; case AL_EAXREVERB_REFLECTIONS_GAIN: if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"); props.ReflectionsGain = val; - break; + return; case AL_EAXREVERB_REFLECTIONS_DELAY: if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"); props.ReflectionsDelay = val; - break; + return; case AL_EAXREVERB_LATE_REVERB_GAIN: if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"); props.LateReverbGain = val; - break; + return; case AL_EAXREVERB_LATE_REVERB_DELAY: if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"); props.LateReverbDelay = val; - break; + return; case AL_EAXREVERB_ECHO_TIME: if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo time out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb echo time out of range"); props.EchoTime = val; - break; + return; case AL_EAXREVERB_ECHO_DEPTH: if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo depth out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb echo depth out of range"); props.EchoDepth = val; - break; + return; case AL_EAXREVERB_MODULATION_TIME: if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation time out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb modulation time out of range"); props.ModulationTime = val; - break; + return; case AL_EAXREVERB_MODULATION_DEPTH: if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation depth out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb modulation depth out of range"); props.ModulationDepth = val; - break; + return; case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"); props.AirAbsorptionGainHF = val; - break; + return; case AL_EAXREVERB_HFREFERENCE: if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb hfreference out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb hfreference out of range"); props.HFReference = val; - break; + return; case AL_EAXREVERB_LFREFERENCE: if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb lfreference out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb lfreference out of range"); props.LFReference = val; - break; + return; case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"); props.RoomRolloffFactor = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; + return; } + context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}", + as_unsigned(param)); } -void ReverbEffectHandler::SetParamfv(ReverbProps &props, ALenum param, const float *vals) +void ReverbEffectHandler::SetParamfv(ALCcontext *context, ReverbProps &props, ALenum param, const float *vals) { static constexpr auto finite_checker = [](float f) -> bool { return std::isfinite(f); }; al::span values; @@ -246,64 +247,60 @@ void ReverbEffectHandler::SetParamfv(ReverbProps &props, ALenum param, const flo case AL_EAXREVERB_REFLECTIONS_PAN: values = {vals, 3_uz}; if(!std::all_of(values.cbegin(), values.cend(), finite_checker)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections pan out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections pan out of range"); std::copy(values.cbegin(), values.cend(), props.ReflectionsPan.begin()); - break; + return; case AL_EAXREVERB_LATE_REVERB_PAN: values = {vals, 3_uz}; if(!std::all_of(values.cbegin(), values.cend(), finite_checker)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb pan out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb pan out of range"); std::copy(values.cbegin(), values.cend(), props.LateReverbPan.begin()); - break; - - default: - SetParamf(props, param, *vals); - break; + return; } + SetParamf(context, props, param, *vals); } -void ReverbEffectHandler::GetParami(const ReverbProps &props, ALenum param, int *val) +void ReverbEffectHandler::GetParami(ALCcontext *context, const ReverbProps &props, ALenum param, int *val) { switch(param) { - case AL_EAXREVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; break; - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", - param}; + case AL_EAXREVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; return; } + context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb integer property {:#04x}", + as_unsigned(param)); } -void ReverbEffectHandler::GetParamiv(const ReverbProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } -void ReverbEffectHandler::GetParamf(const ReverbProps &props, ALenum param, float *val) +void ReverbEffectHandler::GetParamiv(ALCcontext *context, const ReverbProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } +void ReverbEffectHandler::GetParamf(ALCcontext *context, const ReverbProps &props, ALenum param, float *val) { switch(param) { - case AL_EAXREVERB_DENSITY: *val = props.Density; break; - case AL_EAXREVERB_DIFFUSION: *val = props.Diffusion; break; - case AL_EAXREVERB_GAIN: *val = props.Gain; break; - case AL_EAXREVERB_GAINHF: *val = props.GainHF; break; - case AL_EAXREVERB_GAINLF: *val = props.GainLF; break; - case AL_EAXREVERB_DECAY_TIME: *val = props.DecayTime; break; - case AL_EAXREVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; break; - case AL_EAXREVERB_DECAY_LFRATIO: *val = props.DecayLFRatio; break; - case AL_EAXREVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; break; - case AL_EAXREVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; break; - case AL_EAXREVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; break; - case AL_EAXREVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; break; - case AL_EAXREVERB_ECHO_TIME: *val = props.EchoTime; break; - case AL_EAXREVERB_ECHO_DEPTH: *val = props.EchoDepth; break; - case AL_EAXREVERB_MODULATION_TIME: *val = props.ModulationTime; break; - case AL_EAXREVERB_MODULATION_DEPTH: *val = props.ModulationDepth; break; - case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; break; - case AL_EAXREVERB_HFREFERENCE: *val = props.HFReference; break; - case AL_EAXREVERB_LFREFERENCE: *val = props.LFReference; break; - case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; + case AL_EAXREVERB_DENSITY: *val = props.Density; return; + case AL_EAXREVERB_DIFFUSION: *val = props.Diffusion; return; + case AL_EAXREVERB_GAIN: *val = props.Gain; return; + case AL_EAXREVERB_GAINHF: *val = props.GainHF; return; + case AL_EAXREVERB_GAINLF: *val = props.GainLF; return; + case AL_EAXREVERB_DECAY_TIME: *val = props.DecayTime; return; + case AL_EAXREVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; return; + case AL_EAXREVERB_DECAY_LFRATIO: *val = props.DecayLFRatio; return; + case AL_EAXREVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; return; + case AL_EAXREVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; return; + case AL_EAXREVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; return; + case AL_EAXREVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; return; + case AL_EAXREVERB_ECHO_TIME: *val = props.EchoTime; return; + case AL_EAXREVERB_ECHO_DEPTH: *val = props.EchoDepth; return; + case AL_EAXREVERB_MODULATION_TIME: *val = props.ModulationTime; return; + case AL_EAXREVERB_MODULATION_DEPTH: *val = props.ModulationDepth; return; + case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; return; + case AL_EAXREVERB_HFREFERENCE: *val = props.HFReference; return; + case AL_EAXREVERB_LFREFERENCE: *val = props.LFReference; return; + case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}", + as_unsigned(param)); } -void ReverbEffectHandler::GetParamfv(const ReverbProps &props, ALenum param, float *vals) +void ReverbEffectHandler::GetParamfv(ALCcontext *context, const ReverbProps &props, ALenum param, float *vals) { al::span values; switch(param) @@ -311,159 +308,155 @@ void ReverbEffectHandler::GetParamfv(const ReverbProps &props, ALenum param, flo case AL_EAXREVERB_REFLECTIONS_PAN: values = {vals, 3_uz}; std::copy(props.ReflectionsPan.cbegin(), props.ReflectionsPan.cend(), values.begin()); - break; + return; case AL_EAXREVERB_LATE_REVERB_PAN: values = {vals, 3_uz}; std::copy(props.LateReverbPan.cbegin(), props.LateReverbPan.cend(), values.begin()); - break; - - default: - GetParamf(props, param, vals); - break; + return; } + + GetParamf(context, props, param, vals); } const EffectProps StdReverbEffectProps{genDefaultStdProps()}; -void StdReverbEffectHandler::SetParami(ReverbProps &props, ALenum param, int val) +void StdReverbEffectHandler::SetParami(ALCcontext *context, ReverbProps &props, ALenum param, int val) { switch(param) { case AL_REVERB_DECAY_HFLIMIT: if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"); props.DecayHFLimit = val != AL_FALSE; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", - param}; + return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb integer property {:#04x}", + as_unsigned(param)); } -void StdReverbEffectHandler::SetParamiv(ReverbProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void StdReverbEffectHandler::SetParamf(ReverbProps &props, ALenum param, float val) +void StdReverbEffectHandler::SetParamiv(ALCcontext *context, ReverbProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } +void StdReverbEffectHandler::SetParamf(ALCcontext *context, ReverbProps &props, ALenum param, float val) { switch(param) { case AL_REVERB_DENSITY: if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb density out of range"); props.Density = val; - break; + return; case AL_REVERB_DIFFUSION: if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb diffusion out of range"); props.Diffusion = val; - break; + return; case AL_REVERB_GAIN: if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb gain out of range"); props.Gain = val; - break; + return; case AL_REVERB_GAINHF: if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb gainhf out of range"); props.GainHF = val; - break; + return; case AL_REVERB_DECAY_TIME: if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay time out of range"); props.DecayTime = val; - break; + return; case AL_REVERB_DECAY_HFRATIO: if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"); props.DecayHFRatio = val; - break; + return; case AL_REVERB_REFLECTIONS_GAIN: if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"); props.ReflectionsGain = val; - break; + return; case AL_REVERB_REFLECTIONS_DELAY: if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"); props.ReflectionsDelay = val; - break; + return; case AL_REVERB_LATE_REVERB_GAIN: if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"); props.LateReverbGain = val; - break; + return; case AL_REVERB_LATE_REVERB_DELAY: if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"); props.LateReverbDelay = val; - break; + return; case AL_REVERB_AIR_ABSORPTION_GAINHF: if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"); props.AirAbsorptionGainHF = val; - break; + return; case AL_REVERB_ROOM_ROLLOFF_FACTOR: if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"); props.RoomRolloffFactor = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; + return; } -} -void StdReverbEffectHandler::SetParamfv(ReverbProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void StdReverbEffectHandler::GetParami(const ReverbProps &props, ALenum param, int *val) + context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}", + as_unsigned(param)); +} +void StdReverbEffectHandler::SetParamfv(ALCcontext *context, ReverbProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void StdReverbEffectHandler::GetParami(ALCcontext *context, const ReverbProps &props, ALenum param, int *val) { switch(param) { - case AL_REVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; break; - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", - param}; + case AL_REVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; return; } + context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb integer property {:#04x}", + as_unsigned(param)); } -void StdReverbEffectHandler::GetParamiv(const ReverbProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } -void StdReverbEffectHandler::GetParamf(const ReverbProps &props, ALenum param, float *val) +void StdReverbEffectHandler::GetParamiv(ALCcontext *context, const ReverbProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } +void StdReverbEffectHandler::GetParamf(ALCcontext *context, const ReverbProps &props, ALenum param, float *val) { switch(param) { - case AL_REVERB_DENSITY: *val = props.Density; break; - case AL_REVERB_DIFFUSION: *val = props.Diffusion; break; - case AL_REVERB_GAIN: *val = props.Gain; break; - case AL_REVERB_GAINHF: *val = props.GainHF; break; - case AL_REVERB_DECAY_TIME: *val = props.DecayTime; break; - case AL_REVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; break; - case AL_REVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; break; - case AL_REVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; break; - case AL_REVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; break; - case AL_REVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; break; - case AL_REVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; break; - case AL_REVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; + case AL_REVERB_DENSITY: *val = props.Density; return; + case AL_REVERB_DIFFUSION: *val = props.Diffusion; return; + case AL_REVERB_GAIN: *val = props.Gain; return; + case AL_REVERB_GAINHF: *val = props.GainHF; return; + case AL_REVERB_DECAY_TIME: *val = props.DecayTime; return; + case AL_REVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; return; + case AL_REVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; return; + case AL_REVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; return; + case AL_REVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; return; + case AL_REVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; return; + case AL_REVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; return; + case AL_REVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}", + as_unsigned(param)); } -void StdReverbEffectHandler::GetParamfv(const ReverbProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void StdReverbEffectHandler::GetParamfv(ALCcontext *context, const ReverbProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { class EaxReverbEffectException : public EaxException @@ -1064,6 +1057,38 @@ bool EaxReverbCommitter::commit(const EAXREVERBPROPERTIES &props) ret.LFReference = props.flLFReference; ret.RoomRolloffFactor = props.flRoomRolloffFactor; ret.DecayHFLimit = ((props.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0); + if(EaxTraceCommits) UNLIKELY + { + TRACE("Reverb commit:\n" + " Density: {:f}\n" + " Diffusion: {:f}\n" + " Gain: {:f}\n" + " GainHF: {:f}\n" + " GainLF: {:f}\n" + " DecayTime: {:f}\n" + " DecayHFRatio: {:f}\n" + " DecayLFRatio: {:f}\n" + " ReflectionsGain: {:f}\n" + " ReflectionsDelay: {:f}\n" + " ReflectionsPan: {}\n" + " LateReverbGain: {:f}\n" + " LateReverbDelay: {:f}\n" + " LateRevernPan: {}\n" + " EchoTime: {:f}\n" + " EchoDepth: {:f}\n" + " ModulationTime: {:f}\n" + " ModulationDepth: {:f}\n" + " AirAbsorptionGainHF: {:f}\n" + " HFReference: {:f}\n" + " LFReference: {:f}\n" + " RoomRolloffFactor: {:f}\n" + " DecayHFLimit: {}", ret.Density, ret.Diffusion, ret.Gain, ret.GainHF, ret.GainLF, + ret.DecayTime, ret.DecayHFRatio, ret.DecayLFRatio, ret.ReflectionsGain, + ret.ReflectionsDelay, ret.ReflectionsPan, ret.LateReverbGain, ret.LateReverbDelay, + ret.LateReverbPan, ret.EchoTime, ret.EchoDepth, ret.ModulationTime, + ret.ModulationDepth, ret.AirAbsorptionGainHF, ret.HFReference, ret.LFReference, + ret.RoomRolloffFactor, ret.DecayHFLimit ? "true" : "false"); + } return ret; }(); diff --git a/Engine/lib/openal-soft/al/effects/vmorpher.cpp b/Engine/lib/openal-soft/al/effects/vmorpher.cpp index 8c66957be..982bc7e4a 100644 --- a/Engine/lib/openal-soft/al/effects/vmorpher.cpp +++ b/Engine/lib/openal-soft/al/effects/vmorpher.cpp @@ -7,10 +7,11 @@ #include "AL/al.h" #include "AL/efx.h" -#include "core/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include #include "al/eax/effect.h" #include "al/eax/exception.h" @@ -96,7 +97,7 @@ constexpr ALenum EnumFromPhenome(VMorpherPhenome phenome) HANDLE_PHENOME(V); HANDLE_PHENOME(Z); } - throw std::runtime_error{"Invalid phenome: "+std::to_string(static_cast(phenome))}; + throw std::runtime_error{fmt::format("Invalid phenome: {}", int{al::to_underlying(phenome)})}; #undef HANDLE_PHENOME } @@ -118,8 +119,8 @@ constexpr ALenum EnumFromWaveform(VMorpherWaveform type) case VMorpherWaveform::Triangle: return AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE; case VMorpherWaveform::Sawtooth: return AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH; } - throw std::runtime_error{"Invalid vocal morpher waveform: " + - std::to_string(static_cast(type))}; + throw std::runtime_error{fmt::format("Invalid vocal morpher waveform: {}", + int{al::to_underlying(type)})}; } constexpr EffectProps genDefaultProps() noexcept @@ -138,7 +139,7 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps VmorpherEffectProps{genDefaultProps()}; -void VmorpherEffectHandler::SetParami(VmorpherProps &props, ALenum param, int val) +void VmorpherEffectHandler::SetParami(ALCcontext *context, VmorpherProps &props, ALenum param, int val) { switch(param) { @@ -146,101 +147,94 @@ void VmorpherEffectHandler::SetParami(VmorpherProps &props, ALenum param, int va if(auto phenomeopt = PhenomeFromEnum(val)) props.PhonemeA = *phenomeopt; else - throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: 0x%04x", val}; - break; + context->throw_error(AL_INVALID_VALUE, + "Vocal morpher phoneme-a out of range: {:#04x}", as_unsigned(val)); + return; case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING)) - throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a coarse tuning out of range"}; + context->throw_error(AL_INVALID_VALUE, + "Vocal morpher phoneme-a coarse tuning out of range"); props.PhonemeACoarseTuning = val; - break; + return; case AL_VOCAL_MORPHER_PHONEMEB: if(auto phenomeopt = PhenomeFromEnum(val)) props.PhonemeB = *phenomeopt; else - throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: 0x%04x", val}; - break; + context->throw_error(AL_INVALID_VALUE, + "Vocal morpher phoneme-b out of range: {:#04x}", as_unsigned(val)); + return; case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING)) - throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b coarse tuning out of range"}; + context->throw_error(AL_INVALID_VALUE, + "Vocal morpher phoneme-b coarse tuning out of range"); props.PhonemeBCoarseTuning = val; - break; + return; case AL_VOCAL_MORPHER_WAVEFORM: if(auto formopt = WaveformFromEmum(val)) props.Waveform = *formopt; else - throw effect_exception{AL_INVALID_VALUE, "Vocal morpher waveform out of range: 0x%04x", val}; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", - param}; + context->throw_error(AL_INVALID_VALUE, "Vocal morpher waveform out of range: {:#04x}", + as_unsigned(val)); + return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid vocal morpher integer property {:#04x}", + as_unsigned(param)); } -void VmorpherEffectHandler::SetParamiv(VmorpherProps&, ALenum param, const int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", - param}; -} -void VmorpherEffectHandler::SetParamf(VmorpherProps &props, ALenum param, float val) +void VmorpherEffectHandler::SetParamiv(ALCcontext *context, VmorpherProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } +void VmorpherEffectHandler::SetParamf(ALCcontext *context, VmorpherProps &props, ALenum param, float val) { switch(param) { case AL_VOCAL_MORPHER_RATE: if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE)) - throw effect_exception{AL_INVALID_VALUE, "Vocal morpher rate out of range"}; + context->throw_error(AL_INVALID_VALUE, "Vocal morpher rate out of range"); props.Rate = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x", - param}; + return; } -} -void VmorpherEffectHandler::SetParamfv(VmorpherProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void VmorpherEffectHandler::GetParami(const VmorpherProps &props, ALenum param, int* val) + context->throw_error(AL_INVALID_ENUM, "Invalid vocal morpher float property {:#04x}", + as_unsigned(param)); +} +void VmorpherEffectHandler::SetParamfv(ALCcontext *context, VmorpherProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void VmorpherEffectHandler::GetParami(ALCcontext *context, const VmorpherProps &props, ALenum param, int* val) { switch(param) { - case AL_VOCAL_MORPHER_PHONEMEA: *val = EnumFromPhenome(props.PhonemeA); break; - case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: *val = props.PhonemeACoarseTuning; break; - case AL_VOCAL_MORPHER_PHONEMEB: *val = EnumFromPhenome(props.PhonemeB); break; - case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: *val = props.PhonemeBCoarseTuning; break; - case AL_VOCAL_MORPHER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", - param}; + case AL_VOCAL_MORPHER_PHONEMEA: *val = EnumFromPhenome(props.PhonemeA); return; + case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: *val = props.PhonemeACoarseTuning; return; + case AL_VOCAL_MORPHER_PHONEMEB: *val = EnumFromPhenome(props.PhonemeB); return; + case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: *val = props.PhonemeBCoarseTuning; return; + case AL_VOCAL_MORPHER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid vocal morpher integer property {:#04x}", + as_unsigned(param)); } -void VmorpherEffectHandler::GetParamiv(const VmorpherProps&, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", - param}; -} -void VmorpherEffectHandler::GetParamf(const VmorpherProps &props, ALenum param, float *val) +void VmorpherEffectHandler::GetParamiv(ALCcontext *context, const VmorpherProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } +void VmorpherEffectHandler::GetParamf(ALCcontext *context, const VmorpherProps &props, ALenum param, float *val) { switch(param) { - case AL_VOCAL_MORPHER_RATE: - *val = props.Rate; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x", - param}; + case AL_VOCAL_MORPHER_RATE: *val = props.Rate; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid vocal morpher float property {:#04x}", + as_unsigned(param)); } -void VmorpherEffectHandler::GetParamfv(const VmorpherProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void VmorpherEffectHandler::GetParamfv(ALCcontext *context, const VmorpherProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using VocalMorpherCommitter = EaxCommitter; diff --git a/Engine/lib/openal-soft/al/error.cpp b/Engine/lib/openal-soft/al/error.cpp index 24cc51dca..4d2015ce2 100644 --- a/Engine/lib/openal-soft/al/error.cpp +++ b/Engine/lib/openal-soft/al/error.cpp @@ -20,24 +20,19 @@ #include "config.h" -#include "error.h" - #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #endif -#include #include #include #include #include #include -#include #include #include #include -#include #include "AL/al.h" #include "AL/alc.h" @@ -45,53 +40,19 @@ #include "al/debug.h" #include "alc/alconfig.h" #include "alc/context.h" -#include "alc/inprogext.h" +#include "alnumeric.h" +#include "core/except.h" #include "core/logging.h" #include "opthelpers.h" #include "strutils.h" -namespace al { -context_error::context_error(ALenum code, const char *msg, ...) : mErrorCode{code} +void ALCcontext::setErrorImpl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args) { - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - std::va_list args; - va_start(args, msg); - setMessage(msg, args); - va_end(args); - /* NOLINTEND(*-array-to-pointer-decay) */ -} -context_error::~context_error() = default; -} /* namespace al */ + const auto msg = fmt::vformat(fmt, std::move(args)); -void ALCcontext::setError(ALenum errorCode, const char *msg, ...) -{ - auto message = std::vector(256); - - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - std::va_list args, args2; - va_start(args, msg); - va_copy(args2, args); - int msglen{std::vsnprintf(message.data(), message.size(), msg, args)}; - if(msglen >= 0 && static_cast(msglen) >= message.size()) - { - message.resize(static_cast(msglen) + 1u); - msglen = std::vsnprintf(message.data(), message.size(), msg, args2); - } - va_end(args2); - va_end(args); - /* NOLINTEND(*-array-to-pointer-decay) */ - - if(msglen >= 0) - msg = message.data(); - else - { - msg = ""; - msglen = static_cast(strlen(msg)); - } - - WARN("Error generated on context %p, code 0x%04x, \"%s\"\n", - decltype(std::declval()){this}, errorCode, msg); + WARN("Error generated on context {}, code {:#04x}, \"{}\"", + decltype(std::declval()){this}, as_unsigned(errorCode), msg); if(TrapALError) { #ifdef _WIN32 @@ -106,10 +67,18 @@ void ALCcontext::setError(ALenum errorCode, const char *msg, ...) if(mLastThreadError.get() == AL_NO_ERROR) mLastThreadError.set(errorCode); - debugMessage(DebugSource::API, DebugType::Error, 0, DebugSeverity::High, - {msg, static_cast(msglen)}); + debugMessage(DebugSource::API, DebugType::Error, static_cast(errorCode), + DebugSeverity::High, msg); } +void ALCcontext::throw_error_impl(ALenum errorCode, const fmt::string_view fmt, + fmt::format_args args) +{ + setErrorImpl(errorCode, fmt, std::move(args)); + throw al::base_exception{}; +} + + /* Special-case alGetError since it (potentially) raises a debug signal and * returns a non-default value for a null context. */ @@ -125,17 +94,20 @@ AL_API auto AL_APIENTRY alGetError() noexcept -> ALenum optstr = ConfigValueStr({}, "game_compat", optname); if(optstr) { - char *end{}; - auto value = std::strtoul(optstr->c_str(), &end, 0); - if(end && *end == '\0' && value <= std::numeric_limits::max()) - return static_cast(value); - ERR("Invalid default error value: \"%s\"", optstr->c_str()); + try { + auto idx = 0_uz; + auto value = std::stoi(*optstr, &idx, 0); + if(idx >= optstr->size() || std::isspace(optstr->at(idx))) + return static_cast(value); + } catch(...) { + } + ERR("Invalid default error value: \"{}\"", *optstr); } return AL_INVALID_OPERATION; }; static const ALenum deferror{get_value("__ALSOFT_DEFAULT_ERROR", "default-error")}; - WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror); + WARN("Querying error state on null context (implicitly {:#04x})", as_unsigned(deferror)); if(TrapALError) { #ifdef _WIN32 diff --git a/Engine/lib/openal-soft/al/error.h b/Engine/lib/openal-soft/al/error.h deleted file mode 100644 index c50011a54..000000000 --- a/Engine/lib/openal-soft/al/error.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef AL_ERROR_H -#define AL_ERROR_H - -#include "AL/al.h" - -#include "core/except.h" - -namespace al { - -class context_error final : public al::base_exception { - ALenum mErrorCode{}; - -public: -#ifdef __MINGW32__ - [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] -#else - [[gnu::format(printf, 3, 4)]] -#endif - context_error(ALenum code, const char *msg, ...); - ~context_error() final; - - [[nodiscard]] auto errorCode() const noexcept -> ALenum { return mErrorCode; } -}; - -} /* namespace al */ - -#endif /* AL_ERROR_H */ diff --git a/Engine/lib/openal-soft/al/event.cpp b/Engine/lib/openal-soft/al/event.cpp index 5d6a1c38c..9a8d50b69 100644 --- a/Engine/lib/openal-soft/al/event.cpp +++ b/Engine/lib/openal-soft/al/event.cpp @@ -3,7 +3,6 @@ #include "event.h" -#include #include #include #include @@ -12,10 +11,8 @@ #include #include #include -#include #include #include -#include #include #include "AL/al.h" @@ -23,15 +20,17 @@ #include "AL/alext.h" #include "alc/context.h" +#include "alnumeric.h" #include "alsem.h" #include "alspan.h" +#include "alstring.h" #include "core/async_event.h" #include "core/context.h" #include "core/effects/base.h" +#include "core/except.h" #include "core/logging.h" #include "debug.h" #include "direct_defs.h" -#include "error.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "ringbuffer.h" @@ -51,14 +50,15 @@ int EventThread(ALCcontext *context) bool quitnow{false}; while(!quitnow) { - auto evt_data = ring->getReadVector().first; + auto evt_data = ring->getReadVector()[0]; if(evt_data.len == 0) { context->mEventSem.wait(); continue; } - std::lock_guard eventlock{context->mEventCbLock}; + auto eventlock = std::lock_guard{context->mEventCbLock}; + const auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire); auto evt_span = al::span{std::launder(reinterpret_cast(evt_data.buf)), evt_data.len}; for(auto &event : evt_span) @@ -66,7 +66,6 @@ int EventThread(ALCcontext *context) quitnow = std::holds_alternative(event); if(quitnow) UNLIKELY break; - auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire); auto proc_killthread = [](AsyncKillThread&) { }; auto proc_release = [](AsyncEffectReleaseEvent &evt) { @@ -101,7 +100,7 @@ int EventThread(ALCcontext *context) break; } context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.mId, state, - static_cast(msg.length()), msg.c_str(), context->mEventParam); + al::sizei(msg), msg.c_str(), context->mEventParam); }; auto proc_buffercomp = [context,enabledevts](AsyncBufferCompleteEvent &evt) { @@ -113,18 +112,16 @@ int EventThread(ALCcontext *context) if(evt.mCount == 1) msg += " buffer completed"; else msg += " buffers completed"; context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.mId, evt.mCount, - static_cast(msg.length()), msg.c_str(), context->mEventParam); + al::sizei(msg), msg.c_str(), context->mEventParam); }; auto proc_disconnect = [context,enabledevts](AsyncDisconnectEvent &evt) { - context->debugMessage(DebugSource::System, DebugType::Error, 0, - DebugSeverity::High, evt.msg); + if(!context->mEventCb + || !enabledevts.test(al::to_underlying(AsyncEnableBits::Disconnected))) + return; - if(context->mEventCb - && enabledevts.test(al::to_underlying(AsyncEnableBits::Disconnected))) - context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, - static_cast(evt.msg.length()), evt.msg.c_str(), - context->mEventParam); + context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, al::sizei(evt.msg), + evt.msg.c_str(), context->mEventParam); }; std::visit(overloaded{proc_srcstate, proc_buffercomp, proc_release, proc_disconnect, @@ -156,22 +153,22 @@ void StartEventThrd(ALCcontext *ctx) ctx->mEventThread = std::thread{EventThread, ctx}; } catch(std::exception& e) { - ERR("Failed to start event thread: %s\n", e.what()); + ERR("Failed to start event thread: {}", e.what()); } catch(...) { - ERR("Failed to start event thread! Expect problems.\n"); + ERR("Failed to start event thread! Expect problems."); } } void StopEventThrd(ALCcontext *ctx) { RingBuffer *ring{ctx->mAsyncEvents.get()}; - auto evt_data = ring->getWriteVector().first; + auto evt_data = ring->getWriteVector()[0]; if(evt_data.len == 0) { do { std::this_thread::yield(); - evt_data = ring->getWriteVector().first; + evt_data = ring->getWriteVector()[0]; } while(evt_data.len == 0); } std::ignore = InitAsyncEvent(evt_data.buf); @@ -187,18 +184,19 @@ FORCE_ALIGN void AL_APIENTRY alEventControlDirectSOFT(ALCcontext *context, ALsiz const ALenum *types, ALboolean enable) noexcept try { if(count < 0) - throw al::context_error{AL_INVALID_VALUE, "Controlling %d events", count}; + context->throw_error(AL_INVALID_VALUE, "Controlling {} events", count); if(count <= 0) UNLIKELY return; if(!types) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); ContextBase::AsyncEventBitset flags{}; for(ALenum evttype : al::span{types, static_cast(count)}) { auto etype = GetEventType(evttype); if(!etype) - throw al::context_error{AL_INVALID_ENUM, "Invalid event type 0x%04x", evttype}; + context->throw_error(AL_INVALID_ENUM, "Invalid event type {:#04x}", + as_unsigned(evttype)); flags.set(al::to_underlying(*etype)); } @@ -226,15 +224,22 @@ try { std::lock_guard eventlock{context->mEventCbLock}; } } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT2(void, alEventCallback,SOFT, ALEVENTPROCSOFT,callback, void*,userParam) FORCE_ALIGN void AL_APIENTRY alEventCallbackDirectSOFT(ALCcontext *context, ALEVENTPROCSOFT callback, void *userParam) noexcept -{ +try { std::lock_guard eventlock{context->mEventCbLock}; context->mEventCb = callback; context->mEventParam = userParam; } +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); +} diff --git a/Engine/lib/openal-soft/al/extension.cpp b/Engine/lib/openal-soft/al/extension.cpp index 16dc015aa..061addc84 100644 --- a/Engine/lib/openal-soft/al/extension.cpp +++ b/Engine/lib/openal-soft/al/extension.cpp @@ -21,13 +21,11 @@ #include "config.h" #include -#include #include "AL/al.h" #include "AL/alc.h" #include "alc/context.h" -#include "alc/inprogext.h" #include "alstring.h" #include "direct_defs.h" #include "opthelpers.h" diff --git a/Engine/lib/openal-soft/al/filter.cpp b/Engine/lib/openal-soft/al/filter.cpp index 5e56c6f04..938ac48cd 100644 --- a/Engine/lib/openal-soft/al/filter.cpp +++ b/Engine/lib/openal-soft/al/filter.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include @@ -41,12 +40,12 @@ #include "albit.h" #include "alc/context.h" #include "alc/device.h" -#include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" +#include "core/except.h" +#include "core/logging.h" #include "direct_defs.h" -#include "error.h" #include "intrusive_ptr.h" #include "opthelpers.h" @@ -97,7 +96,8 @@ void InitFilterParams(ALfilter *filter, ALenum type) filter->type = type; } -auto EnsureFilters(ALCdevice *device, size_t needed) noexcept -> bool +[[nodiscard]] +auto EnsureFilters(al::Device *device, size_t needed) noexcept -> bool try { size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), 0_uz, [](size_t cur, const FilterSubList &sublist) noexcept -> size_t @@ -121,7 +121,8 @@ catch(...) { } -ALfilter *AllocFilter(ALCdevice *device) noexcept +[[nodiscard]] +auto AllocFilter(al::Device *device) noexcept -> ALfilter* { auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(), [](const FilterSubList &entry) noexcept -> bool @@ -141,7 +142,7 @@ ALfilter *AllocFilter(ALCdevice *device) noexcept return filter; } -void FreeFilter(ALCdevice *device, ALfilter *filter) +void FreeFilter(al::Device *device, ALfilter *filter) { device->mFilterNames.erase(filter->id); @@ -155,7 +156,8 @@ void FreeFilter(ALCdevice *device, ALfilter *filter) } -auto LookupFilter(ALCdevice *device, ALuint id) noexcept -> ALfilter* +[[nodiscard]] +auto LookupFilter(al::Device *device, ALuint id) noexcept -> ALfilter* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -172,171 +174,176 @@ auto LookupFilter(ALCdevice *device, ALuint id) noexcept -> ALfilter* /* Null filter parameter handlers */ template<> -void FilterTable::setParami(ALfilter*, ALenum param, int) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::setParami(ALCcontext *context, ALfilter*, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::setParamiv(ALfilter*, ALenum param, const int*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::setParamiv(ALCcontext *context, ALfilter*, ALenum param, const int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::setParamf(ALfilter*, ALenum param, float) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::setParamf(ALCcontext *context, ALfilter*, ALenum param, float) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::setParamfv(ALfilter*, ALenum param, const float*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::setParamfv(ALCcontext *context, ALfilter*, ALenum param, const float*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::getParami(const ALfilter*, ALenum param, int*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::getParamiv(const ALfilter*, ALenum param, int*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::getParamiv(ALCcontext *context, const ALfilter*, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::getParamf(const ALfilter*, ALenum param, float*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::getParamf(ALCcontext *context, const ALfilter*, ALenum param, float*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::getParamfv(const ALfilter*, ALenum param, float*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::getParamfv(ALCcontext *context, const ALfilter*, ALenum param, float*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } /* Lowpass parameter handlers */ template<> -void FilterTable::setParami(ALfilter*, ALenum param, int) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } +void FilterTable::setParami(ALCcontext *context, ALfilter*, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid low-pass integer property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::setParamiv(ALfilter *filter, ALenum param, const int *values) -{ setParami(filter, param, *values); } +void FilterTable::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values) +{ setParami(context, filter, param, *values); } template<> -void FilterTable::setParamf(ALfilter *filter, ALenum param, float val) +void FilterTable::setParamf(ALCcontext *context, ALfilter *filter, ALenum param, float val) { switch(param) { case AL_LOWPASS_GAIN: if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN)) - throw al::context_error{AL_INVALID_VALUE, "Low-pass gain %f out of range", val}; + context->throw_error(AL_INVALID_VALUE, "Low-pass gain {:f} out of range", val); filter->Gain = val; return; case AL_LOWPASS_GAINHF: if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF)) - throw al::context_error{AL_INVALID_VALUE, "Low-pass gainhf %f out of range", val}; + context->throw_error(AL_INVALID_VALUE, "Low-pass gainhf {:f} out of range", val); filter->GainHF = val; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid low-pass float property {:#04x}", + as_unsigned(param)); } template<> -void FilterTable::setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ setParamf(filter, param, *vals); } +void FilterTable::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals) +{ setParamf(context, filter, param, *vals); } template<> -void FilterTable::getParami(const ALfilter*, ALenum param, int*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } +void FilterTable::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid low-pass integer property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::getParamiv(const ALfilter *filter, ALenum param, int *values) -{ getParami(filter, param, values); } +void FilterTable::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values) +{ getParami(context, filter, param, values); } template<> -void FilterTable::getParamf(const ALfilter *filter, ALenum param, float *val) +void FilterTable::getParamf(ALCcontext *context, const ALfilter *filter, ALenum param, float *val) { switch(param) { case AL_LOWPASS_GAIN: *val = filter->Gain; return; case AL_LOWPASS_GAINHF: *val = filter->GainHF; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid low-pass float property {:#04x}", + as_unsigned(param)); } template<> -void FilterTable::getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ getParamf(filter, param, vals); } +void FilterTable::getParamfv(ALCcontext *context, const ALfilter *filter, ALenum param, float *vals) +{ getParamf(context, filter, param, vals); } /* Highpass parameter handlers */ template<> -void FilterTable::setParami(ALfilter*, ALenum param, int) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } +void FilterTable::setParami(ALCcontext *context, ALfilter*, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid high-pass integer property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::setParamiv(ALfilter *filter, ALenum param, const int *values) -{ setParami(filter, param, *values); } +void FilterTable::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values) +{ setParami(context, filter, param, *values); } template<> -void FilterTable::setParamf(ALfilter *filter, ALenum param, float val) +void FilterTable::setParamf(ALCcontext *context, ALfilter *filter, ALenum param, float val) { switch(param) { case AL_HIGHPASS_GAIN: if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN)) - throw al::context_error{AL_INVALID_VALUE, "High-pass gain %f out of range", val}; + context->throw_error(AL_INVALID_VALUE, "High-pass gain {:f} out of range", val); filter->Gain = val; return; case AL_HIGHPASS_GAINLF: if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF)) - throw al::context_error{AL_INVALID_VALUE, "High-pass gainlf %f out of range", val}; + context->throw_error(AL_INVALID_VALUE, "High-pass gainlf {:f} out of range", val); filter->GainLF = val; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid high-pass float property {:#04x}", + as_unsigned(param)); } template<> -void FilterTable::setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ setParamf(filter, param, *vals); } +void FilterTable::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals) +{ setParamf(context, filter, param, *vals); } template<> -void FilterTable::getParami(const ALfilter*, ALenum param, int*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } +void FilterTable::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid high-pass integer property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::getParamiv(const ALfilter *filter, ALenum param, int *values) -{ getParami(filter, param, values); } +void FilterTable::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values) +{ getParami(context, filter, param, values); } template<> -void FilterTable::getParamf(const ALfilter *filter, ALenum param, float *val) +void FilterTable::getParamf(ALCcontext *context, const ALfilter *filter, ALenum param, float *val) { switch(param) { case AL_HIGHPASS_GAIN: *val = filter->Gain; return; case AL_HIGHPASS_GAINLF: *val = filter->GainLF; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid high-pass float property {:#04x}", + as_unsigned(param)); } template<> -void FilterTable::getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ getParamf(filter, param, vals); } +void FilterTable::getParamfv(ALCcontext *context, const ALfilter *filter, ALenum param, float *vals) +{ getParamf(context, filter, param, vals); } /* Bandpass parameter handlers */ template<> -void FilterTable::setParami(ALfilter*, ALenum param, int) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } +void FilterTable::setParami(ALCcontext *context, ALfilter*, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid band-pass integer property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::setParamiv(ALfilter *filter, ALenum param, const int *values) -{ setParami(filter, param, *values); } +void FilterTable::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values) +{ setParami(context, filter, param, *values); } template<> -void FilterTable::setParamf(ALfilter *filter, ALenum param, float val) +void FilterTable::setParamf(ALCcontext *context, ALfilter *filter, ALenum param, float val) { switch(param) { case AL_BANDPASS_GAIN: if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN)) - throw al::context_error{AL_INVALID_VALUE, "Band-pass gain %f out of range", val}; + context->throw_error(AL_INVALID_VALUE, "Band-pass gain {:f} out of range", val); filter->Gain = val; return; case AL_BANDPASS_GAINHF: if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF)) - throw al::context_error{AL_INVALID_VALUE, "Band-pass gainhf %f out of range", val}; + context->throw_error(AL_INVALID_VALUE, "Band-pass gainhf {:f} out of range", val); filter->GainHF = val; return; case AL_BANDPASS_GAINLF: if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF)) - throw al::context_error{AL_INVALID_VALUE, "Band-pass gainlf %f out of range", val}; + context->throw_error(AL_INVALID_VALUE, "Band-pass gainlf {:f} out of range", val); filter->GainLF = val; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid band-pass float property {:#04x}", + as_unsigned(param)); } template<> -void FilterTable::setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ setParamf(filter, param, *vals); } +void FilterTable::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals) +{ setParamf(context, filter, param, *vals); } template<> -void FilterTable::getParami(const ALfilter*, ALenum param, int*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } +void FilterTable::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid band-pass integer property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::getParamiv(const ALfilter *filter, ALenum param, int *values) -{ getParami(filter, param, values); } +void FilterTable::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values) +{ getParami(context, filter, param, values); } template<> -void FilterTable::getParamf(const ALfilter *filter, ALenum param, float *val) +void FilterTable::getParamf(ALCcontext *context, const ALfilter *filter, ALenum param, float *val) { switch(param) { @@ -344,32 +351,35 @@ void FilterTable::getParamf(const ALfilter *filter, ALenum case AL_BANDPASS_GAINHF: *val = filter->GainHF; return; case AL_BANDPASS_GAINLF: *val = filter->GainLF; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid band-pass float property {:#04x}", + as_unsigned(param)); } template<> -void FilterTable::getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ getParamf(filter, param, vals); } +void FilterTable::getParamfv(ALCcontext *context, const ALfilter *filter, ALenum param, float *vals) +{ getParamf(context, filter, param, vals); } AL_API DECL_FUNC2(void, alGenFilters, ALsizei,n, ALuint*,filters) FORCE_ALIGN void AL_APIENTRY alGenFiltersDirect(ALCcontext *context, ALsizei n, ALuint *filters) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Generating %d filters", n}; + context->throw_error(AL_INVALID_VALUE, "Generating {} filters", n); if(n <= 0) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; const al::span fids{filters, static_cast(n)}; if(!EnsureFilters(device, fids.size())) - throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, - (n == 1) ? "" : "s"}; + context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} filter{}", n, + (n==1) ? "" : "s"); std::generate(fids.begin(), fids.end(), [device]{ return AllocFilter(device)->id; }); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteFilters, ALsizei,n, const ALuint*,filters) @@ -377,11 +387,11 @@ FORCE_ALIGN void AL_APIENTRY alDeleteFiltersDirect(ALCcontext *context, ALsizei const ALuint *filters) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Deleting %d filters", n}; + context->throw_error(AL_INVALID_VALUE, "Deleting {} filters", n); if(n <= 0) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; /* First try to find any filters that are invalid. */ auto validate_filter = [device](const ALuint fid) -> bool @@ -390,7 +400,7 @@ try { const al::span fids{filters, static_cast(n)}; auto invflt = std::find_if_not(fids.begin(), fids.end(), validate_filter); if(invflt != fids.end()) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", *invflt}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", *invflt); /* All good. Delete non-0 filter IDs. */ auto delete_filter = [device](const ALuint fid) -> void @@ -400,15 +410,17 @@ try { }; std::for_each(fids.begin(), fids.end(), delete_filter); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsFilter, ALuint,filter) FORCE_ALIGN ALboolean AL_APIENTRY alIsFilterDirect(ALCcontext *context, ALuint filter) noexcept { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; if(!filter || LookupFilter(device, filter)) return AL_TRUE; return AL_FALSE; @@ -419,29 +431,32 @@ AL_API DECL_FUNC3(void, alFilteri, ALuint,filter, ALenum,param, ALint,value) FORCE_ALIGN void AL_APIENTRY alFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, ALint value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); switch(param) { case AL_FILTER_TYPE: if(!(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS || value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS)) - throw al::context_error{AL_INVALID_VALUE, "Invalid filter type 0x%04x", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid filter type {:#04x}", + as_unsigned(value)); InitFilterParams(alfilt, value); return; } /* Call the appropriate handler */ - std::visit([alfilt,param,value](auto&& thunk){thunk.setParami(alfilt, param, value);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,value](auto&& thunk) + { thunk.setParami(context, alfilt, param, value); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alFilteriv, ALuint,filter, ALenum,param, const ALint*,values) @@ -455,83 +470,89 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ - std::visit([alfilt,param,values](auto&& thunk){thunk.setParamiv(alfilt, param, values);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,values](auto&& thunk) + { thunk.setParamiv(context, alfilt, param, values); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alFilterf, ALuint,filter, ALenum,param, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ - std::visit([alfilt,param,value](auto&& thunk){thunk.setParamf(alfilt, param, value);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,value](auto&& thunk) + { thunk.setParamf(context, alfilt, param, value); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alFilterfv, ALuint,filter, ALenum,param, const ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, const ALfloat *values) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ - std::visit([alfilt,param,values](auto&& thunk){thunk.setParamfv(alfilt, param, values);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,values](auto&& thunk) + { thunk.setParamfv(context, alfilt, param, values); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetFilteri, ALuint,filter, ALenum,param, ALint*,value) FORCE_ALIGN void AL_APIENTRY alGetFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, ALint *value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); switch(param) { - case AL_FILTER_TYPE: - *value = alfilt->type; - return; + case AL_FILTER_TYPE: *value = alfilt->type; return; } /* Call the appropriate handler */ - std::visit([alfilt,param,value](auto&& thunk){thunk.getParami(alfilt, param, value);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,value](auto&& thunk) + { thunk.getParami(context, alfilt, param, value); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetFilteriv, ALuint,filter, ALenum,param, ALint*,values) @@ -545,68 +566,74 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ - std::visit([alfilt,param,values](auto&& thunk){thunk.getParamiv(alfilt, param, values);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,values](auto&& thunk) + { thunk.getParamiv(context, alfilt, param, values); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetFilterf, ALuint,filter, ALenum,param, ALfloat*,value) FORCE_ALIGN void AL_APIENTRY alGetFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat *value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + if(!alfilt) + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ - std::visit([alfilt,param,value](auto&& thunk){thunk.getParamf(alfilt, param, value);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,value](auto&& thunk) + { thunk.getParamf(context, alfilt, param, value); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetFilterfv, ALuint,filter, ALenum,param, ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alGetFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat *values) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + if(!alfilt) + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ - std::visit([alfilt,param,values](auto&& thunk){thunk.getParamfv(alfilt, param, values);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,values](auto&& thunk) + { thunk.getParamfv(context, alfilt, param, values); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } void ALfilter::SetName(ALCcontext *context, ALuint id, std::string_view name) { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; auto filter = LookupFilter(device, id); if(!filter) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", id}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", id); device->mFilterNames.insert_or_assign(id, name); } diff --git a/Engine/lib/openal-soft/al/filter.h b/Engine/lib/openal-soft/al/filter.h index 562ec52e2..70b78ca83 100644 --- a/Engine/lib/openal-soft/al/filter.h +++ b/Engine/lib/openal-soft/al/filter.h @@ -14,21 +14,27 @@ #include "almalloc.h" #include "alnumeric.h" +struct ALfilter; + inline constexpr float LowPassFreqRef{5000.0f}; inline constexpr float HighPassFreqRef{250.0f}; template struct FilterTable { - static void setParami(struct ALfilter*, ALenum, int); - static void setParamiv(struct ALfilter*, ALenum, const int*); - static void setParamf(struct ALfilter*, ALenum, float); - static void setParamfv(struct ALfilter*, ALenum, const float*); + static void setParami(ALCcontext*, ALfilter*, ALenum, int); + static void setParamiv(ALCcontext*, ALfilter*, ALenum, const int*); + static void setParamf(ALCcontext*, ALfilter*, ALenum, float); + static void setParamfv(ALCcontext*, ALfilter*, ALenum, const float*); - static void getParami(const struct ALfilter*, ALenum, int*); - static void getParamiv(const struct ALfilter*, ALenum, int*); - static void getParamf(const struct ALfilter*, ALenum, float*); - static void getParamfv(const struct ALfilter*, ALenum, float*); + static void getParami(ALCcontext*, const ALfilter*, ALenum, int*); + static void getParamiv(ALCcontext*, const ALfilter*, ALenum, int*); + static void getParamf(ALCcontext*, const ALfilter*, ALenum, float*); + static void getParamfv(ALCcontext*, const ALfilter*, ALenum, float*); + +private: + FilterTable() = default; + friend T; }; struct NullFilterTable : public FilterTable { }; diff --git a/Engine/lib/openal-soft/al/listener.cpp b/Engine/lib/openal-soft/al/listener.cpp index 30ceed040..9addee0cd 100644 --- a/Engine/lib/openal-soft/al/listener.cpp +++ b/Engine/lib/openal-soft/al/listener.cpp @@ -31,11 +31,11 @@ #include "AL/efx.h" #include "alc/context.h" -#include "alc/inprogext.h" +#include "alnumeric.h" #include "alspan.h" +#include "core/except.h" +#include "core/logging.h" #include "direct_defs.h" -#include "error.h" -#include "opthelpers.h" namespace { @@ -54,7 +54,7 @@ inline void CommitAndUpdateProps(ALCcontext *context) { if(!context->mDeferUpdates) { -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(context->eaxNeedsCommit()) { context->mPropsDirty = true; @@ -79,22 +79,26 @@ try { { case AL_GAIN: if(!(value >= 0.0f && std::isfinite(value))) - throw al::context_error{AL_INVALID_VALUE, "Listener gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Listener gain {:f} out of range", value); listener.Gain = value; UpdateProps(context); return; case AL_METERS_PER_UNIT: if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT)) - throw al::context_error{AL_INVALID_VALUE, "Listener meters per unit out of range"}; + context->throw_error(AL_INVALID_VALUE, "Listener meters per unit {:f} out of range", + value); listener.mMetersPerUnit = value; UpdateProps(context); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener float property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC4(void, alListener3f, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) @@ -107,7 +111,7 @@ try { { case AL_POSITION: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) - throw al::context_error{AL_INVALID_VALUE, "Listener position out of range"}; + context->throw_error(AL_INVALID_VALUE, "Listener position out of range"); listener.Position[0] = value1; listener.Position[1] = value2; listener.Position[2] = value3; @@ -116,17 +120,20 @@ try { case AL_VELOCITY: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) - throw al::context_error{AL_INVALID_VALUE, "Listener velocity out of range"}; + context->throw_error(AL_INVALID_VALUE, "Listener velocity out of range"); listener.Velocity[0] = value1; listener.Velocity[1] = value2; listener.Velocity[2] = value3; CommitAndUpdateProps(context); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alListenerfv, ALenum,param, const ALfloat*,values) @@ -134,7 +141,7 @@ FORCE_ALIGN void AL_APIENTRY alListenerfvDirect(ALCcontext *context, ALenum para const ALfloat *values) noexcept try { if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -157,17 +164,20 @@ try { case AL_ORIENTATION: auto vals = al::span{values, 6_uz}; if(!std::all_of(vals.cbegin(), vals.cend(), [](float f) { return std::isfinite(f); })) - return context->setError(AL_INVALID_VALUE, "Listener orientation out of range"); + context->throw_error(AL_INVALID_VALUE, "Listener orientation out of range"); /* AT then UP */ std::copy_n(vals.cbegin(), 3, listener.OrientAt.begin()); std::copy_n(vals.cbegin()+3, 3, listener.OrientUp.begin()); CommitAndUpdateProps(context); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener float-vector property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener float-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -175,10 +185,13 @@ AL_API DECL_FUNC2(void, alListeneri, ALenum,param, ALint,value) FORCE_ALIGN void AL_APIENTRY alListeneriDirect(ALCcontext *context, ALenum param, ALint /*value*/) noexcept try { std::lock_guard proplock{context->mPropLock}; - throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC4(void, alListener3i, ALenum,param, ALint,value1, ALint,value2, ALint,value3) @@ -195,10 +208,13 @@ try { } std::lock_guard proplock{context->mPropLock}; - throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-integer property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alListeneriv, ALenum,param, const ALint*,values) @@ -206,7 +222,7 @@ FORCE_ALIGN void AL_APIENTRY alListenerivDirect(ALCcontext *context, ALenum para const ALint *values) noexcept try { if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); al::span vals; switch(param) @@ -229,11 +245,13 @@ try { } std::lock_guard proplock{context->mPropLock}; - throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer-vector property 0x%x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener integer-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -242,7 +260,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListenerfDirect(ALCcontext *context, ALenum pa ALfloat *value) noexcept try { if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); ALlistener &listener = context->mListener; std::lock_guard proplock{context->mPropLock}; @@ -251,10 +269,13 @@ try { case AL_GAIN: *value = listener.Gain; return; case AL_METERS_PER_UNIT: *value = listener.mMetersPerUnit; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener float property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC4(void, alGetListener3f, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) @@ -262,7 +283,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListener3fDirect(ALCcontext *context, ALenum p ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept try { if(!value1 || !value2 || !value3) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); ALlistener &listener = context->mListener; std::lock_guard proplock{context->mPropLock}; @@ -280,10 +301,13 @@ try { *value3 = listener.Velocity[2]; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alGetListenerfv, ALenum,param, ALfloat*,values) @@ -291,7 +315,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListenerfvDirect(ALCcontext *context, ALenum p ALfloat *values) noexcept try { if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -318,22 +342,28 @@ try { std::copy_n(listener.OrientUp.cbegin(), 3, vals.begin()+3); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener float-vector property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener float-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alGetListeneri, ALenum,param, ALint*,value) FORCE_ALIGN void AL_APIENTRY alGetListeneriDirect(ALCcontext *context, ALenum param, ALint *value) noexcept try { - if(!value) throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); std::lock_guard proplock{context->mPropLock}; - throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC4(void, alGetListener3i, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) @@ -341,7 +371,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListener3iDirect(ALCcontext *context, ALenum p ALint *value1, ALint *value2, ALint *value3) noexcept try { if(!value1 || !value2 || !value3) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); ALlistener &listener = context->mListener; std::lock_guard proplock{context->mPropLock}; @@ -359,10 +389,13 @@ try { *value3 = static_cast(listener.Velocity[2]); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-integer property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alGetListeneriv, ALenum,param, ALint*,values) @@ -370,7 +403,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListenerivDirect(ALCcontext *context, ALenum p ALint *values) noexcept try { if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -394,9 +427,11 @@ try { std::transform(listener.OrientUp.cbegin(), listener.OrientUp.cend(), vals.begin()+3, f2i); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer-vector property 0x%x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener integer-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } diff --git a/Engine/lib/openal-soft/al/source.cpp b/Engine/lib/openal-soft/al/source.cpp index 514d4f7fe..77f8bfac6 100644 --- a/Engine/lib/openal-soft/al/source.cpp +++ b/Engine/lib/openal-soft/al/source.cpp @@ -28,20 +28,19 @@ #include #include #include -#include #include #include #include -#include #include +#include #include #include #include #include #include -#include #include #include +#include #include #include #include @@ -65,17 +64,17 @@ #include "auxeffectslot.h" #include "buffer.h" #include "core/buffer_storage.h" +#include "core/except.h" #include "core/logging.h" #include "core/mixer/defs.h" #include "core/voice_change.h" #include "direct_defs.h" -#include "error.h" #include "filter.h" #include "flexarray.h" #include "intrusive_ptr.h" #include "opthelpers.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include "eax/api.h" #include "eax/call.h" #include "eax/fx_slot_index.h" @@ -91,6 +90,8 @@ using source_store_array = std::array; using source_store_vector = std::vector; using source_store_variant = std::variant; +using namespace std::string_view_literals; + Voice *GetSourceVoice(ALsource *source, ALCcontext *context) { @@ -133,7 +134,7 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context props->RefDistance = source->RefDistance; props->MaxDistance = source->MaxDistance; props->RolloffFactor = source->RolloffFactor -#ifdef ALSOFT_EAX +#if ALSOFT_EAX + source->RolloffFactor2 #endif ; @@ -203,7 +204,7 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context */ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime) { - ALCdevice *device{context->mALDevice.get()}; + auto *device = context->mALDevice.get(); const VoiceBufferItem *Current{}; int64_t readPos{}; uint refcount{}; @@ -243,7 +244,7 @@ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds */ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime) { - ALCdevice *device{context->mALDevice.get()}; + auto *device = context->mALDevice.get(); const VoiceBufferItem *Current{}; int64_t readPos{}; uint refcount{}; @@ -293,7 +294,7 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl template NOINLINE T GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) { - ALCdevice *device{context->mALDevice.get()}; + auto *device = context->mALDevice.get(); const VoiceBufferItem *Current{}; int64_t readPos{}; uint readPosFrac{}; @@ -331,51 +332,45 @@ NOINLINE T GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) } ASSUME(BufferFmt != nullptr); - T offset{}; switch(name) { case AL_SEC_OFFSET: if constexpr(std::is_floating_point_v) { - offset = static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; - offset /= static_cast(BufferFmt->mSampleRate); + auto offset = static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; + return offset / static_cast(BufferFmt->mSampleRate); } else { readPos /= BufferFmt->mSampleRate; - offset = static_cast(std::clamp(readPos, std::numeric_limits::min(), + return static_cast(std::clamp(readPos, std::numeric_limits::min(), std::numeric_limits::max())); } - break; case AL_SAMPLE_OFFSET: if constexpr(std::is_floating_point_v) - offset = static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; + return static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; else - offset = static_cast(std::clamp(readPos, std::numeric_limits::min(), + return static_cast(std::clamp(readPos, std::numeric_limits::min(), std::numeric_limits::max())); - break; case AL_BYTE_OFFSET: - const ALuint BlockSamples{BufferFmt->mBlockAlign}; - const ALuint BlockSize{BufferFmt->blockSizeFromFmt()}; /* Round down to the block boundary. */ - readPos = readPos / BlockSamples * BlockSize; + const auto BlockSize = ALuint{BufferFmt->blockSizeFromFmt()}; + readPos = readPos / BufferFmt->mBlockAlign * BlockSize; if constexpr(std::is_floating_point_v) - offset = static_cast(readPos); + return static_cast(readPos); else { if(readPos > std::numeric_limits::max()) - offset = RoundDown(std::numeric_limits::max(), static_cast(BlockSize)); - else if(readPos < std::numeric_limits::min()) - offset = RoundUp(std::numeric_limits::min(), static_cast(BlockSize)); - else - offset = static_cast(readPos); + return RoundDown(std::numeric_limits::max(), static_cast(BlockSize)); + if(readPos < std::numeric_limits::min()) + return RoundUp(std::numeric_limits::min(), static_cast(BlockSize)); + return static_cast(readPos); } - break; } - return offset; + return T{0}; } /* GetSourceLength @@ -394,10 +389,9 @@ NOINLINE T GetSourceLength(const ALsource *source, ALenum name) BufferFmt = listitem.mBuffer; length += listitem.mSampleLen; } - if(length == 0) + if(length == 0 || !BufferFmt) return T{0}; - ASSUME(BufferFmt != nullptr); switch(name) { case AL_SEC_LENGTH_SOFT: @@ -414,10 +408,9 @@ NOINLINE T GetSourceLength(const ALsource *source, ALenum name) return static_cast(std::min(length, std::numeric_limits::max())); case AL_BYTE_LENGTH_SOFT: - const ALuint BlockSamples{BufferFmt->mBlockAlign}; - const ALuint BlockSize{BufferFmt->blockSizeFromFmt()}; /* Round down to the block boundary. */ - length = length / BlockSamples * BlockSize; + const auto BlockSize = ALuint{BufferFmt->blockSizeFromFmt()}; + length = length / BufferFmt->mBlockAlign * BlockSize; if constexpr(std::is_floating_point_v) return static_cast(length); @@ -459,44 +452,43 @@ std::optional GetSampleOffset(std::deque &BufferLis return std::nullopt; /* Get sample frame offset */ - int64_t offset{}; - uint frac{}; - double dbloff, dblfrac; - switch(OffsetType) + auto [offset, frac] = std::invoke([OffsetType,Offset,BufferFmt]() -> std::pair { - case AL_SEC_OFFSET: - dblfrac = std::modf(Offset*BufferFmt->mSampleRate, &dbloff); - if(dblfrac < 0.0) + auto dbloff = double{}; + auto dblfrac = double{}; + switch(OffsetType) { - /* If there's a negative fraction, reduce the offset to "floor" it, - * and convert the fraction to a percentage to the next value (e.g. - * -2.75 -> -3 + 0.25). - */ - dbloff -= 1.0; - dblfrac += 1.0; - } - offset = static_cast(dbloff); - frac = static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0)); - break; + case AL_SEC_OFFSET: + dblfrac = std::modf(Offset*BufferFmt->mSampleRate, &dbloff); + 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 + * greater value (e.g. -2.75 -> -2 + -0.75 -> -3 + 0.25). + */ + dbloff -= 1.0; + dblfrac += 1.0; + } + return {static_cast(dbloff), + static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0))}; - case AL_SAMPLE_OFFSET: - dblfrac = std::modf(Offset, &dbloff); - if(dblfrac < 0.0) - { - dbloff -= 1.0; - dblfrac += 1.0; - } - offset = static_cast(dbloff); - frac = static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0)); - break; + case AL_SAMPLE_OFFSET: + dblfrac = std::modf(Offset, &dbloff); + if(dblfrac < 0.0) + { + dbloff -= 1.0; + dblfrac += 1.0; + } + return {static_cast(dbloff), + static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0))}; - case AL_BYTE_OFFSET: - /* Determine the ByteOffset (and ensure it is block aligned) */ - Offset = std::floor(Offset / BufferFmt->blockSizeFromFmt()); - offset = static_cast(Offset) * BufferFmt->mBlockAlign; - frac = 0; - break; - } + case AL_BYTE_OFFSET: + /* Determine the ByteOffset (and ensure it is block aligned) */ + const auto blockoffset = std::floor(Offset / BufferFmt->blockSizeFromFmt()); + return {static_cast(blockoffset) * BufferFmt->mBlockAlign, 0u}; + } + return {0_i64, 0u}; + }); /* Find the bufferlist item this offset belongs to. */ if(offset < 0) @@ -509,17 +501,14 @@ std::optional GetSampleOffset(std::deque &BufferLis if(BufferFmt->mCallback) return std::nullopt; - int64_t totalBufferLen{0}; for(auto &item : BufferList) { - if(totalBufferLen > offset) - break; - if(item.mSampleLen > offset-totalBufferLen) + if(item.mSampleLen > offset) { /* Offset is in this buffer */ - return VoicePos{static_cast(offset-totalBufferLen), frac, &item}; + return VoicePos{static_cast(offset), frac, &item}; } - totalBufferLen += item.mSampleLen; + offset -= item.mSampleLen; } /* Offset is out of range of the queue */ @@ -528,7 +517,7 @@ std::optional GetSampleOffset(std::deque &BufferLis void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, ALCcontext *context, - ALCdevice *device) + al::Device *device) { voice->mLoopBuffer.store(source->Looping ? &source->mQueue.front() : nullptr, std::memory_order_relaxed); @@ -579,7 +568,7 @@ VoiceChange *GetVoiceChanger(ALCcontext *ctx) void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail) { - ALCdevice *device{ctx->mALDevice.get()}; + auto *device = ctx->mALDevice.get(); VoiceChange *oldhead{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)}; while(VoiceChange *next{oldhead->mNext.load(std::memory_order_relaxed)}) @@ -608,8 +597,8 @@ void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail) } -bool SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALCcontext *context, - ALCdevice *device) +auto SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALCcontext *context, + al::Device *device) -> bool { /* First, get a free voice to start at the new offset. */ auto voicelist = context->getVoicesSpan(); @@ -762,7 +751,7 @@ ALsource *AllocSource(ALCcontext *context) noexcept ASSUME(slidx < 64); ALsource *source{al::construct_at(al::to_address(sublist->Sources->begin() + slidx))}; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX source->eaxInitialize(context); #endif // ALSOFT_EAX @@ -815,7 +804,7 @@ inline ALsource *LookupSource(ALCcontext *context, ALuint id) noexcept return al::to_address(sublist.Sources->begin() + slidx); } -auto LookupBuffer = [](ALCdevice *device, auto id) noexcept -> ALbuffer* +auto LookupBuffer = [](al::Device *device, auto id) noexcept -> ALbuffer* { const auto lidx{(id-1) >> 6}; const auto slidx{(id-1) & 0x3f}; @@ -828,7 +817,7 @@ auto LookupBuffer = [](ALCdevice *device, auto id) noexcept -> ALbuffer* return al::to_address(sublist.Buffers->begin() + static_cast(slidx)); }; -auto LookupFilter = [](ALCdevice *device, auto id) noexcept -> ALfilter* +auto LookupFilter = [](al::Device *device, auto id) noexcept -> ALfilter* { const auto lidx{(id-1) >> 6}; const auto slidx{(id-1) & 0x3f}; @@ -892,7 +881,8 @@ ALenum EnumFromSpatializeMode(SpatializeMode mode) case SpatializeMode::On: return AL_TRUE; case SpatializeMode::Auto: return AL_AUTO_SOFT; } - throw std::runtime_error{"Invalid SpatializeMode: "+std::to_string(int(mode))}; + throw std::runtime_error{fmt::format("Invalid SpatializeMode: {}", + int{al::to_underlying(mode)})}; } auto DirectModeFromEnum = [](auto mode) noexcept -> std::optional @@ -913,7 +903,7 @@ ALenum EnumFromDirectMode(DirectMode mode) case DirectMode::DropMismatch: return AL_DROP_UNMATCHED_SOFT; case DirectMode::RemixMismatch: return AL_REMIX_UNMATCHED_SOFT; } - throw std::runtime_error{"Invalid DirectMode: "+std::to_string(int(mode))}; + throw std::runtime_error{fmt::format("Invalid DirectMode: {}", int{al::to_underlying(mode)})}; } auto DistanceModelFromALenum = [](auto model) noexcept -> std::optional @@ -942,7 +932,8 @@ ALenum ALenumFromDistanceModel(DistanceModel model) case DistanceModel::Exponent: return AL_EXPONENT_DISTANCE; case DistanceModel::ExponentClamped: return AL_EXPONENT_DISTANCE_CLAMPED; } - throw std::runtime_error{"Unexpected distance model "+std::to_string(static_cast(model))}; + throw std::runtime_error{fmt::format("Unexpected distance model: {}", + int{al::to_underlying(model)})}; } enum SourceProp : ALenum { @@ -1352,7 +1343,7 @@ void UpdateSourceProps(ALsource *source, ALCcontext *context) } source->mPropsDirty = true; } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) { if(!context->mDeferUpdates) @@ -1376,69 +1367,56 @@ inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) template -struct PropType { }; +auto PropTypeName() -> std::string_view = delete; template<> -struct PropType { static const char *Name() { return "integer"; } }; +auto PropTypeName() -> std::string_view { return "integer"sv; } template<> -struct PropType { static const char *Name() { return "int64"; } }; +auto PropTypeName() -> std::string_view { return "int64"sv; } template<> -struct PropType { static const char *Name() { return "float"; } }; +auto PropTypeName() -> std::string_view { return "float"sv; } template<> -struct PropType { static const char *Name() { return "double"; } }; - -struct HexPrinter { - std::array mStr{}; - - template - HexPrinter(T value) - { - using ST = std::make_signed_t>; - if constexpr(std::is_same_v) - std::snprintf(mStr.data(), mStr.size(), "0x%x", value); - else if constexpr(std::is_same_v) - std::snprintf(mStr.data(), mStr.size(), "0x%lx", value); - else if constexpr(std::is_same_v) - std::snprintf(mStr.data(), mStr.size(), "0x%llx", value); - } - - [[nodiscard]] auto c_str() const noexcept -> const char* { return mStr.data(); } -}; +auto PropTypeName() -> std::string_view { return "double"sv; } /** * Returns a pair of lambdas to check the following setter. * * The first lambda checks the size of the span is valid for the required size, - * setting the proper context error and throwing a check_size_exception if it - * fails. + * throwing a context error 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. + * The second lambda tests the validity of the value check, throwing a context + * error if it failed. */ +template +struct PairStruct { T First; U Second; }; +template +PairStruct(T,U) -> PairStruct; + template -auto GetCheckers(const SourceProp prop, const al::span values) +auto GetCheckers(ALCcontext *context, const SourceProp prop, const al::span values) { - return std::make_pair( + return PairStruct{ [=](size_t expect) -> void { if(values.size() == expect) return; - throw al::context_error{AL_INVALID_ENUM, - "Property 0x%04x expects %zu value(s), got %zu", prop, expect, values.size()}; + context->throw_error(AL_INVALID_ENUM, "Property {:#04x} expects {} value{}, got {}", + as_unsigned(al::to_underlying(prop)), expect, (expect==1) ? "" : "s", + values.size()); }, - [](bool passed) -> void + [context](bool passed) -> void { if(passed) return; - throw al::context_error{AL_INVALID_VALUE, "Value out of range"}; + context->throw_error(AL_INVALID_VALUE, "Value out of range"); } - ); + }; } template NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values) { - auto [CheckSize, CheckValue] = GetCheckers(prop, values); - ALCdevice *device{Context->mALDevice.get()}; + auto [CheckSize, CheckValue] = GetCheckers(Context, prop, values); + auto *device = Context->mALDevice.get(); switch(prop) { @@ -1449,8 +1427,8 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con if constexpr(std::is_integral_v) { /* Query only */ - throw al::context_error{AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop}; + Context->throw_error(AL_INVALID_OPERATION, + "Setting read-only source property {:#04x}", as_unsigned(al::to_underlying(prop))); } break; @@ -1462,12 +1440,15 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con case AL_SAMPLE_OFFSET_CLOCK_SOFT: case AL_SEC_OFFSET_CLOCK_SOFT: /* Query only */ - throw al::context_error{AL_INVALID_OPERATION, "Setting read-only source property 0x%04x", - prop}; + Context->throw_error(AL_INVALID_OPERATION, "Setting read-only source property {:#04x}", + as_unsigned(al::to_underlying(prop))); case AL_PITCH: CheckSize(1); - CheckValue(values[0] >= T{0}); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); Source->Pitch = static_cast(values[0]); return UpdateSourceProps(Source, Context); @@ -1488,42 +1469,60 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con case AL_GAIN: CheckSize(1); - CheckValue(values[0] >= T{0}); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); Source->Gain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_MAX_DISTANCE: CheckSize(1); - CheckValue(values[0] >= T{0}); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); Source->MaxDistance = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_ROLLOFF_FACTOR: CheckSize(1); - CheckValue(values[0] >= T{0}); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); Source->RolloffFactor = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_REFERENCE_DISTANCE: CheckSize(1); - CheckValue(values[0] >= T{0}); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); Source->RefDistance = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_MIN_GAIN: CheckSize(1); - CheckValue(values[0] >= T{0}); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); Source->MinGain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_MAX_GAIN: CheckSize(1); - CheckValue(values[0] >= T{0}); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); Source->MaxGain = static_cast(values[0]); return UpdateSourceProps(Source, Context); @@ -1605,8 +1604,8 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con CheckSize(1); if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; state == AL_PLAYING || state == AL_PAUSED) - throw al::context_error{AL_INVALID_OPERATION, - "Setting buffer on playing or paused source %u", Source->id}; + Context->throw_error(AL_INVALID_OPERATION, + "Setting buffer on playing or paused source {}", Source->id); std::deque oldlist; if(values[0]) @@ -1615,14 +1614,13 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con std::lock_guard buflock{device->BufferLock}; ALbuffer *buffer{LookupBuffer(device, static_cast(values[0]))}; if(!buffer) - throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %s", - std::to_string(values[0]).c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid buffer ID {}", values[0]); if(buffer->MappedAccess && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) - throw al::context_error{AL_INVALID_OPERATION, - "Setting non-persistently mapped buffer %u", buffer->id}; + Context->throw_error(AL_INVALID_OPERATION, + "Setting non-persistently mapped buffer {}", buffer->id); if(buffer->mCallback && buffer->ref.load(std::memory_order_relaxed) != 0) - throw al::context_error{AL_INVALID_OPERATION, - "Setting already-set callback buffer %u", buffer->id}; + Context->throw_error(AL_INVALID_OPERATION, + "Setting already-set callback buffer {}", buffer->id); /* Add the selected buffer to a one-item queue */ std::deque newlist; @@ -1670,7 +1668,7 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con if(Voice *voice{GetSourceVoice(Source, Context)}) { auto vpos = GetSampleOffset(Source->mQueue, prop, static_cast(values[0])); - if(!vpos) throw al::context_error{AL_INVALID_VALUE, "Invalid offset"}; + if(!vpos) Context->throw_error(AL_INVALID_VALUE, "Invalid offset"); if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mALDevice.get())) return; @@ -1685,8 +1683,9 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con if constexpr(std::is_integral_v) { /* Query only */ - throw al::context_error{AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop}; + Context->throw_error(AL_INVALID_OPERATION, + "Setting read-only source property {:#04x}", + as_unsigned(al::to_underlying(prop))); } } break; @@ -1697,8 +1696,9 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con if constexpr(std::is_integral_v) { /* Query only */ - throw al::context_error{AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop}; + Context->throw_error(AL_INVALID_OPERATION, + "Setting read-only source property {:#04x}", + as_unsigned(al::to_underlying(prop))); } break; } @@ -1722,8 +1722,8 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con CheckSize(1); if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; state == AL_PLAYING || state == AL_PAUSED) - throw al::context_error{AL_INVALID_OPERATION, - "Modifying panning enabled on playing or paused source %u", Source->id}; + Context->throw_error(AL_INVALID_OPERATION, + "Modifying panning enabled on playing or paused source {}", Source->id); CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); @@ -1813,8 +1813,7 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con std::lock_guard filterlock{device->FilterLock}; ALfilter *filter{LookupFilter(device, filterid)}; if(!filter) - throw al::context_error{AL_INVALID_VALUE, "Invalid filter ID %s", - std::to_string(filterid).c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid filter ID {}", filterid); Source->Direct.Gain = filter->Gain; Source->Direct.GainHF = filter->GainHF; Source->Direct.HFReference = filter->HFReference; @@ -1875,8 +1874,8 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con Source->DirectChannels = *mode; return UpdateSourceProps(Source, Context); } - throw al::context_error{AL_INVALID_VALUE, "Invalid direct channels mode: %s\n", - HexPrinter{values[0]}.c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid direct channels mode: {:#x}", + as_unsigned(values[0])); } break; @@ -1891,8 +1890,8 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con UpdateSourceProps(Source, Context); return; } - throw al::context_error{AL_INVALID_VALUE, "Invalid distance model: %s\n", - HexPrinter{values[0]}.c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid distance model: {:#x}", + as_unsigned(values[0])); } break; @@ -1900,7 +1899,7 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con if constexpr(std::is_integral_v) { CheckSize(1); - CheckValue(values[0] >= 0 && values[0] <= static_cast(Resampler::Max)); + CheckValue(values[0] >= 0 && values[0] <= al::to_underlying(Resampler::Max)); Source->mResampler = static_cast(values[0]); return UpdateSourceProps(Source, Context); @@ -1916,8 +1915,8 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con Source->mSpatialize = *mode; return UpdateSourceProps(Source, Context); } - throw al::context_error{AL_INVALID_VALUE, "Invalid source spatialize mode: %s\n", - HexPrinter{values[0]}.c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid source spatialize mode: {}", + values[0]); } break; @@ -1927,16 +1926,16 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con CheckSize(1); if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; state == AL_PLAYING || state == AL_PAUSED) - throw al::context_error{AL_INVALID_OPERATION, - "Modifying stereo mode on playing or paused source %u", Source->id}; + Context->throw_error(AL_INVALID_OPERATION, + "Modifying stereo mode on playing or paused source {}", Source->id); if(auto mode = StereoModeFromEnum(values[0])) { Source->mStereoMode = *mode; return; } - throw al::context_error{AL_INVALID_VALUE, "Invalid stereo mode: %s\n", - HexPrinter{values[0]}.c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid stereo mode: {:#x}", + as_unsigned(values[0])); } break; @@ -1954,13 +1953,11 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con { slot = LookupEffectSlot(Context, slotid); if(!slot) - throw al::context_error{AL_INVALID_VALUE, "Invalid effect ID %s", - std::to_string(slotid).c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid effect ID {}", slotid); } if(sendidx >= device->NumAuxSends) - throw al::context_error{AL_INVALID_VALUE, "Invalid send %s", - std::to_string(sendidx).c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid send {}", sendidx); auto &send = Source->Send[static_cast(sendidx)]; if(values[2]) @@ -1968,8 +1965,7 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con std::lock_guard filterlock{device->FilterLock}; ALfilter *filter{LookupFilter(device, filterid)}; if(!filter) - throw al::context_error{AL_INVALID_VALUE, "Invalid filter ID %s", - std::to_string(filterid).c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid filter ID {}", filterid); send.Gain = filter->Gain; send.GainHF = filter->GainHF; @@ -2016,20 +2012,19 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con break; } - ERR("Unexpected %s property: 0x%04x\n", PropType::Name(), prop); - throw al::context_error{AL_INVALID_ENUM, "Invalid source %s property 0x%04x", - PropType::Name(), prop}; + Context->throw_error(AL_INVALID_ENUM, "Invalid source {} property {:#04x}", PropTypeName(), + as_unsigned(al::to_underlying(prop))); } template -auto GetSizeChecker(const SourceProp prop, const al::span values) +auto GetSizeChecker(ALCcontext *context, const SourceProp prop, const al::span values) { return [=](size_t expect) -> void { if(values.size() == expect) LIKELY return; - throw al::context_error{AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu", - prop, expect, values.size()}; + context->throw_error(AL_INVALID_ENUM, "Property {:#04x} expects {} value{}, got {}", + as_unsigned(al::to_underlying(prop)), expect, (expect==1) ? "" : "s", values.size()); }; } @@ -2038,8 +2033,8 @@ NOINLINE void GetProperty(ALsource *const Source, ALCcontext *const Context, con const al::span values) { using std::chrono::duration_cast; - auto CheckSize = GetSizeChecker(prop, values); - ALCdevice *device{Context->mALDevice.get()}; + auto CheckSize = GetSizeChecker(Context, prop, values); + auto *device = Context->mALDevice.get(); switch(prop) { @@ -2328,7 +2323,7 @@ NOINLINE void GetProperty(ALsource *const Source, ALCcontext *const Context, con if constexpr(std::is_integral_v) { CheckSize(1); - ALbufferQueueItem *BufferList{}; + const ALbufferQueueItem *BufferList{}; /* HACK: This query should technically only return the buffer set * on a static source. However, some apps had used it to detect * when a streaming source changed buffers, so report the current @@ -2342,7 +2337,10 @@ NOINLINE void GetProperty(ALsource *const Source, ALCcontext *const Context, con else if(Voice *voice{GetSourceVoice(Source, Context)}) { VoiceBufferItem *Current{voice->mCurrentBuffer.load(std::memory_order_relaxed)}; - BufferList = static_cast(Current); + const auto iter = std::find_if(Source->mQueue.cbegin(), Source->mQueue.cend(), + [Current](const ALbufferQueueItem &item) noexcept -> bool + { return &item == Current; }); + BufferList = (iter != Source->mQueue.cend()) ? al::to_address(iter) : nullptr; } ALbuffer *buffer{BufferList ? BufferList->mBuffer : nullptr}; values[0] = buffer ? static_cast(buffer->id) : T{0}; @@ -2486,16 +2484,15 @@ NOINLINE void GetProperty(ALsource *const Source, ALCcontext *const Context, con break; } - ERR("Unexpected %s query property: 0x%04x\n", PropType::Name(), prop); - throw al::context_error{AL_INVALID_ENUM, "Invalid source %s query property 0x%04x", - PropType::Name(), prop}; + Context->throw_error(AL_INVALID_ENUM, "Invalid source {} query property {:#04x}", + PropTypeName(), as_unsigned(al::to_underlying(prop))); } void StartSources(ALCcontext *const context, const al::span srchandles, const nanoseconds start_time=nanoseconds::min()) { - ALCdevice *device{context->mALDevice.get()}; + auto *device = context->mALDevice.get(); /* If the device is disconnected, and voices stop on disconnect, go right * to stopped. */ @@ -2584,7 +2581,7 @@ void StartSources(ALCcontext *const context, const al::span srchandle cur->mSourceID = source->id; cur->mState = VChangeState::Play; source->state = AL_PLAYING; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(context->hasEax()) source->eaxCommit(); #endif // ALSOFT_EAX @@ -2604,7 +2601,7 @@ void StartSources(ALCcontext *const context, const al::span srchandle default: assert(voice == nullptr); cur->mOldVoice = nullptr; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(context->hasEax()) source->eaxCommit(); #endif // ALSOFT_EAX @@ -2667,24 +2664,27 @@ AL_API DECL_FUNC2(void, alGenSources, ALsizei,n, ALuint*,sources) FORCE_ALIGN void AL_APIENTRY alGenSourcesDirect(ALCcontext *context, ALsizei n, ALuint *sources) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Generating %d sources", n}; + context->throw_error(AL_INVALID_VALUE, "Generating {} sources", n); if(n <= 0) UNLIKELY return; - std::unique_lock srclock{context->mSourceLock}; - ALCdevice *device{context->mALDevice.get()}; + auto srclock = std::unique_lock{context->mSourceLock}; + auto *device = context->mALDevice.get(); const al::span sids{sources, static_cast(n)}; - if(sids.size() > device->SourcesMax-context->mNumSources) - throw al::context_error{AL_OUT_OF_MEMORY, "Exceeding %u source limit (%u + %d)", - device->SourcesMax, context->mNumSources, n}; + if(context->mNumSources > device->SourcesMax + || sids.size() > device->SourcesMax-context->mNumSources) + context->throw_error(AL_OUT_OF_MEMORY, "Exceeding {} source limit ({} + {})", + device->SourcesMax, context->mNumSources, n); if(!EnsureSources(context, sids.size())) - throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d source%s", n, - (n == 1) ? "" : "s"}; + context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} source{}", n, + (n==1) ? "" : "s"); std::generate(sids.begin(), sids.end(), [context]{ return AllocSource(context)->id; }); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteSources, ALsizei,n, const ALuint*,sources) @@ -2692,7 +2692,7 @@ FORCE_ALIGN void AL_APIENTRY alDeleteSourcesDirect(ALCcontext *context, ALsizei const ALuint *sources) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Deleting %d sources", n}; + context->throw_error(AL_INVALID_VALUE, "Deleting {} sources", n); if(n <= 0) UNLIKELY return; std::lock_guard srclock{context->mSourceLock}; @@ -2704,7 +2704,7 @@ try { const al::span sids{sources, static_cast(n)}; auto invsrc = std::find_if_not(sids.begin(), sids.end(), validate_source); if(invsrc != sids.end()) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", *invsrc}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", *invsrc); /* All good. Delete source IDs. */ auto delete_source = [&context](const ALuint sid) -> void @@ -2714,8 +2714,10 @@ try { }; std::for_each(sids.begin(), sids.end(), delete_source); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsSource, ALuint,source) @@ -2736,12 +2738,14 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); SetProperty(Source, context, static_cast(param), {&value, 1u}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alSource3f, ALuint,source, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) @@ -2752,13 +2756,15 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); const std::array fvals{value1, value2, value3}; SetProperty(Source, context, static_cast(param), fvals); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alSourcefv, ALuint,source, ALenum,param, const ALfloat*,values) @@ -2769,15 +2775,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{FloatValsByProp(param)}; SetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -2789,12 +2797,14 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); SetProperty(Source, context, static_cast(param), {&value, 1}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alSource3d,SOFT, ALuint,source, ALenum,param, ALdouble,value1, ALdouble,value2, ALdouble,value3) @@ -2805,13 +2815,15 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); const std::array dvals{value1, value2, value3}; SetProperty(Source, context, static_cast(param), dvals); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alSourcedv,SOFT, ALuint,source, ALenum,param, const ALdouble*,values) @@ -2822,15 +2834,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{DoubleValsByProp(param)}; SetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -2842,12 +2856,14 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); SetProperty(Source, context, static_cast(param), {&value, 1u}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alSource3i, ALuint,buffer, ALenum,param, ALint,value1, ALint,value2, ALint,value3) @@ -2858,13 +2874,15 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); const std::array ivals{value1, value2, value3}; SetProperty(Source, context, static_cast(param), ivals); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alSourceiv, ALuint,source, ALenum,param, const ALint*,values) @@ -2875,15 +2893,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{IntValsByProp(param)}; SetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -2895,12 +2915,14 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); SetProperty(Source, context, static_cast(param), {&value, 1u}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT,value1, ALint64SOFT,value2, ALint64SOFT,value3) @@ -2911,13 +2933,15 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); const std::array i64vals{value1, value2, value3}; SetProperty(Source, context, static_cast(param), i64vals); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alSourcei64v,SOFT, ALuint,source, ALenum,param, const ALint64SOFT*,values) @@ -2928,15 +2952,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{Int64ValsByProp(param)}; SetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -2947,14 +2973,16 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, static_cast(param), al::span{value, 1u}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alGetSource3f, ALuint,source, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) @@ -2964,9 +2992,9 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!(value1 && value2 && value3)) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); std::array fvals{}; GetProperty(Source, context, static_cast(param), fvals); @@ -2974,8 +3002,10 @@ try { *value2 = fvals[1]; *value3 = fvals[2]; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetSourcefv, ALuint,source, ALenum,param, ALfloat*,values) @@ -2985,15 +3015,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{FloatValsByProp(param)}; GetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3004,14 +3036,16 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, static_cast(param), al::span{value, 1u}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alGetSource3d,SOFT, ALuint,source, ALenum,param, ALdouble*,value1, ALdouble*,value2, ALdouble*,value3) @@ -3021,9 +3055,9 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!(value1 && value2 && value3)) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); std::array dvals{}; GetProperty(Source, context, static_cast(param), dvals); @@ -3031,8 +3065,10 @@ try { *value2 = dvals[1]; *value3 = dvals[2]; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alGetSourcedv,SOFT, ALuint,source, ALenum,param, ALdouble*,values) @@ -3042,15 +3078,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{DoubleValsByProp(param)}; GetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3061,14 +3099,16 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, static_cast(param), al::span{value, 1u}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alGetSource3i, ALuint,source, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) @@ -3078,18 +3118,20 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!(value1 && value2 && value3)) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; - + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); + std::array ivals{}; GetProperty(Source, context, static_cast(param), ivals); *value1 = ivals[0]; *value2 = ivals[1]; *value3 = ivals[2]; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetSourceiv, ALuint,source, ALenum,param, ALint*,values) @@ -3099,15 +3141,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{IntValsByProp(param)}; GetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3117,14 +3161,16 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, static_cast(param), al::span{value, 1u}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alGetSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,value1, ALint64SOFT*,value2, ALint64SOFT*,value3) @@ -3134,9 +3180,9 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!(value1 && value2 && value3)) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); std::array i64vals{}; GetProperty(Source, context, static_cast(param), i64vals); @@ -3144,8 +3190,10 @@ try { *value2 = i64vals[1]; *value3 = i64vals[2]; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alGetSourcei64v,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,values) @@ -3155,15 +3203,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{Int64ValsByProp(param)}; GetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3173,12 +3223,14 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); StartSources(context, {&Source, 1}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT2(void, alSourcePlayAtTime,SOFT, ALuint,source, ALint64SOFT,start_time) @@ -3186,17 +3238,19 @@ FORCE_ALIGN void AL_APIENTRY alSourcePlayAtTimeDirectSOFT(ALCcontext *context, A ALint64SOFT start_time) noexcept try { if(start_time < 0) - throw al::context_error{AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time}; + context->throw_error(AL_INVALID_VALUE, "Invalid time point {}", start_time); std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); StartSources(context, {&Source, 1}, nanoseconds{start_time}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alSourcePlayv, ALsizei,n, const ALuint*,sources) @@ -3204,7 +3258,7 @@ FORCE_ALIGN void AL_APIENTRY alSourcePlayvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Playing %d sources", n}; + context->throw_error(AL_INVALID_VALUE, "Playing {} sources", n); if(n <= 0) UNLIKELY return; al::span sids{sources, static_cast(n)}; @@ -3221,14 +3275,16 @@ try { { if(ALsource *src{LookupSource(context, sid)}) return src; - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); }; std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); StartSources(context, srchandles); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT3(void, alSourcePlayAtTimev,SOFT, ALsizei,n, const ALuint*,sources, ALint64SOFT,start_time) @@ -3236,11 +3292,11 @@ FORCE_ALIGN void AL_APIENTRY alSourcePlayAtTimevDirectSOFT(ALCcontext *context, const ALuint *sources, ALint64SOFT start_time) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Playing %d sources", n}; + context->throw_error(AL_INVALID_VALUE, "Playing {} sources", n); if(n <= 0) UNLIKELY return; if(start_time < 0) - throw al::context_error{AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time}; + context->throw_error(AL_INVALID_VALUE, "Invalid time point {}", start_time); al::span sids{sources, static_cast(n)}; source_store_variant source_store; @@ -3256,14 +3312,16 @@ try { { if(ALsource *src{LookupSource(context, sid)}) return src; - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); }; std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); StartSources(context, srchandles, nanoseconds{start_time}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3276,7 +3334,7 @@ FORCE_ALIGN void AL_APIENTRY alSourcePausevDirect(ALCcontext *context, ALsizei n const ALuint *sources) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Pausing %d sources", n}; + context->throw_error(AL_INVALID_VALUE, "Pausing {} sources", n); if(n <= 0) UNLIKELY return; al::span sids{sources, static_cast(n)}; @@ -3293,7 +3351,7 @@ try { { if(ALsource *src{LookupSource(context, sid)}) return src; - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); }; std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); @@ -3335,8 +3393,10 @@ try { } } } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3349,7 +3409,7 @@ FORCE_ALIGN void AL_APIENTRY alSourceStopvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Stopping %d sources", n}; + context->throw_error(AL_INVALID_VALUE, "Stopping {} sources", n); if(n <= 0) UNLIKELY return; al::span sids{sources, static_cast(n)}; @@ -3366,7 +3426,7 @@ try { { if(ALsource *src{LookupSource(context, sid)}) return src; - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); }; std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); @@ -3395,8 +3455,10 @@ try { if(tail) LIKELY SendVoiceChanges(context, tail); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3409,7 +3471,7 @@ FORCE_ALIGN void AL_APIENTRY alSourceRewindvDirect(ALCcontext *context, ALsizei const ALuint *sources) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Rewinding %d sources", n}; + context->throw_error(AL_INVALID_VALUE, "Rewinding {} sources", n); if(n <= 0) UNLIKELY return; al::span sids{sources, static_cast(n)}; @@ -3426,7 +3488,7 @@ try { { if(ALsource *src{LookupSource(context, sid)}) return src; - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); }; std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); @@ -3457,8 +3519,10 @@ try { if(tail) LIKELY SendVoiceChanges(context, tail); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3467,20 +3531,20 @@ FORCE_ALIGN void AL_APIENTRY alSourceQueueBuffersDirect(ALCcontext *context, ALu ALsizei nb, const ALuint *buffers) noexcept try { if(nb < 0) - throw al::context_error{AL_INVALID_VALUE, "Queueing %d buffers", nb}; + context->throw_error(AL_INVALID_VALUE, "Queueing {} buffers", nb); if(nb <= 0) UNLIKELY return; std::lock_guard sourcelock{context->mSourceLock}; ALsource *source{LookupSource(context,src)}; if(!source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", src}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", src); /* Can't queue on a Static Source */ if(source->SourceType == AL_STATIC) - throw al::context_error{AL_INVALID_OPERATION, "Queueing onto static source %u", src}; + context->throw_error(AL_INVALID_OPERATION, "Queueing onto static source {}", src); /* Check for a valid Buffer, for its frequency and format */ - ALCdevice *device{context->mALDevice.get()}; + auto *device = context->mALDevice.get(); ALbuffer *BufferFmt{nullptr}; for(auto &item : source->mQueue) { @@ -3493,26 +3557,25 @@ try { const size_t NewListStart{source->mQueue.size()}; try { ALbufferQueueItem *BufferList{nullptr}; - std::for_each(bids.cbegin(), bids.cend(), - [source,device,&BufferFmt,&BufferList](const ALuint bid) + auto append_buffer = [context,source,device,&BufferFmt,&BufferList](const ALuint bid) { ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr}; if(bid && !buffer) - throw al::context_error{AL_INVALID_NAME, "Queueing invalid buffer ID %u", bid}; + context->throw_error(AL_INVALID_NAME, "Queueing invalid buffer ID {}", bid); if(buffer) { if(buffer->mSampleRate < 1) - throw al::context_error{AL_INVALID_OPERATION, - "Queueing buffer %u with no format", buffer->id}; + context->throw_error(AL_INVALID_OPERATION, + "Queueing buffer {} with no format", buffer->id); if(buffer->mCallback) - throw al::context_error{AL_INVALID_OPERATION, "Queueing callback buffer %u", - buffer->id}; + context->throw_error(AL_INVALID_OPERATION, "Queueing callback buffer {}", + buffer->id); if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) - throw al::context_error{AL_INVALID_OPERATION, - "Queueing non-persistently mapped buffer %u", buffer->id}; + context->throw_error(AL_INVALID_OPERATION, + "Queueing non-persistently mapped buffer {}", buffer->id); } source->mQueue.emplace_back(); @@ -3532,11 +3595,11 @@ try { BufferList->mBuffer = buffer; IncrementRef(buffer->ref); - bool fmt_mismatch{false}; if(BufferFmt == nullptr) BufferFmt = buffer; else { + auto fmt_mismatch = false; fmt_mismatch |= BufferFmt->mSampleRate != buffer->mSampleRate; fmt_mismatch |= BufferFmt->mChannels != buffer->mChannels; fmt_mismatch |= BufferFmt->mType != buffer->mType; @@ -3546,15 +3609,16 @@ try { fmt_mismatch |= BufferFmt->mAmbiScaling != buffer->mAmbiScaling; } fmt_mismatch |= BufferFmt->mAmbiOrder != buffer->mAmbiOrder; + if(fmt_mismatch) + context->throw_error(AL_INVALID_OPERATION, + "Queueing buffer with mismatched format\n" + " Expected: {}hz, {}, {} ; Got: {}hz, {}, {}\n", BufferFmt->mSampleRate, + NameFromFormat(BufferFmt->mType), NameFromFormat(BufferFmt->mChannels), + buffer->mSampleRate, NameFromFormat(buffer->mType), + NameFromFormat(buffer->mChannels)); } - if(fmt_mismatch) - throw al::context_error{AL_INVALID_OPERATION, - "Queueing buffer with mismatched format\n" - " Expected: %uhz, %s, %s ; Got: %uhz, %s, %s\n", BufferFmt->mSampleRate, - NameFromFormat(BufferFmt->mType), NameFromFormat(BufferFmt->mChannels), - buffer->mSampleRate, NameFromFormat(buffer->mType), - NameFromFormat(buffer->mChannels)}; - }); + }; + std::for_each(bids.cbegin(), bids.cend(), append_buffer); } catch(...) { /* A buffer failed (invalid ID or format), or there was some other @@ -3581,8 +3645,10 @@ try { (iter-1)->mNext.store(al::to_address(iter), std::memory_order_release); } } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alSourceUnqueueBuffers, ALuint,source, ALsizei,nb, ALuint*,buffers) @@ -3590,18 +3656,18 @@ FORCE_ALIGN void AL_APIENTRY alSourceUnqueueBuffersDirect(ALCcontext *context, A ALsizei nb, ALuint *buffers) noexcept try { if(nb < 0) - throw al::context_error{AL_INVALID_VALUE, "Unqueueing %d buffers", nb}; + context->throw_error(AL_INVALID_VALUE, "Unqueueing {} buffers", nb); if(nb <= 0) UNLIKELY return; std::lock_guard sourcelock{context->mSourceLock}; ALsource *source{LookupSource(context,src)}; if(!source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", src}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", src); if(source->SourceType != AL_STREAMING) - throw al::context_error{AL_INVALID_VALUE, "Unqueueing from a non-streaming source %u",src}; + context->throw_error(AL_INVALID_VALUE, "Unqueueing from a non-streaming source {}", src); if(source->Looping) - throw al::context_error{AL_INVALID_VALUE, "Unqueueing from looping source %u", src}; + context->throw_error(AL_INVALID_VALUE, "Unqueueing from looping source {}", src); /* Make sure enough buffers have been processed to unqueue. */ const al::span bids{buffers, static_cast(nb)}; @@ -3619,8 +3685,8 @@ try { } } if(processed < bids.size()) - throw al::context_error{AL_INVALID_VALUE, "Unqueueing %d buffer%s (only %zu processed)", - nb, (nb==1)?"":"s", processed}; + context->throw_error(AL_INVALID_VALUE, "Unqueueing {} buffer{} (only {} processed)", + nb, (nb==1) ? "" : "s", processed); std::generate(bids.begin(), bids.end(), [source]() noexcept -> ALuint { @@ -3635,8 +3701,10 @@ try { return bid; }); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3704,7 +3772,7 @@ void ALsource::SetName(ALCcontext *context, ALuint id, std::string_view name) auto source = LookupSource(context, id); if(!source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", id}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", id); context->mSourceNames.insert_or_assign(id, name); } @@ -3728,7 +3796,7 @@ SourceSubList::~SourceSubList() } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX void ALsource::eaxInitialize(ALCcontext *context) noexcept { assert(context != nullptr); @@ -3742,11 +3810,6 @@ void ALsource::eaxInitialize(ALCcontext *context) noexcept mEaxChanged = true; } -void ALsource::eaxDispatch(const EaxCall& call) -{ - call.is_get() ? eax_get(call) : eax_set(call); -} - ALsource* ALsource::EaxLookupSource(ALCcontext& al_context, ALuint source_id) noexcept { return LookupSource(&al_context, source_id); @@ -3794,7 +3857,7 @@ void ALsource::eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) } } -void ALsource::eax1_set_defaults(Eax1Props& props) noexcept +void ALsource::eax1_set_defaults(EAXBUFFER_REVERBPROPERTIES& props) noexcept { props.fMix = EAX_REVERBMIX_USEDISTANCE; } @@ -3805,7 +3868,7 @@ void ALsource::eax1_set_defaults() noexcept mEax1.d = mEax1.i; } -void ALsource::eax2_set_defaults(Eax2Props& props) noexcept +void ALsource::eax2_set_defaults(EAX20BUFFERPROPERTIES& props) noexcept { props.lDirect = EAXSOURCE_DEFAULTDIRECT; props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF; @@ -3828,7 +3891,7 @@ void ALsource::eax2_set_defaults() noexcept mEax2.d = mEax2.i; } -void ALsource::eax3_set_defaults(Eax3Props& props) noexcept +void ALsource::eax3_set_defaults(EAX30SOURCEPROPERTIES& props) noexcept { props.lDirect = EAXSOURCE_DEFAULTDIRECT; props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF; @@ -3876,7 +3939,7 @@ void ALsource::eax4_set_defaults() noexcept void ALsource::eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept { - eax3_set_defaults(static_cast(props)); + eax3_set_defaults(static_cast(props)); props.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; } @@ -3923,7 +3986,7 @@ void ALsource::eax_set_defaults() noexcept eax5_set_defaults(); } -void ALsource::eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept +void ALsource::eax1_translate(const EAXBUFFER_REVERBPROPERTIES& src, Eax5Props& dst) noexcept { eax5_set_defaults(dst); @@ -3940,7 +4003,7 @@ void ALsource::eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept } } -void ALsource::eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept +void ALsource::eax2_translate(const EAX20BUFFERPROPERTIES& src, Eax5Props& dst) noexcept { // Source. // @@ -3971,11 +4034,11 @@ void ALsource::eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept eax5_set_speaker_levels_defaults(dst.speaker_levels); } -void ALsource::eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept +void ALsource::eax3_translate(const EAX30SOURCEPROPERTIES& src, Eax5Props& dst) noexcept { // Source. // - static_cast(dst.source) = src; + static_cast(dst.source) = src; dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; // Set everything else to defaults. @@ -3989,7 +4052,7 @@ void ALsource::eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept { // Source. // - static_cast(dst.source) = src.source; + static_cast(dst.source) = src.source; dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; // Sends. @@ -4001,32 +4064,30 @@ void ALsource::eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept // Active FX slots. // - for(size_t i{0};i < EAX50_MAX_ACTIVE_FXSLOTS;++i) + auto translate_slotid = [](const GUID &src_id) -> GUID { - auto& dst_id = dst.active_fx_slots.guidActiveFXSlots[i]; + if(src_id == EAX_NULL_GUID) + return EAX_NULL_GUID; + if(src_id == EAX_PrimaryFXSlotID) + return EAX_PrimaryFXSlotID; + if(src_id == EAXPROPERTYID_EAX40_FXSlot0) + return EAXPROPERTYID_EAX50_FXSlot0; + if(src_id == EAXPROPERTYID_EAX40_FXSlot1) + return EAXPROPERTYID_EAX50_FXSlot1; + if(src_id == EAXPROPERTYID_EAX40_FXSlot2) + return EAXPROPERTYID_EAX50_FXSlot2; + if(src_id == EAXPROPERTYID_EAX40_FXSlot3) + return EAXPROPERTYID_EAX50_FXSlot3; - if(i < EAX40_MAX_ACTIVE_FXSLOTS) - { - const auto& src_id = src.active_fx_slots.guidActiveFXSlots[i]; - - if(src_id == EAX_NULL_GUID) - dst_id = EAX_NULL_GUID; - else if(src_id == EAX_PrimaryFXSlotID) - dst_id = EAX_PrimaryFXSlotID; - else if(src_id == EAXPROPERTYID_EAX40_FXSlot0) - dst_id = EAXPROPERTYID_EAX50_FXSlot0; - else if(src_id == EAXPROPERTYID_EAX40_FXSlot1) - dst_id = EAXPROPERTYID_EAX50_FXSlot1; - else if(src_id == EAXPROPERTYID_EAX40_FXSlot2) - dst_id = EAXPROPERTYID_EAX50_FXSlot2; - else if(src_id == EAXPROPERTYID_EAX40_FXSlot3) - dst_id = EAXPROPERTYID_EAX50_FXSlot3; - else - assert(false && "Unknown active FX slot ID."); - } - else - dst_id = EAX_NULL_GUID; - } + UNLIKELY + ERR("Unexpected active FX slot ID"); + return EAX_NULL_GUID; + }; + const auto src_slots = al::span{src.active_fx_slots.guidActiveFXSlots}; + const auto dst_slots = al::span{dst.active_fx_slots.guidActiveFXSlots}; + auto dstiter = std::transform(src_slots.cbegin(), src_slots.cend(), dst_slots.begin(), + translate_slotid); + std::fill(dstiter, dst_slots.end(), EAX_NULL_GUID); // Speaker levels. // @@ -4047,60 +4108,55 @@ float ALsource::eax_calculate_dst_occlusion_mb( EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept { - auto gain_mb = - static_cast(mEax.source.lDirect) + - (static_cast(mEax.source.lObstruction) * mEax.source.flObstructionLFRatio) + - eax_calculate_dst_occlusion_mb( - mEax.source.lOcclusion, - mEax.source.flOcclusionDirectRatio, - mEax.source.flOcclusionLFRatio); + const auto &source = mEax.source; - const auto has_source_occlusion = (mEax.source.lOcclusion != 0); - - auto gain_hf_mb = - static_cast(mEax.source.lDirectHF) + - static_cast(mEax.source.lObstruction); + auto gain_mb = static_cast(source.lObstruction) * source.flObstructionLFRatio; + auto gainhf_mb = static_cast(source.lObstruction); for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) { if(!mEaxActiveFxSlots[i]) continue; - if(has_source_occlusion) + if(source.lOcclusion != 0) { const auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i); const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot(); - const auto is_environmental_fx = ((fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0); + const auto is_environmental_fx = ((fx_slot_eax.ulFlags&EAXFXSLOTFLAGS_ENVIRONMENT) != 0); const auto is_primary = (mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index()); - const auto is_listener_environment = (is_environmental_fx && is_primary); - if(is_listener_environment) + if(is_environmental_fx && is_primary) { - gain_mb += eax_calculate_dst_occlusion_mb( - mEax.source.lOcclusion, - mEax.source.flOcclusionDirectRatio, - mEax.source.flOcclusionLFRatio); + gain_mb += eax_calculate_dst_occlusion_mb(source.lOcclusion, + source.flOcclusionDirectRatio, source.flOcclusionLFRatio); - gain_hf_mb += static_cast(mEax.source.lOcclusion) * mEax.source.flOcclusionDirectRatio; + gainhf_mb += static_cast(source.lOcclusion) * source.flOcclusionDirectRatio; } } const auto& send = mEax.sends[i]; - if(send.lOcclusion != 0) { - gain_mb += eax_calculate_dst_occlusion_mb( - send.lOcclusion, - send.flOcclusionDirectRatio, + gain_mb += eax_calculate_dst_occlusion_mb(send.lOcclusion, send.flOcclusionDirectRatio, send.flOcclusionLFRatio); - gain_hf_mb += static_cast(send.lOcclusion) * send.flOcclusionDirectRatio; + gainhf_mb += static_cast(send.lOcclusion) * send.flOcclusionDirectRatio; } } - return EaxAlLowPassParam{ - level_mb_to_gain(gain_mb), - std::min(level_mb_to_gain(gain_hf_mb), 1.0f)}; + /* gainhf_mb is the absolute mBFS of the filter's high-frequency volume, + * and gain_mb is the absolute mBFS of the filter's low-frequency volume. + * Adjust the HF volume to be relative to the LF volume, to make the + * appropriate main and relative HF filter volumes. + * + * Also add the Direct and DirectHF properties to the filter, which are + * already the main and relative HF volumes. + */ + gainhf_mb -= gain_mb - static_cast(source.lDirectHF); + gain_mb += static_cast(source.lDirect); + + return EaxAlLowPassParam{level_mb_to_gain(gain_mb), + std::min(level_mb_to_gain(gainhf_mb), 1.0f)}; } EaxAlLowPassParam ALsource::eax_create_room_filter_param( @@ -4108,42 +4164,40 @@ EaxAlLowPassParam ALsource::eax_create_room_filter_param( const EAXSOURCEALLSENDPROPERTIES& send) const noexcept { const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot(); - const auto is_environmental_fx = ((fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0); - const auto is_primary = (mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index()); - const auto is_listener_environment = (is_environmental_fx && is_primary); + const auto is_environmental_fx = bool{(fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0}; + const auto is_primary = bool{mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index()}; - const auto gain_mb = - (static_cast(fx_slot_eax.lOcclusion) * fx_slot_eax.flOcclusionLFRatio) + - static_cast((is_environmental_fx ? mEax.source.lRoom : 0) + send.lSend) + - (is_listener_environment ? - eax_calculate_dst_occlusion_mb( - mEax.source.lOcclusion, - mEax.source.flOcclusionRoomRatio, - mEax.source.flOcclusionLFRatio) : - 0.0f) + - eax_calculate_dst_occlusion_mb( - send.lOcclusion, - send.flOcclusionRoomRatio, - send.flOcclusionLFRatio) + - (is_listener_environment ? - (static_cast(mEax.source.lExclusion) * mEax.source.flExclusionLFRatio) : - 0.0f) + - (static_cast(send.lExclusion) * send.flExclusionLFRatio); + auto gain_mb = (static_cast(fx_slot_eax.lOcclusion) * fx_slot_eax.flOcclusionLFRatio) + + eax_calculate_dst_occlusion_mb(send.lOcclusion, send.flOcclusionRoomRatio, + send.flOcclusionLFRatio) + + (static_cast(send.lExclusion) * send.flExclusionLFRatio); - const auto gain_hf_mb = - static_cast(fx_slot_eax.lOcclusion) + - static_cast((is_environmental_fx ? mEax.source.lRoomHF : 0) + send.lSendHF) + - (is_listener_environment ? - ((static_cast(mEax.source.lOcclusion) * mEax.source.flOcclusionRoomRatio)) : - 0.0f) + - (static_cast(send.lOcclusion) * send.flOcclusionRoomRatio) + - (is_listener_environment ? - static_cast(mEax.source.lExclusion + send.lExclusion) : - 0.0f); + auto gainhf_mb = static_cast(fx_slot_eax.lOcclusion) + + (static_cast(send.lOcclusion) * send.flOcclusionRoomRatio); - return EaxAlLowPassParam{ - level_mb_to_gain(gain_mb), - std::min(level_mb_to_gain(gain_hf_mb), 1.0f)}; + if(is_environmental_fx && is_primary) + { + const auto &source = mEax.source; + + gain_mb += eax_calculate_dst_occlusion_mb(source.lOcclusion, source.flOcclusionRoomRatio, + source.flOcclusionLFRatio); + gain_mb += static_cast(source.lExclusion) * source.flExclusionLFRatio; + + gainhf_mb += static_cast(source.lOcclusion) * source.flOcclusionRoomRatio; + gainhf_mb += static_cast(source.lExclusion + send.lExclusion); + } + + gainhf_mb -= gain_mb - static_cast(send.lSendHF); + gain_mb += static_cast(send.lSend); + if(is_environmental_fx) + { + const auto &source = mEax.source; + gain_mb += static_cast(source.lRoom); + gainhf_mb += static_cast(source.lRoomHF); + } + + return EaxAlLowPassParam{level_mb_to_gain(gain_mb), + std::min(level_mb_to_gain(gainhf_mb), 1.0f)}; } void ALsource::eax_update_direct_filter() @@ -4214,7 +4268,7 @@ void ALsource::eax_set_efx_wet_gain_hf_auto() WetGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMHFAUTO) != 0); } -void ALsource::eax1_set(const EaxCall& call, Eax1Props& props) +void ALsource::eax1_set(const EaxCall& call, EAXBUFFER_REVERBPROPERTIES& props) { switch (call.get_property_id()) { case DSPROPERTY_EAXBUFFER_ALL: @@ -4230,7 +4284,7 @@ void ALsource::eax1_set(const EaxCall& call, Eax1Props& props) } } -void ALsource::eax2_set(const EaxCall& call, Eax2Props& props) +void ALsource::eax2_set(const EaxCall& call, EAX20BUFFERPROPERTIES& props) { switch (call.get_property_id()) { case DSPROPERTY_EAX20BUFFER_NONE: @@ -4297,7 +4351,7 @@ void ALsource::eax2_set(const EaxCall& call, Eax2Props& props) } } -void ALsource::eax3_set(const EaxCall& call, Eax3Props& props) +void ALsource::eax3_set(const EaxCall& call, EAX30SOURCEPROPERTIES& props) { switch (call.get_property_id()) { case EAXSOURCE_NONE: @@ -4569,7 +4623,7 @@ void ALsource::eax_get_active_fx_slot_id(const EaxCall& call, const al::span(props.lObstruction); call.set_value(subprops); } -void ALsource::eax3_get_occlusion(const EaxCall& call, const Eax3Props& props) +void ALsource::eax3_get_occlusion(const EaxCall& call, const EAX30SOURCEPROPERTIES& props) { const auto& subprops = reinterpret_cast(props.lOcclusion); call.set_value(subprops); } -void ALsource::eax3_get_exclusion(const EaxCall& call, const Eax3Props& props) +void ALsource::eax3_get_exclusion(const EaxCall& call, const EAX30SOURCEPROPERTIES& props) { const auto& subprops = reinterpret_cast(props.lExclusion); call.set_value(subprops); } -void ALsource::eax3_get(const EaxCall& call, const Eax3Props& props) +void ALsource::eax3_get(const EaxCall& call, const EAX30SOURCEPROPERTIES& props) { switch (call.get_property_id()) { case EAXSOURCE_NONE: diff --git a/Engine/lib/openal-soft/al/source.h b/Engine/lib/openal-soft/al/source.h index 9f70bfafd..7d56e3a57 100644 --- a/Engine/lib/openal-soft/al/source.h +++ b/Engine/lib/openal-soft/al/source.h @@ -1,6 +1,8 @@ #ifndef AL_SOURCE_H #define AL_SOURCE_H +#include "config.h" + #include #include #include @@ -20,7 +22,7 @@ #include "core/context.h" #include "core/voice.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include "eax/api.h" #include "eax/call.h" #include "eax/exception.h" @@ -45,12 +47,10 @@ inline bool sBufferSubDataCompat{false}; struct ALbufferQueueItem : public VoiceBufferItem { ALbuffer *mBuffer{nullptr}; - - DISABLE_ALLOC }; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX class EaxSourceException : public EaxException { public: explicit EaxSourceException(const char* message) @@ -71,7 +71,7 @@ struct ALsource { float RefDistance{1.0f}; float MaxDistance{std::numeric_limits::max()}; float RolloffFactor{1.0f}; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX // For EAXSOURCE_ROLLOFFFACTOR, which is distinct from and added to // AL_ROLLOFF_FACTOR float RolloffFactor2{0.0f}; @@ -165,10 +165,10 @@ struct ALsource { DISABLE_ALLOC -#ifdef ALSOFT_EAX +#if ALSOFT_EAX public: void eaxInitialize(ALCcontext *context) noexcept; - void eaxDispatch(const EaxCall& call); + void eaxDispatch(const EaxCall& call) { call.is_get() ? eax_get(call) : eax_set(call); } void eaxCommit(); void eaxMarkAsChanged() noexcept { mEaxChanged = true; } @@ -199,26 +199,23 @@ private: using EaxSpeakerLevels = std::array; using EaxSends = std::array; - using Eax1Props = EAXBUFFER_REVERBPROPERTIES; struct Eax1State { - Eax1Props i; // Immediate. - Eax1Props d; // Deferred. + EAXBUFFER_REVERBPROPERTIES i; // Immediate. + EAXBUFFER_REVERBPROPERTIES d; // Deferred. }; - using Eax2Props = EAX20BUFFERPROPERTIES; struct Eax2State { - Eax2Props i; // Immediate. - Eax2Props d; // Deferred. + EAX20BUFFERPROPERTIES i; // Immediate. + EAX20BUFFERPROPERTIES d; // Deferred. }; - using Eax3Props = EAX30SOURCEPROPERTIES; struct Eax3State { - Eax3Props i; // Immediate. - Eax3Props d; // Deferred. + EAX30SOURCEPROPERTIES i; // Immediate. + EAX30SOURCEPROPERTIES d; // Deferred. }; struct Eax4Props { - Eax3Props source; + EAX30SOURCEPROPERTIES source; EaxSends sends; EAX40ACTIVEFXSLOTS active_fx_slots; }; @@ -490,14 +487,14 @@ private: }; struct Eax1SourceAllValidator { - void operator()(const Eax1Props& props) const + void operator()(const EAXBUFFER_REVERBPROPERTIES& props) const { Eax1SourceReverbMixValidator{}(props.fMix); } }; struct Eax2SourceAllValidator { - void operator()(const Eax2Props& props) const + void operator()(const EAX20BUFFERPROPERTIES& props) const { Eax2SourceDirectValidator{}(props.lDirect); Eax2SourceDirectHfValidator{}(props.lDirectHF); @@ -516,7 +513,7 @@ private: }; struct Eax3SourceAllValidator { - void operator()(const Eax3Props& props) const + void operator()(const EAX30SOURCEPROPERTIES& props) const { Eax2SourceDirectValidator{}(props.lDirect); Eax2SourceDirectHfValidator{}(props.lDirectHF); @@ -823,11 +820,11 @@ private: [[noreturn]] static void eax_fail_unknown_receiving_fx_slot_id(); static void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept; - static void eax1_set_defaults(Eax1Props& props) noexcept; + static void eax1_set_defaults(EAXBUFFER_REVERBPROPERTIES& props) noexcept; void eax1_set_defaults() noexcept; - static void eax2_set_defaults(Eax2Props& props) noexcept; + static void eax2_set_defaults(EAX20BUFFERPROPERTIES& props) noexcept; void eax2_set_defaults() noexcept; - static void eax3_set_defaults(Eax3Props& props) noexcept; + static void eax3_set_defaults(EAX30SOURCEPROPERTIES& props) noexcept; void eax3_set_defaults() noexcept; static void eax4_set_sends_defaults(EaxSends& sends) noexcept; static void eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept; @@ -840,9 +837,9 @@ private: void eax5_set_defaults() noexcept; void eax_set_defaults() noexcept; - static void eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept; - static void eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept; - static void eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept; + static void eax1_translate(const EAXBUFFER_REVERBPROPERTIES& src, Eax5Props& dst) noexcept; + static void eax2_translate(const EAX20BUFFERPROPERTIES& src, Eax5Props& dst) noexcept; + static void eax3_translate(const EAX30SOURCEPROPERTIES& src, Eax5Props& dst) noexcept; static void eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept; static float eax_calculate_dst_occlusion_mb( @@ -907,12 +904,12 @@ private: } static void eax_get_active_fx_slot_id(const EaxCall& call, const al::span src_ids); - static void eax1_get(const EaxCall& call, const Eax1Props& props); - static void eax2_get(const EaxCall& call, const Eax2Props& props); - static void eax3_get_obstruction(const EaxCall& call, const Eax3Props& props); - static void eax3_get_occlusion(const EaxCall& call, const Eax3Props& props); - static void eax3_get_exclusion(const EaxCall& call, const Eax3Props& props); - static void eax3_get(const EaxCall& call, const Eax3Props& props); + static void eax1_get(const EaxCall& call, const EAXBUFFER_REVERBPROPERTIES& props); + static void eax2_get(const EaxCall& call, const EAX20BUFFERPROPERTIES& props); + static void eax3_get_obstruction(const EaxCall& call, const EAX30SOURCEPROPERTIES& props); + static void eax3_get_occlusion(const EaxCall& call, const EAX30SOURCEPROPERTIES& props); + static void eax3_get_exclusion(const EaxCall& call, const EAX30SOURCEPROPERTIES& props); + static void eax3_get(const EaxCall& call, const EAX30SOURCEPROPERTIES& props); void eax4_get(const EaxCall& call, const Eax4Props& props); static void eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props); static void eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props); @@ -1034,9 +1031,9 @@ private: void eax_set_efx_wet_gain_auto(); void eax_set_efx_wet_gain_hf_auto(); - static void eax1_set(const EaxCall& call, Eax1Props& props); - static void eax2_set(const EaxCall& call, Eax2Props& props); - void eax3_set(const EaxCall& call, Eax3Props& props); + static void eax1_set(const EaxCall& call, EAXBUFFER_REVERBPROPERTIES& props); + static void eax2_set(const EaxCall& call, EAX20BUFFERPROPERTIES& props); + void eax3_set(const EaxCall& call, EAX30SOURCEPROPERTIES& props); void eax4_set(const EaxCall& call, Eax4Props& props); static void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props); static void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props); diff --git a/Engine/lib/openal-soft/al/state.cpp b/Engine/lib/openal-soft/al/state.cpp index f0216b22f..e30b7a89e 100644 --- a/Engine/lib/openal-soft/al/state.cpp +++ b/Engine/lib/openal-soft/al/state.cpp @@ -40,6 +40,7 @@ #include "al/listener.h" #include "alc/alu.h" #include "alc/context.h" +#include "alc/device.h" #include "alc/inprogext.h" #include "alnumeric.h" #include "atomic.h" @@ -52,9 +53,7 @@ #include "opthelpers.h" #include "strutils.h" -#ifdef ALSOFT_EAX -#include "alc/device.h" - +#if ALSOFT_EAX #include "eax/globals.h" #include "eax/x_ram.h" #endif // ALSOFT_EAX @@ -62,6 +61,8 @@ namespace { +using ALvoidptr = ALvoid*; + [[nodiscard]] constexpr auto GetVendorString() noexcept { return "OpenAL Community"; } [[nodiscard]] constexpr auto GetVersionString() noexcept { return "1.1 ALSOFT " ALSOFT_VERSION; } [[nodiscard]] constexpr auto GetRendererString() noexcept { return "OpenAL Soft"; } @@ -94,6 +95,10 @@ template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "23rd order Sinc (fast)"; } }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "23rd order Sinc"; } }; +template<> struct ResamplerName +{ static constexpr const ALchar *Get() noexcept { return "47th order Sinc (fast)"; } }; +template<> struct ResamplerName +{ static constexpr const ALchar *Get() noexcept { return "47th order Sinc"; } }; const ALchar *GetResamplerName(const Resampler rtype) { @@ -108,6 +113,8 @@ const ALchar *GetResamplerName(const Resampler rtype) HANDLE_RESAMPLER(Resampler::BSinc12); HANDLE_RESAMPLER(Resampler::FastBSinc24); HANDLE_RESAMPLER(Resampler::BSinc24); + HANDLE_RESAMPLER(Resampler::FastBSinc48); + HANDLE_RESAMPLER(Resampler::BSinc48); } #undef HANDLE_RESAMPLER /* Should never get here. */ @@ -144,24 +151,24 @@ constexpr auto ALenumFromDistanceModel(DistanceModel model) -> ALenum } enum PropertyValue : ALenum { - DopplerFactor = AL_DOPPLER_FACTOR, - DopplerVelocity = AL_DOPPLER_VELOCITY, - DistanceModel = AL_DISTANCE_MODEL, - SpeedOfSound = AL_SPEED_OF_SOUND, - DeferredUpdates = AL_DEFERRED_UPDATES_SOFT, - GainLimit = AL_GAIN_LIMIT_SOFT, - NumResamplers = AL_NUM_RESAMPLERS_SOFT, - DefaultResampler = AL_DEFAULT_RESAMPLER_SOFT, - DebugLoggedMessages = AL_DEBUG_LOGGED_MESSAGES_EXT, - DebugNextLoggedMessageLength = AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT, - MaxDebugMessageLength = AL_MAX_DEBUG_MESSAGE_LENGTH_EXT, - MaxDebugLoggedMessages = AL_MAX_DEBUG_LOGGED_MESSAGES_EXT, - MaxDebugGroupDepth = AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT, - MaxLabelLength = AL_MAX_LABEL_LENGTH_EXT, - ContextFlags = AL_CONTEXT_FLAGS_EXT, -#ifdef ALSOFT_EAX - EaxRamSize = AL_EAX_RAM_SIZE, - EaxRamFree = AL_EAX_RAM_FREE, + DopplerFactorProp = AL_DOPPLER_FACTOR, + DopplerVelocityProp = AL_DOPPLER_VELOCITY, + DistanceModelProp = AL_DISTANCE_MODEL, + SpeedOfSoundProp = AL_SPEED_OF_SOUND, + DeferredUpdatesProp = AL_DEFERRED_UPDATES_SOFT, + GainLimitProp = AL_GAIN_LIMIT_SOFT, + NumResamplersProp = AL_NUM_RESAMPLERS_SOFT, + DefaultResamplerProp = AL_DEFAULT_RESAMPLER_SOFT, + DebugLoggedMessagesProp = AL_DEBUG_LOGGED_MESSAGES_EXT, + DebugNextLoggedMessageLengthProp = AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT, + MaxDebugMessageLengthProp = AL_MAX_DEBUG_MESSAGE_LENGTH_EXT, + MaxDebugLoggedMessagesProp = AL_MAX_DEBUG_LOGGED_MESSAGES_EXT, + MaxDebugGroupDepthProp = AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT, + MaxLabelLengthProp = AL_MAX_LABEL_LENGTH_EXT, + ContextFlagsProp = AL_CONTEXT_FLAGS_EXT, +#if ALSOFT_EAX + EaxRamSizeProp = AL_EAX_RAM_SIZE, + EaxRamFreeProp = AL_EAX_RAM_FREE, #endif }; @@ -259,7 +266,7 @@ void GetValue(ALCcontext *context, ALenum pname, T *values) *values = cast_value(context->mContextFlags.to_ulong()); return; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #define EAX_ERROR "[alGetInteger] EAX not enabled" case AL_EAX_RAM_SIZE: @@ -268,7 +275,7 @@ void GetValue(ALCcontext *context, ALenum pname, T *values) *values = cast_value(eax_x_ram_max_size); return; } - ERR(EAX_ERROR "\n"); + ERR(EAX_ERROR); break; case AL_EAX_RAM_FREE: @@ -279,13 +286,13 @@ void GetValue(ALCcontext *context, ALenum pname, T *values) *values = cast_value(device->eax_x_ram_free_size); return; } - ERR(EAX_ERROR "\n"); + ERR(EAX_ERROR); break; #undef EAX_ERROR #endif // ALSOFT_EAX } - context->setError(AL_INVALID_ENUM, "Invalid context property 0x%04x", pname); + context->setError(AL_INVALID_ENUM, "Invalid context property {:#04x}", as_unsigned(pname)); } @@ -331,7 +338,8 @@ FORCE_ALIGN void AL_APIENTRY alEnableDirect(ALCcontext *context, ALenum capabili context->setError(AL_INVALID_OPERATION, "Re-enabling AL_STOP_SOURCES_ON_DISCONNECT_SOFT not yet supported"); return; } - context->setError(AL_INVALID_VALUE, "Invalid enable property 0x%04x", capability); + context->setError(AL_INVALID_VALUE, "Invalid enable property {:#04x}", + as_unsigned(capability)); } AL_API DECL_FUNC1(void, alDisable, ALenum,capability) @@ -355,7 +363,8 @@ FORCE_ALIGN void AL_APIENTRY alDisableDirect(ALCcontext *context, ALenum capabil context->mStopVoicesOnDisconnect.store(false); return; } - context->setError(AL_INVALID_VALUE, "Invalid disable property 0x%04x", capability); + context->setError(AL_INVALID_VALUE, "Invalid disable property {:#04x}", + as_unsigned(capability)); } AL_API DECL_FUNC1(ALboolean, alIsEnabled, ALenum,capability) @@ -369,14 +378,15 @@ FORCE_ALIGN ALboolean AL_APIENTRY alIsEnabledDirect(ALCcontext *context, ALenum case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: return context->mStopVoicesOnDisconnect.load() ? AL_TRUE : AL_FALSE; } - context->setError(AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability); + context->setError(AL_INVALID_VALUE, "Invalid is enabled property {:#04x}", + as_unsigned(capability)); return AL_FALSE; } #define DECL_GETFUNC(R, Name, Ext) \ -AL_API auto AL_APIENTRY Name##Ext(ALenum pname) noexcept -> R \ +auto AL_APIENTRY Name##Ext(ALenum pname) noexcept -> R \ { \ - R value{}; \ + auto value = R{}; \ auto context = GetContextRef(); \ if(!context) UNLIKELY return value; \ Name##vDirect##Ext(GetContextRef().get(), pname, &value); \ @@ -384,18 +394,19 @@ AL_API auto AL_APIENTRY Name##Ext(ALenum pname) noexcept -> R \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, ALenum pname) noexcept -> R \ { \ - R value{}; \ + auto value = R{}; \ Name##vDirect##Ext(context, pname, &value); \ return value; \ } -DECL_GETFUNC(ALboolean, alGetBoolean,) -DECL_GETFUNC(ALdouble, alGetDouble,) -DECL_GETFUNC(ALfloat, alGetFloat,) -DECL_GETFUNC(ALint, alGetInteger,) +AL_API DECL_GETFUNC(ALboolean, alGetBoolean,) +AL_API DECL_GETFUNC(ALdouble, alGetDouble,) +AL_API DECL_GETFUNC(ALfloat, alGetFloat,) +AL_API DECL_GETFUNC(ALint, alGetInteger,) -DECL_GETFUNC(ALint64SOFT, alGetInteger64,SOFT) -DECL_GETFUNC(ALvoid*, alGetPointer,SOFT) +DECL_GETFUNC(ALvoidptr, alGetPointer,EXT) +AL_API DECL_GETFUNC(ALint64SOFT, alGetInteger64,SOFT) +AL_API DECL_GETFUNC(ALvoidptr, alGetPointer,SOFT) #undef DECL_GETFUNC @@ -442,6 +453,10 @@ FORCE_ALIGN void AL_APIENTRY alGetInteger64vDirectSOFT(ALCcontext *context, ALen AL_API DECL_FUNCEXT2(void, alGetPointerv,SOFT, ALenum,pname, ALvoid**,values) FORCE_ALIGN void AL_APIENTRY alGetPointervDirectSOFT(ALCcontext *context, ALenum pname, ALvoid **values) noexcept +{ return alGetPointervDirectEXT(context, pname, values); } + +FORCE_ALIGN DECL_FUNCEXT2(void, alGetPointerv,EXT, ALenum,pname, ALvoid**,values) +FORCE_ALIGN void AL_APIENTRY alGetPointervDirectEXT(ALCcontext *context, ALenum pname, ALvoid **values) noexcept { if(!values) UNLIKELY return context->setError(AL_INVALID_VALUE, "NULL pointer"); @@ -464,7 +479,8 @@ FORCE_ALIGN void AL_APIENTRY alGetPointervDirectSOFT(ALCcontext *context, ALenum *values = context->mDebugParam; return; } - context->setError(AL_INVALID_ENUM, "Invalid context pointer property 0x%04x", pname); + context->setError(AL_INVALID_ENUM, "Invalid context pointer property {:#04x}", + as_unsigned(pname)); } AL_API DECL_FUNC1(const ALchar*, alGetString, ALenum,pname) @@ -472,9 +488,18 @@ FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringDirect(ALCcontext *context, ALe { switch(pname) { - case AL_VENDOR: return GetVendorString(); - case AL_VERSION: return GetVersionString(); - case AL_RENDERER: return GetRendererString(); + case AL_VENDOR: + if(auto device = context->mALDevice.get(); !device->mVendorOverride.empty()) + return device->mVendorOverride.c_str(); + return GetVendorString(); + case AL_VERSION: + if(auto device = context->mALDevice.get(); !device->mVersionOverride.empty()) + return device->mVersionOverride.c_str(); + return GetVersionString(); + case AL_RENDERER: + if(auto device = context->mALDevice.get(); !device->mRendererOverride.empty()) + return device->mRendererOverride.c_str(); + return GetRendererString(); case AL_EXTENSIONS: return context->mExtensionsString.c_str(); case AL_NO_ERROR: return GetNoErrorString(); case AL_INVALID_NAME: return GetInvalidNameString(); @@ -485,7 +510,7 @@ FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringDirect(ALCcontext *context, ALe case AL_STACK_OVERFLOW_EXT: return GetStackOverflowString(); case AL_STACK_UNDERFLOW_EXT: return GetStackUnderflowString(); } - context->setError(AL_INVALID_VALUE, "Invalid string property 0x%04x", pname); + context->setError(AL_INVALID_VALUE, "Invalid string property {:#04x}", as_unsigned(pname)); return nullptr; } @@ -493,7 +518,7 @@ AL_API DECL_FUNC1(void, alDopplerFactor, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alDopplerFactorDirect(ALCcontext *context, ALfloat value) noexcept { if(!(value >= 0.0f && std::isfinite(value))) - context->setError(AL_INVALID_VALUE, "Doppler factor %f out of range", value); + context->setError(AL_INVALID_VALUE, "Doppler factor {:f} out of range", value); else { std::lock_guard proplock{context->mPropLock}; @@ -506,7 +531,7 @@ AL_API DECL_FUNC1(void, alSpeedOfSound, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alSpeedOfSoundDirect(ALCcontext *context, ALfloat value) noexcept { if(!(value > 0.0f && std::isfinite(value))) - context->setError(AL_INVALID_VALUE, "Speed of sound %f out of range", value); + context->setError(AL_INVALID_VALUE, "Speed of sound {:f} out of range", value); else { std::lock_guard proplock{context->mPropLock}; @@ -526,7 +551,8 @@ FORCE_ALIGN void AL_APIENTRY alDistanceModelDirect(ALCcontext *context, ALenum v UpdateProps(context); } else - context->setError(AL_INVALID_VALUE, "Distance model 0x%04x out of range", value); + context->setError(AL_INVALID_VALUE, "Distance model {:#04x} out of range", + as_unsigned(value)); } @@ -551,12 +577,13 @@ FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringiDirectSOFT(ALCcontext *context switch(pname) { case AL_RESAMPLER_NAME_SOFT: - if(index >= 0 && index <= static_cast(Resampler::Max)) + if(index >= 0 && index <= al::to_underlying(Resampler::Max)) return GetResamplerName(static_cast(index)); - context->setError(AL_INVALID_VALUE, "Resampler name index %d out of range", index); + context->setError(AL_INVALID_VALUE, "Resampler name index {} out of range", index); return nullptr; } - context->setError(AL_INVALID_VALUE, "Invalid string indexed property"); + context->setError(AL_INVALID_VALUE, "Invalid string indexed property {:#04x}", + as_unsigned(pname)); return nullptr; } @@ -567,13 +594,13 @@ AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value) noexcept if(!context) UNLIKELY return; if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY - context->debugMessage(DebugSource::API, DebugType::DeprecatedBehavior, 0, + context->debugMessage(DebugSource::API, DebugType::DeprecatedBehavior, 1, DebugSeverity::Medium, "alDopplerVelocity is deprecated in AL 1.1, use alSpeedOfSound; " "alDopplerVelocity(x) -> alSpeedOfSound(343.3f * x)"); if(!(value >= 0.0f && std::isfinite(value))) - context->setError(AL_INVALID_VALUE, "Doppler velocity %f out of range", value); + context->setError(AL_INVALID_VALUE, "Doppler velocity {:f} out of range", value); else { std::lock_guard proplock{context->mPropLock}; @@ -611,6 +638,9 @@ void UpdateContextProps(ALCcontext *context) props->DopplerFactor = context->mDopplerFactor; props->DopplerVelocity = context->mDopplerVelocity; props->SpeedOfSound = context->mSpeedOfSound; +#if ALSOFT_EAX + props->DistanceFactor = context->eaxGetDistanceFactor(); +#endif props->SourceDistanceModel = context->mSourceDistanceModel; props->mDistanceModel = context->mDistanceModel; diff --git a/Engine/lib/openal-soft/alc/alc.cpp b/Engine/lib/openal-soft/alc/alc.cpp index f87fb2076..b55fce800 100644 --- a/Engine/lib/openal-soft/alc/alc.cpp +++ b/Engine/lib/openal-soft/alc/alc.cpp @@ -19,6 +19,8 @@ */ #include "config.h" +#include "config_backends.h" +#include "config_simd.h" #include "version.h" @@ -34,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -100,6 +101,7 @@ #include "effects/base.h" #include "export_list.h" #include "flexarray.h" +#include "fmt/core.h" #include "inprogext.h" #include "intrusive_ptr.h" #include "opthelpers.h" @@ -108,56 +110,62 @@ #include "backends/base.h" #include "backends/null.h" #include "backends/loopback.h" -#ifdef HAVE_PIPEWIRE +#if HAVE_PIPEWIRE #include "backends/pipewire.h" #endif -#ifdef HAVE_JACK +#if HAVE_JACK #include "backends/jack.h" #endif -#ifdef HAVE_PULSEAUDIO +#if HAVE_PULSEAUDIO #include "backends/pulseaudio.h" #endif -#ifdef HAVE_ALSA +#if HAVE_ALSA #include "backends/alsa.h" #endif -#ifdef HAVE_WASAPI +#if HAVE_WASAPI #include "backends/wasapi.h" #endif -#ifdef HAVE_COREAUDIO +#if HAVE_COREAUDIO #include "backends/coreaudio.h" #endif -#ifdef HAVE_OPENSL +#if HAVE_OPENSL #include "backends/opensl.h" #endif -#ifdef HAVE_OBOE +#if HAVE_OBOE #include "backends/oboe.h" #endif -#ifdef HAVE_SOLARIS +#if HAVE_SOLARIS #include "backends/solaris.h" #endif -#ifdef HAVE_SNDIO -#include "backends/sndio.h" +#if HAVE_SNDIO +#include "backends/sndio.hpp" #endif -#ifdef HAVE_OSS +#if HAVE_OSS #include "backends/oss.h" #endif -#ifdef HAVE_DSOUND +#if HAVE_DSOUND #include "backends/dsound.h" #endif -#ifdef HAVE_WINMM +#if HAVE_WINMM #include "backends/winmm.h" #endif -#ifdef HAVE_PORTAUDIO -#include "backends/portaudio.h" +#if HAVE_PORTAUDIO +#include "backends/portaudio.hpp" #endif -#ifdef HAVE_SDL2 +#if HAVE_SDL3 +#include "backends/sdl3.h" +#endif +#if HAVE_SDL2 #include "backends/sdl2.h" #endif -#ifdef HAVE_WAVE +#if HAVE_OTHERIO +#include "backends/otherio.h" +#endif +#if HAVE_WAVE #include "backends/wave.h" #endif -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include "al/eax/api.h" #include "al/eax/globals.h" #endif @@ -191,6 +199,15 @@ using voidp = void*; using float2 = std::array; +auto gProcessRunning = true; +struct ProcessWatcher { + ProcessWatcher() = default; + ProcessWatcher(const ProcessWatcher&) = delete; + ProcessWatcher& operator=(const ProcessWatcher&) = delete; + ~ProcessWatcher() { gProcessRunning = false; } +}; +ProcessWatcher gProcessWatcher; + /************************************************ * Backends ************************************************/ @@ -200,54 +217,60 @@ struct BackendInfo { }; std::array BackendList{ -#ifdef HAVE_PIPEWIRE +#if HAVE_PIPEWIRE BackendInfo{"pipewire", PipeWireBackendFactory::getFactory}, #endif -#ifdef HAVE_PULSEAUDIO +#if HAVE_PULSEAUDIO BackendInfo{"pulse", PulseBackendFactory::getFactory}, #endif -#ifdef HAVE_WASAPI +#if HAVE_WASAPI BackendInfo{"wasapi", WasapiBackendFactory::getFactory}, #endif -#ifdef HAVE_COREAUDIO +#if HAVE_COREAUDIO BackendInfo{"core", CoreAudioBackendFactory::getFactory}, #endif -#ifdef HAVE_OBOE +#if HAVE_OBOE BackendInfo{"oboe", OboeBackendFactory::getFactory}, #endif -#ifdef HAVE_OPENSL +#if HAVE_OPENSL BackendInfo{"opensl", OSLBackendFactory::getFactory}, #endif -#ifdef HAVE_ALSA +#if HAVE_ALSA BackendInfo{"alsa", AlsaBackendFactory::getFactory}, #endif -#ifdef HAVE_SOLARIS +#if HAVE_SOLARIS BackendInfo{"solaris", SolarisBackendFactory::getFactory}, #endif -#ifdef HAVE_SNDIO +#if HAVE_SNDIO BackendInfo{"sndio", SndIOBackendFactory::getFactory}, #endif -#ifdef HAVE_OSS +#if HAVE_OSS BackendInfo{"oss", OSSBackendFactory::getFactory}, #endif -#ifdef HAVE_JACK - BackendInfo{"jack", JackBackendFactory::getFactory}, -#endif -#ifdef HAVE_DSOUND +#if HAVE_DSOUND BackendInfo{"dsound", DSoundBackendFactory::getFactory}, #endif -#ifdef HAVE_WINMM +#if HAVE_WINMM BackendInfo{"winmm", WinMMBackendFactory::getFactory}, #endif -#ifdef HAVE_PORTAUDIO +#if HAVE_PORTAUDIO BackendInfo{"port", PortBackendFactory::getFactory}, #endif -#ifdef HAVE_SDL2 +#if HAVE_SDL3 + BackendInfo{"sdl3", SDL3BackendFactory::getFactory}, +#endif +#if HAVE_SDL2 BackendInfo{"sdl2", SDL2BackendFactory::getFactory}, #endif +#if HAVE_JACK + BackendInfo{"jack", JackBackendFactory::getFactory}, +#endif +#if HAVE_OTHERIO + BackendInfo{"otherio", OtherIOBackendFactory::getFactory}, +#endif BackendInfo{"null", NullBackendFactory::getFactory}, -#ifdef HAVE_WAVE +#if HAVE_WAVE BackendInfo{"wave", WaveBackendFactory::getFactory}, #endif }; @@ -265,6 +288,12 @@ BackendFactory *CaptureFactory{}; [[nodiscard]] constexpr auto GetDefaultName() noexcept { return "OpenAL Soft\0"; } +#ifdef _WIN32 +[[nodiscard]] constexpr auto GetDevicePrefix() noexcept { return "OpenAL Soft on "sv; } +#else +[[nodiscard]] constexpr auto GetDevicePrefix() noexcept { return std::string_view{}; } +#endif + /************************************************ * Global variables ************************************************/ @@ -299,9 +328,9 @@ constexpr uint DitherRNGSeed{22222u}; /************************************************ * ALC information ************************************************/ -[[nodiscard]] constexpr auto GetNoDeviceExtList() noexcept -> std::string_view +[[nodiscard]] constexpr auto GetNoDeviceExtList() noexcept -> const char* { - return "ALC_ENUMERATE_ALL_EXT " + return "ALC_ENUMERATE_ALL_EXT " "ALC_ENUMERATION_EXT " "ALC_EXT_CAPTURE " "ALC_EXT_direct_context " @@ -310,9 +339,9 @@ constexpr uint DitherRNGSeed{22222u}; "ALC_SOFT_loopback " "ALC_SOFT_loopback_bformat " "ALC_SOFT_reopen_device " - "ALC_SOFT_system_events"sv; + "ALC_SOFT_system_events"; } -[[nodiscard]] constexpr auto GetExtensionList() noexcept -> std::string_view +[[nodiscard]] constexpr auto GetExtensionList() noexcept -> const char* { return "ALC_ENUMERATE_ALL_EXT " "ALC_ENUMERATION_EXT " @@ -331,7 +360,7 @@ constexpr uint DitherRNGSeed{22222u}; "ALC_SOFT_output_mode " "ALC_SOFT_pause_device " "ALC_SOFT_reopen_device " - "ALC_SOFT_system_events"sv; + "ALC_SOFT_system_events"; } constexpr int alcMajorVersion{1}; @@ -341,13 +370,13 @@ constexpr int alcEFXMajorVersion{1}; constexpr int alcEFXMinorVersion{0}; -using DeviceRef = al::intrusive_ptr; +using DeviceRef = al::intrusive_ptr; /************************************************ * Device lists ************************************************/ -std::vector DeviceList; +std::vector DeviceList; std::vector ContextList; std::recursive_mutex ListLock; @@ -374,7 +403,7 @@ void alc_initconfig() else { auto u8name = wstr_to_utf8(*logfile); - ERR("Failed to open log file '%s'\n", u8name.c_str()); + ERR("Failed to open log file '{}'", u8name); } } #else @@ -382,11 +411,11 @@ void alc_initconfig() { FILE *logf{fopen(logfile->c_str(), "wt")}; if(logf) gLogFile = logf; - else ERR("Failed to open log file '%s'\n", logfile->c_str()); + else ERR("Failed to open log file '{}'", *logfile); } #endif - TRACE("Initializing library v%s-%s %s\n", ALSOFT_VERSION, ALSOFT_GIT_COMMIT_HASH, + TRACE("Initializing library v{}-{} {}", ALSOFT_VERSION, ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); { std::string names; @@ -402,7 +431,7 @@ void alc_initconfig() names += backend.name; } } - TRACE("Supported backends: %s\n", names.c_str()); + TRACE("Supported backends: {}", names); } ReadALConfig(); @@ -411,23 +440,23 @@ void alc_initconfig() if(al::case_compare(*suspendmode, "ignore"sv) == 0) { SuspendDefers = false; - TRACE("Selected context suspend behavior, \"ignore\"\n"); + TRACE("Selected context suspend behavior, \"ignore\""); } else - ERR("Unhandled context suspend behavior setting: \"%s\"\n", suspendmode->c_str()); + ERR("Unhandled context suspend behavior setting: \"{}\"", *suspendmode); } int capfilter{0}; -#if defined(HAVE_SSE4_1) +#if HAVE_SSE4_1 capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1; -#elif defined(HAVE_SSE3) +#elif HAVE_SSE3 capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3; -#elif defined(HAVE_SSE2) +#elif HAVE_SSE2 capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2; -#elif defined(HAVE_SSE) +#elif HAVE_SSE capfilter |= CPU_CAP_SSE; #endif -#ifdef HAVE_NEON +#if HAVE_NEON capfilter |= CPU_CAP_NEON; #endif if(auto cpuopt = ConfigValueStr({}, {}, "disable-cpu-exts"sv)) @@ -462,24 +491,24 @@ void alc_initconfig() else if(al::case_compare(entry, "neon"sv) == 0) capfilter &= ~CPU_CAP_NEON; else - WARN("Invalid CPU extension \"%.*s\"\n", al::sizei(entry), entry.data()); + WARN("Invalid CPU extension \"{}\"", entry); } } if(auto cpuopt = GetCPUInfo()) { if(!cpuopt->mVendor.empty() || !cpuopt->mName.empty()) { - TRACE("Vendor ID: \"%s\"\n", cpuopt->mVendor.c_str()); - TRACE("Name: \"%s\"\n", cpuopt->mName.c_str()); + TRACE("Vendor ID: \"{}\"", cpuopt->mVendor); + TRACE("Name: \"{}\"", cpuopt->mName); } const int caps{cpuopt->mCaps}; - TRACE("Extensions:%s%s%s%s%s%s\n", - ((capfilter&CPU_CAP_SSE) ? ((caps&CPU_CAP_SSE) ? " +SSE" : " -SSE") : ""), - ((capfilter&CPU_CAP_SSE2) ? ((caps&CPU_CAP_SSE2) ? " +SSE2" : " -SSE2") : ""), - ((capfilter&CPU_CAP_SSE3) ? ((caps&CPU_CAP_SSE3) ? " +SSE3" : " -SSE3") : ""), - ((capfilter&CPU_CAP_SSE4_1) ? ((caps&CPU_CAP_SSE4_1) ? " +SSE4.1" : " -SSE4.1") : ""), - ((capfilter&CPU_CAP_NEON) ? ((caps&CPU_CAP_NEON) ? " +NEON" : " -NEON") : ""), - ((!capfilter) ? " -none-" : "")); + TRACE("Extensions:{}{}{}{}{}{}", + ((capfilter&CPU_CAP_SSE) ?(caps&CPU_CAP_SSE) ?" +SSE"sv : " -SSE"sv : ""sv), + ((capfilter&CPU_CAP_SSE2) ?(caps&CPU_CAP_SSE2) ?" +SSE2"sv : " -SSE2"sv : ""sv), + ((capfilter&CPU_CAP_SSE3) ?(caps&CPU_CAP_SSE3) ?" +SSE3"sv : " -SSE3"sv : ""sv), + ((capfilter&CPU_CAP_SSE4_1)?(caps&CPU_CAP_SSE4_1)?" +SSE4.1"sv : " -SSE4.1"sv : ""sv), + ((capfilter&CPU_CAP_NEON) ?(caps&CPU_CAP_NEON) ?" +NEON"sv : " -NEON"sv : ""sv), + (!capfilter) ? " -none-"sv : ""sv); CPUCapFlags = caps & capfilter; } @@ -517,7 +546,7 @@ void alc_initconfig() else if(al::case_compare(*uhjfiltopt, "iir"sv) == 0) UhjDecodeQuality = UhjQualityType::IIR; else - WARN("Unsupported uhj/decode-filter: %s\n", uhjfiltopt->c_str()); + WARN("Unsupported uhj/decode-filter: {}", *uhjfiltopt); } if(auto uhjfiltopt = ConfigValueStr({}, "uhj"sv, "encode-filter"sv)) { @@ -528,7 +557,7 @@ void alc_initconfig() else if(al::case_compare(*uhjfiltopt, "iir"sv) == 0) UhjEncodeQuality = UhjQualityType::IIR; else - WARN("Unsupported uhj/encode-filter: %s\n", uhjfiltopt->c_str()); + WARN("Unsupported uhj/encode-filter: {}", *uhjfiltopt); } if(auto traperr = al::getenv("ALSOFT_TRAP_ERROR"); traperr @@ -618,6 +647,20 @@ void alc_initconfig() if(endlist) BackendListEnd = backendlist_cur; } + else + { + /* Exclude the null and wave writer backends from being considered by + * default. This ensures there will be no available devices if none of + * the normal backends are usable, rather than pretending there is a + * device but outputs nowhere. + */ + while(BackendListEnd != BackendList.begin()) + { + --BackendListEnd; + if(BackendListEnd->name == "null"sv) + break; + } + } auto init_backend = [](BackendInfo &backend) -> void { @@ -627,20 +670,20 @@ void alc_initconfig() BackendFactory &factory = backend.getFactory(); if(!factory.init()) { - WARN("Failed to initialize backend \"%s\"\n", backend.name); + WARN("Failed to initialize backend \"{}\"", backend.name); return; } - TRACE("Initialized backend \"%s\"\n", backend.name); + TRACE("Initialized backend \"{}\"", backend.name); if(!PlaybackFactory && factory.querySupport(BackendType::Playback)) { PlaybackFactory = &factory; - TRACE("Added \"%s\" for playback\n", backend.name); + TRACE("Added \"{}\" for playback", backend.name); } if(!CaptureFactory && factory.querySupport(BackendType::Capture)) { CaptureFactory = &factory; - TRACE("Added \"%s\" for capture\n", backend.name); + TRACE("Added \"{}\" for capture", backend.name); } }; std::for_each(BackendList.begin(), BackendListEnd, init_backend); @@ -648,9 +691,9 @@ void alc_initconfig() LoopbackBackendFactory::getFactory().init(); if(!PlaybackFactory) - WARN("No playback backend available!\n"); + WARN("No playback backend available!"); if(!CaptureFactory) - WARN("No capture backend available!\n"); + WARN("No capture backend available!"); if(auto exclopt = ConfigValueStr({}, {}, "excludefx"sv)) { @@ -675,27 +718,36 @@ void alc_initconfig() if(!defrevopt) defrevopt = ConfigValueStr({}, {}, "default-reverb"sv); if(defrevopt) LoadReverbPreset(*defrevopt, &ALCcontext::sDefaultEffect); -#ifdef ALSOFT_EAX +#if ALSOFT_EAX + if(const auto eax_enable_opt = ConfigValueBool({}, "eax", "enable")) { - if(const auto eax_enable_opt = ConfigValueBool({}, "eax", "enable")) + eax_g_is_enabled = *eax_enable_opt; + if(!eax_g_is_enabled) + TRACE("EAX disabled by a configuration."); + } + else + eax_g_is_enabled = true; + + if((DisabledEffects.test(EAXREVERB_EFFECT) || DisabledEffects.test(CHORUS_EFFECT)) + && eax_g_is_enabled) + { + eax_g_is_enabled = false; + TRACE("EAX disabled because {} disabled.", + (DisabledEffects.test(EAXREVERB_EFFECT) && DisabledEffects.test(CHORUS_EFFECT)) + ? "EAXReverb and Chorus are"sv : + DisabledEffects.test(EAXREVERB_EFFECT) ? "EAXReverb is"sv : + DisabledEffects.test(CHORUS_EFFECT) ? "Chorus is"sv : ""sv); + } + + if(eax_g_is_enabled) + { + if(auto optval = al::getenv("ALSOFT_EAX_TRACE_COMMITS")) { - eax_g_is_enabled = *eax_enable_opt; - if(!eax_g_is_enabled) - TRACE("%s\n", "EAX disabled by a configuration."); + EaxTraceCommits = al::case_compare(*optval, "true"sv) == 0 + || strtol(optval->c_str(), nullptr, 0) == 1; } else - eax_g_is_enabled = true; - - if((DisabledEffects.test(EAXREVERB_EFFECT) || DisabledEffects.test(CHORUS_EFFECT)) - && eax_g_is_enabled) - { - eax_g_is_enabled = false; - TRACE("EAX disabled because %s disabled.\n", - (DisabledEffects.test(EAXREVERB_EFFECT) && DisabledEffects.test(CHORUS_EFFECT)) - ? "EAXReverb and Chorus are" : - DisabledEffects.test(EAXREVERB_EFFECT) ? "EAXReverb is" : - DisabledEffects.test(CHORUS_EFFECT) ? "Chorus is" : ""); - } + EaxTraceCommits = GetConfigValueBool({}, "eax"sv, "trace-commits"sv, false); } #endif // ALSOFT_EAX } @@ -719,6 +771,10 @@ void ProbeAllDevicesList() else { alcAllDevicesArray = PlaybackFactory->enumerate(BackendType::Playback); + if(const auto prefix = GetDevicePrefix(); !prefix.empty()) + std::for_each(alcAllDevicesArray.begin(), alcAllDevicesArray.end(), + [prefix](std::string &name) { name.insert(0, prefix); }); + decltype(alcAllDevicesList){}.swap(alcAllDevicesList); if(alcAllDevicesArray.empty()) alcAllDevicesList += '\0'; @@ -739,6 +795,10 @@ void ProbeCaptureDeviceList() else { alcCaptureDeviceArray = CaptureFactory->enumerate(BackendType::Capture); + if(const auto prefix = GetDevicePrefix(); !prefix.empty()) + std::for_each(alcCaptureDeviceArray.begin(), alcCaptureDeviceArray.end(), + [prefix](std::string &name) { name.insert(0, prefix); }); + decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList); if(alcCaptureDeviceArray.empty()) alcCaptureDeviceList += '\0'; @@ -826,7 +886,7 @@ std::optional DevFmtTypeFromEnum(ALCenum type) case ALC_UNSIGNED_INT_SOFT: return DevFmtUInt; case ALC_FLOAT_SOFT: return DevFmtFloat; } - WARN("Unsupported format type: 0x%04x\n", type); + WARN("Unsupported format type: {:#04x}", as_unsigned(type)); return std::nullopt; } ALCenum EnumFromDevFmt(DevFmtType type) @@ -841,7 +901,7 @@ ALCenum EnumFromDevFmt(DevFmtType type) case DevFmtUInt: return ALC_UNSIGNED_INT_SOFT; case DevFmtFloat: return ALC_FLOAT_SOFT; } - throw std::runtime_error{"Invalid DevFmtType: "+std::to_string(int(type))}; + throw std::runtime_error{fmt::format("Invalid DevFmtType: {}", int{al::to_underlying(type)})}; } std::optional DevFmtChannelsFromEnum(ALCenum channels) @@ -856,7 +916,7 @@ std::optional DevFmtChannelsFromEnum(ALCenum channels) case ALC_7POINT1_SOFT: return DevFmtX71; case ALC_BFORMAT3D_SOFT: return DevFmtAmbi3D; } - WARN("Unsupported format channels: 0x%04x\n", channels); + WARN("Unsupported format channels: {:#04x}", as_unsigned(channels)); return std::nullopt; } ALCenum EnumFromDevFmt(DevFmtChannels channels) @@ -875,7 +935,8 @@ ALCenum EnumFromDevFmt(DevFmtChannels channels) case DevFmtX7144: case DevFmtX3D71: break; } - throw std::runtime_error{"Invalid DevFmtChannels: "+std::to_string(int(channels))}; + throw std::runtime_error{fmt::format("Invalid DevFmtChannels: {}", + int{al::to_underlying(channels)})}; } std::optional DevAmbiLayoutFromEnum(ALCenum layout) @@ -885,7 +946,7 @@ std::optional DevAmbiLayoutFromEnum(ALCenum layout) case ALC_FUMA_SOFT: return DevAmbiLayout::FuMa; case ALC_ACN_SOFT: return DevAmbiLayout::ACN; } - WARN("Unsupported ambisonic layout: 0x%04x\n", layout); + WARN("Unsupported ambisonic layout: {:#04x}", as_unsigned(layout)); return std::nullopt; } ALCenum EnumFromDevAmbi(DevAmbiLayout layout) @@ -895,7 +956,8 @@ ALCenum EnumFromDevAmbi(DevAmbiLayout layout) case DevAmbiLayout::FuMa: return ALC_FUMA_SOFT; case DevAmbiLayout::ACN: return ALC_ACN_SOFT; } - throw std::runtime_error{"Invalid DevAmbiLayout: "+std::to_string(int(layout))}; + throw std::runtime_error{fmt::format("Invalid DevAmbiLayout: {}", + int{al::to_underlying(layout)})}; } std::optional DevAmbiScalingFromEnum(ALCenum scaling) @@ -906,7 +968,7 @@ std::optional DevAmbiScalingFromEnum(ALCenum scaling) case ALC_SN3D_SOFT: return DevAmbiScaling::SN3D; case ALC_N3D_SOFT: return DevAmbiScaling::N3D; } - WARN("Unsupported ambisonic scaling: 0x%04x\n", scaling); + WARN("Unsupported ambisonic scaling: {:#04x}", as_unsigned(scaling)); return std::nullopt; } ALCenum EnumFromDevAmbi(DevAmbiScaling scaling) @@ -917,7 +979,8 @@ ALCenum EnumFromDevAmbi(DevAmbiScaling scaling) case DevAmbiScaling::SN3D: return ALC_SN3D_SOFT; case DevAmbiScaling::N3D: return ALC_N3D_SOFT; } - throw std::runtime_error{"Invalid DevAmbiScaling: "+std::to_string(int(scaling))}; + throw std::runtime_error{fmt::format("Invalid DevAmbiScaling: {}", + int{al::to_underlying(scaling)})}; } @@ -977,13 +1040,9 @@ constexpr std::array X71Downmix{ }; -std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const float threshold) +auto CreateDeviceLimiter(const al::Device *device, const float threshold) + -> std::unique_ptr { - static constexpr bool AutoKnee{true}; - static constexpr bool AutoAttack{true}; - static constexpr bool AutoRelease{true}; - static constexpr bool AutoPostGain{true}; - static constexpr bool AutoDeclip{true}; static constexpr float LookAheadTime{0.001f}; static constexpr float HoldTime{0.002f}; static constexpr float PreGainDb{0.0f}; @@ -993,9 +1052,12 @@ std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const f static constexpr float AttackTime{0.02f}; static constexpr float ReleaseTime{0.2f}; - return Compressor::Create(device->RealOut.Buffer.size(), static_cast(device->Frequency), - AutoKnee, AutoAttack, AutoRelease, AutoPostGain, AutoDeclip, LookAheadTime, HoldTime, - PreGainDb, PostGainDb, threshold, Ratio, KneeDb, AttackTime, ReleaseTime); + const auto flags = Compressor::FlagBits{}.set(Compressor::AutoKnee).set(Compressor::AutoAttack) + .set(Compressor::AutoRelease).set(Compressor::AutoPostGain).set(Compressor::AutoDeclip); + + return Compressor::Create(device->RealOut.Buffer.size(), + static_cast(device->mSampleRate), flags, LookAheadTime, HoldTime, PreGainDb, + PostGainDb, threshold, Ratio, KneeDb, AttackTime, ReleaseTime); } /** @@ -1004,15 +1066,23 @@ std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const f * to jump forward or back. Must not be called while the device is running/ * mixing. */ -inline void UpdateClockBase(ALCdevice *device) +inline void UpdateClockBase(al::Device *device) { + using std::chrono::duration_cast; + const auto mixLock = device->getWriteMixLock(); - auto samplesDone = device->mSamplesDone.load(std::memory_order_relaxed); - auto clockBase = device->mClockBase.load(std::memory_order_relaxed); + auto clockBaseSec = device->mClockBaseSec.load(std::memory_order_relaxed); + auto clockBaseNSec = nanoseconds{device->mClockBaseNSec.load(std::memory_order_relaxed)}; + clockBaseNSec += nanoseconds{seconds{device->mSamplesDone.load(std::memory_order_relaxed)}} + / device->mSampleRate; - clockBase += nanoseconds{seconds{samplesDone}} / device->Frequency; - device->mClockBase.store(clockBase, std::memory_order_relaxed); + clockBaseSec += duration_cast(clockBaseNSec); + clockBaseNSec %= seconds{1}; + + device->mClockBaseSec.store(clockBaseSec, std::memory_order_relaxed); + device->mClockBaseNSec.store(duration_cast(clockBaseNSec), + std::memory_order_relaxed); device->mSamplesDone.store(0, std::memory_order_relaxed); } @@ -1020,11 +1090,11 @@ inline void UpdateClockBase(ALCdevice *device) * Updates device parameters according to the attribute list (caller is * responsible for holding the list lock). */ -ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList) +auto UpdateDeviceParams(al::Device *device, const al::span attrList) -> ALCenum { if(attrList.empty() && device->Type == DeviceType::Loopback) { - WARN("Missing attributes for loopback device\n"); + WARN("Missing attributes for loopback device"); return ALC_INVALID_VALUE; } @@ -1078,12 +1148,11 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList TypeMap{"float32"sv, DevFmtFloat }, }; - const ALCchar *fmt{typeopt->c_str()}; auto iter = std::find_if(typelist.begin(), typelist.end(), - [svfmt=std::string_view{fmt}](const TypeMap &entry) -> bool + [svfmt=std::string_view{*typeopt}](const TypeMap &entry) -> bool { return al::case_compare(entry.name, svfmt) == 0; }); if(iter == typelist.end()) - ERR("Unsupported sample-type: %s\n", fmt); + ERR("Unsupported sample-type: {}", *typeopt); else opttype = iter->type; } @@ -1110,12 +1179,11 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList ChannelMap{"ambi3"sv, DevFmtAmbi3D, 3}, }; - const ALCchar *fmt{chanopt->c_str()}; auto iter = std::find_if(chanlist.begin(), chanlist.end(), - [svfmt=std::string_view{fmt}](const ChannelMap &entry) -> bool + [svfmt=std::string_view{*chanopt}](const ChannelMap &entry) -> bool { return al::case_compare(entry.name, svfmt) == 0; }); if(iter == chanlist.end()) - ERR("Unsupported channels: %s\n", fmt); + ERR("Unsupported channels: {}", *chanopt); else { optchans = iter->chans; @@ -1146,12 +1214,12 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList optscale = DevAmbiScaling::N3D; } else - ERR("Unsupported ambi-format: %s\n", ambiopt->c_str()); + ERR("Unsupported ambi-format: {}", *ambiopt); } if(auto hrtfopt = device->configValue({}, "hrtf"sv)) { - WARN("general/hrtf is deprecated, please use stereo-encoding instead\n"); + WARN("general/hrtf is deprecated, please use stereo-encoding instead"); if(al::case_compare(*hrtfopt, "true"sv) == 0) stereomode = StereoEncoding::Hrtf; @@ -1161,7 +1229,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList stereomode = StereoEncoding::Default; } else if(al::case_compare(*hrtfopt, "auto"sv) != 0) - ERR("Unexpected hrtf value: %s\n", hrtfopt->c_str()); + ERR("Unexpected hrtf value: {}", *hrtfopt); } } @@ -1174,7 +1242,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList else if(al::case_compare(*encopt, "hrtf") == 0) stereomode = StereoEncoding::Hrtf; else - ERR("Unexpected stereo-encoding: %s\n", encopt->c_str()); + ERR("Unexpected stereo-encoding: {}", *encopt); } // Check for app-specified attributes @@ -1184,17 +1252,18 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList std::optional opthrtf; int freqAttr{}; -#define ATTRIBUTE(a) a: TRACE("%s = %d\n", #a, attrList[attrIdx + 1]); +#define ATTRIBUTE(a) a: TRACE("{} = {}", #a, attrList[attrIdx + 1]); +#define ATTRIBUTE_HEX(a) a: TRACE("{} = {:#x}", #a, as_unsigned(attrList[attrIdx + 1])); for(size_t attrIdx{0};attrIdx < attrList.size();attrIdx+=2) { switch(attrList[attrIdx]) { - case ATTRIBUTE(ALC_FORMAT_CHANNELS_SOFT) + case ATTRIBUTE_HEX(ALC_FORMAT_CHANNELS_SOFT) if(device->Type == DeviceType::Loopback) optchans = DevFmtChannelsFromEnum(attrList[attrIdx + 1]); break; - case ATTRIBUTE(ALC_FORMAT_TYPE_SOFT) + case ATTRIBUTE_HEX(ALC_FORMAT_TYPE_SOFT) if(device->Type == DeviceType::Loopback) opttype = DevFmtTypeFromEnum(attrList[attrIdx + 1]); break; @@ -1203,12 +1272,12 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList freqAttr = attrList[attrIdx + 1]; break; - case ATTRIBUTE(ALC_AMBISONIC_LAYOUT_SOFT) + case ATTRIBUTE_HEX(ALC_AMBISONIC_LAYOUT_SOFT) if(device->Type == DeviceType::Loopback) optlayout = DevAmbiLayoutFromEnum(attrList[attrIdx + 1]); break; - case ATTRIBUTE(ALC_AMBISONIC_SCALING_SOFT) + case ATTRIBUTE_HEX(ALC_AMBISONIC_SCALING_SOFT) if(device->Type == DeviceType::Loopback) optscale = DevAmbiScalingFromEnum(attrList[attrIdx + 1]); break; @@ -1256,16 +1325,25 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList optlimit = std::nullopt; break; - case ATTRIBUTE(ALC_OUTPUT_MODE_SOFT) + case ATTRIBUTE_HEX(ALC_OUTPUT_MODE_SOFT) outmode = attrList[attrIdx + 1]; break; + case ATTRIBUTE_HEX(ALC_CONTEXT_FLAGS_EXT) + /* Handled in alcCreateContext */ + break; + + case ATTRIBUTE(ALC_SYNC) + /* Ignored attribute */ + break; + default: - TRACE("0x%04X = %d (0x%x)\n", attrList[attrIdx], - attrList[attrIdx + 1], attrList[attrIdx + 1]); + TRACE("{:#04x} = {} ({:#x})", as_unsigned(attrList[attrIdx]), + attrList[attrIdx + 1], as_unsigned(attrList[attrIdx + 1])); break; } } +#undef ATTRIBUTE_HEX #undef ATTRIBUTE if(device->Type == DeviceType::Loopback) @@ -1322,7 +1400,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList if(outmode != ALC_ANY_SOFT) { - using OutputMode = ALCdevice::OutputMode; + using OutputMode = al::Device::OutputMode; switch(OutputMode(outmode)) { case OutputMode::Any: break; @@ -1410,7 +1488,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList if(device->Type == DeviceType::Loopback) { - device->Frequency = *optsrate; + device->mSampleRate = *optsrate; device->FmtChans = *optchans; device->FmtType = *opttype; if(device->FmtChans == DevFmtAmbi3D) @@ -1426,9 +1504,9 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList device->FmtType = opttype.value_or(DevFmtTypeDefault); device->FmtChans = optchans.value_or(DevFmtChannelsDefault); device->mAmbiOrder = 0; - device->BufferSize = buffer_size; - device->UpdateSize = period_size; - device->Frequency = optsrate.value_or(DefaultOutputRate); + device->mBufferSize = buffer_size; + device->mUpdateSize = period_size; + device->mSampleRate = optsrate.value_or(DefaultOutputRate); device->Flags.set(FrequencyRequest, optsrate.has_value()) .set(ChannelsRequest, optchans.has_value()) .set(SampleTypeRequest, opttype.has_value()); @@ -1442,20 +1520,20 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList && (device->mAmbiLayout == DevAmbiLayout::FuMa || device->mAmbiScale == DevAmbiScaling::FuMa)) { - ERR("FuMa is incompatible with %d%s order ambisonics (up to 3rd order only)\n", + ERR("FuMa is incompatible with {}{} order ambisonics (up to 3rd order only)", device->mAmbiOrder, GetCounterSuffix(device->mAmbiOrder)); device->mAmbiOrder = 3; } } } - TRACE("Pre-reset: %s%s, %s%s, %s%uhz, %u / %u buffer\n", + TRACE("Pre-reset: {}{}, {}{}, {}{}hz, {} / {} buffer", device->Flags.test(ChannelsRequest)?"*":"", DevFmtChannelsString(device->FmtChans), device->Flags.test(SampleTypeRequest)?"*":"", DevFmtTypeString(device->FmtType), - device->Flags.test(FrequencyRequest)?"*":"", device->Frequency, - device->UpdateSize, device->BufferSize); + device->Flags.test(FrequencyRequest)?"*":"", device->mSampleRate, + device->mUpdateSize, device->mBufferSize); - const uint oldFreq{device->Frequency}; + const uint oldFreq{device->mSampleRate}; const DevFmtChannels oldChans{device->FmtChans}; const DevFmtType oldType{device->FmtType}; try { @@ -1464,32 +1542,32 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList throw al::backend_exception{al::backend_error::DeviceError, "Device reset failure"}; } catch(std::exception &e) { - ERR("Device error: %s\n", e.what()); - device->handleDisconnect("%s", e.what()); + ERR("Device error: {}", e.what()); + device->handleDisconnect("{}", e.what()); return ALC_INVALID_DEVICE; } if(device->FmtChans != oldChans && device->Flags.test(ChannelsRequest)) { - ERR("Failed to set %s, got %s instead\n", DevFmtChannelsString(oldChans), + ERR("Failed to set {}, got {} instead", DevFmtChannelsString(oldChans), DevFmtChannelsString(device->FmtChans)); device->Flags.reset(ChannelsRequest); } if(device->FmtType != oldType && device->Flags.test(SampleTypeRequest)) { - ERR("Failed to set %s, got %s instead\n", DevFmtTypeString(oldType), + ERR("Failed to set {}, got {} instead", DevFmtTypeString(oldType), DevFmtTypeString(device->FmtType)); device->Flags.reset(SampleTypeRequest); } - if(device->Frequency != oldFreq && device->Flags.test(FrequencyRequest)) + if(device->mSampleRate != oldFreq && device->Flags.test(FrequencyRequest)) { - WARN("Failed to set %uhz, got %uhz instead\n", oldFreq, device->Frequency); + WARN("Failed to set {}hz, got {}hz instead", oldFreq, device->mSampleRate); device->Flags.reset(FrequencyRequest); } - TRACE("Post-reset: %s, %s, %uhz, %u / %u buffer\n", + TRACE("Post-reset: {}, {}, {}hz, {} / {} buffer", DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), - device->Frequency, device->UpdateSize, device->BufferSize); + device->mSampleRate, device->mUpdateSize, device->mBufferSize); if(device->Type != DeviceType::Loopback) { @@ -1500,7 +1578,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList else if(al::case_compare(*modeopt, "speakers"sv) == 0) device->Flags.reset(DirectEar); else if(al::case_compare(*modeopt, "auto"sv) != 0) - ERR("Unexpected stereo-mode: %s\n", modeopt->c_str()); + ERR("Unexpected stereo-mode: {}", *modeopt); } } @@ -1529,7 +1607,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList numSends = std::min(numSends, std::clamp(*sendsopt, 0u, uint{MaxSendCount})); device->NumAuxSends = numSends; - TRACE("Max sources: %d (%d + %d), effect slots: %d, sends: %d\n", + TRACE("Max sources: {} ({} + {}), effect slots: {}, sends: {}", device->SourcesMax, device->NumMonoSources, device->NumStereoSources, device->AuxiliaryEffectSlotMax, device->NumAuxSends); @@ -1583,10 +1661,10 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList } } if(!(device->DitherDepth > 0.0f)) - TRACE("Dithering disabled\n"); + TRACE("Dithering disabled"); else - TRACE("Dithering enabled (%d-bit, %g)\n", float2int(std::log2(device->DitherDepth)+0.5f)+1, - device->DitherDepth); + TRACE("Dithering enabled ({}-bit, {:g})", + float2int(std::log2(device->DitherDepth)+0.5f)+1, device->DitherDepth); if(!optlimit) optlimit = device->configValue({}, "output-limiter"); @@ -1612,7 +1690,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList } } if(!optlimit.value_or(false)) - TRACE("Output limiter disabled\n"); + TRACE("Output limiter disabled"); else { float thrshld{1.0f}; @@ -1639,18 +1717,20 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList sample_delay += limiter->getLookAhead(); device->Limiter = std::move(limiter); - TRACE("Output limiter enabled, %.4fdB limit\n", thrshld_dB); + TRACE("Output limiter enabled, {:.4f}dB limit", thrshld_dB); } /* Convert the sample delay from samples to nanosamples to nanoseconds. */ sample_delay = std::min(sample_delay, std::numeric_limits::max()); - device->FixedLatency += nanoseconds{seconds{sample_delay}} / device->Frequency; - TRACE("Fixed device latency: %" PRId64 "ns\n", int64_t{device->FixedLatency.count()}); + device->FixedLatency += nanoseconds{seconds{sample_delay}} / device->mSampleRate; + TRACE("Fixed device latency: {}ns", device->FixedLatency.count()); FPUCtl mixer_mode{}; auto reset_context = [device](ContextBase *ctxbase) { - auto *context = static_cast(ctxbase); + auto *context = dynamic_cast(ctxbase); + assert(context != nullptr); + if(!context) return; std::unique_lock proplock{context->mPropLock}; std::unique_lock slotlock{context->mEffectSlotLock}; @@ -1808,13 +1888,13 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList device->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { - ERR("%s\n", e.what()); - device->handleDisconnect("%s", e.what()); + ERR("{}", e.what()); + device->handleDisconnect("{}", e.what()); return ALC_INVALID_DEVICE; } - TRACE("Post-start: %s, %s, %uhz, %u / %u buffer\n", + TRACE("Post-start: {}, {}, {}hz, {} / {} buffer", DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), - device->Frequency, device->UpdateSize, device->BufferSize); + device->mSampleRate, device->mUpdateSize, device->mBufferSize); } return ALC_NO_ERROR; @@ -1824,7 +1904,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList * Updates device parameters as above, and also first clears the disconnected * status, if set. */ -bool ResetDeviceParams(ALCdevice *device, const al::span attrList) +auto ResetDeviceParams(al::Device *device, const al::span attrList) -> bool { /* If the device was disconnected, reset it since we're opened anew. */ if(!device->Connected.load(std::memory_order_relaxed)) UNLIKELY @@ -1834,8 +1914,9 @@ bool ResetDeviceParams(ALCdevice *device, const al::span attrList) for(ContextBase *ctxbase : *device->mContexts.load(std::memory_order_acquire)) { - auto *ctx = static_cast(ctxbase); - if(!ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) + auto *ctx = dynamic_cast(ctxbase); + assert(ctx != nullptr); + if(!ctx || !ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) continue; /* Clear any pending voice changes and reallocate voices to get a @@ -1922,9 +2003,9 @@ ContextRef GetContextRef() noexcept return ContextRef{context}; } -void alcSetError(ALCdevice *device, ALCenum errorCode) +void alcSetError(al::Device *device, ALCenum errorCode) { - WARN("Error generated on device %p, code 0x%04x\n", voidp{device}, errorCode); + WARN("Error generated on device {}, code {:#04x}", voidp{device}, as_unsigned(errorCode)); if(TrapALCError) { #ifdef _WIN32 @@ -1948,6 +2029,9 @@ void alcSetError(ALCdevice *device, ALCenum errorCode) ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) noexcept { + if(!gProcessRunning) + return ALC_INVALID_DEVICE; + DeviceRef dev{VerifyDevice(device)}; if(dev) return dev->LastError.exchange(ALC_NO_ERROR); return LastNullDeviceError.exchange(ALC_NO_ERROR); @@ -1963,7 +2047,7 @@ ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) noexcept return; } - if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY + if(ctx->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY ctx->debugMessage(DebugSource::API, DebugType::Portability, 0, DebugSeverity::Medium, "alcSuspendContext behavior is not portable -- some implementations suspend all " "rendering, some only defer property changes, and some are completely no-op; consider " @@ -1986,8 +2070,8 @@ ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) noexcept return; } - if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY - ctx->debugMessage(DebugSource::API, DebugType::Portability, 0, DebugSeverity::Medium, + if(ctx->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY + ctx->debugMessage(DebugSource::API, DebugType::Portability, 1, DebugSeverity::Medium, "alcProcessContext behavior is not portable -- some implementations resume rendering, " "some apply deferred property changes, and some are completely no-op; consider using " "alcDeviceResumeSOFT to resume rendering, or alProcessUpdatesSOFT to apply deferred " @@ -2001,121 +2085,101 @@ ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) noexcept } -ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param) noexcept +ALC_API auto ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param) noexcept -> const ALCchar* { - const ALCchar *value{nullptr}; - switch(param) { - case ALC_NO_ERROR: value = GetNoErrorString(); break; - case ALC_INVALID_ENUM: value = GetInvalidEnumString(); break; - case ALC_INVALID_VALUE: value = GetInvalidValueString(); break; - case ALC_INVALID_DEVICE: value = GetInvalidDeviceString(); break; - case ALC_INVALID_CONTEXT: value = GetInvalidContextString(); break; - case ALC_OUT_OF_MEMORY: value = GetOutOfMemoryString(); break; + case ALC_NO_ERROR: return GetNoErrorString(); + case ALC_INVALID_ENUM: return GetInvalidEnumString(); + case ALC_INVALID_VALUE: return GetInvalidValueString(); + case ALC_INVALID_DEVICE: return GetInvalidDeviceString(); + case ALC_INVALID_CONTEXT: return GetInvalidContextString(); + case ALC_OUT_OF_MEMORY: return GetOutOfMemoryString(); case ALC_DEVICE_SPECIFIER: - value = GetDefaultName(); - break; + return GetDefaultName(); case ALC_ALL_DEVICES_SPECIFIER: if(DeviceRef dev{VerifyDevice(Device)}) { if(dev->Type == DeviceType::Capture) - alcSetError(dev.get(), ALC_INVALID_ENUM); - else if(dev->Type == DeviceType::Loopback) - value = GetDefaultName(); - else { - std::lock_guard statelock{dev->StateLock}; - value = dev->DeviceName.c_str(); + alcSetError(dev.get(), ALC_INVALID_ENUM); + return nullptr; } + if(dev->Type == DeviceType::Loopback) + return GetDefaultName(); + + auto statelock = std::lock_guard{dev->StateLock}; + return dev->mDeviceName.c_str(); } - else - { - ProbeAllDevicesList(); - value = alcAllDevicesList.c_str(); - } - break; + ProbeAllDevicesList(); + return alcAllDevicesList.c_str(); case ALC_CAPTURE_DEVICE_SPECIFIER: if(DeviceRef dev{VerifyDevice(Device)}) { if(dev->Type != DeviceType::Capture) - alcSetError(dev.get(), ALC_INVALID_ENUM); - else { - std::lock_guard statelock{dev->StateLock}; - value = dev->DeviceName.c_str(); + alcSetError(dev.get(), ALC_INVALID_ENUM); + return nullptr; } + + auto statelock = std::lock_guard{dev->StateLock}; + return dev->mDeviceName.c_str(); } - else - { - ProbeCaptureDeviceList(); - value = alcCaptureDeviceList.c_str(); - } - break; + ProbeCaptureDeviceList(); + return alcCaptureDeviceList.c_str(); /* Default devices are always first in the list */ case ALC_DEFAULT_DEVICE_SPECIFIER: - value = GetDefaultName(); - break; + return GetDefaultName(); case ALC_DEFAULT_ALL_DEVICES_SPECIFIER: if(alcAllDevicesList.empty()) ProbeAllDevicesList(); /* Copy first entry as default. */ - if(alcAllDevicesArray.empty()) - value = GetDefaultName(); - else - { + if(!alcAllDevicesArray.empty()) alcDefaultAllDevicesSpecifier = alcAllDevicesArray.front(); - value = alcDefaultAllDevicesSpecifier.c_str(); - } - break; + else + alcDefaultAllDevicesSpecifier.clear(); + return alcDefaultAllDevicesSpecifier.c_str(); case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER: if(alcCaptureDeviceList.empty()) ProbeCaptureDeviceList(); /* Copy first entry as default. */ - if(alcCaptureDeviceArray.empty()) - value = GetDefaultName(); - else - { + if(!alcCaptureDeviceArray.empty()) alcCaptureDefaultDeviceSpecifier = alcCaptureDeviceArray.front(); - value = alcCaptureDefaultDeviceSpecifier.c_str(); - } - break; + else + alcCaptureDefaultDeviceSpecifier.clear(); + return alcCaptureDefaultDeviceSpecifier.c_str(); case ALC_EXTENSIONS: if(VerifyDevice(Device)) - value = GetExtensionList().data(); - else - value = GetNoDeviceExtList().data(); - break; + return GetExtensionList(); + return GetNoDeviceExtList(); case ALC_HRTF_SPECIFIER_SOFT: if(DeviceRef dev{VerifyDevice(Device)}) { std::lock_guard statelock{dev->StateLock}; - value = (dev->mHrtf ? dev->mHrtfName.c_str() : ""); + return dev->mHrtf ? dev->mHrtfName.c_str() : ""; } - else - alcSetError(nullptr, ALC_INVALID_DEVICE); - break; + alcSetError(nullptr, ALC_INVALID_DEVICE); + return nullptr; default: alcSetError(VerifyDevice(Device).get(), ALC_INVALID_ENUM); - break; } - return value; + return nullptr; } - -static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values) +namespace { +auto GetIntegerv(al::Device *device, ALCenum param, const al::span values) -> size_t { if(values.empty()) { @@ -2217,20 +2281,20 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span } /* render device */ - auto NumAttrsForDevice = [](const ALCdevice *aldev) noexcept -> uint8_t + auto NumAttrsForDevice = [device]() noexcept -> uint8_t { - if(aldev->Type == DeviceType::Loopback && aldev->FmtChans == DevFmtAmbi3D) + if(device->Type == DeviceType::Loopback && device->FmtChans == DevFmtAmbi3D) return 37; return 31; }; switch(param) { case ALC_ATTRIBUTES_SIZE: - values[0] = NumAttrsForDevice(device); + values[0] = NumAttrsForDevice(); return 1; case ALC_ALL_ATTRIBUTES: - if(values.size() >= NumAttrsForDevice(device)) + if(values.size() >= NumAttrsForDevice()) { size_t i{0}; values[i++] = ALC_MAJOR_VERSION; @@ -2243,11 +2307,11 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[i++] = alcEFXMinorVersion; values[i++] = ALC_FREQUENCY; - values[i++] = static_cast(device->Frequency); + values[i++] = static_cast(device->mSampleRate); if(device->Type != DeviceType::Loopback) { values[i++] = ALC_REFRESH; - values[i++] = static_cast(device->Frequency / device->UpdateSize); + values[i++] = static_cast(device->mSampleRate / device->mUpdateSize); values[i++] = ALC_SYNC; values[i++] = ALC_FALSE; @@ -2298,7 +2362,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[i++] = static_cast(device->getOutputMode1()); values[i++] = 0; - assert(i == NumAttrsForDevice(device)); + assert(i == NumAttrsForDevice()); return i; } alcSetError(device, ALC_INVALID_VALUE); @@ -2321,7 +2385,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span return 1; case ALC_FREQUENCY: - values[0] = static_cast(device->Frequency); + values[0] = static_cast(device->mSampleRate); return 1; case ALC_REFRESH: @@ -2330,7 +2394,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span alcSetError(device, ALC_INVALID_DEVICE); return 0; } - values[0] = static_cast(device->Frequency / device->UpdateSize); + values[0] = static_cast(device->mSampleRate / device->mUpdateSize); return 1; case ALC_SYNC: @@ -2434,6 +2498,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span } return 0; } +} // namespace ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) noexcept { @@ -2461,7 +2526,7 @@ ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, return; } /* render device */ - auto NumAttrsForDevice = [](ALCdevice *aldev) noexcept -> size_t + auto NumAttrsForDevice = [](al::Device *aldev) noexcept -> size_t { if(aldev->Type == DeviceType::Loopback && aldev->FmtChans == DevFmtAmbi3D) return 41; @@ -2481,12 +2546,12 @@ ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, { size_t i{0}; valuespan[i++] = ALC_FREQUENCY; - valuespan[i++] = dev->Frequency; + valuespan[i++] = dev->mSampleRate; if(dev->Type != DeviceType::Loopback) { valuespan[i++] = ALC_REFRESH; - valuespan[i++] = dev->Frequency / dev->UpdateSize; + valuespan[i++] = dev->mSampleRate / dev->mUpdateSize; valuespan[i++] = ALC_SYNC; valuespan[i++] = ALC_FALSE; @@ -2538,7 +2603,7 @@ ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, valuespan[i++] = clock.Latency.count(); valuespan[i++] = ALC_OUTPUT_MODE_SOFT; - valuespan[i++] = al::to_underlying(device->getOutputMode1()); + valuespan[i++] = al::to_underlying(dev->getOutputMode1()); valuespan[i++] = 0; } @@ -2547,15 +2612,18 @@ ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, case ALC_DEVICE_CLOCK_SOFT: { uint samplecount, refcount; - nanoseconds basecount; + seconds clocksec; + nanoseconds clocknsec; do { refcount = dev->waitForMix(); - basecount = dev->mClockBase.load(std::memory_order_relaxed); samplecount = dev->mSamplesDone.load(std::memory_order_relaxed); + clocksec = dev->mClockBaseSec.load(std::memory_order_relaxed); + clocknsec = dev->mClockBaseNSec.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_acquire); } while(refcount != dev->mMixCount.load(std::memory_order_relaxed)); - basecount += nanoseconds{seconds{samplecount}} / dev->Frequency; - valuespan[0] = basecount.count(); + + valuespan[0] = nanoseconds{clocksec + nanoseconds{clocknsec} + + nanoseconds{seconds{samplecount}}/dev->mSampleRate}.count(); } break; @@ -2593,7 +2661,8 @@ ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const A } const std::string_view tofind{extName}; - const auto extlist = dev ? GetExtensionList() : GetNoDeviceExtList(); + const auto extlist = dev ? std::string_view{GetExtensionList()} + : std::string_view{GetNoDeviceExtList()}; auto matchpos = extlist.find(tofind); while(matchpos != std::string_view::npos) { @@ -2619,7 +2688,7 @@ ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar return nullptr; } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(eax_g_is_enabled) { for(const auto &func : eaxFunctions) @@ -2647,7 +2716,7 @@ ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *e return 0; } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(eax_g_is_enabled) { for(const auto &enm : eaxEnumerations) @@ -2716,14 +2785,14 @@ ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCin { const float valf{*volopt}; if(!std::isfinite(valf)) - ERR("volume-adjust must be finite: %f\n", valf); + ERR("volume-adjust must be finite: {:f}", valf); else { const float db{std::clamp(valf, -24.0f, 24.0f)}; if(db != valf) - WARN("volume-adjust clamped: %f, range: +/-%f\n", valf, 24.0f); + WARN("volume-adjust clamped: {:f}, range: +/-24", valf); context->mGainBoost = std::pow(10.0f, db/20.0f); - TRACE("volume-adjust gain: %f\n", context->mGainBoost); + TRACE("volume-adjust gain: {:f}", context->mGainBoost); } } @@ -2733,7 +2802,7 @@ ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCin /* Allocate a new context array, which holds 1 more than the current/ * old array. */ - auto *oldarray = device->mContexts.load(); + auto *oldarray = dev->mContexts.load(); auto newarray = ContextArray::Create(oldarray->size() + 1); /* Copy the current/old context handles to the new array, appending the @@ -2764,15 +2833,18 @@ ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCin if(sloterr == AL_NO_ERROR) slot->updateProps(context.get()); else - ERR("Failed to initialize the default effect\n"); + ERR("Failed to initialize the default effect"); } - TRACE("Created context %p\n", voidp{context.get()}); + TRACE("Created context {}", voidp{context.get()}); return context.release(); } ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) noexcept { + if(!gProcessRunning) + return; + std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), context); if(iter == ContextList.end() || *iter != context) @@ -2788,8 +2860,7 @@ ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) noexcept ContextRef ctx{*iter}; ContextList.erase(iter); - ALCdevice *Device{ctx->mALDevice.get()}; - + auto *Device = ctx->mALDevice.get(); std::lock_guard statelock{Device->StateLock}; ctx->deinit(); } @@ -2888,7 +2959,7 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) noexcep std::string_view devname{deviceName ? deviceName : ""}; if(!devname.empty()) { - TRACE("Opening playback device \"%.*s\"\n", al::sizei(devname), devname.data()); + TRACE("Opening playback device \"{}\"", devname); if(al::case_compare(devname, GetDefaultName()) == 0 #ifdef _WIN32 /* Some old Windows apps hardcode these expecting OpenAL to use a @@ -2906,21 +2977,28 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) noexcep || al::starts_with(devname, "'("sv) || al::case_compare(devname, "openal-soft"sv) == 0) devname = {}; + else + { + const auto prefix = GetDevicePrefix(); + if(!prefix.empty() && devname.size() > prefix.size() + && al::starts_with(devname, prefix)) + devname = devname.substr(prefix.size()); + } } else - TRACE("Opening default playback device\n"); + TRACE("Opening default playback device"); const uint DefaultSends{ -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : #endif // ALSOFT_EAX uint{DefaultSendCount} }; - DeviceRef device{new(std::nothrow) ALCdevice{DeviceType::Playback}}; + auto device = DeviceRef{new(std::nothrow) al::Device{DeviceType::Playback}}; if(!device) { - WARN("Failed to create playback device handle\n"); + WARN("Failed to create playback device handle"); alcSetError(nullptr, ALC_OUT_OF_MEMORY); return nullptr; } @@ -2928,9 +3006,9 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) noexcep /* Set output format */ device->FmtChans = DevFmtChannelsDefault; device->FmtType = DevFmtTypeDefault; - device->Frequency = DefaultOutputRate; - device->UpdateSize = DefaultUpdateSize; - device->BufferSize = DefaultUpdateSize * DefaultNumUpdates; + device->mSampleRate = DefaultOutputRate; + device->mUpdateSize = DefaultUpdateSize; + device->mBufferSize = DefaultUpdateSize * DefaultNumUpdates; device->SourcesMax = 256; device->NumStereoSources = 1; @@ -2942,27 +3020,52 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) noexcep auto backend = PlaybackFactory->createBackend(device.get(), BackendType::Playback); std::lock_guard listlock{ListLock}; backend->open(devname); + device->mDeviceName = std::string{GetDevicePrefix()}+backend->mDeviceName; device->Backend = std::move(backend); } catch(al::backend_exception &e) { - WARN("Failed to open playback device: %s\n", e.what()); + WARN("Failed to open playback device: {}", e.what()); alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; } + auto checkopt = [&device](const char *envname, const std::string_view optname) + { + if(auto optval = al::getenv(envname)) return optval; + return device->configValue("game_compat", optname); + }; + if(auto overrideopt = checkopt("__ALSOFT_VENDOR_OVERRIDE", "vendor-override"sv)) + { + device->mVendorOverride = std::move(*overrideopt); + TRACE("Overriding vendor string: \"{}\"", device->mVendorOverride); + } + if(auto overrideopt = checkopt("__ALSOFT_VERSION_OVERRIDE", "version-override"sv)) + { + device->mVersionOverride = std::move(*overrideopt); + TRACE("Overriding version string: \"{}\"", device->mVersionOverride); + } + if(auto overrideopt = checkopt("__ALSOFT_RENDERER_OVERRIDE", "renderer-override"sv)) + { + device->mRendererOverride = std::move(*overrideopt); + TRACE("Overriding renderer string: \"{}\"", device->mRendererOverride); + } + { std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); DeviceList.emplace(iter, device.get()); } - TRACE("Created device %p, \"%s\"\n", voidp{device.get()}, device->DeviceName.c_str()); + TRACE("Created device {}, \"{}\"", voidp{device.get()}, device->mDeviceName); return device.release(); } ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) noexcept { + if(!gProcessRunning) + return ALC_FALSE; + std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); if(iter == DeviceList.end() || *iter != device) @@ -2997,7 +3100,7 @@ ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) noexcept for(ContextRef &context : orphanctxs) { - WARN("Releasing orphaned context %p\n", voidp{context.get()}); + WARN("Releasing orphaned context {}", voidp{context.get()}); context->deinit(); } orphanctxs.clear(); @@ -3034,18 +3137,25 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, std::string_view devname{deviceName ? deviceName : ""}; if(!devname.empty()) { - TRACE("Opening capture device \"%.*s\"\n", al::sizei(devname), devname.data()); + TRACE("Opening capture device \"{}\"", devname); if(al::case_compare(devname, GetDefaultName()) == 0 || al::case_compare(devname, "openal-soft"sv) == 0) devname = {}; + else + { + const auto prefix = GetDevicePrefix(); + if(!prefix.empty() && devname.size() > prefix.size() + && al::starts_with(devname, prefix)) + devname = devname.substr(prefix.size()); + } } else - TRACE("Opening default capture device\n"); + TRACE("Opening default capture device"); - DeviceRef device{new(std::nothrow) ALCdevice{DeviceType::Capture}}; + auto device = DeviceRef{new(std::nothrow) al::Device{DeviceType::Capture}}; if(!device) { - WARN("Failed to create capture device handle\n"); + WARN("Failed to create capture device handle"); alcSetError(nullptr, ALC_OUT_OF_MEMORY); return nullptr; } @@ -3057,28 +3167,29 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, return nullptr; } - device->Frequency = frequency; + device->mSampleRate = frequency; device->FmtChans = decompfmt->chans; device->FmtType = decompfmt->type; device->Flags.set(FrequencyRequest); device->Flags.set(ChannelsRequest); device->Flags.set(SampleTypeRequest); - device->UpdateSize = static_cast(samples); - device->BufferSize = static_cast(samples); + device->mUpdateSize = static_cast(samples); + device->mBufferSize = static_cast(samples); - TRACE("Capture format: %s, %s, %uhz, %u / %u buffer\n", DevFmtChannelsString(device->FmtChans), - DevFmtTypeString(device->FmtType), device->Frequency, device->UpdateSize, - device->BufferSize); + TRACE("Capture format: {}, {}, {}hz, {} / {} buffer", + DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), + device->mSampleRate, device->mUpdateSize, device->mBufferSize); try { auto backend = CaptureFactory->createBackend(device.get(), BackendType::Capture); std::lock_guard listlock{ListLock}; backend->open(devname); + device->mDeviceName = std::string{GetDevicePrefix()}+backend->mDeviceName; device->Backend = std::move(backend); } catch(al::backend_exception &e) { - WARN("Failed to open capture device: %s\n", e.what()); + WARN("Failed to open capture device: {}", e.what()); alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; @@ -3091,12 +3202,15 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, } device->mDeviceState = DeviceState::Configured; - TRACE("Created capture device %p, \"%s\"\n", voidp{device.get()}, device->DeviceName.c_str()); + TRACE("Created capture device {}, \"{}\"", voidp{device.get()}, device->mDeviceName); return device.release(); } ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) noexcept { + if(!gProcessRunning) + return ALC_FALSE; + std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); if(iter == DeviceList.end() || *iter != device) @@ -3145,8 +3259,8 @@ ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) noexcept dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { - ERR("%s\n", e.what()); - dev->handleDisconnect("%s", e.what()); + ERR("{}", e.what()); + dev->handleDisconnect("{}", e.what()); alcSetError(dev.get(), ALC_INVALID_DEVICE); } } @@ -3216,16 +3330,16 @@ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceN } const uint DefaultSends{ -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : #endif // ALSOFT_EAX uint{DefaultSendCount} }; - DeviceRef device{new(std::nothrow) ALCdevice{DeviceType::Loopback}}; + auto device = DeviceRef{new(std::nothrow) al::Device{DeviceType::Loopback}}; if(!device) { - WARN("Failed to create loopback device handle\n"); + WARN("Failed to create loopback device handle"); alcSetError(nullptr, ALC_OUT_OF_MEMORY); return nullptr; } @@ -3235,10 +3349,10 @@ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceN device->NumAuxSends = DefaultSends; //Set output format - device->BufferSize = 0; - device->UpdateSize = 0; + device->mBufferSize = 0; + device->mUpdateSize = 0; - device->Frequency = DefaultOutputRate; + device->mSampleRate = DefaultOutputRate; device->FmtChans = DevFmtChannelsDefault; device->FmtType = DevFmtTypeDefault; @@ -3249,10 +3363,11 @@ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceN auto backend = LoopbackBackendFactory::getFactory().createBackend(device.get(), BackendType::Playback); backend->open("Loopback"); + device->mDeviceName = std::string{GetDevicePrefix()}+backend->mDeviceName; device->Backend = std::move(backend); } catch(al::backend_exception &e) { - WARN("Failed to open loopback device: %s\n", e.what()); + WARN("Failed to open loopback device: {}", e.what()); alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; @@ -3264,7 +3379,7 @@ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceN DeviceList.emplace(iter, device.get()); } - TRACE("Created loopback device %p\n", voidp{device.get()}); + TRACE("Created loopback device {}", voidp{device.get()}); return device.release(); } @@ -3300,12 +3415,13 @@ ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device #endif ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) noexcept { - if(!device || device->Type != DeviceType::Loopback) UNLIKELY - alcSetError(device, ALC_INVALID_DEVICE); + auto aldev = dynamic_cast(device); + if(!aldev || aldev->Type != DeviceType::Loopback) UNLIKELY + alcSetError(aldev, ALC_INVALID_DEVICE); else if(samples < 0 || (samples > 0 && buffer == nullptr)) UNLIKELY - alcSetError(device, ALC_INVALID_VALUE); + alcSetError(aldev, ALC_INVALID_VALUE); else - device->renderSamples(buffer, static_cast(samples), device->channelsFromFmt()); + aldev->renderSamples(buffer, static_cast(samples), aldev->channelsFromFmt()); } @@ -3346,13 +3462,13 @@ ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) noexcept return; if(dev->mDeviceState < DeviceState::Configured) { - WARN("Cannot resume unconfigured device\n"); + WARN("Cannot resume unconfigured device"); alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } if(!dev->Connected.load()) { - WARN("Cannot resume a disconnected device\n"); + WARN("Cannot resume a disconnected device"); alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } @@ -3366,14 +3482,14 @@ ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) noexcept dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { - ERR("%s\n", e.what()); - dev->handleDisconnect("%s", e.what()); + ERR("{}", e.what()); + dev->handleDisconnect("{}", e.what()); alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } - TRACE("Post-resume: %s, %s, %uhz, %u / %u buffer\n", + TRACE("Post-resume: {}, {}, {}hz, {} / {} buffer", DevFmtChannelsString(dev->FmtChans), DevFmtTypeString(dev->FmtType), - dev->Frequency, dev->UpdateSize, dev->BufferSize); + dev->mSampleRate, dev->mUpdateSize, dev->mBufferSize); } @@ -3453,13 +3569,20 @@ FORCE_ALIGN ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, { if(devname.length() >= size_t{std::numeric_limits::max()}) { - ERR("Device name too long (%zu >= %d)\n", devname.length(), + ERR("Device name too long ({} >= {})", devname.length(), std::numeric_limits::max()); alcSetError(dev.get(), ALC_INVALID_VALUE); return ALC_FALSE; } if(al::case_compare(devname, GetDefaultName()) == 0) devname = {}; + else + { + const auto prefix = GetDevicePrefix(); + if(!prefix.empty() && devname.size() > prefix.size() + && al::starts_with(devname, prefix)) + devname = devname.substr(prefix.size()); + } } /* Force the backend device to stop first since we're opening another one. */ @@ -3479,7 +3602,7 @@ FORCE_ALIGN ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, listlock.unlock(); newbackend = nullptr; - WARN("Failed to reopen playback device: %s\n", e.what()); + WARN("Failed to reopen playback device: {}", e.what()); alcSetError(dev.get(), (e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); @@ -3491,16 +3614,41 @@ FORCE_ALIGN ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception &be) { - ERR("%s\n", be.what()); - dev->handleDisconnect("%s", be.what()); + ERR("{}", be.what()); + dev->handleDisconnect("{}", be.what()); } } return ALC_FALSE; } listlock.unlock(); + dev->mDeviceName = std::string{GetDevicePrefix()}+newbackend->mDeviceName; dev->Backend = std::move(newbackend); dev->mDeviceState = DeviceState::Unprepared; - TRACE("Reopened device %p, \"%s\"\n", voidp{dev.get()}, dev->DeviceName.c_str()); + TRACE("Reopened device {}, \"{}\"", voidp{dev.get()}, dev->mDeviceName); + + std::string{}.swap(dev->mVendorOverride); + std::string{}.swap(dev->mVersionOverride); + std::string{}.swap(dev->mRendererOverride); + auto checkopt = [&dev](const char *envname, const std::string_view optname) + { + if(auto optval = al::getenv(envname)) return optval; + return dev->configValue("game_compat", optname); + }; + if(auto overrideopt = checkopt("__ALSOFT_VENDOR_OVERRIDE", "vendor-override"sv)) + { + dev->mVendorOverride = std::move(*overrideopt); + TRACE("Overriding vendor string: \"{}\"", dev->mVendorOverride); + } + if(auto overrideopt = checkopt("__ALSOFT_VERSION_OVERRIDE", "version-override"sv)) + { + dev->mVersionOverride = std::move(*overrideopt); + TRACE("Overriding version string: \"{}\"", dev->mVersionOverride); + } + if(auto overrideopt = checkopt("__ALSOFT_RENDERER_OVERRIDE", "renderer-override"sv)) + { + dev->mRendererOverride = std::move(*overrideopt); + TRACE("Overriding renderer string: \"{}\"", dev->mRendererOverride); + } /* Always return true even if resetting fails. It shouldn't fail, but this * is primarily to avoid confusion by the app seeing the function return @@ -3526,27 +3674,25 @@ FORCE_ALIGN ALCenum ALC_APIENTRY alcEventIsSupportedSOFT(ALCenum eventType, ALCe auto etype = alc::GetEventType(eventType); if(!etype) { - WARN("Invalid event type: 0x%04x\n", eventType); + WARN("Invalid event type: {:#04x}", as_unsigned(eventType)); alcSetError(nullptr, ALC_INVALID_ENUM); - return ALC_EVENT_NOT_SUPPORTED_SOFT; + return ALC_FALSE; } auto supported = alc::EventSupport::NoSupport; switch(deviceType) { - case ALC_PLAYBACK_DEVICE_SOFT: - if(PlaybackFactory) - supported = PlaybackFactory->queryEventSupport(*etype, BackendType::Playback); - break; + case ALC_PLAYBACK_DEVICE_SOFT: + if(PlaybackFactory) + supported = PlaybackFactory->queryEventSupport(*etype, BackendType::Playback); + return al::to_underlying(supported); - case ALC_CAPTURE_DEVICE_SOFT: - if(CaptureFactory) - supported = CaptureFactory->queryEventSupport(*etype, BackendType::Capture); - break; - - default: - WARN("Invalid device type: 0x%04x\n", deviceType); - alcSetError(nullptr, ALC_INVALID_ENUM); + case ALC_CAPTURE_DEVICE_SOFT: + if(CaptureFactory) + supported = CaptureFactory->queryEventSupport(*etype, BackendType::Capture); + return al::to_underlying(supported); } - return al::to_underlying(supported); + WARN("Invalid device type: {:#04x}", as_unsigned(deviceType)); + alcSetError(nullptr, ALC_INVALID_ENUM); + return ALC_FALSE; } diff --git a/Engine/lib/openal-soft/alc/alconfig.cpp b/Engine/lib/openal-soft/alc/alconfig.cpp index 8c6c7595a..e0a3920ce 100644 --- a/Engine/lib/openal-soft/alc/alconfig.cpp +++ b/Engine/lib/openal-soft/alc/alconfig.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include #include #include @@ -47,9 +46,10 @@ #include "alstring.h" #include "core/helpers.h" #include "core/logging.h" +#include "filesystem.h" #include "strutils.h" -#if defined(ALSOFT_UWP) +#if ALSOFT_UWP #include // !!This is important!! #include #include @@ -61,7 +61,7 @@ namespace { using namespace std::string_view_literals; -#if defined(_WIN32) && !defined(_GAMING_XBOX) && !defined(ALSOFT_UWP) +#if defined(_WIN32) && !defined(_GAMING_XBOX) && !ALSOFT_UWP struct CoTaskMemDeleter { void operator()(void *mem) const { CoTaskMemFree(mem); } }; @@ -153,7 +153,7 @@ void LoadConfigFromFile(std::istream &f) auto endpos = buffer.find(']', 1); if(endpos == 1 || endpos == std::string::npos) { - ERR(" config parse error: bad line \"%s\"\n", buffer.c_str()); + ERR(" config parse error: bad line \"{}\"", buffer); continue; } if(buffer[endpos+1] != '\0') @@ -164,7 +164,7 @@ void LoadConfigFromFile(std::istream &f) if(last < buffer.size() && buffer[last] != '#') { - ERR(" config parse error: bad line \"%s\"\n", buffer.c_str()); + ERR(" config parse error: bad line \"{}\"", buffer); continue; } } @@ -234,7 +234,7 @@ void LoadConfigFromFile(std::istream &f) auto sep = buffer.find('='); if(sep == std::string::npos) { - ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); + ERR(" config parse error: malformed option line: \"{}\"", buffer); continue; } auto keypart = std::string_view{buffer}.substr(0, sep++); @@ -242,7 +242,7 @@ void LoadConfigFromFile(std::istream &f) keypart.remove_suffix(1); if(keypart.empty()) { - ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); + ERR(" config parse error: malformed option line: \"{}\"", buffer); continue; } auto valpart = std::string_view{buffer}.substr(sep); @@ -259,7 +259,7 @@ void LoadConfigFromFile(std::istream &f) if(valpart.size() > size_t{std::numeric_limits::max()}) { - ERR(" config parse error: value too long in line \"%s\"\n", buffer.c_str()); + ERR(" config parse error: value too long in line \"{}\"", buffer); continue; } if(valpart.size() > 1) @@ -272,7 +272,7 @@ void LoadConfigFromFile(std::istream &f) } } - TRACE(" setting '%s' = '%.*s'\n", fullKey.c_str(), al::sizei(valpart), valpart.data()); + TRACE(" setting '{}' = '{}'", fullKey, valpart); /* Check if we already have this option set */ auto find_key = [&fullKey](const ConfigEntry &entry) -> bool @@ -291,11 +291,12 @@ void LoadConfigFromFile(std::istream &f) ConfOpts.shrink_to_fit(); } -const char *GetConfigValue(const std::string_view devName, const std::string_view blockName, - const std::string_view keyName) +auto GetConfigValue(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) -> const std::string& { + static const auto emptyString = std::string{}; if(keyName.empty()) - return nullptr; + return emptyString; std::string key; if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0) @@ -314,14 +315,14 @@ const char *GetConfigValue(const std::string_view devName, const std::string_vie [&key](const ConfigEntry &entry) -> bool { return entry.key == key; }); if(iter != ConfOpts.cend()) { - TRACE("Found option %s = \"%s\"\n", key.c_str(), iter->value.c_str()); + TRACE("Found option {} = \"{}\"", key, iter->value); if(!iter->value.empty()) - return iter->value.c_str(); - return nullptr; + return iter->value; + return emptyString; } if(devName.empty()) - return nullptr; + return emptyString; return GetConfigValue({}, blockName, keyName); } @@ -331,12 +332,11 @@ const char *GetConfigValue(const std::string_view devName, const std::string_vie #ifdef _WIN32 void ReadALConfig() { - namespace fs = std::filesystem; fs::path path; #if !defined(_GAMING_XBOX) { -#if !defined(ALSOFT_UWP) +#if !ALSOFT_UWP std::unique_ptr bufstore; const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, nullptr, al::out_ptr(bufstore))}; @@ -352,8 +352,8 @@ void ReadALConfig() path = fs::path{buffer}; path /= L"alsoft.ini"; - TRACE("Loading config %s...\n", path.u8string().c_str()); - if(std::ifstream f{path}; f.is_open()) + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); + if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } } @@ -362,17 +362,17 @@ void ReadALConfig() path = fs::u8path(GetProcBinary().path); if(!path.empty()) { - path /= "alsoft.ini"; - TRACE("Loading config %s...\n", path.u8string().c_str()); - if(std::ifstream f{path}; f.is_open()) + path /= L"alsoft.ini"; + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); + if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } if(auto confpath = al::getenv(L"ALSOFT_CONF")) { path = *confpath; - TRACE("Loading config %s...\n", path.u8string().c_str()); - if(std::ifstream f{path}; f.is_open()) + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); + if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } } @@ -381,11 +381,10 @@ void ReadALConfig() void ReadALConfig() { - namespace fs = std::filesystem; fs::path path{"/etc/openal/alsoft.conf"}; - TRACE("Loading config %s...\n", path.u8string().c_str()); - if(std::ifstream f{path}; f.is_open()) + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); + if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")}; @@ -409,13 +408,13 @@ void ReadALConfig() } if(!path.is_absolute()) - WARN("Ignoring XDG config dir: %s\n", path.u8string().c_str()); + WARN("Ignoring XDG config dir: {}", al::u8_as_char(path.u8string())); else { path /= "alsoft.conf"; - TRACE("Loading config %s...\n", path.u8string().c_str()); - if(std::ifstream f{path}; f.is_open()) + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); + if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } } @@ -441,7 +440,7 @@ void ReadALConfig() path = *homedir; path /= ".alsoftrc"; - TRACE("Loading config %s...\n", path.u8string().c_str()); + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } @@ -462,7 +461,7 @@ void ReadALConfig() } if(!path.empty()) { - TRACE("Loading config %s...\n", path.u8string().c_str()); + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } @@ -472,66 +471,99 @@ void ReadALConfig() { path /= "alsoft.conf"; - TRACE("Loading config %s...\n", path.u8string().c_str()); + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } if(auto confname = al::getenv("ALSOFT_CONF")) { - TRACE("Loading config %s...\n", confname->c_str()); + TRACE("Loading config {}...", *confname); if(std::ifstream f{*confname}; f.is_open()) LoadConfigFromFile(f); } } #endif -std::optional ConfigValueStr(const std::string_view devName, - const std::string_view blockName, const std::string_view keyName) +auto ConfigValueStr(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) -> std::optional { - if(const char *val{GetConfigValue(devName, blockName, keyName)}) + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) return val; return std::nullopt; } -std::optional ConfigValueInt(const std::string_view devName, const std::string_view blockName, - const std::string_view keyName) +auto ConfigValueInt(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) -> std::optional { - if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return static_cast(std::strtol(val, nullptr, 0)); + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { + return static_cast(std::stol(val, nullptr, 0)); + } + catch(std::exception&) { + WARN("Option is not an int: {} = {}", keyName, val); + } + return std::nullopt; } -std::optional ConfigValueUInt(const std::string_view devName, - const std::string_view blockName, const std::string_view keyName) +auto ConfigValueUInt(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) -> std::optional { - if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return static_cast(std::strtoul(val, nullptr, 0)); + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { + return static_cast(std::stoul(val, nullptr, 0)); + } + catch(std::exception&) { + WARN("Option is not an unsigned int: {} = {}", keyName, val); + } return std::nullopt; } -std::optional ConfigValueFloat(const std::string_view devName, - const std::string_view blockName, const std::string_view keyName) +auto ConfigValueFloat(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) -> std::optional { - if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return std::strtof(val, nullptr); + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { + return std::stof(val); + } + catch(std::exception&) { + WARN("Option is not a float: {} = {}", keyName, val); + } return std::nullopt; } -std::optional ConfigValueBool(const std::string_view devName, - const std::string_view blockName, const std::string_view keyName) +auto ConfigValueBool(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) -> std::optional { - if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 - || al::strcasecmp(val, "true") == 0 || atoi(val) != 0; + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { + return al::case_compare(val, "on"sv) == 0 || al::case_compare(val, "yes"sv) == 0 + || al::case_compare(val, "true"sv) == 0 || std::stoll(val) != 0; + } + catch(std::out_of_range&) { + /* If out of range, the value is some non-0 (true) value and it doesn't + * matter that it's too big or small. + */ + return true; + } + catch(std::exception&) { + /* If stoll fails to convert for any other reason, it's some other word + * that's treated as false. + */ + return false; + } return std::nullopt; } -bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName, - const std::string_view keyName, bool def) +auto GetConfigValueBool(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName, bool def) -> bool { - if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 - || al::strcasecmp(val, "true") == 0 || atoi(val) != 0; + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { + return al::case_compare(val, "on"sv) == 0 || al::case_compare(val, "yes"sv) == 0 + || al::case_compare(val, "true"sv) == 0 || std::stoll(val) != 0; + } + catch(std::out_of_range&) { + return true; + } + catch(std::exception&) { + return false; + } return def; } diff --git a/Engine/lib/openal-soft/alc/alu.cpp b/Engine/lib/openal-soft/alc/alu.cpp index 7f8503dce..aab7eeeca 100644 --- a/Engine/lib/openal-soft/alc/alu.cpp +++ b/Engine/lib/openal-soft/alc/alu.cpp @@ -19,6 +19,7 @@ */ #include "config.h" +#include "config_simd.h" #include "alu.h" @@ -26,23 +27,25 @@ #include #include #include -#include -#include +#include #include +#include #include #include #include -#include #include #include #include -#include #include +#include +#include #include +#include #include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" +#include "alsem.h" #include "alspan.h" #include "alstring.h" #include "atomic.h" @@ -70,6 +73,7 @@ #include "core/mixer/defs.h" #include "core/mixer/hrtfdefs.h" #include "core/resampler_limits.h" +#include "core/storage_formats.h" #include "core/uhjfilter.h" #include "core/voice.h" #include "core/voice_change.h" @@ -78,19 +82,18 @@ #include "ringbuffer.h" #include "strutils.h" #include "vecmat.h" -#include "vector.h" struct CTag; -#ifdef HAVE_SSE +#if HAVE_SSE struct SSETag; #endif -#ifdef HAVE_SSE2 +#if HAVE_SSE2 struct SSE2Tag; #endif -#ifdef HAVE_SSE4_1 +#if HAVE_SSE4_1 struct SSE4Tag; #endif -#ifdef HAVE_NEON +#if HAVE_NEON struct NEONTag; #endif struct PointTag; @@ -143,11 +146,11 @@ HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_}; inline HrtfDirectMixerFunc SelectHrtfMixer() { -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return MixDirectHrtf_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return MixDirectHrtf_; #endif @@ -186,60 +189,62 @@ inline ResamplerFunc SelectResampler(Resampler resampler, uint increment) case Resampler::Point: return Resample_; case Resampler::Linear: -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_; #endif -#ifdef HAVE_SSE4_1 +#if HAVE_SSE4_1 if((CPUCapFlags&CPU_CAP_SSE4_1)) return Resample_; #endif -#ifdef HAVE_SSE2 +#if HAVE_SSE2 if((CPUCapFlags&CPU_CAP_SSE2)) return Resample_; #endif return Resample_; case Resampler::Spline: case Resampler::Gaussian: -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_; #endif -#ifdef HAVE_SSE4_1 +#if HAVE_SSE4_1 if((CPUCapFlags&CPU_CAP_SSE4_1)) return Resample_; #endif -#ifdef HAVE_SSE2 +#if HAVE_SSE2 if((CPUCapFlags&CPU_CAP_SSE2)) return Resample_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Resample_; #endif return Resample_; case Resampler::BSinc12: case Resampler::BSinc24: + case Resampler::BSinc48: if(increment > MixerFracOne) { -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Resample_; #endif return Resample_; } - /* fall-through */ + [[fallthrough]]; case Resampler::FastBSinc12: case Resampler::FastBSinc24: -#ifdef HAVE_NEON + case Resampler::FastBSinc48: +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Resample_; #endif @@ -283,6 +288,10 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState case Resampler::BSinc24: BsincPrepare(increment, &state->emplace(), &gBSinc24); break; + case Resampler::FastBSinc48: + case Resampler::BSinc48: + BsincPrepare(increment, &state->emplace(), &gBSinc48); + break; } return SelectResampler(resampler, increment); } @@ -334,7 +343,8 @@ void DeviceBase::ProcessBs2b(const size_t SamplesToDo) const size_t ridx{RealOut.ChannelIndex[FrontRight]}; /* Now apply the BS2B binaural/crossfeed filter. */ - Bs2b->cross_feed(RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), SamplesToDo); + Bs2b->cross_feed(al::span{RealOut.Buffer[lidx]}.first(SamplesToDo), + al::span{RealOut.Buffer[ridx]}.first(SamplesToDo)); } @@ -434,11 +444,19 @@ bool CalcContextParams(ContextBase *ctx) ctx->mParams.Velocity = rot * vel; ctx->mParams.Gain = props->Gain * ctx->mGainBoost; - ctx->mParams.MetersPerUnit = props->MetersPerUnit; + ctx->mParams.MetersPerUnit = props->MetersPerUnit +#if ALSOFT_EAX + * props->DistanceFactor +#endif + ; ctx->mParams.AirAbsorptionGainHF = props->AirAbsorptionGainHF; ctx->mParams.DopplerFactor = props->DopplerFactor; - ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity; + ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity +#if ALSOFT_EAX + / props->DistanceFactor +#endif + ; ctx->mParams.SourceDistanceModel = props->SourceDistanceModel; ctx->mParams.mDistanceModel = props->mDistanceModel; @@ -462,23 +480,27 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa slot->Target = props->Target; slot->EffectType = props->Type; slot->mEffectProps = props->Props; + + slot->RoomRolloff = 0.0f; + slot->DecayTime = 0.0f; + slot->DecayLFRatio = 0.0f; + slot->DecayHFRatio = 0.0f; + slot->DecayHFLimit = false; + slot->AirAbsorptionGainHF = 1.0f; if(auto *reverbprops = std::get_if(&props->Props)) { slot->RoomRolloff = reverbprops->RoomRolloffFactor; - slot->DecayTime = reverbprops->DecayTime; - slot->DecayLFRatio = reverbprops->DecayLFRatio; - slot->DecayHFRatio = reverbprops->DecayHFRatio; - slot->DecayHFLimit = reverbprops->DecayHFLimit; slot->AirAbsorptionGainHF = reverbprops->AirAbsorptionGainHF; - } - else - { - slot->RoomRolloff = 0.0f; - slot->DecayTime = 0.0f; - slot->DecayLFRatio = 0.0f; - slot->DecayHFRatio = 0.0f; - slot->DecayHFLimit = false; - slot->AirAbsorptionGainHF = 1.0f; + /* If this effect slot's Auxiliary Send Auto is off, don't apply the + * automatic send adjustments based on source distance. + */ + if(slot->AuxSendAuto) + { + slot->DecayTime = reverbprops->DecayTime; + slot->DecayLFRatio = reverbprops->DecayLFRatio; + slot->DecayHFRatio = reverbprops->DecayHFRatio; + slot->DecayHFLimit = reverbprops->DecayHFLimit; + } } EffectState *state{props->State.release()}; @@ -493,9 +515,9 @@ 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(evt_vec.first.len > 0) LIKELY + if(evt_vec[0].len > 0) LIKELY { - auto &evt = InitAsyncEvent(evt_vec.first.buf); + auto &evt = InitAsyncEvent(evt_vec[0].buf); evt.mEffectState = oldstate; ring->writeAdvance(1); } @@ -686,8 +708,8 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order) /* Don't do anything for < 2nd order. */ if(order < 2) return; - auto P = [](const int i, const int l, const int a, const int n, const size_t last_band, - const AmbiRotateMatrix &R) + static constexpr auto P = [](const int i, const int l, const int a, const int n, + const size_t last_band, const AmbiRotateMatrix &R) { const float ri1{ R[ 1+2][static_cast(i+2_z)]}; const float rim1{R[-1+2][static_cast(i+2_z)]}; @@ -701,12 +723,12 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order) return ri0*R[last_band + static_cast(l-1_z+n)][y]; }; - auto U = [P](const int l, const int m, const int n, const size_t last_band, + static constexpr auto U = [](const int l, const int m, const int n, const size_t last_band, const AmbiRotateMatrix &R) { return P(0, l, m, n, last_band, R); }; - auto V = [P](const int l, const int m, const int n, const size_t last_band, + static constexpr auto V = [](const int l, const int m, const int n, const size_t last_band, const AmbiRotateMatrix &R) { using namespace al::numbers; @@ -722,7 +744,7 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order) const float p1{P(-1, l, -m-1, n, last_band, R)}; return d ? p1*sqrt2_v : (p0 + p1); }; - auto W = [P](const int l, const int m, const int n, const size_t last_band, + static constexpr auto W = [](const int l, const int m, const int n, const size_t last_band, const AmbiRotateMatrix &R) { assert(m != 0); @@ -836,7 +858,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, }; - const auto Frequency = static_cast(Device->Frequency); + const auto Frequency = static_cast(Device->mSampleRate); const uint NumSends{Device->NumAuxSends}; const size_t num_channels{voice->mChans.size()}; @@ -1183,8 +1205,6 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con } else for(size_t c{0};c < num_channels;c++) { - using namespace al::numbers; - /* Skip LFE */ if(chans[c].channel == LFE) continue; const float pangain{SelectChannelGain(chans[c].channel)}; @@ -1194,7 +1214,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * the source position, at full spread (pi*2), each channel is * left unchanged. */ - const float a{1.0f - (inv_pi_v/2.0f)*Spread}; + const float a{1.0f - (al::numbers::inv_pi_v/2.0f)*Spread}; std::array pos{ lerpf(chans[c].pos[0], xpos, a), lerpf(chans[c].pos[1], ypos, a), @@ -1310,57 +1330,51 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con voice->mChans[0].mWetParams[i].Gains.Target); } } - else + else for(size_t c{0};c < num_channels;c++) { - using namespace al::numbers; + const auto pangain = SelectChannelGain(chans[c].channel); - for(size_t c{0};c < num_channels;c++) + /* Special-case LFE */ + if(chans[c].channel == LFE) { - const float pangain{SelectChannelGain(chans[c].channel)}; - - /* Special-case LFE */ - if(chans[c].channel == LFE) + if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) { - if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) - { - const uint idx{Device->channelIdxByName(chans[c].channel)}; - if(idx != InvalidChannelIndex) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base - * pangain; - } - continue; + const auto idx = uint{Device->channelIdxByName(chans[c].channel)}; + if(idx != InvalidChannelIndex) + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain; } + continue; + } - /* Warp the channel position toward the source position as - * the spread decreases. With no spread, all channels are - * at the source position, at full spread (pi*2), each - * channel position is left unchanged. - */ - const float a{1.0f - (inv_pi_v/2.0f)*Spread}; - std::array pos{ - lerpf(chans[c].pos[0], xpos, a), - lerpf(chans[c].pos[1], ypos, a), - lerpf(chans[c].pos[2], zpos, a)}; - const float len{std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2])}; - if(len < 1.0f) - { - pos[0] /= len; - pos[1] /= len; - pos[2] /= len; - } + /* 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 auto a = 1.0f - (al::numbers::inv_pi_v/2.0f)*Spread; + auto pos = std::array{ + lerpf(chans[c].pos[0], xpos, a), + lerpf(chans[c].pos[1], ypos, a), + lerpf(chans[c].pos[2], zpos, a)}; + const auto len = std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2]); + if(len < 1.0f) + { + pos[0] /= len; + pos[1] /= len; + pos[2] /= len; + } - if(Device->mRenderMode == RenderMode::Pairwise) - pos = ScaleAzimuthFront3(pos); - const auto coeffs = CalcDirectionCoeffs(pos, 0.0f); + if(Device->mRenderMode == RenderMode::Pairwise) + pos = ScaleAzimuthFront3(pos); + const auto coeffs = CalcDirectionCoeffs(pos, 0.0f); - ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain, - voice->mChans[c].mDryParams.Gains.Target); - for(uint i{0};i < NumSends;i++) - { - if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, - voice->mChans[c].mWetParams[i].Gains.Target); - } + ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain, + voice->mChans[c].mDryParams.Gains.Target); + for(uint i{0};i < NumSends;i++) + { + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, + voice->mChans[c].mWetParams[i].Gains.Target); } } } @@ -1473,7 +1487,7 @@ void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const Contex /* Calculate the stepping value */ const auto Pitch = static_cast(voice->mFrequency) / - static_cast(Device->Frequency) * props->Pitch; + static_cast(Device->mSampleRate) * props->Pitch; if(Pitch > float{MaxPitch}) voice->mStep = MaxPitch<mDirect.Buffer = Device->Dry.Buffer; std::array SendSlots{}; std::array RoomRolloff{}; - std::bitset UseDryAttnForRoom{0}; for(uint i{0};i < NumSends;i++) { SendSlots[i] = props->Send[i].Slot; if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None) + { SendSlots[i] = nullptr; - else if(SendSlots[i]->AuxSendAuto) + voice->mSend[i].Buffer = {}; + } + else { /* NOTE: Contrary to the EFX docs, the effect's room rolloff factor * applies to the selected distance model along with the source's * room rolloff factor, not necessarily the inverse distance model. - * - * Generic Software also applies these rolloff factors regardless - * of any setting. It doesn't seem to use the effect slot's send - * auto for anything, though as far as I understand, it's supposed - * to control whether the send gets the same gain/gainhf as the - * direct path (excluding the filter). */ RoomRolloff[i] = props->RoomRolloffFactor + SendSlots[i]->RoomRolloff; - } - else - UseDryAttnForRoom.set(i); - if(!SendSlots[i]) - voice->mSend[i].Buffer = {}; - else voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer; + } } /* Transform source to listener space (convert to head relative) */ @@ -1670,28 +1675,34 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa std::array WetGain{}; for(uint i{0};i < NumSends;i++) { - WetGainBase[i] = std::clamp(WetGainBase[i]*WetCone, props->MinGain, props->MaxGain) * + const auto gain = std::clamp(WetGainBase[i]*WetCone, props->MinGain, props->MaxGain) * context->mParams.Gain; - /* If this effect slot's Auxiliary Send Auto is off, then use the dry - * path distance and cone attenuation, otherwise use the wet (room) - * path distance and cone attenuation. The send filter is used instead - * of the direct filter, regardless. - */ - const bool use_room{!UseDryAttnForRoom.test(i)}; - const float gain{use_room ? WetGainBase[i] : DryGainBase}; WetGain[i].Base = std::min(gain * props->Send[i].Gain, GainMixMax); - WetGain[i].HF = (use_room ? WetConeHF : ConeHF) * props->Send[i].GainHF; + WetGain[i].HF = WetConeHF * props->Send[i].GainHF; WetGain[i].LF = props->Send[i].GainLF; } /* Distance-based air absorption and initial send decay. */ if(Distance > props->RefDistance) LIKELY { - const float distance_base{(Distance-props->RefDistance) * props->RolloffFactor}; - 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); + /* FIXME: In keeping with EAX, the base air absorption gain should be + * taken from the reverb property in the "primary fx slot" when it has + * a reverb effect and the environment flag set, and be applied to the + * direct path and all environment sends, rather than each path using + * the air absorption gain associated with the given slot's effect. At + * this point in the mixer, and even in EFX itself, there's no concept + * of a "primary fx slot" so it's unclear which effect slot should be + * checked. + * + * The HF reference is also intended to be handled the same way, but + * again, there's no concept of a "primary fx slot" here and no way to + * know which effect slot to look at for the reference frequency. + */ + const auto distance_units = float{(Distance-props->RefDistance) * props->RolloffFactor}; + const auto distance_meters = float{distance_units * context->mParams.MetersPerUnit}; + const auto absorb = float{distance_meters * props->AirAbsorptionFactor}; + if(absorb > std::numeric_limits::epsilon()) + DryGain.HF *= std::pow(context->mParams.AirAbsorptionGainHF, absorb); /* If the source's Auxiliary Send Filter Gain Auto is off, no extra * adjustment is applied to the send gains. @@ -1701,18 +1712,9 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa if(!SendSlots[i] || !(SendSlots[i]->DecayTime > 0.0f)) continue; - 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. - * - * NOTE: Generic Software applies the initial decay regardless of - * this setting. It doesn't seem to use it for anything, only the - * source's send filter gain auto flag affects this. - */ - if(!SendSlots[i]->AuxSendAuto) - continue; + if(SendSlots[i]->AirAbsorptionGainHF < 1.0f + && absorb > std::numeric_limits::epsilon()) + WetGain[i].HF *= std::pow(SendSlots[i]->AirAbsorptionGainHF, absorb); const float DecayDistance{SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec}; @@ -1726,7 +1728,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa * with the reverb and source rolloff parameters. */ const float baseAttn{DryAttnBase}; - const float fact{distance_base / DecayDistance}; + const float fact{distance_meters / DecayDistance}; const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn}; WetGain[i].Base *= gain; } @@ -1771,7 +1773,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa /* Adjust pitch based on the buffer and output frequencies, and calculate * fixed-point stepping value. */ - Pitch *= static_cast(voice->mFrequency) / static_cast(Device->Frequency); + Pitch *= static_cast(voice->mFrequency) / static_cast(Device->mSampleRate); if(Pitch > float{MaxPitch}) voice->mStep = MaxPitch<mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); - if(evt_vec.first.len < 1) return; + if(evt_vec[0].len < 1) return; - auto &evt = InitAsyncEvent(evt_vec.first.buf); + auto &evt = InitAsyncEvent(evt_vec[0].buf); evt.mId = id; switch(state) { @@ -1962,34 +1964,35 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) { ASSUME(SamplesToDo > 0); - const nanoseconds curtime{device->mClockBase.load(std::memory_order_relaxed) + - nanoseconds{seconds{device->mSamplesDone.load(std::memory_order_relaxed)}}/ - device->Frequency}; + const auto curtime = device->getClockTime(); - for(ContextBase *ctx : *device->mContexts.load(std::memory_order_acquire)) + auto proc_context = [SamplesToDo,curtime](ContextBase *ctx) { const auto auxslotspan = al::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)}; const auto auxslots = auxslotspan.first(auxslotspan.size()>>1); const auto sorted_slots = auxslotspan.last(auxslotspan.size()>>1); - const al::span voices{ctx->getVoicesSpanAcquired()}; + const auto voices = ctx->getVoicesSpanAcquired(); /* Process pending property updates for objects on the context. */ ProcessParamUpdates(ctx, auxslots, sorted_slots, voices); /* Clear auxiliary effect slot mixing buffers. */ - for(EffectSlot *slot : auxslots) + auto clear_wetbuffers = [](EffectSlot *slot) { - for(auto &buffer : slot->Wet.Buffer) - buffer.fill(0.0f); - } + auto clear_buffer = [](const FloatBufferSpan buffer) + { std::fill(buffer.begin(), buffer.end(), 0.0f); }; + std::for_each(slot->Wet.Buffer.begin(), slot->Wet.Buffer.end(), clear_buffer); + }; + std::for_each(auxslots.begin(), auxslots.end(), clear_wetbuffers); /* Process voices that have a playing source. */ - for(Voice *voice : voices) + auto proc_voice = [ctx,curtime,SamplesToDo](Voice *voice) { const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)}; if(vstate != Voice::Stopped && vstate != Voice::Pending) voice->mix(vstate, ctx, curtime, SamplesToDo); - } + }; + std::for_each(voices.begin(), voices.end(), proc_voice); /* Process effects. */ if(!auxslots.empty()) @@ -2000,60 +2003,54 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) */ if(!sorted_slots[0]) { - /* First, copy the slots to the sorted list, then partition the - * sorted list so that all slots without a target slot go to - * the end. + /* First, copy the slots to the sorted list and partition them, + * so that all slots without a target slot go to the end. */ - std::copy(auxslots.begin(), auxslots.end(), sorted_slots.begin()); - auto split_point = std::partition(sorted_slots.begin(), sorted_slots.end(), - [](const EffectSlot *slot) noexcept -> bool - { return slot->Target != nullptr; }); + auto has_target = [](const EffectSlot *slot) noexcept -> bool + { return slot->Target != nullptr; }; + auto split_point = std::partition_copy(auxslots.rbegin(), auxslots.rend(), + sorted_slots.begin(), sorted_slots.rbegin(), has_target).first; /* There must be at least one slot without a slot target. */ assert(split_point != sorted_slots.end()); - /* Simple case: no more than 1 slot has a target slot. Either - * all slots go right to the output, or the remaining one must - * target an already-partitioned slot. + /* Starting from the back of the sorted list, continue + * partitioning the front of the list given each target until + * all targets are accounted for. This ensures all slots + * without a target go last, all slots directly targeting those + * last slots go second-to-last, all slots directly targeting + * those second-last slots go third-to-last, etc. */ - if(split_point - sorted_slots.begin() > 1) + auto next_target = sorted_slots.end(); + while(std::distance(sorted_slots.begin(), split_point) > 1) { - /* At least two slots target other slots. Starting from the - * back of the sorted list, continue partitioning the front - * of the list given each target until all targets are - * accounted for. This ensures all slots without a target - * go last, all slots directly targeting those last slots - * go second-to-last, all slots directly targeting those - * second-last slots go third-to-last, etc. + /* This shouldn't happen, but if there's unsorted slots + * left that don't target any sorted slots, they can't + * contribute to the output, so leave them. */ - auto next_target = sorted_slots.end(); - do { - /* This shouldn't happen, but if there's unsorted slots - * left that don't target any sorted slots, they can't - * contribute to the output, so leave them. - */ - if(next_target == split_point) UNLIKELY - break; + if(next_target == split_point) UNLIKELY + break; - --next_target; - split_point = std::partition(sorted_slots.begin(), split_point, - [next_target](const EffectSlot *slot) noexcept -> bool - { return slot->Target != *next_target; }); - } while(split_point - sorted_slots.begin() > 1); + --next_target; + auto not_next = [next_target](const EffectSlot *slot) noexcept -> bool + { return slot->Target != *next_target; }; + split_point = std::partition(sorted_slots.begin(), split_point, not_next); } } - for(const EffectSlot *slot : sorted_slots) + auto proc_slot = [SamplesToDo](const EffectSlot *slot) { EffectState *state{slot->mEffectState.get()}; state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget); - } + }; + std::for_each(sorted_slots.begin(), sorted_slots.end(), proc_slot); } /* Signal the event handler if there are any events to read. */ - RingBuffer *ring{ctx->mAsyncEvents.get()}; - if(ring->readSpace() > 0) + if(RingBuffer *ring{ctx->mAsyncEvents.get()}; ring->readSpace() > 0) ctx->mEventSem.post(); - } + }; + const auto contexts = al::span{*device->mContexts.load(std::memory_order_acquire)}; + std::for_each(contexts.begin(), contexts.end(), proc_context); } @@ -2152,25 +2149,47 @@ void Write(const al::span InBuffer, void *OutBuffer, cons ASSUME(FrameStep > 0); ASSUME(SamplesToDo > 0); - const auto output = al::span{static_cast(OutBuffer), (Offset+SamplesToDo)*FrameStep} - .subspan(Offset*FrameStep); - size_t c{0}; - for(const FloatBufferLine &inbuf : InBuffer) + /* Some Clang versions don't like calling subspan on an rvalue here. */ + const auto output_ = al::span{static_cast(OutBuffer), (Offset+SamplesToDo)*FrameStep}; + const auto output = output_.subspan(Offset*FrameStep); + + /* If there's extra channels in the interleaved output buffer to skip, + * clear the whole output buffer. This is simpler to ensure the extra + * channels are silent than trying to clear just the extra channels. + */ + if(FrameStep > InBuffer.size()) + std::fill(output.begin(), output.end(), SampleConv(0.0f)); + + auto outbase = output.begin(); + for(const auto &srcbuf : InBuffer) { - auto out = output.begin(); - auto conv_sample = [FrameStep,c,&out](const float s) noexcept + const auto src = al::span{srcbuf}.first(SamplesToDo); + auto out = outbase++; + + *out = SampleConv(src.front()); + std::for_each(src.begin()+1, src.end(), [FrameStep,&out](const float s) noexcept { - out[c] = SampleConv(s); out += ptrdiff_t(FrameStep); - }; - std::for_each_n(inbuf.cbegin(), SamplesToDo, conv_sample); - ++c; + *out = SampleConv(s); + }); } - if(const size_t extra{FrameStep - c}) +} + +template +void Write(const al::span InBuffer, al::span OutBuffers, + const size_t Offset, const size_t SamplesToDo) +{ + ASSUME(SamplesToDo > 0); + + auto srcbuf = InBuffer.cbegin(); + for(auto *dstbuf : OutBuffers) { - const auto silence = SampleConv(0.0f); - for(size_t i{0};i < SamplesToDo;++i) - std::fill_n(&output[i*FrameStep + c], extra, silence); + const auto src = al::span{*srcbuf}.first(SamplesToDo); + /* Some Clang versions don't like calling subspan on an rvalue here. */ + const auto dst_ = al::span{static_cast(dstbuf), Offset+SamplesToDo}; + const auto dst = dst_.subspan(Offset); + std::transform(src.begin(), src.end(), dst.begin(), SampleConv); + ++srcbuf; } } @@ -2195,10 +2214,10 @@ uint DeviceBase::renderSamples(const uint numSamples) * also guarantees a stable conversion. */ auto samplesDone = mSamplesDone.load(std::memory_order_relaxed) + samplesToDo; - auto clockBase = mClockBase.load(std::memory_order_relaxed) + - std::chrono::seconds{samplesDone/Frequency}; - mSamplesDone.store(samplesDone%Frequency, std::memory_order_relaxed); - mClockBase.store(clockBase, std::memory_order_relaxed); + auto clockBaseSec = mClockBaseSec.load(std::memory_order_relaxed) + + seconds32{samplesDone/mSampleRate}; + mSamplesDone.store(samplesDone%mSampleRate, std::memory_order_relaxed); + mClockBaseSec.store(clockBaseSec, std::memory_order_relaxed); } /* Apply any needed post-process for finalizing the Dry mix to the RealOut @@ -2207,7 +2226,7 @@ uint DeviceBase::renderSamples(const uint numSamples) postProcess(samplesToDo); /* Apply compression, limiting sample amplitude if needed or desired. */ - if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data()); + if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer); /* Apply delays and attenuation for mismatched speaker distances. */ if(ChannelDelays) @@ -2222,7 +2241,7 @@ uint DeviceBase::renderSamples(const uint numSamples) return samplesToDo; } -void DeviceBase::renderSamples(const al::span outBuffers, const uint numSamples) +void DeviceBase::renderSamples(const al::span outBuffers, const uint numSamples) { FPUCtl mixer_mode{}; uint total{0}; @@ -2230,13 +2249,19 @@ void DeviceBase::renderSamples(const al::span outBuffers, const uint num { const uint samplesToDo{renderSamples(todo)}; - auto srcbuf = RealOut.Buffer.cbegin(); - for(auto *dstbuf : outBuffers) + switch(FmtType) { - const auto dst = al::span{dstbuf, numSamples}.subspan(total); - std::copy_n(srcbuf->cbegin(), samplesToDo, dst.begin()); - ++srcbuf; +#define HANDLE_WRITE(T) case T: \ + Write>(RealOut.Buffer, outBuffers, total, samplesToDo); break; + HANDLE_WRITE(DevFmtByte) + HANDLE_WRITE(DevFmtUByte) + HANDLE_WRITE(DevFmtShort) + HANDLE_WRITE(DevFmtUShort) + HANDLE_WRITE(DevFmtInt) + HANDLE_WRITE(DevFmtUInt) + HANDLE_WRITE(DevFmtFloat) } +#undef HANDLE_WRITE total += samplesToDo; } @@ -2274,7 +2299,7 @@ void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const siz } } -void DeviceBase::handleDisconnect(const char *msg, ...) +void DeviceBase::doDisconnect(std::string msg) { const auto mixLock = getWriteMixLock(); @@ -2282,29 +2307,12 @@ void DeviceBase::handleDisconnect(const char *msg, ...) { AsyncEvent evt{std::in_place_type}; auto &disconnect = std::get(evt); - - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - va_list args, args2; - va_start(args, msg); - va_copy(args2, args); - if(int msglen{vsnprintf(nullptr, 0, msg, args)}; msglen > 0) - { - disconnect.msg.resize(static_cast(msglen)+1_uz); - vsnprintf(disconnect.msg.data(), disconnect.msg.size(), msg, args2); - } - else - disconnect.msg = ""; - va_end(args2); - va_end(args); - /* NOLINTEND(*-array-to-pointer-decay) */ - - while(!disconnect.msg.empty() && disconnect.msg.back() == '\0') - disconnect.msg.pop_back(); + disconnect.msg = std::move(msg); for(ContextBase *ctx : *mContexts.load()) { RingBuffer *ring{ctx->mAsyncEvents.get()}; - auto evt_data = ring->getWriteVector().first; + auto evt_data = ring->getWriteVector()[0]; if(evt_data.len > 0) { al::construct_at(reinterpret_cast(evt_data.buf), evt); diff --git a/Engine/lib/openal-soft/alc/alu.h b/Engine/lib/openal-soft/alc/alu.h index ef7ddd4c5..6da8d9174 100644 --- a/Engine/lib/openal-soft/alc/alu.h +++ b/Engine/lib/openal-soft/alc/alu.h @@ -11,6 +11,9 @@ struct EffectSlot; enum class StereoEncoding : std::uint8_t; +namespace al { +struct Device; +} // namespace al constexpr float GainMixMax{1000.0f}; /* +60dB */ @@ -31,7 +34,7 @@ void aluInit(CompatFlagBitset flags, const float nfcscale); * Set up the appropriate panning method and mixing method given the device * properties. */ -void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional stereomode); +void aluInitRenderer(al::Device *device, int hrtf_id, std::optional stereomode); void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context); diff --git a/Engine/lib/openal-soft/alc/backends/alsa.cpp b/Engine/lib/openal-soft/alc/backends/alsa.cpp index b0acf015d..a241be7ab 100644 --- a/Engine/lib/openal-soft/alc/backends/alsa.cpp +++ b/Engine/lib/openal-soft/alc/backends/alsa.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -38,16 +37,15 @@ #include #include -#include "albit.h" #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" -#include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" +#include "fmt/core.h" #include "ringbuffer.h" #include @@ -60,7 +58,7 @@ using namespace std::string_view_literals; [[nodiscard]] constexpr auto GetDefaultName() noexcept { return "ALSA Default"sv; } -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD #define ALSA_FUNCS(MAGIC) \ MAGIC(snd_strerror); \ MAGIC(snd_pcm_open); \ @@ -272,7 +270,7 @@ struct SndCtlCardInfo { SndCtlCardInfo& operator=(const SndCtlCardInfo&) = delete; [[nodiscard]] - operator snd_ctl_card_info_t*() const noexcept { return mInfo; } + operator snd_ctl_card_info_t*() const noexcept { return mInfo; } /* NOLINT(google-explicit-constructor) */ }; struct SndPcmInfo { @@ -284,7 +282,7 @@ struct SndPcmInfo { SndPcmInfo& operator=(const SndPcmInfo&) = delete; [[nodiscard]] - operator snd_pcm_info_t*() const noexcept { return mInfo; } + operator snd_pcm_info_t*() const noexcept { return mInfo; } /* NOLINT(google-explicit-constructor) */ }; struct SndCtl { @@ -299,7 +297,7 @@ struct SndCtl { auto open(const char *name, int mode) { return snd_ctl_open(&mHandle, name, mode); } [[nodiscard]] - operator snd_ctl_t*() const noexcept { return mHandle; } + operator snd_ctl_t*() const noexcept { return mHandle; } /* NOLINT(google-explicit-constructor) */ }; @@ -324,15 +322,15 @@ std::vector probe_devices(snd_pcm_stream_t stream) const size_t seppos{customdevs->find('=', curpos)}; if(seppos == curpos || seppos >= nextpos) { - const std::string spec{customdevs->substr(curpos, nextpos-curpos)}; - ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str()); + const auto spec = std::string_view{*customdevs}.substr(curpos, nextpos-curpos); + ERR("Invalid ALSA device specification \"{}\"", spec); } else { const std::string_view strview{*customdevs}; const auto &entry = devlist.emplace_back(strview.substr(curpos, seppos-curpos), strview.substr(seppos+1, nextpos-seppos-1)); - TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); + TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name); } if(nextpos < customdevs->length()) @@ -354,13 +352,13 @@ std::vector probe_devices(snd_pcm_stream_t stream) err = handle.open(name.c_str(), 0); if(err < 0) { - ERR("control open (hw:%d): %s\n", card, snd_strerror(err)); + ERR("control open (hw:{}): {}", card, snd_strerror(err)); continue; } err = snd_ctl_card_info(handle, info); if(err < 0) { - ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err)); + ERR("control hardware info (hw:{}): {}", card, snd_strerror(err)); continue; } @@ -375,7 +373,7 @@ std::vector probe_devices(snd_pcm_stream_t stream) while(true) { if(snd_ctl_pcm_next_device(handle, &dev) < 0) - ERR("snd_ctl_pcm_next_device failed\n"); + ERR("snd_ctl_pcm_next_device failed"); if(dev < 0) break; snd_pcm_info_set_device(pcminfo, static_cast(dev)); @@ -385,42 +383,28 @@ std::vector probe_devices(snd_pcm_stream_t stream) if(err < 0) { if(err != -ENOENT) - ERR("control digital audio info (hw:%d): %s\n", card, snd_strerror(err)); + ERR("control digital audio info (hw:{}): {}", card, snd_strerror(err)); continue; } /* "prefix-cardid-dev" */ - name = prefix_name(stream); - name += '-'; - name += cardid; - name += '-'; - name += std::to_string(dev); - const std::string device_prefix{ConfigValueStr({}, "alsa"sv, name) + name = fmt::format("{}-{}-{}", prefix_name(stream), cardid, dev); + const auto device_prefix = std::string{ConfigValueStr({}, "alsa"sv, name) .value_or(card_prefix)}; /* "CardName, PcmName (CARD=cardid,DEV=dev)" */ - name = cardname; - name += ", "; - name += snd_pcm_info_get_name(pcminfo); - name += " (CARD="; - name += cardid; - name += ",DEV="; - name += std::to_string(dev); - name += ')'; + name = fmt::format("{}, {} (CARD={},DEV={})", cardname, snd_pcm_info_get_name(pcminfo), + cardid, dev); /* "devprefixCARD=cardid,DEV=dev" */ - std::string device{device_prefix}; - device += "CARD="; - device += cardid; - device += ",DEV="; - device += std::to_string(dev); + auto device = fmt::format("{}CARD={},DEV={}", device_prefix, cardid, dev); const auto &entry = devlist.emplace_back(std::move(name), std::move(device)); - TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); + TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name); } } if(err < 0) - ERR("snd_card_next failed: %s\n", snd_strerror(err)); + ERR("snd_card_next failed: {}", snd_strerror(err)); return devlist; } @@ -470,7 +454,7 @@ int verify_state(snd_pcm_t *handle) struct AlsaPlayback final : public BackendBase { - AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~AlsaPlayback() override; int mixerProc(); @@ -507,29 +491,29 @@ int AlsaPlayback::mixerProc() SetRTPriority(); althrd_setname(GetMixerThreadName()); - const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; - const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; + const snd_pcm_uframes_t update_size{mDevice->mUpdateSize}; + const snd_pcm_uframes_t buffer_size{mDevice->mBufferSize}; while(!mKillNow.load(std::memory_order_acquire)) { int state{verify_state(mPcmHandle)}; if(state < 0) { - ERR("Invalid state detected: %s\n", snd_strerror(state)); - mDevice->handleDisconnect("Bad state: %s", snd_strerror(state)); + ERR("Invalid state detected: {}", snd_strerror(state)); + mDevice->handleDisconnect("Bad state: {}", snd_strerror(state)); break; } snd_pcm_sframes_t avails{snd_pcm_avail_update(mPcmHandle)}; if(avails < 0) { - ERR("available update failed: %s\n", snd_strerror(static_cast(avails))); + ERR("available update failed: {}", snd_strerror(static_cast(avails))); continue; } snd_pcm_uframes_t avail{static_cast(avails)}; if(avail > buffer_size) { - WARN("available samples exceeds the buffer size\n"); + WARN("available samples exceeds the buffer size"); snd_pcm_reset(mPcmHandle); continue; } @@ -542,12 +526,12 @@ int AlsaPlayback::mixerProc() int err{snd_pcm_start(mPcmHandle)}; if(err < 0) { - ERR("start failed: %s\n", snd_strerror(err)); + ERR("start failed: {}", snd_strerror(err)); continue; } } if(snd_pcm_wait(mPcmHandle, 1000) == 0) - ERR("Wait timeout... buffer size too low?\n"); + ERR("Wait timeout... buffer size too low?"); continue; } avail -= avail%update_size; @@ -563,7 +547,7 @@ int AlsaPlayback::mixerProc() int err{snd_pcm_mmap_begin(mPcmHandle, &areas, &offset, &frames)}; if(err < 0) { - ERR("mmap begin error: %s\n", snd_strerror(err)); + ERR("mmap begin error: {}", snd_strerror(err)); break; } @@ -574,7 +558,7 @@ int AlsaPlayback::mixerProc() snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)}; if(commitres < 0 || static_cast(commitres) != frames) { - ERR("mmap commit error: %s\n", + ERR("mmap commit error: {}", snd_strerror(commitres >= 0 ? -EPIPE : static_cast(commitres))); break; } @@ -591,28 +575,28 @@ int AlsaPlayback::mixerNoMMapProc() SetRTPriority(); althrd_setname(GetMixerThreadName()); - const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; - const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; + const snd_pcm_uframes_t update_size{mDevice->mUpdateSize}; + const snd_pcm_uframes_t buffer_size{mDevice->mBufferSize}; while(!mKillNow.load(std::memory_order_acquire)) { int state{verify_state(mPcmHandle)}; if(state < 0) { - ERR("Invalid state detected: %s\n", snd_strerror(state)); - mDevice->handleDisconnect("Bad state: %s", snd_strerror(state)); + ERR("Invalid state detected: {}", snd_strerror(state)); + mDevice->handleDisconnect("Bad state: {}", snd_strerror(state)); break; } snd_pcm_sframes_t avail{snd_pcm_avail_update(mPcmHandle)}; if(avail < 0) { - ERR("available update failed: %s\n", snd_strerror(static_cast(avail))); + ERR("available update failed: {}", snd_strerror(static_cast(avail))); continue; } if(static_cast(avail) > buffer_size) { - WARN("available samples exceeds the buffer size\n"); + WARN("available samples exceeds the buffer size"); snd_pcm_reset(mPcmHandle); continue; } @@ -624,12 +608,12 @@ int AlsaPlayback::mixerNoMMapProc() int err{snd_pcm_start(mPcmHandle)}; if(err < 0) { - ERR("start failed: %s\n", snd_strerror(err)); + ERR("start failed: {}", snd_strerror(err)); continue; } } if(snd_pcm_wait(mPcmHandle, 1000) == 0) - ERR("Wait timeout... buffer size too low?\n"); + ERR("Wait timeout... buffer size too low?"); continue; } @@ -686,7 +670,7 @@ void AlsaPlayback::open(std::string_view name) [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; driver = iter->device_name; } else @@ -695,13 +679,13 @@ void AlsaPlayback::open(std::string_view name) if(auto driveropt = ConfigValueStr({}, "alsa"sv, "device"sv)) driver = std::move(driveropt).value(); } - TRACE("Opening device \"%s\"\n", driver.c_str()); + TRACE("Opening device \"{}\"", driver); snd_pcm_t *pcmHandle{}; 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.c_str()}; + "Could not open ALSA device \"{}\"", driver}; if(mPcmHandle) snd_pcm_close(mPcmHandle); mPcmHandle = pcmHandle; @@ -709,7 +693,7 @@ void AlsaPlayback::open(std::string_view name) /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ snd_config_update_free_global(); - mDevice->DeviceName = name; + mDeviceName = name; } bool AlsaPlayback::reset() @@ -740,15 +724,15 @@ bool AlsaPlayback::reset() break; } - bool allowmmap{GetConfigValueBool(mDevice->DeviceName, "alsa"sv, "mmap"sv, true)}; - uint periodLen{static_cast(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)}; - uint bufferLen{static_cast(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)}; - uint rate{mDevice->Frequency}; + bool allowmmap{GetConfigValueBool(mDevice->mDeviceName, "alsa"sv, "mmap"sv, true)}; + uint periodLen{static_cast(mDevice->mUpdateSize * 1000000_u64 / mDevice->mSampleRate)}; + uint bufferLen{static_cast(mDevice->mBufferSize * 1000000_u64 / mDevice->mSampleRate)}; + uint rate{mDevice->mSampleRate}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ if(int err{x}; err < 0) \ - throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ + throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \ snd_strerror(err)}; \ } while(0) CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get())); @@ -798,21 +782,21 @@ bool AlsaPlayback::reset() else mDevice->FmtChans = DevFmtStereo; } /* set rate (implicitly constrains period/buffer parameters) */ - if(!GetConfigValueBool(mDevice->DeviceName, "alsa", "allow-resampler", false) + if(!GetConfigValueBool(mDevice->mDeviceName, "alsa", "allow-resampler", false) || !mDevice->Flags.test(FrequencyRequest)) { if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0) - WARN("Failed to disable ALSA resampler\n"); + WARN("Failed to disable ALSA resampler"); } else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 1) < 0) - WARN("Failed to enable ALSA resampler\n"); + WARN("Failed to enable ALSA resampler"); CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr)); /* set period time (implicitly constrains period/buffer parameters) */ if(int err{snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)}; err < 0) - ERR("snd_pcm_hw_params_set_period_time_near failed: %s\n", snd_strerror(err)); + ERR("snd_pcm_hw_params_set_period_time_near failed: {}", snd_strerror(err)); /* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */ if(int err{snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)}; err < 0) - ERR("snd_pcm_hw_params_set_buffer_time_near failed: %s\n", snd_strerror(err)); + ERR("snd_pcm_hw_params_set_buffer_time_near failed: {}", snd_strerror(err)); /* install and prepare hardware configuration */ CHECK(snd_pcm_hw_params(mPcmHandle, hp.get())); @@ -835,9 +819,9 @@ bool AlsaPlayback::reset() #undef CHECK sp = nullptr; - mDevice->BufferSize = static_cast(bufferSizeInFrames); - mDevice->UpdateSize = static_cast(periodSizeInFrames); - mDevice->Frequency = rate; + mDevice->mBufferSize = static_cast(bufferSizeInFrames); + mDevice->mUpdateSize = static_cast(periodSizeInFrames); + mDevice->mSampleRate = rate; setDefaultChannelOrder(); @@ -850,7 +834,7 @@ void AlsaPlayback::start() HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ if(int err{x}; err < 0) \ - throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ + throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \ snd_strerror(err)}; \ } while(0) CHECK(snd_pcm_hw_params_current(mPcmHandle, hp.get())); @@ -861,7 +845,7 @@ void AlsaPlayback::start() int (AlsaPlayback::*thread_func)(){}; if(access == SND_PCM_ACCESS_RW_INTERLEAVED) { - auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize); + auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->mUpdateSize); mBuffer.resize(static_cast(datalen)); thread_func = &AlsaPlayback::mixerNoMMapProc; } @@ -874,11 +858,11 @@ void AlsaPlayback::start() try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(thread_func), this}; + mThread = std::thread{thread_func, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -891,7 +875,7 @@ void AlsaPlayback::stop() mBuffer.clear(); int err{snd_pcm_drop(mPcmHandle)}; if(err < 0) - ERR("snd_pcm_drop failed: %s\n", snd_strerror(err)); + ERR("snd_pcm_drop failed: {}", snd_strerror(err)); } ClockLatency AlsaPlayback::getClockLatency() @@ -903,18 +887,18 @@ ClockLatency AlsaPlayback::getClockLatency() int err{snd_pcm_delay(mPcmHandle, &delay)}; if(err < 0) { - ERR("Failed to get pcm delay: %s\n", snd_strerror(err)); + ERR("Failed to get pcm delay: {}", snd_strerror(err)); delay = 0; } ret.Latency = std::chrono::seconds{std::max(0, delay)}; - ret.Latency /= mDevice->Frequency; + ret.Latency /= mDevice->mSampleRate; return ret; } struct AlsaCapture final : public BackendBase { - AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~AlsaCapture() override; void open(std::string_view name) override; @@ -954,7 +938,7 @@ void AlsaCapture::open(std::string_view name) [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; driver = iter->device_name; } else @@ -964,10 +948,10 @@ void AlsaCapture::open(std::string_view name) driver = std::move(driveropt).value(); } - TRACE("Opening device \"%s\"\n", driver.c_str()); + TRACE("Opening device \"{}\"", driver); if(int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; err < 0) throw al::backend_exception{al::backend_error::NoDevice, - "Could not open ALSA device \"%s\"", driver.c_str()}; + "Could not open ALSA device \"{}\"", driver}; /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ snd_config_update_free_global(); @@ -998,16 +982,16 @@ void AlsaCapture::open(std::string_view name) break; } - snd_pcm_uframes_t bufferSizeInFrames{std::max(mDevice->BufferSize, - 100u*mDevice->Frequency/1000u)}; - snd_pcm_uframes_t periodSizeInFrames{std::min(mDevice->BufferSize, - 25u*mDevice->Frequency/1000u)}; + snd_pcm_uframes_t bufferSizeInFrames{std::max(mDevice->mBufferSize, + 100u*mDevice->mSampleRate/1000u)}; + snd_pcm_uframes_t periodSizeInFrames{std::min(mDevice->mBufferSize, + 25u*mDevice->mSampleRate/1000u)}; bool needring{false}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ if(int err{x}; err < 0) \ - throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ + throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \ snd_strerror(err)}; \ } while(0) CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get())); @@ -1018,11 +1002,11 @@ void AlsaCapture::open(std::string_view name) /* set channels (implicitly sets frame bits) */ CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt())); /* set rate (implicitly constrains period/buffer parameters) */ - CHECK(snd_pcm_hw_params_set_rate(mPcmHandle, hp.get(), mDevice->Frequency, 0)); + CHECK(snd_pcm_hw_params_set_rate(mPcmHandle, hp.get(), mDevice->mSampleRate, 0)); /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */ if(snd_pcm_hw_params_set_buffer_size_min(mPcmHandle, hp.get(), &bufferSizeInFrames) < 0) { - TRACE("Buffer too large, using intermediate ring buffer\n"); + TRACE("Buffer too large, using intermediate ring buffer"); needring = true; CHECK(snd_pcm_hw_params_set_buffer_size_near(mPcmHandle, hp.get(), &bufferSizeInFrames)); } @@ -1036,20 +1020,20 @@ void AlsaCapture::open(std::string_view name) hp = nullptr; if(needring) - mRing = RingBuffer::Create(mDevice->BufferSize, mDevice->frameSizeFromFmt(), false); + mRing = RingBuffer::Create(mDevice->mBufferSize, mDevice->frameSizeFromFmt(), false); - mDevice->DeviceName = name; + mDeviceName = name; } void AlsaCapture::start() { if(int err{snd_pcm_prepare(mPcmHandle)}; err < 0) - throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s", + throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: {}", snd_strerror(err)}; if(int err{snd_pcm_start(mPcmHandle)}; err < 0) - throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s", + throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: {}", snd_strerror(err)}; mDoCapture = true; @@ -1073,7 +1057,7 @@ void AlsaCapture::stop() mBuffer = std::move(temp); } if(int err{snd_pcm_drop(mPcmHandle)}; err < 0) - ERR("drop failed: %s\n", snd_strerror(err)); + ERR("snd_pcm_drop failed: {}", snd_strerror(err)); mDoCapture = false; } @@ -1109,7 +1093,7 @@ void AlsaCapture::captureSamples(std::byte *buffer, uint samples) amt = snd_pcm_readi(mPcmHandle, al::to_address(outiter), samples); if(amt < 0) { - ERR("read error: %s\n", snd_strerror(static_cast(amt))); + ERR("read error: {}", snd_strerror(static_cast(amt))); if(amt == -EAGAIN) continue; @@ -1123,8 +1107,8 @@ void AlsaCapture::captureSamples(std::byte *buffer, uint samples) if(amt < 0) { const char *err{snd_strerror(static_cast(amt))}; - ERR("restore error: %s\n", err); - mDevice->handleDisconnect("Capture recovery failure: %s", err); + ERR("restore error: {}", err); + mDevice->handleDisconnect("Capture recovery failure: {}", err); break; } /* If the amount available is less than what's asked, we lost it @@ -1149,7 +1133,7 @@ uint AlsaCapture::availableSamples() avail = snd_pcm_avail_update(mPcmHandle); if(avail < 0) { - ERR("avail update failed: %s\n", snd_strerror(static_cast(avail))); + ERR("snd_pcm_avail_update failed: {}", snd_strerror(static_cast(avail))); avail = snd_pcm_recover(mPcmHandle, static_cast(avail), 1); if(avail >= 0) @@ -1162,29 +1146,29 @@ uint AlsaCapture::availableSamples() if(avail < 0) { const char *err{snd_strerror(static_cast(avail))}; - ERR("restore error: %s\n", err); - mDevice->handleDisconnect("Capture recovery failure: %s", err); + ERR("restore error: {}", err); + mDevice->handleDisconnect("Capture recovery failure: {}", err); } } if(!mRing) { - if(avail < 0) avail = 0; + avail = std::max(avail, 0); avail += snd_pcm_bytes_to_frames(mPcmHandle, static_cast(mBuffer.size())); - if(avail > mLastAvail) mLastAvail = avail; + mLastAvail = std::max(mLastAvail, avail); return static_cast(mLastAvail); } while(avail > 0) { auto vec = mRing->getWriteVector(); - if(vec.first.len == 0) break; + if(vec[0].len == 0) break; - snd_pcm_sframes_t amt{std::min(static_cast(vec.first.len), avail)}; - amt = snd_pcm_readi(mPcmHandle, vec.first.buf, static_cast(amt)); + snd_pcm_sframes_t amt{std::min(static_cast(vec[0].len), avail)}; + amt = snd_pcm_readi(mPcmHandle, vec[0].buf, static_cast(amt)); if(amt < 0) { - ERR("read error: %s\n", snd_strerror(static_cast(amt))); + ERR("read error: {}", snd_strerror(static_cast(amt))); if(amt == -EAGAIN) continue; @@ -1199,8 +1183,8 @@ uint AlsaCapture::availableSamples() if(amt < 0) { const char *err{snd_strerror(static_cast(amt))}; - ERR("restore error: %s\n", err); - mDevice->handleDisconnect("Capture recovery failure: %s", err); + ERR("restore error: {}", err); + mDevice->handleDisconnect("Capture recovery failure: {}", err); break; } avail = amt; @@ -1222,11 +1206,11 @@ ClockLatency AlsaCapture::getClockLatency() int err{snd_pcm_delay(mPcmHandle, &delay)}; if(err < 0) { - ERR("Failed to get pcm delay: %s\n", snd_strerror(err)); + ERR("Failed to get pcm delay: {}", snd_strerror(err)); delay = 0; } ret.Latency = std::chrono::seconds{std::max(0, delay)}; - ret.Latency /= mDevice->Frequency; + ret.Latency /= mDevice->mSampleRate; return ret; } @@ -1236,13 +1220,13 @@ ClockLatency AlsaCapture::getClockLatency() bool AlsaBackendFactory::init() { -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD if(!alsa_handle) { alsa_handle = LoadLib("libasound.so.2"); if(!alsa_handle) { - WARN("Failed to load %s\n", "libasound.so.2"); + WARN("Failed to load {}", "libasound.so.2"); return false; } @@ -1256,7 +1240,7 @@ bool AlsaBackendFactory::init() if(!missing_funcs.empty()) { - WARN("Missing expected functions:%s\n", missing_funcs.c_str()); + WARN("Missing expected functions:{}", missing_funcs); CloseLib(alsa_handle); alsa_handle = nullptr; return false; diff --git a/Engine/lib/openal-soft/alc/backends/base.cpp b/Engine/lib/openal-soft/alc/backends/base.cpp index 4f90fef1f..9d9ce4a06 100644 --- a/Engine/lib/openal-soft/alc/backends/base.cpp +++ b/Engine/lib/openal-soft/alc/backends/base.cpp @@ -3,26 +3,18 @@ #include "base.h" -#include #include #include +#include #include "core/devformat.h" namespace al { +auto backend_exception::make_string(fmt::string_view fmt, fmt::format_args args) -> std::string +{ return fmt::vformat(fmt, std::move(args)); } -backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code} -{ - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - std::va_list args; - va_start(args, msg); - setMessage(msg, args); - va_end(args); - /* NOLINTEND(*-array-to-pointer-decay) */ -} backend_exception::~backend_exception() = default; - } // namespace al @@ -50,8 +42,8 @@ ClockLatency BackendBase::getClockLatency() * any given time during playback. Without a more accurate measurement from * the output, this is an okay approximation. */ - ret.Latency = std::chrono::seconds{mDevice->BufferSize - mDevice->UpdateSize}; - ret.Latency /= mDevice->Frequency; + ret.Latency = std::chrono::seconds{mDevice->mBufferSize - mDevice->mUpdateSize}; + ret.Latency /= mDevice->mSampleRate; return ret; } diff --git a/Engine/lib/openal-soft/alc/backends/base.h b/Engine/lib/openal-soft/alc/backends/base.h index 2503c3df2..4e59836d1 100644 --- a/Engine/lib/openal-soft/alc/backends/base.h +++ b/Engine/lib/openal-soft/alc/backends/base.h @@ -5,14 +5,14 @@ #include #include #include -#include #include #include #include +#include "alc/events.h" #include "core/device.h" #include "core/except.h" -#include "alc/events.h" +#include "fmt/core.h" using uint = unsigned int; @@ -35,11 +35,12 @@ struct BackendBase { virtual ClockLatency getClockLatency(); DeviceBase *const mDevice; + std::string mDeviceName; BackendBase() = delete; BackendBase(const BackendBase&) = delete; BackendBase(BackendBase&&) = delete; - BackendBase(DeviceBase *device) noexcept : mDevice{device} { } + explicit BackendBase(DeviceBase *device) noexcept : mDevice{device} { } virtual ~BackendBase() = default; void operator=(const BackendBase&) = delete; @@ -102,15 +103,20 @@ enum class backend_error { class backend_exception final : public base_exception { backend_error mErrorCode; + static auto make_string(fmt::string_view fmt, fmt::format_args args) -> std::string; + public: -#ifdef __MINGW32__ - [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] -#else - [[gnu::format(printf, 3, 4)]] -#endif - backend_exception(backend_error code, const char *msg, ...); + template + backend_exception(backend_error code, fmt::format_string fmt, Args&& ...args) + : base_exception{make_string(fmt, fmt::make_format_args(args...))}, mErrorCode{code} + { } + backend_exception(const backend_exception&) = default; + backend_exception(backend_exception&&) = default; ~backend_exception() override; + backend_exception& operator=(const backend_exception&) = default; + backend_exception& operator=(backend_exception&&) = default; + [[nodiscard]] auto errorCode() const noexcept -> backend_error { return mErrorCode; } }; diff --git a/Engine/lib/openal-soft/alc/backends/coreaudio.cpp b/Engine/lib/openal-soft/alc/backends/coreaudio.cpp index fd4abce04..138859cbc 100644 --- a/Engine/lib/openal-soft/alc/backends/coreaudio.cpp +++ b/Engine/lib/openal-soft/alc/backends/coreaudio.cpp @@ -24,7 +24,9 @@ #include #include +#include #include +#include #include #include #include @@ -32,13 +34,13 @@ #include #include #include -#include #include "alnumeric.h" #include "alstring.h" #include "core/converter.h" #include "core/device.h" #include "core/logging.h" +#include "fmt/core.h" #include "ringbuffer.h" #include @@ -56,10 +58,40 @@ namespace { constexpr auto OutputElement = 0; constexpr auto InputElement = 1; +// These following arrays should always be defined in ascending AudioChannelLabel value order +constexpr std::array MonoChanMap { kAudioChannelLabel_Mono }; +constexpr std::array StereoChanMap { kAudioChannelLabel_Left, kAudioChannelLabel_Right}; +constexpr std::array QuadChanMap { + kAudioChannelLabel_Left, kAudioChannelLabel_Right, + kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround +}; +constexpr std::array X51ChanMap { + kAudioChannelLabel_Left, kAudioChannelLabel_Right, + kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen, + kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround +}; +constexpr std::array X51RearChanMap { + kAudioChannelLabel_Left, kAudioChannelLabel_Right, + kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen, + kAudioChannelLabel_RearSurroundRight, kAudioChannelLabel_RearSurroundLeft +}; +constexpr std::array X61ChanMap { + kAudioChannelLabel_Left, kAudioChannelLabel_Right, + kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen, + kAudioChannelLabel_CenterSurround, + kAudioChannelLabel_RearSurroundRight, kAudioChannelLabel_RearSurroundLeft +}; +constexpr std::array X71ChanMap { + kAudioChannelLabel_Left, kAudioChannelLabel_Right, + kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen, + kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, + kAudioChannelLabel_LeftCenter, kAudioChannelLabel_RightCenter +}; + struct FourCCPrinter { char mString[sizeof(UInt32) + 1]{}; - constexpr FourCCPrinter(UInt32 code) noexcept + explicit constexpr FourCCPrinter(UInt32 code) noexcept { for(size_t i{0};i < sizeof(UInt32);++i) { @@ -73,7 +105,7 @@ struct FourCCPrinter { code >>= 8; } } - constexpr FourCCPrinter(int code) noexcept : FourCCPrinter{static_cast(code)} { } + explicit constexpr FourCCPrinter(OSStatus code) noexcept : FourCCPrinter{static_cast(code)} { } constexpr const char *c_str() const noexcept { return mString; } }; @@ -159,19 +191,19 @@ std::string GetDeviceName(AudioDeviceID devId) /* Clear extraneous nul chars that may have been written with the name * string, and return it. */ - while(!devname.back()) + while(!devname.empty() && !devname.back()) devname.pop_back(); return devname; } -UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) +auto GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) -> UInt32 { - UInt32 propSize{}; + auto propSize = UInt32{}; auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, &propSize); if(err) { - ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n", + ERR("kAudioDevicePropertyStreamConfiguration size query failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return 0; } @@ -183,15 +215,14 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) buflist); if(err) { - ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n", + ERR("kAudioDevicePropertyStreamConfiguration query failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return 0; } - UInt32 numChannels{0}; + auto numChannels = UInt32{0}; for(size_t i{0};i < buflist->mNumberBuffers;++i) numChannels += buflist->mBuffers[i].mNumberChannels; - return numChannels; } @@ -201,14 +232,14 @@ void EnumerateDevices(std::vector &list, bool isCapture) UInt32 propSize{}; if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize)) { - ERR("Failed to get device list size: %u\n", err); + ERR("Failed to get device list size: {}", err); return; } auto devIds = std::vector(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown); if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data())) { - ERR("Failed to get device list: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); + ERR("Failed to get device list: '{}' ({})", FourCCPrinter{err}.c_str(), err); return; } @@ -223,7 +254,7 @@ void EnumerateDevices(std::vector &list, bool isCapture) { newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)}); const auto &entry = newdevs.back(); - TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId); + TRACE("Got device: {} = ID {}", entry.mName, entry.mId); } for(const AudioDeviceID devId : devIds) { @@ -240,7 +271,7 @@ void EnumerateDevices(std::vector &list, bool isCapture) { newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)}); const auto &entry = newdevs.back(); - TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId); + TRACE("Got device: {} = ID {}", entry.mName, entry.mId); } } @@ -255,14 +286,12 @@ void EnumerateDevices(std::vector &list, bool isCapture) { return entry.mName == curitem->mName; }; if(std::find_if(newdevs.begin(), curitem, check_match) != curitem) { - std::string name{curitem->mName}; - size_t count{1}; + auto name = std::string{curitem->mName}; + auto count = 1_uz; auto check_name = [&name](const DeviceEntry &entry) -> bool { return entry.mName == name; }; do { - name = curitem->mName; - name += " #"; - name += std::to_string(++count); + name = fmt::format("{} #{}", curitem->mName, ++count); } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem); curitem->mName = std::move(name); } @@ -277,18 +306,18 @@ struct DeviceHelper { DeviceHelper() { AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain}; + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil); if (status != noErr) - ERR("AudioObjectAddPropertyListener fail: %d", status); + ERR("AudioObjectAddPropertyListener fail: {}", status); } ~DeviceHelper() { AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain}; + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil); if (status != noErr) - ERR("AudioObjectRemovePropertyListener fail: %d", status); + ERR("AudioObjectRemovePropertyListener fail: {}", status); } static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses, @@ -322,7 +351,7 @@ static constexpr char ca_device[] = "CoreAudio Default"; struct CoreAudioPlayback final : public BackendBase { - CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~CoreAudioPlayback() override; OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags, @@ -377,7 +406,7 @@ void CoreAudioPlayback::open(std::string_view name) auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name); if(devmatch == PlaybackList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; audioDevice = devmatch->mId; } @@ -385,8 +414,8 @@ void CoreAudioPlayback::open(std::string_view name) if(name.empty()) name = ca_device; else if(name != ca_device) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; #endif /* open the default output unit */ @@ -410,7 +439,7 @@ void CoreAudioPlayback::open(std::string_view name) OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, - "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "Could not create component instance: '{}' ({})", FourCCPrinter{err}.c_str(), err}; #if CAN_ENUMERATE if(audioDevice != kAudioDeviceUnknown) @@ -421,7 +450,7 @@ void CoreAudioPlayback::open(std::string_view name) err = AudioUnitInitialize(audioUnit); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "Could not initialize audio unit: '{}' ({})", FourCCPrinter{err}.c_str(), err}; /* WARNING: I don't know if "valid" audio unit values are guaranteed to be * non-0. If not, this logic is broken. @@ -435,7 +464,7 @@ void CoreAudioPlayback::open(std::string_view name) #if CAN_ENUMERATE if(!name.empty()) - mDevice->DeviceName = name; + mDeviceName = name; else { UInt32 propSize{sizeof(audioDevice)}; @@ -444,8 +473,8 @@ void CoreAudioPlayback::open(std::string_view name) kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize); std::string devname{GetDeviceName(audioDevice)}; - if(!devname.empty()) mDevice->DeviceName = std::move(devname); - else mDevice->DeviceName = "Unknown Device Name"; + if(!devname.empty()) mDeviceName = std::move(devname); + else mDeviceName = "Unknown Device Name"; } if(audioDevice != kAudioDeviceUnknown) @@ -454,16 +483,16 @@ void CoreAudioPlayback::open(std::string_view name) err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false, kAudioObjectPropertyElementMaster, sizeof(type), &type); if(err != noErr) - ERR("Failed to get audio device type: %u\n", err); + WARN("Failed to get audio device type: '{}' ({})", FourCCPrinter{err}.c_str(), err); else { - TRACE("Got device type '%s'\n", FourCCPrinter{type}.c_str()); + TRACE("Got device type '{}'", FourCCPrinter{type}.c_str()); mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones)); } } #else - mDevice->DeviceName = name; + mDeviceName = name; #endif } @@ -471,7 +500,7 @@ bool CoreAudioPlayback::reset() { OSStatus err{AudioUnitUninitialize(mAudioUnit)}; if(err != noErr) - ERR("AudioUnitUninitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); + ERR("AudioUnitUninitialize failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); /* retrieve default output unit's properties (output side) */ AudioStreamBasicDescription streamFormat{}; @@ -480,33 +509,75 @@ bool CoreAudioPlayback::reset() OutputElement, &streamFormat, &size); if(err != noErr || size != sizeof(streamFormat)) { - ERR("AudioUnitGetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), + ERR("AudioUnitGetProperty(StreamFormat) failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return false; } -#if 0 - TRACE("Output streamFormat of default output unit -\n"); - TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket); - TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame); - TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel); - TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket); - TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame); - TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate); -#endif - /* Use the sample rate from the output unit's current parameters, but reset * everything else. */ - if(mDevice->Frequency != streamFormat.mSampleRate) + if(mDevice->mSampleRate != streamFormat.mSampleRate) { - mDevice->BufferSize = static_cast(mDevice->BufferSize*streamFormat.mSampleRate/ - mDevice->Frequency + 0.5); - mDevice->Frequency = static_cast(streamFormat.mSampleRate); + mDevice->mBufferSize = static_cast(mDevice->mBufferSize*streamFormat.mSampleRate/ + mDevice->mSampleRate + 0.5); + mDevice->mSampleRate = static_cast(streamFormat.mSampleRate); } - /* FIXME: How to tell what channels are what in the output device, and how - * to specify what we're giving? e.g. 6.0 vs 5.1 + struct ChannelMap { + DevFmtChannels fmt; + al::span map; + bool is_51rear; + }; + + static constexpr std::array chanmaps{{ + { DevFmtX71, X71ChanMap, false }, + { DevFmtX61, X61ChanMap, false }, + { DevFmtX51, X51ChanMap, false }, + { DevFmtX51, X51RearChanMap, true }, + { DevFmtQuad, QuadChanMap, false }, + { DevFmtStereo, StereoChanMap, false }, + { DevFmtMono, MonoChanMap, false } + }}; + + if(!mDevice->Flags.test(ChannelsRequest)) + { + auto propSize = UInt32{}; + auto writable = Boolean{}; + + err = AudioUnitGetPropertyInfo(mAudioUnit, kAudioUnitProperty_AudioChannelLayout, + kAudioUnitScope_Output, OutputElement, &propSize, &writable); + if(err == noErr) + { + auto layout_data = std::make_unique(propSize); + auto *layout = reinterpret_cast(layout_data.get()); + + err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_AudioChannelLayout, + kAudioUnitScope_Output, OutputElement, layout, &propSize); + if(err == noErr) + { + auto descs = al::span{std::data(layout->mChannelDescriptions), + layout->mNumberChannelDescriptions}; + auto labels = std::vector(descs.size()); + + std::transform(descs.begin(), descs.end(), labels.begin(), + std::mem_fn(&AudioChannelDescription::mChannelLabel)); + sort(labels.begin(), labels.end()); + + auto check_labels = [&labels](const ChannelMap &chanmap) -> bool + { + return std::includes(labels.begin(), labels.end(), chanmap.map.begin(), + chanmap.map.end()); + }; + auto chaniter = std::find_if(chanmaps.cbegin(), chanmaps.cend(), check_labels); + if(chaniter != chanmaps.cend()) + mDevice->FmtChans = chaniter->fmt; + } + } + } + + /* TODO: Also set kAudioUnitProperty_AudioChannelLayout according to the AL + * device's channel configuration. */ streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt(); @@ -548,7 +619,7 @@ bool CoreAudioPlayback::reset() OutputElement, &streamFormat, sizeof(streamFormat)); if(err != noErr) { - ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), + ERR("AudioUnitSetProperty(StreamFormat) failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return false; } @@ -566,7 +637,7 @@ bool CoreAudioPlayback::reset() kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) { - ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n", + ERR("AudioUnitSetProperty(SetRenderCallback) failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return false; } @@ -575,7 +646,7 @@ bool CoreAudioPlayback::reset() err = AudioUnitInitialize(mAudioUnit); if(err != noErr) { - ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); + ERR("AudioUnitInitialize failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return false; } @@ -587,19 +658,19 @@ void CoreAudioPlayback::start() const OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "AudioOutputUnitStart failed: '{}' ({})", FourCCPrinter{err}.c_str(), err}; } void CoreAudioPlayback::stop() { OSStatus err{AudioOutputUnitStop(mAudioUnit)}; if(err != noErr) - ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); + ERR("AudioOutputUnitStop failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); } struct CoreAudioCapture final : public BackendBase { - CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~CoreAudioCapture() override; OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags, @@ -650,7 +721,7 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags, inNumberFrames, &audiobuf.list)}; if(err != noErr) { - ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); + ERR("AudioUnitRender capture error: '{}' ({})", FourCCPrinter{err}.c_str(), err); return err; } @@ -676,7 +747,7 @@ void CoreAudioCapture::open(std::string_view name) auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name); if(devmatch == CaptureList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; audioDevice = devmatch->mId; } @@ -684,8 +755,8 @@ void CoreAudioCapture::open(std::string_view name) if(name.empty()) name = ca_device; else if(name != ca_device) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; #endif AudioComponentDescription desc{}; @@ -709,7 +780,7 @@ void CoreAudioCapture::open(std::string_view name) OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, - "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "Could not create component instance: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Turn off AudioUnit output UInt32 enableIO{0}; @@ -717,7 +788,7 @@ void CoreAudioCapture::open(std::string_view name) kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not disable audio unit output property: '%s' (%u)", FourCCPrinter{err}.c_str(), + "Could not disable audio unit output property: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Turn on AudioUnit input @@ -726,7 +797,7 @@ void CoreAudioCapture::open(std::string_view name) kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not enable audio unit input property: '%s' (%u)", FourCCPrinter{err}.c_str(), + "Could not enable audio unit input property: '{}' ({})", FourCCPrinter{err}.c_str(), err}; #if CAN_ENUMERATE @@ -745,7 +816,7 @@ void CoreAudioCapture::open(std::string_view name) kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not set capture callback: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "Could not set capture callback: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Disable buffer allocation for capture UInt32 flag{0}; @@ -753,14 +824,14 @@ void CoreAudioCapture::open(std::string_view name) kAudioUnitScope_Output, InputElement, &flag, sizeof(flag)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter{err}.c_str(), + "Could not disable buffer allocation property: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Initialize the device err = AudioUnitInitialize(mAudioUnit); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "Could not initialize audio unit: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Get the hardware format AudioStreamBasicDescription hardwareFormat{}; @@ -769,7 +840,7 @@ void CoreAudioCapture::open(std::string_view name) InputElement, &hardwareFormat, &propertySize); if(err != noErr || propertySize != sizeof(hardwareFormat)) throw al::backend_exception{al::backend_error::DeviceError, - "Could not get input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "Could not get input format: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Set up the requested format description AudioStreamBasicDescription requestedFormat{}; @@ -825,13 +896,13 @@ void CoreAudioCapture::open(std::string_view name) case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: - throw al::backend_exception{al::backend_error::DeviceError, "%s not supported", + throw al::backend_exception{al::backend_error::DeviceError, "{} not supported", DevFmtChannelsString(mDevice->FmtChans)}; } requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8; requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame; - requestedFormat.mSampleRate = mDevice->Frequency; + requestedFormat.mSampleRate = mDevice->mSampleRate; requestedFormat.mFormatID = kAudioFormatLinearPCM; requestedFormat.mReserved = 0; requestedFormat.mFramesPerPacket = 1; @@ -851,18 +922,18 @@ void CoreAudioCapture::open(std::string_view name) InputElement, &outputFormat, sizeof(outputFormat)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not set input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "Could not set input format: '{}' ({})", FourCCPrinter{err}.c_str(), err}; /* Calculate the minimum AudioUnit output format frame count for the pre- * conversion ring buffer. Ensure at least 100ms for the total buffer. */ - double srateScale{outputFormat.mSampleRate / mDevice->Frequency}; - auto FrameCount64 = std::max(static_cast(std::ceil(mDevice->BufferSize*srateScale)), + double srateScale{outputFormat.mSampleRate / mDevice->mSampleRate}; + auto FrameCount64 = std::max(static_cast(std::ceil(mDevice->mBufferSize*srateScale)), static_cast(outputFormat.mSampleRate)/10_u64); FrameCount64 += MaxResamplerPadding; if(FrameCount64 > std::numeric_limits::max()) throw al::backend_exception{al::backend_error::DeviceError, - "Calculated frame count is too large: %" PRIu64, FrameCount64}; + "Calculated frame count is too large: {}", FrameCount64}; UInt32 outputFrameCount{}; propertySize = sizeof(outputFrameCount); @@ -870,7 +941,7 @@ void CoreAudioCapture::open(std::string_view name) kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize); if(err != noErr || propertySize != sizeof(outputFrameCount)) throw al::backend_exception{al::backend_error::DeviceError, - "Could not get input frame count: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "Could not get input frame count: '{}' ({})", FourCCPrinter{err}.c_str(), err}; mCaptureData.resize(outputFrameCount * mFrameSize); @@ -878,14 +949,14 @@ void CoreAudioCapture::open(std::string_view name) mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false); /* Set up sample converter if needed */ - if(outputFormat.mSampleRate != mDevice->Frequency) + if(outputFormat.mSampleRate != mDevice->mSampleRate) mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, mFormat.mChannelsPerFrame, static_cast(hardwareFormat.mSampleRate), - mDevice->Frequency, Resampler::FastBSinc24); + mDevice->mSampleRate, Resampler::FastBSinc24); #if CAN_ENUMERATE if(!name.empty()) - mDevice->DeviceName = name; + mDeviceName = name; else { UInt32 propSize{sizeof(audioDevice)}; @@ -894,11 +965,11 @@ void CoreAudioCapture::open(std::string_view name) kAudioUnitScope_Global, InputElement, &audioDevice, &propSize); std::string devname{GetDeviceName(audioDevice)}; - if(!devname.empty()) mDevice->DeviceName = std::move(devname); - else mDevice->DeviceName = "Unknown Device Name"; + if(!devname.empty()) mDeviceName = std::move(devname); + else mDeviceName = "Unknown Device Name"; } #else - mDevice->DeviceName = name; + mDeviceName = name; #endif } @@ -908,14 +979,14 @@ void CoreAudioCapture::start() OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "AudioOutputUnitStart failed: '{}' ({})", FourCCPrinter{err}.c_str(), err}; } void CoreAudioCapture::stop() { OSStatus err{AudioOutputUnitStop(mAudioUnit)}; if(err != noErr) - ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); + ERR("AudioOutputUnitStop failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); } void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples) @@ -927,16 +998,16 @@ void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples) } auto rec_vec = mRing->getReadVector(); - const void *src0{rec_vec.first.buf}; - auto src0len = static_cast(rec_vec.first.len); + const void *src0{rec_vec[0].buf}; + auto src0len = static_cast(rec_vec[0].len); uint got{mConverter->convert(&src0, &src0len, buffer, samples)}; - size_t total_read{rec_vec.first.len - src0len}; - if(got < samples && !src0len && rec_vec.second.len > 0) + size_t total_read{rec_vec[0].len - src0len}; + if(got < samples && !src0len && rec_vec[1].len > 0) { - const void *src1{rec_vec.second.buf}; - auto src1len = static_cast(rec_vec.second.len); + const void *src1{rec_vec[1].buf}; + auto src1len = static_cast(rec_vec[1].len); got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got); - total_read += rec_vec.second.len - src1len; + total_read += rec_vec[1].len - src1len; } mRing->readAdvance(total_read); diff --git a/Engine/lib/openal-soft/alc/backends/dsound.cpp b/Engine/lib/openal-soft/alc/backends/dsound.cpp index 0f5993c6d..2bb7f5f5c 100644 --- a/Engine/lib/openal-soft/alc/backends/dsound.cpp +++ b/Engine/lib/openal-soft/alc/backends/dsound.cpp @@ -37,21 +37,20 @@ #include #include #include -#include #include -#include #include #include #include +#include "alnumeric.h" #include "alspan.h" -#include "alstring.h" #include "althrd_setname.h" #include "comptr.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" +#include "fmt/core.h" #include "ringbuffer.h" #include "strutils.h" @@ -90,10 +89,7 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0 namespace { -#define DEVNAME_HEAD "OpenAL Soft on " - - -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD void *ds_handle; HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter); HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext); @@ -146,24 +142,19 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi return TRUE; auto& devices = *static_cast*>(data); - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)}; + const auto basename = wstr_to_utf8(desc); - int count{1}; - std::string newname{basename}; + auto count = 1; + auto newname = basename; while(checkName(devices, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - devices.emplace_back(std::move(newname), *guid); - const DevMap &newentry = devices.back(); + newname = fmt::format("{} #{}", basename, ++count); + const DevMap &newentry = devices.emplace_back(std::move(newname), *guid); OLECHAR *guidstr{nullptr}; HRESULT hr{StringFromCLSID(*guid, &guidstr)}; if(SUCCEEDED(hr)) { - TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr); + TRACE("Got device \"{}\", GUID \"{}\"", newentry.name, wstr_to_utf8(guidstr)); CoTaskMemFree(guidstr); } @@ -172,7 +163,7 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi struct DSoundPlayback final : public BackendBase { - DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~DSoundPlayback() override; int mixerProc(); @@ -215,14 +206,15 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() HRESULT err{mBuffer->GetCaps(&DSBCaps)}; if(FAILED(err)) { - ERR("Failed to get buffer caps: 0x%lx\n", err); - mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err); + ERR("Failed to get buffer caps: {:#x}", as_unsigned(err)); + mDevice->handleDisconnect("Failure retrieving playback buffer info: {:#x}", + as_unsigned(err)); return 1; } const size_t FrameStep{mDevice->channelsFromFmt()}; uint FrameSize{mDevice->frameSizeFromFmt()}; - DWORD FragSize{mDevice->UpdateSize * FrameSize}; + DWORD FragSize{mDevice->mUpdateSize * FrameSize}; bool Playing{false}; DWORD LastCursor{0u}; @@ -242,8 +234,9 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() err = mBuffer->Play(0, 0, DSBPLAY_LOOPING); if(FAILED(err)) { - ERR("Failed to play buffer: 0x%lx\n", err); - mDevice->handleDisconnect("Failure starting playback: 0x%lx", err); + ERR("Failed to play buffer: {:#x}", as_unsigned(err)); + mDevice->handleDisconnect("Failure starting playback: {:#x}", + as_unsigned(err)); return 1; } Playing = true; @@ -251,7 +244,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE); if(avail != WAIT_OBJECT_0) - ERR("WaitForSingleObjectEx error: 0x%lx\n", avail); + ERR("WaitForSingleObjectEx error: {:#x}", avail); continue; } avail -= avail%FragSize; @@ -264,7 +257,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() // If the buffer is lost, restore it and lock if(err == DSERR_BUFFERLOST) { - WARN("Buffer lost, restoring...\n"); + WARN("Buffer lost, restoring..."); err = mBuffer->Restore(); if(SUCCEEDED(err)) { @@ -274,22 +267,19 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() &WritePtr2, &WriteCnt2, 0); } } - - if(SUCCEEDED(err)) + if(FAILED(err)) { - mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep); - if(WriteCnt2 > 0) - mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep); - - mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2); - } - else - { - ERR("Buffer lock error: %#lx\n", err); - mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err); + ERR("Buffer lock error: {:#x}", as_unsigned(err)); + mDevice->handleDisconnect("Failed to lock output buffer: {:#x}", as_unsigned(err)); return 1; } + mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep); + if(WriteCnt2 > 0) + mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep); + + mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2); + // Update old write cursor location LastCursor += WriteCnt1+WriteCnt2; LastCursor %= DSBCaps.dwBufferBytes; @@ -307,7 +297,7 @@ void DSoundPlayback::open(std::string_view name) ComWrapper com{}; hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); if(FAILED(hr)) - ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); + ERR("Error enumerating DirectSound devices: {:#x}", as_unsigned(hr)); } const GUID *guid{nullptr}; @@ -329,7 +319,7 @@ void DSoundPlayback::open(std::string_view name) [&id](const DevMap &entry) -> bool { return entry.guid == id; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; } guid = &iter->guid; } @@ -348,15 +338,15 @@ void DSoundPlayback::open(std::string_view name) if(SUCCEEDED(hr)) hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); if(FAILED(hr)) - throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", - hr}; + throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}", + as_unsigned(hr)}; mNotifies = nullptr; mBuffer = nullptr; mPrimaryBuffer = nullptr; mDS = std::move(ds); - mDevice->DeviceName = name; + mDeviceName = name; } bool DSoundPlayback::reset() @@ -391,7 +381,7 @@ bool DSoundPlayback::reset() HRESULT hr{mDS->GetSpeakerConfig(&speakers)}; if(FAILED(hr)) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to get speaker config: 0x%08lx", hr}; + "Failed to get speaker config: {:#x}", as_unsigned(hr)}; speakers = DSSPEAKER_CONFIG(speakers); if(!mDevice->Flags.test(ChannelsRequest)) @@ -407,7 +397,7 @@ bool DSoundPlayback::reset() else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND) mDevice->FmtChans = DevFmtX71; else - ERR("Unknown system speaker config: 0x%lx\n", speakers); + ERR("Unknown system speaker config: {:#x}", speakers); } mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE)); const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK}; @@ -435,7 +425,7 @@ bool DSoundPlayback::reset() OutputType.Format.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels * OutputType.Format.wBitsPerSample / 8); - OutputType.Format.nSamplesPerSec = mDevice->Frequency; + OutputType.Format.nSamplesPerSec = mDevice->mSampleRate; OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * OutputType.Format.nBlockAlign; OutputType.Format.cbSize = 0; @@ -469,16 +459,16 @@ bool DSoundPlayback::reset() if(FAILED(hr)) break; - uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; + uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize}; if(num_updates > MAX_UPDATES) num_updates = MAX_UPDATES; - mDevice->BufferSize = mDevice->UpdateSize * num_updates; + mDevice->mBufferSize = mDevice->mUpdateSize * num_updates; DSBUFFERDESC DSBDescription{}; DSBDescription.dwSize = sizeof(DSBDescription); DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS; - DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign; + DSBDescription.dwBufferBytes = mDevice->mBufferSize * OutputType.Format.nBlockAlign; DSBDescription.lpwfxFormat = &OutputType.Format; hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr); @@ -492,13 +482,13 @@ bool DSoundPlayback::reset() hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies)); if(SUCCEEDED(hr)) { - uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; + uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize}; assert(num_updates <= MAX_UPDATES); std::array nots{}; for(uint i{0};i < num_updates;++i) { - nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign; + nots[i].dwOffset = i * mDevice->mUpdateSize * OutputType.Format.nBlockAlign; nots[i].hEventNotify = mNotifyEvent; } if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK) @@ -524,11 +514,11 @@ void DSoundPlayback::start() { try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this}; + mThread = std::thread{&DSoundPlayback::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -543,7 +533,7 @@ void DSoundPlayback::stop() struct DSoundCapture final : public BackendBase { - DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~DSoundCapture() override; void open(std::string_view name) override; @@ -580,7 +570,7 @@ void DSoundCapture::open(std::string_view name) ComWrapper com{}; hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); if(FAILED(hr)) - ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); + ERR("Error enumerating DirectSound devices: {:#x}", as_unsigned(hr)); } const GUID *guid{nullptr}; @@ -602,7 +592,7 @@ void DSoundCapture::open(std::string_view name) [&id](const DevMap &entry) -> bool { return entry.guid == id; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; } guid = &iter->guid; } @@ -612,9 +602,9 @@ void DSoundCapture::open(std::string_view name) case DevFmtByte: case DevFmtUShort: case DevFmtUInt: - WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType)); + WARN("{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)); throw al::backend_exception{al::backend_error::DeviceError, - "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; + "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; case DevFmtUByte: case DevFmtShort: @@ -636,8 +626,8 @@ void DSoundCapture::open(std::string_view name) case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: - WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans)); - throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", + WARN("{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)); + throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } @@ -646,7 +636,7 @@ void DSoundCapture::open(std::string_view name) InputType.Format.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); InputType.Format.nBlockAlign = static_cast(InputType.Format.nChannels * InputType.Format.wBitsPerSample / 8); - InputType.Format.nSamplesPerSec = mDevice->Frequency; + InputType.Format.nSamplesPerSec = mDevice->mSampleRate; InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec * InputType.Format.nBlockAlign; InputType.Format.cbSize = 0; @@ -663,7 +653,7 @@ void DSoundCapture::open(std::string_view name) InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); } - const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; + const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)}; DSCBUFFERDESC DSCBDescription{}; DSCBDescription.dwSize = sizeof(DSCBDescription); @@ -676,7 +666,7 @@ void DSoundCapture::open(std::string_view name) if(SUCCEEDED(hr)) mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr); if(SUCCEEDED(hr)) - mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false); + mRing = RingBuffer::Create(mDevice->mBufferSize, InputType.Format.nBlockAlign, false); if(FAILED(hr)) { @@ -684,14 +674,14 @@ void DSoundCapture::open(std::string_view name) mDSCbuffer = nullptr; mDSC = nullptr; - throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", - hr}; + throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}", + as_unsigned(hr)}; } mBufferBytes = DSCBDescription.dwBufferBytes; setDefaultWFXChannelOrder(); - mDevice->DeviceName = name; + mDeviceName = name; } void DSoundCapture::start() @@ -699,7 +689,7 @@ void DSoundCapture::start() const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)}; if(FAILED(hr)) throw al::backend_exception{al::backend_error::DeviceError, - "Failure starting capture: 0x%lx", hr}; + "Failure starting capture: {:#x}", as_unsigned(hr)}; } void DSoundCapture::stop() @@ -707,8 +697,8 @@ void DSoundCapture::stop() HRESULT hr{mDSCbuffer->Stop()}; if(FAILED(hr)) { - ERR("stop failed: 0x%08lx\n", hr); - mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr); + ERR("stop failed: {:#x}", as_unsigned(hr)); + mDevice->handleDisconnect("Failure stopping capture: {:#x}", as_unsigned(hr)); } } @@ -745,8 +735,8 @@ uint DSoundCapture::availableSamples() if(FAILED(hr)) { - ERR("update failed: 0x%08lx\n", hr); - mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr); + ERR("update failed: {:#x}", as_unsigned(hr)); + mDevice->handleDisconnect("Failure retrieving capture data: {:#x}", as_unsigned(hr)); } return static_cast(mRing->readSpace()); @@ -763,13 +753,13 @@ BackendFactory &DSoundBackendFactory::getFactory() bool DSoundBackendFactory::init() { -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD if(!ds_handle) { ds_handle = LoadLib("dsound.dll"); if(!ds_handle) { - ERR("Failed to load dsound.dll\n"); + ERR("Failed to load dsound.dll"); return false; } @@ -808,7 +798,7 @@ auto DSoundBackendFactory::enumerate(BackendType type) -> std::vector std::vector #include #include -#include #include #include "alc/alconfig.h" #include "alnumeric.h" #include "alsem.h" -#include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" +#include "fmt/format.h" #include "ringbuffer.h" #include @@ -51,7 +50,7 @@ namespace { using namespace std::string_view_literals; -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD #define JACK_FUNCS(MAGIC) \ MAGIC(jack_client_open); \ MAGIC(jack_client_close); \ @@ -108,10 +107,12 @@ jack_options_t ClientOptions = JackNullOption; bool jack_load() { -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD if(!jack_handle) { -#ifdef _WIN32 +#if defined(_WIN64) +#define JACKLIB "libjack64.dll" +#elif defined(_WIN32) #define JACKLIB "libjack.dll" #else #define JACKLIB "libjack.so.0" @@ -119,7 +120,7 @@ bool jack_load() jack_handle = LoadLib(JACKLIB); if(!jack_handle) { - WARN("Failed to load %s\n", JACKLIB); + WARN("Failed to load {}", JACKLIB); return false; } @@ -137,7 +138,7 @@ bool jack_load() if(!missing_funcs.empty()) { - WARN("Missing expected functions:%s\n", missing_funcs.c_str()); + WARN("Missing expected functions:{}", missing_funcs); CloseLib(jack_handle); jack_handle = nullptr; return false; @@ -194,15 +195,15 @@ void EnumerateDevices(jack_client_t *client, std::vector &list) if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend()) continue; - const auto &entry = list.emplace_back(portdev, std::string{portdev}+":"); - TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); + const auto &entry = list.emplace_back(portdev, fmt::format("{}:", portdev)); + TRACE("Got device: {} = {}", entry.mName, entry.mPattern); } /* There are ports but couldn't get device names from them. Add a * generic entry. */ if(ports[0] && list.empty()) { - WARN("No device names found in available ports, adding a generic name.\n"); + WARN("No device names found in available ports, adding a generic name."); list.emplace_back("JACK"sv, ""sv); } } @@ -216,7 +217,7 @@ void EnumerateDevices(jack_client_t *client, std::vector &list) if(seppos >= nextpos || seppos == strpos) { const auto entry = std::string_view{*listopt}.substr(strpos, nextpos-strpos); - ERR("Invalid device entry: \"%.*s\"\n", al::sizei(entry), entry.data()); + ERR("Invalid device entry: \"{}\"", entry); if(nextpos != std::string::npos) ++nextpos; strpos = nextpos; continue; @@ -234,14 +235,13 @@ void EnumerateDevices(jack_client_t *client, std::vector &list) { /* If so, replace the name with this custom one. */ itemmatch->mName = name; - TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(), - itemmatch->mPattern.c_str()); + TRACE("Customized device name: {} = {}", itemmatch->mName, itemmatch->mPattern); } else { /* Otherwise, add a new device entry. */ const auto &entry = list.emplace_back(name, pattern); - TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); + TRACE("Got custom device: {} = {}", entry.mName, entry.mPattern); } if(nextpos != std::string::npos) ++nextpos; @@ -277,7 +277,7 @@ void EnumerateDevices(jack_client_t *client, std::vector &list) struct JackPlayback final : public BackendBase { - JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~JackPlayback() override; int processRt(jack_nframes_t numframes) noexcept; @@ -329,13 +329,13 @@ JackPlayback::~JackPlayback() int JackPlayback::processRt(jack_nframes_t numframes) noexcept { - auto outptrs = std::array{}; + auto outptrs = std::array{}; auto numchans = size_t{0}; for(auto port : mPort) { if(!port || numchans == mDevice->RealOut.Buffer.size()) break; - outptrs[numchans++] = static_cast(jack_port_get_buffer(port, numframes)); + outptrs[numchans++] = jack_port_get_buffer(port, numframes); } const auto dst = al::span{outptrs}.first(numchans); @@ -343,8 +343,8 @@ int JackPlayback::processRt(jack_nframes_t numframes) noexcept mDevice->renderSamples(dst, static_cast(numframes)); else { - std::for_each(dst.begin(), dst.end(), [numframes](float *outbuf) -> void - { std::fill_n(outbuf, numframes, 0.0f); }); + std::for_each(dst.begin(), dst.end(), [numframes](void *outbuf) -> void + { std::fill_n(static_cast(outbuf), numframes, 0.0f); }); } return 0; @@ -365,13 +365,13 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept if(mPlaying.load(std::memory_order_acquire)) LIKELY { auto data = mRing->getReadVector(); - const auto update_size = size_t{mDevice->UpdateSize}; + const auto update_size = size_t{mDevice->mUpdateSize}; const auto outlen = size_t{numframes / update_size}; - const auto len1 = size_t{std::min(data.first.len/update_size, outlen)}; - const auto len2 = size_t{std::min(data.second.len/update_size, outlen-len1)}; + const auto len1 = size_t{std::min(data[0].len/update_size, outlen)}; + const auto len2 = size_t{std::min(data[1].len/update_size, outlen-len1)}; - auto src = al::span{reinterpret_cast(data.first.buf), update_size*len1*numchans}; + auto src = al::span{reinterpret_cast(data[0].buf), update_size*len1*numchans}; for(size_t i{0};i < len1;++i) { for(size_t c{0};c < numchans;++c) @@ -383,7 +383,7 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept total += update_size; } - src = al::span{reinterpret_cast(data.second.buf), update_size*len2*numchans}; + src = al::span{reinterpret_cast(data[1].buf), update_size*len2*numchans}; for(size_t i{0};i < len2;++i) { for(size_t c{0};c < numchans;++c) @@ -414,9 +414,9 @@ int JackPlayback::mixerProc() SetRTPriority(); althrd_setname(GetMixerThreadName()); - const auto update_size = uint{mDevice->UpdateSize}; + const auto update_size = uint{mDevice->mUpdateSize}; const auto num_channels = size_t{mDevice->channelsFromFmt()}; - auto outptrs = std::vector(num_channels); + auto outptrs = std::vector(num_channels); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) @@ -428,12 +428,11 @@ int JackPlayback::mixerProc() } auto data = mRing->getWriteVector(); - const auto len1 = size_t{data.first.len / update_size}; - const auto len2 = size_t{data.second.len / update_size}; + const auto len1 = size_t{data[0].len / update_size}; + const auto len2 = size_t{data[1].len / update_size}; std::lock_guard dlock{mMutex}; - auto buffer = al::span{reinterpret_cast(data.first.buf), - data.first.len*num_channels}; + auto buffer = al::span{reinterpret_cast(data[0].buf), data[0].len*num_channels}; auto bufiter = buffer.begin(); for(size_t i{0};i < len1;++i) { @@ -447,8 +446,7 @@ int JackPlayback::mixerProc() } if(len2 > 0) { - buffer = al::span{reinterpret_cast(data.second.buf), - data.second.len*num_channels}; + buffer = al::span{reinterpret_cast(data[1].buf), data[1].len*num_channels}; bufiter = buffer.begin(); for(size_t i{0};i < len2;++i) { @@ -479,13 +477,14 @@ void JackPlayback::open(std::string_view name) mClient = jack_client_open(client_name, ClientOptions, &status, nullptr); if(mClient == nullptr) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to open client connection: 0x%02x", status}; + "Failed to open client connection: {:#02x}", + as_unsigned(al::to_underlying(status))}; if((status&JackServerStarted)) - TRACE("JACK server started\n"); + TRACE("JACK server started"); if((status&JackNameNotUnique)) { client_name = jack_get_client_name(mClient); - TRACE("Client name not unique, got '%s' instead\n", client_name); + TRACE("Client name not unique, got '{}' instead", client_name); } } @@ -504,11 +503,11 @@ void JackPlayback::open(std::string_view name) auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name); if(iter == PlaybackList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; mPortPattern = iter->mPattern; } - mDevice->DeviceName = name; + mDeviceName = name; } bool JackPlayback::reset() @@ -518,28 +517,29 @@ bool JackPlayback::reset() std::for_each(mPort.begin(), mPort.end(), unregister_port); mPort.fill(nullptr); - mRTMixing = GetConfigValueBool(mDevice->DeviceName, "jack", "rt-mix", true); + mRTMixing = GetConfigValueBool(mDevice->mDeviceName, "jack", "rt-mix", true); jack_set_process_callback(mClient, mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); /* Ignore the requested buffer metrics and just keep one JACK-sized buffer * ready for when requested. */ - mDevice->Frequency = jack_get_sample_rate(mClient); - mDevice->UpdateSize = jack_get_buffer_size(mClient); + mDevice->mSampleRate = jack_get_sample_rate(mClient); + mDevice->mUpdateSize = jack_get_buffer_size(mClient); if(mRTMixing) { /* Assume only two periods when directly mixing. Should try to query * the total port latency when connected. */ - mDevice->BufferSize = mDevice->UpdateSize * 2; + mDevice->mBufferSize = mDevice->mUpdateSize * 2; } else { - const std::string_view devname{mDevice->DeviceName}; - uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize); - mDevice->BufferSize = bufsize + mDevice->UpdateSize; + const auto devname = std::string_view{mDevice->mDeviceName}; + auto bufsize = ConfigValueUInt(devname, "jack", "buffer-size") + .value_or(mDevice->mUpdateSize); + bufsize = std::max(NextPowerOf2(bufsize), mDevice->mUpdateSize); + mDevice->mBufferSize = bufsize + mDevice->mUpdateSize; } /* Force 32-bit float output. */ @@ -558,7 +558,7 @@ bool JackPlayback::reset() } if(bad_port != ports.end()) { - ERR("Failed to register enough JACK ports for %s output\n", + ERR("Failed to register enough JACK ports for {} output", DevFmtChannelsString(mDevice->FmtChans)); if(bad_port == ports.begin()) return false; @@ -586,7 +586,7 @@ void JackPlayback::start() if(jack_activate(mClient)) throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"}; - const std::string_view devname{mDevice->DeviceName}; + const auto devname = std::string_view{mDevice->mDeviceName}; if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true)) { JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE, @@ -601,11 +601,11 @@ void JackPlayback::start() { if(!pnames[i]) { - ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i])); + ERR("No physical playback port for \"{}\"", jack_port_name(mPort[i])); break; } if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i])) - ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort[i]), + ERR("Failed to connect output port \"{}\" to \"{}\"", jack_port_name(mPort[i]), pnames[i]); } } @@ -614,31 +614,32 @@ void JackPlayback::start() * (it won't change again after jack_activate), then allocate the ring * buffer with the appropriate size. */ - mDevice->Frequency = jack_get_sample_rate(mClient); - mDevice->UpdateSize = jack_get_buffer_size(mClient); - mDevice->BufferSize = mDevice->UpdateSize * 2; + mDevice->mSampleRate = jack_get_sample_rate(mClient); + mDevice->mUpdateSize = jack_get_buffer_size(mClient); + mDevice->mBufferSize = mDevice->mUpdateSize * 2; mRing = nullptr; if(mRTMixing) mPlaying.store(true, std::memory_order_release); else { - uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize); - mDevice->BufferSize = bufsize + mDevice->UpdateSize; + uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size") + .value_or(mDevice->mUpdateSize)}; + bufsize = std::max(NextPowerOf2(bufsize), mDevice->mUpdateSize); + mDevice->mBufferSize = bufsize + mDevice->mUpdateSize; mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true); try { mPlaying.store(true, std::memory_order_release); mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this}; + mThread = std::thread{&JackPlayback::mixerProc, this}; } catch(std::exception& e) { jack_deactivate(mClient); mPlaying.store(false, std::memory_order_release); throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } } @@ -665,8 +666,8 @@ ClockLatency JackPlayback::getClockLatency() std::lock_guard dlock{mMutex}; ClockLatency ret{}; ret.ClockTime = mDevice->getClockTime(); - ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize}; - ret.Latency /= mDevice->Frequency; + ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->mUpdateSize}; + ret.Latency /= mDevice->mSampleRate; return ret; } @@ -674,7 +675,7 @@ ClockLatency JackPlayback::getClockLatency() void jack_msg_handler(const char *message) { - WARN("%s\n", message); + WARN("{}", message); } } // namespace @@ -697,9 +698,9 @@ bool JackBackendFactory::init() jack_set_error_function(old_error_cb); if(!client) { - WARN("jack_client_open() failed, 0x%02x\n", status); + WARN("jack_client_open() failed, {:#02x}", as_unsigned(al::to_underlying(status))); if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer)) - ERR("Unable to connect to JACK server\n"); + ERR("Unable to connect to JACK server"); return false; } @@ -728,7 +729,7 @@ auto JackBackendFactory::enumerate(BackendType type) -> std::vector jack_client_close(client); } else - WARN("jack_client_open() failed, 0x%02x\n", status); + WARN("jack_client_open() failed, {:#02x}", as_unsigned(al::to_underlying(status))); outnames.reserve(PlaybackList.size()); std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); break; diff --git a/Engine/lib/openal-soft/alc/backends/loopback.cpp b/Engine/lib/openal-soft/alc/backends/loopback.cpp index e999ca514..66b58bc23 100644 --- a/Engine/lib/openal-soft/alc/backends/loopback.cpp +++ b/Engine/lib/openal-soft/alc/backends/loopback.cpp @@ -28,7 +28,7 @@ namespace { struct LoopbackBackend final : public BackendBase { - LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { } + explicit LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { } void open(std::string_view name) override; bool reset() override; @@ -39,7 +39,7 @@ struct LoopbackBackend final : public BackendBase { void LoopbackBackend::open(std::string_view name) { - mDevice->DeviceName = name; + mDeviceName = name; } bool LoopbackBackend::reset() diff --git a/Engine/lib/openal-soft/alc/backends/null.cpp b/Engine/lib/openal-soft/alc/backends/null.cpp index d2e036ad9..25511d7fe 100644 --- a/Engine/lib/openal-soft/alc/backends/null.cpp +++ b/Engine/lib/openal-soft/alc/backends/null.cpp @@ -27,11 +27,8 @@ #include #include #include -#include #include -#include "almalloc.h" -#include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" @@ -48,7 +45,7 @@ using namespace std::string_view_literals; struct NullBackend final : public BackendBase { - NullBackend(DeviceBase *device) noexcept : BackendBase{device} { } + explicit NullBackend(DeviceBase *device) noexcept : BackendBase{device} { } int mixerProc(); @@ -63,7 +60,7 @@ struct NullBackend final : public BackendBase { int NullBackend::mixerProc() { - const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; + const milliseconds restTime{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2}; SetRTPriority(); althrd_setname(GetMixerThreadName()); @@ -76,16 +73,17 @@ int NullBackend::mixerProc() auto now = std::chrono::steady_clock::now(); /* This converts from nanoseconds to nanosamples, then to samples. */ - int64_t avail{std::chrono::duration_cast((now-start) * mDevice->Frequency).count()}; - if(avail-done < mDevice->UpdateSize) + const auto avail = int64_t{std::chrono::duration_cast((now-start) + * mDevice->mSampleRate).count()}; + if(avail-done < mDevice->mUpdateSize) { std::this_thread::sleep_for(restTime); continue; } - while(avail-done >= mDevice->UpdateSize) + while(avail-done >= mDevice->mUpdateSize) { - mDevice->renderSamples(nullptr, mDevice->UpdateSize, 0u); - done += mDevice->UpdateSize; + mDevice->renderSamples(nullptr, mDevice->mUpdateSize, 0u); + done += mDevice->mUpdateSize; } /* For every completed second, increment the start time and reduce the @@ -93,11 +91,11 @@ int NullBackend::mixerProc() * and current time from growing too large, while maintaining the * correct number of samples to render. */ - if(done >= mDevice->Frequency) + if(done >= mDevice->mSampleRate) { - seconds s{done/mDevice->Frequency}; + seconds s{done/mDevice->mSampleRate}; start += s; - done -= mDevice->Frequency*s.count(); + done -= mDevice->mSampleRate*s.count(); } } @@ -110,10 +108,10 @@ void NullBackend::open(std::string_view name) if(name.empty()) name = GetDeviceName(); else if(name != GetDeviceName()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; - mDevice->DeviceName = name; + mDeviceName = name; } bool NullBackend::reset() @@ -126,11 +124,11 @@ void NullBackend::start() { try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this}; + mThread = std::thread{&NullBackend::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } diff --git a/Engine/lib/openal-soft/alc/backends/oboe.cpp b/Engine/lib/openal-soft/alc/backends/oboe.cpp index 68acd0094..2aa9960cb 100644 --- a/Engine/lib/openal-soft/alc/backends/oboe.cpp +++ b/Engine/lib/openal-soft/alc/backends/oboe.cpp @@ -24,7 +24,7 @@ using namespace std::string_view_literals; struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback { - OboePlayback(DeviceBase *device) : BackendBase{device} { } + explicit OboePlayback(DeviceBase *device) : BackendBase{device} { } oboe::ManagedStream mStream; @@ -54,8 +54,9 @@ oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStrea void OboePlayback::onErrorAfterClose(oboe::AudioStream*, oboe::Result error) { if(error == oboe::Result::ErrorDisconnected) - mDevice->handleDisconnect("Oboe AudioStream was disconnected: %s", oboe::convertToText(error)); - TRACE("Error was %s", oboe::convertToText(error)); + mDevice->handleDisconnect("Oboe AudioStream was disconnected: {}", + oboe::convertToText(error)); + TRACE("Error was {}", oboe::convertToText(error)); } void OboePlayback::open(std::string_view name) @@ -63,8 +64,8 @@ void OboePlayback::open(std::string_view name) if(name.empty()) name = GetDeviceName(); else if(name != GetDeviceName()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; /* Open a basic output stream, just to ensure it can work. */ oboe::ManagedStream stream; @@ -72,10 +73,10 @@ void OboePlayback::open(std::string_view name) ->setPerformanceMode(oboe::PerformanceMode::LowLatency) ->openManagedStream(stream)}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", + throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}", oboe::convertToText(result)}; - mDevice->DeviceName = name; + mDeviceName = name; } bool OboePlayback::reset() @@ -95,7 +96,7 @@ bool OboePlayback::reset() if(mDevice->Flags.test(FrequencyRequest)) { builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High); - builder.setSampleRate(static_cast(mDevice->Frequency)); + builder.setSampleRate(static_cast(mDevice->mSampleRate)); } if(mDevice->Flags.test(ChannelsRequest)) { @@ -145,11 +146,11 @@ bool OboePlayback::reset() result = builder.openManagedStream(mStream); } if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", + throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}", oboe::convertToText(result)}; - mStream->setBufferSizeInFrames(std::min(static_cast(mDevice->BufferSize), + mStream->setBufferSizeInFrames(std::min(static_cast(mDevice->mBufferSize), mStream->getBufferCapacityInFrames())); - TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); + TRACE("Got stream with properties:\n{}", oboe::convertToText(mStream.get())); if(static_cast(mStream->getChannelCount()) != mDevice->channelsFromFmt()) { @@ -159,7 +160,7 @@ bool OboePlayback::reset() mDevice->FmtChans = DevFmtMono; else throw al::backend_exception{al::backend_error::DeviceError, - "Got unhandled channel count: %d", mStream->getChannelCount()}; + "Got unhandled channel count: {}", mStream->getChannelCount()}; } setDefaultWFXChannelOrder(); @@ -183,18 +184,18 @@ bool OboePlayback::reset() case oboe::AudioFormat::Unspecified: case oboe::AudioFormat::Invalid: throw al::backend_exception{al::backend_error::DeviceError, - "Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())}; + "Got unhandled sample type: {}", oboe::convertToText(mStream->getFormat())}; } - mDevice->Frequency = static_cast(mStream->getSampleRate()); + mDevice->mSampleRate = static_cast(mStream->getSampleRate()); /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0 * indicating variable updates, but OpenAL should have a reasonable minimum update size set. * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum * update size. */ - mDevice->UpdateSize = std::max(mDevice->Frequency/100u, + mDevice->mUpdateSize = std::max(mDevice->mSampleRate/100u, static_cast(mStream->getFramesPerBurst())); - mDevice->BufferSize = std::max(mDevice->UpdateSize*2u, + mDevice->mBufferSize = std::max(mDevice->mUpdateSize*2u, static_cast(mStream->getBufferSizeInFrames())); return true; @@ -204,7 +205,7 @@ void OboePlayback::start() { const oboe::Result result{mStream->start()}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s", + throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: {}", oboe::convertToText(result)}; } @@ -212,12 +213,12 @@ void OboePlayback::stop() { oboe::Result result{mStream->stop()}; if(result != oboe::Result::OK) - ERR("Failed to stop stream: %s\n", oboe::convertToText(result)); + ERR("Failed to stop stream: {}", oboe::convertToText(result)); } struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback { - OboeCapture(DeviceBase *device) : BackendBase{device} { } + explicit OboeCapture(DeviceBase *device) : BackendBase{device} { } oboe::ManagedStream mStream; @@ -246,8 +247,8 @@ void OboeCapture::open(std::string_view name) if(name.empty()) name = GetDeviceName(); else if(name != GetDeviceName()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; oboe::AudioStreamBuilder builder; builder.setDirection(oboe::Direction::Input) @@ -255,7 +256,7 @@ void OboeCapture::open(std::string_view name) ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High) ->setChannelConversionAllowed(true) ->setFormatConversionAllowed(true) - ->setSampleRate(static_cast(mDevice->Frequency)) + ->setSampleRate(static_cast(mDevice->mSampleRate)) ->setCallback(this); /* Only use mono or stereo at user request. There's no telling what * other counts may be inferred as. @@ -276,7 +277,7 @@ void OboeCapture::open(std::string_view name) case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: - throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", + throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } @@ -301,28 +302,28 @@ void OboeCapture::open(std::string_view name) case DevFmtUShort: case DevFmtUInt: throw al::backend_exception{al::backend_error::DeviceError, - "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; + "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } oboe::Result result{builder.openManagedStream(mStream)}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", + throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}", oboe::convertToText(result)}; - TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); + TRACE("Got stream with properties:\n{}", oboe::convertToText(mStream.get())); /* Ensure a minimum ringbuffer size of 100ms. */ - mRing = RingBuffer::Create(std::max(mDevice->BufferSize, mDevice->Frequency/10u), + mRing = RingBuffer::Create(std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u), static_cast(mStream->getBytesPerFrame()), false); - mDevice->DeviceName = name; + mDeviceName = name; } void OboeCapture::start() { const oboe::Result result{mStream->start()}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s", + throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: {}", oboe::convertToText(result)}; } @@ -330,7 +331,7 @@ void OboeCapture::stop() { const oboe::Result result{mStream->stop()}; if(result != oboe::Result::OK) - ERR("Failed to stop stream: %s\n", oboe::convertToText(result)); + ERR("Failed to stop stream: {}", oboe::convertToText(result)); } uint OboeCapture::availableSamples() diff --git a/Engine/lib/openal-soft/alc/backends/opensl.cpp b/Engine/lib/openal-soft/alc/backends/opensl.cpp index 26e690709..155972b98 100644 --- a/Engine/lib/openal-soft/alc/backends/opensl.cpp +++ b/Engine/lib/openal-soft/alc/backends/opensl.cpp @@ -41,6 +41,7 @@ #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" +#include "dynload.h" #include "opthelpers.h" #include "ringbuffer.h" @@ -53,6 +54,32 @@ namespace { using namespace std::string_view_literals; + +#if HAVE_DYNLOAD +#define SLES_SYMBOLS(MAGIC) \ + MAGIC(slCreateEngine); \ + MAGIC(SL_IID_ANDROIDCONFIGURATION); \ + MAGIC(SL_IID_ANDROIDSIMPLEBUFFERQUEUE); \ + MAGIC(SL_IID_ENGINE); \ + MAGIC(SL_IID_PLAY); \ + MAGIC(SL_IID_RECORD); + +void *sles_handle; +#define MAKE_SYMBOL(f) decltype(f) * p##f +SLES_SYMBOLS(MAKE_SYMBOL) +#undef MAKE_SYMBOL + +#ifndef IN_IDE_PARSER +#define slCreateEngine (*pslCreateEngine) +#define SL_IID_ANDROIDCONFIGURATION (*pSL_IID_ANDROIDCONFIGURATION) +#define SL_IID_ANDROIDSIMPLEBUFFERQUEUE (*pSL_IID_ANDROIDSIMPLEBUFFERQUEUE) +#define SL_IID_ENGINE (*pSL_IID_ENGINE) +#define SL_IID_PLAY (*pSL_IID_PLAY) +#define SL_IID_RECORD (*pSL_IID_RECORD) +#endif +#endif + + /* Helper macros */ #define EXTRACT_VCALL_ARGS(...) __VA_ARGS__)) #define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS @@ -156,12 +183,12 @@ constexpr const char *res_str(SLresult result) noexcept inline void PrintErr(SLresult res, const char *str) { if(res != SL_RESULT_SUCCESS) UNLIKELY - ERR("%s: %s\n", str, res_str(res)); + ERR("{}: {}", str, res_str(res)); } struct OpenSLPlayback final : public BackendBase { - OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~OpenSLPlayback() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; @@ -247,7 +274,7 @@ int OpenSLPlayback::mixerProc() const size_t frame_step{mDevice->channelsFromFmt()}; if(SL_RESULT_SUCCESS != result) - mDevice->handleDisconnect("Failed to get playback buffer: 0x%08x", result); + mDevice->handleDisconnect("Failed to get playback buffer: {:#08x}", result); while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) @@ -265,7 +292,7 @@ int OpenSLPlayback::mixerProc() } if(SL_RESULT_SUCCESS != result) { - mDevice->handleDisconnect("Failed to start playback: 0x%08x", result); + mDevice->handleDisconnect("Failed to start playback: {:#08x}", result); break; } @@ -278,35 +305,35 @@ int OpenSLPlayback::mixerProc() std::unique_lock dlock{mMutex}; auto data = mRing->getWriteVector(); - mDevice->renderSamples(data.first.buf, - static_cast(data.first.len)*mDevice->UpdateSize, frame_step); - if(data.second.len > 0) - mDevice->renderSamples(data.second.buf, - static_cast(data.second.len)*mDevice->UpdateSize, frame_step); + mDevice->renderSamples(data[0].buf, + static_cast(data[0].len)*mDevice->mUpdateSize, frame_step); + if(data[1].len > 0) + mDevice->renderSamples(data[1].buf, + static_cast(data[1].len)*mDevice->mUpdateSize, frame_step); - size_t todo{data.first.len + data.second.len}; + const auto todo = size_t{data[0].len + data[1].len}; mRing->writeAdvance(todo); dlock.unlock(); for(size_t i{0};i < todo;i++) { - if(!data.first.len) + if(!data[0].len) { - data.first = data.second; - data.second.buf = nullptr; - data.second.len = 0; + data[0] = data[1]; + data[1].buf = nullptr; + data[1].len = 0; } - result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize); + result = VCALL(bufferQueue,Enqueue)(data[0].buf, mDevice->mUpdateSize*mFrameSize); PrintErr(result, "bufferQueue->Enqueue"); if(SL_RESULT_SUCCESS != result) { - mDevice->handleDisconnect("Failed to queue audio: 0x%08x", result); + mDevice->handleDisconnect("Failed to queue audio: {:#08x}", result); break; } - data.first.len--; - data.first.buf += mDevice->UpdateSize*mFrameSize; + data[0].len--; + data[0].buf += mDevice->mUpdateSize*mFrameSize; } } @@ -319,8 +346,8 @@ void OpenSLPlayback::open(std::string_view name) if(name.empty()) name = GetDeviceName(); else if(name != GetDeviceName()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; /* There's only one device, so if it's already open, there's nothing to do. */ if(mEngineObj) return; @@ -361,10 +388,10 @@ void OpenSLPlayback::open(std::string_view name) mEngine = nullptr; throw al::backend_exception{al::backend_error::DeviceError, - "Failed to initialize OpenSL device: 0x%08x", result}; + "Failed to initialize OpenSL device: {:#08x}", result}; } - mDevice->DeviceName = name; + mDeviceName = name; } bool OpenSLPlayback::reset() @@ -397,14 +424,14 @@ bool OpenSLPlayback::reset() SLDataLocator_AndroidSimpleBufferQueue loc_bufq{}; loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; - loc_bufq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize; + loc_bufq.numBuffers = mDevice->mBufferSize / mDevice->mUpdateSize; SLDataSource audioSrc{}; #ifdef SL_ANDROID_DATAFORMAT_PCM_EX SLAndroidDataFormat_PCM_EX format_pcm_ex{}; format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; format_pcm_ex.numChannels = mDevice->channelsFromFmt(); - format_pcm_ex.sampleRate = mDevice->Frequency * 1000; + format_pcm_ex.sampleRate = mDevice->mSampleRate * 1000; format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample; format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans); @@ -435,7 +462,7 @@ bool OpenSLPlayback::reset() SLDataFormat_PCM format_pcm{}; format_pcm.formatType = SL_DATAFORMAT_PCM; format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.samplesPerSec = mDevice->Frequency * 1000; + format_pcm.samplesPerSec = mDevice->mSampleRate * 1000; format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm.containerSize = format_pcm.bitsPerSample; format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); @@ -472,8 +499,8 @@ bool OpenSLPlayback::reset() } if(SL_RESULT_SUCCESS == result) { - const uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; - mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->UpdateSize, true); + const uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize}; + mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->mUpdateSize, true); } if(SL_RESULT_SUCCESS != result) @@ -505,15 +532,15 @@ void OpenSLPlayback::start() } if(SL_RESULT_SUCCESS != result) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to register callback: 0x%08x", result}; + "Failed to register callback: {:#08x}", result}; try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this); + mThread = std::thread(&OpenSLPlayback::mixerProc, this); } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -566,15 +593,15 @@ ClockLatency OpenSLPlayback::getClockLatency() std::lock_guard dlock{mMutex}; ret.ClockTime = mDevice->getClockTime(); - ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize}; - ret.Latency /= mDevice->Frequency; + ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->mUpdateSize}; + ret.Latency /= mDevice->mSampleRate; return ret; } struct OpenSLCapture final : public BackendBase { - OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~OpenSLCapture() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; @@ -623,8 +650,8 @@ void OpenSLCapture::open(std::string_view name) if(name.empty()) name = GetDeviceName(); else if(name != GetDeviceName()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; PrintErr(result, "slCreateEngine"); @@ -642,16 +669,16 @@ void OpenSLCapture::open(std::string_view name) { mFrameSize = mDevice->frameSizeFromFmt(); /* Ensure the total length is at least 100ms */ - uint length{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; + uint length{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)}; /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */ - uint update_len{std::clamp(mDevice->BufferSize/3u, mDevice->Frequency/100u, - mDevice->Frequency/100u*5u)}; + uint update_len{std::clamp(mDevice->mBufferSize/3u, mDevice->mSampleRate/100u, + mDevice->mSampleRate/100u*5u)}; uint num_updates{(length+update_len-1) / update_len}; mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false); - mDevice->UpdateSize = update_len; - mDevice->BufferSize = static_cast(mRing->writeSpace() * update_len); + mDevice->mUpdateSize = update_len; + mDevice->mBufferSize = static_cast(mRing->writeSpace() * update_len); } if(SL_RESULT_SUCCESS == result) { @@ -670,14 +697,14 @@ void OpenSLCapture::open(std::string_view name) SLDataLocator_AndroidSimpleBufferQueue loc_bq{}; loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; - loc_bq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize; + loc_bq.numBuffers = mDevice->mBufferSize / mDevice->mUpdateSize; SLDataSink audioSnk{}; #ifdef SL_ANDROID_DATAFORMAT_PCM_EX SLAndroidDataFormat_PCM_EX format_pcm_ex{}; format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; format_pcm_ex.numChannels = mDevice->channelsFromFmt(); - format_pcm_ex.sampleRate = mDevice->Frequency * 1000; + format_pcm_ex.sampleRate = mDevice->mSampleRate * 1000; format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample; format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans); @@ -700,7 +727,7 @@ void OpenSLCapture::open(std::string_view name) SLDataFormat_PCM format_pcm{}; format_pcm.formatType = SL_DATAFORMAT_PCM; format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.samplesPerSec = mDevice->Frequency * 1000; + format_pcm.samplesPerSec = mDevice->mSampleRate * 1000; format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm.containerSize = format_pcm.bitsPerSample; format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); @@ -752,20 +779,20 @@ void OpenSLCapture::open(std::string_view name) } if(SL_RESULT_SUCCESS == result) { - const uint chunk_size{mDevice->UpdateSize * mFrameSize}; + const uint chunk_size{mDevice->mUpdateSize * mFrameSize}; const auto silence = (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{0}; auto data = mRing->getWriteVector(); - std::fill_n(data.first.buf, data.first.len*chunk_size, silence); - std::fill_n(data.second.buf, data.second.len*chunk_size, silence); - for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++) + std::fill_n(data[0].buf, data[0].len*chunk_size, silence); + std::fill_n(data[1].buf, data[1].len*chunk_size, silence); + for(size_t i{0u};i < data[0].len && SL_RESULT_SUCCESS == result;i++) { - result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size); + result = VCALL(bufferQueue,Enqueue)(data[0].buf + chunk_size*i, chunk_size); PrintErr(result, "bufferQueue->Enqueue"); } - for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++) + for(size_t i{0u};i < data[1].len && SL_RESULT_SUCCESS == result;i++) { - result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size); + result = VCALL(bufferQueue,Enqueue)(data[1].buf + chunk_size*i, chunk_size); PrintErr(result, "bufferQueue->Enqueue"); } } @@ -782,10 +809,10 @@ void OpenSLCapture::open(std::string_view name) mEngine = nullptr; throw al::backend_exception{al::backend_error::DeviceError, - "Failed to initialize OpenSL device: 0x%08x", result}; + "Failed to initialize OpenSL device: {:#08x}", result}; } - mDevice->DeviceName = name; + mDeviceName = name; } void OpenSLCapture::start() @@ -801,7 +828,7 @@ void OpenSLCapture::start() } if(SL_RESULT_SUCCESS != result) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start capture: 0x%08x", result}; + "Failed to start capture: {:#08x}", result}; } void OpenSLCapture::stop() @@ -819,7 +846,7 @@ void OpenSLCapture::stop() void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) { - const uint update_size{mDevice->UpdateSize}; + const uint update_size{mDevice->mUpdateSize}; const uint chunk_size{update_size * mFrameSize}; /* Read the desired samples from the ring buffer then advance its read @@ -830,7 +857,7 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) for(uint i{0};i < samples;) { const uint rem{std::min(samples - i, update_size - mSplOffset)}; - std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize}, + std::copy_n(rdata[0].buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize}, buffer + i*size_t{mFrameSize}); mSplOffset += rem; @@ -840,11 +867,11 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) mSplOffset = 0; ++adv_count; - rdata.first.len -= 1; - if(!rdata.first.len) - rdata.first = rdata.second; + rdata[0].len -= 1; + if(!rdata[0].len) + rdata[0] = rdata[1]; else - rdata.first.buf += chunk_size; + rdata[0].buf += chunk_size; } i += rem; @@ -858,7 +885,7 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) PrintErr(result, "recordObj->GetInterface"); if(SL_RESULT_SUCCESS != result) UNLIKELY { - mDevice->handleDisconnect("Failed to get capture buffer queue: 0x%08x", result); + mDevice->handleDisconnect("Failed to get capture buffer queue: {:#08x}", result); bufferQueue = nullptr; } } @@ -877,20 +904,20 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) SLresult result{SL_RESULT_SUCCESS}; auto wdata = mRing->getWriteVector(); - if(adv_count > wdata.second.len) LIKELY + if(adv_count > wdata[1].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); + auto len1 = std::min(wdata[0].len, adv_count-wdata[1].len); + auto buf1 = wdata[0].buf + chunk_size*(wdata[0].len-len1); for(size_t i{0u};i < len1 && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(buf1 + chunk_size*i, chunk_size); PrintErr(result, "bufferQueue->Enqueue"); } } - if(wdata.second.len > 0) + if(wdata[1].len > 0) { - auto len2 = std::min(wdata.second.len, adv_count); - auto buf2 = wdata.second.buf + chunk_size*(wdata.second.len-len2); + auto len2 = std::min(wdata[1].len, adv_count); + auto buf2 = wdata[1].buf + chunk_size*(wdata[1].len-len2); for(size_t i{0u};i < len2 && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(buf2 + chunk_size*i, chunk_size); @@ -900,11 +927,43 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) } uint OpenSLCapture::availableSamples() -{ return static_cast(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); } +{ return static_cast(mRing->readSpace()*mDevice->mUpdateSize - mSplOffset); } } // namespace -bool OSLBackendFactory::init() { return true; } +bool OSLBackendFactory::init() +{ +#if HAVE_DYNLOAD + if(!sles_handle) + { +#define SLES_LIBNAME "libOpenSLES.so" + sles_handle = LoadLib(SLES_LIBNAME); + if(!sles_handle) + { + WARN("Failed to load {}", SLES_LIBNAME); + return false; + } + + std::string missing_syms; +#define LOAD_SYMBOL(f) do { \ + p##f = reinterpret_cast(GetSymbol(sles_handle, #f)); \ + if(p##f == nullptr) missing_syms += "\n" #f; \ +} while(0) + SLES_SYMBOLS(LOAD_SYMBOL); +#undef LOAD_SYMBOL + + if(!missing_syms.empty()) + { + WARN("Missing expected symbols:{}", missing_syms); + CloseLib(sles_handle); + sles_handle = nullptr; + return false; + } + } +#endif + + return true; +} bool OSLBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } diff --git a/Engine/lib/openal-soft/alc/backends/oss.cpp b/Engine/lib/openal-soft/alc/backends/oss.cpp index bab99842c..cf73c18ca 100644 --- a/Engine/lib/openal-soft/alc/backends/oss.cpp +++ b/Engine/lib/openal-soft/alc/backends/oss.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -43,13 +42,12 @@ #include #include "alc/alconfig.h" -#include "almalloc.h" #include "alnumeric.h" -#include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" +#include "fmt/core.h" #include "ringbuffer.h" #include @@ -169,18 +167,13 @@ void ALCossListAppend(std::vector &list, std::string_view handle, std::s auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; }; return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); }; - int count{1}; - std::string newname{handle}; + auto count = 1; + auto newname = std::string{handle}; while(checkName(newname)) - { - newname = handle; - newname += " #"; - newname += std::to_string(++count); - } + newname = fmt::format("{} #{}", handle, ++count); - const DevMap &entry = list.emplace_back(std::move(newname), path); - - TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); + const auto &entry = list.emplace_back(std::move(newname), path); + TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name); } void ALCossListPopulate(std::vector &devlist, int type_flag) @@ -189,13 +182,13 @@ void ALCossListPopulate(std::vector &devlist, int type_flag) FileHandle file; if(!file.open("/dev/mixer", O_RDONLY)) { - TRACE("Could not open /dev/mixer: %s\n", std::generic_category().message(errno).c_str()); + TRACE("Could not open /dev/mixer: {}", std::generic_category().message(errno)); goto done; } if(ioctl(file.get(), SNDCTL_SYSINFO, &si) == -1) { - TRACE("SNDCTL_SYSINFO failed: %s\n", std::generic_category().message(errno).c_str()); + TRACE("SNDCTL_SYSINFO failed: {}", std::generic_category().message(errno)); goto done; } @@ -205,8 +198,7 @@ void ALCossListPopulate(std::vector &devlist, int type_flag) ai.dev = i; if(ioctl(file.get(), SNDCTL_AUDIOINFO, &ai) == -1) { - ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, - std::generic_category().message(errno).c_str()); + ERR("SNDCTL_AUDIOINFO ({}) failed: {}", i, std::generic_category().message(errno)); continue; } if(!(ai.caps&type_flag) || ai.devnode[0] == '\0') @@ -256,7 +248,7 @@ uint log2i(uint x) struct OSSPlayback final : public BackendBase { - OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~OSSPlayback() override; int mixerProc(); @@ -302,13 +294,13 @@ int OSSPlayback::mixerProc() if(errno == EINTR || errno == EAGAIN) continue; const auto errstr = std::generic_category().message(errno); - ERR("poll failed: %s\n", errstr.c_str()); - mDevice->handleDisconnect("Failed waiting for playback buffer: %s", errstr.c_str()); + ERR("poll failed: {}", errstr); + mDevice->handleDisconnect("Failed waiting for playback buffer: {}", errstr); break; } else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */ { - WARN("poll timeout\n"); + WARN("poll timeout"); continue; } @@ -323,8 +315,8 @@ int OSSPlayback::mixerProc() if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; const auto errstr = std::generic_category().message(errno); - ERR("write failed: %s\n", errstr.c_str()); - mDevice->handleDisconnect("Failed writing playback samples: %s", errstr.c_str()); + ERR("write failed: {}", errstr); + mDevice->handleDisconnect("Failed writing playback samples: {}", errstr); break; } @@ -352,20 +344,20 @@ void OSSPlayback::open(std::string_view name) ); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; devname = iter->device_name.c_str(); } - int fd{::open(devname, O_WRONLY)}; + const auto fd = ::open(devname, O_WRONLY); /* NOLINT(cppcoreguidelines-pro-type-vararg) */ if(fd == -1) - throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname, - std::generic_category().message(errno).c_str()}; + throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", devname, + std::generic_category().message(errno)}; if(mFd != -1) ::close(mFd); mFd = fd; - mDevice->DeviceName = name; + mDeviceName = name; } bool OSSPlayback::reset() @@ -390,31 +382,33 @@ bool OSSPlayback::reset() break; } - uint periods{mDevice->BufferSize / mDevice->UpdateSize}; + uint periods{mDevice->mBufferSize / mDevice->mUpdateSize}; uint numChannels{mDevice->channelsFromFmt()}; - uint ossSpeed{mDevice->Frequency}; + uint ossSpeed{mDevice->mSampleRate}; uint frameSize{numChannels * mDevice->bytesFromFmt()}; /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */ - uint log2FragmentSize{std::max(log2i(mDevice->UpdateSize*frameSize), 4u)}; + uint log2FragmentSize{std::max(log2i(mDevice->mUpdateSize*frameSize), 4u)}; uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; audio_buf_info info{}; #define CHECKERR(func) if((func) < 0) \ - throw al::backend_exception{al::backend_error::DeviceError, "%s failed: %s\n", #func, \ - std::generic_category().message(errno).c_str()}; + throw al::backend_exception{al::backend_error::DeviceError, #func " failed: {}", \ + std::generic_category().message(errno)}; /* Don't fail if SETFRAGMENT fails. We can handle just about anything * that's reported back via GETOSPACE */ + /* NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) */ ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize); CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed)); CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info)); + /* NOLINTEND(cppcoreguidelines-pro-type-vararg) */ #undef CHECKERR if(mDevice->channelsFromFmt() != numChannels) { - ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans), + ERR("Failed to set {}, got {} channels instead", DevFmtChannelsString(mDevice->FmtChans), numChannels); return false; } @@ -423,18 +417,18 @@ bool OSSPlayback::reset() (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) || (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort))) { - ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType), - ossFormat); + ERR("Failed to set {} samples, got OSS format {:#x}", DevFmtTypeString(mDevice->FmtType), + as_unsigned(ossFormat)); return false; } - mDevice->Frequency = ossSpeed; - mDevice->UpdateSize = static_cast(info.fragsize) / frameSize; - mDevice->BufferSize = static_cast(info.fragments) * mDevice->UpdateSize; + mDevice->mSampleRate = ossSpeed; + mDevice->mUpdateSize = static_cast(info.fragsize) / frameSize; + mDevice->mBufferSize = static_cast(info.fragments) * mDevice->mUpdateSize; setDefaultChannelOrder(); - mMixData.resize(size_t{mDevice->UpdateSize} * mDevice->frameSizeFromFmt()); + mMixData.resize(size_t{mDevice->mUpdateSize} * mDevice->frameSizeFromFmt()); return true; } @@ -443,11 +437,11 @@ void OSSPlayback::start() { try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this}; + mThread = std::thread{&OSSPlayback::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -457,13 +451,13 @@ void OSSPlayback::stop() return; mThread.join(); - if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) - ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str()); + if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) /* NOLINT(cppcoreguidelines-pro-type-vararg) */ + ERR("Error resetting device: {}", std::generic_category().message(errno)); } struct OSScapture final : public BackendBase { - OSScapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit OSScapture(DeviceBase *device) noexcept : BackendBase{device} { } ~OSScapture() override; int recordProc(); @@ -507,25 +501,25 @@ int OSScapture::recordProc() if(errno == EINTR || errno == EAGAIN) continue; const auto errstr = std::generic_category().message(errno); - ERR("poll failed: %s\n", errstr.c_str()); - mDevice->handleDisconnect("Failed to check capture samples: %s", errstr.c_str()); + ERR("poll failed: {}", errstr); + mDevice->handleDisconnect("Failed to check capture samples: {}", errstr); break; } else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */ { - WARN("poll timeout\n"); + WARN("poll timeout"); continue; } auto vec = mRing->getWriteVector(); - if(vec.first.len > 0) + if(vec[0].len > 0) { - ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)}; + ssize_t amt{read(mFd, vec[0].buf, vec[0].len*frame_size)}; if(amt < 0) { const auto errstr = std::generic_category().message(errno); - ERR("read failed: %s\n", errstr.c_str()); - mDevice->handleDisconnect("Failed reading capture samples: %s", errstr.c_str()); + ERR("read failed: {}", errstr); + mDevice->handleDisconnect("Failed reading capture samples: {}", errstr); break; } mRing->writeAdvance(static_cast(amt)/frame_size); @@ -552,14 +546,14 @@ void OSScapture::open(std::string_view name) ); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; devname = iter->device_name.c_str(); } - mFd = ::open(devname, O_RDONLY); + mFd = ::open(devname, O_RDONLY); /* NOLINT(cppcoreguidelines-pro-type-vararg) */ if(mFd == -1) - throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname, - std::generic_category().message(errno).c_str()}; + throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", devname, + std::generic_category().message(errno)}; int ossFormat{}; switch(mDevice->FmtType) @@ -578,55 +572,57 @@ void OSScapture::open(std::string_view name) case DevFmtUInt: case DevFmtFloat: throw al::backend_exception{al::backend_error::DeviceError, - "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; + "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } uint periods{4}; uint numChannels{mDevice->channelsFromFmt()}; uint frameSize{numChannels * mDevice->bytesFromFmt()}; - uint ossSpeed{mDevice->Frequency}; + uint ossSpeed{mDevice->mSampleRate}; /* according to the OSS spec, 16 bytes are the minimum */ - uint log2FragmentSize{std::max(log2i(mDevice->BufferSize * frameSize / periods), 4u)}; + uint log2FragmentSize{std::max(log2i(mDevice->mBufferSize * frameSize / periods), 4u)}; uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; audio_buf_info info{}; #define CHECKERR(func) if((func) < 0) { \ - throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \ - std::generic_category().message(errno).c_str()}; \ + throw al::backend_exception{al::backend_error::DeviceError, #func " failed: {}", \ + std::generic_category().message(errno)}; \ } + /* NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) */ CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed)); CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info)); + /* NOLINTEND(cppcoreguidelines-pro-type-vararg) */ #undef CHECKERR if(mDevice->channelsFromFmt() != numChannels) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to set %s, got %d channels instead", DevFmtChannelsString(mDevice->FmtChans), + "Failed to set {}, got {} channels instead", DevFmtChannelsString(mDevice->FmtChans), numChannels}; if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) || (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) || (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort))) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice->FmtType), - ossFormat}; + "Failed to set {} samples, got OSS format {:#x}", DevFmtTypeString(mDevice->FmtType), + as_unsigned(ossFormat)}; - mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false); + mRing = RingBuffer::Create(mDevice->mBufferSize, frameSize, false); - mDevice->DeviceName = name; + mDeviceName = name; } void OSScapture::start() { try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this}; + mThread = std::thread{&OSScapture::recordProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start recording thread: %s", e.what()}; + "Failed to start recording thread: {}", e.what()}; } } @@ -636,8 +632,8 @@ void OSScapture::stop() return; mThread.join(); - if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) - ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str()); + if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) /* NOLINT(cppcoreguidelines-pro-type-vararg) */ + ERR("Error resetting device: {}", std::generic_category().message(errno)); } void OSScapture::captureSamples(std::byte *buffer, uint samples) diff --git a/Engine/lib/openal-soft/alc/backends/otherio.cpp b/Engine/lib/openal-soft/alc/backends/otherio.cpp new file mode 100644 index 000000000..e7b33a78b --- /dev/null +++ b/Engine/lib/openal-soft/alc/backends/otherio.cpp @@ -0,0 +1,700 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2024 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "otherio.h" + +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#ifndef _WAVEFORMATEXTENSIBLE_ +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "albit.h" +#include "alnumeric.h" +#include "althrd_setname.h" +#include "comptr.h" +#include "core/converter.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "strutils.h" + + +/* A custom C++ interface that should be capable of interoperating with ASIO + * drivers. + */ +enum class ORIOError : LONG { + Okay = 0, + Success = 0x3f4847a0, + NotPresent = -1000, + HWMalfunction, + InvalidParameter, + InvalidMode, + SPNotAdvancing, + NoClock, + NoMemory, +}; + +/* A 64-bit integer or double, which has the most significant 32-bit word first. */ +struct ORIO64Bit { + uint32_t hi; + uint32_t lo; + + template + auto as() const -> T = delete; +}; + +template<> [[nodiscard]] +auto ORIO64Bit::as() const -> uint64_t { return (uint64_t{hi}<<32) | lo; } +template<> [[nodiscard]] +auto ORIO64Bit::as() const -> int64_t { return static_cast(as()); } +template<> [[nodiscard]] +auto ORIO64Bit::as() const -> double { return al::bit_cast(as()); } + + +enum class ORIOSampleType : LONG { + Int16BE = 0, + Int24BE = 1, + Int32BE = 2, + Float32BE = 3, + Float64BE = 4, + Int32BE16 = 8, + Int32BE18 = 9, + Int32BE20 = 10, + Int32BE24 = 11, + + Int16LE = 16, + Int24LE = 17, + Int32LE = 18, + Float32LE = 19, + Float64LE = 20, + Int32LE16 = 24, + Int32LE18 = 25, + Int32LE20 = 26, + Int32LE24 = 27, + + DSDInt8LSB1 = 32, + DSDInt8MSB1 = 33, + + DSDInt8 = 40, +}; + +struct ORIOClockSource { + LONG mIndex; + LONG mAssocChannel; + LONG mAssocGroup; + LONG mIsCurrent; + std::array mName; +}; + +struct ORIOChannelInfo { + LONG mChannel; + LONG mIsInput; + LONG mIsActive; + LONG mGroup; + ORIOSampleType mSampleType; + std::array mName; +}; + +struct ORIOBufferInfo { + LONG mIsInput; + LONG mChannelNum; + std::array mBuffers; +}; + +struct ORIOTime { + struct TimeInfo { + double mSpeed; + ORIO64Bit mSystemTime; + ORIO64Bit mSamplePosition; + double mSampleRate; + ULONG mFlags; + std::array mReserved; + }; + struct TimeCode { + double mSpeed; + ORIO64Bit mTimeCodeSamples; + ULONG mFlags; + std::array mFuture; + }; + + std::array mReserved; + TimeInfo mTimeInfo; + TimeCode mTimeCode; +}; + +#ifdef _WIN64 +#define ORIO_CALLBACK CALLBACK +#else +#define ORIO_CALLBACK +#endif + +struct ORIOCallbacks { + void (ORIO_CALLBACK*BufferSwitch)(LONG bufferIndex, LONG directProcess) noexcept; + void (ORIO_CALLBACK*SampleRateDidChange)(double srate) noexcept; + auto (ORIO_CALLBACK*Message)(LONG selector, LONG value, void *message, double *opt) noexcept -> LONG; + auto (ORIO_CALLBACK*BufferSwitchTimeInfo)(ORIOTime *timeInfo, LONG bufferIndex, LONG directProcess) noexcept -> ORIOTime*; +}; + +/* COM interfaces don't include a virtual destructor in their pure-virtual + * classes, and we can't add one without breaking ABI. + */ +#ifdef __GNUC__ +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"") +#endif +/* NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor) */ +struct ORIOiface : public IUnknown { + STDMETHOD_(LONG, Init)(void *sysHandle) = 0; + /* A fixed-length span should be passed exactly the same as one pointer. + * This ensures an appropriately-sized buffer for the driver. + */ + STDMETHOD_(void, GetDriverName)(al::span name) = 0; + STDMETHOD_(LONG, GetDriverVersion)() = 0; + STDMETHOD_(void, GetErrorMessage)(al::span message) = 0; + STDMETHOD_(ORIOError, Start)() = 0; + STDMETHOD_(ORIOError, Stop)() = 0; + STDMETHOD_(ORIOError, GetChannels)(LONG *numInput, LONG *numOutput) = 0; + STDMETHOD_(ORIOError, GetLatencies)(LONG *inputLatency, LONG *outputLatency) = 0; + STDMETHOD_(ORIOError, GetBufferSize)(LONG *minSize, LONG *maxSize, LONG *preferredSize, LONG *granularity) = 0; + STDMETHOD_(ORIOError, CanSampleRate)(double srate) = 0; + STDMETHOD_(ORIOError, GetSampleRate)(double *srate) = 0; + STDMETHOD_(ORIOError, SetSampleRate)(double srate) = 0; + STDMETHOD_(ORIOError, GetClockSources)(ORIOClockSource *clocks, LONG *numSources) = 0; + STDMETHOD_(ORIOError, SetClockSource)(LONG index) = 0; + STDMETHOD_(ORIOError, GetSamplePosition)(ORIO64Bit *splPos, ORIO64Bit *tstampNS) = 0; + STDMETHOD_(ORIOError, GetChannelInfo)(ORIOChannelInfo *info) = 0; + STDMETHOD_(ORIOError, CreateBuffers)(ORIOBufferInfo *infos, LONG numInfos, LONG bufferSize, ORIOCallbacks *callbacks) = 0; + STDMETHOD_(ORIOError, DisposeBuffers)() = 0; + STDMETHOD_(ORIOError, ControlPanel)() = 0; + STDMETHOD_(ORIOError, Future)(LONG selector, void *opt) = 0; + STDMETHOD_(ORIOError, OutputReady)() = 0; + + ORIOiface() = default; + ORIOiface(const ORIOiface&) = delete; + auto operator=(const ORIOiface&) -> ORIOiface& = delete; + ~ORIOiface() = delete; +}; +#ifdef __GNUC__ +_Pragma("GCC diagnostic pop") +#endif + +namespace { + +using namespace std::string_view_literals; +using std::chrono::nanoseconds; +using std::chrono::milliseconds; +using std::chrono::seconds; + + +struct DeviceEntry { + std::string mDrvName; + CLSID mDrvGuid{}; +}; + +std::vector gDeviceList; + + +struct KeyCloser { + void operator()(HKEY key) { RegCloseKey(key); } +}; +using KeyPtr = std::unique_ptr,KeyCloser>; + +[[nodiscard]] +auto PopulateDeviceList() -> HRESULT +{ + auto regbase = KeyPtr{}; + auto res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\ASIO", 0, KEY_READ, + al::out_ptr(regbase)); + if(res != ERROR_SUCCESS) + { + ERR("Error opening HKLM\\Software\\ASIO: {}", res); + return E_NOINTERFACE; + } + + auto numkeys = DWORD{}; + auto maxkeylen = DWORD{}; + res = RegQueryInfoKeyW(regbase.get(), nullptr, nullptr, nullptr, &numkeys, &maxkeylen, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr); + if(res != ERROR_SUCCESS) + { + ERR("Error querying HKLM\\Software\\ASIO info: {}", res); + return E_FAIL; + } + + /* maxkeylen is the max number of unicode characters a subkey is. A unicode + * character can occupy two WCHARs, so ensure there's enough space for them + * and the null char. + */ + auto keyname = std::vector(maxkeylen*2 + 1); + for(DWORD i{0};i < numkeys;++i) + { + auto namelen = static_cast(keyname.size()); + res = RegEnumKeyExW(regbase.get(), i, keyname.data(), &namelen, nullptr, nullptr, nullptr, + nullptr); + if(res != ERROR_SUCCESS) + { + ERR("Error querying HKLM\\Software\\ASIO subkey {}: {}", i, res); + continue; + } + if(namelen == 0) + { + ERR("HKLM\\Software\\ASIO subkey {} is blank?", i); + continue; + } + auto subkeyname = wstr_to_utf8({keyname.data(), namelen}); + + auto subkey = KeyPtr{}; + res = RegOpenKeyExW(regbase.get(), keyname.data(), 0, KEY_READ, al::out_ptr(subkey)); + if(res != ERROR_SUCCESS) + { + ERR("Error opening HKLM\\Software\\ASIO\\{}: {}", subkeyname, res); + continue; + } + + auto idstr = std::array{}; + auto readsize = DWORD{idstr.size()*sizeof(WCHAR)}; + res = RegGetValueW(subkey.get(), L"", L"CLSID", RRF_RT_REG_SZ, nullptr, idstr.data(), + &readsize); + if(res != ERROR_SUCCESS) + { + ERR("Failed to read HKLM\\Software\\ASIO\\{}\\CLSID: {}", subkeyname, res); + continue; + } + idstr.back() = 0; + + auto guid = CLSID{}; + if(auto hr = CLSIDFromString(idstr.data(), &guid); FAILED(hr)) + { + ERR("Failed to parse CLSID \"{}\": {:#x}", wstr_to_utf8(idstr.data()), + as_unsigned(hr)); + continue; + } + + /* The CLSID is also used for the IID. */ + auto iface = ComPtr{}; + auto hr = CoCreateInstance(guid, nullptr, CLSCTX_INPROC_SERVER, guid, al::out_ptr(iface)); + if(SUCCEEDED(hr)) + { +#if !ALSOFT_UWP + if(!iface->Init(GetForegroundWindow())) +#else + if(!iface->Init(nullptr)) +#endif + { + ERR("Failed to initialize {}", subkeyname); + continue; + } + auto drvname = std::array{}; + iface->GetDriverName(drvname); + auto drvver = iface->GetDriverVersion(); + + auto &entry = gDeviceList.emplace_back(); + entry.mDrvName = drvname.data(); + entry.mDrvGuid = guid; + + TRACE("Got {} v{}, CLSID {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}", + entry.mDrvName, drvver, guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], + guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], + guid.Data4[6], guid.Data4[7]); + } + else + ERR("Failed to create {} instance for CLSID {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}: {:#x}", + subkeyname.c_str(), guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], + guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], + guid.Data4[6], guid.Data4[7], as_unsigned(hr)); + } + + return S_OK; +} + + +enum class MsgType { + OpenDevice, + ResetDevice, + StartDevice, + StopDevice, + CloseDevice, + + QuitThread +}; + +constexpr const char *GetMessageTypeName(MsgType type) noexcept +{ + switch(type) + { + case MsgType::OpenDevice: return "Open Device"; + case MsgType::ResetDevice: return "Reset Device"; + case MsgType::StartDevice: return "Start Device"; + case MsgType::StopDevice: return "Stop Device"; + case MsgType::CloseDevice: return "Close Device"; + case MsgType::QuitThread: break; + } + return ""; +} + + +/* Proxy interface used by the message handler, to ensure COM objects are used + * on a thread where COM is initialized. + */ +struct OtherIOProxy { + OtherIOProxy() = default; + OtherIOProxy(const OtherIOProxy&) = delete; + OtherIOProxy(OtherIOProxy&&) = delete; + virtual ~OtherIOProxy() = default; + + void operator=(const OtherIOProxy&) = delete; + void operator=(OtherIOProxy&&) = delete; + + virtual HRESULT openProxy(std::string_view name) = 0; + virtual void closeProxy() = 0; + + virtual HRESULT resetProxy() = 0; + virtual HRESULT startProxy() = 0; + virtual void stopProxy() = 0; + + struct Msg { + MsgType mType; + OtherIOProxy *mProxy; + std::string_view mParam; + std::promise mPromise; + + explicit operator bool() const noexcept { return mType != MsgType::QuitThread; } + }; + static inline std::deque mMsgQueue; + static inline std::mutex mMsgQueueLock; + static inline std::condition_variable mMsgQueueCond; + + auto pushMessage(MsgType type, std::string_view param={}) -> std::future + { + auto promise = std::promise{}; + auto future = std::future{promise.get_future()}; + { + auto msglock = std::lock_guard{mMsgQueueLock}; + mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)}); + } + mMsgQueueCond.notify_one(); + return future; + } + + static auto popMessage() -> Msg + { + auto lock = std::unique_lock{mMsgQueueLock}; + mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();}); + auto msg = Msg{std::move(mMsgQueue.front())}; + mMsgQueue.pop_front(); + return msg; + } + + static void messageHandler(std::promise *promise); +}; + +void OtherIOProxy::messageHandler(std::promise *promise) +{ + TRACE("Starting COM message thread"); + + auto com = ComWrapper{COINIT_APARTMENTTHREADED}; + if(!com) + { + WARN("Failed to initialize COM: {:#x}", as_unsigned(com.status())); + promise->set_value(com.status()); + return; + } + + auto hr = PopulateDeviceList(); + if(FAILED(hr)) + { + promise->set_value(hr); + return; + } + + promise->set_value(S_OK); + promise = nullptr; + + TRACE("Starting message loop"); + while(Msg msg{popMessage()}) + { + TRACE("Got message \"{}\" ({:#04x}, this={}, param=\"{}\")", + GetMessageTypeName(msg.mType), static_cast(msg.mType), + static_cast(msg.mProxy), msg.mParam); + + switch(msg.mType) + { + case MsgType::OpenDevice: + hr = msg.mProxy->openProxy(msg.mParam); + msg.mPromise.set_value(hr); + continue; + + case MsgType::ResetDevice: + hr = msg.mProxy->resetProxy(); + msg.mPromise.set_value(hr); + continue; + + case MsgType::StartDevice: + hr = msg.mProxy->startProxy(); + msg.mPromise.set_value(hr); + continue; + + case MsgType::StopDevice: + msg.mProxy->stopProxy(); + msg.mPromise.set_value(S_OK); + continue; + + case MsgType::CloseDevice: + msg.mProxy->closeProxy(); + msg.mPromise.set_value(S_OK); + continue; + + case MsgType::QuitThread: + break; + } + ERR("Unexpected message: {}", int{al::to_underlying(msg.mType)}); + msg.mPromise.set_value(E_FAIL); + } + TRACE("Message loop finished"); +} + + +struct OtherIOPlayback final : public BackendBase, OtherIOProxy { + explicit OtherIOPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + ~OtherIOPlayback() final; + + void mixerProc(); + + void open(std::string_view name) final; + auto openProxy(std::string_view name) -> HRESULT final; + void closeProxy() final; + auto reset() -> bool final; + auto resetProxy() -> HRESULT final; + void start() final; + auto startProxy() -> HRESULT final; + void stop() final; + void stopProxy() final; + + HRESULT mOpenStatus{E_FAIL}; + + std::atomic mKillNow{true}; + std::thread mThread; +}; + +OtherIOPlayback::~OtherIOPlayback() +{ + if(SUCCEEDED(mOpenStatus)) + pushMessage(MsgType::CloseDevice).wait(); +} + +void OtherIOPlayback::mixerProc() +{ + const auto restTime = milliseconds{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2}; + + SetRTPriority(); + althrd_setname(GetMixerThreadName()); + + auto done = int64_t{0}; + auto start = std::chrono::steady_clock::now(); + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) + { + auto now = std::chrono::steady_clock::now(); + + /* This converts from nanoseconds to nanosamples, then to samples. */ + const auto avail = int64_t{std::chrono::duration_cast((now-start) + * mDevice->mSampleRate).count()}; + if(avail-done < mDevice->mUpdateSize) + { + std::this_thread::sleep_for(restTime); + continue; + } + while(avail-done >= mDevice->mUpdateSize) + { + mDevice->renderSamples(nullptr, mDevice->mUpdateSize, 0u); + done += mDevice->mUpdateSize; + } + + if(done >= mDevice->mSampleRate) + { + auto s = seconds{done/mDevice->mSampleRate}; + start += s; + done -= mDevice->mSampleRate*s.count(); + } + } +} + + +void OtherIOPlayback::open(std::string_view name) +{ + if(name.empty() && !gDeviceList.empty()) + name = gDeviceList[0].mDrvName; + else + { + auto iter = std::find_if(gDeviceList.cbegin(), gDeviceList.cend(), + [name](const DeviceEntry &entry) { return entry.mDrvName == name; }); + if(iter == gDeviceList.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"{}\" not found", name}; + } + + mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); + if(FAILED(mOpenStatus)) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to open \"{}\"", name}; + + mDeviceName = name; +} + +auto OtherIOPlayback::openProxy(std::string_view name [[maybe_unused]]) -> HRESULT +{ + return S_OK; +} + +void OtherIOPlayback::closeProxy() +{ +} + +auto OtherIOPlayback::reset() -> bool +{ + return SUCCEEDED(pushMessage(MsgType::ResetDevice).get()); +} + +auto OtherIOPlayback::resetProxy() -> HRESULT +{ + setDefaultWFXChannelOrder(); + return S_OK; +} + +void OtherIOPlayback::start() +{ + auto hr = pushMessage(MsgType::StartDevice).get(); + if(FAILED(hr)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start playback: {:#x}", as_unsigned(hr)}; +} + +auto OtherIOPlayback::startProxy() -> HRESULT +{ + try { + mKillNow.store(false, std::memory_order_release); + mThread = std::thread{&OtherIOPlayback::mixerProc, this}; + return S_OK; + } + catch(std::exception& e) { + ERR("Failed to start mixing thread: {}", e.what()); + } + return E_FAIL; +} + +void OtherIOPlayback::stop() +{ + pushMessage(MsgType::StopDevice).wait(); +} + +void OtherIOPlayback::stopProxy() +{ + if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) + return; + mThread.join(); +} + +} // namespace + + +auto OtherIOBackendFactory::init() -> bool +{ + static HRESULT InitResult{E_FAIL}; + if(FAILED(InitResult)) try + { + auto promise = std::promise{}; + auto future = promise.get_future(); + + std::thread{&OtherIOProxy::messageHandler, &promise}.detach(); + InitResult = future.get(); + } + catch(...) { + } + + return SUCCEEDED(InitResult); +} + +auto OtherIOBackendFactory::querySupport(BackendType type) -> bool +{ return type == BackendType::Playback; } + +auto OtherIOBackendFactory::enumerate(BackendType type) -> std::vector +{ + std::vector outnames; + + switch(type) + { + case BackendType::Playback: + std::for_each(gDeviceList.cbegin(), gDeviceList.cend(), + [&outnames](const DeviceEntry &entry) { outnames.emplace_back(entry.mDrvName); }); + break; + + case BackendType::Capture: + break; + } + + return outnames; +} + +auto OtherIOBackendFactory::createBackend(DeviceBase *device, BackendType type) -> BackendPtr +{ + if(type == BackendType::Playback) + return BackendPtr{new OtherIOPlayback{device}}; + return nullptr; +} + +auto OtherIOBackendFactory::getFactory() -> BackendFactory& +{ + static auto factory = OtherIOBackendFactory{}; + return factory; +} + +auto OtherIOBackendFactory::queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport +{ + return alc::EventSupport::NoSupport; +} diff --git a/Engine/lib/openal-soft/alc/backends/otherio.h b/Engine/lib/openal-soft/alc/backends/otherio.h new file mode 100644 index 000000000..64cb436ea --- /dev/null +++ b/Engine/lib/openal-soft/alc/backends/otherio.h @@ -0,0 +1,21 @@ +#ifndef BACKENDS_OTHERIO_H +#define BACKENDS_OTHERIO_H + +#include "base.h" + +struct OtherIOBackendFactory final : public BackendFactory { +public: + auto init() -> bool final; + + auto querySupport(BackendType type) -> bool final; + + auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; + + auto enumerate(BackendType type) -> std::vector final; + + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; + + static auto getFactory() -> BackendFactory&; +}; + +#endif /* BACKENDS_OTHERIO_H */ diff --git a/Engine/lib/openal-soft/alc/backends/pipewire.cpp b/Engine/lib/openal-soft/alc/backends/pipewire.cpp index 23589dc6a..12951bb80 100644 --- a/Engine/lib/openal-soft/alc/backends/pipewire.cpp +++ b/Engine/lib/openal-soft/alc/backends/pipewire.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,8 @@ #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" +#include "fmt/core.h" +#include "fmt/ranges.h" #include "opthelpers.h" #include "ringbuffer.h" @@ -132,6 +135,9 @@ _Pragma("GCC diagnostic pop") namespace { +template [[nodiscard]] constexpr +auto as_const_ptr(T *ptr) noexcept -> std::add_const_t* { return ptr; } + struct PodDynamicBuilder { private: std::vector mStorage; @@ -143,7 +149,7 @@ private: mStorage.resize(size); } catch(...) { - ERR("Failed to resize POD storage\n"); + ERR("Failed to resize POD storage"); return -ENOMEM; } mPod.data = mStorage.data(); @@ -152,7 +158,7 @@ private: } public: - PodDynamicBuilder(uint32_t initSize=0) : mStorage(initSize) + explicit PodDynamicBuilder(uint32_t initSize=1024) : mStorage(initSize) , mPod{make_pod_builder(mStorage.data(), initSize)} { static constexpr auto callbacks{[] @@ -189,12 +195,13 @@ bool check_version(const char *version) * future. */ int major{0}, minor{0}, revision{0}; + /* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */ int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)}; return ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR) || (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO)); } -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD #define PWIRE_FUNCS(MAGIC) \ MAGIC(pw_context_connect) \ MAGIC(pw_context_destroy) \ @@ -251,7 +258,7 @@ bool pwire_load() pwire_handle = LoadLib(pwire_library); if(!pwire_handle) { - WARN("Failed to load %s\n", pwire_library); + WARN("Failed to load {}", pwire_library); return false; } @@ -265,7 +272,7 @@ bool pwire_load() if(!missing_funcs.empty()) { - WARN("Missing expected functions:%s\n", missing_funcs.c_str()); + WARN("Missing expected functions:{}", missing_funcs); CloseLib(pwire_handle); pwire_handle = nullptr; return false; @@ -411,9 +418,10 @@ struct PwStreamDeleter { }; using PwStreamPtr = std::unique_ptr; -/* Enums for bitflags... again... *sigh* */ +/* NOLINTBEGIN(*EnumCastOutOfRange) Enums for bitflags... again... *sigh* */ constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept { return static_cast(lhs | al::to_underlying(rhs)); } +/* NOLINTEND(*EnumCastOutOfRange) */ constexpr pw_stream_flags& operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept { lhs = lhs | rhs; return lhs; } @@ -497,7 +505,7 @@ struct NodeProxy { uint32_t mId{}; - PwNodePtr mNode{}; + PwNodePtr mNode; spa_hook mListener{}; NodeProxy(uint32_t id, PwNodePtr node) @@ -532,7 +540,7 @@ struct MetadataProxy { uint32_t mId{}; - PwMetadataPtr mMetadata{}; + PwMetadataPtr mMetadata; spa_hook mListener{}; MetadataProxy(uint32_t id, PwMetadataPtr mdata) @@ -553,10 +561,10 @@ struct MetadataProxy { * to objects being added to or removed from the registry. */ struct EventManager { - ThreadMainloop mLoop{}; - PwContextPtr mContext{}; - PwCorePtr mCore{}; - PwRegistryPtr mRegistry{}; + ThreadMainloop mLoop; + PwContextPtr mContext; + PwCorePtr mCore; + PwRegistryPtr mRegistry; spa_hook mRegistryListener{}; spa_hook mCoreListener{}; @@ -585,8 +593,8 @@ struct EventManager { auto lock() const { return mLoop.lock(); } auto unlock() const { return mLoop.unlock(); } - [[nodiscard]] inline - bool initIsDone(std::memory_order m=std::memory_order_seq_cst) const noexcept + [[nodiscard]] + auto initIsDone(std::memory_order m=std::memory_order_seq_cst) const noexcept -> bool { return mInitDone.load(m); } /** @@ -759,7 +767,7 @@ void DeviceNode::Remove(uint32_t id) { if(n.mId != id) return false; - TRACE("Removing device \"%s\"\n", n.mDevName.c_str()); + TRACE("Removing device \"{}\"", n.mDevName); if(gEventHandler.initIsDone(std::memory_order_relaxed)) { const std::string msg{"Device removed: "+n.mName}; @@ -828,7 +836,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce const uint podType{get_pod_type(value)}; if(podType != SPA_TYPE_Int) { - WARN(" Unhandled sample rate POD type: %u\n", podType); + WARN(" Unhandled sample rate POD type: {}", podType); return; } @@ -836,13 +844,13 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce { if(nvals != 3) { - WARN(" Unexpected SPA_CHOICE_Range count: %u\n", nvals); + WARN(" Unexpected SPA_CHOICE_Range count: {}", nvals); return; } auto srates = get_pod_body(value); /* [0] is the default, [1] is the min, and [2] is the max. */ - TRACE(" sample rate: %d (range: %d -> %d)\n", srates[0], srates[1], srates[2]); + TRACE(" sample rate: {}, range: {}", srates[0], srates.subspan<1>()); if(!mSampleRate || force_update) mSampleRate = static_cast(std::clamp(srates[0], MinOutputRate, MaxOutputRate)); @@ -853,19 +861,13 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce { if(nvals == 0) { - WARN(" Unexpected SPA_CHOICE_Enum count: %u\n", nvals); + WARN(" Unexpected SPA_CHOICE_Enum count: {}", nvals); return; } auto srates = get_pod_body(value, nvals); /* [0] is the default, [1...size()-1] are available selections. */ - std::string others{(srates.size() > 1) ? std::to_string(srates[1]) : std::string{}}; - for(size_t i{2};i < srates.size();++i) - { - others += ", "; - others += std::to_string(srates[i]); - } - TRACE(" sample rate: %d (%s)\n", srates[0], others.c_str()); + TRACE(" sample rate: {}, list: {}", srates[0], srates.subspan(1)); /* Pick the first rate listed that's within the allowed range (default * rate if possible). */ @@ -885,19 +887,19 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce { if(nvals != 1) { - WARN(" Unexpected SPA_CHOICE_None count: %u\n", nvals); + WARN(" Unexpected SPA_CHOICE_None count: {}", nvals); return; } auto srates = get_pod_body(value); - TRACE(" sample rate: %d\n", srates[0]); + TRACE(" sample rate: {}", srates[0]); if(!mSampleRate || force_update) mSampleRate = static_cast(std::clamp(srates[0], MinOutputRate, MaxOutputRate)); return; } - WARN(" Unhandled sample rate choice type: %u\n", choiceType); + WARN(" Unhandled sample rate choice type: {}", choiceType); } void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcept @@ -907,7 +909,7 @@ void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcep if(choiceType != SPA_CHOICE_None || choiceCount != 1) { - ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount); + ERR(" Unexpected positions choice: type={}, count={}", choiceType, choiceCount); return; } @@ -938,7 +940,7 @@ void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcep else mChannels = DevFmtMono; } - TRACE(" %zu position%s for %s%s\n", chanmap.size(), (chanmap.size()==1)?"":"s", + TRACE(" {} position{} for {}{}", chanmap.size(), (chanmap.size()==1)?"":"s", DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":""); } @@ -950,7 +952,7 @@ void DeviceNode::parseChannelCount(const spa_pod *value, bool force_update) noex if(choiceType != SPA_CHOICE_None || choiceCount != 1) { - ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount); + ERR(" Unexpected positions choice: type={}, count={}", choiceType, choiceCount); return; } @@ -966,7 +968,7 @@ void DeviceNode::parseChannelCount(const spa_pod *value, bool force_update) noex else if(*chancount >= 1) mChannels = DevFmtMono; } - TRACE(" %d channel%s for %s\n", *chancount, (*chancount==1)?"":"s", + TRACE(" {} channel{} for {}", *chancount, (*chancount==1)?"":"s", DevFmtChannelsString(mChannels)); } @@ -1005,7 +1007,7 @@ void NodeProxy::infoCallback(void*, const pw_node_info *info) noexcept ntype = NodeType::Duplex; else { - TRACE("Dropping device node %u which became type \"%s\"\n", info->id, media_class); + TRACE("Dropping device node {} which became type \"{}\"", info->id, media_class); DeviceNode::Remove(info->id); return; } @@ -1024,20 +1026,23 @@ void NodeProxy::infoCallback(void*, const pw_node_info *info) noexcept serial_id = std::strtoull(serial_str, &serial_end, 0); if(*serial_end != '\0' || errno == ERANGE) { - ERR("Unexpected object serial: %s\n", serial_str); + ERR("Unexpected object serial: {}", serial_str); serial_id = info->id; } } #endif - std::string name; - if(nodeName && *nodeName) name = nodeName; - else name = "PipeWire node #"+std::to_string(info->id); + auto name = std::invoke([nodeName,info]() -> std::string + { + if(nodeName && *nodeName) + return std::string{nodeName}; + return fmt::format("PipeWire node #{}", info->id); + }); const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)}; - TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)", + TRACE("Got {} device \"{}\"{}{}{}", AsString(ntype), devName ? devName : "(nil)", form_factor?" (":"", form_factor?form_factor:"", form_factor?")":""); - TRACE(" \"%s\" = ID %" PRIu64 "\n", name.c_str(), serial_id); + TRACE(" \"{}\" = ID {}", name, serial_id); DeviceNode &node = DeviceNode::Add(info->id); node.mSerial = serial_id; @@ -1087,7 +1092,7 @@ void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_po DeviceNode *node{DeviceNode::Find(mId)}; if(!node) UNLIKELY return; - TRACE("Device ID %" PRIu64 " %s format%s:\n", node->mSerial, + TRACE("Device ID {} {} format{}:", node->mSerial, (id == SPA_PARAM_EnumFormat) ? "available" : "current", (id == SPA_PARAM_EnumFormat) ? "s" : ""); @@ -1122,14 +1127,14 @@ auto MetadataProxy::propertyCallback(void*, uint32_t id, const char *key, const if(!type) { - TRACE("Default %s device cleared\n", isCapture ? "capture" : "playback"); + TRACE("Default {} device cleared", isCapture ? "capture" : "playback"); if(!isCapture) DefaultSinkDevice.clear(); else DefaultSourceDevice.clear(); return 0; } if("Spa:String:JSON"sv != type) { - ERR("Unexpected %s property type: %s\n", key, type); + ERR("Unexpected {} property type: {}", key, type); return 0; } @@ -1160,8 +1165,8 @@ auto MetadataProxy::propertyCallback(void*, uint32_t id, const char *key, const auto propValue = get_json_string(&std::get<1>(it)); if(!propValue) break; - TRACE("Got default %s device \"%s\"\n", isCapture ? "capture" : "playback", - propValue->c_str()); + TRACE("Got default {} device \"{}\"", isCapture ? "capture" : "playback", + *propValue); if(!isCapture && DefaultSinkDevice != *propValue) { if(gEventHandler.mInitDone.load(std::memory_order_relaxed)) @@ -1203,28 +1208,28 @@ bool EventManager::init() mLoop = ThreadMainloop::Create("PWEventThread"); if(!mLoop) { - ERR("Failed to create PipeWire event thread loop (errno: %d)\n", errno); + ERR("Failed to create PipeWire event thread loop (errno: {})", errno); return false; } mContext = mLoop.newContext(); if(!mContext) { - ERR("Failed to create PipeWire event context (errno: %d)\n", errno); + ERR("Failed to create PipeWire event context (errno: {})", errno); return false; } mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)}; if(!mCore) { - ERR("Failed to connect PipeWire event context (errno: %d)\n", errno); + ERR("Failed to connect PipeWire event context (errno: {})", errno); return false; } mRegistry = PwRegistryPtr{pw_core_get_registry(mCore.get(), PW_VERSION_REGISTRY, 0)}; if(!mRegistry) { - ERR("Failed to get PipeWire event registry (errno: %d)\n", errno); + ERR("Failed to get PipeWire event registry (errno: {})", errno); return false; } @@ -1241,7 +1246,7 @@ bool EventManager::init() if(int res{mLoop.start()}) { - ERR("Failed to start PipeWire event thread loop (res: %d)\n", res); + ERR("Failed to start PipeWire event thread loop (res: {})", res); return false; } @@ -1279,7 +1284,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t if(!isGood) { if(!al::contains(className, "/Video"sv) && !al::starts_with(className, "Stream/"sv)) - TRACE("Ignoring node class %s\n", media_class); + TRACE("Ignoring node class {}", media_class); return; } @@ -1288,7 +1293,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t version, 0))}; if(!node) { - ERR("Failed to create node proxy object (errno: %d)\n", errno); + ERR("Failed to create node proxy object (errno: {})", errno); return; } @@ -1311,13 +1316,13 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t if("default"sv != data_class) { - TRACE("Ignoring metadata \"%s\"\n", data_class); + TRACE("Ignoring metadata \"{}\"", data_class); return; } if(mDefaultMetadata) { - ERR("Duplicate default metadata\n"); + ERR("Duplicate default metadata"); return; } @@ -1325,7 +1330,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t type, version, 0))}; if(!mdata) { - ERR("Failed to create metadata proxy object (errno: %d)\n", errno); + ERR("Failed to create metadata proxy object (errno: {})", errno); return; } @@ -1382,7 +1387,7 @@ spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e u case DevFmtFloat: info.format = SPA_AUDIO_FORMAT_F32; break; } - info.rate = device->Frequency; + info.rate = device->mSampleRate; al::span map{}; switch(device->FmtChans) @@ -1432,7 +1437,7 @@ class PipeWirePlayback final : public BackendBase { PwStreamPtr mStream; spa_hook mStreamListener{}; spa_io_rate_match *mRateMatch{}; - std::vector mChannelPtrs; + std::vector mChannelPtrs; static constexpr pw_stream_events CreateEvents() { @@ -1448,7 +1453,7 @@ class PipeWirePlayback final : public BackendBase { } public: - PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~PipeWirePlayback() final { /* Stop the mainloop so the stream can be properly destroyed. */ @@ -1492,7 +1497,7 @@ void PipeWirePlayback::outputCallback() noexcept uint length{mRateMatch ? mRateMatch->size : 0u}; #endif /* If no length is specified, use the device's update size as a fallback. */ - if(!length) UNLIKELY length = mDevice->UpdateSize; + if(!length) UNLIKELY length = mDevice->mUpdateSize; /* 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 @@ -1503,7 +1508,7 @@ void PipeWirePlayback::outputCallback() noexcept for(const auto &data : datas) { length = std::min(length, data.maxsize/uint{sizeof(float)}); - *chanptr_end = static_cast(data.data); + *chanptr_end = data.data; ++chanptr_end; data.chunk->offset = 0; @@ -1560,7 +1565,7 @@ void PipeWirePlayback::open(std::string_view name) auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name); if(match == devlist.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; targetid = match->mSerial; devname = match->mName; @@ -1568,15 +1573,15 @@ void PipeWirePlayback::open(std::string_view name) if(!mLoop) { - const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)}; - const std::string thread_name{"ALSoftP" + std::to_string(count)}; + const auto count = OpenCount.fetch_add(1u, std::memory_order_relaxed); + const auto thread_name = fmt::format("ALSoftP{}", count); mLoop = ThreadMainloop::Create(thread_name.c_str()); if(!mLoop) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to create PipeWire mainloop (errno: %d)", errno}; + "Failed to create PipeWire mainloop (errno: {})", errno}; if(int res{mLoop.start()}) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start PipeWire mainloop (res: %d)", res}; + "Failed to start PipeWire mainloop (res: {})", res}; } MainloopUniqueLock mlock{mLoop}; if(!mContext) @@ -1585,14 +1590,14 @@ void PipeWirePlayback::open(std::string_view name) mContext = mLoop.newContext(cprops); if(!mContext) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to create PipeWire event context (errno: %d)\n", errno}; + "Failed to create PipeWire event context (errno: {})\n", errno}; } if(!mCore) { mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)}; if(!mCore) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to connect PipeWire event context (errno: %d)\n", errno}; + "Failed to connect PipeWire event context (errno: {})\n", errno}; } mlock.unlock(); @@ -1600,9 +1605,9 @@ void PipeWirePlayback::open(std::string_view name) mTargetId = targetid; if(!devname.empty()) - mDevice->DeviceName = std::move(devname); + mDeviceName = std::move(devname); else - mDevice->DeviceName = "PipeWire Output"sv; + mDeviceName = "PipeWire Output"sv; } bool PipeWirePlayback::reset() @@ -1634,13 +1639,13 @@ bool PipeWirePlayback::reset() if(!mDevice->Flags.test(FrequencyRequest) && match->mSampleRate > 0) { /* Scale the update size if the sample rate changes. */ - const double scale{static_cast(match->mSampleRate) / mDevice->Frequency}; - const double updatesize{std::round(mDevice->UpdateSize * scale)}; - const double buffersize{std::round(mDevice->BufferSize * scale)}; + const double scale{static_cast(match->mSampleRate) / mDevice->mSampleRate}; + const double updatesize{std::round(mDevice->mUpdateSize * scale)}; + const double buffersize{std::round(mDevice->mBufferSize * scale)}; - mDevice->Frequency = match->mSampleRate; - mDevice->UpdateSize = static_cast(std::clamp(updatesize, 64.0, 8192.0)); - mDevice->BufferSize = static_cast(std::max(buffersize, 128.0)); + mDevice->mSampleRate = match->mSampleRate; + mDevice->mUpdateSize = static_cast(std::clamp(updatesize, 64.0, 8192.0)); + mDevice->mBufferSize = static_cast(std::max(buffersize, 128.0)); } if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig) mDevice->FmtChans = match->mChannels; @@ -1652,12 +1657,10 @@ bool PipeWirePlayback::reset() /* Force planar 32-bit float output for playback. This is what PipeWire * handles internally, and it's easier for us too. */ - spa_audio_info_raw info{make_spa_info(mDevice, is51rear, ForceF32Planar)}; + auto info = spa_audio_info_raw{make_spa_info(mDevice, is51rear, ForceF32Planar)}; - static constexpr uint32_t pod_buffer_size{1024}; - PodDynamicBuilder b(pod_buffer_size); - - const spa_pod *params{spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info)}; + auto b = PodDynamicBuilder{}; + auto params = as_const_ptr(spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info)); if(!params) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set PipeWire audio format parameters"}; @@ -1666,7 +1669,7 @@ bool PipeWirePlayback::reset() * be useful? */ auto&& binary = GetProcBinary(); - const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; + const char *appname{!binary.fname.empty() ? 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", @@ -1676,11 +1679,11 @@ bool PipeWirePlayback::reset() nullptr)}; if(!props) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to create PipeWire stream properties (errno: %d)", errno}; + "Failed to create PipeWire stream properties (errno: {})", errno}; - 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); + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->mUpdateSize, + mDevice->mSampleRate); + pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->mSampleRate); #ifdef PW_KEY_TARGET_OBJECT pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId); #else @@ -1692,17 +1695,17 @@ bool PipeWirePlayback::reset() mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Playback Stream", props)}; if(!mStream) throw al::backend_exception{al::backend_error::NoDevice, - "Failed to create PipeWire stream (errno: %d)", errno}; + "Failed to create PipeWire stream (errno: {})", errno}; static constexpr pw_stream_events streamEvents{CreateEvents()}; pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this); pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS}; - if(GetConfigValueBool(mDevice->DeviceName, "pipewire", "rt-mix", false)) + if(GetConfigValueBool(mDevice->mDeviceName, "pipewire", "rt-mix", false)) flags |= PW_STREAM_FLAG_RT_PROCESS; if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, PwIdAny, flags, ¶ms, 1)}) throw al::backend_exception{al::backend_error::DeviceError, - "Error connecting PipeWire stream (res: %d)", res}; + "Error connecting PipeWire stream (res: {})", res}; /* Wait for the stream to become paused (ready to start streaming). */ plock.wait([stream=mStream.get()]() @@ -1711,12 +1714,12 @@ bool PipeWirePlayback::reset() pw_stream_state state{pw_stream_get_state(stream, &error)}; if(state == PW_STREAM_STATE_ERROR) throw al::backend_exception{al::backend_error::DeviceError, - "Error connecting PipeWire stream: \"%s\"", error}; + "Error connecting PipeWire stream: \"{}\"", error}; return state == PW_STREAM_STATE_PAUSED; }); - /* TODO: Update mDevice->UpdateSize with the stream's quantum, and - * mDevice->BufferSize with the total known buffering delay from the head + /* TODO: Update mDevice->mUpdateSize with the stream's quantum, and + * mDevice->mBufferSize 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. @@ -1735,7 +1738,7 @@ void PipeWirePlayback::start() MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), true)}) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start PipeWire stream (res: %d)", res}; + "Failed to start PipeWire stream (res: {})", res}; /* Wait for the stream to start playing (would be nice to not, but we need * the actual update size which is only available after starting). @@ -1746,7 +1749,7 @@ void PipeWirePlayback::start() pw_stream_state state{pw_stream_get_state(stream, &error)}; if(state == PW_STREAM_STATE_ERROR) throw al::backend_exception{al::backend_error::DeviceError, - "PipeWire stream error: %s", error ? error : "(unknown)"}; + "PipeWire stream error: {}", error ? error : "(unknown)"}; return state == PW_STREAM_STATE_STREAMING; }); @@ -1761,7 +1764,7 @@ void PipeWirePlayback::start() 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); + ERR("Failed to get PipeWire stream time (res: {})", res); break; } @@ -1776,11 +1779,11 @@ void PipeWirePlayback::start() 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 * + const uint64_t delay{static_cast(ptime.delay) * mDevice->mSampleRate * ptime.rate.num / ptime.rate.denom}; - mDevice->UpdateSize = updatesize; - mDevice->BufferSize = static_cast(ptime.buffered + delay + + mDevice->mUpdateSize = updatesize; + mDevice->mBufferSize = static_cast(ptime.buffered + delay + uint64_t{totalbuffers}*updatesize); break; } @@ -1791,17 +1794,17 @@ void PipeWirePlayback::start() if(ptime.rate.denom > 0 && updatesize > 0) { /* Ensure the delay is in sample frames. */ - const uint64_t delay{static_cast(ptime.delay) * mDevice->Frequency * + const uint64_t delay{static_cast(ptime.delay) * mDevice->mSampleRate * ptime.rate.num / ptime.rate.denom}; - mDevice->UpdateSize = updatesize; - mDevice->BufferSize = static_cast(delay + updatesize); + mDevice->mUpdateSize = updatesize; + mDevice->mBufferSize = static_cast(delay + updatesize); break; } #endif if(!--wait_count) { - ERR("Timeout getting PipeWire stream buffering info\n"); + ERR("Timeout getting PipeWire stream buffering info"); break; } @@ -1815,7 +1818,7 @@ void PipeWirePlayback::stop() { MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), false)}) - ERR("Failed to stop PipeWire stream (res: %d)\n", res); + ERR("Failed to stop PipeWire stream (res: {})", res); /* Wait for the stream to stop playing. */ plock.wait([stream=mStream.get()]() @@ -1836,7 +1839,7 @@ ClockLatency PipeWirePlayback::getClockLatency() { MainloopLockGuard looplock{mLoop}; if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))}) - ERR("Failed to get PipeWire stream time (res: %d)\n", res); + ERR("Failed to get PipeWire stream time (res: {})", res); } /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the @@ -1864,7 +1867,7 @@ ClockLatency PipeWirePlayback::getClockLatency() */ ptime.now = monoclock.count(); curtic = mixtime; - delay = nanoseconds{seconds{mDevice->BufferSize}} / mDevice->Frequency; + delay = nanoseconds{seconds{mDevice->mBufferSize}} / mDevice->mSampleRate; } else { @@ -1924,7 +1927,7 @@ class PipeWireCapture final : public BackendBase { PwStreamPtr mStream; spa_hook mStreamListener{}; - RingBufferPtr mRing{}; + RingBufferPtr mRing; static constexpr pw_stream_events CreateEvents() { @@ -1938,7 +1941,7 @@ class PipeWireCapture final : public BackendBase { } public: - PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~PipeWireCapture() final { if(mLoop) mLoop.stop(); } }; @@ -2025,7 +2028,7 @@ void PipeWireCapture::open(std::string_view name) } if(match == devlist.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; targetid = match->mSerial; if(match->mType != NodeType::Sink) devname = match->mName; @@ -2034,15 +2037,15 @@ void PipeWireCapture::open(std::string_view name) if(!mLoop) { - const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)}; - const std::string thread_name{"ALSoftC" + std::to_string(count)}; + const auto count = OpenCount.fetch_add(1u, std::memory_order_relaxed); + const auto thread_name = fmt::format("ALSoftC{}", count); mLoop = ThreadMainloop::Create(thread_name.c_str()); if(!mLoop) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to create PipeWire mainloop (errno: %d)", errno}; + "Failed to create PipeWire mainloop (errno: {})", errno}; if(int res{mLoop.start()}) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start PipeWire mainloop (res: %d)", res}; + "Failed to start PipeWire mainloop (res: {})", res}; } MainloopUniqueLock mlock{mLoop}; if(!mContext) @@ -2051,14 +2054,14 @@ void PipeWireCapture::open(std::string_view name) mContext = mLoop.newContext(cprops); if(!mContext) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to create PipeWire event context (errno: %d)\n", errno}; + "Failed to create PipeWire event context (errno: {})\n", errno}; } if(!mCore) { mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)}; if(!mCore) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to connect PipeWire event context (errno: %d)\n", errno}; + "Failed to connect PipeWire event context (errno: {})\n", errno}; } mlock.unlock(); @@ -2066,9 +2069,9 @@ void PipeWireCapture::open(std::string_view name) mTargetId = targetid; if(!devname.empty()) - mDevice->DeviceName = std::move(devname); + mDeviceName = std::move(devname); else - mDevice->DeviceName = "PipeWire Input"sv; + mDeviceName = "PipeWire Input"sv; bool is51rear{false}; @@ -2083,19 +2086,16 @@ void PipeWireCapture::open(std::string_view name) if(match != devlist.cend()) is51rear = match->mIs51Rear; } - spa_audio_info_raw info{make_spa_info(mDevice, is51rear, UseDevType)}; + auto info = spa_audio_info_raw{make_spa_info(mDevice, is51rear, UseDevType)}; - static constexpr uint32_t pod_buffer_size{1024}; - PodDynamicBuilder b(pod_buffer_size); - - std::array params{static_cast(spa_format_audio_raw_build(b.get(), - SPA_PARAM_EnumFormat, &info))}; - if(!params[0]) + auto b = PodDynamicBuilder{}; + auto params = as_const_ptr(spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info)); + if(!params) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set PipeWire audio format parameters"}; auto&& binary = GetProcBinary(); - const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; + const char *appname{!binary.fname.empty() ? binary.fname.c_str() : "OpenAL Soft"}; pw_properties *props{pw_properties_new( PW_KEY_NODE_NAME, appname, PW_KEY_NODE_DESCRIPTION, appname, @@ -2106,15 +2106,15 @@ void PipeWireCapture::open(std::string_view name) nullptr)}; if(!props) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to create PipeWire stream properties (errno: %d)", errno}; + "Failed to create PipeWire stream properties (errno: {})", errno}; /* 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. */ - 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); + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->mSampleRate+25) / 50, + mDevice->mSampleRate); + pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->mSampleRate); #ifdef PW_KEY_TARGET_OBJECT pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId); #else @@ -2125,15 +2125,15 @@ void PipeWireCapture::open(std::string_view name) mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Capture Stream", props)}; if(!mStream) throw al::backend_exception{al::backend_error::NoDevice, - "Failed to create PipeWire stream (errno: %d)", errno}; + "Failed to create PipeWire stream (errno: {})", errno}; 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_INPUT, PwIdAny, Flags, params.data(), 1)}) + if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, ¶ms, 1)}) throw al::backend_exception{al::backend_error::DeviceError, - "Error connecting PipeWire stream (res: %d)", res}; + "Error connecting PipeWire stream (res: {})", res}; /* Wait for the stream to become paused (ready to start streaming). */ plock.wait([stream=mStream.get()]() @@ -2142,7 +2142,7 @@ void PipeWireCapture::open(std::string_view name) pw_stream_state state{pw_stream_get_state(stream, &error)}; if(state == PW_STREAM_STATE_ERROR) throw al::backend_exception{al::backend_error::DeviceError, - "Error connecting PipeWire stream: \"%s\"", error}; + "Error connecting PipeWire stream: \"{}\"", error}; return state == PW_STREAM_STATE_PAUSED; }); plock.unlock(); @@ -2150,7 +2150,7 @@ void PipeWireCapture::open(std::string_view name) setDefaultWFXChannelOrder(); /* Ensure at least a 100ms capture buffer. */ - mRing = RingBuffer::Create(std::max(mDevice->Frequency/10u, mDevice->BufferSize), + mRing = RingBuffer::Create(std::max(mDevice->mSampleRate/10u, mDevice->mBufferSize), mDevice->frameSizeFromFmt(), false); } @@ -2160,7 +2160,7 @@ void PipeWireCapture::start() MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), true)}) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start PipeWire stream (res: %d)", res}; + "Failed to start PipeWire stream (res: {})", res}; plock.wait([stream=mStream.get()]() { @@ -2168,7 +2168,7 @@ void PipeWireCapture::start() pw_stream_state state{pw_stream_get_state(stream, &error)}; if(state == PW_STREAM_STATE_ERROR) throw al::backend_exception{al::backend_error::DeviceError, - "PipeWire stream error: %s", error ? error : "(unknown)"}; + "PipeWire stream error: {}", error ? error : "(unknown)"}; return state == PW_STREAM_STATE_STREAMING; }); } @@ -2177,7 +2177,7 @@ void PipeWireCapture::stop() { MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), false)}) - ERR("Failed to stop PipeWire stream (res: %d)\n", res); + ERR("Failed to stop PipeWire stream (res: {})", res); plock.wait([stream=mStream.get()]() { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; }); @@ -2200,11 +2200,11 @@ bool PipeWireBackendFactory::init() const char *version{pw_get_library_version()}; if(!check_version(version)) { - WARN("PipeWire version \"%s\" too old (%s or newer required)\n", version, + WARN("PipeWire version \"{}\" too old ({} or newer required)", version, pw_get_headers_version()); return false; } - TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version, pw_get_headers_version()); + TRACE("Found PipeWire version \"{}\" ({} or newer)", version, pw_get_headers_version()); pw_init(nullptr, nullptr); if(!gEventHandler.init()) @@ -2217,7 +2217,7 @@ bool PipeWireBackendFactory::init() /* TODO: Temporary warning, until PipeWire gets a proper way to report * audio support. */ - WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong.\n"); + WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong."); return false; } return true; diff --git a/Engine/lib/openal-soft/alc/backends/portaudio.cpp b/Engine/lib/openal-soft/alc/backends/portaudio.cpp index eb1c289f3..75497c3f3 100644 --- a/Engine/lib/openal-soft/alc/backends/portaudio.cpp +++ b/Engine/lib/openal-soft/alc/backends/portaudio.cpp @@ -20,27 +20,25 @@ #include "config.h" -#include "portaudio.h" +#include "portaudio.hpp" +#include #include #include #include -#include "albit.h" #include "alc/alconfig.h" -#include "alnumeric.h" -#include "alstring.h" #include "core/device.h" #include "core/logging.h" #include "dynload.h" #include "ringbuffer.h" -#include /* NOLINT(*-duplicate-include) Not the same header. */ +#include namespace { -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD void *pa_handle; #define MAKE_FUNC(x) decltype(x) * p##x MAKE_FUNC(Pa_Initialize); @@ -75,8 +73,8 @@ MAKE_FUNC(Pa_GetStreamInfo); struct DeviceEntry { std::string mName; - bool mIsPlayback{}; - bool mIsCapture{}; + uint mPlaybackChannels{}; + uint mCaptureChannels{}; }; std::vector DeviceNames; @@ -85,7 +83,7 @@ void EnumerateDevices() const auto devcount = Pa_GetDeviceCount(); if(devcount < 0) { - ERR("Error getting device count: %s\n", Pa_GetErrorText(devcount)); + ERR("Error getting device count: {}", Pa_GetErrorText(devcount)); return; } @@ -95,44 +93,42 @@ void EnumerateDevices() { if(auto info = Pa_GetDeviceInfo(idx); info && info->name) { -#ifdef _WIN32 - entry.mName = "OpenAL Soft on "+std::string{info->name}; -#else entry.mName = info->name; -#endif - entry.mIsPlayback = (info->maxOutputChannels > 0); - entry.mIsCapture = (info->maxInputChannels > 0); - TRACE("Device %d \"%s\": %d playback, %d capture channels\n", idx, entry.mName.c_str(), + entry.mPlaybackChannels = static_cast(std::max(info->maxOutputChannels, 0)); + entry.mCaptureChannels = static_cast(std::max(info->maxInputChannels, 0)); + TRACE("Device {} \"{}\": {} playback, {} capture channels", idx, entry.mName, info->maxOutputChannels, info->maxInputChannels); } ++idx; } } +struct StreamParamsExt : public PaStreamParameters { uint updateSize; }; + struct PortPlayback final : public BackendBase { - PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~PortPlayback() override; int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept; + void createStream(PaDeviceIndex deviceid); + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; PaStream *mStream{nullptr}; - PaStreamParameters mParams{}; - DevFmtChannels mChannelConfig{}; - uint mAmbiOrder{}; - uint mUpdateSize{0u}; + StreamParamsExt mParams{}; + PaDeviceIndex mDeviceIdx{-1}; }; PortPlayback::~PortPlayback() { PaError err{mStream ? Pa_CloseStream(mStream) : paNoError}; if(err != paNoError) - ERR("Error closing stream: %s\n", Pa_GetErrorText(err)); + ERR("Error closing stream: {}", Pa_GetErrorText(err)); mStream = nullptr; } @@ -146,12 +142,62 @@ int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long f } +void PortPlayback::createStream(PaDeviceIndex deviceid) +{ + auto &devinfo = DeviceNames.at(static_cast(deviceid)); + + auto params = StreamParamsExt{}; + params.device = deviceid; + params.suggestedLatency = mDevice->mBufferSize / static_cast(mDevice->mSampleRate); + params.hostApiSpecificStreamInfo = nullptr; + params.channelCount = static_cast(std::min(devinfo.mPlaybackChannels, + mDevice->channelsFromFmt())); + switch(mDevice->FmtType) + { + case DevFmtByte: params.sampleFormat = paInt8; break; + case DevFmtUByte: params.sampleFormat = paUInt8; break; + case DevFmtUShort: [[fallthrough]]; + case DevFmtShort: params.sampleFormat = paInt16; break; + case DevFmtUInt: [[fallthrough]]; + case DevFmtInt: params.sampleFormat = paInt32; break; + case DevFmtFloat: params.sampleFormat = paFloat32; break; + } + params.updateSize = mDevice->mUpdateSize; + + auto srate = uint{mDevice->mSampleRate}; + + static constexpr auto writeCallback = [](const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, + const PaStreamCallbackFlags statusFlags, void *userData) noexcept + { + return static_cast(userData)->writeCallback(inputBuffer, outputBuffer, + framesPerBuffer, timeInfo, statusFlags); + }; + while(PaError err{Pa_OpenStream(&mStream, nullptr, ¶ms, srate, params.updateSize, paNoFlag, + writeCallback, this)}) + { + if(params.updateSize != DefaultUpdateSize) + params.updateSize = DefaultUpdateSize; + else if(srate != 48000u) + srate = (srate != 44100u) ? 44100u : 48000u; + else if(params.sampleFormat != paInt16) + params.sampleFormat = paInt16; + else if(params.channelCount != 2) + params.channelCount = 2; + else + throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: {}", + Pa_GetErrorText(err)}; + } + + mParams = params; +} + void PortPlayback::open(std::string_view name) { if(DeviceNames.empty()) EnumerateDevices(); - int deviceid{-1}; + PaDeviceIndex deviceid{-1}; if(name.empty()) { if(auto devidopt = ConfigValueInt({}, "port", "device")) @@ -163,101 +209,65 @@ void PortPlayback::open(std::string_view name) else { auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(), - [name](const DeviceEntry &entry) { return entry.mIsPlayback && name == entry.mName; }); + [name](const DeviceEntry &entry) + { return entry.mPlaybackChannels > 0 && name == entry.mName; }); if(iter == DeviceNames.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; deviceid = static_cast(std::distance(DeviceNames.cbegin(), iter)); } - PaStreamParameters params{}; - params.device = deviceid; - params.suggestedLatency = mDevice->BufferSize / static_cast(mDevice->Frequency); - params.hostApiSpecificStreamInfo = nullptr; + createStream(deviceid); + mDeviceIdx = deviceid; - mChannelConfig = mDevice->FmtChans; - mAmbiOrder = mDevice->mAmbiOrder; - params.channelCount = static_cast(mDevice->channelsFromFmt()); - - switch(mDevice->FmtType) - { - case DevFmtByte: - params.sampleFormat = paInt8; - break; - case DevFmtUByte: - params.sampleFormat = paUInt8; - break; - case DevFmtUShort: - /* fall-through */ - case DevFmtShort: - params.sampleFormat = paInt16; - break; - case DevFmtUInt: - /* fall-through */ - case DevFmtInt: - params.sampleFormat = paInt32; - break; - case DevFmtFloat: - params.sampleFormat = paFloat32; - break; - } - - static constexpr auto writeCallback = [](const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData) noexcept - { - return static_cast(userData)->writeCallback(inputBuffer, outputBuffer, - framesPerBuffer, timeInfo, statusFlags); - }; - PaStream *stream{}; - while(PaError err{Pa_OpenStream(&stream, nullptr, ¶ms, mDevice->Frequency, - mDevice->UpdateSize, paNoFlag, writeCallback, this)}) - { - if(params.sampleFormat != paFloat32) - throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", - Pa_GetErrorText(err)}; - params.sampleFormat = paInt16; - } - - Pa_CloseStream(mStream); - mStream = stream; - mParams = params; - mUpdateSize = mDevice->UpdateSize; - - mDevice->DeviceName = name; + mDeviceName = name; } bool PortPlayback::reset() { + if(mStream) + { + auto err = Pa_CloseStream(mStream); + if(err != paNoError) + ERR("Error closing stream: {}", Pa_GetErrorText(err)); + mStream = nullptr; + } + + createStream(mDeviceIdx); + + switch(mParams.sampleFormat) + { + case paFloat32: mDevice->FmtType = DevFmtFloat; break; + case paInt32: mDevice->FmtType = DevFmtInt; break; + case paInt16: mDevice->FmtType = DevFmtShort; break; + case paInt8: mDevice->FmtType = DevFmtByte; break; + case paUInt8: mDevice->FmtType = DevFmtUByte; break; + default: + ERR("Unexpected PortAudio sample format: {}", mParams.sampleFormat); + throw al::backend_exception{al::backend_error::NoDevice, "Invalid sample format: {}", + mParams.sampleFormat}; + } + + if(mParams.channelCount != static_cast(mDevice->channelsFromFmt())) + { + if(mParams.channelCount >= 2) + mDevice->FmtChans = DevFmtStereo; + else if(mParams.channelCount == 1) + mDevice->FmtChans = DevFmtMono; + mDevice->mAmbiOrder = 0; + } + const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)}; - mDevice->Frequency = static_cast(streamInfo->sampleRate); - mDevice->FmtChans = mChannelConfig; - mDevice->mAmbiOrder = mAmbiOrder; - mDevice->UpdateSize = mUpdateSize; - mDevice->BufferSize = mUpdateSize * 2; + mDevice->mSampleRate = static_cast(std::lround(streamInfo->sampleRate)); + mDevice->mUpdateSize = mParams.updateSize; + mDevice->mBufferSize = mDevice->mUpdateSize * 2; if(streamInfo->outputLatency > 0.0f) { const double sampleLatency{streamInfo->outputLatency * streamInfo->sampleRate}; - TRACE("Reported stream latency: %f sec (%f samples)\n", streamInfo->outputLatency, + TRACE("Reported stream latency: {:f} sec ({:f} samples)", streamInfo->outputLatency, sampleLatency); - mDevice->BufferSize = static_cast(std::clamp(sampleLatency, - double(mDevice->BufferSize), double{std::numeric_limits::max()})); - } - - if(mParams.sampleFormat == paInt8) - mDevice->FmtType = DevFmtByte; - else if(mParams.sampleFormat == paUInt8) - mDevice->FmtType = DevFmtUByte; - else if(mParams.sampleFormat == paInt16) - mDevice->FmtType = DevFmtShort; - else if(mParams.sampleFormat == paInt32) - mDevice->FmtType = DevFmtInt; - else if(mParams.sampleFormat == paFloat32) - mDevice->FmtType = DevFmtFloat; - else - { - ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat); - return false; + mDevice->mBufferSize = static_cast(std::clamp(sampleLatency, + double(mDevice->mBufferSize), double{std::numeric_limits::max()})); } setDefaultChannelOrder(); @@ -268,19 +278,19 @@ bool PortPlayback::reset() void PortPlayback::start() { if(const PaError err{Pa_StartStream(mStream)}; err != paNoError) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s", + throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: {}", Pa_GetErrorText(err)}; } void PortPlayback::stop() { if(PaError err{Pa_StopStream(mStream)}; err != paNoError) - ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); + ERR("Error stopping stream: {}", Pa_GetErrorText(err)); } struct PortCapture final : public BackendBase { - PortCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit PortCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~PortCapture() override; int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, @@ -302,7 +312,7 @@ PortCapture::~PortCapture() { PaError err{mStream ? Pa_CloseStream(mStream) : paNoError}; if(err != paNoError) - ERR("Error closing stream: %s\n", Pa_GetErrorText(err)); + ERR("Error closing stream: {}", Pa_GetErrorText(err)); mStream = nullptr; } @@ -332,14 +342,15 @@ void PortCapture::open(std::string_view name) else { auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(), - [name](const DeviceEntry &entry) { return entry.mIsCapture && name == entry.mName; }); + [name](const DeviceEntry &entry) + { return entry.mCaptureChannels > 0 && name == entry.mName; }); if(iter == DeviceNames.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; deviceid = static_cast(std::distance(DeviceNames.cbegin(), iter)); } - const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; + const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)}; const uint frame_size{mDevice->frameSizeFromFmt()}; mRing = RingBuffer::Create(samples, frame_size, false); @@ -367,7 +378,7 @@ void PortCapture::open(std::string_view name) break; case DevFmtUInt: case DevFmtUShort: - throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported", + throw al::backend_exception{al::backend_error::DeviceError, "{} samples not supported", DevFmtTypeString(mDevice->FmtType)}; } mParams.channelCount = static_cast(mDevice->channelsFromFmt()); @@ -379,13 +390,13 @@ void PortCapture::open(std::string_view name) return static_cast(userData)->readCallback(inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags); }; - PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency, + PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->mSampleRate, paFramesPerBufferUnspecified, paNoFlag, readCallback, this)}; if(err != paNoError) - throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", + throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: {}", Pa_GetErrorText(err)}; - mDevice->DeviceName = name; + mDeviceName = name; } @@ -393,13 +404,13 @@ void PortCapture::start() { if(const PaError err{Pa_StartStream(mStream)}; err != paNoError) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start recording: %s", Pa_GetErrorText(err)}; + "Failed to start recording: {}", Pa_GetErrorText(err)}; } void PortCapture::stop() { if(PaError err{Pa_StopStream(mStream)}; err != paNoError) - ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); + ERR("Error stopping stream: {}", Pa_GetErrorText(err)); } @@ -414,7 +425,7 @@ void PortCapture::captureSamples(std::byte *buffer, uint samples) bool PortBackendFactory::init() { -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD if(!pa_handle) { #ifdef _WIN32 @@ -457,7 +468,7 @@ bool PortBackendFactory::init() const PaError err{Pa_Initialize()}; if(err != paNoError) { - ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); + ERR("Pa_Initialize() returned an error: {}", Pa_GetErrorText(err)); CloseLib(pa_handle); pa_handle = nullptr; return false; @@ -467,7 +478,7 @@ bool PortBackendFactory::init() const PaError err{Pa_Initialize()}; if(err != paNoError) { - ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); + ERR("Pa_Initialize() returned an error: {}", Pa_GetErrorText(err)); return false; } #endif @@ -493,7 +504,7 @@ auto PortBackendFactory::enumerate(BackendType type) -> std::vector for(size_t i{0};i < DeviceNames.size();++i) { - if(DeviceNames[i].mIsPlayback) + if(DeviceNames[i].mPlaybackChannels > 0) { if(defaultid >= 0 && static_cast(defaultid) == i) devices.emplace(devices.cbegin(), DeviceNames[i].mName); @@ -511,7 +522,7 @@ auto PortBackendFactory::enumerate(BackendType type) -> std::vector for(size_t i{0};i < DeviceNames.size();++i) { - if(DeviceNames[i].mIsCapture) + if(DeviceNames[i].mCaptureChannels > 0) { if(defaultid >= 0 && static_cast(defaultid) == i) devices.emplace(devices.cbegin(), DeviceNames[i].mName); diff --git a/Engine/lib/openal-soft/alc/backends/portaudio.h b/Engine/lib/openal-soft/alc/backends/portaudio.hpp similarity index 79% rename from Engine/lib/openal-soft/alc/backends/portaudio.h rename to Engine/lib/openal-soft/alc/backends/portaudio.hpp index f5abe8e4c..69c78ea9d 100644 --- a/Engine/lib/openal-soft/alc/backends/portaudio.h +++ b/Engine/lib/openal-soft/alc/backends/portaudio.hpp @@ -1,5 +1,5 @@ -#ifndef BACKENDS_PORTAUDIO_H -#define BACKENDS_PORTAUDIO_H +#ifndef BACKENDS_PORTAUDIO_HPP +#define BACKENDS_PORTAUDIO_HPP #include "base.h" @@ -16,4 +16,4 @@ public: static auto getFactory() -> BackendFactory&; }; -#endif /* BACKENDS_PORTAUDIO_H */ +#endif /* BACKENDS_PORTAUDIO_HPP */ diff --git a/Engine/lib/openal-soft/alc/backends/pulseaudio.cpp b/Engine/lib/openal-soft/alc/backends/pulseaudio.cpp index 91fa4b6c1..aac014157 100644 --- a/Engine/lib/openal-soft/alc/backends/pulseaudio.cpp +++ b/Engine/lib/openal-soft/alc/backends/pulseaudio.cpp @@ -32,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -43,13 +42,14 @@ #include #include "alc/alconfig.h" +#include "alnumeric.h" #include "alspan.h" -#include "alstring.h" #include "base.h" #include "core/devformat.h" #include "core/device.h" #include "core/logging.h" #include "dynload.h" +#include "fmt/core.h" #include "opthelpers.h" #include "strutils.h" @@ -58,9 +58,10 @@ namespace { +using namespace std::string_view_literals; using uint = unsigned int; -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD #define PULSE_FUNCS(MAGIC) \ MAGIC(pa_context_new); \ MAGIC(pa_context_unref); \ @@ -72,8 +73,10 @@ using uint = unsigned int; MAGIC(pa_context_errno); \ MAGIC(pa_context_connect); \ MAGIC(pa_context_get_server_info); \ + MAGIC(pa_context_get_sink_info_by_index); \ MAGIC(pa_context_get_sink_info_by_name); \ MAGIC(pa_context_get_sink_info_list); \ + MAGIC(pa_context_get_source_info_by_index); \ MAGIC(pa_context_get_source_info_by_name); \ MAGIC(pa_context_get_source_info_list); \ MAGIC(pa_stream_new); \ @@ -145,8 +148,10 @@ PULSE_FUNCS(MAKE_FUNC) #define pa_context_errno ppa_context_errno #define pa_context_connect ppa_context_connect #define pa_context_get_server_info ppa_context_get_server_info +#define pa_context_get_sink_info_by_index ppa_context_get_sink_info_by_index #define pa_context_get_sink_info_by_name ppa_context_get_sink_info_by_name #define pa_context_get_sink_info_list ppa_context_get_sink_info_list +#define pa_context_get_source_info_by_index ppa_context_get_source_info_by_index #define pa_context_get_source_info_by_name ppa_context_get_source_info_by_name #define pa_context_get_source_info_list ppa_context_get_source_info_list #define pa_stream_new ppa_stream_new @@ -252,7 +257,7 @@ constexpr pa_channel_map MonoChanMap{ }; -/* *grumble* Don't use enums for bitflags. */ +/* NOLINTBEGIN(*EnumCastOutOfRange) *grumble* Don't use enums for bitflags. */ 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) @@ -278,11 +283,13 @@ constexpr pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_fla constexpr pa_subscription_mask_t operator|(pa_subscription_mask_t lhs, pa_subscription_mask_t rhs) { return pa_subscription_mask_t(lhs | al::to_underlying(rhs)); } +/* NOLINTEND(*EnumCastOutOfRange) */ struct DevMap { std::string name; std::string device_name; + uint32_t index{}; }; bool checkName(const al::span list, const std::string &name) @@ -294,6 +301,9 @@ bool checkName(const al::span list, const std::string &name) std::vector PlaybackDevices; std::vector CaptureDevices; +std::string DefaultPlaybackDevName; +std::string DefaultCaptureDevName; + /* Global flags and properties */ pa_context_flags_t pulse_ctx_flags; @@ -344,6 +354,32 @@ public: void close(pa_stream *stream=nullptr); + void updateDefaultDevice(pa_context*, const pa_server_info *info) const + { + auto default_sink = info->default_sink_name ? std::string_view{info->default_sink_name} + : std::string_view{}; + auto default_src = info->default_source_name ? std::string_view{info->default_source_name} + : std::string_view{}; + + if(default_sink != DefaultPlaybackDevName) + { + TRACE("Default playback device: {}", default_sink); + DefaultPlaybackDevName = default_sink; + + const auto msg = fmt::format("Default playback device changed: {}", default_sink); + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg); + } + if(default_src != DefaultCaptureDevName) + { + TRACE("Default capture device: {}", default_src); + DefaultCaptureDevName = default_src; + + const auto msg = fmt::format("Default capture device changed: {}", default_src); + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg); + } + signal(); + } + void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) const noexcept { if(eol) @@ -361,18 +397,18 @@ public: /* Make sure the display name (description) is unique. Append a number * counter as needed. */ - int count{1}; - std::string newname{info->description}; + auto count = 1; + auto newname = std::string{info->description}; while(checkName(PlaybackDevices, newname)) - { - newname = info->description; - newname += " #"; - newname += std::to_string(++count); - } - PlaybackDevices.emplace_back(DevMap{std::move(newname), info->name}); - DevMap &newentry = PlaybackDevices.back(); + newname = fmt::format("{} #{}", info->description, ++count); - TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); + const auto &newentry = PlaybackDevices.emplace_back(DevMap{std::move(newname), + info->name, info->index}); + TRACE("Got device \"{}\", \"{}\" ({})", newentry.name, newentry.device_name, + newentry.index); + + const auto msg = fmt::format("Device added: {}", newentry.device_name); + alc::Event(alc::EventType::DeviceAdded, alc::DeviceType::Playback, msg); } void deviceSourceCallback(pa_context*, const pa_source_info *info, int eol) const noexcept @@ -392,22 +428,77 @@ public: /* Make sure the display name (description) is unique. Append a number * counter as needed. */ - int count{1}; - std::string newname{info->description}; + auto count = 1; + auto newname = std::string{info->description}; while(checkName(CaptureDevices, newname)) - { - newname = info->description; - newname += " #"; - newname += std::to_string(++count); - } - CaptureDevices.emplace_back(DevMap{std::move(newname), info->name}); - DevMap &newentry = CaptureDevices.back(); + newname = fmt::format("{} #{}", info->description, ++count); - TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); + const auto &newentry = CaptureDevices.emplace_back(DevMap{std::move(newname), info->name, + info->index}); + TRACE("Got device \"{}\", \"{}\" ({})", newentry.name, newentry.device_name, + newentry.index); + + const auto msg = fmt::format("Device added: {}", newentry.device_name); + alc::Event(alc::EventType::DeviceAdded, alc::DeviceType::Capture, msg); } - void probePlaybackDevices(); - void probeCaptureDevices(); + void eventCallback(pa_context *context, pa_subscription_event_type_t t, uint32_t idx) noexcept + { + const auto eventFacility = (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK); + const auto eventType = (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK); + + if(eventFacility == PA_SUBSCRIPTION_EVENT_SERVER + && eventType == PA_SUBSCRIPTION_EVENT_CHANGE) + { + static constexpr auto server_cb = [](pa_context *ctx, const pa_server_info *info, + void *pdata) noexcept + { return static_cast(pdata)->updateDefaultDevice(ctx, info); }; + auto *op = pa_context_get_server_info(context, server_cb, this); + if(op) pa_operation_unref(op); + } + + if(eventFacility != PA_SUBSCRIPTION_EVENT_SINK + && eventFacility != PA_SUBSCRIPTION_EVENT_SOURCE) + return; + + const auto devtype = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK) + ? alc::DeviceType::Playback : alc::DeviceType::Capture; + + if(eventType == PA_SUBSCRIPTION_EVENT_NEW) + { + if(eventFacility == PA_SUBSCRIPTION_EVENT_SINK) + { + static constexpr auto devcallback = [](pa_context *ctx, const pa_sink_info *info, + int eol, void *pdata) noexcept + { return static_cast(pdata)->deviceSinkCallback(ctx, info, eol); }; + auto *op = pa_context_get_sink_info_by_index(context, idx, devcallback, this); + if(op) pa_operation_unref(op); + } + else + { + static constexpr auto devcallback = [](pa_context *ctx, const pa_source_info *info, + int eol, void *pdata) noexcept + { return static_cast(pdata)->deviceSourceCallback(ctx,info,eol); }; + auto *op = pa_context_get_source_info_by_index(context, idx, devcallback, this); + if(op) pa_operation_unref(op); + } + } + else if(eventType == PA_SUBSCRIPTION_EVENT_REMOVE) + { + auto find_index = [idx](const DevMap &entry) noexcept { return entry.index == idx; }; + + auto &devlist = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK) + ? PlaybackDevices : CaptureDevices; + auto iter = std::find_if(devlist.cbegin(), devlist.cend(), find_index); + if(iter != devlist.cend()) + { + devlist.erase(iter); + + const auto msg = fmt::format("Device removed: {}", idx); + alc::Event(alc::EventType::DeviceRemoved, devtype, msg); + } + } + } friend struct MainloopUniqueLock; }; @@ -434,36 +525,38 @@ struct MainloopUniqueLock : public std::unique_lock { void setEventHandler() { - pa_operation *op{pa_context_subscribe(mutex()->mContext, - PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, - [](pa_context*, int, void *pdata) noexcept - { static_cast(pdata)->signal(); }, - mutex())}; + auto *context = mutex()->mContext; + + /* Watch for device added/removed and server changed events. */ + static constexpr auto submask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE + | PA_SUBSCRIPTION_MASK_SERVER; + static constexpr auto do_signal = [](pa_context*, int, void *pdata) noexcept + { static_cast(pdata)->signal(); }; + auto *op = pa_context_subscribe(context, submask, do_signal, mutex()); waitForOperation(op); - /* Watch for device added/removed events. - * - * TODO: Also track the "default" device, in as much as PulseAudio has - * the concept of a default device (whatever device is opened when not - * specifying a specific sink or source name). There doesn't seem to be - * an event for this. - */ - auto handler = [](pa_context*, pa_subscription_event_type_t t, uint32_t, void*) noexcept - { - const auto eventFacility = (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK); - if(eventFacility == PA_SUBSCRIPTION_EVENT_SINK - || eventFacility == PA_SUBSCRIPTION_EVENT_SOURCE) - { - const auto deviceType = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK) - ? alc::DeviceType::Playback : alc::DeviceType::Capture; - const auto eventType = (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK); - if(eventType == PA_SUBSCRIPTION_EVENT_NEW) - alc::Event(alc::EventType::DeviceAdded, deviceType, "Device added"); - else if(eventType == PA_SUBSCRIPTION_EVENT_REMOVE) - alc::Event(alc::EventType::DeviceRemoved, deviceType, "Device removed"); - } - }; - pa_context_set_subscribe_callback(mutex()->mContext, handler, nullptr); + static constexpr auto handler = [](pa_context *ctx, pa_subscription_event_type_t t, + uint32_t index, void *pdata) noexcept + { return static_cast(pdata)->eventCallback(ctx, t, index); }; + pa_context_set_subscribe_callback(context, handler, mutex()); + + /* Fill in the initial device lists, and get the defaults. */ + auto sink_callback = [](pa_context *ctx, const pa_sink_info *info, int eol, void *pdata) noexcept + { return static_cast(pdata)->deviceSinkCallback(ctx, info, eol); }; + + auto src_callback = [](pa_context *ctx, const pa_source_info *info, int eol, void *pdata) noexcept + { return static_cast(pdata)->deviceSourceCallback(ctx, info, eol); }; + + auto server_callback = [](pa_context *ctx, const pa_server_info *info, void *pdata) noexcept + { return static_cast(pdata)->updateDefaultDevice(ctx, info); }; + + auto *sinkop = pa_context_get_sink_info_list(context, sink_callback, mutex()); + auto *srcop = pa_context_get_source_info_list(context, src_callback, mutex()); + auto *serverop = pa_context_get_server_info(context, server_callback, mutex()); + + waitForOperation(sinkop); + waitForOperation(srcop); + waitForOperation(serverop); } @@ -484,6 +577,13 @@ struct MainloopUniqueLock : public std::unique_lock { void connectContext(); pa_stream *connectStream(const char *device_name, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type); + + pa_stream *connectStream(const std::string &device_name, pa_stream_flags_t flags, + pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type) + { + return connectStream(device_name.empty() ? nullptr : device_name.c_str(), flags, attr, + spec, chanmap, type); + } }; using MainloopLockGuard = std::lock_guard; @@ -533,7 +633,7 @@ void MainloopUniqueLock::connectContext() { pa_context_unref(mutex()->mContext); mutex()->mContext = nullptr; - throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect (%s)", + throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect ({})", pa_strerror(err)}; } } @@ -544,7 +644,7 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_stream_ const char *stream_id{(type==BackendType::Playback) ? "Playback Stream" : "Capture Stream"}; pa_stream *stream{pa_stream_new(mutex()->mContext, stream_id, spec, chanmap)}; if(!stream) - throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed (%s)", + throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed ({})", pa_strerror(pa_context_errno(mutex()->mContext))}; pa_stream_set_state_callback(stream, [](pa_stream *strm, void *pdata) noexcept @@ -556,7 +656,7 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_stream_ if(err < 0) { pa_stream_unref(stream); - throw al::backend_exception{al::backend_error::DeviceError, "%s did not connect (%s)", + throw al::backend_exception{al::backend_error::DeviceError, "%s did not connect ({})", stream_id, pa_strerror(err)}; } @@ -568,7 +668,7 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_stream_ err = pa_context_errno(mutex()->mContext); pa_stream_unref(stream); throw al::backend_exception{al::backend_error::DeviceError, - "%s did not get ready (%s)", stream_id, pa_strerror(err)}; + "{} did not get ready ({})", stream_id, pa_strerror(err)}; } return state == PA_STREAM_READY; }); @@ -593,56 +693,12 @@ void PulseMainloop::close(pa_stream *stream) } -void PulseMainloop::probePlaybackDevices() -{ - PlaybackDevices.clear(); - try { - MainloopUniqueLock plock{*this}; - plock.connectContext(); - - auto sink_callback = [](pa_context *ctx, const pa_sink_info *info, int eol, void *pdata) noexcept - { return static_cast(pdata)->deviceSinkCallback(ctx, info, eol); }; - - pa_operation *op{pa_context_get_sink_info_by_name(mContext, nullptr, sink_callback, this)}; - plock.waitForOperation(op); - - op = pa_context_get_sink_info_list(mContext, sink_callback, this); - plock.waitForOperation(op); - } - catch(std::exception &e) { - ERR("Error enumerating devices: %s\n", e.what()); - } -} - -void PulseMainloop::probeCaptureDevices() -{ - CaptureDevices.clear(); - try { - MainloopUniqueLock plock{*this}; - plock.connectContext(); - - auto src_callback = [](pa_context *ctx, const pa_source_info *info, int eol, void *pdata) noexcept - { return static_cast(pdata)->deviceSourceCallback(ctx, info, eol); }; - - pa_operation *op{pa_context_get_source_info_by_name(mContext, nullptr, src_callback, - this)}; - plock.waitForOperation(op); - - op = pa_context_get_source_info_list(mContext, src_callback, this); - plock.waitForOperation(op); - } - catch(std::exception &e) { - ERR("Error enumerating devices: %s\n", e.what()); - } -} - - /* Used for initial connection test and enumeration. */ PulseMainloop gGlobalMainloop; struct PulsePlayback final : public BackendBase { - PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~PulsePlayback() override; void bufferAttrCallback(pa_stream *stream) noexcept; @@ -660,7 +716,7 @@ struct PulsePlayback final : public BackendBase { PulseMainloop mMainloop; - std::optional mDeviceName{std::nullopt}; + std::optional mDeviceId{std::nullopt}; bool mIs51Rear{false}; pa_buffer_attr mAttr{}; @@ -683,14 +739,14 @@ void PulsePlayback::bufferAttrCallback(pa_stream *stream) noexcept * leaving it alone means ALC_REFRESH will be off. */ mAttr = *(pa_stream_get_buffer_attr(stream)); - TRACE("minreq=%d, tlength=%d, prebuf=%d\n", mAttr.minreq, mAttr.tlength, mAttr.prebuf); + TRACE("minreq={}, tlength={}, prebuf={}", mAttr.minreq, mAttr.tlength, mAttr.prebuf); } void PulsePlayback::streamStateCallback(pa_stream *stream) noexcept { if(pa_stream_get_state(stream) == PA_STREAM_FAILED) { - ERR("Received stream failure!\n"); + ERR("Received stream failure!"); mDevice->handleDisconnect("Playback stream failure"); } mMainloop.signal(); @@ -716,7 +772,7 @@ void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexce int ret{pa_stream_write(stream, buf, buflen, free_func, 0, PA_SEEK_RELATIVE)}; if(ret != PA_OK) UNLIKELY - ERR("Failed to write to stream: %d, %s\n", ret, pa_strerror(ret)); + ERR("Failed to write to stream: {}, {}", ret, pa_strerror(ret)); } while(nbytes > 0); } @@ -759,11 +815,11 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int mIs51Rear = false; std::array chanmap_str{}; pa_channel_map_snprint(chanmap_str.data(), chanmap_str.size(), &info->channel_map); - WARN("Failed to find format for channel map:\n %s\n", chanmap_str.data()); + WARN("Failed to find format for channel map:\n {}", chanmap_str.data()); } if(info->active_port) - TRACE("Active port: %s (%s)\n", info->active_port->name, info->active_port->description); + TRACE("Active port: {} ({})", info->active_port->name, info->active_port->description); mDevice->Flags.set(DirectEar, (info->active_port && strcmp(info->active_port->name, "analog-output-headphones") == 0)); } @@ -775,13 +831,13 @@ void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int mMainloop.signal(); return; } - mDevice->DeviceName = info->description; + mDeviceName = info->description; } void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept { - mDeviceName = pa_stream_get_device_name(stream); - TRACE("Stream moved to %s\n", mDeviceName->c_str()); + mDeviceId = pa_stream_get_device_name(stream); + TRACE("Stream moved to {}", *mDeviceId); } @@ -792,22 +848,20 @@ void PulsePlayback::open(std::string_view name) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start device mainloop"}; - const char *pulse_name{nullptr}; - std::string_view display_name; + auto pulse_name = std::string{}; if(!name.empty()) { - if(PlaybackDevices.empty()) - mMainloop.probePlaybackDevices(); - auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name || entry.device_name == name; }; + + auto plock = MainloopUniqueLock{gGlobalMainloop}; auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), match_name); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; - pulse_name = iter->device_name.c_str(); - display_name = iter->name; + pulse_name = iter->device_name; + mDeviceName = iter->name; } MainloopUniqueLock plock{mMainloop}; @@ -823,38 +877,38 @@ void PulsePlayback::open(std::string_view name) spec.rate = 44100; spec.channels = 2; - if(!pulse_name) + if(pulse_name.empty()) { static const auto defname = al::getenv("ALSOFT_PULSE_DEFAULT"); - if(defname) pulse_name = defname->c_str(); + if(defname) pulse_name = *defname; } - TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); + TRACE("Connecting to \"{}\"", pulse_name.empty() ? "(default)"sv:std::string_view{pulse_name}); mStream = plock.connectStream(pulse_name, flags, nullptr, &spec, nullptr, BackendType::Playback); - constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept + static constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept { return static_cast(pdata)->streamMovedCallback(stream); }; pa_stream_set_moved_callback(mStream, move_callback, this); mFrameSize = static_cast(pa_frame_size(pa_stream_get_sample_spec(mStream))); - if(pulse_name) mDeviceName.emplace(pulse_name); - else mDeviceName.reset(); - if(display_name.empty()) + if(!pulse_name.empty()) + mDeviceId.emplace(std::move(pulse_name)); + + if(mDeviceName.empty()) { - auto name_callback = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept + static constexpr auto name_callback = [](pa_context *context, const pa_sink_info *info, + int eol, void *pdata) noexcept { return static_cast(pdata)->sinkNameCallback(context, info, eol); }; pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), pa_stream_get_device_name(mStream), name_callback, this)}; plock.waitForOperation(op); } - else - mDevice->DeviceName = display_name; } bool PulsePlayback::reset() { MainloopUniqueLock plock{mMainloop}; - const auto deviceName = mDeviceName ? mDeviceName->c_str() : nullptr; + const auto deviceName = mDeviceId ? mDeviceId->c_str() : nullptr; if(mStream) { @@ -877,7 +931,7 @@ bool PulsePlayback::reset() PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS}; if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; - if(GetConfigValueBool(mDevice->DeviceName, "pulse", "adjust-latency", false)) + if(GetConfigValueBool(mDevice->mDeviceName, "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, @@ -886,7 +940,7 @@ bool PulsePlayback::reset() flags &= ~PA_STREAM_EARLY_REQUESTS; flags |= PA_STREAM_ADJUST_LATENCY; } - if(GetConfigValueBool(mDevice->DeviceName, "pulse", "fix-rate", false) + if(GetConfigValueBool(mDevice->mDeviceName, "pulse", "fix-rate", false) || !mDevice->Flags.test(FrequencyRequest)) flags |= PA_STREAM_FIX_RATE; @@ -948,16 +1002,16 @@ bool PulsePlayback::reset() mSpec.format = PA_SAMPLE_FLOAT32NE; break; } - mSpec.rate = mDevice->Frequency; + mSpec.rate = mDevice->mSampleRate; mSpec.channels = static_cast(mDevice->channelsFromFmt()); if(pa_sample_spec_valid(&mSpec) == 0) throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample spec"}; const auto frame_size = static_cast(pa_frame_size(&mSpec)); mAttr.maxlength = ~0u; - mAttr.tlength = mDevice->BufferSize * frame_size; + mAttr.tlength = mDevice->mBufferSize * frame_size; mAttr.prebuf = 0u; - mAttr.minreq = mDevice->UpdateSize * frame_size; + mAttr.minreq = mDevice->mUpdateSize * frame_size; mAttr.fragsize = ~0u; mStream = plock.connectStream(deviceName, flags, &mAttr, &mSpec, &chanmap, @@ -974,15 +1028,15 @@ bool PulsePlayback::reset() mSpec = *(pa_stream_get_sample_spec(mStream)); mFrameSize = static_cast(pa_frame_size(&mSpec)); - if(mDevice->Frequency != mSpec.rate) + if(mDevice->mSampleRate != mSpec.rate) { /* Server updated our playback rate, so modify the buffer attribs * accordingly. */ - const auto scale = static_cast(mSpec.rate) / mDevice->Frequency; - const auto perlen = std::clamp(std::round(scale*mDevice->UpdateSize), 64.0, 8192.0); + const auto scale = static_cast(mSpec.rate) / mDevice->mSampleRate; + const auto perlen = std::clamp(std::round(scale*mDevice->mUpdateSize), 64.0, 8192.0); const auto bufmax = uint{std::numeric_limits::max()} / mFrameSize; - const auto buflen = std::clamp(std::round(scale*mDevice->BufferSize), perlen*2.0, + const auto buflen = std::clamp(std::round(scale*mDevice->mBufferSize), perlen*2.0, static_cast(bufmax)); mAttr.maxlength = ~0u; @@ -994,7 +1048,7 @@ bool PulsePlayback::reset() &mMainloop); plock.waitForOperation(op); - mDevice->Frequency = mSpec.rate; + mDevice->mSampleRate = mSpec.rate; } constexpr auto attr_callback = [](pa_stream *stream, void *pdata) noexcept @@ -1002,8 +1056,8 @@ bool PulsePlayback::reset() pa_stream_set_buffer_attr_callback(mStream, attr_callback, this); bufferAttrCallback(mStream); - mDevice->BufferSize = mAttr.tlength / mFrameSize; - mDevice->UpdateSize = mAttr.minreq / mFrameSize; + mDevice->mBufferSize = mAttr.tlength / mFrameSize; + mDevice->mUpdateSize = mAttr.minreq / mFrameSize; return true; } @@ -1061,8 +1115,8 @@ ClockLatency PulsePlayback::getClockLatency() * server yet. Give a generic value since nothing better is available. */ if(err != -PA_ERR_NODATA) - ERR("Failed to get stream latency: 0x%x\n", err); - latency = mDevice->BufferSize - mDevice->UpdateSize; + ERR("Failed to get stream latency: {:#x}", as_unsigned(err)); + latency = mDevice->mBufferSize - mDevice->mUpdateSize; neg = 0; } else if(neg) UNLIKELY @@ -1074,7 +1128,7 @@ ClockLatency PulsePlayback::getClockLatency() struct PulseCapture final : public BackendBase { - PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~PulseCapture() override; void streamStateCallback(pa_stream *stream) noexcept; @@ -1090,7 +1144,7 @@ struct PulseCapture final : public BackendBase { PulseMainloop mMainloop; - std::optional mDeviceName{std::nullopt}; + std::optional mDeviceId{std::nullopt}; al::span mCapBuffer; size_t mHoleLength{0}; @@ -1113,7 +1167,7 @@ void PulseCapture::streamStateCallback(pa_stream *stream) noexcept { if(pa_stream_get_state(stream) == PA_STREAM_FAILED) { - ERR("Received stream failure!\n"); + ERR("Received stream failure!"); mDevice->handleDisconnect("Capture stream failure"); } mMainloop.signal(); @@ -1126,13 +1180,13 @@ void PulseCapture::sourceNameCallback(pa_context*, const pa_source_info *info, i mMainloop.signal(); return; } - mDevice->DeviceName = info->description; + mDeviceName = info->description; } void PulseCapture::streamMovedCallback(pa_stream *stream) noexcept { - mDeviceName = pa_stream_get_device_name(stream); - TRACE("Stream moved to %s\n", mDeviceName->c_str()); + mDeviceId = pa_stream_get_device_name(stream); + TRACE("Stream moved to {}", *mDeviceId); } @@ -1146,21 +1200,20 @@ void PulseCapture::open(std::string_view name) "Failed to start device mainloop"}; } - const char *pulse_name{nullptr}; + auto pulse_name = std::string{}; if(!name.empty()) { - if(CaptureDevices.empty()) - mMainloop.probeCaptureDevices(); - auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name || entry.device_name == name; }; + + auto plock = MainloopUniqueLock{gGlobalMainloop}; auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), match_name); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; - pulse_name = iter->device_name.c_str(); - mDevice->DeviceName = iter->name; + pulse_name = iter->device_name; + mDeviceName = iter->name; } MainloopUniqueLock plock{mMainloop}; @@ -1179,7 +1232,7 @@ void PulseCapture::open(std::string_view name) case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: - throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", + throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } setDefaultWFXChannelOrder(); @@ -1203,26 +1256,26 @@ void PulseCapture::open(std::string_view name) case DevFmtUShort: case DevFmtUInt: throw al::backend_exception{al::backend_error::DeviceError, - "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; + "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } - mSpec.rate = mDevice->Frequency; + mSpec.rate = mDevice->mSampleRate; mSpec.channels = static_cast(mDevice->channelsFromFmt()); if(pa_sample_spec_valid(&mSpec) == 0) throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample format"}; const auto frame_size = static_cast(pa_frame_size(&mSpec)); - const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency*100u/1000u)}; + const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate*100u/1000u)}; mAttr.minreq = ~0u; mAttr.prebuf = ~0u; mAttr.maxlength = samples * frame_size; mAttr.tlength = ~0u; - mAttr.fragsize = std::min(samples, mDevice->Frequency*50u/1000u) * frame_size; + mAttr.fragsize = std::min(samples, mDevice->mSampleRate*50u/1000u) * frame_size; pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY}; if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; - TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); + TRACE("Connecting to \"{}\"", pulse_name.empty() ? "(default)"sv:std::string_view{pulse_name}); mStream = plock.connectStream(pulse_name, flags, &mAttr, &mSpec, &chanmap, BackendType::Capture); @@ -1234,9 +1287,10 @@ void PulseCapture::open(std::string_view name) { return static_cast(pdata)->streamStateCallback(stream); }; pa_stream_set_state_callback(mStream, state_callback, this); - if(pulse_name) mDeviceName.emplace(pulse_name); - else mDeviceName.reset(); - if(mDevice->DeviceName.empty()) + if(!pulse_name.empty()) + mDeviceId.emplace(std::move(pulse_name)); + + if(mDeviceName.empty()) { constexpr auto name_callback = [](pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept @@ -1305,7 +1359,7 @@ void PulseCapture::captureSamples(std::byte *buffer, uint samples) const pa_stream_state_t state{pa_stream_get_state(mStream)}; if(!PA_STREAM_IS_GOOD(state)) UNLIKELY { - mDevice->handleDisconnect("Bad capture state: %u", state); + mDevice->handleDisconnect("Bad capture state: {}", al::to_underlying(state)); break; } @@ -1313,7 +1367,7 @@ void PulseCapture::captureSamples(std::byte *buffer, uint samples) size_t caplen{}; if(pa_stream_peek(mStream, &capbuf, &caplen) < 0) UNLIKELY { - mDevice->handleDisconnect("Failed retrieving capture samples: %s", + mDevice->handleDisconnect("Failed retrieving capture samples: {}", pa_strerror(pa_context_errno(mMainloop.getContext()))); break; } @@ -1341,8 +1395,8 @@ uint PulseCapture::availableSamples() if(static_cast(got) < 0) UNLIKELY { const char *err{pa_strerror(static_cast(got))}; - ERR("pa_stream_readable_size() failed: %s\n", err); - mDevice->handleDisconnect("Failed getting readable size: %s", err); + ERR("pa_stream_readable_size() failed: {}", err); + mDevice->handleDisconnect("Failed getting readable size: {}", err); } else { @@ -1377,7 +1431,7 @@ ClockLatency PulseCapture::getClockLatency() if(err != 0) UNLIKELY { - ERR("Failed to get stream latency: 0x%x\n", err); + ERR("Failed to get stream latency: {:#x}", as_unsigned(err)); latency = 0; neg = 0; } @@ -1393,7 +1447,7 @@ ClockLatency PulseCapture::getClockLatency() bool PulseBackendFactory::init() { -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD if(!pulse_handle) { #ifdef _WIN32 @@ -1406,7 +1460,7 @@ bool PulseBackendFactory::init() pulse_handle = LoadLib(PALIB); if(!pulse_handle) { - WARN("Failed to load %s\n", PALIB); + WARN("Failed to load {}", PALIB); return false; } @@ -1420,13 +1474,13 @@ bool PulseBackendFactory::init() if(!missing_funcs.empty()) { - WARN("Missing expected functions:%s\n", missing_funcs.c_str()); + WARN("Missing expected functions:{}", missing_funcs); CloseLib(pulse_handle); pulse_handle = nullptr; return false; } } -#endif /* HAVE_DYNLOAD */ +#endif pulse_ctx_flags = PA_CONTEXT_NOFLAGS; if(!GetConfigValueBool({}, "pulse", "spawn-server", false)) @@ -1460,21 +1514,32 @@ auto PulseBackendFactory::enumerate(BackendType type) -> std::vector outnames; - auto add_device = [&outnames](const DevMap &entry) -> void - { outnames.push_back(entry.name); }; + auto add_playback_device = [&outnames](const DevMap &entry) -> void + { + if(entry.device_name == DefaultPlaybackDevName) + outnames.emplace(outnames.cbegin(), entry.name); + else + outnames.push_back(entry.name); + }; + auto add_capture_device = [&outnames](const DevMap &entry) -> void + { + if(entry.device_name == DefaultCaptureDevName) + outnames.emplace(outnames.cbegin(), entry.name); + else + outnames.push_back(entry.name); + }; + auto plock = MainloopUniqueLock{gGlobalMainloop}; switch(type) { case BackendType::Playback: - gGlobalMainloop.probePlaybackDevices(); outnames.reserve(PlaybackDevices.size()); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); + std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_playback_device); break; case BackendType::Capture: - gGlobalMainloop.probeCaptureDevices(); outnames.reserve(CaptureDevices.size()); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); + std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_capture_device); break; } @@ -1502,9 +1567,9 @@ alc::EventSupport PulseBackendFactory::queryEventSupport(alc::EventType eventTyp { case alc::EventType::DeviceAdded: case alc::EventType::DeviceRemoved: + case alc::EventType::DefaultDeviceChanged: return alc::EventSupport::FullSupport; - case alc::EventType::DefaultDeviceChanged: case alc::EventType::Count: break; } diff --git a/Engine/lib/openal-soft/alc/backends/sdl2.cpp b/Engine/lib/openal-soft/alc/backends/sdl2.cpp index ec3be6b0e..a0e41f4a0 100644 --- a/Engine/lib/openal-soft/alc/backends/sdl2.cpp +++ b/Engine/lib/openal-soft/alc/backends/sdl2.cpp @@ -28,10 +28,8 @@ #include #include -#include "almalloc.h" #include "alnumeric.h" #include "core/device.h" -#include "core/logging.h" _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") @@ -41,18 +39,13 @@ _Pragma("GCC diagnostic pop") namespace { -#ifdef _WIN32 -#define DEVNAME_PREFIX "OpenAL Soft on " -#else -#define DEVNAME_PREFIX "" -#endif +using namespace std::string_view_literals; -constexpr auto getDevicePrefix() noexcept -> std::string_view { return DEVNAME_PREFIX; } -constexpr auto getDefaultDeviceName() noexcept -> std::string_view -{ return DEVNAME_PREFIX "Default Device"; } +[[nodiscard]] constexpr auto getDefaultDeviceName() noexcept -> std::string_view +{ return "Default Device"sv; } struct Sdl2Backend final : public BackendBase { - Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { } + explicit Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { } ~Sdl2Backend() override; void audioCallback(Uint8 *stream, int len) noexcept; @@ -62,13 +55,9 @@ struct Sdl2Backend final : public BackendBase { void start() override; void stop() override; + std::string mSDLName; SDL_AudioDeviceID mDeviceID{0u}; uint mFrameSize{0}; - - uint mFrequency{0u}; - DevFmtChannels mFmtChans{}; - DevFmtType mFmtType{}; - uint mUpdateSize{0u}; }; Sdl2Backend::~Sdl2Backend() @@ -89,7 +78,7 @@ void Sdl2Backend::open(std::string_view name) { SDL_AudioSpec want{}, have{}; - want.freq = static_cast(mDevice->Frequency); + want.freq = static_cast(mDevice->mSampleRate); switch(mDevice->FmtType) { case DevFmtUByte: want.format = AUDIO_U8; break; @@ -100,8 +89,9 @@ void Sdl2Backend::open(std::string_view name) case DevFmtInt: want.format = AUDIO_S32SYS; break; case DevFmtFloat: want.format = AUDIO_F32; break; } - want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2; - want.samples = static_cast(std::min(mDevice->UpdateSize, 8192u)); + want.channels = static_cast(std::min(mDevice->channelsFromFmt(), + std::numeric_limits::max())); + want.samples = static_cast(std::min(mDevice->mUpdateSize, 8192u)); want.callback = [](void *ptr, Uint8 *stream, int len) noexcept { return static_cast(ptr)->audioCallback(stream, len); }; want.userdata = this; @@ -110,45 +100,21 @@ void Sdl2Backend::open(std::string_view name) * necessarily the first in the list. */ const auto defaultDeviceName = getDefaultDeviceName(); - SDL_AudioDeviceID devid; if(name.empty() || name == defaultDeviceName) { name = defaultDeviceName; - devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); + mSDLName.clear(); + mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, + SDL_AUDIO_ALLOW_ANY_CHANGE); } else { - const auto namePrefix = getDevicePrefix(); - if(name.size() >= namePrefix.size() && name.substr(0, namePrefix.size()) == namePrefix) - { - /* Copy the string_view to a string to ensure it's null terminated - * for this call. - */ - const std::string devname{name.substr(namePrefix.size())}; - devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); - } - else - { - const std::string devname{name}; - devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); - } - } - if(!devid) - throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()}; - - DevFmtChannels devchans{}; - if(have.channels >= 2) - devchans = DevFmtStereo; - else if(have.channels == 1) - devchans = DevFmtMono; - else - { - SDL_CloseAudioDevice(devid); - throw al::backend_exception{al::backend_error::DeviceError, - "Unhandled SDL channel count: %d", int{have.channels}}; + mSDLName = name; + mDeviceID = SDL_OpenAudioDevice(mSDLName.c_str(), SDL_FALSE, &want, &have, + SDL_AUDIO_ALLOW_ANY_CHANGE); } + if(!mDeviceID) + throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()}; DevFmtType devtype{}; switch(have.format) @@ -160,32 +126,100 @@ void Sdl2Backend::open(std::string_view name) case AUDIO_S32SYS: devtype = DevFmtInt; break; case AUDIO_F32SYS: devtype = DevFmtFloat; break; default: - SDL_CloseAudioDevice(devid); - throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: 0x%04x", - have.format}; + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL format: {:#04x}", have.format}; } - if(mDeviceID) - SDL_CloseAudioDevice(mDeviceID); - mDeviceID = devid; - mFrameSize = BytesFromDevFmt(devtype) * have.channels; - mFrequency = static_cast(have.freq); - mFmtChans = devchans; - mFmtType = devtype; - mUpdateSize = have.samples; - mDevice->DeviceName = name; + mDeviceName = name; } bool Sdl2Backend::reset() { - mDevice->Frequency = mFrequency; - mDevice->FmtChans = mFmtChans; - mDevice->FmtType = mFmtType; - mDevice->UpdateSize = mUpdateSize; - mDevice->BufferSize = mUpdateSize * 2; /* SDL always (tries to) use two periods. */ + if(mDeviceID) + SDL_CloseAudioDevice(mDeviceID); + mDeviceID = 0; + + auto want = SDL_AudioSpec{}; + want.freq = static_cast(mDevice->mSampleRate); + switch(mDevice->FmtType) + { + case DevFmtUByte: want.format = AUDIO_U8; break; + case DevFmtByte: want.format = AUDIO_S8; break; + case DevFmtUShort: want.format = AUDIO_U16SYS; break; + case DevFmtShort: want.format = AUDIO_S16SYS; break; + case DevFmtUInt: [[fallthrough]]; + case DevFmtInt: want.format = AUDIO_S32SYS; break; + case DevFmtFloat: want.format = AUDIO_F32; break; + } + want.channels = static_cast(std::min(mDevice->channelsFromFmt(), + std::numeric_limits::max())); + want.samples = static_cast(std::min(mDevice->mUpdateSize, 8192u)); + want.callback = [](void *ptr, Uint8 *stream, int len) noexcept + { return static_cast(ptr)->audioCallback(stream, len); }; + want.userdata = this; + + auto have = SDL_AudioSpec{}; + if(mSDLName.empty()) + { + mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, + SDL_AUDIO_ALLOW_ANY_CHANGE); + } + else + { + mDeviceID = SDL_OpenAudioDevice(mSDLName.c_str(), SDL_FALSE, &want, &have, + SDL_AUDIO_ALLOW_ANY_CHANGE); + } + if(!mDeviceID) + throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()}; + + if(have.channels != mDevice->channelsFromFmt()) + { + /* SDL guarantees these layouts for the given channel count. */ + if(have.channels == 8) + mDevice->FmtChans = DevFmtX71; + else if(have.channels == 7) + mDevice->FmtChans = DevFmtX61; + else if(have.channels == 6) + mDevice->FmtChans = DevFmtX51; + else if(have.channels == 4) + mDevice->FmtChans = DevFmtQuad; + else if(have.channels >= 2) + mDevice->FmtChans = DevFmtStereo; + else if(have.channels == 1) + mDevice->FmtChans = DevFmtMono; + else + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL channel count: {}", int{have.channels}}; + mDevice->mAmbiOrder = 0; + } + + switch(have.format) + { + case AUDIO_U8: mDevice->FmtType = DevFmtUByte; break; + case AUDIO_S8: mDevice->FmtType = DevFmtByte; break; + case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break; + case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort; break; + case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break; + case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat; break; + default: + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL format: {:#04x}", have.format}; + } + + mFrameSize = BytesFromDevFmt(mDevice->FmtType) * have.channels; + + if(have.freq < int{MinOutputRate}) + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL sample rate: {}", have.freq}; + + mDevice->mSampleRate = static_cast(have.freq); + mDevice->mUpdateSize = have.samples; + mDevice->mBufferSize = std::max(have.size/mFrameSize, mDevice->mUpdateSize*2u); + setDefaultWFXChannelOrder(); + return true; } @@ -220,16 +254,14 @@ auto SDL2BackendFactory::enumerate(BackendType type) -> std::vector if(num_devices <= 0) return outnames; - outnames.reserve(static_cast(num_devices)); + outnames.reserve(static_cast(num_devices)+1_uz); outnames.emplace_back(getDefaultDeviceName()); for(int i{0};i < num_devices;++i) { - std::string outname{getDevicePrefix()}; if(const char *name = SDL_GetAudioDeviceName(i, SDL_FALSE)) - outname += name; + outnames.emplace_back(name); else - outname += "Unknown Device Name #"+std::to_string(i); - outnames.emplace_back(std::move(outname)); + outnames.emplace_back("Unknown Device Name #"+std::to_string(i)); } return outnames; } diff --git a/Engine/lib/openal-soft/alc/backends/sdl3.cpp b/Engine/lib/openal-soft/alc/backends/sdl3.cpp new file mode 100644 index 000000000..b10ad591e --- /dev/null +++ b/Engine/lib/openal-soft/alc/backends/sdl3.cpp @@ -0,0 +1,393 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2024 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "sdl3.h" + +#include +#include +#include +#include +#include +#include + +#include "almalloc.h" +#include "core/device.h" +#include "core/logging.h" + +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") +#include "SDL3/SDL_audio.h" +#include "SDL3/SDL_init.h" +#include "SDL3/SDL_stdinc.h" +_Pragma("GCC diagnostic pop") + + +namespace { + +using namespace std::string_view_literals; + +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") +constexpr auto DefaultPlaybackDeviceID = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; +_Pragma("GCC diagnostic pop") + + +template +struct SdlDeleter { + /* NOLINTNEXTLINE(cppcoreguidelines-no-malloc) */ + void operator()(gsl::owner ptr) const { SDL_free(ptr); } +}; +template +using unique_sdl_ptr = std::unique_ptr>; + + +struct DeviceEntry { + std::string mName; + SDL_AudioDeviceID mPhysDeviceID{}; +}; + +std::vector gPlaybackDevices; + +void EnumeratePlaybackDevices() +{ + auto numdevs = int{}; + auto devicelist = unique_sdl_ptr{SDL_GetAudioPlaybackDevices(&numdevs)}; + if(!devicelist || numdevs < 0) + { + ERR("Failed to get playback devices: {}", SDL_GetError()); + return; + } + + auto devids = al::span{devicelist.get(), static_cast(numdevs)}; + auto newlist = std::vector{}; + + newlist.reserve(devids.size()); + std::transform(devids.begin(), devids.end(), std::back_inserter(newlist), + [](SDL_AudioDeviceID id) + { + auto *name = SDL_GetAudioDeviceName(id); + if(!name) return DeviceEntry{}; + TRACE("Got device \"{}\", ID {}", name, id); + return DeviceEntry{name, id}; + }); + + gPlaybackDevices.swap(newlist); +} + +[[nodiscard]] constexpr auto getDefaultDeviceName() noexcept -> std::string_view +{ return "Default Device"sv; } + + +struct Sdl3Backend final : public BackendBase { + explicit Sdl3Backend(DeviceBase *device) noexcept : BackendBase{device} { } + ~Sdl3Backend() final; + + void audioCallback(SDL_AudioStream *stream, int additional_amount, int total_amount) noexcept; + + void open(std::string_view name) final; + auto reset() -> bool final; + void start() final; + void stop() final; + + SDL_AudioDeviceID mDeviceID{0}; + SDL_AudioStream *mStream{nullptr}; + uint mNumChannels{0}; + uint mFrameSize{0}; + std::vector mBuffer; +}; + +Sdl3Backend::~Sdl3Backend() +{ + if(mStream) + SDL_DestroyAudioStream(mStream); + mStream = nullptr; +} + +void Sdl3Backend::audioCallback(SDL_AudioStream *stream, int additional_amount, int total_amount) + noexcept +{ + if(additional_amount < 0) + additional_amount = total_amount; + if(additional_amount <= 0) + return; + + const auto ulen = static_cast(additional_amount); + assert((ulen % mFrameSize) == 0); + + if(ulen > mBuffer.size()) + { + mBuffer.resize(ulen); + std::fill(mBuffer.begin(), mBuffer.end(), (mDevice->FmtType == DevFmtUByte) + ? std::byte{0x80} : std::byte{}); + } + + mDevice->renderSamples(mBuffer.data(), ulen / mFrameSize, mNumChannels); + SDL_PutAudioStreamData(stream, mBuffer.data(), additional_amount); +} + +void Sdl3Backend::open(std::string_view name) +{ + const auto defaultDeviceName = getDefaultDeviceName(); + if(name.empty() || name == defaultDeviceName) + { + name = defaultDeviceName; + mDeviceID = DefaultPlaybackDeviceID; + } + else + { + if(gPlaybackDevices.empty()) + EnumeratePlaybackDevices(); + + const auto iter = std::find_if(gPlaybackDevices.cbegin(), gPlaybackDevices.cend(), + [name](const DeviceEntry &entry) { return name == entry.mName; }); + if(iter == gPlaybackDevices.cend()) + throw al::backend_exception{al::backend_error::NoDevice, "No device named {}", name}; + + mDeviceID = iter->mPhysDeviceID; + } + + mStream = SDL_OpenAudioDeviceStream(mDeviceID, nullptr, nullptr, nullptr); + if(!mStream) + throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()}; + + auto have = SDL_AudioSpec{}; + auto update_size = int{}; + if(SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(mStream), &have, &update_size)) + { + auto devtype = mDevice->FmtType; + switch(have.format) + { + case SDL_AUDIO_U8: devtype = DevFmtUByte; break; + case SDL_AUDIO_S8: devtype = DevFmtByte; break; + case SDL_AUDIO_S16: devtype = DevFmtShort; break; + case SDL_AUDIO_S32: devtype = DevFmtInt; break; + case SDL_AUDIO_F32: devtype = DevFmtFloat; break; + default: break; + } + mDevice->FmtType = devtype; + + if(have.freq >= int{MinOutputRate} && have.freq <= int{MaxOutputRate}) + mDevice->mSampleRate = static_cast(have.freq); + + /* SDL guarantees these layouts for the given channel count. */ + if(have.channels == 8) + mDevice->FmtChans = DevFmtX71; + else if(have.channels == 7) + mDevice->FmtChans = DevFmtX61; + else if(have.channels == 6) + mDevice->FmtChans = DevFmtX51; + else if(have.channels == 4) + mDevice->FmtChans = DevFmtQuad; + else if(have.channels >= 2) + mDevice->FmtChans = DevFmtStereo; + else if(have.channels == 1) + mDevice->FmtChans = DevFmtMono; + mDevice->mAmbiOrder = 0; + + mNumChannels = static_cast(have.channels); + mFrameSize = mDevice->bytesFromFmt() * mNumChannels; + + if(update_size >= 64) + { + /* We have to assume the total buffer size is just twice the update + * size. SDL doesn't tell us the full end-to-end buffer latency. + */ + mDevice->mUpdateSize = static_cast(update_size); + mDevice->mBufferSize = mDevice->mUpdateSize*2u; + } + else + ERR("Invalid update size from SDL stream: {}", update_size); + } + else + ERR("Failed to get format from SDL stream: {}", SDL_GetError()); + + mDeviceName = name; +} + +auto Sdl3Backend::reset() -> bool +{ + static constexpr auto callback = [](void *ptr, SDL_AudioStream *stream, int additional_amount, + int total_amount) noexcept + { + return static_cast(ptr)->audioCallback(stream, additional_amount, + total_amount); + }; + + if(mStream) + SDL_DestroyAudioStream(mStream); + mStream = nullptr; + + mBuffer.clear(); + mBuffer.shrink_to_fit(); + + auto want = SDL_AudioSpec{}; + if(!SDL_GetAudioDeviceFormat(mDeviceID, &want, nullptr)) + ERR("Failed to get device format: {}", SDL_GetError()); + + if(mDevice->Flags.test(FrequencyRequest) || want.freq < int{MinOutputRate}) + want.freq = static_cast(mDevice->mSampleRate); + if(mDevice->Flags.test(SampleTypeRequest) + || !(want.format == SDL_AUDIO_U8 || want.format == SDL_AUDIO_S8 + || want.format == SDL_AUDIO_S16 || want.format == SDL_AUDIO_S32 + || want.format == SDL_AUDIO_F32)) + { + switch(mDevice->FmtType) + { + case DevFmtUByte: want.format = SDL_AUDIO_U8; break; + case DevFmtByte: want.format = SDL_AUDIO_S8; break; + case DevFmtUShort: [[fallthrough]]; + case DevFmtShort: want.format = SDL_AUDIO_S16; break; + case DevFmtUInt: [[fallthrough]]; + case DevFmtInt: want.format = SDL_AUDIO_S32; break; + case DevFmtFloat: want.format = SDL_AUDIO_F32; break; + } + } + if(mDevice->Flags.test(ChannelsRequest) || want.channels < 1) + want.channels = static_cast(std::min(mDevice->channelsFromFmt(), + std::numeric_limits::max())); + + mStream = SDL_OpenAudioDeviceStream(mDeviceID, &want, callback, this); + if(!mStream) + { + /* If creating the stream failed, try again without a specific format. */ + mStream = SDL_OpenAudioDeviceStream(mDeviceID, nullptr, callback, this); + if(!mStream) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to recreate stream: {}", SDL_GetError()}; + } + + auto update_size = int{}; + auto have = SDL_AudioSpec{}; + SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(mStream), &have, &update_size); + + have = SDL_AudioSpec{}; + if(!SDL_GetAudioStreamFormat(mStream, &have, nullptr)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to get stream format: {}", SDL_GetError()}; + + if(!mDevice->Flags.test(ChannelsRequest) + || (static_cast(have.channels) != mDevice->channelsFromFmt() + && !(mDevice->FmtChans == DevFmtStereo && have.channels >= 2))) + { + /* SDL guarantees these layouts for the given channel count. */ + if(have.channels == 8) + mDevice->FmtChans = DevFmtX71; + else if(have.channels == 7) + mDevice->FmtChans = DevFmtX61; + else if(have.channels == 6) + mDevice->FmtChans = DevFmtX51; + else if(have.channels == 4) + mDevice->FmtChans = DevFmtQuad; + else if(have.channels >= 2) + mDevice->FmtChans = DevFmtStereo; + else if(have.channels == 1) + mDevice->FmtChans = DevFmtMono; + else + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL channel count: {}", have.channels}; + mDevice->mAmbiOrder = 0; + } + mNumChannels = static_cast(have.channels); + + switch(have.format) + { + case SDL_AUDIO_U8: mDevice->FmtType = DevFmtUByte; break; + case SDL_AUDIO_S8: mDevice->FmtType = DevFmtByte; break; + case SDL_AUDIO_S16: mDevice->FmtType = DevFmtShort; break; + case SDL_AUDIO_S32: mDevice->FmtType = DevFmtInt; break; + case SDL_AUDIO_F32: mDevice->FmtType = DevFmtFloat; break; + default: + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL format: {:#04x}", al::to_underlying(have.format)}; + } + + mFrameSize = mDevice->bytesFromFmt() * mNumChannels; + + if(have.freq < int{MinOutputRate}) + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL sample rate: {}", have.freq}; + mDevice->mSampleRate = static_cast(have.freq); + + if(update_size >= 64) + { + mDevice->mUpdateSize = static_cast(update_size); + mDevice->mBufferSize = mDevice->mUpdateSize*2u; + + mBuffer.resize(size_t{mDevice->mUpdateSize} * mFrameSize); + std::fill(mBuffer.begin(), mBuffer.end(), (mDevice->FmtType == DevFmtUByte) + ? std::byte{0x80} : std::byte{}); + } + else + ERR("Invalid update size from SDL stream: {}", update_size); + + setDefaultWFXChannelOrder(); + + return true; +} + +void Sdl3Backend::start() +{ SDL_ResumeAudioStreamDevice(mStream); } + +void Sdl3Backend::stop() +{ SDL_PauseAudioStreamDevice(mStream); } + +} // namespace + +auto SDL3BackendFactory::getFactory() -> BackendFactory& +{ + static SDL3BackendFactory factory{}; + return factory; +} + +auto SDL3BackendFactory::init() -> bool +{ + if(!SDL_InitSubSystem(SDL_INIT_AUDIO)) + return false; + TRACE("Current SDL3 audio driver: \"{}\"", SDL_GetCurrentAudioDriver()); + return true; +} + +auto SDL3BackendFactory::querySupport(BackendType type) -> bool +{ return type == BackendType::Playback; } + +auto SDL3BackendFactory::enumerate(BackendType type) -> std::vector +{ + auto outnames = std::vector{}; + + if(type != BackendType::Playback) + return outnames; + + EnumeratePlaybackDevices(); + outnames.reserve(gPlaybackDevices.size()+1); + outnames.emplace_back(getDefaultDeviceName()); + std::transform(gPlaybackDevices.begin(), gPlaybackDevices.end(), std::back_inserter(outnames), + std::mem_fn(&DeviceEntry::mName)); + + return outnames; +} + +auto SDL3BackendFactory::createBackend(DeviceBase *device, BackendType type) -> BackendPtr +{ + if(type == BackendType::Playback) + return BackendPtr{new Sdl3Backend{device}}; + return nullptr; +} diff --git a/Engine/lib/openal-soft/alc/backends/sdl3.h b/Engine/lib/openal-soft/alc/backends/sdl3.h new file mode 100644 index 000000000..4d6730658 --- /dev/null +++ b/Engine/lib/openal-soft/alc/backends/sdl3.h @@ -0,0 +1,19 @@ +#ifndef BACKENDS_SDL3_H +#define BACKENDS_SDL3_H + +#include "base.h" + +struct SDL3BackendFactory final : public BackendFactory { +public: + auto init() -> bool final; + + auto querySupport(BackendType type) -> bool final; + + auto enumerate(BackendType type) -> std::vector final; + + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; + + static auto getFactory() -> BackendFactory&; +}; + +#endif /* BACKENDS_SDL3_H */ diff --git a/Engine/lib/openal-soft/alc/backends/sndio.cpp b/Engine/lib/openal-soft/alc/backends/sndio.cpp index 8d0693af7..187e13057 100644 --- a/Engine/lib/openal-soft/alc/backends/sndio.cpp +++ b/Engine/lib/openal-soft/alc/backends/sndio.cpp @@ -20,27 +20,23 @@ #include "config.h" -#include "sndio.h" +#include "sndio.hpp" -#include #include #include #include -#include #include #include #include #include -#include "alnumeric.h" -#include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "ringbuffer.h" -#include /* NOLINT(*-duplicate-include) Not the same header. */ +#include namespace { @@ -56,7 +52,7 @@ struct SioPar : public sio_par { }; struct SndioPlayback final : public BackendBase { - SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~SndioPlayback() override; int mixerProc(); @@ -102,7 +98,7 @@ int SndioPlayback::mixerProc() size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())}; if(wrote > buffer.size() || wrote == 0) { - ERR("sio_write failed: 0x%" PRIx64 "\n", wrote); + ERR("sio_write failed: {:#x}", wrote); mDevice->handleDisconnect("Failed to write playback samples"); break; } @@ -119,8 +115,8 @@ void SndioPlayback::open(std::string_view name) if(name.empty()) name = GetDefaultName(); else if(name != GetDefaultName()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)}; if(!sndHandle) @@ -130,7 +126,7 @@ void SndioPlayback::open(std::string_view name) sio_close(mSndHandle); mSndHandle = sndHandle; - mDevice->DeviceName = name; + mDeviceName = name; } bool SndioPlayback::reset() @@ -172,12 +168,12 @@ bool SndioPlayback::reset() par.le = SIO_LE_NATIVE; par.msb = 1; - par.rate = mDevice->Frequency; + par.rate = mDevice->mSampleRate; par.pchan = mDevice->channelsFromFmt(); - par.round = mDevice->UpdateSize; - par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize; - if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize; + par.round = mDevice->mUpdateSize; + par.appbufsz = mDevice->mBufferSize - mDevice->mUpdateSize; + if(!par.appbufsz) par.appbufsz = mDevice->mUpdateSize; try { if(!sio_setpar(mSndHandle, &par)) @@ -191,10 +187,10 @@ bool SndioPlayback::reset() if(par.bps > 1 && par.le != SIO_LE_NATIVE) throw al::backend_exception{al::backend_error::DeviceError, - "%s-endian samples not supported", par.le ? "Little" : "Big"}; + "{}-endian samples not supported", par.le ? "Little" : "Big"}; if(par.bits < par.bps*8 && !par.msb) throw al::backend_exception{al::backend_error::DeviceError, - "MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8}; + "MSB-padded samples not supported ({} of {} bits)", par.bits, par.bps*8}; if(par.pchan < 1) throw al::backend_exception{al::backend_error::DeviceError, "No playback channels on device"}; @@ -217,24 +213,24 @@ bool SndioPlayback::reset() mDevice->FmtType = (par.sig==1) ? DevFmtInt : DevFmtUInt; else throw al::backend_exception{al::backend_error::DeviceError, - "Unhandled sample format: %s %u-bit", (par.sig?"signed":"unsigned"), par.bps*8}; + "Unhandled sample format: {} {}-bit", (par.sig?"signed":"unsigned"), par.bps*8}; mFrameStep = par.pchan; if(par.pchan != mDevice->channelsFromFmt()) { - WARN("Got %u channel%s for %s\n", par.pchan, (par.pchan==1)?"":"s", + WARN("Got {} channel{} for {}", par.pchan, (par.pchan==1)?"":"s", DevFmtChannelsString(mDevice->FmtChans)); if(par.pchan < 2) mDevice->FmtChans = DevFmtMono; else mDevice->FmtChans = DevFmtStereo; } - mDevice->Frequency = par.rate; + mDevice->mSampleRate = par.rate; setDefaultChannelOrder(); - mDevice->UpdateSize = par.round; - mDevice->BufferSize = par.bufsz + par.round; + mDevice->mUpdateSize = par.round; + mDevice->mBufferSize = par.bufsz + par.round; - mBuffer.resize(size_t{mDevice->UpdateSize} * par.pchan*par.bps); + mBuffer.resize(size_t{mDevice->mUpdateSize} * par.pchan*par.bps); if(par.sig == 1) std::fill(mBuffer.begin(), mBuffer.end(), std::byte{}); else if(par.bits == 8) @@ -254,12 +250,12 @@ void SndioPlayback::start() try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this}; + mThread = std::thread{&SndioPlayback::mixerProc, this}; } catch(std::exception& e) { sio_stop(mSndHandle); throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -270,7 +266,7 @@ void SndioPlayback::stop() mThread.join(); if(!sio_stop(mSndHandle)) - ERR("Error stopping device\n"); + ERR("Error stopping device"); } @@ -280,7 +276,7 @@ void SndioPlayback::stop() * capture buffer sizes apps may request. */ struct SndioCapture final : public BackendBase { - SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~SndioCapture() override; int recordProc(); @@ -316,7 +312,7 @@ int SndioCapture::recordProc() int nfds_pre{sio_nfds(mSndHandle)}; if(nfds_pre <= 0) { - mDevice->handleDisconnect("Incorrect return value from sio_nfds(): %d", nfds_pre); + mDevice->handleDisconnect("Incorrect return value from sio_nfds(): {}", nfds_pre); return 1; } @@ -329,15 +325,14 @@ int SndioCapture::recordProc() const int nfds{sio_pollfd(mSndHandle, fds.data(), POLLIN)}; if(nfds <= 0) { - mDevice->handleDisconnect("Failed to get polling fds: %d", nfds); + mDevice->handleDisconnect("Failed to get polling fds: {}", nfds); break; } int pollres{::poll(fds.data(), fds.size(), 2000)}; if(pollres < 0) { if(errno == EINTR) continue; - mDevice->handleDisconnect("Poll error: %s", - std::generic_category().message(errno).c_str()); + mDevice->handleDisconnect("Poll error: {}", std::generic_category().message(errno)); break; } if(pollres == 0) @@ -353,7 +348,7 @@ int SndioCapture::recordProc() continue; auto data = mRing->getWriteVector(); - al::span buffer{data.first.buf, data.first.len*frameSize}; + al::span buffer{data[0].buf, data[0].len*frameSize}; while(!buffer.empty()) { size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())}; @@ -361,8 +356,8 @@ int SndioCapture::recordProc() break; if(got > buffer.size()) { - ERR("sio_read failed: 0x%" PRIx64 "\n", got); - mDevice->handleDisconnect("sio_read failed: 0x%" PRIx64, got); + ERR("sio_read failed: {:#x}", got); + mDevice->handleDisconnect("sio_read failed: {:#x}", got); break; } @@ -371,7 +366,7 @@ int SndioCapture::recordProc() if(buffer.empty()) { data = mRing->getWriteVector(); - buffer = {data.first.buf, data.first.len*frameSize}; + buffer = {data[0].buf, data[0].len*frameSize}; } } if(buffer.empty()) @@ -391,8 +386,8 @@ void SndioCapture::open(std::string_view name) if(name.empty()) name = GetDefaultName(); else if(name != GetDefaultName()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; mSndHandle = sio_open(nullptr, SIO_REC, true); if(mSndHandle == nullptr) @@ -427,16 +422,16 @@ void SndioCapture::open(std::string_view name) break; case DevFmtFloat: throw al::backend_exception{al::backend_error::DeviceError, - "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; + "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } par.bps = SIO_BPS(par.bits); par.le = SIO_LE_NATIVE; par.msb = 1; par.rchan = mDevice->channelsFromFmt(); - par.rate = mDevice->Frequency; + par.rate = mDevice->mSampleRate; - par.appbufsz = std::max(mDevice->BufferSize, mDevice->Frequency/10u); - par.round = std::min(par.appbufsz/2u, mDevice->Frequency/40u); + par.appbufsz = std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u); + par.round = std::min(par.appbufsz/2u, mDevice->mSampleRate/40u); if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) throw al::backend_exception{al::backend_error::DeviceError, @@ -444,10 +439,10 @@ void SndioCapture::open(std::string_view name) if(par.bps > 1 && par.le != SIO_LE_NATIVE) throw al::backend_exception{al::backend_error::DeviceError, - "%s-endian samples not supported", par.le ? "Little" : "Big"}; + "{}-endian samples not supported", par.le ? "Little" : "Big"}; if(par.bits < par.bps*8 && !par.msb) throw al::backend_exception{al::backend_error::DeviceError, - "Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8}; + "Padded samples not supported (got {} of {} bits)", par.bits, par.bps*8}; auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool { @@ -459,19 +454,19 @@ void SndioCapture::open(std::string_view name) || (fmttype == DevFmtUInt && p.bps == 4 && p.sig == 0); }; if(!match_fmt(mDevice->FmtType, par) || mDevice->channelsFromFmt() != par.rchan - || mDevice->Frequency != par.rate) + || mDevice->mSampleRate != par.rate) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead", + "Failed to set format {} {} {}hz, got {}{} {}-channel {}hz instead", DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans), - mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate}; + mDevice->mSampleRate, par.sig?'s':'u', par.bps*8, par.rchan, par.rate}; - mRing = RingBuffer::Create(mDevice->BufferSize, size_t{par.bps}*par.rchan, false); - mDevice->BufferSize = static_cast(mRing->writeSpace()); - mDevice->UpdateSize = par.round; + mRing = RingBuffer::Create(mDevice->mBufferSize, size_t{par.bps}*par.rchan, false); + mDevice->mBufferSize = static_cast(mRing->writeSpace()); + mDevice->mUpdateSize = par.round; setDefaultChannelOrder(); - mDevice->DeviceName = name; + mDeviceName = name; } void SndioCapture::start() @@ -481,12 +476,12 @@ void SndioCapture::start() try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this}; + mThread = std::thread{&SndioCapture::recordProc, this}; } catch(std::exception& e) { sio_stop(mSndHandle); throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start capture thread: %s", e.what()}; + "Failed to start capture thread: {}", e.what()}; } } @@ -497,7 +492,7 @@ void SndioCapture::stop() mThread.join(); if(!sio_stop(mSndHandle)) - ERR("Error stopping device\n"); + ERR("Error stopping device"); } void SndioCapture::captureSamples(std::byte *buffer, uint samples) diff --git a/Engine/lib/openal-soft/alc/backends/sndio.h b/Engine/lib/openal-soft/alc/backends/sndio.hpp similarity index 81% rename from Engine/lib/openal-soft/alc/backends/sndio.h rename to Engine/lib/openal-soft/alc/backends/sndio.hpp index a4496c92a..4327524ff 100644 --- a/Engine/lib/openal-soft/alc/backends/sndio.h +++ b/Engine/lib/openal-soft/alc/backends/sndio.hpp @@ -1,5 +1,5 @@ -#ifndef BACKENDS_SNDIO_H -#define BACKENDS_SNDIO_H +#ifndef BACKENDS_SNDIO_HPP +#define BACKENDS_SNDIO_HPP #include "base.h" @@ -16,4 +16,4 @@ public: static auto getFactory() -> BackendFactory&; }; -#endif /* BACKENDS_SNDIO_H */ +#endif /* BACKENDS_SNDIO_HPP */ diff --git a/Engine/lib/openal-soft/alc/backends/solaris.cpp b/Engine/lib/openal-soft/alc/backends/solaris.cpp index 047736c6a..c42015e74 100644 --- a/Engine/lib/openal-soft/alc/backends/solaris.cpp +++ b/Engine/lib/openal-soft/alc/backends/solaris.cpp @@ -60,7 +60,7 @@ std::string solaris_driver{"/dev/audio"}; struct SolarisBackend final : public BackendBase { - SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { } + explicit SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { } ~SolarisBackend() override; int mixerProc(); @@ -106,13 +106,13 @@ int SolarisBackend::mixerProc() { if(errno == EINTR || errno == EAGAIN) continue; - ERR("poll failed: %s\n", strerror(errno)); - mDevice->handleDisconnect("Failed to wait for playback buffer: %s", strerror(errno)); + ERR("poll failed: {}", strerror(errno)); + mDevice->handleDisconnect("Failed to wait for playback buffer: {}", strerror(errno)); break; } else if(pret == 0) { - WARN("poll timeout\n"); + WARN("poll timeout"); continue; } @@ -126,8 +126,8 @@ int SolarisBackend::mixerProc() { if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; - ERR("write failed: %s\n", strerror(errno)); - mDevice->handleDisconnect("Failed to write playback samples: %s", strerror(errno)); + ERR("write failed: {}", strerror(errno)); + mDevice->handleDisconnect("Failed to write playback samples: {}", strerror(errno)); break; } @@ -144,19 +144,19 @@ void SolarisBackend::open(std::string_view name) if(name.empty()) name = GetDefaultName(); else if(name != GetDefaultName()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; int fd{::open(solaris_driver.c_str(), O_WRONLY)}; if(fd == -1) - throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", - solaris_driver.c_str(), strerror(errno)}; + throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", + solaris_driver, strerror(errno)}; if(mFd != -1) ::close(mFd); mFd = fd; - mDevice->DeviceName = name; + mDeviceName = name; } bool SolarisBackend::reset() @@ -164,7 +164,7 @@ bool SolarisBackend::reset() audio_info_t info; AUDIO_INITINFO(&info); - info.play.sample_rate = mDevice->Frequency; + info.play.sample_rate = mDevice->mSampleRate; info.play.channels = mDevice->channelsFromFmt(); switch(mDevice->FmtType) { @@ -187,11 +187,11 @@ bool SolarisBackend::reset() info.play.encoding = AUDIO_ENCODING_LINEAR; break; } - info.play.buffer_size = mDevice->BufferSize * mDevice->frameSizeFromFmt(); + info.play.buffer_size = mDevice->mBufferSize * mDevice->frameSizeFromFmt(); if(ioctl(mFd, AUDIO_SETINFO, &info) < 0) { - ERR("ioctl failed: %s\n", strerror(errno)); + ERR("ioctl failed: {}", strerror(errno)); return false; } @@ -203,7 +203,7 @@ bool SolarisBackend::reset() mDevice->FmtChans = DevFmtMono; else throw al::backend_exception{al::backend_error::DeviceError, - "Got %u device channels", info.play.channels}; + "Got {} device channels", info.play.channels}; } if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8) @@ -216,20 +216,20 @@ bool SolarisBackend::reset() mDevice->FmtType = DevFmtInt; else { - ERR("Got unhandled sample type: %d (0x%x)\n", info.play.precision, info.play.encoding); + ERR("Got unhandled sample type: {} ({:#x})", info.play.precision, info.play.encoding); return false; } uint frame_size{mDevice->bytesFromFmt() * info.play.channels}; mFrameStep = info.play.channels; - mDevice->Frequency = info.play.sample_rate; - mDevice->BufferSize = info.play.buffer_size / frame_size; + mDevice->mSampleRate = info.play.sample_rate; + mDevice->mBufferSize = info.play.buffer_size / frame_size; /* How to get the actual period size/count? */ - mDevice->UpdateSize = mDevice->BufferSize / 2; + mDevice->mUpdateSize = mDevice->mBufferSize / 2; setDefaultChannelOrder(); - mBuffer.resize(mDevice->UpdateSize * size_t{frame_size}); + mBuffer.resize(mDevice->mUpdateSize * size_t{frame_size}); std::fill(mBuffer.begin(), mBuffer.end(), std::byte{}); return true; @@ -239,11 +239,11 @@ void SolarisBackend::start() { try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this}; + mThread = std::thread{&SolarisBackend::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -254,7 +254,7 @@ void SolarisBackend::stop() mThread.join(); if(ioctl(mFd, AUDIO_DRAIN) < 0) - ERR("Error draining device: %s\n", strerror(errno)); + ERR("Error draining device: {}", strerror(errno)); } } // namespace diff --git a/Engine/lib/openal-soft/alc/backends/wasapi.cpp b/Engine/lib/openal-soft/alc/backends/wasapi.cpp index d640e1d41..9f9449689 100644 --- a/Engine/lib/openal-soft/alc/backends/wasapi.cpp +++ b/Engine/lib/openal-soft/alc/backends/wasapi.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -50,7 +51,6 @@ #include #include #include -#include #include #include #include @@ -68,13 +68,13 @@ #include "comptr.h" #include "core/converter.h" #include "core/device.h" -#include "core/helpers.h" #include "core/logging.h" +#include "fmt/core.h" +#include "fmt/chrono.h" #include "ringbuffer.h" #include "strutils.h" -#if defined(ALSOFT_UWP) - +#if ALSOFT_UWP #include // !!This is important!! #include #include @@ -82,11 +82,7 @@ #include #include -using namespace winrt; -using namespace Windows::Foundation; -using namespace Windows::Media::Devices; -using namespace Windows::Devices::Enumeration; -using namespace Windows::Media::Devices; +#include "alstring.h" #endif /* Some headers seem to define these as macros for __uuidof, which is annoying @@ -99,7 +95,7 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); #endif -#if !defined(ALSOFT_UWP) +#if !ALSOFT_UWP DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14); DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0); DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 ); @@ -107,13 +103,22 @@ DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x namespace { +#if ALSOFT_UWP +using namespace winrt; +using namespace Windows::Foundation; +using namespace Windows::Media::Devices; +using namespace Windows::Devices::Enumeration; +using namespace Windows::Media::Devices; +#endif +#ifndef E_NOTFOUND +#define E_NOTFOUND E_NOINTERFACE +#endif + using namespace std::string_view_literals; using std::chrono::nanoseconds; using std::chrono::milliseconds; using std::chrono::seconds; -[[nodiscard]] constexpr auto GetDevicePrefix() noexcept { return "OpenAL Soft on "sv; } - using ReferenceTime = std::chrono::duration>; @@ -126,7 +131,7 @@ using ReferenceTime = std::chrono::duration DWORD { b |= b>>1; b |= b>>2; @@ -158,9 +163,6 @@ constexpr AudioObjectType ChannelMask_Quad{AudioObjectType_FrontLeft | AudioObje constexpr AudioObjectType ChannelMask_X51{AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft | AudioObjectType_SideRight}; -constexpr AudioObjectType ChannelMask_X51Rear{AudioObjectType_FrontLeft - | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency - | AudioObjectType_BackLeft | AudioObjectType_BackRight}; constexpr AudioObjectType ChannelMask_X61{AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft | AudioObjectType_SideRight | AudioObjectType_BackCenter}; @@ -189,16 +191,15 @@ overloaded(Ts...) -> overloaded; template -constexpr auto as_unsigned(T value) noexcept -{ - using UT = std::make_unsigned_t; - return static_cast(value); -} +struct CoTaskMemDeleter { + void operator()(T *ptr) const { CoTaskMemFree(ptr); } +}; +template +using unique_coptr = std::unique_ptr>; /* Scales the given reftime value, rounding the result. */ -template -constexpr uint RefTime2Samples(const ReferenceTime &val, T srate) noexcept +constexpr auto RefTime2Samples(const ReferenceTime &val, DWORD srate) noexcept -> uint { const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1}; return static_cast(std::min(retval, std::numeric_limits::max())); @@ -206,16 +207,16 @@ constexpr uint RefTime2Samples(const ReferenceTime &val, T srate) noexcept class GuidPrinter { - std::array mMsg{}; + std::string mMsg; public: - GuidPrinter(const GUID &guid) - { - std::snprintf(mMsg.data(), mMsg.size(), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", - DWORD{guid.Data1}, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], - guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); - } - [[nodiscard]] auto c_str() const -> const char* { return mMsg.data(); } + explicit GuidPrinter(const GUID &guid) + : mMsg{fmt::format( + "{{{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}}}", + guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], + guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7])} + { } + [[nodiscard]] auto str() const noexcept -> const std::string& { return mMsg; } }; struct PropVariant { @@ -246,10 +247,11 @@ public: { if constexpr(std::is_same_v) { - alassert(mProp.vt == VT_UI4); - return mProp.uiVal; + alassert(mProp.vt == VT_UI4 || mProp.vt == VT_UINT); + return mProp.uintVal; } - else if constexpr(std::is_same_v || std::is_same_v) + else if constexpr(std::is_same_v || std::is_same_v + || std::is_same_v || std::is_same_v) { alassert(mProp.vt == VT_LPWSTR); return mProp.pwszVal; @@ -316,9 +318,18 @@ struct DeviceListLock : public std::unique_lock { }; DeviceList gDeviceList; +std::condition_variable_any gInitCV; +std::atomic gInitDone{false}; -#if defined(ALSOFT_UWP) +#ifdef AVRTAPI +struct AvrtHandleCloser { + void operator()(HANDLE handle) { AvRevertMmThreadCharacteristics(handle); } +}; +using AvrtHandlePtr = std::unique_ptr,AvrtHandleCloser>; +#endif + +#if ALSOFT_UWP enum EDataFlow { eRender = 0, eCapture = (eRender + 1), @@ -327,7 +338,7 @@ enum EDataFlow { }; #endif -#if defined(ALSOFT_UWP) +#if ALSOFT_UWP using DeviceHandle = Windows::Devices::Enumeration::DeviceInformation; using EventRegistrationToken = winrt::event_token; #else @@ -335,43 +346,42 @@ using DeviceHandle = ComPtr; #endif -using NameGUIDPair = std::pair; -NameGUIDPair GetDeviceNameAndGuid(const DeviceHandle &device) +struct NameGUIDPair { std::string mName; std::string mGuid; }; +auto GetDeviceNameAndGuid(const DeviceHandle &device) -> NameGUIDPair { constexpr auto UnknownName = "Unknown Device Name"sv; constexpr auto UnknownGuid = "Unknown Device GUID"sv; -#if !defined(ALSOFT_UWP) - std::string name, guid; - - ComPtr ps; - HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))}; +#if !ALSOFT_UWP + auto ps = ComPtr{}; + auto hr = device->OpenPropertyStore(STGM_READ, al::out_ptr(ps)); if(FAILED(hr)) { - WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return {std::string{UnknownName}, std::string{UnknownGuid}}; + WARN("OpenPropertyStore failed: {:#x}", as_unsigned(hr)); + return NameGUIDPair{std::string{UnknownName}, std::string{UnknownGuid}}; } - PropVariant pvprop; + auto ret = NameGUIDPair{}; + auto pvprop = PropVariant{}; hr = ps->GetValue(al::bit_cast(DEVPKEY_Device_FriendlyName), pvprop.get()); if(FAILED(hr)) - WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr); + WARN("GetValue Device_FriendlyName failed: {:#x}", as_unsigned(hr)); else if(pvprop.type() == VT_LPWSTR) - name = wstr_to_utf8(pvprop.value()); + ret.mName = wstr_to_utf8(pvprop.value()); else - WARN("Unexpected Device_FriendlyName PROPVARIANT type: 0x%04x\n", pvprop.type()); + WARN("Unexpected Device_FriendlyName PROPVARIANT type: {:#04x}", pvprop.type()); pvprop.clear(); hr = ps->GetValue(al::bit_cast(PKEY_AudioEndpoint_GUID), pvprop.get()); if(FAILED(hr)) - WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr); + WARN("GetValue AudioEndpoint_GUID failed: {:#x}", as_unsigned(hr)); else if(pvprop.type() == VT_LPWSTR) - guid = wstr_to_utf8(pvprop.value()); + ret.mGuid = wstr_to_utf8(pvprop.value()); else - WARN("Unexpected AudioEndpoint_GUID PROPVARIANT type: 0x%04x\n", pvprop.type()); + WARN("Unexpected AudioEndpoint_GUID PROPVARIANT type: {:#04x}", pvprop.type()); #else - std::string name{wstr_to_utf8(device.Name())}; - std::string guid; + auto ret = NameGUIDPair{wstr_to_utf8(device.Name()), {}}; + // device->Id is DeviceInterfacePath: \\?\SWD#MMDEVAPI#{0.0.0.00000000}.{a21c17a0-fc1d-405e-ab5a-b513422b57d1}#{e6327cad-dcec-4949-ae8a-991e976a79d2} auto devIfPath = device.Id(); if(auto devIdStart = wcsstr(devIfPath.data(), L"}.")) @@ -379,25 +389,25 @@ NameGUIDPair GetDeviceNameAndGuid(const DeviceHandle &device) devIdStart += 2; // L"}." if(auto devIdStartEnd = wcschr(devIdStart, L'#')) { - std::wstring wDevId{devIdStart, static_cast(devIdStartEnd - devIdStart)}; - guid = wstr_to_utf8(wDevId.c_str()); - std::transform(guid.begin(), guid.end(), guid.begin(), + ret.mGuid = wstr_to_utf8(std::wstring_view{devIdStart, + static_cast(devIdStartEnd - devIdStart)}); + std::transform(ret.mGuid.begin(), ret.mGuid.end(), ret.mGuid.begin(), [](char ch) { return static_cast(std::toupper(ch)); }); } } #endif - if(name.empty()) name = UnknownName; - if(guid.empty()) guid = UnknownGuid; - return {std::move(name), std::move(guid)}; + if(ret.mName.empty()) ret.mName = UnknownName; + if(ret.mGuid.empty()) ret.mGuid = UnknownGuid; + return ret; } -#if !defined(ALSOFT_UWP) +#if !ALSOFT_UWP EndpointFormFactor GetDeviceFormfactor(IMMDevice *device) { ComPtr ps; HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))}; if(FAILED(hr)) { - WARN("OpenPropertyStore failed: 0x%08lx\n", hr); + WARN("OpenPropertyStore failed: {:#x}", as_unsigned(hr)); return UnknownFormFactor; } @@ -405,68 +415,73 @@ EndpointFormFactor GetDeviceFormfactor(IMMDevice *device) PropVariant pvform; hr = ps->GetValue(PKEY_AudioEndpoint_FormFactor, pvform.get()); if(FAILED(hr)) - WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr); + WARN("GetValue AudioEndpoint_FormFactor failed: {:#x}", as_unsigned(hr)); else if(pvform.type() == VT_UI4) formfactor = static_cast(pvform.value()); else if(pvform.type() != VT_EMPTY) - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform.type()); + WARN("Unexpected PROPVARIANT type: {:#04x}", pvform.type()); return formfactor; } #endif -#if defined(ALSOFT_UWP) -struct DeviceHelper final : public IActivateAudioInterfaceCompletionHandler -#else -struct DeviceHelper final : private IMMNotificationClient -#endif -{ -#if defined(ALSOFT_UWP) - DeviceHelper() +#if ALSOFT_UWP +struct DeviceEnumHelper final : public IActivateAudioInterfaceCompletionHandler { + DeviceEnumHelper() { /* TODO: UWP also needs to watch for device added/removed events and * dynamically add/remove devices from the lists. */ mActiveClientEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - mRenderDeviceChangedToken = MediaDevice::DefaultAudioRenderDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioRenderDeviceChangedEventArgs& args) { - if (args.Role() == AudioDeviceRole::Default) + static constexpr auto playback_cb = [](const IInspectable &sender [[maybe_unused]], + const DefaultAudioRenderDeviceChangedEventArgs &args) + { + if(args.Role() == AudioDeviceRole::Default) { - const std::string msg{ "Default playback device changed: " + + const auto msg = std::string{"Default playback device changed: " + wstr_to_utf8(args.Id())}; - alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, - msg); + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg); } - }); + }; + mRenderDeviceChangedToken = MediaDevice::DefaultAudioRenderDeviceChanged(playback_cb); - mCaptureDeviceChangedToken = MediaDevice::DefaultAudioCaptureDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioCaptureDeviceChangedEventArgs& args) { - if (args.Role() == AudioDeviceRole::Default) + static constexpr auto capture_cb = [](const IInspectable &sender [[maybe_unused]], + const DefaultAudioCaptureDeviceChangedEventArgs &args) + { + if(args.Role() == AudioDeviceRole::Default) { - const std::string msg{ "Default capture device changed: " + - wstr_to_utf8(args.Id()) }; - alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, - msg); + const auto msg = std::string{"Default capture device changed: " + + wstr_to_utf8(args.Id())}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg); } - }); + }; + mCaptureDeviceChangedToken = MediaDevice::DefaultAudioCaptureDeviceChanged(capture_cb); } -#else - DeviceHelper() = default; -#endif - ~DeviceHelper() + + ~DeviceEnumHelper() { -#if defined(ALSOFT_UWP) MediaDevice::DefaultAudioRenderDeviceChanged(mRenderDeviceChangedToken); MediaDevice::DefaultAudioCaptureDeviceChanged(mCaptureDeviceChangedToken); if(mActiveClientEvent != nullptr) CloseHandle(mActiveClientEvent); mActiveClientEvent = nullptr; + } #else + +struct DeviceEnumHelper final : private IMMNotificationClient { + DeviceEnumHelper() = default; + ~DeviceEnumHelper() + { if(mEnumerator) mEnumerator->UnregisterEndpointNotificationCallback(this); mEnumerator = nullptr; -#endif } +#endif + + template + auto as() noexcept -> T { return T{this}; } /** -------------------------- IUnknown ----------------------------- */ std::atomic mRefCount{1}; @@ -492,24 +507,24 @@ struct DeviceHelper final : private IMMNotificationClient // that, from the client's point of view, has a reference count of one. If the client then calls AddRef on the // interface pointer, the reference count becomes two. The client must call Release twice on the interface // pointer to drop all of its references to the object. -#if defined(ALSOFT_UWP) +#if ALSOFT_UWP if(IId == __uuidof(IActivateAudioInterfaceCompletionHandler)) { - *UnknownPtrPtr = static_cast(this); + *UnknownPtrPtr = as(); AddRef(); return S_OK; } #else if(IId == __uuidof(IMMNotificationClient)) { - *UnknownPtrPtr = static_cast(this); + *UnknownPtrPtr = as(); AddRef(); return S_OK; } #endif else if(IId == __uuidof(IAgileObject) || IId == __uuidof(IUnknown)) { - *UnknownPtrPtr = static_cast(this); + *UnknownPtrPtr = as(); AddRef(); return S_OK; } @@ -519,7 +534,7 @@ struct DeviceHelper final : private IMMNotificationClient return E_NOINTERFACE; } -#if defined(ALSOFT_UWP) +#if ALSOFT_UWP /** ----------------------- IActivateAudioInterfaceCompletionHandler ------------ */ HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation*) override { @@ -530,82 +545,42 @@ struct DeviceHelper final : private IMMNotificationClient } #else /** ----------------------- IMMNotificationClient ------------ */ - STDMETHODIMP OnDeviceStateChanged(LPCWSTR /*pwstrDeviceId*/, DWORD /*dwNewState*/) noexcept override { return S_OK; } - - STDMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId) noexcept override + STDMETHODIMP OnDeviceStateChanged(LPCWSTR deviceId, DWORD newState) noexcept override { - ComPtr device; - HRESULT hr{mEnumerator->GetDevice(pwstrDeviceId, al::out_ptr(device))}; - if(FAILED(hr)) - { - ERR("Failed to get device: 0x%08lx\n", hr); - return S_OK; - } + TRACE("OnDeviceStateChanged({}, {:#x})", deviceId ? wstr_to_utf8(deviceId) + : std::string{""}, newState); - ComPtr endpoint; - hr = device->QueryInterface(__uuidof(IMMEndpoint), al::out_ptr(endpoint)); - if(FAILED(hr)) - { - ERR("Failed to get device endpoint: 0x%08lx\n", hr); - return S_OK; - } - - EDataFlow flowdir{}; - hr = endpoint->GetDataFlow(&flowdir); - if(FAILED(hr)) - { - ERR("Failed to get endpoint data flow: 0x%08lx\n", hr); - return S_OK; - } - - auto devlock = DeviceListLock{gDeviceList}; - auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); - - if(AddDevice(device, pwstrDeviceId, list)) - { - const auto devtype = (flowdir==eRender) ? alc::DeviceType::Playback - : alc::DeviceType::Capture; - const std::string msg{"Device added: "+list.back().name}; - alc::Event(alc::EventType::DeviceAdded, devtype, msg); - } - - return S_OK; + if(!(newState&DEVICE_STATE_ACTIVE)) + return DeviceRemoved(deviceId); + return DeviceAdded(deviceId); } - STDMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId) noexcept override + STDMETHODIMP OnDeviceAdded(LPCWSTR deviceId) noexcept override { - auto devlock = DeviceListLock{gDeviceList}; - for(auto flowdir : std::array{eRender, eCapture}) - { - auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); - auto devtype = (flowdir==eRender)?alc::DeviceType::Playback : alc::DeviceType::Capture; - - /* Find the ID in the list to remove. */ - auto iter = std::find_if(list.begin(), list.end(), - [pwstrDeviceId](const DevMap &entry) noexcept - { return pwstrDeviceId == entry.devid; }); - if(iter == list.end()) continue; - - TRACE("Removing device \"%s\", \"%s\", \"%ls\"\n", iter->name.c_str(), - iter->endpoint_guid.c_str(), iter->devid.c_str()); - - std::string msg{"Device removed: "+std::move(iter->name)}; - list.erase(iter); - - alc::Event(alc::EventType::DeviceRemoved, devtype, msg); - } - return S_OK; + TRACE("OnDeviceAdded({})", deviceId ? wstr_to_utf8(deviceId) : std::string{""}); + return DeviceAdded(deviceId); } + STDMETHODIMP OnDeviceRemoved(LPCWSTR deviceId) noexcept override + { + TRACE("OnDeviceRemoved({})", deviceId ? wstr_to_utf8(deviceId) : std::string{""}); + return DeviceRemoved(deviceId); + } + + /* NOLINTNEXTLINE(clazy-function-args-by-ref) */ STDMETHODIMP OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/) noexcept override { return S_OK; } - STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) noexcept override + STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR defaultDeviceId) noexcept override { + TRACE("OnDefaultDeviceChanged({}, {}, {})", al::to_underlying(flow), + al::to_underlying(role), defaultDeviceId ? wstr_to_utf8(defaultDeviceId) + : std::string{""}); + if(role != eMultimedia) return S_OK; - const std::wstring_view devid{pwstrDefaultDeviceId ? pwstrDefaultDeviceId - : std::wstring_view{}}; + const auto devid = defaultDeviceId ? std::wstring_view{defaultDeviceId} + : std::wstring_view{}; if(flow == eRender) { DeviceListLock{gDeviceList}.setPlaybackDefaultId(devid); @@ -622,95 +597,34 @@ struct DeviceHelper final : private IMMNotificationClient } #endif - /** -------------------------- DeviceHelper ----------------------------- */ + /** ------------------------ DeviceEnumHelper -------------------------- */ HRESULT init() { -#if !defined(ALSOFT_UWP) +#if !ALSOFT_UWP HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), al::out_ptr(mEnumerator))}; if(SUCCEEDED(hr)) mEnumerator->RegisterEndpointNotificationCallback(this); else - WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); + WARN("Failed to create IMMDeviceEnumerator instance: {:#x}", as_unsigned(hr)); return hr; #else return S_OK; #endif } - HRESULT openDevice(std::wstring_view devid, EDataFlow flow, DeviceHandle& device) - { -#if !defined(ALSOFT_UWP) - HRESULT hr{E_FAIL}; - if(mEnumerator) - { - if(devid.empty()) - hr = mEnumerator->GetDefaultAudioEndpoint(flow, eMultimedia, al::out_ptr(device)); - else - hr = mEnumerator->GetDevice(devid.data(), al::out_ptr(device)); - } - return hr; -#else - const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default; - auto devIfPath = - devid.empty() ? (flow == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) : MediaDevice::GetDefaultAudioCaptureId(deviceRole)) - : winrt::hstring(devid.data()); - if (devIfPath.empty()) - return E_POINTER; - - auto&& deviceInfo = DeviceInformation::CreateFromIdAsync(devIfPath, nullptr, DeviceInformationKind::DeviceInterface).get(); - if (!deviceInfo) - return E_NOINTERFACE; - device = deviceInfo; - return S_OK; -#endif - } - -#if !defined(ALSOFT_UWP) - static HRESULT activateAudioClient(_In_ DeviceHandle &device, REFIID iid, void **ppv) - { return device->Activate(iid, CLSCTX_INPROC_SERVER, nullptr, ppv); } -#else - HRESULT activateAudioClient(_In_ DeviceHandle &device, _In_ REFIID iid, void **ppv) - { - ComPtr asyncOp; - HRESULT hr{ActivateAudioInterfaceAsync(device.Id().data(), iid, nullptr, this, - al::out_ptr(asyncOp))}; - if(FAILED(hr)) - return hr; - - /* I don't like waiting for INFINITE time, but the activate operation - * can take an indefinite amount of time since it can require user - * input. - */ - DWORD res{WaitForSingleObjectEx(mActiveClientEvent, INFINITE, FALSE)}; - if(res != WAIT_OBJECT_0) - { - ERR("WaitForSingleObjectEx error: 0x%lx\n", res); - return E_FAIL; - } - - HRESULT hrActivateRes{E_FAIL}; - ComPtr punkAudioIface; - hr = asyncOp->GetActivateResult(&hrActivateRes, al::out_ptr(punkAudioIface)); - if(SUCCEEDED(hr)) hr = hrActivateRes; - if(FAILED(hr)) return hr; - - return punkAudioIface->QueryInterface(iid, ppv); - } -#endif - std::wstring probeDevices(EDataFlow flowdir, std::vector &list) { std::wstring defaultId; std::vector{}.swap(list); -#if !defined(ALSOFT_UWP) +#if !ALSOFT_UWP ComPtr coll; HRESULT hr{mEnumerator->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, al::out_ptr(coll))}; if(FAILED(hr)) { - ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr); + ERR("Failed to enumerate audio endpoints: {:#x}", as_unsigned(hr)); return defaultId; } @@ -723,11 +637,11 @@ struct DeviceHelper final : private IMMNotificationClient hr = mEnumerator->GetDefaultAudioEndpoint(flowdir, eMultimedia, al::out_ptr(device)); if(SUCCEEDED(hr)) { - if(WCHAR *devid{GetDeviceId(device.get())}) - { - defaultId = devid; - CoTaskMemFree(devid); - } + auto devid = unique_coptr{}; + if(auto hr2 = device->GetId(al::out_ptr(devid)); SUCCEEDED(hr2)) + defaultId = devid.get(); + else + ERR("Failed to get device id: {:#x}", as_unsigned(hr)); device = nullptr; } @@ -737,11 +651,11 @@ struct DeviceHelper final : private IMMNotificationClient if(FAILED(hr)) continue; - if(WCHAR *devid{GetDeviceId(device.get())}) - { - std::ignore = AddDevice(device, devid, list); - CoTaskMemFree(devid); - } + auto devid = unique_coptr{}; + if(auto hr2 = device->GetId(al::out_ptr(devid)); SUCCEEDED(hr2)) + std::ignore = AddDevice(device, devid.get(), list); + else + ERR("Failed to get device id: {:#x}", as_unsigned(hr)); device = nullptr; } #else @@ -789,37 +703,93 @@ private: return false; } - auto name_guid = GetDeviceNameAndGuid(device); - int count{1}; - std::string newname{name_guid.first}; + auto [name, guid] = GetDeviceNameAndGuid(device); + auto count = 1; + auto newname = name; while(checkName(list, newname)) - { - newname = name_guid.first; - newname += " #"; - newname += std::to_string(++count); - } - list.emplace_back(std::move(newname), std::move(name_guid.second), devid); - const DevMap &newentry = list.back(); + newname = fmt::format("{} #{}", name, ++count); + const auto &newentry = list.emplace_back(std::move(newname), std::move(guid), devid); - TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(), - newentry.endpoint_guid.c_str(), newentry.devid.c_str()); + TRACE("Got device \"{}\", \"{}\", \"{}\"", newentry.name, newentry.endpoint_guid, + wstr_to_utf8(newentry.devid)); return true; } -#if !defined(ALSOFT_UWP) - static WCHAR *GetDeviceId(IMMDevice *device) +#if !ALSOFT_UWP + STDMETHODIMP DeviceAdded(LPCWSTR deviceId) noexcept { - WCHAR *devid; - - const HRESULT hr{device->GetId(&devid)}; + auto device = ComPtr{}; + auto hr = mEnumerator->GetDevice(deviceId, al::out_ptr(device)); if(FAILED(hr)) { - ERR("Failed to get device id: %lx\n", hr); - return nullptr; + ERR("Failed to get device: {:#x}", as_unsigned(hr)); + return S_OK; } - return devid; + auto state = DWORD{}; + hr = device->GetState(&state); + if(FAILED(hr)) + { + ERR("Failed to get device state: {:#x}", as_unsigned(hr)); + return S_OK; + } + if(!(state&DEVICE_STATE_ACTIVE)) + return S_OK; + + auto endpoint = ComPtr{}; + hr = device->QueryInterface(__uuidof(IMMEndpoint), al::out_ptr(endpoint)); + if(FAILED(hr)) + { + ERR("Failed to get device endpoint: {:#x}", as_unsigned(hr)); + return S_OK; + } + + auto flowdir = EDataFlow{}; + hr = endpoint->GetDataFlow(&flowdir); + if(FAILED(hr)) + { + ERR("Failed to get endpoint data flow: {:#x}", as_unsigned(hr)); + return S_OK; + } + + auto devlock = DeviceListLock{gDeviceList}; + auto &list = (flowdir == eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); + + if(AddDevice(device, deviceId, list)) + { + const auto devtype = (flowdir == eRender) ? alc::DeviceType::Playback + : alc::DeviceType::Capture; + const auto msg = "Device added: "+list.back().name; + alc::Event(alc::EventType::DeviceAdded, devtype, msg); + } + + return S_OK; } + + STDMETHODIMP DeviceRemoved(LPCWSTR deviceId) noexcept + { + auto devlock = DeviceListLock{gDeviceList}; + for(auto flowdir : std::array{eRender, eCapture}) + { + auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); + auto devtype = (flowdir==eRender)?alc::DeviceType::Playback : alc::DeviceType::Capture; + + /* Find the ID in the list to remove. */ + auto iter = std::find_if(list.begin(), list.end(), + [deviceId](const DevMap &entry) noexcept { return deviceId == entry.devid; }); + if(iter == list.end()) continue; + + TRACE("Removing device \"{}\", \"{}\", \"{}\"", iter->name, iter->endpoint_guid, + wstr_to_utf8(iter->devid)); + + std::string msg{"Device removed: "+std::move(iter->name)}; + list.erase(iter); + + alc::Event(alc::EventType::DeviceRemoved, devtype, msg); + } + return S_OK; + } + ComPtr mEnumerator{nullptr}; #else @@ -829,8 +799,239 @@ private: EventRegistrationToken mRenderDeviceChangedToken; EventRegistrationToken mCaptureDeviceChangedToken; #endif + + static inline std::mutex mMsgLock; + static inline std::condition_variable mMsgCond; + static inline bool mQuit{false}; + + [[nodiscard]] + static bool quit() + { + auto lock = std::unique_lock{mMsgLock}; + mMsgCond.wait(lock, []{return mQuit;}); + return mQuit; + } + +public: + static void messageHandler(std::promise *promise); }; +/* Manages a DeviceEnumHelper on its own thread, to track available devices. */ +void DeviceEnumHelper::messageHandler(std::promise *promise) +{ + TRACE("Starting watcher thread"); + + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) + { + WARN("Failed to initialize COM: {:#x}", as_unsigned(com.status())); + promise->set_value(com.status()); + return; + } + + auto helper = std::optional{}; + try { + auto devlock = DeviceListLock{gDeviceList}; + + auto hr = helper.emplace().init(); + promise->set_value(hr); + promise = nullptr; + if(FAILED(hr)) + return; + + auto defaultId = helper->probeDevices(eRender, devlock.getPlaybackList()); + if(!defaultId.empty()) devlock.setPlaybackDefaultId(defaultId); + + defaultId = helper->probeDevices(eCapture, devlock.getCaptureList()); + if(!defaultId.empty()) devlock.setCaptureDefaultId(defaultId); + + gInitDone.store(true, std::memory_order_relaxed); + } + catch(std::exception &e) { + ERR("Exception probing devices: {}", e.what()); + if(promise) + promise->set_value(E_FAIL); + return; + } + + TRACE("Watcher thread started"); + gInitCV.notify_all(); + + while(!quit()) { + /* Do nothing. */ + } +} + + +#if ALSOFT_UWP +struct DeviceHelper final : public IActivateAudioInterfaceCompletionHandler { + DeviceHelper() + { + mActiveClientEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + } + ~DeviceHelper() + { + if(mActiveClientEvent != nullptr) + CloseHandle(mActiveClientEvent); + mActiveClientEvent = nullptr; + } + + template + auto as() noexcept -> T { return T{this}; } + + /** -------------------------- IUnknown ----------------------------- */ + std::atomic mRefCount{1}; + STDMETHODIMP_(ULONG) AddRef() noexcept override { return mRefCount.fetch_add(1u) + 1u; } + STDMETHODIMP_(ULONG) Release() noexcept override { return mRefCount.fetch_sub(1u) - 1u; } + + STDMETHODIMP QueryInterface(const IID& IId, void **UnknownPtrPtr) noexcept override + { + // Three rules of QueryInterface: + // https://docs.microsoft.com/en-us/windows/win32/com/rules-for-implementing-queryinterface + // 1. Objects must have identity. + // 2. The set of interfaces on an object instance must be static. + // 3. It must be possible to query successfully for any interface on an object from any other interface. + + // If ppvObject(the address) is nullptr, then this method returns E_POINTER. + if(!UnknownPtrPtr) + return E_POINTER; + + if(IId == __uuidof(IActivateAudioInterfaceCompletionHandler)) + { + *UnknownPtrPtr = as(); + AddRef(); + return S_OK; + } + else if(IId == __uuidof(IAgileObject) || IId == __uuidof(IUnknown)) + { + *UnknownPtrPtr = as(); + AddRef(); + return S_OK; + } + + // This method returns S_OK if the interface is supported, and E_NOINTERFACE otherwise. + *UnknownPtrPtr = nullptr; + return E_NOINTERFACE; + } + + /** ----------------------- IActivateAudioInterfaceCompletionHandler ------------ */ + HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation*) override + { + SetEvent(mActiveClientEvent); + + // Need to return S_OK + return S_OK; + } + + /** -------------------------- DeviceHelper ----------------------------- */ + [[nodiscard]] constexpr + auto init() -> HRESULT { return S_OK; } + + [[nodiscard]] + auto openDevice(const std::wstring &devid, EDataFlow flow, DeviceHandle &device) -> HRESULT + { + const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default; + auto devIfPath = + devid.empty() ? (flow == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) + : MediaDevice::GetDefaultAudioCaptureId(deviceRole)) + : winrt::hstring(devid.c_str()); + if (devIfPath.empty()) + return E_POINTER; + + auto&& deviceInfo = DeviceInformation::CreateFromIdAsync(devIfPath, nullptr, + DeviceInformationKind::DeviceInterface).get(); + if(!deviceInfo) + return E_NOINTERFACE; + device = deviceInfo; + return S_OK; + } + + [[nodiscard]] + auto activateAudioClient(_In_ DeviceHandle &device, _In_ REFIID iid, void **ppv) -> HRESULT + { + ComPtr asyncOp; + HRESULT hr{ActivateAudioInterfaceAsync(device.Id().data(), iid, nullptr, this, + al::out_ptr(asyncOp))}; + if(FAILED(hr)) + return hr; + + /* I don't like waiting for INFINITE time, but the activate operation + * can take an indefinite amount of time since it can require user + * input. + */ + DWORD res{WaitForSingleObjectEx(mActiveClientEvent, INFINITE, FALSE)}; + if(res != WAIT_OBJECT_0) + { + ERR("WaitForSingleObjectEx error: {:#x}", res); + return E_FAIL; + } + + HRESULT hrActivateRes{E_FAIL}; + ComPtr punkAudioIface; + hr = asyncOp->GetActivateResult(&hrActivateRes, al::out_ptr(punkAudioIface)); + if(SUCCEEDED(hr)) hr = hrActivateRes; + if(FAILED(hr)) return hr; + + return punkAudioIface->QueryInterface(iid, ppv); + } + + HANDLE mActiveClientEvent{nullptr}; +}; + +#else + +struct DeviceHelper { + DeviceHelper() = default; + ~DeviceHelper() = default; + + [[nodiscard]] + auto init() -> HRESULT + { + HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), al::out_ptr(mEnumerator))}; + if(FAILED(hr)) + WARN("Failed to create IMMDeviceEnumerator instance: {:#x}", as_unsigned(hr)); + return hr; + } + + [[nodiscard]] + auto openDevice(const std::wstring &devid, EDataFlow flow, DeviceHandle &device) const + -> HRESULT + { + HRESULT hr{E_FAIL}; + if(mEnumerator) + { + if(devid.empty()) + hr = mEnumerator->GetDefaultAudioEndpoint(flow, eMultimedia, al::out_ptr(device)); + else + hr = mEnumerator->GetDevice(devid.c_str(), al::out_ptr(device)); + } + return hr; + } + + [[nodiscard]] + static auto activateAudioClient(_In_ DeviceHandle &device, REFIID iid, void **ppv) -> HRESULT + { + if(iid == __uuidof(IAudioClient)) + { + /* Always (try) to activate an IAudioClient3, even if giving back + * an IAudioClient iface. This may(?) offer more features even if + * not using its new methods. + */ + auto ac3 = ComPtr{}; + const auto hr = device->Activate(__uuidof(IAudioClient3), CLSCTX_INPROC_SERVER, + nullptr, al::out_ptr(ac3)); + if(SUCCEEDED(hr)) + return ac3->QueryInterface(iid, ppv); + } + return device->Activate(iid, CLSCTX_INPROC_SERVER, nullptr, ppv); + } + + ComPtr mEnumerator{nullptr}; +}; +#endif + + bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) { *out = WAVEFORMATEXTENSIBLE{}; @@ -850,7 +1051,7 @@ bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) else if(out->Format.nChannels == 2) out->dwChannelMask = STEREO; else - ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels); + ERR("Unhandled PCM channel count: {}", out->Format.nChannels); out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; } else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) @@ -864,252 +1065,109 @@ bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) else if(out->Format.nChannels == 2) out->dwChannelMask = STEREO; else - ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels); + ERR("Unhandled IEEE float channel count: {}", out->Format.nChannels); out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; } else { - ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag); + ERR("Unhandled format tag: {:#06x}", in->wFormatTag); return false; } return true; } -void TraceFormat(const char *msg, const WAVEFORMATEX *format) +void TraceFormat(const std::string_view msg, const WAVEFORMATEX *format) { constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)}; if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size) { - const WAVEFORMATEXTENSIBLE *fmtex{ - CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)}; + const auto *fmtex = CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format); /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */ - TRACE("%s:\n" - " FormatTag = 0x%04x\n" - " Channels = %d\n" - " SamplesPerSec = %lu\n" - " AvgBytesPerSec = %lu\n" - " BlockAlign = %d\n" - " BitsPerSample = %d\n" - " Size = %d\n" - " Samples = %d\n" - " ChannelMask = 0x%lx\n" - " SubFormat = %s\n", + TRACE("{}:\n" + " FormatTag = {:#06x}\n" + " Channels = {}\n" + " SamplesPerSec = {}\n" + " AvgBytesPerSec = {}\n" + " BlockAlign = {}\n" + " BitsPerSample = {}\n" + " Size = {}\n" + " Samples = {}\n" + " ChannelMask = {:#x}\n" + " SubFormat = {}", msg, fmtex->Format.wFormatTag, fmtex->Format.nChannels, fmtex->Format.nSamplesPerSec, fmtex->Format.nAvgBytesPerSec, fmtex->Format.nBlockAlign, fmtex->Format.wBitsPerSample, fmtex->Format.cbSize, fmtex->Samples.wReserved, fmtex->dwChannelMask, - GuidPrinter{fmtex->SubFormat}.c_str()); + GuidPrinter{fmtex->SubFormat}.str()); /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */ } else - TRACE("%s:\n" - " FormatTag = 0x%04x\n" - " Channels = %d\n" - " SamplesPerSec = %lu\n" - " AvgBytesPerSec = %lu\n" - " BlockAlign = %d\n" - " BitsPerSample = %d\n" - " Size = %d\n", + TRACE("{}:\n" + " FormatTag = {:#06x}\n" + " Channels = {}\n" + " SamplesPerSec = {}\n" + " AvgBytesPerSec = {}\n" + " BlockAlign = {}\n" + " BitsPerSample = {}\n" + " Size = {}", msg, format->wFormatTag, format->nChannels, format->nSamplesPerSec, format->nAvgBytesPerSec, format->nBlockAlign, format->wBitsPerSample, format->cbSize); } -enum class MsgType { - OpenDevice, - ResetDevice, - StartDevice, - StopDevice, - CloseDevice, - - QuitThread -}; - -constexpr const char *GetMessageTypeName(MsgType type) noexcept +/* Duplicates the first sample of each sample frame to the second sample, at + * half volume. Essentially converting mono to stereo. + */ +template +void DuplicateSamples(al::span insamples, size_t step) { - switch(type) + auto samples = al::span{reinterpret_cast(insamples.data()), insamples.size()/sizeof(T)}; + if constexpr(std::is_floating_point_v) { - case MsgType::OpenDevice: return "Open Device"; - case MsgType::ResetDevice: return "Reset Device"; - case MsgType::StartDevice: return "Start Device"; - case MsgType::StopDevice: return "Stop Device"; - case MsgType::CloseDevice: return "Close Device"; - case MsgType::QuitThread: break; + for(size_t i{0};i < samples.size();i+=step) + { + const auto s = samples[i] * T{0.5}; + samples[i+1] = samples[i] = s; + } + } + else if constexpr(std::is_signed_v) + { + for(size_t i{0};i < samples.size();i+=step) + { + const auto s = samples[i] / 2; + samples[i+1] = samples[i] = T(s); + } + } + else + { + using ST = std::make_signed_t; + static constexpr auto SignBit = T{1u << (sizeof(T)*8 - 1)}; + for(size_t i{0};i < samples.size();i+=step) + { + const auto s = static_cast(samples[i]^SignBit) / 2; + samples[i+1] = samples[i] = T(s)^SignBit; + } + } +} + +void DuplicateSamples(al::span insamples, DevFmtType sampletype, size_t step) +{ + switch(sampletype) + { + case DevFmtByte: return DuplicateSamples(insamples, step); + case DevFmtUByte: return DuplicateSamples(insamples, step); + case DevFmtShort: return DuplicateSamples(insamples, step); + case DevFmtUShort: return DuplicateSamples(insamples, step); + case DevFmtInt: return DuplicateSamples(insamples, step); + case DevFmtUInt: return DuplicateSamples(insamples, step); + case DevFmtFloat: return DuplicateSamples(insamples, step); } - return ""; } -/* Proxy interface used by the message handler. */ -struct WasapiProxy { - WasapiProxy() = default; - WasapiProxy(const WasapiProxy&) = delete; - WasapiProxy(WasapiProxy&&) = delete; - virtual ~WasapiProxy() = default; - - void operator=(const WasapiProxy&) = delete; - void operator=(WasapiProxy&&) = delete; - - virtual HRESULT openProxy(std::string_view name) = 0; - virtual void closeProxy() = 0; - - virtual HRESULT resetProxy() = 0; - virtual HRESULT startProxy() = 0; - virtual void stopProxy() = 0; - - struct Msg { - MsgType mType; - WasapiProxy *mProxy; - std::string_view mParam; - std::promise mPromise; - - explicit operator bool() const noexcept { return mType != MsgType::QuitThread; } - }; - static inline std::deque mMsgQueue; - static inline std::mutex mMsgQueueLock; - static inline std::condition_variable mMsgQueueCond; - - static inline std::optional sDeviceHelper; - - std::future pushMessage(MsgType type, std::string_view param={}) - { - std::promise promise; - std::future future{promise.get_future()}; - { - std::lock_guard msglock{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)}); - } - mMsgQueueCond.notify_one(); - return future; - } - - static std::future pushMessageStatic(MsgType type) - { - std::promise promise; - std::future future{promise.get_future()}; - { - std::lock_guard msglock{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, nullptr, {}, std::move(promise)}); - } - mMsgQueueCond.notify_one(); - return future; - } - - static Msg popMessage() - { - std::unique_lock lock{mMsgQueueLock}; - mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();}); - Msg msg{std::move(mMsgQueue.front())}; - mMsgQueue.pop_front(); - return msg; - } - - static int messageHandler(std::promise *promise); -}; - -int WasapiProxy::messageHandler(std::promise *promise) -{ - TRACE("Starting message thread\n"); - - ComWrapper com{COINIT_MULTITHREADED}; - if(!com) - { - WARN("Failed to initialize COM: 0x%08lx\n", com.status()); - promise->set_value(com.status()); - return 0; - } - - struct HelperResetter { - ~HelperResetter() { sDeviceHelper.reset(); } - }; - HelperResetter scoped_watcher; - - HRESULT hr{sDeviceHelper.emplace().init()}; - promise->set_value(hr); - promise = nullptr; - if(FAILED(hr)) - return 0; - - { - auto devlock = DeviceListLock{gDeviceList}; - auto defaultId = sDeviceHelper->probeDevices(eRender, devlock.getPlaybackList()); - if(!defaultId.empty()) devlock.setPlaybackDefaultId(defaultId); - defaultId = sDeviceHelper->probeDevices(eCapture, devlock.getCaptureList()); - if(!defaultId.empty()) devlock.setCaptureDefaultId(defaultId); - } - - TRACE("Starting message loop\n"); - while(Msg msg{popMessage()}) - { - TRACE("Got message \"%s\" (0x%04x, this=%p, param=\"%.*s\")\n", - GetMessageTypeName(msg.mType), static_cast(msg.mType), - static_cast(msg.mProxy), al::sizei(msg.mParam), msg.mParam.data()); - - switch(msg.mType) - { - case MsgType::OpenDevice: - hr = msg.mProxy->openProxy(msg.mParam); - msg.mPromise.set_value(hr); - continue; - - case MsgType::ResetDevice: - hr = msg.mProxy->resetProxy(); - msg.mPromise.set_value(hr); - continue; - - case MsgType::StartDevice: - hr = msg.mProxy->startProxy(); - msg.mPromise.set_value(hr); - continue; - - case MsgType::StopDevice: - msg.mProxy->stopProxy(); - msg.mPromise.set_value(S_OK); - continue; - - case MsgType::CloseDevice: - msg.mProxy->closeProxy(); - msg.mPromise.set_value(S_OK); - continue; - - case MsgType::QuitThread: - break; - } - ERR("Unexpected message: %u\n", static_cast(msg.mType)); - msg.mPromise.set_value(E_FAIL); - } - TRACE("Message loop finished\n"); - - return 0; -} - -struct WasapiPlayback final : public BackendBase, WasapiProxy { - WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { } +struct WasapiPlayback final : public BackendBase { + explicit WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~WasapiPlayback() override; - int mixerProc(); - int mixerSpatialProc(); - - void open(std::string_view name) override; - HRESULT openProxy(std::string_view name) override; - void closeProxy() override; - - bool reset() override; - HRESULT resetProxy() override; - void start() override; - HRESULT startProxy() override; - void stop() override; - void stopProxy() override; - - ClockLatency getClockLatency() override; - - void prepareFormat(WAVEFORMATEXTENSIBLE &OutputType); - void finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType); - - auto initSpatial() -> bool; - - HRESULT mOpenStatus{E_FAIL}; - DeviceHandle mMMDev{nullptr}; - struct PlainDevice { ComPtr mClient{nullptr}; ComPtr mRender{nullptr}; @@ -1119,28 +1177,80 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { ComPtr mRender{nullptr}; AudioObjectType mStaticMask{}; }; - std::variant mAudio; + + void mixerProc(PlainDevice &audio); + void mixerProc(SpatialDevice &audio); + + auto openProxy(const std::string_view name, DeviceHelper &helper, DeviceHandle &mmdev) + -> HRESULT; + void finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType); + auto initSpatial(DeviceHelper &helper, DeviceHandle &mmdev, SpatialDevice &audio) -> bool; + auto resetProxy(DeviceHelper &helper, DeviceHandle &mmdev, + std::variant &audiodev) -> HRESULT; + + void proc_thread(std::string&& name); + + + void open(std::string_view name) override; + bool reset() override; + void start() override; + void stop() override; + + ClockLatency getClockLatency() override; + + + std::thread mProcThread; + std::mutex mProcMutex; + std::condition_variable mProcCond; + HRESULT mProcResult{E_FAIL}; + + enum class ThreadState : uint8_t { + Initializing, + Waiting, + Playing, + Done + }; + ThreadState mState{ThreadState::Initializing}; + + enum class ThreadAction : uint8_t { + Nothing, + Configure, + Play, + Quit + }; + ThreadAction mAction{ThreadAction::Nothing}; + + + static inline DWORD sAvIndex{}; + HANDLE mNotifyEvent{nullptr}; - UINT32 mOrigBufferSize{}, mOrigUpdateSize{}; - std::vector mResampleBuffer{}; + UINT32 mOutBufferSize{}, mOutUpdateSize{}; + std::vector mResampleBuffer; uint mBufferFilled{0}; SampleConverterPtr mResampler; + bool mMonoUpsample{false}; + bool mExclusiveMode{false}; WAVEFORMATEXTENSIBLE mFormat{}; std::atomic mPadding{0u}; std::mutex mMutex; - std::atomic mKillNow{true}; - std::thread mThread; }; WasapiPlayback::~WasapiPlayback() { - if(SUCCEEDED(mOpenStatus)) - pushMessage(MsgType::CloseDevice).wait(); - mOpenStatus = E_FAIL; + if(mProcThread.joinable()) + { + { + auto plock = std::lock_guard{mProcMutex}; + mKillNow = true; + mAction = ThreadAction::Quit; + } + mProcCond.notify_all(); + mProcThread.join(); + } if(mNotifyEvent != nullptr) CloseHandle(mNotifyEvent); @@ -1148,152 +1258,182 @@ WasapiPlayback::~WasapiPlayback() } -FORCE_ALIGN int WasapiPlayback::mixerProc() +FORCE_ALIGN void WasapiPlayback::mixerProc(PlainDevice &audio) { - ComWrapper com{COINIT_MULTITHREADED}; - if(!com) - { - ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); - mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); - return 1; - } + class PriorityControl { + const int mOldPriority; - auto &audio = std::get(mAudio); - - SetRTPriority(); - althrd_setname(GetMixerThreadName()); + public: + PriorityControl() : mOldPriority{GetThreadPriority(GetCurrentThread())} + { + if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) + ERR("Failed to set priority level for thread"); + } + ~PriorityControl() + { SetThreadPriority(GetCurrentThread(), mOldPriority); } + }; + auto prioctrl = PriorityControl{}; const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u}; - const uint update_size{mOrigUpdateSize}; - const UINT32 buffer_len{mOrigBufferSize}; + const UINT32 buffer_len{mOutBufferSize}; const void *resbufferptr{}; + assert(buffer_len > 0); + +#ifdef AVRTAPI + /* TODO: "Audio" or "Pro Audio"? The suggestion is to use "Pro Audio" for + * device periods less than 10ms, and "Audio" for greater than or equal to + * 10ms. + */ + auto taskname = (mOutUpdateSize < mFormat.Format.nSamplesPerSec/100) ? L"Pro Audio" : L"Audio"; + auto avhandle = AvrtHandlePtr{AvSetMmThreadCharacteristicsW(taskname, &sAvIndex)}; +#endif + + auto prefilling = true; mBufferFilled = 0; while(!mKillNow.load(std::memory_order_relaxed)) { - UINT32 written; - HRESULT hr{audio.mClient->GetCurrentPadding(&written)}; - if(FAILED(hr)) + /* For exclusive mode, assume buffer_len sample frames are writable. + * The first pass will be a prefill of the buffer, while subsequent + * passes will only occur after notify events. + * IAudioClient::GetCurrentPadding shouldn't be used with exclusive + * streams that use event notifications, according to the docs, we + * should just assume a buffer length is writable after notification. + */ + auto written = UINT32{}; + if(!mExclusiveMode) { - ERR("Failed to get padding: 0x%08lx\n", hr); - mDevice->handleDisconnect("Failed to retrieve buffer padding: 0x%08lx", hr); - break; - } - mPadding.store(written, std::memory_order_relaxed); - - uint len{buffer_len - written}; - if(len < update_size) - { - DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; - if(res != WAIT_OBJECT_0) - ERR("WaitForSingleObjectEx error: 0x%lx\n", res); - continue; - } - - BYTE *buffer; - hr = audio.mRender->GetBuffer(len, &buffer); - if(SUCCEEDED(hr)) - { - if(mResampler) + if(auto hr = audio.mClient->GetCurrentPadding(&written); FAILED(hr)) { - std::lock_guard dlock{mMutex}; - auto dst = al::span{buffer, size_t{len}*frame_size}; - for(UINT32 done{0};done < len;) + ERR("Failed to get padding: {:#x}", as_unsigned(hr)); + mDevice->handleDisconnect("Failed to retrieve buffer padding: {:#x}", + as_unsigned(hr)); + break; + } + mPadding.store(written, std::memory_order_relaxed); + } + + if(const auto len = uint{buffer_len - written}) + { + auto buffer = LPBYTE{}; + auto hr = audio.mRender->GetBuffer(len, &buffer); + if(SUCCEEDED(hr)) + { + if(mResampler) { - if(mBufferFilled == 0) + auto dlock = std::lock_guard{mMutex}; + auto dst = al::span{buffer, size_t{len}*frame_size}; + for(UINT32 done{0};done < len;) { - mDevice->renderSamples(mResampleBuffer.data(), mDevice->UpdateSize, - mFormat.Format.nChannels); - resbufferptr = mResampleBuffer.data(); - mBufferFilled = mDevice->UpdateSize; + if(mBufferFilled == 0) + { + mDevice->renderSamples(mResampleBuffer.data(), mDevice->mUpdateSize, + mFormat.Format.nChannels); + resbufferptr = mResampleBuffer.data(); + mBufferFilled = mDevice->mUpdateSize; + } + + const auto got = mResampler->convert(&resbufferptr, &mBufferFilled, + dst.data(), len-done); + dst = dst.subspan(size_t{got}*frame_size); + done += got; } - - uint got{mResampler->convert(&resbufferptr, &mBufferFilled, dst.data(), - len-done)}; - dst = dst.subspan(size_t{got}*frame_size); - done += got; - - mPadding.store(written + done, std::memory_order_relaxed); + mPadding.store(written + len, std::memory_order_relaxed); } + else + { + auto dlock = std::lock_guard{mMutex}; + mDevice->renderSamples(buffer, len, mFormat.Format.nChannels); + mPadding.store(written + len, std::memory_order_relaxed); + } + + if(mMonoUpsample) + { + DuplicateSamples(al::span{buffer, size_t{len}*frame_size}, mDevice->FmtType, + mFormat.Format.nChannels); + } + + hr = audio.mRender->ReleaseBuffer(len, 0); } - else + if(FAILED(hr)) { - std::lock_guard dlock{mMutex}; - mDevice->renderSamples(buffer, len, mFormat.Format.nChannels); - mPadding.store(written + len, std::memory_order_relaxed); + ERR("Failed to buffer data: {:#x}", as_unsigned(hr)); + mDevice->handleDisconnect("Failed to send playback samples: {:#x}", + as_unsigned(hr)); + break; } - hr = audio.mRender->ReleaseBuffer(len, 0); } - if(FAILED(hr)) + + if(prefilling) { - ERR("Failed to buffer data: 0x%08lx\n", hr); - mDevice->handleDisconnect("Failed to send playback samples: 0x%08lx", hr); - break; + prefilling = false; + ResetEvent(mNotifyEvent); + if(auto hr = audio.mClient->Start(); FAILED(hr)) + { + ERR("Failed to start audio client: {:#x}", as_unsigned(hr)); + mDevice->handleDisconnect("Failed to start audio client: {:#x}", + as_unsigned(hr)); + break; + } } + + if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; res != WAIT_OBJECT_0) + ERR("WaitForSingleObjectEx error: {:#x}", res); } mPadding.store(0u, std::memory_order_release); - - return 0; + audio.mClient->Stop(); } -FORCE_ALIGN int WasapiPlayback::mixerSpatialProc() +FORCE_ALIGN void WasapiPlayback::mixerProc(SpatialDevice &audio) { - ComWrapper com{COINIT_MULTITHREADED}; - if(!com) - { - ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); - mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); - return 1; - } + class PriorityControl { + int mOldPriority; + public: + PriorityControl() : mOldPriority{GetThreadPriority(GetCurrentThread())} + { + if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) + ERR("Failed to set priority level for thread"); + } + ~PriorityControl() + { SetThreadPriority(GetCurrentThread(), mOldPriority); } + }; + auto prioctrl = PriorityControl{}; - auto &audio = std::get(mAudio); - - SetRTPriority(); - althrd_setname(GetMixerThreadName()); +#ifdef AVRTAPI + auto taskname = (mOutUpdateSize < mFormat.Format.nSamplesPerSec/100) ? L"Pro Audio" : L"Audio"; + auto avhandle = AvrtHandlePtr{AvSetMmThreadCharacteristicsW(taskname, &sAvIndex)}; +#endif std::vector> channels; - std::vector buffers; - std::vector resbuffers; + std::vector buffers; + std::vector resbuffers; std::vector tmpbuffers; /* TODO: Set mPadding appropriately. There doesn't seem to be a way to * update it dynamically based on the stream, so a fixed size may be the * best we can do. */ - mPadding.store(mOrigBufferSize-mOrigUpdateSize, std::memory_order_release); + mPadding.store(mOutBufferSize-mOutUpdateSize, std::memory_order_release); mBufferFilled = 0; + auto firstupdate = true; while(!mKillNow.load(std::memory_order_relaxed)) { - if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 1000, FALSE)}; res != WAIT_OBJECT_0) - { - ERR("WaitForSingleObjectEx error: 0x%lx\n", res); - - HRESULT hr{audio.mRender->Reset()}; - if(FAILED(hr)) - { - ERR("ISpatialAudioObjectRenderStream::Reset failed: 0x%08lx\n", hr); - mDevice->handleDisconnect("Device lost: 0x%08lx", hr); - break; - } - } - UINT32 dynamicCount{}, framesToDo{}; HRESULT hr{audio.mRender->BeginUpdatingAudioObjects(&dynamicCount, &framesToDo)}; if(SUCCEEDED(hr)) { if(channels.empty()) UNLIKELY { - auto flags = as_unsigned(audio.mStaticMask); + auto flags = as_unsigned(al::to_underlying(audio.mStaticMask)); channels.reserve(as_unsigned(al::popcount(flags))); while(flags) { auto id = decltype(flags){1} << al::countr_zero(flags); flags &= ~id; - channels.emplace_back(); audio.mRender->ActivateSpatialAudioObject(static_cast(id), - al::out_ptr(channels.back())); + al::out_ptr(channels.emplace_back())); } buffers.resize(channels.size()); if(mResampler) @@ -1303,8 +1443,8 @@ FORCE_ALIGN int WasapiPlayback::mixerSpatialProc() auto bufptr = mResampleBuffer.begin(); for(size_t i{0};i < tmpbuffers.size();++i) { - resbuffers[i] = reinterpret_cast(al::to_address(bufptr)); - bufptr += ptrdiff_t(mDevice->UpdateSize*sizeof(float)); + resbuffers[i] = al::to_address(bufptr); + bufptr += ptrdiff_t(mDevice->mUpdateSize*sizeof(float)); } } } @@ -1313,12 +1453,12 @@ FORCE_ALIGN int WasapiPlayback::mixerSpatialProc() * update, unfortunately. */ std::transform(channels.cbegin(), channels.cend(), buffers.begin(), - [](const ComPtr &obj) -> float* + [](const ComPtr &obj) -> void* { - BYTE *buffer{}; - UINT32 size{}; + auto buffer = LPBYTE{}; + auto size = UINT32{}; obj->GetBuffer(&buffer, &size); - return reinterpret_cast(buffer); + return buffer; }); if(!mResampler) @@ -1330,15 +1470,15 @@ FORCE_ALIGN int WasapiPlayback::mixerSpatialProc() { if(mBufferFilled == 0) { - mDevice->renderSamples(resbuffers, mDevice->UpdateSize); + mDevice->renderSamples(resbuffers, mDevice->mUpdateSize); std::copy(resbuffers.cbegin(), resbuffers.cend(), tmpbuffers.begin()); - mBufferFilled = mDevice->UpdateSize; + mBufferFilled = mDevice->mUpdateSize; } const uint got{mResampler->convertPlanar(tmpbuffers.data(), &mBufferFilled, - reinterpret_cast(buffers.data()), framesToDo-pos)}; + buffers.data(), framesToDo-pos)}; for(auto &buf : buffers) - buf += got; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ + buf = static_cast(buf) + got; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ pos += got; } } @@ -1346,212 +1486,217 @@ FORCE_ALIGN int WasapiPlayback::mixerSpatialProc() hr = audio.mRender->EndUpdatingAudioObjects(); } + if(firstupdate) + { + firstupdate = false; + ResetEvent(mNotifyEvent); + hr = audio.mRender->Start(); + if(FAILED(hr)) + { + ERR("Failed to start spatial audio stream: {:#x}", as_unsigned(hr)); + mDevice->handleDisconnect("Failed to start spatial audio stream: {:#x}", + as_unsigned(hr)); + return; + } + } + + if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 1000, FALSE)}; res != WAIT_OBJECT_0) + { + ERR("WaitForSingleObjectEx error: {:#x}", res); + + hr = audio.mRender->Reset(); + if(FAILED(hr)) + { + ERR("ISpatialAudioObjectRenderStream::Reset failed: {:#x}", as_unsigned(hr)); + mDevice->handleDisconnect("Device lost: {:#x}", as_unsigned(hr)); + break; + } + firstupdate = true; + } + if(FAILED(hr)) - ERR("Failed to update playback objects: 0x%08lx\n", hr); + ERR("Failed to update playback objects: {:#x}", as_unsigned(hr)); } mPadding.store(0u, std::memory_order_release); + audio.mRender->Stop(); + audio.mRender->Reset(); +} - return 0; + +void WasapiPlayback::proc_thread(std::string&& name) +try { + auto com = ComWrapper{COINIT_MULTITHREADED}; + if(!com) + { + const auto hr = as_unsigned(com.status()); + ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: {:#x}", hr); + mDevice->handleDisconnect("COM init failed: {:#x}", hr); + + auto plock = std::lock_guard{mProcMutex}; + mProcResult = com.status(); + mState = ThreadState::Done; + mProcCond.notify_all(); + return; + } + + if(!gInitDone.load(std::memory_order_relaxed)) + { + auto devlock = DeviceListLock{gDeviceList}; + gInitCV.wait(devlock, []() -> bool { return gInitDone; }); + } + + auto helper = DeviceHelper{}; + if(HRESULT hr{helper.init()}; FAILED(hr)) + { + mDevice->handleDisconnect("Helper init failed: {:#x}", as_unsigned(hr)); + + auto plock = std::lock_guard{mProcMutex}; + mProcResult = hr; + mState = ThreadState::Done; + mProcCond.notify_all(); + return; + } + + althrd_setname(GetMixerThreadName()); + + auto mmdev = DeviceHandle{nullptr}; + if(auto hr = openProxy(name, helper, mmdev); FAILED(hr)) + { + auto plock = std::lock_guard{mProcMutex}; + mProcResult = hr; + mState = ThreadState::Done; + mProcCond.notify_all(); + return; + } + + auto audiodev = std::variant{}; + + auto plock = std::unique_lock{mProcMutex}; + mProcResult = S_OK; + while(mState != ThreadState::Done) + { + mAction = ThreadAction::Nothing; + mState = ThreadState::Waiting; + mProcCond.notify_all(); + + mProcCond.wait(plock, [this]() noexcept { return mAction != ThreadAction::Nothing; }); + switch(mAction) + { + case ThreadAction::Nothing: + break; + + case ThreadAction::Configure: + { + plock.unlock(); + const auto hr = resetProxy(helper, mmdev, audiodev); + plock.lock(); + mProcResult = hr; + } + break; + + case ThreadAction::Play: + mKillNow.store(false, std::memory_order_release); + + mAction = ThreadAction::Nothing; + mState = ThreadState::Playing; + mProcResult = S_OK; + plock.unlock(); + mProcCond.notify_all(); + + std::visit([this](auto &audio) -> void { mixerProc(audio); }, audiodev); + + plock.lock(); + break; + + case ThreadAction::Quit: + mAction = ThreadAction::Nothing; + mState = ThreadState::Done; + mProcCond.notify_all(); + break; + } + } +} +catch(...) { + auto plock = std::lock_guard{mProcMutex}; + mProcResult = E_FAIL; + mAction = ThreadAction::Nothing; + mState = ThreadState::Done; + mProcCond.notify_all(); } void WasapiPlayback::open(std::string_view name) { - 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 events: %lu\n", GetLastError()); + ERR("Failed to create notify events: {}", GetLastError()); throw al::backend_exception{al::backend_error::DeviceError, "Failed to create notify events"}; } - if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix)) - name = name.substr(prefix.size()); + mProcThread = std::thread{&WasapiPlayback::proc_thread, this, std::string{name}}; - mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); - if(FAILED(mOpenStatus)) - throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", - mOpenStatus}; + auto plock = std::unique_lock{mProcMutex}; + mProcCond.wait(plock, [this]() noexcept { return mState != ThreadState::Initializing; }); + + if(mProcResult == E_NOTFOUND) + throw al::backend_exception{al::backend_error::NoDevice, "Device \"{}\" not found", name}; + if(FAILED(mProcResult) || mState == ThreadState::Done) + throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}", + as_unsigned(mProcResult)}; } -HRESULT WasapiPlayback::openProxy(std::string_view name) +auto WasapiPlayback::openProxy(const std::string_view name, DeviceHelper &helper, + DeviceHandle &mmdev) -> HRESULT { - std::string devname; - std::wstring devid; + auto devname = std::string{}; + auto devid = std::wstring{}; if(!name.empty()) { auto devlock = DeviceListLock{gDeviceList}; auto list = al::span{devlock.getPlaybackList()}; auto iter = std::find_if(list.cbegin(), list.cend(), [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; }); + { return entry.name == name || al::case_compare(entry.endpoint_guid, name) == 0; }); if(iter == list.cend()) { const std::wstring wname{utf8_to_wstr(name)}; iter = std::find_if(list.cbegin(), list.cend(), [&wname](const DevMap &entry) -> bool - { return entry.devid == wname; }); + { return al::case_compare(entry.devid, wname) == 0; }); } if(iter == list.cend()) { - WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data()); - return E_FAIL; + WARN("Failed to find device name matching \"{}\"", name); + return E_NOTFOUND; } devname = iter->name; devid = iter->devid; } - HRESULT hr{sDeviceHelper->openDevice(devid, eRender, mMMDev)}; - if(FAILED(hr)) + if(HRESULT hr{helper.openDevice(devid, eRender, mmdev)}; FAILED(hr)) { - WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str()); + WARN("Failed to open device \"{}\": {:#x}", devname.empty() + ? "(default)"sv : std::string_view{devname}, as_unsigned(hr)); return hr; } if(!devname.empty()) - mDevice->DeviceName = std::string{GetDevicePrefix()}+std::move(devname); + mDeviceName = std::move(devname); else - mDevice->DeviceName = std::string{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev).first; + mDeviceName = GetDeviceNameAndGuid(mmdev).mName; return S_OK; } -void WasapiPlayback::closeProxy() -{ - mAudio.emplace(); - mMMDev = nullptr; -} - - -void WasapiPlayback::prepareFormat(WAVEFORMATEXTENSIBLE &OutputType) -{ - bool isRear51{false}; - - if(!mDevice->Flags.test(FrequencyRequest)) - mDevice->Frequency = OutputType.Format.nSamplesPerSec; - if(!mDevice->Flags.test(ChannelsRequest)) - { - /* If not requesting a channel configuration, auto-select given what - * fits the mask's lsb (to ensure no gaps in the output channels). If - * there's no mask, we can only assume mono or stereo. - */ - const uint32_t chancount{OutputType.Format.nChannels}; - const DWORD chanmask{OutputType.dwChannelMask}; - if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) - mDevice->FmtChans = DevFmtX714; - else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) - mDevice->FmtChans = DevFmtX71; - else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) - mDevice->FmtChans = DevFmtX61; - else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1) - mDevice->FmtChans = DevFmtX51; - else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR) - { - mDevice->FmtChans = DevFmtX51; - isRear51 = true; - } - else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) - mDevice->FmtChans = DevFmtStereo; - else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)) - mDevice->FmtChans = DevFmtMono; - else - ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask); - } - else - { - const uint32_t chancount{OutputType.Format.nChannels}; - const DWORD chanmask{OutputType.dwChannelMask}; - isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR); - } - - OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - switch(mDevice->FmtChans) - { - case DevFmtMono: - OutputType.Format.nChannels = 1; - OutputType.dwChannelMask = MONO; - break; - case DevFmtAmbi3D: - mDevice->FmtChans = DevFmtStereo; - /*fall-through*/ - case DevFmtStereo: - OutputType.Format.nChannels = 2; - OutputType.dwChannelMask = STEREO; - break; - case DevFmtQuad: - OutputType.Format.nChannels = 4; - OutputType.dwChannelMask = QUAD; - break; - case DevFmtX51: - OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; - break; - case DevFmtX61: - OutputType.Format.nChannels = 7; - OutputType.dwChannelMask = X6DOT1; - break; - case DevFmtX71: - case DevFmtX3D71: - OutputType.Format.nChannels = 8; - OutputType.dwChannelMask = X7DOT1; - break; - case DevFmtX7144: - mDevice->FmtChans = DevFmtX714; - /*fall-through*/ - case DevFmtX714: - OutputType.Format.nChannels = 12; - OutputType.dwChannelMask = X7DOT1DOT4; - break; - } - switch(mDevice->FmtType) - { - case DevFmtByte: - mDevice->FmtType = DevFmtUByte; - /* fall-through */ - case DevFmtUByte: - OutputType.Format.wBitsPerSample = 8; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - OutputType.Format.wBitsPerSample = 16; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - /* fall-through */ - case DevFmtInt: - OutputType.Format.wBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtFloat: - OutputType.Format.wBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - break; - } - /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ - OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; - OutputType.Format.nSamplesPerSec = mDevice->Frequency; - - OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels * - OutputType.Format.wBitsPerSample / 8); - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * - OutputType.Format.nBlockAlign; -} void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType) { - if(!GetConfigValueBool(mDevice->DeviceName, "wasapi", "allow-resampler", true)) - mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec); + if(!GetConfigValueBool(mDevice->mDeviceName, "wasapi", "allow-resampler", true)) + mDevice->mSampleRate = uint(OutputType.Format.nSamplesPerSec); else - mDevice->Frequency = std::min(mDevice->Frequency, uint(OutputType.Format.nSamplesPerSec)); + mDevice->mSampleRate = std::min(mDevice->mSampleRate, + uint(OutputType.Format.nSamplesPerSec)); const uint32_t chancount{OutputType.Format.nChannels}; const DWORD chanmask{OutputType.dwChannelMask}; @@ -1569,6 +1714,12 @@ void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType) { case DevFmtMono: chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)); + if(!chansok && chancount >= 2 && (chanmask&StereoMask) == STEREO) + { + /* Mono rendering with stereo+ output is handled specially. */ + chansok = true; + mMonoUpsample = true; + } break; case DevFmtStereo: chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)); @@ -1613,7 +1764,7 @@ void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType) mDevice->FmtChans = DevFmtMono; else { - ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, + ERR("Unhandled extensible channels: {} -- {:#08x}", OutputType.Format.nChannels, OutputType.dwChannelMask); mDevice->FmtChans = DevFmtStereo; OutputType.Format.nChannels = 2; @@ -1642,7 +1793,7 @@ void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType) } else { - ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str()); + ERR("Unhandled format sub-type: {}", GuidPrinter{OutputType.SubFormat}.str()); mDevice->FmtType = DevFmtShort; if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; @@ -1654,14 +1805,14 @@ void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType) } -auto WasapiPlayback::initSpatial() -> bool +auto WasapiPlayback::initSpatial(DeviceHelper &helper, DeviceHandle &mmdev, SpatialDevice &audio) + -> bool { - auto &audio = mAudio.emplace(); - HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(ISpatialAudioClient), + HRESULT hr{helper.activateAudioClient(mmdev, __uuidof(ISpatialAudioClient), al::out_ptr(audio.mClient))}; if(FAILED(hr)) { - ERR("Failed to activate spatial audio client: 0x%08lx\n", hr); + ERR("Failed to activate spatial audio client: {:#x}", as_unsigned(hr)); return false; } @@ -1669,7 +1820,7 @@ auto WasapiPlayback::initSpatial() -> bool hr = audio.mClient->GetSupportedAudioObjectFormatEnumerator(al::out_ptr(fmtenum)); if(FAILED(hr)) { - ERR("Failed to get format enumerator: 0x%08lx\n", hr); + ERR("Failed to get format enumerator: {:#x}", as_unsigned(hr)); return false; } @@ -1677,7 +1828,7 @@ auto WasapiPlayback::initSpatial() -> bool hr = fmtenum->GetCount(&fmtcount); if(FAILED(hr) || fmtcount == 0) { - ERR("Failed to get format count: 0x%08lx\n", hr); + ERR("Failed to get format count: {:#08x}", as_unsigned(hr)); return false; } @@ -1685,7 +1836,7 @@ auto WasapiPlayback::initSpatial() -> bool hr = fmtenum->GetFormat(0, &preferredFormat); if(FAILED(hr)) { - ERR("Failed to get preferred format: 0x%08lx\n", hr); + ERR("Failed to get preferred format: {:#x}", as_unsigned(hr)); return false; } TraceFormat("Preferred mix format", preferredFormat); @@ -1693,24 +1844,24 @@ auto WasapiPlayback::initSpatial() -> bool UINT32 maxFrames{}; hr = audio.mClient->GetMaxFrameCount(preferredFormat, &maxFrames); if(FAILED(hr)) - ERR("Failed to get max frames: 0x%08lx\n", hr); + ERR("Failed to get max frames: {:#x}", as_unsigned(hr)); else - TRACE("Max sample frames: %u\n", maxFrames); + TRACE("Max sample frames: {}", maxFrames); for(UINT32 i{1};i < fmtcount;++i) { WAVEFORMATEX *otherFormat{}; hr = fmtenum->GetFormat(i, &otherFormat); if(FAILED(hr)) - ERR("Failed to format %u: 0x%08lx\n", i+1, hr); + ERR("Failed to get format {}: {:#x}", i+1, as_unsigned(hr)); else { TraceFormat("Other mix format", otherFormat); UINT32 otherMaxFrames{}; hr = audio.mClient->GetMaxFrameCount(otherFormat, &otherMaxFrames); if(FAILED(hr)) - ERR("Failed to get max frames: 0x%08lx\n", hr); + ERR("Failed to get max frames: {:#x}", as_unsigned(hr)); else - TRACE("Max sample frames: %u\n", otherMaxFrames); + TRACE("Max sample frames: {}", otherMaxFrames); } } @@ -1718,6 +1869,12 @@ auto WasapiPlayback::initSpatial() -> bool if(!MakeExtensible(&OutputType, preferredFormat)) return false; + /* This seems to be the format of each "object", which should be mono. */ + if(!(OutputType.Format.nChannels == 1 + && (OutputType.dwChannelMask == MONO || !OutputType.dwChannelMask))) + ERR("Unhandled channel config: {} -- {:#08x}", OutputType.Format.nChannels, + OutputType.dwChannelMask); + /* Force 32-bit float. This is currently required for planar output. */ if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE && OutputType.Format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT) @@ -1739,53 +1896,18 @@ auto WasapiPlayback::initSpatial() -> bool /* Match the output rate if not requesting anything specific. */ if(!mDevice->Flags.test(FrequencyRequest)) - mDevice->Frequency = OutputType.Format.nSamplesPerSec; + mDevice->mSampleRate = OutputType.Format.nSamplesPerSec; - bool isRear51{false}; - if(!mDevice->Flags.test(ChannelsRequest)) - { - const uint32_t chancount{OutputType.Format.nChannels}; - const DWORD chanmask{OutputType.dwChannelMask}; - if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) - mDevice->FmtChans = DevFmtX714; - else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) - mDevice->FmtChans = DevFmtX71; - else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) - mDevice->FmtChans = DevFmtX61; - else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1) - mDevice->FmtChans = DevFmtX51; - else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR) - { - mDevice->FmtChans = DevFmtX51; - isRear51 = true; - } - else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) - mDevice->FmtChans = DevFmtStereo; - /* HACK: Don't autoselect mono. Wine returns this and makes the audio - * terrible. - */ - else if(!(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))) - ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask); - } - else - { - const uint32_t chancount{OutputType.Format.nChannels}; - const DWORD chanmask{OutputType.dwChannelMask}; - isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR); - } - - auto getTypeMask = [isRear51](DevFmtChannels chans) noexcept + auto getTypeMask = [](DevFmtChannels chans) noexcept { switch(chans) { case DevFmtMono: return ChannelMask_Mono; case DevFmtStereo: return ChannelMask_Stereo; case DevFmtQuad: return ChannelMask_Quad; - case DevFmtX51: return isRear51 ? ChannelMask_X51Rear : ChannelMask_X51; + case DevFmtX51: return ChannelMask_X51; case DevFmtX61: return ChannelMask_X61; - case DevFmtX3D71: + case DevFmtX3D71: [[fallthrough]]; case DevFmtX71: return ChannelMask_X71; case DevFmtX714: return ChannelMask_X714; case DevFmtX7144: return ChannelMask_X7144; @@ -1808,7 +1930,7 @@ auto WasapiPlayback::initSpatial() -> bool __uuidof(ISpatialAudioObjectRenderStream), al::out_ptr(audio.mRender)); if(FAILED(hr)) { - ERR("Failed to activate spatial audio stream: 0x%08lx\n", hr); + ERR("Failed to activate spatial audio stream: {:#x}", as_unsigned(hr)); return false; } @@ -1819,66 +1941,45 @@ auto WasapiPlayback::initSpatial() -> bool mDevice->Flags.reset(DirectEar).set(Virtualization); if(streamParams.StaticObjectTypeMask == ChannelMask_Stereo) mDevice->FmtChans = DevFmtStereo; - if(!GetConfigValueBool(mDevice->DeviceName, "wasapi", "allow-resampler", true)) - mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec); + if(!GetConfigValueBool(mDevice->mDeviceName, "wasapi", "allow-resampler", true)) + mDevice->mSampleRate = uint(OutputType.Format.nSamplesPerSec); else - mDevice->Frequency = std::min(mDevice->Frequency, + mDevice->mSampleRate = std::min(mDevice->mSampleRate, uint(OutputType.Format.nSamplesPerSec)); setDefaultWFXChannelOrder(); - /* FIXME: Get the real update and buffer size. Presumably the actual device - * is configured once ActivateSpatialAudioStream succeeds, and an - * IAudioClient from the same IMMDevice accesses the same device - * configuration. This isn't obviously correct, but for now assume - * IAudioClient::GetDevicePeriod returns the current device period time - * that ISpatialAudioObjectRenderStream will try to wake up at. + /* TODO: ISpatialAudioClient::GetMaxFrameCount returns the maximum number + * of frames per processing pass, which is ostensibly the period size. This + * should be checked on a real Windows system. * - * Unfortunately this won't get the buffer size of the + * In either case, this won't get the buffer size of the * ISpatialAudioObjectRenderStream, so we only assume there's two periods. */ - mOrigUpdateSize = mDevice->UpdateSize; - mOrigBufferSize = mOrigUpdateSize*2; - ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency}; + mOutUpdateSize = maxFrames; + mOutBufferSize = mOutUpdateSize*2; - ComPtr tmpClient; - hr = sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), - al::out_ptr(tmpClient)); - if(FAILED(hr)) - ERR("Failed to activate audio client: 0x%08lx\n", hr); - else - { - hr = tmpClient->GetDevicePeriod(&reinterpret_cast(per_time), nullptr); - if(FAILED(hr)) - ERR("Failed to get device period: 0x%08lx\n", hr); - else - { - mOrigUpdateSize = RefTime2Samples(per_time, mFormat.Format.nSamplesPerSec); - mOrigBufferSize = mOrigUpdateSize*2; - } - } - tmpClient = nullptr; - - mDevice->UpdateSize = RefTime2Samples(per_time, mDevice->Frequency); - mDevice->BufferSize = mDevice->UpdateSize*2; + mDevice->mUpdateSize = static_cast((uint64_t{mOutUpdateSize}*mDevice->mSampleRate + + (mFormat.Format.nSamplesPerSec-1)) / mFormat.Format.nSamplesPerSec); + mDevice->mBufferSize = mDevice->mUpdateSize*2; mResampler = nullptr; mResampleBuffer.clear(); mResampleBuffer.shrink_to_fit(); mBufferFilled = 0; - if(mDevice->Frequency != mFormat.Format.nSamplesPerSec) + if(mDevice->mSampleRate != mFormat.Format.nSamplesPerSec) { - const auto flags = as_unsigned(streamParams.StaticObjectTypeMask); + const auto flags = as_unsigned(al::to_underlying(streamParams.StaticObjectTypeMask)); const auto channelCount = as_unsigned(al::popcount(flags)); mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, channelCount, - mDevice->Frequency, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24); - mResampleBuffer.resize(size_t{mDevice->UpdateSize} * channelCount * + mDevice->mSampleRate, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24); + mResampleBuffer.resize(size_t{mDevice->mUpdateSize} * channelCount * mFormat.Format.wBitsPerSample / 8); - TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n", + TRACE("Created converter for {}/{} format, dst: {}hz ({}), src: {}hz ({})", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency, - mDevice->UpdateSize); + mFormat.Format.nSamplesPerSec, mOutUpdateSize, mDevice->mSampleRate, + mDevice->mUpdateSize); } return true; @@ -1886,145 +1987,353 @@ auto WasapiPlayback::initSpatial() -> bool bool WasapiPlayback::reset() { - HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; - if(FAILED(hr)) - throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr}; + auto plock = std::unique_lock{mProcMutex}; + if(mState != ThreadState::Waiting) + throw al::backend_exception{al::backend_error::DeviceError, "Invalid state: {}", + unsigned{al::to_underlying(mState)}}; + + mAction = ThreadAction::Configure; + mProcCond.notify_all(); + mProcCond.wait(plock, [this]() noexcept { return mAction != ThreadAction::Configure; }); + + if(FAILED(mProcResult) || mState != ThreadState::Waiting) + throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}", + as_unsigned(mProcResult)}; return true; } -HRESULT WasapiPlayback::resetProxy() +auto WasapiPlayback::resetProxy(DeviceHelper &helper, DeviceHandle &mmdev, + std::variant &audiodev) -> HRESULT { - if(GetConfigValueBool(mDevice->DeviceName, "wasapi", "spatial-api", false)) + if(GetConfigValueBool(mDevice->mDeviceName, "wasapi", "spatial-api", false)) { - if(initSpatial()) + if(initSpatial(helper, mmdev, audiodev.emplace())) return S_OK; } mDevice->Flags.reset(Virtualization); + mMonoUpsample = false; + mExclusiveMode = false; - auto &audio = mAudio.emplace(); - HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), - al::out_ptr(audio.mClient))}; + auto &audio = audiodev.emplace(); + auto hr = helper.activateAudioClient(mmdev, __uuidof(IAudioClient), + al::out_ptr(audio.mClient)); if(FAILED(hr)) { - ERR("Failed to reactivate audio client: 0x%08lx\n", hr); + ERR("Failed to reactivate audio client: {:#x}", as_unsigned(hr)); return hr; } - WAVEFORMATEX *wfx; - hr = audio.mClient->GetMixFormat(&wfx); + auto wfx = unique_coptr{}; + hr = audio.mClient->GetMixFormat(al::out_ptr(wfx)); if(FAILED(hr)) { - ERR("Failed to get mix format: 0x%08lx\n", hr); + ERR("Failed to get mix format: {:#x}", as_unsigned(hr)); return hr; } - TraceFormat("Device mix format", wfx); + TraceFormat("Device mix format", wfx.get()); - WAVEFORMATEXTENSIBLE OutputType; - if(!MakeExtensible(&OutputType, wfx)) - { - CoTaskMemFree(wfx); + auto OutputType = WAVEFORMATEXTENSIBLE{}; + if(!MakeExtensible(&OutputType, wfx.get())) return E_FAIL; - } - CoTaskMemFree(wfx); wfx = nullptr; - const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency}; - const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency}; + /* Get the buffer and update sizes as a ReferenceTime before potentially + * altering the sample rate. + */ + const auto buf_time = ReferenceTime{seconds{mDevice->mBufferSize}} / mDevice->mSampleRate; + const auto per_time = ReferenceTime{seconds{mDevice->mUpdateSize}} / mDevice->mSampleRate; - prepareFormat(OutputType); + /* Update the mDevice format for non-requested properties. */ + bool isRear51{false}; + if(!mDevice->Flags.test(FrequencyRequest)) + mDevice->mSampleRate = OutputType.Format.nSamplesPerSec; + if(!mDevice->Flags.test(ChannelsRequest)) + { + /* If not requesting a channel configuration, auto-select given what + * fits the mask's lsb (to ensure no gaps in the output channels). If + * there's no mask, we can only assume mono or stereo. + */ + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) + mDevice->FmtChans = DevFmtX714; + else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) + mDevice->FmtChans = DevFmtX71; + else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) + mDevice->FmtChans = DevFmtX61; + else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1) + mDevice->FmtChans = DevFmtX51; + else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR) + { + mDevice->FmtChans = DevFmtX51; + isRear51 = true; + } + else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) + mDevice->FmtChans = DevFmtQuad; + else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) + mDevice->FmtChans = DevFmtStereo; + else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)) + mDevice->FmtChans = DevFmtMono; + else + ERR("Unhandled channel config: {} -- {:#08x}", chancount, chanmask); + } + else + { + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR); + } + + /* Request a format matching the mDevice. */ + OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + switch(mDevice->FmtChans) + { + case DevFmtMono: + OutputType.Format.nChannels = 1; + OutputType.dwChannelMask = MONO; + break; + case DevFmtAmbi3D: + mDevice->FmtChans = DevFmtStereo; + [[fallthrough]]; + case DevFmtStereo: + OutputType.Format.nChannels = 2; + OutputType.dwChannelMask = STEREO; + break; + case DevFmtQuad: + OutputType.Format.nChannels = 4; + OutputType.dwChannelMask = QUAD; + break; + case DevFmtX51: + OutputType.Format.nChannels = 6; + OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; + break; + case DevFmtX61: + OutputType.Format.nChannels = 7; + OutputType.dwChannelMask = X6DOT1; + break; + case DevFmtX71: + case DevFmtX3D71: + OutputType.Format.nChannels = 8; + OutputType.dwChannelMask = X7DOT1; + break; + case DevFmtX7144: + mDevice->FmtChans = DevFmtX714; + [[fallthrough]]; + case DevFmtX714: + OutputType.Format.nChannels = 12; + OutputType.dwChannelMask = X7DOT1DOT4; + break; + } + switch(mDevice->FmtType) + { + case DevFmtByte: + mDevice->FmtType = DevFmtUByte; + [[fallthrough]]; + case DevFmtUByte: + OutputType.Format.wBitsPerSample = 8; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtUShort: + mDevice->FmtType = DevFmtShort; + [[fallthrough]]; + case DevFmtShort: + OutputType.Format.wBitsPerSample = 16; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtUInt: + mDevice->FmtType = DevFmtInt; + [[fallthrough]]; + case DevFmtInt: + OutputType.Format.wBitsPerSample = 32; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtFloat: + OutputType.Format.wBitsPerSample = 32; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + break; + } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + OutputType.Format.nSamplesPerSec = mDevice->mSampleRate; + OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels + * OutputType.Format.wBitsPerSample / 8); + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec + * OutputType.Format.nBlockAlign; + + const auto sharemode = + GetConfigValueBool(mDevice->mDeviceName, "wasapi", "exclusive-mode", false) + ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED; + mExclusiveMode = (sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE); TraceFormat("Requesting playback format", &OutputType.Format); - hr = audio.mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); + hr = audio.mClient->IsFormatSupported(sharemode, &OutputType.Format, al::out_ptr(wfx)); if(FAILED(hr)) { - WARN("Failed to check format support: 0x%08lx\n", hr); - hr = audio.mClient->GetMixFormat(&wfx); + if(sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) + { + /* For exclusive mode, IAudioClient::IsFormatSupported won't give + * back a supported format. However, a common failure is an + * unsupported sample type, so try a fallback to 16-bit int. + */ + if(hr == AUDCLNT_E_UNSUPPORTED_FORMAT && mDevice->FmtType != DevFmtShort) + { + mDevice->FmtType = DevFmtShort; + + OutputType.Format.wBitsPerSample = 16; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels + * OutputType.Format.wBitsPerSample / 8); + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec + * OutputType.Format.nBlockAlign; + + hr = audio.mClient->IsFormatSupported(sharemode, &OutputType.Format, + al::out_ptr(wfx)); + } + } + else + { + WARN("Failed to check format support: {:#x}", as_unsigned(hr)); + hr = audio.mClient->GetMixFormat(al::out_ptr(wfx)); + } } if(FAILED(hr)) { - ERR("Failed to find a supported format: 0x%08lx\n", hr); + ERR("Failed to find a supported format: {:#x}", as_unsigned(hr)); return hr; } - if(wfx != nullptr) + if(wfx) { - TraceFormat("Got playback format", wfx); - if(!MakeExtensible(&OutputType, wfx)) - { - CoTaskMemFree(wfx); + TraceFormat("Got playback format", wfx.get()); + if(!MakeExtensible(&OutputType, wfx.get())) return E_FAIL; - } - CoTaskMemFree(wfx); wfx = nullptr; finalizeFormat(OutputType); } mFormat = OutputType; -#if !defined(ALSOFT_UWP) - const EndpointFormFactor formfactor{GetDeviceFormfactor(mMMDev.get())}; +#if !ALSOFT_UWP + const EndpointFormFactor formfactor{GetDeviceFormfactor(mmdev.get())}; mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset)); #else mDevice->Flags.set(DirectEar, false); #endif setDefaultWFXChannelOrder(); - hr = audio.mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - buf_time.count(), 0, &OutputType.Format, nullptr); + if(sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) + { + auto period_time = per_time; + auto min_period = ReferenceTime{}; + hr = audio.mClient->GetDevicePeriod(nullptr, + &reinterpret_cast(min_period)); + if(FAILED(hr)) + ERR("Failed to get minimum period time: {:#x}", as_unsigned(hr)); + else if(min_period > period_time) + { + period_time = min_period; + WARN("Clamping to minimum period time, {}", nanoseconds{min_period}); + } + + hr = audio.mClient->Initialize(sharemode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + period_time.count(), period_time.count(), &OutputType.Format, nullptr); + if(hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) + { + auto newsize = UINT32{}; + hr = audio.mClient->GetBufferSize(&newsize); + if(SUCCEEDED(hr)) + { + period_time = ReferenceTime{seconds{newsize}} + / OutputType.Format.nSamplesPerSec; + WARN("Adjusting to supported period time, {}", nanoseconds{period_time}); + + audio.mClient = nullptr; + hr = helper.activateAudioClient(mmdev, __uuidof(IAudioClient), + al::out_ptr(audio.mClient)); + if(FAILED(hr)) + { + ERR("Failed to reactivate audio client: {:#x}", as_unsigned(hr)); + return hr; + } + hr = audio.mClient->Initialize(sharemode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + period_time.count(), period_time.count(), &OutputType.Format, nullptr); + } + } + } + else + hr = audio.mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + buf_time.count(), 0, &OutputType.Format, nullptr); if(FAILED(hr)) { - ERR("Failed to initialize audio client: 0x%08lx\n", hr); + ERR("Failed to initialize audio client: {:#x}", as_unsigned(hr)); return hr; } - UINT32 buffer_len{}; - ReferenceTime min_per{}; - hr = audio.mClient->GetDevicePeriod(&reinterpret_cast(min_per), nullptr); + auto buffer_len = UINT32{}; + auto period_time = ReferenceTime{}; + hr = audio.mClient->GetDevicePeriod(&reinterpret_cast(period_time), nullptr); if(SUCCEEDED(hr)) hr = audio.mClient->GetBufferSize(&buffer_len); if(FAILED(hr)) { - ERR("Failed to get audio buffer info: 0x%08lx\n", hr); + ERR("Failed to get audio buffer info: {:#x}", as_unsigned(hr)); return hr; } hr = audio.mClient->SetEventHandle(mNotifyEvent); if(FAILED(hr)) { - ERR("Failed to set event handle: 0x%08lx\n", hr); + ERR("Failed to set event handle: {:#x}", as_unsigned(hr)); return hr; } - /* Find the nearest multiple of the period size to the update size */ - if(min_per < per_time) - min_per *= std::max((per_time + min_per/2) / min_per, 1_i64); + hr = audio.mClient->GetService(__uuidof(IAudioRenderClient), al::out_ptr(audio.mRender)); + if(FAILED(hr)) + { + ERR("Failed to get IAudioRenderClient: {:#x}", as_unsigned(hr)); + return hr; + } - mOrigBufferSize = buffer_len; - mOrigUpdateSize = std::min(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec), - buffer_len/2u); + mOutBufferSize = buffer_len; + if(sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) + { + /* For exclusive mode, the buffer size is the update size, and there's + * implicitly two update periods on the device. + */ + mOutUpdateSize = buffer_len; + mDevice->mUpdateSize = static_cast(uint64_t{buffer_len} * mDevice->mSampleRate / + mFormat.Format.nSamplesPerSec); + mDevice->mBufferSize = mDevice->mUpdateSize * 2; + } + else + { + mOutUpdateSize = RefTime2Samples(period_time, mFormat.Format.nSamplesPerSec); - mDevice->BufferSize = static_cast(uint64_t{buffer_len} * mDevice->Frequency / - mFormat.Format.nSamplesPerSec); - mDevice->UpdateSize = std::min(RefTime2Samples(min_per, mDevice->Frequency), - mDevice->BufferSize/2u); + mDevice->mBufferSize = static_cast(uint64_t{buffer_len} * mDevice->mSampleRate / + mFormat.Format.nSamplesPerSec); + mDevice->mUpdateSize = std::min(RefTime2Samples(period_time, mDevice->mSampleRate), + mDevice->mBufferSize/2u); + } mResampler = nullptr; mResampleBuffer.clear(); mResampleBuffer.shrink_to_fit(); mBufferFilled = 0; - if(mDevice->Frequency != mFormat.Format.nSamplesPerSec) + if(mDevice->mSampleRate != mFormat.Format.nSamplesPerSec) { mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, - mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec, + mFormat.Format.nChannels, mDevice->mSampleRate, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24); - mResampleBuffer.resize(size_t{mDevice->UpdateSize} * mFormat.Format.nChannels * + mResampleBuffer.resize(size_t{mDevice->mUpdateSize} * mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8); - TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n", + TRACE("Created converter for {}/{} format, dst: {}hz ({}), src: {}hz ({})", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency, - mDevice->UpdateSize); + mFormat.Format.nSamplesPerSec, mOutUpdateSize, mDevice->mSampleRate, + mDevice->mUpdateSize); } return hr; @@ -2033,98 +2342,28 @@ HRESULT WasapiPlayback::resetProxy() void WasapiPlayback::start() { - const HRESULT hr{pushMessage(MsgType::StartDevice).get()}; - if(FAILED(hr)) - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start playback: 0x%lx", hr}; + auto plock = std::unique_lock{mProcMutex}; + if(mState != ThreadState::Waiting) + throw al::backend_exception{al::backend_error::DeviceError, "Invalid state: {}", + unsigned{al::to_underlying(mState)}}; + + mAction = ThreadAction::Play; + mProcCond.notify_all(); + mProcCond.wait(plock, [this]() noexcept { return mAction != ThreadAction::Play; }); + + if(FAILED(mProcResult) || mState != ThreadState::Playing) + throw al::backend_exception{al::backend_error::DeviceError, "Device playback failed: {:#x}", + as_unsigned(mProcResult)}; } -HRESULT WasapiPlayback::startProxy() -{ - ResetEvent(mNotifyEvent); - - auto mstate_fallback = [](std::monostate) -> HRESULT - { return E_FAIL; }; - auto start_plain = [&](PlainDevice &audio) -> HRESULT - { - HRESULT hr{audio.mClient->Start()}; - if(FAILED(hr)) - { - ERR("Failed to start audio client: 0x%08lx\n", hr); - return hr; - } - - hr = audio.mClient->GetService(__uuidof(IAudioRenderClient), al::out_ptr(audio.mRender)); - if(SUCCEEDED(hr)) - { - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this}; - } - catch(...) { - audio.mRender = nullptr; - ERR("Failed to start thread\n"); - hr = E_FAIL; - } - } - if(FAILED(hr)) - audio.mClient->Stop(); - return hr; - }; - auto start_spatial = [&](SpatialDevice &audio) -> HRESULT - { - HRESULT hr{audio.mRender->Start()}; - if(FAILED(hr)) - { - ERR("Failed to start spatial audio stream: 0x%08lx\n", hr); - return hr; - } - - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerSpatialProc), this}; - } - catch(...) { - ERR("Failed to start thread\n"); - hr = E_FAIL; - } - - if(FAILED(hr)) - { - audio.mRender->Stop(); - audio.mRender->Reset(); - } - return hr; - }; - - return std::visit(overloaded{mstate_fallback, start_plain, start_spatial}, mAudio); -} - - void WasapiPlayback::stop() -{ pushMessage(MsgType::StopDevice).wait(); } - -void WasapiPlayback::stopProxy() { - if(!mThread.joinable()) - return; - - mKillNow.store(true, std::memory_order_release); - mThread.join(); - - auto mstate_fallback = [](std::monostate) -> void - { }; - auto stop_plain = [](PlainDevice &audio) -> void + auto plock = std::unique_lock{mProcMutex}; + if(mState == ThreadState::Playing) { - audio.mRender = nullptr; - audio.mClient->Stop(); - }; - auto stop_spatial = [](SpatialDevice &audio) -> void - { - audio.mRender->Stop(); - audio.mRender->Reset(); - }; - std::visit(overloaded{mstate_fallback, stop_plain, stop_spatial}, mAudio); + mKillNow = true; + mProcCond.wait(plock, [this]() noexcept { return mState != ThreadState::Playing; }); + } } @@ -2138,37 +2377,55 @@ ClockLatency WasapiPlayback::getClockLatency() if(mResampler) { auto extra = mResampler->currentInputDelay(); - ret.Latency += std::chrono::duration_cast(extra) / mDevice->Frequency; - ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->Frequency; + ret.Latency += std::chrono::duration_cast(extra) / mDevice->mSampleRate; + ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->mSampleRate; } return ret; } -struct WasapiCapture final : public BackendBase, WasapiProxy { - WasapiCapture(DeviceBase *device) noexcept : BackendBase{device} { } +struct WasapiCapture final : public BackendBase { + explicit WasapiCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~WasapiCapture() override; - int recordProc(); + void recordProc(IAudioClient *client, IAudioCaptureClient *capture); + + void proc_thread(std::string&& name); + + auto openProxy(const std::string_view name, DeviceHelper &helper, DeviceHandle &mmdev) + -> HRESULT; + auto resetProxy(DeviceHelper &helper, DeviceHandle &mmdev, ComPtr &client, + ComPtr &capture) -> HRESULT; void open(std::string_view name) override; - HRESULT openProxy(std::string_view name) override; - void closeProxy() override; - - HRESULT resetProxy() override; void start() override; - HRESULT startProxy() override; void stop() override; - void stopProxy() override; - void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; - HRESULT mOpenStatus{E_FAIL}; - DeviceHandle mMMDev{nullptr}; - ComPtr mClient{nullptr}; - ComPtr mCapture{nullptr}; + + std::thread mProcThread; + std::mutex mProcMutex; + std::condition_variable mProcCond; + HRESULT mProcResult{E_FAIL}; + + enum class ThreadState : uint8_t { + Initializing, + Waiting, + Recording, + Done + }; + ThreadState mState{ThreadState::Initializing}; + + enum class ThreadAction : uint8_t { + Nothing, + Record, + Quit + }; + ThreadAction mAction{ThreadAction::Nothing}; + + HANDLE mNotifyEvent{nullptr}; ChannelConverter mChannelConv{}; @@ -2181,9 +2438,16 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { WasapiCapture::~WasapiCapture() { - if(SUCCEEDED(mOpenStatus)) - pushMessage(MsgType::CloseDevice).wait(); - mOpenStatus = E_FAIL; + if(mProcThread.joinable()) + { + { + auto plock = std::lock_guard{mProcMutex}; + mKillNow = true; + mAction = ThreadAction::Quit; + } + mProcCond.notify_all(); + mProcThread.join(); + } if(mNotifyEvent != nullptr) CloseHandle(mNotifyEvent); @@ -2191,34 +2455,32 @@ WasapiCapture::~WasapiCapture() } -FORCE_ALIGN int WasapiCapture::recordProc() +FORCE_ALIGN void WasapiCapture::recordProc(IAudioClient *client, IAudioCaptureClient *capture) { - ComWrapper com{COINIT_MULTITHREADED}; - if(!com) + ResetEvent(mNotifyEvent); + if(HRESULT hr{client->Start()}; FAILED(hr)) { - ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); - mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); - return 1; + ERR("Failed to start audio client: {:#x}", as_unsigned(hr)); + mDevice->handleDisconnect("Failed to start audio client: {:#x}", as_unsigned(hr)); + return; } - althrd_setname(GetRecordThreadName()); - std::vector samples; while(!mKillNow.load(std::memory_order_relaxed)) { - UINT32 avail; - HRESULT hr{mCapture->GetNextPacketSize(&avail)}; + auto avail = UINT32{}; + auto hr = capture->GetNextPacketSize(&avail); if(FAILED(hr)) - ERR("Failed to get next packet size: 0x%08lx\n", hr); + ERR("Failed to get next packet size: {:#x}", as_unsigned(hr)); else if(avail > 0) { - UINT32 numsamples; - DWORD flags; - BYTE *rdata; + auto numsamples = UINT32{}; + auto flags = DWORD{}; + BYTE *rdata{}; - hr = mCapture->GetBuffer(&rdata, &numsamples, &flags, nullptr, nullptr); + hr = capture->GetBuffer(&rdata, &numsamples, &flags, nullptr, nullptr); if(FAILED(hr)) - ERR("Failed to get capture buffer: 0x%08lx\n", hr); + ERR("Failed to get capture buffer: {:#x}", as_unsigned(hr)); else { if(mChannelConv.is_active()) @@ -2237,170 +2499,257 @@ FORCE_ALIGN int WasapiCapture::recordProc() const void *srcdata{rdata}; uint srcframes{numsamples}; - dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf, - static_cast(std::min(data.first.len, lenlimit))); - if(srcframes > 0 && dstframes == data.first.len && data.second.len > 0) + dstframes = mSampleConv->convert(&srcdata, &srcframes, data[0].buf, + static_cast(std::min(data[0].len, lenlimit))); + if(srcframes > 0 && dstframes == data[0].len && data[1].len > 0) { /* If some source samples remain, all of the first dest * block was filled, and there's space in the second * dest block, do another run for the second block. */ - dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf, - static_cast(std::min(data.second.len, lenlimit))); + dstframes += mSampleConv->convert(&srcdata, &srcframes, data[1].buf, + static_cast(std::min(data[1].len, lenlimit))); } } else { const uint framesize{mDevice->frameSizeFromFmt()}; auto dst = al::span{rdata, size_t{numsamples}*framesize}; - size_t len1{std::min(data.first.len, size_t{numsamples})}; - size_t len2{std::min(data.second.len, numsamples-len1)}; + size_t len1{std::min(data[0].len, size_t{numsamples})}; + size_t len2{std::min(data[1].len, numsamples-len1)}; - memcpy(data.first.buf, dst.data(), len1*framesize); + memcpy(data[0].buf, dst.data(), len1*framesize); if(len2 > 0) { dst = dst.subspan(len1*framesize); - memcpy(data.second.buf, dst.data(), len2*framesize); + memcpy(data[1].buf, dst.data(), len2*framesize); } dstframes = len1 + len2; } mRing->writeAdvance(dstframes); - hr = mCapture->ReleaseBuffer(numsamples); - if(FAILED(hr)) ERR("Failed to release capture buffer: 0x%08lx\n", hr); + hr = capture->ReleaseBuffer(numsamples); + if(FAILED(hr)) ERR("Failed to release capture buffer: {:#x}", as_unsigned(hr)); } } if(FAILED(hr)) { - mDevice->handleDisconnect("Failed to capture samples: 0x%08lx", hr); + mDevice->handleDisconnect("Failed to capture samples: {:#x}", as_unsigned(hr)); break; } - DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; - if(res != WAIT_OBJECT_0) - ERR("WaitForSingleObjectEx error: 0x%lx\n", res); + if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; res != WAIT_OBJECT_0) + ERR("WaitForSingleObjectEx error: {:#x}", res); } - return 0; + client->Stop(); + client->Reset(); +} + + +void WasapiCapture::proc_thread(std::string&& name) +try { + auto com = ComWrapper{COINIT_MULTITHREADED}; + if(!com) + { + const auto hr = as_unsigned(com.status()); + ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: {:#x}", hr); + mDevice->handleDisconnect("COM init failed: {:#x}", hr); + + auto plock = std::lock_guard{mProcMutex}; + mProcResult = com.status(); + mState = ThreadState::Done; + mProcCond.notify_all(); + return; + } + + if(!gInitDone.load(std::memory_order_relaxed)) + { + auto devlock = DeviceListLock{gDeviceList}; + gInitCV.wait(devlock, []() -> bool { return gInitDone; }); + } + + auto helper = DeviceHelper{}; + if(HRESULT hr{helper.init()}; FAILED(hr)) + { + mDevice->handleDisconnect("Helper init failed: {:#x}", as_unsigned(hr)); + + auto plock = std::lock_guard{mProcMutex}; + mProcResult = hr; + mState = ThreadState::Done; + mProcCond.notify_all(); + return; + } + + althrd_setname(GetRecordThreadName()); + + auto mmdev = DeviceHandle{nullptr}; + if(auto hr = openProxy(name, helper, mmdev); FAILED(hr)) + { + auto plock = std::lock_guard{mProcMutex}; + mProcResult = hr; + mState = ThreadState::Done; + mProcCond.notify_all(); + return; + } + + auto client = ComPtr{}; + auto capture = ComPtr{}; + if(auto hr = resetProxy(helper, mmdev, client, capture); FAILED(hr)) + { + auto plock = std::lock_guard{mProcMutex}; + mProcResult = hr; + mState = ThreadState::Done; + mProcCond.notify_all(); + return; + } + + auto plock = std::unique_lock{mProcMutex}; + mProcResult = S_OK; + while(mState != ThreadState::Done) + { + mAction = ThreadAction::Nothing; + mState = ThreadState::Waiting; + mProcCond.notify_all(); + + mProcCond.wait(plock, [this]() noexcept { return mAction != ThreadAction::Nothing; }); + switch(mAction) + { + case ThreadAction::Nothing: + break; + + case ThreadAction::Record: + mKillNow.store(false, std::memory_order_release); + + mAction = ThreadAction::Nothing; + mState = ThreadState::Recording; + mProcResult = S_OK; + plock.unlock(); + mProcCond.notify_all(); + + recordProc(client.get(), capture.get()); + + plock.lock(); + break; + + case ThreadAction::Quit: + mAction = ThreadAction::Nothing; + mState = ThreadState::Done; + mProcCond.notify_all(); + break; + } + } +} +catch(...) { + auto plock = std::lock_guard{mProcMutex}; + mProcResult = E_FAIL; + mAction = ThreadAction::Nothing; + mState = ThreadState::Done; + mProcCond.notify_all(); } void WasapiCapture::open(std::string_view name) { - 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 events: %lu\n", GetLastError()); + ERR("Failed to create notify events: {}", GetLastError()); throw al::backend_exception{al::backend_error::DeviceError, "Failed to create notify events"}; } - if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix)) - name = name.substr(prefix.size()); + mProcThread = std::thread{&WasapiCapture::proc_thread, this, std::string{name}}; - mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); - if(FAILED(mOpenStatus)) - throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", - mOpenStatus}; + auto plock = std::unique_lock{mProcMutex}; + mProcCond.wait(plock, [this]() noexcept { return mState != ThreadState::Initializing; }); - HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; - if(FAILED(hr)) - { - if(hr == E_OUTOFMEMORY) - throw al::backend_exception{al::backend_error::OutOfMemory, "Out of memory"}; - throw al::backend_exception{al::backend_error::DeviceError, "Device reset failed"}; - } + if(mProcResult == E_NOTFOUND) + throw al::backend_exception{al::backend_error::NoDevice, "Device \"{}\" not found", name}; + if(mProcResult == E_OUTOFMEMORY) + throw al::backend_exception{al::backend_error::OutOfMemory, "Out of memory"}; + if(FAILED(mProcResult) || mState == ThreadState::Done) + throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}", + as_unsigned(mProcResult)}; } -HRESULT WasapiCapture::openProxy(std::string_view name) +auto WasapiCapture::openProxy(const std::string_view name, DeviceHelper &helper, + DeviceHandle &mmdev) -> HRESULT { - std::string devname; - std::wstring devid; + auto devname = std::string{}; + auto devid = std::wstring{}; if(!name.empty()) { auto devlock = DeviceListLock{gDeviceList}; auto devlist = al::span{devlock.getCaptureList()}; auto iter = std::find_if(devlist.cbegin(), devlist.cend(), [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; }); + { return entry.name == name || al::case_compare(entry.endpoint_guid, name) == 0; }); if(iter == devlist.cend()) { const std::wstring wname{utf8_to_wstr(name)}; iter = std::find_if(devlist.cbegin(), devlist.cend(), [&wname](const DevMap &entry) -> bool - { return entry.devid == wname; }); + { return al::case_compare(entry.devid, wname) == 0; }); } if(iter == devlist.cend()) { - WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data()); - return E_FAIL; + WARN("Failed to find device name matching \"{}\"", name); + return E_NOTFOUND; } devname = iter->name; devid = iter->devid; } - HRESULT hr{sDeviceHelper->openDevice(devid, eCapture, mMMDev)}; + auto hr = helper.openDevice(devid, eCapture, mmdev); if(FAILED(hr)) { - WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str()); + WARN("Failed to open device \"{}\": {:#x}", devname.empty() + ? "(default)"sv : std::string_view{devname}, as_unsigned(hr)); return hr; } - mClient = nullptr; if(!devname.empty()) - mDevice->DeviceName = std::string{GetDevicePrefix()}+std::move(devname); + mDeviceName = std::move(devname); else - mDevice->DeviceName = std::string{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev).first; + mDeviceName = GetDeviceNameAndGuid(mmdev).mName; return S_OK; } -void WasapiCapture::closeProxy() +auto WasapiCapture::resetProxy(DeviceHelper &helper, DeviceHandle &mmdev, + ComPtr &client, ComPtr &capture) -> HRESULT { - mClient = nullptr; - mMMDev = nullptr; -} + capture = nullptr; + client = nullptr; -HRESULT WasapiCapture::resetProxy() -{ - mClient = nullptr; - - HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), - al::out_ptr(mClient))}; + auto hr = helper.activateAudioClient(mmdev, __uuidof(IAudioClient), al::out_ptr(client)); if(FAILED(hr)) { - ERR("Failed to reactivate audio client: 0x%08lx\n", hr); + ERR("Failed to reactivate audio client: {:#x}", as_unsigned(hr)); return hr; } - WAVEFORMATEX *wfx; - hr = mClient->GetMixFormat(&wfx); + auto wfx = unique_coptr{}; + hr = client->GetMixFormat(al::out_ptr(wfx)); if(FAILED(hr)) { - ERR("Failed to get capture format: 0x%08lx\n", hr); + ERR("Failed to get capture format: {:#x}", as_unsigned(hr)); return hr; } - TraceFormat("Device capture format", wfx); + TraceFormat("Device capture format", wfx.get()); - WAVEFORMATEXTENSIBLE InputType{}; - if(!MakeExtensible(&InputType, wfx)) - { - CoTaskMemFree(wfx); + auto InputType = WAVEFORMATEXTENSIBLE{}; + if(!MakeExtensible(&InputType, wfx.get())) return E_FAIL; - } - CoTaskMemFree(wfx); wfx = nullptr; const bool isRear51{InputType.Format.nChannels == 6 && (InputType.dwChannelMask&X51RearMask) == X5DOT1REAR}; // Make sure buffer is at least 100ms in size - ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency}; + ReferenceTime buf_time{ReferenceTime{seconds{mDevice->mBufferSize}} / mDevice->mSampleRate}; buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}}); InputType = {}; @@ -2466,7 +2815,7 @@ HRESULT WasapiCapture::resetProxy() } /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample; - InputType.Format.nSamplesPerSec = mDevice->Frequency; + InputType.Format.nSamplesPerSec = mDevice->mSampleRate; InputType.Format.nBlockAlign = static_cast(InputType.Format.nChannels * InputType.Format.wBitsPerSample / 8); @@ -2475,15 +2824,15 @@ HRESULT WasapiCapture::resetProxy() InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format); TraceFormat("Requesting capture format", &InputType.Format); - hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx); + hr = client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, al::out_ptr(wfx)); if(FAILED(hr)) { - WARN("Failed to check capture format support: 0x%08lx\n", hr); - hr = mClient->GetMixFormat(&wfx); + WARN("Failed to check capture format support: {:#x}", as_unsigned(hr)); + hr = client->GetMixFormat(al::out_ptr(wfx)); } if(FAILED(hr)) { - ERR("Failed to find a supported capture format: 0x%08lx\n", hr); + ERR("Failed to find a supported capture format: {:#x}", as_unsigned(hr)); return hr; } @@ -2492,13 +2841,9 @@ HRESULT WasapiCapture::resetProxy() if(wfx != nullptr) { - TraceFormat("Got capture format", wfx); - if(!MakeExtensible(&InputType, wfx)) - { - CoTaskMemFree(wfx); + TraceFormat("Got capture format", wfx.get()); + if(!MakeExtensible(&InputType, wfx.get())) return E_FAIL; - } - CoTaskMemFree(wfx); wfx = nullptr; auto validate_fmt = [](DeviceBase *device, uint32_t chancount, DWORD chanmask) noexcept @@ -2536,9 +2881,9 @@ HRESULT WasapiCapture::resetProxy() }; if(!validate_fmt(mDevice, InputType.Format.nChannels, InputType.dwChannelMask)) { - ERR("Failed to match format, wanted: %s %s %uhz, got: 0x%08lx mask %d channel%s %d-bit %luhz\n", + ERR("Failed to match format, wanted: {} {} {}hz, got: {:#08x} mask {} channel{} {}-bit {}hz", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, InputType.dwChannelMask, InputType.Format.nChannels, + mDevice->mSampleRate, InputType.dwChannelMask, InputType.Format.nChannels, (InputType.Format.nChannels==1)?"":"s", InputType.Format.wBitsPerSample, InputType.Format.nSamplesPerSec); return E_FAIL; @@ -2556,7 +2901,7 @@ HRESULT WasapiCapture::resetProxy() srcType = DevFmtInt; else { - ERR("Unhandled integer bit depth: %d\n", InputType.Format.wBitsPerSample); + ERR("Unhandled integer bit depth: {}", InputType.Format.wBitsPerSample); return E_FAIL; } } @@ -2566,13 +2911,13 @@ HRESULT WasapiCapture::resetProxy() srcType = DevFmtFloat; else { - ERR("Unhandled float bit depth: %d\n", InputType.Format.wBitsPerSample); + ERR("Unhandled float bit depth: {}", InputType.Format.wBitsPerSample); return E_FAIL; } } else { - ERR("Unhandled format sub-type: %s\n", GuidPrinter{InputType.SubFormat}.c_str()); + ERR("Unhandled format sub-type: {}", GuidPrinter{InputType.SubFormat}.str()); return E_FAIL; } @@ -2589,7 +2934,7 @@ HRESULT WasapiCapture::resetProxy() mChannelConv = ChannelConverter{srcType, InputType.Format.nChannels, chanmask, mDevice->FmtChans}; - TRACE("Created %s multichannel-to-mono converter\n", DevFmtTypeString(srcType)); + TRACE("Created {} multichannel-to-mono converter", DevFmtTypeString(srcType)); /* The channel converter always outputs float, so change the input type * for the resampler/type-converter. */ @@ -2598,54 +2943,61 @@ HRESULT WasapiCapture::resetProxy() else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1) { mChannelConv = ChannelConverter{srcType, 1, 0x1, mDevice->FmtChans}; - TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType)); + TRACE("Created {} mono-to-stereo converter", DevFmtTypeString(srcType)); srcType = DevFmtFloat; } - if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType) + if(mDevice->mSampleRate != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType) { mSampleConv = SampleConverter::Create(srcType, mDevice->FmtType, - mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->Frequency, + mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->mSampleRate, Resampler::FastBSinc24); if(!mSampleConv) { - ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n", + ERR("Failed to create converter for {} format, dst: {} {}hz, src: {} {}hz", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec); + mDevice->mSampleRate, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec); return E_FAIL; } - TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n", + TRACE("Created converter for {} format, dst: {} {}hz, src: {} {}hz", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec); + mDevice->mSampleRate, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec); } - hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time.count(), 0, &InputType.Format, nullptr); if(FAILED(hr)) { - ERR("Failed to initialize audio client: 0x%08lx\n", hr); + ERR("Failed to initialize audio client: {:#x}", as_unsigned(hr)); + return hr; + } + + hr = client->GetService(__uuidof(IAudioCaptureClient), al::out_ptr(capture)); + if(FAILED(hr)) + { + ERR("Failed to get IAudioCaptureClient: {:#x}", as_unsigned(hr)); return hr; } UINT32 buffer_len{}; ReferenceTime min_per{}; - hr = mClient->GetDevicePeriod(&reinterpret_cast(min_per), nullptr); + hr = client->GetDevicePeriod(&reinterpret_cast(min_per), nullptr); if(SUCCEEDED(hr)) - hr = mClient->GetBufferSize(&buffer_len); + hr = client->GetBufferSize(&buffer_len); if(FAILED(hr)) { - ERR("Failed to get buffer size: 0x%08lx\n", hr); + ERR("Failed to get buffer size: {:#x}", as_unsigned(hr)); return hr; } - mDevice->UpdateSize = RefTime2Samples(min_per, mDevice->Frequency); - mDevice->BufferSize = buffer_len; + mDevice->mUpdateSize = RefTime2Samples(min_per, mDevice->mSampleRate); + mDevice->mBufferSize = buffer_len; mRing = RingBuffer::Create(buffer_len, mDevice->frameSizeFromFmt(), false); - hr = mClient->SetEventHandle(mNotifyEvent); + hr = client->SetEventHandle(mNotifyEvent); if(FAILED(hr)) { - ERR("Failed to set event handle: 0x%08lx\n", hr); + ERR("Failed to set event handle: {:#x}", as_unsigned(hr)); return hr; } @@ -2655,61 +3007,28 @@ HRESULT WasapiCapture::resetProxy() void WasapiCapture::start() { - const HRESULT hr{pushMessage(MsgType::StartDevice).get()}; - if(FAILED(hr)) - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start recording: 0x%lx", hr}; + auto plock = std::unique_lock{mProcMutex}; + if(mState != ThreadState::Waiting) + throw al::backend_exception{al::backend_error::DeviceError, "Invalid state: {}", + unsigned{al::to_underlying(mState)}}; + + mAction = ThreadAction::Record; + mProcCond.notify_all(); + mProcCond.wait(plock, [this]() noexcept { return mAction != ThreadAction::Record; }); + + if(FAILED(mProcResult) || mState != ThreadState::Recording) + throw al::backend_exception{al::backend_error::DeviceError, "Device capture failed: {:#x}", + as_unsigned(mProcResult)}; } -HRESULT WasapiCapture::startProxy() -{ - ResetEvent(mNotifyEvent); - - HRESULT hr{mClient->Start()}; - if(FAILED(hr)) - { - ERR("Failed to start audio client: 0x%08lx\n", hr); - return hr; - } - - hr = mClient->GetService(__uuidof(IAudioCaptureClient), al::out_ptr(mCapture)); - if(SUCCEEDED(hr)) - { - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this}; - } - catch(...) { - mCapture = nullptr; - ERR("Failed to start thread\n"); - hr = E_FAIL; - } - } - - if(FAILED(hr)) - { - mClient->Stop(); - mClient->Reset(); - } - - return hr; -} - - void WasapiCapture::stop() -{ pushMessage(MsgType::StopDevice).wait(); } - -void WasapiCapture::stopProxy() { - if(!mCapture || !mThread.joinable()) - return; - - mKillNow.store(true, std::memory_order_release); - mThread.join(); - - mCapture = nullptr; - mClient->Stop(); - mClient->Reset(); + auto plock = std::unique_lock{mProcMutex}; + if(mState == ThreadState::Recording) + { + mKillNow = true; + mProcCond.wait(plock, [this]() noexcept { return mState != ThreadState::Recording; }); + } } @@ -2730,7 +3049,7 @@ bool WasapiBackendFactory::init() std::promise promise; auto future = promise.get_future(); - std::thread{&WasapiProxy::messageHandler, &promise}.detach(); + std::thread{&DeviceEnumHelper::messageHandler, &promise}.detach(); InitResult = future.get(); } catch(...) { @@ -2752,15 +3071,19 @@ auto WasapiBackendFactory::enumerate(BackendType type) -> std::vector bool { return entry.devid == defaultId; }); + if(defiter != devlist.cend()) { - if(entry.devid != defaultId) - { - outnames.emplace_back(std::string{GetDevicePrefix()}+entry.name); - continue; - } - /* Default device goes first. */ - outnames.emplace(outnames.cbegin(), std::string{GetDevicePrefix()}+entry.name); + const auto defname = outnames.begin() + std::distance(devlist.cbegin(), defiter); + std::rotate(outnames.begin(), defname, defname+1); } } break; @@ -2768,14 +3091,19 @@ auto WasapiBackendFactory::enumerate(BackendType type) -> std::vector bool { return entry.devid == defaultId; }); + if(defiter != devlist.cend()) { - if(entry.devid != defaultId) - { - outnames.emplace_back(std::string{GetDevicePrefix()}+entry.name); - continue; - } - outnames.emplace(outnames.cbegin(), std::string{GetDevicePrefix()}+entry.name); + const auto defname = outnames.begin() + std::distance(devlist.cbegin(), defiter); + std::rotate(outnames.begin(), defname, defname+1); } } break; @@ -2808,7 +3136,7 @@ alc::EventSupport WasapiBackendFactory::queryEventSupport(alc::EventType eventTy case alc::EventType::DeviceAdded: case alc::EventType::DeviceRemoved: -#if !defined(ALSOFT_UWP) +#if !ALSOFT_UWP return alc::EventSupport::FullSupport; #endif diff --git a/Engine/lib/openal-soft/alc/backends/wave.cpp b/Engine/lib/openal-soft/alc/backends/wave.cpp index 5e61ab52b..153e61452 100644 --- a/Engine/lib/openal-soft/alc/backends/wave.cpp +++ b/Engine/lib/openal-soft/alc/backends/wave.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -39,12 +38,9 @@ #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" -#include "alstring.h" #include "althrd_setname.h" #include "core/device.h" -#include "core/helpers.h" #include "core/logging.h" -#include "opthelpers.h" #include "strutils.h" @@ -99,7 +95,7 @@ void fwrite32le(uint val, FILE *f) struct WaveBackend final : public BackendBase { - WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { } + explicit WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { } ~WaveBackend() override; int mixerProc(); @@ -122,7 +118,7 @@ WaveBackend::~WaveBackend() = default; int WaveBackend::mixerProc() { - const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; + const milliseconds restTime{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2}; althrd_setname(GetMixerThreadName()); @@ -137,17 +133,17 @@ int WaveBackend::mixerProc() auto now = std::chrono::steady_clock::now(); /* This converts from nanoseconds to nanosamples, then to samples. */ - int64_t avail{std::chrono::duration_cast((now-start) * - mDevice->Frequency).count()}; - if(avail-done < mDevice->UpdateSize) + const auto avail = int64_t{std::chrono::duration_cast((now-start) * + mDevice->mSampleRate).count()}; + if(avail-done < mDevice->mUpdateSize) { std::this_thread::sleep_for(restTime); continue; } - while(avail-done >= mDevice->UpdateSize) + while(avail-done >= mDevice->mUpdateSize) { - mDevice->renderSamples(mBuffer.data(), mDevice->UpdateSize, frameStep); - done += mDevice->UpdateSize; + mDevice->renderSamples(mBuffer.data(), mDevice->mUpdateSize, frameStep); + done += mDevice->mUpdateSize; if(al::endian::native != al::endian::little) { @@ -170,10 +166,10 @@ int WaveBackend::mixerProc() } } - const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile.get())}; - if(fs < mDevice->UpdateSize || ferror(mFile.get())) + const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->mUpdateSize, mFile.get())}; + if(fs < mDevice->mUpdateSize || ferror(mFile.get())) { - ERR("Error writing to file\n"); + ERR("Error writing to file"); mDevice->handleDisconnect("Failed to write playback samples"); break; } @@ -184,10 +180,10 @@ int WaveBackend::mixerProc() * and current time from growing too large, while maintaining the * correct number of samples to render. */ - if(done >= mDevice->Frequency) + if(done >= mDevice->mSampleRate) { - seconds s{done/mDevice->Frequency}; - done %= mDevice->Frequency; + seconds s{done/mDevice->mSampleRate}; + done %= mDevice->mSampleRate; start += s; } } @@ -204,8 +200,8 @@ void WaveBackend::open(std::string_view name) if(name.empty()) name = GetDeviceName(); else if(name != GetDeviceName()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; /* There's only one "device", so if it's already open, we're done. */ if(mFile) return; @@ -219,20 +215,14 @@ void WaveBackend::open(std::string_view name) mFile = FilePtr{fopen(fname->c_str(), "wb")}; #endif if(!mFile) - throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s", - fname->c_str(), std::generic_category().message(errno).c_str()}; + throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '{}': {}", + *fname, std::generic_category().message(errno)}; - mDevice->DeviceName = name; + mDeviceName = name; } bool WaveBackend::reset() { - uint channels{0}, bytes{0}, chanmask{0}; - bool isbformat{false}; - - fseek(mFile.get(), 0, SEEK_SET); - clearerr(mFile.get()); - if(GetConfigValueBool({}, "wave", "bformat", false)) { mDevice->FmtChans = DevFmtAmbi3D; @@ -256,6 +246,8 @@ bool WaveBackend::reset() case DevFmtFloat: break; } + auto chanmask = 0u; + auto isbformat = false; switch(mDevice->FmtChans) { case DevFmtMono: chanmask = 0x04; break; @@ -282,13 +274,24 @@ bool WaveBackend::reset() chanmask = 0; break; } - bytes = mDevice->bytesFromFmt(); - channels = mDevice->channelsFromFmt(); + const auto bytes = mDevice->bytesFromFmt(); + const auto channels = mDevice->channelsFromFmt(); - rewind(mFile.get()); + if(fseek(mFile.get(), 0, SEEK_CUR) != 0) + { + /* ESPIPE means the underlying file isn't seekable, which is fine for + * piped output. + */ + if(auto errcode = errno; errcode != ESPIPE) + { + ERR("Failed to reset file offset: {} ({})", std::generic_category().message(errcode), + errcode); + } + } + clearerr(mFile.get()); fputs("RIFF", mFile.get()); - fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at close + fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at stop fputs("WAVE", mFile.get()); @@ -300,9 +303,9 @@ bool WaveBackend::reset() // 16-bit val, channel count fwrite16le(static_cast(channels), mFile.get()); // 32-bit val, frequency - fwrite32le(mDevice->Frequency, mFile.get()); + fwrite32le(mDevice->mSampleRate, mFile.get()); // 32-bit val, bytes per second - fwrite32le(mDevice->Frequency * channels * bytes, mFile.get()); + fwrite32le(mDevice->mSampleRate * channels * bytes, mFile.get()); // 16-bit val, frame size fwrite16le(static_cast(channels * bytes), mFile.get()); // 16-bit val, bits per sample @@ -319,18 +322,18 @@ bool WaveBackend::reset() (isbformat ? SUBTYPE_BFORMAT_PCM.data() : SUBTYPE_PCM.data()), 1, 16, mFile.get()); fputs("data", mFile.get()); - fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at close + fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at stop if(ferror(mFile.get())) { - ERR("Error writing header: %s\n", std::generic_category().message(errno).c_str()); + ERR("Error writing header: {}", std::generic_category().message(errno)); return false; } mDataStart = ftell(mFile.get()); setDefaultWFXChannelOrder(); - const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize}; + const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->mUpdateSize}; mBuffer.resize(bufsize); return true; @@ -339,14 +342,14 @@ bool WaveBackend::reset() void WaveBackend::start() { if(mDataStart > 0 && fseek(mFile.get(), 0, SEEK_END) != 0) - WARN("Failed to seek on output file\n"); + WARN("Failed to seek on output file"); try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this}; + mThread = std::thread{&WaveBackend::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } diff --git a/Engine/lib/openal-soft/alc/backends/winmm.cpp b/Engine/lib/openal-soft/alc/backends/winmm.cpp index ea4fee1e8..349a244de 100644 --- a/Engine/lib/openal-soft/alc/backends/winmm.cpp +++ b/Engine/lib/openal-soft/alc/backends/winmm.cpp @@ -36,14 +36,14 @@ #include #include #include -#include +#include "alnumeric.h" #include "alsem.h" -#include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" +#include "fmt/core.h" #include "ringbuffer.h" #include "strutils.h" #include "vector.h" @@ -54,9 +54,6 @@ namespace { -#define DEVNAME_HEAD "OpenAL Soft on " - - std::vector PlaybackDevices; std::vector CaptureDevices; @@ -76,19 +73,15 @@ void ProbePlaybackDevices() WAVEOUTCAPSW WaveCaps{}; if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) { - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))}; + const auto basename = wstr_to_utf8(std::data(WaveCaps.szPname)); - int count{1}; - std::string newname{basename}; + auto count = 1; + auto newname = basename; while(checkName(PlaybackDevices, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } + newname = fmt::format("{} #{}", basename, ++count); dname = std::move(newname); - TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i); + TRACE("Got device \"{}\", ID {}", dname, i); } PlaybackDevices.emplace_back(std::move(dname)); } @@ -107,19 +100,15 @@ void ProbeCaptureDevices() WAVEINCAPSW WaveCaps{}; if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) { - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))}; + const auto basename = wstr_to_utf8(std::data(WaveCaps.szPname)); - int count{1}; - std::string newname{basename}; + auto count = 1; + auto newname = basename; while(checkName(CaptureDevices, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } + newname = fmt::format("{} #{}", basename, ++count); dname = std::move(newname); - TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i); + TRACE("Got device \"{}\", ID {}", dname, i); } CaptureDevices.emplace_back(std::move(dname)); } @@ -127,7 +116,7 @@ void ProbeCaptureDevices() struct WinMMPlayback final : public BackendBase { - WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~WinMMPlayback() override; void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept; @@ -194,7 +183,7 @@ FORCE_ALIGN int WinMMPlayback::mixerProc() WAVEHDR &waveHdr = mWaveBuffer[widx]; if(++widx == mWaveBuffer.size()) widx = 0; - mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, mFormat.nChannels); + mDevice->renderSamples(waveHdr.lpData, mDevice->mUpdateSize, mFormat.nChannels); mWritable.fetch_sub(1, std::memory_order_acq_rel); waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR)); } while(--todo); @@ -215,8 +204,8 @@ void WinMMPlayback::open(std::string_view name) std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) : PlaybackDevices.cbegin(); if(iter == PlaybackDevices.cend()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; auto DeviceID = static_cast(std::distance(PlaybackDevices.cbegin(), iter)); DevFmtType fmttype{mDevice->FmtType}; @@ -238,7 +227,7 @@ void WinMMPlayback::open(std::string_view name) } format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); format.nBlockAlign = static_cast(format.wBitsPerSample * format.nChannels / 8); - format.nSamplesPerSec = mDevice->Frequency; + format.nSamplesPerSec = mDevice->mSampleRate; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; format.cbSize = 0; @@ -248,7 +237,7 @@ void WinMMPlayback::open(std::string_view name) if(res == MMSYSERR_NOERROR) break; if(fmttype != DevFmtFloat) - throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", + throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: {}", res}; fmttype = DevFmtShort; @@ -256,16 +245,16 @@ void WinMMPlayback::open(std::string_view name) mFormat = format; - mDevice->DeviceName = PlaybackDevices[DeviceID]; + mDeviceName = PlaybackDevices[DeviceID]; } bool WinMMPlayback::reset() { - mDevice->BufferSize = static_cast(uint64_t{mDevice->BufferSize} * - mFormat.nSamplesPerSec / mDevice->Frequency); - mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3u; - mDevice->UpdateSize = mDevice->BufferSize / 4; - mDevice->Frequency = mFormat.nSamplesPerSec; + mDevice->mBufferSize = static_cast(uint64_t{mDevice->mBufferSize} * + mFormat.nSamplesPerSec / mDevice->mSampleRate); + mDevice->mBufferSize = (mDevice->mBufferSize+3) & ~0x3u; + mDevice->mUpdateSize = mDevice->mBufferSize / 4; + mDevice->mSampleRate = mFormat.nSamplesPerSec; if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { @@ -273,7 +262,7 @@ bool WinMMPlayback::reset() mDevice->FmtType = DevFmtFloat; else { - ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample); + ERR("Unhandled IEEE float sample depth: {}", mFormat.wBitsPerSample); return false; } } @@ -285,13 +274,13 @@ bool WinMMPlayback::reset() mDevice->FmtType = DevFmtUByte; else { - ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample); + ERR("Unhandled PCM sample depth: {}", mFormat.wBitsPerSample); return false; } } else { - ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag); + ERR("Unhandled format tag: {:#04x}", as_unsigned(mFormat.wFormatTag)); return false; } @@ -301,12 +290,12 @@ bool WinMMPlayback::reset() mDevice->FmtChans = DevFmtMono; else { - ERR("Unhandled channel count: %d\n", mFormat.nChannels); + ERR("Unhandled channel count: {}", mFormat.nChannels); return false; } setDefaultWFXChannelOrder(); - const uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; + const uint BufferSize{mDevice->mUpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer); mWaveBuffer[0] = WAVEHDR{}; @@ -331,11 +320,11 @@ void WinMMPlayback::start() mWritable.store(static_cast(mWaveBuffer.size()), std::memory_order_release); mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this}; + mThread = std::thread{&WinMMPlayback::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -354,7 +343,7 @@ void WinMMPlayback::stop() struct WinMMCapture final : public BackendBase { - WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~WinMMCapture() override; void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept; @@ -446,8 +435,8 @@ void WinMMCapture::open(std::string_view name) std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) : CaptureDevices.cbegin(); if(iter == CaptureDevices.cend()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; auto DeviceID = static_cast(std::distance(CaptureDevices.cbegin(), iter)); switch(mDevice->FmtChans) @@ -464,7 +453,7 @@ void WinMMCapture::open(std::string_view name) case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: - throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", + throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } @@ -479,7 +468,7 @@ void WinMMCapture::open(std::string_view name) case DevFmtByte: case DevFmtUShort: case DevFmtUInt: - throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported", + throw al::backend_exception{al::backend_error::DeviceError, "{} samples not supported", DevFmtTypeString(mDevice->FmtType)}; } @@ -489,7 +478,7 @@ void WinMMCapture::open(std::string_view name) mFormat.nChannels = static_cast(mDevice->channelsFromFmt()); mFormat.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); mFormat.nBlockAlign = static_cast(mFormat.wBitsPerSample * mFormat.nChannels / 8); - mFormat.nSamplesPerSec = mDevice->Frequency; + mFormat.nSamplesPerSec = mDevice->mSampleRate; mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign; mFormat.cbSize = 0; @@ -497,7 +486,7 @@ void WinMMCapture::open(std::string_view name) reinterpret_cast(&WinMMCapture::waveInProcC), reinterpret_cast(this), CALLBACK_FUNCTION)}; if(res != MMSYSERR_NOERROR) - throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: %u", res}; + throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: {}", res}; // Ensure each buffer is 50ms each DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u}; @@ -505,7 +494,7 @@ void WinMMCapture::open(std::string_view name) // Allocate circular memory buffer for the captured audio // Make sure circular buffer is at least 100ms in size - const auto CapturedDataSize = std::max(mDevice->BufferSize, + const auto CapturedDataSize = std::max(mDevice->mBufferSize, BufferSize*mWaveBuffer.size()); mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false); @@ -521,7 +510,7 @@ void WinMMCapture::open(std::string_view name) mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength; } - mDevice->DeviceName = CaptureDevices[DeviceID]; + mDeviceName = CaptureDevices[DeviceID]; } void WinMMCapture::start() @@ -534,13 +523,13 @@ void WinMMCapture::start() } mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this}; + mThread = std::thread{&WinMMCapture::captureProc, this}; waveInStart(mInHdl); } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start recording thread: %s", e.what()}; + "Failed to start recording thread: {}", e.what()}; } } diff --git a/Engine/lib/openal-soft/alc/context.cpp b/Engine/lib/openal-soft/alc/context.cpp index 2b7a9ea9d..3e1c5f870 100644 --- a/Engine/lib/openal-soft/alc/context.cpp +++ b/Engine/lib/openal-soft/alc/context.cpp @@ -6,12 +6,12 @@ #include #include #include -#include #include -#include +#include #include -#include +#include #include +#include #include #include "AL/efx.h" @@ -25,19 +25,22 @@ #include "albit.h" #include "alc/alu.h" #include "alc/backends/base.h" +#include "alnumeric.h" #include "alspan.h" +#include "atomic.h" #include "core/async_event.h" +#include "core/devformat.h" #include "core/device.h" #include "core/effectslot.h" #include "core/logging.h" -#include "core/voice.h" #include "core/voice_change.h" #include "device.h" #include "flexarray.h" #include "ringbuffer.h" #include "vecmat.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX +#include "al/eax/call.h" #include "al/eax/globals.h" #endif // ALSOFT_EAX @@ -70,7 +73,7 @@ std::vector getContextExtensions() noexcept "AL_EXT_STEREO_ANGLES"sv, "AL_LOKI_quadriphonic"sv, "AL_SOFT_bformat_ex"sv, - "AL_SOFTX_bformat_hoa"sv, + "AL_SOFT_bformat_hoa"sv, "AL_SOFT_block_alignment"sv, "AL_SOFT_buffer_length_query"sv, "AL_SOFT_callback_buffer"sv, @@ -107,7 +110,7 @@ ALCcontext::ThreadCtx::~ThreadCtx() if(ALCcontext *ctx{std::exchange(ALCcontext::sLocalContext, nullptr)}) { const bool result{ctx->releaseIfNoDelete()}; - ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx}, + ERR("Context {} current for thread being destroyed{}!", voidp{ctx}, result ? "" : ", leak detected"); } } @@ -116,26 +119,30 @@ thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext; ALeffect ALCcontext::sDefaultEffect; -ALCcontext::ALCcontext(al::intrusive_ptr device, ContextFlagBitset flags) +ALCcontext::ALCcontext(al::intrusive_ptr device, ContextFlagBitset flags) : ContextBase{device.get()}, mALDevice{std::move(device)}, mContextFlags{flags} { mDebugGroups.emplace_back(DebugSource::Other, 0, std::string{}); mDebugEnabled.store(mContextFlags.test(ContextFlags::DebugBit), std::memory_order_relaxed); + + /* Low-severity debug messages are disabled by default. */ + alDebugMessageControlDirectEXT(this, AL_DONT_CARE_EXT, AL_DONT_CARE_EXT, + AL_DEBUG_SEVERITY_LOW_EXT, 0, nullptr, AL_FALSE); } ALCcontext::~ALCcontext() { - TRACE("Freeing context %p\n", voidp{this}); + TRACE("Freeing context {}", voidp{this}); size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), 0_uz, [](size_t cur, const SourceSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); })}; if(count > 0) - WARN("%zu Source%s not deleted\n", count, (count==1)?"":"s"); + WARN("{} Source{} not deleted", count, (count==1)?"":"s"); mSourceList.clear(); mNumSources = 0; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eaxUninitialize(); #endif // ALSOFT_EAX @@ -144,7 +151,7 @@ ALCcontext::~ALCcontext() [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) - WARN("%zu AuxiliaryEffectSlot%s not deleted\n", count, (count==1)?"":"s"); + WARN("{} AuxiliaryEffectSlot{} not deleted", count, (count==1)?"":"s"); mEffectSlotList.clear(); mNumEffectSlots = 0; } @@ -183,13 +190,15 @@ void ALCcontext::init() { auto iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_EXT_SOURCE_RADIUS"sv); if(iter != mExtensions.end()) mExtensions.erase(iter); - /* TODO: Would be nice to sort this alphabetically. Needs case- - * insensitive searching. + + /* Insert the AL_SOFT_buffer_sub_data extension string between + * AL_SOFT_buffer_length_query and AL_SOFT_callback_buffer. */ - mExtensions.emplace_back("AL_SOFT_buffer_sub_data"sv); + iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_SOFT_callback_buffer"sv); + mExtensions.emplace(iter, "AL_SOFT_buffer_sub_data"sv); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eax_initialize_extensions(); #endif // ALSOFT_EAX @@ -212,14 +221,26 @@ void ALCcontext::init() mExtensionsString = std::move(extensions); } +#if ALSOFT_EAX + eax_set_defaults(); +#endif + mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f}; mParams.Matrix = alu::Matrix::Identity(); mParams.Velocity = alu::Vector{}; mParams.Gain = mListener.Gain; - mParams.MetersPerUnit = mListener.mMetersPerUnit; + mParams.MetersPerUnit = mListener.mMetersPerUnit +#if ALSOFT_EAX + * eaxGetDistanceFactor() +#endif + ; mParams.AirAbsorptionGainHF = mAirAbsorptionGainHF; mParams.DopplerFactor = mDopplerFactor; - mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity; + mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity +#if ALSOFT_EAX + / eaxGetDistanceFactor() +#endif + ; mParams.SourceDistanceModel = mSourceDistanceModel; mParams.mDistanceModel = mDistanceModel; @@ -236,35 +257,34 @@ void ALCcontext::deinit() { if(sLocalContext == this) { - WARN("%p released while current on thread\n", voidp{this}); + WARN("{} released while current on thread", voidp{this}); + auto _ = ContextRef{sLocalContext}; sThreadContext.set(nullptr); - dec_ref(); } - ALCcontext *origctx{this}; - if(sGlobalContext.compare_exchange_strong(origctx, nullptr)) + if(ALCcontext *origctx{this}; sGlobalContext.compare_exchange_strong(origctx, nullptr)) { + auto _ = ContextRef{origctx}; while(sGlobalContextLock.load()) { /* Wait to make sure another thread didn't get the context and is * trying to increment its refcount. */ } - dec_ref(); } bool stopPlayback{}; /* First make sure this context exists in the device's list. */ - auto *oldarray = mDevice->mContexts.load(std::memory_order_acquire); - if(auto toremove = static_cast(std::count(oldarray->begin(), oldarray->end(), this))) + auto oldarray = al::span{*mDevice->mContexts.load(std::memory_order_acquire)}; + if(auto toremove = static_cast(std::count(oldarray.begin(), oldarray.end(), this))) { using ContextArray = al::FlexArray; - const size_t newsize{oldarray->size() - toremove}; + const auto newsize = size_t{oldarray.size() - toremove}; auto newarray = ContextArray::Create(newsize); /* Copy the current/old context handles to the new array, excluding the * given context. */ - std::copy_if(oldarray->begin(), oldarray->end(), newarray->begin(), + std::copy_if(oldarray.begin(), oldarray.end(), newarray->begin(), [this](ContextBase *ctx) { return ctx != this; }); /* Store the new context array in the device. Wait for any current mix @@ -276,7 +296,7 @@ void ALCcontext::deinit() stopPlayback = (newsize == 0); } else - stopPlayback = oldarray->empty(); + stopPlayback = oldarray.empty(); StopEventThrd(this); @@ -297,7 +317,7 @@ void ALCcontext::applyAllUpdates() /* busy-wait */ } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(mEaxNeedsCommit) eaxCommit(); #endif @@ -314,7 +334,7 @@ void ALCcontext::applyAllUpdates() } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { template @@ -492,7 +512,7 @@ void ALCcontext::eax_initialize() eax_ensure_compatibility(); eax_set_defaults(); - eax_context_commit_air_absorbtion_hf(); + eax_context_commit_air_absorption_hf(); eax_update_speaker_configuration(); eax_initialize_fx_slots(); @@ -561,7 +581,8 @@ unsigned long ALCcontext::eax_detect_speaker_configuration() const */ case DevFmtAmbi3D: return SPEAKERS_7; } - ERR(EAX_PREFIX "Unexpected device channel format 0x%x.\n", mDevice->FmtChans); + ERR(EAX_PREFIX "Unexpected device channel format {:#x}.", + uint{al::to_underlying(mDevice->FmtChans)}); return HEADPHONES; #undef EAX_PREFIX @@ -619,7 +640,7 @@ void ALCcontext::eax_context_set_defaults() eax5_context_set_defaults(mEax5); mEax = mEax5.i; mEaxVersion = 5; - mEaxDf = EaxDirtyFlags{}; + mEaxDf.reset(); } void ALCcontext::eax_set_defaults() @@ -746,14 +767,11 @@ void ALCcontext::eax_context_commit_primary_fx_slot_id() void ALCcontext::eax_context_commit_distance_factor() { - if(mListener.mMetersPerUnit == mEax.flDistanceFactor) - return; - - mListener.mMetersPerUnit = mEax.flDistanceFactor; + /* mEax.flDistanceFactor was changed, so the context props are dirty. */ mPropsDirty = true; } -void ALCcontext::eax_context_commit_air_absorbtion_hf() +void ALCcontext::eax_context_commit_air_absorption_hf() { const auto new_value = level_mb_to_gain(mEax.flAirAbsorptionHF); @@ -814,16 +832,16 @@ void ALCcontext::eax4_defer_all(const EaxCall& call, Eax4State& state) dst_d = src; if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) - mEaxDf |= eax_primary_fx_slot_id_dirty_bit; + mEaxDf.set(eax_primary_fx_slot_id_dirty_bit); if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) - mEaxDf |= eax_distance_factor_dirty_bit; + mEaxDf.set(eax_distance_factor_dirty_bit); if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) - mEaxDf |= eax_air_absorption_hf_dirty_bit; + mEaxDf.set(eax_air_absorption_hf_dirty_bit); if(dst_i.flHFReference != dst_d.flHFReference) - mEaxDf |= eax_hf_reference_dirty_bit; + mEaxDf.set(eax_hf_reference_dirty_bit); } void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state) @@ -834,20 +852,20 @@ void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state) eax4_defer_all(call, state); break; case EAXCONTEXT_PRIMARYFXSLOTID: - eax_defer( - call, state, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); + eax_defer(call, state, + &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); break; case EAXCONTEXT_DISTANCEFACTOR: - eax_defer( - call, state, &EAX40CONTEXTPROPERTIES::flDistanceFactor); + eax_defer(call, state, + &EAX40CONTEXTPROPERTIES::flDistanceFactor); break; case EAXCONTEXT_AIRABSORPTIONHF: - eax_defer( - call, state, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); + eax_defer(call, state, + &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); break; case EAXCONTEXT_HFREFERENCE: - eax_defer( - call, state, &EAX40CONTEXTPROPERTIES::flHFReference); + eax_defer(call, state, + &EAX40CONTEXTPROPERTIES::flHFReference); break; default: eax_set_misc(call); @@ -864,19 +882,19 @@ void ALCcontext::eax5_defer_all(const EaxCall& call, Eax5State& state) dst_d = src; if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) - mEaxDf |= eax_primary_fx_slot_id_dirty_bit; + mEaxDf.set(eax_primary_fx_slot_id_dirty_bit); if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) - mEaxDf |= eax_distance_factor_dirty_bit; + mEaxDf.set(eax_distance_factor_dirty_bit); if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) - mEaxDf |= eax_air_absorption_hf_dirty_bit; + mEaxDf.set(eax_air_absorption_hf_dirty_bit); if(dst_i.flHFReference != dst_d.flHFReference) - mEaxDf |= eax_hf_reference_dirty_bit; + mEaxDf.set(eax_hf_reference_dirty_bit); if(dst_i.flMacroFXFactor != dst_d.flMacroFXFactor) - mEaxDf |= eax_macro_fx_factor_dirty_bit; + mEaxDf.set(eax_macro_fx_factor_dirty_bit); } void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state) @@ -887,24 +905,24 @@ void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state) eax5_defer_all(call, state); break; case EAXCONTEXT_PRIMARYFXSLOTID: - eax_defer( - call, state, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); + eax_defer(call, state, + &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); break; case EAXCONTEXT_DISTANCEFACTOR: - eax_defer( - call, state, &EAX50CONTEXTPROPERTIES::flDistanceFactor); + eax_defer(call, state, + &EAX50CONTEXTPROPERTIES::flDistanceFactor); break; case EAXCONTEXT_AIRABSORPTIONHF: - eax_defer( - call, state, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); + eax_defer(call, state, + &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); break; case EAXCONTEXT_HFREFERENCE: - eax_defer( - call, state, &EAX50CONTEXTPROPERTIES::flHFReference); + eax_defer(call, state, + &EAX50CONTEXTPROPERTIES::flHFReference); break; case EAXCONTEXT_MACROFXFACTOR: - eax_defer( - call, state, &EAX50CONTEXTPROPERTIES::flMacroFXFactor); + eax_defer(call, state, + &EAX50CONTEXTPROPERTIES::flMacroFXFactor); break; default: eax_set_misc(call); @@ -922,49 +940,49 @@ void ALCcontext::eax_set(const EaxCall& call) default: eax_fail_unknown_version(); } if(version != mEaxVersion) - mEaxDf = ~EaxDirtyFlags(); + mEaxDf.set(); mEaxVersion = version; } -void ALCcontext::eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df) +void ALCcontext::eax4_context_commit(Eax4State& state, std::bitset& dst_df) { - if(mEaxDf == EaxDirtyFlags{}) + if(mEaxDf.none()) return; - eax_context_commit_property( - state, dst_df, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); - eax_context_commit_property( - state, dst_df, &EAX40CONTEXTPROPERTIES::flDistanceFactor); - eax_context_commit_property( - state, dst_df, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); - eax_context_commit_property( - state, dst_df, &EAX40CONTEXTPROPERTIES::flHFReference); + eax_context_commit_property(state, dst_df, + &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); + eax_context_commit_property(state, dst_df, + &EAX40CONTEXTPROPERTIES::flDistanceFactor); + eax_context_commit_property(state, dst_df, + &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); + eax_context_commit_property(state, dst_df, + &EAX40CONTEXTPROPERTIES::flHFReference); - mEaxDf = EaxDirtyFlags{}; + mEaxDf.reset(); } -void ALCcontext::eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df) +void ALCcontext::eax5_context_commit(Eax5State& state, std::bitset& dst_df) { - if(mEaxDf == EaxDirtyFlags{}) + if(mEaxDf.none()) 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); + eax_context_commit_property(state, dst_df, + &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); + eax_context_commit_property(state, dst_df, + &EAX50CONTEXTPROPERTIES::flDistanceFactor); + eax_context_commit_property(state, dst_df, + &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); + eax_context_commit_property(state, dst_df, + &EAX50CONTEXTPROPERTIES::flHFReference); + eax_context_commit_property(state, dst_df, + &EAX50CONTEXTPROPERTIES::flMacroFXFactor); - mEaxDf = EaxDirtyFlags{}; + mEaxDf.reset(); } void ALCcontext::eax_context_commit() { - auto dst_df = EaxDirtyFlags{}; + auto dst_df = std::bitset{}; switch(mEaxVersion) { @@ -981,25 +999,25 @@ void ALCcontext::eax_context_commit() break; } - if(dst_df == EaxDirtyFlags{}) + if(dst_df.none()) return; - if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{}) + if(dst_df.test(eax_primary_fx_slot_id_dirty_bit)) eax_context_commit_primary_fx_slot_id(); - if((dst_df & eax_distance_factor_dirty_bit) != EaxDirtyFlags{}) + if(dst_df.test(eax_distance_factor_dirty_bit)) eax_context_commit_distance_factor(); - if((dst_df & eax_air_absorption_hf_dirty_bit) != EaxDirtyFlags{}) - eax_context_commit_air_absorbtion_hf(); + if(dst_df.test(eax_air_absorption_hf_dirty_bit)) + eax_context_commit_air_absorption_hf(); - if((dst_df & eax_hf_reference_dirty_bit) != EaxDirtyFlags{}) + if(dst_df.test(eax_hf_reference_dirty_bit)) eax_context_commit_hf_reference(); - if((dst_df & eax_macro_fx_factor_dirty_bit) != EaxDirtyFlags{}) + if(dst_df.test(eax_macro_fx_factor_dirty_bit)) eax_context_commit_macro_fx_factor(); - if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{}) + if(dst_df.test(eax_primary_fx_slot_id_dirty_bit)) eax_update_sources(); } diff --git a/Engine/lib/openal-soft/alc/context.h b/Engine/lib/openal-soft/alc/context.h index e1437fb31..3791e0b6d 100644 --- a/Engine/lib/openal-soft/alc/context.h +++ b/Engine/lib/openal-soft/alc/context.h @@ -1,8 +1,10 @@ #ifndef ALC_CONTEXT_H #define ALC_CONTEXT_H -#include +#include "config.h" + #include +#include #include #include #include @@ -18,25 +20,24 @@ #include "AL/alext.h" #include "al/listener.h" -#include "almalloc.h" -#include "alnumeric.h" #include "althreads.h" -#include "atomic.h" #include "core/context.h" -#include "inprogext.h" +#include "fmt/core.h" #include "intrusive_ptr.h" +#include "opthelpers.h" -#ifdef ALSOFT_EAX -#include "al/eax/call.h" +#if ALSOFT_EAX +#include "al/eax/api.h" #include "al/eax/exception.h" #include "al/eax/fx_slot_index.h" #include "al/eax/fx_slots.h" #include "al/eax/utils.h" + +class EaxCall; #endif // ALSOFT_EAX struct ALeffect; struct ALeffectslot; -struct ALsource; struct DebugGroup; struct EffectSlotSubList; struct SourceSubList; @@ -72,9 +73,12 @@ struct DebugLogEntry { }; -struct ALCcontext : public al::intrusive_ref, ContextBase { - const al::intrusive_ptr mALDevice; +namespace al { +struct Device; +} // namespace al +struct ALCcontext final : public al::intrusive_ref, ContextBase { + const al::intrusive_ptr mALDevice; bool mPropsDirty{true}; bool mDeferUpdates{false}; @@ -118,15 +122,15 @@ struct ALCcontext : public al::intrusive_ref, ContextBase { std::unique_ptr mDefaultSlot; std::vector mExtensions; - std::string mExtensionsString{}; + std::string mExtensionsString; std::unordered_map mSourceNames; std::unordered_map mEffectSlotNames; - ALCcontext(al::intrusive_ptr device, ContextFlagBitset flags); + ALCcontext(al::intrusive_ptr device, ContextFlagBitset flags); ALCcontext(const ALCcontext&) = delete; ALCcontext& operator=(const ALCcontext&) = delete; - ~ALCcontext(); + ~ALCcontext() final; void init(); /** @@ -159,12 +163,18 @@ struct ALCcontext : public al::intrusive_ref, ContextBase { */ void applyAllUpdates(); -#ifdef __MINGW32__ - [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] -#else - [[gnu::format(printf, 3, 4)]] -#endif - void setError(ALenum errorCode, const char *msg, ...); + void setErrorImpl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args); + + template + void setError(ALenum errorCode, fmt::format_string msg, Args&& ...args) + { setErrorImpl(errorCode, msg, fmt::make_format_args(args...)); } + + [[noreturn]] + void throw_error_impl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args); + + template [[noreturn]] + void throw_error(ALenum errorCode, fmt::format_string fmt, Args&&... args) + { throw_error_impl(errorCode, fmt, fmt::make_format_args(args...)); } void sendDebugMessage(std::unique_lock &debuglock, DebugSource source, DebugType type, ALuint id, DebugSeverity severity, std::string_view message); @@ -191,6 +201,10 @@ private: */ class ThreadCtx { public: + ThreadCtx() = default; + ThreadCtx(const ThreadCtx&) = delete; + auto operator=(const ThreadCtx&) -> ThreadCtx& = delete; + ~ThreadCtx(); /* NOLINTBEGIN(readability-convert-member-functions-to-static) * This should be non-static to invoke construction of the thread-local @@ -210,7 +224,7 @@ public: /* Default effect that applies to sources that don't have an effect on send 0. */ static ALeffect sDefaultEffect; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX bool hasEax() const noexcept { return mEaxIsInitialized; } bool eaxIsCapable() const noexcept; @@ -232,7 +246,11 @@ public: void eaxSetLastError() noexcept; - EaxFxSlotIndex eaxGetPrimaryFxSlotIndex() const noexcept + [[nodiscard]] + auto eaxGetDistanceFactor() const noexcept -> float { return mEax.flDistanceFactor; } + + [[nodiscard]] + auto eaxGetPrimaryFxSlotIndex() const noexcept -> EaxFxSlotIndex { return mEaxPrimaryFxSlotIndex; } const ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index) const @@ -247,11 +265,14 @@ public: { mEaxFxSlots.commit(); } private: - 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; + enum { + eax_primary_fx_slot_id_dirty_bit, + eax_distance_factor_dirty_bit, + eax_air_absorption_hf_dirty_bit, + eax_hf_reference_dirty_bit, + eax_macro_fx_factor_dirty_bit, + eax_dirty_bit_count + }; using Eax4Props = EAX40CONTEXTPROPERTIES; @@ -267,12 +288,11 @@ private: Eax5Props d; // Deferred. }; - class ContextException : public EaxException - { + class ContextException final : public EaxException { public: - explicit ContextException(const char* message) + explicit ContextException(const char *message) : EaxException{"EAX_CONTEXT", message} - {} + { } }; struct Eax4PrimaryFxSlotIdValidator { @@ -420,7 +440,7 @@ private: int mEaxVersion{}; // Current EAX version. bool mEaxNeedsCommit{}; - EaxDirtyFlags mEaxDf{}; // Dirty flags for the current EAX version. + std::bitset mEaxDf; // Dirty flags for the current EAX version. Eax5State mEax123{}; // EAX1/EAX2/EAX3 state. Eax4State mEax4{}; // EAX4 state. Eax5State mEax5{}; // EAX5 state. @@ -450,7 +470,7 @@ private: // updates a dirty flag. template< typename TValidator, - EaxDirtyFlags TDirtyBit, + size_t DirtyBit, typename TMemberResult, typename TProps, typename TState> @@ -463,20 +483,20 @@ private: dst_d = src; if(dst_i != dst_d) - mEaxDf |= TDirtyBit; + mEaxDf.set(DirtyBit); } template< - EaxDirtyFlags TDirtyBit, + size_t DirtyBit, typename TMemberResult, typename TProps, typename TState> - void eax_context_commit_property(TState& state, EaxDirtyFlags& dst_df, + void eax_context_commit_property(TState& state, std::bitset& dst_df, TMemberResult TProps::*member) noexcept { - if((mEaxDf & TDirtyBit) != EaxDirtyFlags{}) + if(mEaxDf.test(DirtyBit)) { - dst_df |= TDirtyBit; + dst_df.set(DirtyBit); const auto& src_d = state.d.*member; state.i.*member = src_d; mEax.*member = src_d; @@ -514,7 +534,7 @@ private: 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_air_absorption_hf(); void eax_context_commit_hf_reference(); void eax_context_commit_macro_fx_factor(); @@ -529,8 +549,8 @@ private: void eax5_defer(const EaxCall& call, Eax5State& state); void eax_set(const EaxCall& call); - void eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df); - void eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df); + void eax4_context_commit(Eax4State& state, std::bitset& dst_df); + void eax5_context_commit(Eax5State& state, std::bitset& dst_df); void eax_context_commit(); #endif // ALSOFT_EAX }; @@ -545,7 +565,7 @@ void UpdateContextProps(ALCcontext *context); inline bool TrapALError{false}; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX auto AL_APIENTRY EAXSet(const GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum; diff --git a/Engine/lib/openal-soft/alc/device.cpp b/Engine/lib/openal-soft/alc/device.cpp index 86fddcabd..ad6071605 100644 --- a/Engine/lib/openal-soft/alc/device.cpp +++ b/Engine/lib/openal-soft/alc/device.cpp @@ -27,13 +27,14 @@ using voidp = void*; } // namespace +namespace al { -ALCdevice::ALCdevice(DeviceType type) : DeviceBase{type} +Device::Device(DeviceType type) : DeviceBase{type} { } -ALCdevice::~ALCdevice() +Device::~Device() { - TRACE("Freeing device %p\n", voidp{this}); + TRACE("Freeing device {}", voidp{this}); Backend = nullptr; @@ -41,35 +42,35 @@ ALCdevice::~ALCdevice() [](size_t cur, const BufferSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); })}; if(count > 0) - WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s"); + WARN("{} Buffer{} not deleted", count, (count==1)?"":"s"); count = std::accumulate(EffectList.cbegin(), EffectList.cend(), 0_uz, [](size_t cur, const EffectSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) - WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s"); + WARN("{} Effect{} not deleted", count, (count==1)?"":"s"); count = std::accumulate(FilterList.cbegin(), FilterList.cend(), 0_uz, [](size_t cur, const FilterSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) - WARN("%zu Filter%s not deleted\n", count, (count==1)?"":"s"); + WARN("{} Filter{} not deleted", count, (count==1)?"":"s"); } -void ALCdevice::enumerateHrtfs() +void Device::enumerateHrtfs() { mHrtfList = EnumerateHrtf(configValue({}, "hrtf-paths")); if(auto defhrtfopt = configValue({}, "default-hrtf")) { auto iter = std::find(mHrtfList.begin(), mHrtfList.end(), *defhrtfopt); if(iter == mHrtfList.end()) - WARN("Failed to find default HRTF \"%s\"\n", defhrtfopt->c_str()); + WARN("Failed to find default HRTF \"{}\"", *defhrtfopt); else if(iter != mHrtfList.begin()) std::rotate(mHrtfList.begin(), iter, iter+1); } } -auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1 +auto Device::getOutputMode1() const noexcept -> OutputMode1 { if(mContexts.load(std::memory_order_relaxed)->empty()) return OutputMode1::Any; @@ -95,3 +96,5 @@ auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1 } return OutputMode1::Any; } + +} // namespace al diff --git a/Engine/lib/openal-soft/alc/device.h b/Engine/lib/openal-soft/alc/device.h index 47a5513f8..1ca800c8b 100644 --- a/Engine/lib/openal-soft/alc/device.h +++ b/Engine/lib/openal-soft/alc/device.h @@ -1,6 +1,8 @@ #ifndef ALC_DEVICE_H #define ALC_DEVICE_H +#include "config.h" + #include #include #include @@ -18,7 +20,7 @@ #include "core/device.h" #include "intrusive_ptr.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include "al/eax/x_ram.h" #endif // ALSOFT_EAX @@ -30,7 +32,11 @@ struct FilterSubList; using uint = unsigned int; -struct ALCdevice : public al::intrusive_ref, DeviceBase { +struct ALCdevice { virtual ~ALCdevice() = default; }; + +namespace al { + +struct Device final : public ALCdevice, al::intrusive_ref, DeviceBase { /* This lock protects the device state (format, update size, etc) from * being from being changed in multiple threads, or being accessed while * being changed. It's also used to serialize calls to the backend. @@ -80,7 +86,7 @@ struct ALCdevice : public al::intrusive_ref, DeviceBase { std::mutex FilterLock; std::vector FilterList; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX ALuint eax_x_ram_free_size{eax_x_ram_max_size}; #endif // ALSOFT_EAX @@ -89,35 +95,41 @@ struct ALCdevice : public al::intrusive_ref, DeviceBase { std::unordered_map mEffectNames; std::unordered_map mFilterNames; - ALCdevice(DeviceType type); - ~ALCdevice(); + std::string mVendorOverride; + std::string mVersionOverride; + std::string mRendererOverride; + + explicit Device(DeviceType type); + ~Device() final; void enumerateHrtfs(); bool getConfigValueBool(const std::string_view block, const std::string_view key, bool def) - { return GetConfigValueBool(DeviceName, block, key, def); } + { return GetConfigValueBool(mDeviceName, block, key, def); } template - inline std::optional configValue(const std::string_view block, const std::string_view key) = delete; + auto configValue(const std::string_view block, const std::string_view key) -> std::optional = delete; }; -template<> -inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) -{ return ConfigValueStr(DeviceName, block, key); } -template<> -inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) -{ return ConfigValueInt(DeviceName, block, key); } -template<> -inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) -{ return ConfigValueUInt(DeviceName, block, key); } -template<> -inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) -{ return ConfigValueFloat(DeviceName, block, key); } -template<> -inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) -{ return ConfigValueBool(DeviceName, block, key); } +template<> inline +auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional +{ return ConfigValueStr(mDeviceName, block, key); } +template<> inline +auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional +{ return ConfigValueInt(mDeviceName, block, key); } +template<> inline +auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional +{ return ConfigValueUInt(mDeviceName, block, key); } +template<> inline +auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional +{ return ConfigValueFloat(mDeviceName, block, key); } +template<> inline +auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional +{ return ConfigValueBool(mDeviceName, block, key); } + +} // namespace al /** Stores the latest ALC device error. */ -void alcSetError(ALCdevice *device, ALCenum errorCode); +void alcSetError(al::Device *device, ALCenum errorCode); #endif diff --git a/Engine/lib/openal-soft/alc/effects/autowah.cpp b/Engine/lib/openal-soft/alc/effects/autowah.cpp index b208996e7..90006c157 100644 --- a/Engine/lib/openal-soft/alc/effects/autowah.cpp +++ b/Engine/lib/openal-soft/alc/effects/autowah.cpp @@ -122,7 +122,7 @@ void AutowahState::update(const ContextBase *context, const EffectSlot *slot, { auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; - const auto frequency = static_cast(device->Frequency); + const auto frequency = static_cast(device->mSampleRate); const float ReleaseTime{std::clamp(props.ReleaseTime, 0.001f, 1.0f)}; diff --git a/Engine/lib/openal-soft/alc/effects/chorus.cpp b/Engine/lib/openal-soft/alc/effects/chorus.cpp index 4e6d36c55..414dd458c 100644 --- a/Engine/lib/openal-soft/alc/effects/chorus.cpp +++ b/Engine/lib/openal-soft/alc/effects/chorus.cpp @@ -105,7 +105,7 @@ struct ChorusState final : public EffectState { void ChorusState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) { constexpr auto MaxDelay = std::max(ChorusMaxDelay, FlangerMaxDelay); - const auto frequency = static_cast(Device->Frequency); + const auto frequency = static_cast(Device->mSampleRate); const size_t maxlen{NextPowerOf2(float2uint(MaxDelay*2.0f*frequency) + 1u)}; if(maxlen != mDelayBuffer.size()) decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer); @@ -128,7 +128,7 @@ void ChorusState::update(const ContextBase *context, const EffectSlot *slot, * delay and depth to allow enough padding for resampling. */ const DeviceBase *device{context->mDevice}; - const auto frequency = static_cast(device->Frequency); + const auto frequency = static_cast(device->mSampleRate); mWaveform = props.Waveform; diff --git a/Engine/lib/openal-soft/alc/effects/compressor.cpp b/Engine/lib/openal-soft/alc/effects/compressor.cpp index 3197119c7..27245b69c 100644 --- a/Engine/lib/openal-soft/alc/effects/compressor.cpp +++ b/Engine/lib/openal-soft/alc/effects/compressor.cpp @@ -89,8 +89,8 @@ void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage /* Number of samples to do a full attack and release (non-integer sample * counts are okay). */ - const float attackCount{static_cast(device->Frequency) * AttackTime}; - const float releaseCount{static_cast(device->Frequency) * ReleaseTime}; + const float attackCount{static_cast(device->mSampleRate) * AttackTime}; + const float releaseCount{static_cast(device->mSampleRate) * ReleaseTime}; /* Calculate per-sample multipliers to attack and release at the desired * rates. diff --git a/Engine/lib/openal-soft/alc/effects/convolution.cpp b/Engine/lib/openal-soft/alc/effects/convolution.cpp index 7eb9cd6fd..cafa4e6f1 100644 --- a/Engine/lib/openal-soft/alc/effects/convolution.cpp +++ b/Engine/lib/openal-soft/alc/effects/convolution.cpp @@ -1,5 +1,6 @@ #include "config.h" +#include "config_simd.h" #include #include @@ -11,11 +12,10 @@ #include #include #include -#include -#ifdef HAVE_SSE_INTRINSICS +#if HAVE_SSE_INTRINSICS #include -#elif defined(HAVE_NEON) +#elif HAVE_NEON #include #endif @@ -171,7 +171,7 @@ constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2}; void apply_fir(al::span dst, const al::span input, const al::span filter) { auto src = input.begin(); -#ifdef HAVE_SSE_INTRINSICS +#if HAVE_SSE_INTRINSICS std::generate(dst.begin(), dst.end(), [&src,filter] { __m128 r4{_mm_setzero_ps()}; @@ -189,7 +189,7 @@ void apply_fir(al::span dst, const al::span input, const al: return _mm_cvtss_f32(r4); }); -#elif defined(HAVE_NEON) +#elif HAVE_NEON std::generate(dst.begin(), dst.end(), [&src,filter] { @@ -227,7 +227,7 @@ struct ConvolutionState final : public EffectState { al::vector,16> mFilter; al::vector,16> mOutput; - PFFFTSetup mFft{}; + PFFFTSetup mFft; alignas(16) std::array mFftBuffer{}; alignas(16) std::array mFftWorkBuffer{}; @@ -237,7 +237,7 @@ struct ConvolutionState final : public EffectState { struct ChannelData { alignas(16) FloatBufferLine mBuffer{}; float mHfScale{}, mLfScale{}; - BandSplitter mFilter{}; + BandSplitter mFilter; std::array Current{}; std::array Target{}; }; @@ -322,13 +322,13 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag * called very infrequently, go ahead and use the polyphase resampler. */ PPhaseResampler resampler; - if(device->Frequency != buffer->mSampleRate) - resampler.init(buffer->mSampleRate, device->Frequency); + if(device->mSampleRate != buffer->mSampleRate) + resampler.init(buffer->mSampleRate, device->mSampleRate); const auto resampledCount = static_cast( - (uint64_t{buffer->mSampleLen}*device->Frequency+(buffer->mSampleRate-1)) / + (uint64_t{buffer->mSampleLen}*device->mSampleRate+(buffer->mSampleRate-1)) / buffer->mSampleRate); - const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; + const BandSplitter splitter{device->mXOverFreq / static_cast(device->mSampleRate)}; for(auto &e : mChans) e.mFilter = splitter; diff --git a/Engine/lib/openal-soft/alc/effects/distortion.cpp b/Engine/lib/openal-soft/alc/effects/distortion.cpp index 202e4fd79..963ddad11 100644 --- a/Engine/lib/openal-soft/alc/effects/distortion.cpp +++ b/Engine/lib/openal-soft/alc/effects/distortion.cpp @@ -87,7 +87,7 @@ void DistortionState::update(const ContextBase *context, const EffectSlot *slot, /* Divide normalized frequency by the amount of oversampling done during * processing. */ - auto frequency = static_cast(device->Frequency); + auto frequency = static_cast(device->mSampleRate); mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth); cutoff = props.EQCenter; diff --git a/Engine/lib/openal-soft/alc/effects/echo.cpp b/Engine/lib/openal-soft/alc/effects/echo.cpp index a303117d9..6809ddcbb 100644 --- a/Engine/lib/openal-soft/alc/effects/echo.cpp +++ b/Engine/lib/openal-soft/alc/effects/echo.cpp @@ -78,7 +78,7 @@ struct EchoState final : public EffectState { void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) { - const auto frequency = static_cast(Device->Frequency); + const auto frequency = static_cast(Device->mSampleRate); // Use the next power of 2 for the buffer length, so the tap offsets can be // wrapped using a mask instead of a modulo @@ -100,7 +100,7 @@ void EchoState::update(const ContextBase *context, const EffectSlot *slot, { auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; - const auto frequency = static_cast(device->Frequency); + const auto frequency = static_cast(device->mSampleRate); mDelayTap[0] = std::max(float2uint(std::round(props.Delay*frequency)), 1u); mDelayTap[1] = float2uint(std::round(props.LRDelay*frequency)) + mDelayTap[0]; diff --git a/Engine/lib/openal-soft/alc/effects/equalizer.cpp b/Engine/lib/openal-soft/alc/effects/equalizer.cpp index bf951f670..8d7d5068c 100644 --- a/Engine/lib/openal-soft/alc/effects/equalizer.cpp +++ b/Engine/lib/openal-soft/alc/effects/equalizer.cpp @@ -123,7 +123,7 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot, { auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; - auto frequency = static_cast(device->Frequency); + auto frequency = static_cast(device->mSampleRate); /* Calculate coefficients for the each type of filter. Note that the shelf * and peaking filters' gain is for the centerpoint of the transition band, diff --git a/Engine/lib/openal-soft/alc/effects/fshifter.cpp b/Engine/lib/openal-soft/alc/effects/fshifter.cpp index 7790a2435..ee04fd71f 100644 --- a/Engine/lib/openal-soft/alc/effects/fshifter.cpp +++ b/Engine/lib/openal-soft/alc/effects/fshifter.cpp @@ -134,7 +134,7 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot, auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; - const float step{props.Frequency / static_cast(device->Frequency)}; + const float step{props.Frequency / static_cast(device->mSampleRate)}; mPhaseStep[0] = mPhaseStep[1] = fastf2u(std::min(step, 1.0f) * MixerFracOne); switch(props.LeftDirection) diff --git a/Engine/lib/openal-soft/alc/effects/modulator.cpp b/Engine/lib/openal-soft/alc/effects/modulator.cpp index e86a3c5d8..983a945dd 100644 --- a/Engine/lib/openal-soft/alc/effects/modulator.cpp +++ b/Engine/lib/openal-soft/alc/effects/modulator.cpp @@ -122,10 +122,10 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot, * many iterations per sample. */ const float samplesPerCycle{props.Frequency > 0.0f - ? static_cast(device->Frequency)/props.Frequency + 0.5f + ? static_cast(device->mSampleRate)/props.Frequency + 0.5f : 1.0f}; const uint range{static_cast(std::clamp(samplesPerCycle, 1.0f, - static_cast(device->Frequency)))}; + static_cast(device->mSampleRate)))}; mIndex = static_cast(uint64_t{mIndex} * range / mRange); mRange = range; @@ -155,7 +155,7 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot, mSampleGen.emplace(); } - float f0norm{props.HighPassCutoff / static_cast(device->Frequency)}; + float f0norm{props.HighPassCutoff / static_cast(device->mSampleRate)}; f0norm = std::clamp(f0norm, 1.0f/512.0f, 0.49f); /* Bandwidth value is constant in octaves. */ mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f); diff --git a/Engine/lib/openal-soft/alc/effects/reverb.cpp b/Engine/lib/openal-soft/alc/effects/reverb.cpp index 63cb629b2..7e907911b 100644 --- a/Engine/lib/openal-soft/alc/effects/reverb.cpp +++ b/Engine/lib/openal-soft/alc/effects/reverb.cpp @@ -28,8 +28,6 @@ #include #include #include -#include -#include #include "alc/effects/base.h" #include "alnumbers.h" @@ -403,7 +401,7 @@ struct EarlyReflections { */ DelayLineU Delay; std::array Offset{}; - std::array Coeff{}; + float Coeff{}; /* The gain for each output channel based on 3D panning. */ struct OutGains { @@ -502,7 +500,7 @@ struct ReverbPipeline { /* Tap points for early reflection input delay. */ std::array,NUM_LINES> mEarlyDelayTap{}; - std::array,NUM_LINES> mEarlyDelayCoeff{}; + std::array mEarlyDelayCoeff{}; /* Tap points for late reverb feed and delay. */ std::array,NUM_LINES> mLateDelayTap{}; @@ -520,7 +518,7 @@ struct ReverbPipeline { size_t mFadeSampleCount{1}; void updateDelayLine(const float gain, const float earlyDelay, const float lateDelay, - const float density_mult, const float decayTime, const float frequency); + const float density_mult, const float frequency); void update3DPanning(const al::span ReflectionsPan, const al::span LateReverbPan, const float earlyGain, const float lateGain, const bool doUpmix, const MixParams *mainMix); @@ -792,7 +790,7 @@ void ReverbState::allocLines(const float frequency) void ReverbState::deviceUpdate(const DeviceBase *device, const BufferStorage*) { - const auto frequency = static_cast(device->Frequency); + const auto frequency = static_cast(device->mSampleRate); /* Allocate the delay lines. */ allocLines(frequency); @@ -928,10 +926,15 @@ void EarlyReflections::updateLines(const float density_mult, const float diffusi /* Calculate the delay length of each delay line. */ length = EARLY_LINE_LENGTHS[i] * density_mult; Offset[i] = float2uint(length * frequency); - - /* Calculate the gain (coefficient) for each line. */ - Coeff[i] = CalcDecayCoeff(length, decayTime); } + + /* Calculate the gain (coefficient) for the secondary reflections based on + * the average delay and decay time. + */ + const auto length = std::reduce(EARLY_LINE_LENGTHS.begin(), EARLY_LINE_LENGTHS.end(), 0.0f) + / float{EARLY_LINE_LENGTHS.size()} * density_mult; + Coeff = CalcDecayCoeff(length, decayTime); + } /* Update the EAX modulation step and depth. Keep in mind that this kind of @@ -1038,7 +1041,7 @@ void LateReverb::updateLines(const float density_mult, const float diffusion, /* Update the offsets for the main effect delay line. */ void ReverbPipeline::updateDelayLine(const float gain, const float earlyDelay, - const float lateDelay, const float density_mult, const float decayTime, const float frequency) + const float lateDelay, const float density_mult, const float frequency) { /* Early reflection taps are decorrelated by means of an average room * reflection approximation described above the definition of the taps. @@ -1050,11 +1053,11 @@ void ReverbPipeline::updateDelayLine(const float gain, const float earlyDelay, * delay path and offsets that would continue the propagation naturally * into the late lines. */ + mEarlyDelayCoeff[1] = gain; for(size_t i{0u};i < NUM_LINES;i++) { float length{EARLY_TAP_LENGTHS[i]*density_mult}; mEarlyDelayTap[i][1] = float2uint((earlyDelay+length) * frequency); - mEarlyDelayCoeff[i][1] = CalcDecayCoeff(length, decayTime) * gain; /* Reduce the late delay tap by the shortest early delay line length to * compensate for the late line input being fed by the delayed early @@ -1120,7 +1123,8 @@ void ReverbPipeline::update3DPanning(const al::span ReflectionsPa const auto earlymat = GetTransformFromVector(ReflectionsPan); const auto latemat = GetTransformFromVector(LateReverbPan); - const auto [earlycoeffs, latecoeffs] = [&]{ + const auto get_coeffs = [&] + { if(doUpmix) { /* When upsampling, combine the early and late transforms with the @@ -1148,7 +1152,7 @@ void ReverbPipeline::update3DPanning(const al::span ReflectionsPa return res; }; - return std::make_pair(mult_matrix(earlymat), mult_matrix(latemat)); + return std::array{mult_matrix(earlymat), mult_matrix(latemat)}; } /* When not upsampling, combine the early and late A-to-B-Format @@ -1175,8 +1179,9 @@ void ReverbPipeline::update3DPanning(const al::span ReflectionsPa return res; }; - return std::make_pair(mult_matrix(EarlyA2B, earlymat), mult_matrix(LateA2B, latemat)); - }(); + return std::array{mult_matrix(EarlyA2B, earlymat), mult_matrix(LateA2B, latemat)}; + }; + const auto [earlycoeffs, latecoeffs] = get_coeffs(); auto earlygains = mEarly.Gains.begin(); for(auto &coeffs : earlycoeffs) @@ -1191,7 +1196,7 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, { auto &props = std::get(*props_); const DeviceBase *Device{Context->mDevice}; - const auto frequency = static_cast(Device->Frequency); + const auto frequency = static_cast(Device->mSampleRate); /* If the HF limit parameter is flagged, calculate an appropriate limit * based on the air absorption parameter. @@ -1243,8 +1248,7 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, mCurrentPipeline = !mCurrentPipeline; auto &oldpipeline = mPipelines[!mCurrentPipeline]; - for(size_t j{0};j < NUM_LINES;++j) - oldpipeline.mEarlyDelayCoeff[j][1] = 0.0f; + oldpipeline.mEarlyDelayCoeff[1] = 0.0f; } auto &pipeline = mPipelines[mCurrentPipeline]; @@ -1253,7 +1257,7 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, /* Update the main effect delay and associated taps. */ pipeline.updateDelayLine(props.Gain, props.ReflectionsDelay, props.LateReverbDelay, - density_mult, props.DecayTime, frequency); + density_mult, frequency); /* Update early and late 3D panning. */ mOutTarget = target.Main->Buffer; @@ -1507,17 +1511,17 @@ void ReverbPipeline::processEarly(const DelayLineU &main_delay, size_t offset, /* First, load decorrelated samples from the main delay line as the * primary reflections. */ - const auto fadeStep = float{1.0f / static_cast(todo)}; + const auto fadeStep = 1.0f / static_cast(todo); + const auto earlycoeff0 = float{mEarlyDelayCoeff[0]}; + const auto earlycoeff1 = float{mEarlyDelayCoeff[1]}; + mEarlyDelayCoeff[0] = mEarlyDelayCoeff[1]; for(size_t j{0_uz};j < NUM_LINES;j++) { const auto input = in_delay.get(j); auto early_delay_tap0 = size_t{offset - mEarlyDelayTap[j][0]}; auto early_delay_tap1 = size_t{offset - mEarlyDelayTap[j][1]}; mEarlyDelayTap[j][0] = mEarlyDelayTap[j][1]; - const auto coeff0 = float{mEarlyDelayCoeff[j][0]}; - const auto coeff1 = float{mEarlyDelayCoeff[j][1]}; - mEarlyDelayCoeff[j][0] = mEarlyDelayCoeff[j][1]; - auto fadeCount = float{0.0f}; + auto fadeCount = 0.0f; auto tmp = tempSamples[j].begin(); for(size_t i{0_uz};i < todo;) @@ -1529,10 +1533,10 @@ void ReverbPipeline::processEarly(const DelayLineU &main_delay, size_t offset, const auto intap0 = input.subspan(early_delay_tap0, td); const auto intap1 = input.subspan(early_delay_tap1, td); - auto do_blend = [coeff0,coeff1,fadeStep,&fadeCount](const float in0, + auto do_blend = [earlycoeff0,earlycoeff1,fadeStep,&fadeCount](const float in0, const float in1) noexcept -> float { - const auto ret = lerpf(in0*coeff0, in1*coeff1, fadeStep*fadeCount); + const auto ret = lerpf(in0*earlycoeff0, in1*earlycoeff1, fadeStep*fadeCount); fadeCount += 1.0f; return ret; }; @@ -1552,11 +1556,11 @@ void ReverbPipeline::processEarly(const DelayLineU &main_delay, size_t offset, /* Apply a delay and bounce to generate secondary reflections. */ early_delay.writeReflected(offset, tempSamples, todo); + const auto feedb_coeff = mEarly.Coeff; for(size_t j{0_uz};j < NUM_LINES;j++) { const auto input = early_delay.get(j); auto feedb_tap = size_t{offset - mEarly.Offset[j]}; - const auto feedb_coeff = float{mEarly.Coeff[j]}; auto out = outSamples[j].begin() + base; auto tmp = tempSamples[j].begin(); @@ -1597,20 +1601,20 @@ void ReverbPipeline::processEarly(const DelayLineU &main_delay, size_t offset, auto Modulation::calcDelays(size_t todo) -> al::span { - auto idx = uint{Index}; - const auto step = uint{Step}; - const auto depth = float{Depth * float{gCubicTable.sTableSteps}}; + auto idx = Index; + const auto step = Step; + const auto depth = Depth * float{gCubicTable.sTableSteps}; const auto delays = al::span{ModDelays}.first(todo); std::generate(delays.begin(), delays.end(), [step,depth,&idx] { idx += step; - const auto x = float{static_cast(idx&MOD_FRACMASK) * (1.0f/MOD_FRACONE)}; + const auto x = static_cast(idx&MOD_FRACMASK) * (1.0f/MOD_FRACONE); /* Approximate sin(x*2pi). As long as it roughly fits a sinusoid shape * and stays within [-1...+1], it needn't be perfect. */ - const auto lfo = float{!(idx&(MOD_FRACONE>>1)) + const auto lfo = !(idx&(MOD_FRACONE>>1)) ? ((-16.0f * x * x) + (8.0f * x)) - : ((16.0f * x * x) + (-8.0f * x) + (-16.0f * x) + 8.0f)}; + : ((16.0f * x * x) + (-8.0f * x) + (-16.0f * x) + 8.0f); return float2uint((lfo+1.0f) * depth); }); Index = idx; @@ -1655,7 +1659,7 @@ void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo, for(size_t j{0_uz};j < NUM_LINES;++j) { const auto input = late_delay.get(j); - const auto midGain = float{mLate.T60[j].MidGain}; + const auto midGain = mLate.T60[j].MidGain; auto late_feedb_tap = size_t{offset - mLate.Offset[j]}; auto proc_sample = [input,midGain,&late_feedb_tap](const size_t idelay) -> float @@ -1663,7 +1667,7 @@ void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo, /* Calculate the read sample offset and sub-sample offset * between it and the next sample. */ - const auto delay = size_t{late_feedb_tap - (idelay>>gCubicTable.sTableBits)}; + const auto delay = late_feedb_tap - (idelay>>gCubicTable.sTableBits); const auto delayoffset = size_t{idelay & gCubicTable.sTableMask}; ++late_feedb_tap; @@ -1675,10 +1679,10 @@ void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo, const auto out2 = float{input[(delay-2) & (input.size()-1)]}; const auto out3 = float{input[(delay-3) & (input.size()-1)]}; - const auto out = float{out0*gCubicTable.getCoeff0(delayoffset) + const auto out = out0*gCubicTable.getCoeff0(delayoffset) + out1*gCubicTable.getCoeff1(delayoffset) + out2*gCubicTable.getCoeff2(delayoffset) - + out3*gCubicTable.getCoeff3(delayoffset)}; + + out3*gCubicTable.getCoeff3(delayoffset); return out * midGain; }; std::transform(delays.begin(), delays.end(), tempSamples[j].begin(), proc_sample); @@ -1694,10 +1698,10 @@ void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo, auto late_delay_tap0 = size_t{offset - mLateDelayTap[j][0]}; auto late_delay_tap1 = size_t{offset - mLateDelayTap[j][1]}; mLateDelayTap[j][0] = mLateDelayTap[j][1]; - const auto densityGain = float{mLate.DensityGain}; - const auto densityStep = float{late_delay_tap0 != late_delay_tap1 - ? densityGain*fadeStep : 0.0f}; - auto fadeCount = float{0.0f}; + const auto densityGain = mLate.DensityGain; + const auto densityStep = late_delay_tap0 != late_delay_tap1 + ? densityGain*fadeStep : 0.0f; + auto fadeCount = 0.0f; auto samples = tempSamples[j].begin(); for(size_t i{0u};i < todo;) @@ -1766,8 +1770,7 @@ void ReverbState::process(const size_t samplesToDo, const al::span(*props_); const DeviceBase *device{context->mDevice}; - const float frequency{static_cast(device->Frequency)}; + const float frequency{static_cast(device->mSampleRate)}; const float step{props.Rate / frequency}; mStep = fastf2u(std::clamp(step*WaveformFracOne, 0.0f, WaveformFracOne-1.0f)); diff --git a/Engine/lib/openal-soft/alc/events.cpp b/Engine/lib/openal-soft/alc/events.cpp index 1010a3384..dfabcf897 100644 --- a/Engine/lib/openal-soft/alc/events.cpp +++ b/Engine/lib/openal-soft/alc/events.cpp @@ -3,9 +3,11 @@ #include "events.h" +#include "alnumeric.h" #include "alspan.h" #include "core/logging.h" #include "device.h" +#include "fmt/core.h" namespace { @@ -19,7 +21,7 @@ ALCenum EnumFromEventType(const alc::EventType type) case alc::EventType::DeviceRemoved: return ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT; case alc::EventType::Count: break; } - throw std::runtime_error{"Invalid EventType: "+std::to_string(al::to_underlying(type))}; + throw std::runtime_error{fmt::format("Invalid EventType: {}", int{al::to_underlying(type)})}; } } // namespace @@ -74,7 +76,7 @@ FORCE_ALIGN ALCboolean ALC_APIENTRY alcEventControlSOFT(ALCsizei count, const AL auto etype = alc::GetEventType(type); if(!etype) { - WARN("Invalid event type: 0x%04x\n", type); + WARN("Invalid event type: {:#04x}", as_unsigned(type)); alcSetError(nullptr, ALC_INVALID_ENUM); return ALC_FALSE; } diff --git a/Engine/lib/openal-soft/alc/export_list.h b/Engine/lib/openal-soft/alc/export_list.h index 2c291ac0d..b83f2c38a 100644 --- a/Engine/lib/openal-soft/alc/export_list.h +++ b/Engine/lib/openal-soft/alc/export_list.h @@ -1,12 +1,14 @@ #ifndef ALC_EXPORT_LIST_H #define ALC_EXPORT_LIST_H +#include "config.h" + #include "AL/alc.h" #include "AL/al.h" #include "AL/alext.h" #include "inprogext.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include "context.h" #include "al/eax/x_ram.h" #endif @@ -215,6 +217,10 @@ inline const FuncExport alcFunctions[]{ DECL(alPushDebugGroupEXT), DECL(alPopDebugGroupEXT), DECL(alGetDebugMessageLogEXT), + DECL(alObjectLabelEXT), + DECL(alGetObjectLabelEXT), + DECL(alGetPointerEXT), + DECL(alGetPointervEXT), /* Direct Context functions */ DECL(alcGetProcAddress2), @@ -365,15 +371,15 @@ inline const FuncExport alcFunctions[]{ DECL(alPushDebugGroupDirectEXT), DECL(alPopDebugGroupDirectEXT), DECL(alGetDebugMessageLogDirectEXT), - DECL(alObjectLabelEXT), DECL(alObjectLabelDirectEXT), - DECL(alGetObjectLabelEXT), DECL(alGetObjectLabelDirectEXT), + DECL(alGetPointerDirectEXT), + DECL(alGetPointervDirectEXT), /* Extra functions */ DECL(alsoft_set_log_callback), }; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX inline const std::array eaxFunctions{ DECL(EAXGet), DECL(EAXSet), @@ -627,6 +633,8 @@ inline const EnumExport alcEnumerations[]{ DECL(AL_FORMAT_51CHN_I32), DECL(AL_FORMAT_61CHN_I32), DECL(AL_FORMAT_71CHN_I32), + DECL(AL_FORMAT_BFORMAT2D_I32), + DECL(AL_FORMAT_BFORMAT3D_I32), DECL(AL_FORMAT_UHJ2CHN_I32_SOFT), DECL(AL_FORMAT_UHJ3CHN_I32_SOFT), DECL(AL_FORMAT_UHJ4CHN_I32_SOFT), @@ -903,7 +911,7 @@ inline const EnumExport alcEnumerations[]{ DECL(AL_STOP_SOURCES_ON_DISCONNECT_SOFT), }; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX inline const std::array eaxEnumerations{ DECL(AL_EAX_RAM_SIZE), DECL(AL_EAX_RAM_FREE), @@ -911,7 +919,7 @@ inline const std::array eaxEnumerations{ DECL(AL_STORAGE_HARDWARE), DECL(AL_STORAGE_ACCESSIBLE), }; -#endif // ALSOFT_EAX +#endif #undef DECL #endif /* ALC_EXPORT_LIST_H */ diff --git a/Engine/lib/openal-soft/alc/inprogext.h b/Engine/lib/openal-soft/alc/inprogext.h index ba5d4351f..eb421fd63 100644 --- a/Engine/lib/openal-soft/alc/inprogext.h +++ b/Engine/lib/openal-soft/alc/inprogext.h @@ -37,11 +37,6 @@ void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffe #endif #endif -#ifndef AL_SOFT_bformat_hoa -#define AL_SOFT_bformat_hoa -#define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D -#endif - #ifndef AL_SOFT_convolution_effect #define AL_SOFT_convolution_effect #define AL_EFFECT_CONVOLUTION_SOFT 0xA000 @@ -70,15 +65,18 @@ void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffe #define AL_FORMAT_71CHN_I32 0x19E5 #define AL_FORMAT_71CHN_FLOAT32 0x19E6 -#define AL_FORMAT_UHJ2CHN_I32_SOFT 0x19E7 -#define AL_FORMAT_UHJ3CHN_I32_SOFT 0x19E8 -#define AL_FORMAT_UHJ4CHN_I32_SOFT 0x19E9 +#define AL_FORMAT_BFORMAT2D_I32 0x19E7 +#define AL_FORMAT_BFORMAT3D_I32 0x19E8 + +#define AL_FORMAT_UHJ2CHN_I32_SOFT 0x19E9 +#define AL_FORMAT_UHJ3CHN_I32_SOFT 0x19EA +#define AL_FORMAT_UHJ4CHN_I32_SOFT 0x19EB #endif #ifndef AL_SOFT_source_panning #define AL_SOFT_source_panning -#define AL_PANNING_ENABLED_SOFT 0x19EA -#define AL_PAN_SOFT 0x19EB +#define AL_PANNING_ENABLED_SOFT 0x19EC +#define AL_PAN_SOFT 0x19ED #endif /* Non-standard exports. Not part of any extension. */ @@ -91,6 +89,11 @@ void ALC_APIENTRY alsoft_set_log_callback(LPALSOFTLOGCALLBACK callback, void *us AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb, const ALuint *buffers) noexcept; +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid) noexcept; +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids) noexcept; +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid) noexcept; +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids) noexcept; + AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) AL_API_NOEXCEPT; ALint64SOFT AL_APIENTRY alGetInteger64DirectSOFT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT; diff --git a/Engine/lib/openal-soft/alc/panning.cpp b/Engine/lib/openal-soft/alc/panning.cpp index 83a410ba2..bf0736de9 100644 --- a/Engine/lib/openal-soft/alc/panning.cpp +++ b/Engine/lib/openal-soft/alc/panning.cpp @@ -76,53 +76,77 @@ using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::nanoseconds; -const char *GetLabelFromChannel(Channel channel) +[[nodiscard]] +auto GetLabelFromChannel(Channel channel) -> std::string_view { switch(channel) { - case FrontLeft: return "front-left"; - case FrontRight: return "front-right"; - case FrontCenter: return "front-center"; - case LFE: return "lfe"; - case BackLeft: return "back-left"; - case BackRight: return "back-right"; - case BackCenter: return "back-center"; - case SideLeft: return "side-left"; - case SideRight: return "side-right"; + case FrontLeft: return "front-left"sv; + case FrontRight: return "front-right"sv; + case FrontCenter: return "front-center"sv; + case LFE: return "lfe"sv; + case BackLeft: return "back-left"sv; + case BackRight: return "back-right"sv; + case BackCenter: return "back-center"sv; + case SideLeft: return "side-left"sv; + case SideRight: return "side-right"sv; - case TopFrontLeft: return "top-front-left"; - case TopFrontCenter: return "top-front-center"; - case TopFrontRight: return "top-front-right"; - case TopCenter: return "top-center"; - case TopBackLeft: return "top-back-left"; - case TopBackCenter: return "top-back-center"; - case TopBackRight: return "top-back-right"; + case TopFrontLeft: return "top-front-left"sv; + case TopFrontCenter: return "top-front-center"sv; + case TopFrontRight: return "top-front-right"sv; + case TopCenter: return "top-center"sv; + case TopBackLeft: return "top-back-left"sv; + case TopBackCenter: return "top-back-center"sv; + case TopBackRight: return "top-back-right"sv; - case BottomFrontLeft: return "bottom-front-left"; - case BottomFrontRight: return "bottom-front-right"; - case BottomBackLeft: return "bottom-back-left"; - case BottomBackRight: return "bottom-back-right"; + case BottomFrontLeft: return "bottom-front-left"sv; + case BottomFrontRight: return "bottom-front-right"sv; + case BottomBackLeft: return "bottom-back-left"sv; + case BottomBackRight: return "bottom-back-right"sv; - case Aux0: return "Aux0"; - case Aux1: return "Aux1"; - case Aux2: return "Aux2"; - case Aux3: return "Aux3"; - case Aux4: return "Aux4"; - case Aux5: return "Aux5"; - case Aux6: return "Aux6"; - case Aux7: return "Aux7"; - case Aux8: return "Aux8"; - case Aux9: return "Aux9"; - case Aux10: return "Aux10"; - case Aux11: return "Aux11"; - case Aux12: return "Aux12"; - case Aux13: return "Aux13"; - case Aux14: return "Aux14"; - case Aux15: return "Aux15"; + case Aux0: return "Aux0"sv; + case Aux1: return "Aux1"sv; + case Aux2: return "Aux2"sv; + case Aux3: return "Aux3"sv; + case Aux4: return "Aux4"sv; + case Aux5: return "Aux5"sv; + case Aux6: return "Aux6"sv; + case Aux7: return "Aux7"sv; + case Aux8: return "Aux8"sv; + case Aux9: return "Aux9"sv; + case Aux10: return "Aux10"sv; + case Aux11: return "Aux11"sv; + case Aux12: return "Aux12"sv; + case Aux13: return "Aux13"sv; + case Aux14: return "Aux14"sv; + case Aux15: return "Aux15"sv; - case MaxChannels: break; + case MaxChannels: break; } - return "(unknown)"; + return "(unknown)"sv; +} + +[[nodiscard]] +auto GetLayoutName(DevAmbiLayout layout) noexcept -> std::string_view +{ + switch(layout) + { + case DevAmbiLayout::FuMa: return "FuMa"sv; + case DevAmbiLayout::ACN: return "ACN"sv; + } + return ""sv; +} + +[[nodiscard]] +auto GetScalingName(DevAmbiScaling scaling) noexcept -> std::string_view +{ + switch(scaling) + { + case DevAmbiScaling::FuMa: return "FuMa"sv; + case DevAmbiScaling::SN3D: return "SN3D"sv; + case DevAmbiScaling::N3D: return "N3D"sv; + } + return ""sv; } @@ -140,14 +164,14 @@ std::unique_ptr CreateStablizer(const size_t outchans, const uin return stablizer; } -void AllocChannels(ALCdevice *device, const size_t main_chans, const size_t real_chans) +void AllocChannels(al::Device *device, const size_t main_chans, const size_t real_chans) { - TRACE("Channel config, Main: %zu, Real: %zu\n", main_chans, real_chans); + TRACE("Channel config, Main: {}, Real: {}", main_chans, real_chans); /* Allocate extra channels for any post-filter output. */ const size_t num_chans{main_chans + real_chans}; - TRACE("Allocating %zu channels, %zu bytes\n", num_chans, + TRACE("Allocating {} channels, {} bytes", num_chans, num_chans*sizeof(device->MixBuffer[0])); device->MixBuffer.resize(num_chans); al::span buffer{device->MixBuffer}; @@ -239,7 +263,8 @@ struct DecoderConfig { using DecoderView = DecoderConfig; -void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint order, const bool is3d) +void InitNearFieldCtrl(al::Device *device, const float ctrl_dist, const uint order, + const bool is3d) { static const std::array chans_per_order2d{{1, 2, 2, 2}}; static const std::array chans_per_order3d{{1, 3, 5, 7}}; @@ -249,10 +274,10 @@ void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint orde return; device->AvgSpeakerDist = std::clamp(ctrl_dist, 0.1f, 10.0f); - TRACE("Using near-field reference distance: %.2f meters\n", device->AvgSpeakerDist); + TRACE("Using near-field reference distance: {:.2f} meters", device->AvgSpeakerDist); const float w1{SpeedOfSoundMetersPerSec / - (device->AvgSpeakerDist * static_cast(device->Frequency))}; + (device->AvgSpeakerDist * static_cast(device->mSampleRate))}; device->mNFCtrlFilter.init(w1); auto iter = std::copy_n(is3d ? chans_per_order3d.begin() : chans_per_order2d.begin(), order+1u, @@ -260,7 +285,7 @@ void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint orde std::fill(iter, device->NumChannelsPerOrder.end(), 0u); } -void InitDistanceComp(ALCdevice *device, const al::span channels, +void InitDistanceComp(al::Device *device, const al::span channels, const al::span dists) { const float maxdist{std::accumulate(dists.begin(), dists.end(), 0.0f, @@ -269,7 +294,7 @@ void InitDistanceComp(ALCdevice *device, const al::span channels, if(!device->getConfigValueBool("decoder", "distance-comp", true) || !(maxdist > 0.0f)) return; - const auto distSampleScale = static_cast(device->Frequency) / SpeedOfSoundMetersPerSec; + const auto distSampleScale = static_cast(device->mSampleRate)/SpeedOfSoundMetersPerSec; struct DistCoeffs { uint Length{}; float Gain{}; }; std::vector ChanDelay; @@ -294,7 +319,7 @@ void InitDistanceComp(ALCdevice *device, const al::span channels, float delay{std::floor((maxdist - distance)*distSampleScale + 0.5f)}; if(delay > float{DistanceComp::MaxDelay-1}) { - ERR("Delay for channel %zu (%s) exceeds buffer length (%f > %d)\n", idx, + ERR("Delay for channel {} ({}) exceeds buffer length ({:f} > {})", idx, GetLabelFromChannel(ch), delay, DistanceComp::MaxDelay-1); delay = float{DistanceComp::MaxDelay-1}; } @@ -302,7 +327,7 @@ void InitDistanceComp(ALCdevice *device, const al::span channels, ChanDelay.resize(std::max(ChanDelay.size(), idx+1_uz)); ChanDelay[idx].Length = static_cast(delay); ChanDelay[idx].Gain = distance / maxdist; - TRACE("Channel %s distance comp: %u samples, %f gain\n", GetLabelFromChannel(ch), + TRACE("Channel {} distance comp: {} samples, {:f} gain", GetLabelFromChannel(ch), ChanDelay[idx].Length, ChanDelay[idx].Gain); /* Round up to the next 4th sample, so each channel buffer starts @@ -345,8 +370,8 @@ constexpr auto GetAmbiLayout(DevAmbiLayout layouttype) noexcept } -DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, - DecoderConfig &decoder) +auto MakeDecoderView(al::Device *device, const AmbDecConf *conf, + DecoderConfig &decoder) -> DecoderView { DecoderView ret{}; @@ -441,10 +466,11 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, { int idx{}; char c{}; + /* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */ if(sscanf(speaker.Name.c_str(), "AUX%d%c", &idx, &c) != 1 || idx < 0 || idx >= MaxChannels-Aux0) { - ERR("AmbDec speaker label \"%s\" not recognized\n", speaker.Name.c_str()); + ERR("AmbDec speaker label \"{}\" not recognized", speaker.Name); continue; } ch = static_cast(Aux0+idx); @@ -649,7 +675,7 @@ constexpr DecoderConfig X7144Config{ }} }; -void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=false, +void InitPanning(al::Device *device, const bool hqdec=false, const bool stablize=false, DecoderView decoder={}) { if(!decoder) @@ -682,10 +708,13 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= avg_dist = *distopt; else if(auto delayopt = device->configValue("decoder", "nfc-ref-delay")) { - WARN("nfc-ref-delay is deprecated, use speaker-dist instead\n"); + WARN("nfc-ref-delay is deprecated, use speaker-dist instead"); avg_dist = *delayopt * SpeedOfSoundMetersPerSec; } + TRACE("{}{} order ambisonic output ({} layout, {} scaling)", device->mAmbiOrder, + GetCounterSuffix(device->mAmbiOrder), GetLayoutName(device->mAmbiLayout), + GetScalingName(device->mAmbiScale)); InitNearFieldCtrl(device, avg_dist, device->mAmbiOrder, true); return; } @@ -700,7 +729,7 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= const size_t idx{device->channelIdxByName(decoder.mChannels[i])}; if(idx == InvalidChannelIndex) { - ERR("Failed to find %s channel in device\n", + ERR("Failed to find {} channel in device", GetLabelFromChannel(decoder.mChannels[i])); continue; } @@ -756,22 +785,21 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= } if(!hasfc) { - stablizer = CreateStablizer(device->channelsFromFmt(), device->Frequency); - TRACE("Front stablizer enabled\n"); + stablizer = CreateStablizer(device->channelsFromFmt(), device->mSampleRate); + TRACE("Front stablizer enabled"); } } - TRACE("Enabling %s-band %s-order%s ambisonic decoder\n", - !dual_band ? "single" : "dual", + TRACE("Enabling {}-band {}-order{} ambisonic decoder", !dual_band ? "single" : "dual", (decoder.mOrder > 3) ? "fourth" : (decoder.mOrder > 2) ? "third" : (decoder.mOrder > 1) ? "second" : "first", decoder.mIs3D ? " periphonic" : ""); device->AmbiDecoder = BFormatDec::Create(ambicount, chancoeffs, chancoeffslf, - device->mXOverFreq/static_cast(device->Frequency), std::move(stablizer)); + device->mXOverFreq/static_cast(device->mSampleRate), std::move(stablizer)); } -void InitHrtfPanning(ALCdevice *device) +void InitHrtfPanning(al::Device *device) { static constexpr float Deg180{al::numbers::pi_v}; static constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/}; @@ -929,7 +957,7 @@ void InitHrtfPanning(ALCdevice *device) std::string_view mode{*modeopt}; if(al::case_compare(mode, "basic"sv) == 0) { - ERR("HRTF mode \"%s\" deprecated, substituting \"%s\"\n", modeopt->c_str(), "ambi2"); + ERR("HRTF mode \"{}\" deprecated, substituting \"{}\"", *modeopt, "ambi2"); mode = "ambi2"; } @@ -937,16 +965,16 @@ void InitHrtfPanning(ALCdevice *device) { return al::case_compare(mode, entry.name) == 0; }; auto iter = std::find_if(hrtf_modes.begin(), hrtf_modes.end(), match_entry); if(iter == hrtf_modes.end()) - ERR("Unexpected hrtf-mode: %s\n", modeopt->c_str()); + ERR("Unexpected hrtf-mode: {}", *modeopt); else { device->mRenderMode = iter->mode; ambi_order = iter->order; } } - TRACE("%u%s order %sHRTF rendering enabled, using \"%s\"\n", ambi_order, + TRACE("{}{} order {}HRTF rendering enabled, using \"{}\"", ambi_order, GetCounterSuffix(ambi_order), (device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "", - device->mHrtfName.c_str()); + device->mHrtfName); bool perHrirMin{false}; auto AmbiPoints = al::span{AmbiPoints1O}.subspan(0); @@ -983,7 +1011,7 @@ void InitHrtfPanning(ALCdevice *device) InitNearFieldCtrl(device, Hrtf->mFields[0].distance, ambi_order, true); } -void InitUhjPanning(ALCdevice *device) +void InitUhjPanning(al::Device *device) { /* UHJ is always 2D first-order. */ static constexpr size_t count{Ambi2DChannelsFromOrder(1)}; @@ -996,11 +1024,21 @@ void InitUhjPanning(ALCdevice *device) [](const uint8_t &acn) noexcept -> BFChannelConfig { return BFChannelConfig{1.0f/AmbiScale::FromUHJ[acn], acn}; }); AllocChannels(device, count, device->channelsFromFmt()); + + /* TODO: Should this default to something else? This is simply a regular + * (first-order) B-Format mixing which just happens to be UHJ-encoded. As I + * understand it, a proper first-order B-Format signal essentially has an + * infinite control distance, which we can't really do. However, from what + * I've read, 2 meters or so should be sufficient as the near-field + * reference becomes inconsequential beyond that. + */ + const auto spkr_dist = ConfigValueFloat({}, "uhj"sv, "distance-ref"sv).value_or(2.0f); + InitNearFieldCtrl(device, spkr_dist, device->mAmbiOrder, !device->m2DMixing); } } // namespace -void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional stereomode) +void aluInitRenderer(al::Device *device, int hrtf_id, std::optional stereomode) { /* Hold the HRTF the device last used, in case it's used again. */ HrtfStorePtr old_hrtf{std::move(device->mHrtf)}; @@ -1044,25 +1082,25 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optionalc_str()); + ERR("Failed to load layout file {}", config); + ERR(" {}", *err); return false; } if(conf.Speakers.size() > MaxOutputChannels) { - ERR("Unsupported decoder speaker count %zu (max %zu)\n", conf.Speakers.size(), + ERR("Unsupported decoder speaker count {} (max {})", conf.Speakers.size(), MaxOutputChannels); return false; } if(conf.ChanMask > Ambi3OrderMask) { - ERR("Unsupported decoder channel mask 0x%04x (max 0x%x)\n", conf.ChanMask, + ERR("Unsupported decoder channel mask {:#x} (max {:#x})", conf.ChanMask, Ambi3OrderMask); return false; } - TRACE("Using %s decoder: \"%s\"\n", DevFmtChannelsString(device->FmtChans), - conf.Description.c_str()); + TRACE("Using {} decoder: \"{}\"", DevFmtChannelsString(device->FmtChans), + conf.Description); device->mXOverFreq = std::clamp(conf.XOverFreq, 100.0f, 1000.0f); decoder_store = std::make_unique>(); @@ -1081,7 +1119,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optionalc_str()); } if(!usingCustom && device->FmtChans != DevFmtAmbi3D) - TRACE("Using built-in %s decoder\n", DevFmtChannelsString(device->FmtChans)); + TRACE("Using built-in {} decoder", DevFmtChannelsString(device->FmtChans)); /* Enable the stablizer only for formats that have front-left, front- * right, and front-center outputs. @@ -1113,8 +1151,8 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optionalAmbiDecoder.get()}) { - device->PostProcess = ambidec->hasStablizer() ? &ALCdevice::ProcessAmbiDecStablized - : &ALCdevice::ProcessAmbiDec; + device->PostProcess = ambidec->hasStablizer() ? &al::Device::ProcessAmbiDecStablized + : &al::Device::ProcessAmbiDec; } return; } @@ -1132,7 +1170,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional= 0 && static_cast(hrtf_id) < device->mHrtfList.size()) { const std::string_view hrtfname{device->mHrtfList[static_cast(hrtf_id)]}; - if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) + if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->mSampleRate)}) { device->mHrtf = std::move(hrtf); device->mHrtfName = hrtfname; @@ -1143,7 +1181,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optionalmHrtfList) { - if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) + if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->mSampleRate)}) { device->mHrtf = std::move(hrtf); device->mHrtfName = hrtfname; @@ -1165,7 +1203,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optionalPostProcess = &ALCdevice::ProcessHrtf; + device->PostProcess = &al::Device::ProcessHrtf; device->mHrtfStatus = ALC_HRTF_ENABLED_SOFT; return; } @@ -1174,23 +1212,27 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optionalmUhjEncoder = std::make_unique(); + ftype = "IIR"sv; break; case UhjQualityType::FIR256: device->mUhjEncoder = std::make_unique>(); + ftype = "FIR-256"sv; break; case UhjQualityType::FIR512: device->mUhjEncoder = std::make_unique>(); + ftype = "FIR-512"sv; break; } assert(device->mUhjEncoder != nullptr); - TRACE("UHJ enabled\n"); + TRACE("UHJ enabled ({} encoder)", ftype); InitUhjPanning(device); - device->PostProcess = &ALCdevice::ProcessUhj; + device->PostProcess = &al::Device::ProcessUhj; return; } @@ -1202,19 +1244,19 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional 0 && *cflevopt <= 6) { auto bs2b = std::make_unique(); - bs2b->set_params(*cflevopt, static_cast(device->Frequency)); + bs2b->set_params(*cflevopt, static_cast(device->mSampleRate)); device->Bs2b = std::move(bs2b); - TRACE("BS2B enabled\n"); + TRACE("BS2B enabled"); InitPanning(device); - device->PostProcess = &ALCdevice::ProcessBs2b; + device->PostProcess = &al::Device::ProcessBs2b; return; } } } - TRACE("Stereo rendering\n"); + TRACE("Stereo rendering"); InitPanning(device); - device->PostProcess = &ALCdevice::ProcessAmbiDec; + device->PostProcess = &al::Device::ProcessAmbiDec; } diff --git a/Engine/lib/openal-soft/alsoftrc.sample b/Engine/lib/openal-soft/alsoftrc.sample index dd3ff8660..617fb617b 100644 --- a/Engine/lib/openal-soft/alsoftrc.sample +++ b/Engine/lib/openal-soft/alsoftrc.sample @@ -189,7 +189,11 @@ # between 24 and 48 points, with anti-aliasing) # fast_bsinc24 - same as bsinc24, except without interpolation between down- # sampling scales -#resampler = gaussian +# bsinc48 - extrapolates samples using a band-limited Sinc filter (48 points, +# with anti-aliasing) +# fast_bsinc48 - same as bsinc48, except without interpolation between down- +# sampling scales +#resampler = spline ## rt-prio: (global) # Sets the real-time priority value for the mixing thread. Not all drivers may @@ -587,6 +591,13 @@ # configurations. Very experimental. #spatial-api = false +## exclusive-mode: +# Enables Exlusive mode for playback devices. This uses the device directly, +# allowing lower latencies but prevents the device from being used multiple +# times simultaneously. Ignores the periods setting when enabled, as WASAPI +# automatically sets a buffer size based on the period size. +#exclusive-mode = false + ## allow-resampler: # Specifies whether to allow an extra resampler pass on the output. Enabling # this will allow the playback device to be set to a different sample rate @@ -645,6 +656,11 @@ # Sets whether to enable EAX extensions or not. #enable = true +## trace-commits: (global) +# Sets whether log EAX property commits with trace messages. This can +# significantly increase the amount of log messages for apps that use EAX. +#trace-commits = false + ## ## Per-game compatibility options (these should only be set in per-game config ## files, *NOT* system- or user-level!) @@ -686,3 +702,15 @@ ## reverse-z: (global) # Reverses the local Z (front-back) position of 3D sound sources. #reverse-z = false + +## vendor-override: +# Overrides the string returned by alGetString(AL_VENDOR). +#vendor-override = + +## version-override: +# Overrides the string returned by alGetString(AL_VERSION). +#version-override = + +## renderer-override: +# Overrides the string returned by alGetString(AL_RENDERER). +#renderer-override = diff --git a/Engine/lib/openal-soft/appveyor.yml b/Engine/lib/openal-soft/appveyor.yml index 8ad0f6bf1..ed0f07a3b 100644 --- a/Engine/lib/openal-soft/appveyor.yml +++ b/Engine/lib/openal-soft/appveyor.yml @@ -1,4 +1,4 @@ -version: 1.23.1.{build} +version: 1.24.3.{build} environment: APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 diff --git a/Engine/lib/openal-soft/cmake/FindFFmpeg.cmake b/Engine/lib/openal-soft/cmake/FindFFmpeg.cmake index 26ed4d2fa..d243defdc 100644 --- a/Engine/lib/openal-soft/cmake/FindFFmpeg.cmake +++ b/Engine/lib/openal-soft/cmake/FindFFmpeg.cmake @@ -80,8 +80,37 @@ macro(find_component _component _pkgconfig _library _header) ${PC_LIB${_component}_LIBRARY_DIRS} ) - 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) + if(DEFINED ${PC_${_component}_VERSION}) + set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING + "The ${_component} version number." FORCE) + elseif(EXISTS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version.h") + if(EXISTS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version_major.h") + file(STRINGS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version_major.h" majorver + REGEX "^#define[ \t]+LIB${_component}_VERSION_MAJOR[ \t]+[0-9]+$") + else() + file(STRINGS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version.h" majorver + REGEX "^#define[ \t]+LIB${_component}_VERSION_MAJOR[ \t]+[0-9]+$") + endif() + file(STRINGS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version.h" minorver + REGEX "^#define[ \t]+LIB${_component}_VERSION_MINOR[ \t]+[0-9]+$") + file(STRINGS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version.h" microver + REGEX "^#define[ \t]+LIB${_component}_VERSION_MICRO[ \t]+[0-9]+$") + + string(REGEX REPLACE "^#define[ \t]+LIB${_component}_VERSION_MAJOR[ \t]+([0-9]+)$" "\\1" + majorver "${majorver}") + string(REGEX REPLACE "^#define[ \t]+LIB${_component}_VERSION_MINOR[ \t]+([0-9]+)$" "\\1" + minorver "${minorver}") + string(REGEX REPLACE "^#define[ \t]+LIB${_component}_VERSION_MICRO[ \t]+([0-9]+)$" "\\1" + microver "${microver}") + + set(${_component}_VERSION "${majorver}.${minorver}.${microver}" CACHE STRING + "The ${_component} version number." FORCE) + unset(microver) + unset(minorver) + unset(majorver) + endif() + set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING + "The ${_component} CFLAGS." FORCE) set_component_found(${_component}) diff --git a/Engine/lib/openal-soft/cmake/FindJACK.cmake b/Engine/lib/openal-soft/cmake/FindJACK.cmake index b72fe3f9a..c85d0643f 100644 --- a/Engine/lib/openal-soft/cmake/FindJACK.cmake +++ b/Engine/lib/openal-soft/cmake/FindJACK.cmake @@ -43,7 +43,7 @@ find_path(JACK_INCLUDE_DIR NAMES jack/jack.h DOC "The JACK include directory" ) -find_library(JACK_LIBRARY NAMES jack +find_library(JACK_LIBRARY NAMES jack jack64 DOC "The JACK library" ) diff --git a/Engine/lib/openal-soft/common/alassert.cpp b/Engine/lib/openal-soft/common/alassert.cpp index f50642308..aa44841e4 100644 --- a/Engine/lib/openal-soft/common/alassert.cpp +++ b/Engine/lib/openal-soft/common/alassert.cpp @@ -1,38 +1,44 @@ #include "alassert.h" -#include #include #include +namespace { + +[[noreturn]] +void throw_error(const std::string &message) +{ + throw std::runtime_error{message}; +} + +} /* namespace */ + namespace al { [[noreturn]] void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept { - std::string errstr{filename}; + /* Throwing an exception that tries to leave a noexcept function will + * hopefully cause the system to provide info about the caught exception in + * an error dialog. At least on Linux, this results in the process printing + * + * terminate called after throwing an instance of 'std::runtime_error' + * what(): + * + * before terminating from a SIGABRT. Hopefully Windows and Mac will do the + * appropriate things with the message to alert the user about an abnormal + * termination. + */ + auto errstr = std::string{filename}; errstr += ':'; errstr += std::to_string(linenum); errstr += ": "; errstr += funcname; errstr += ": "; errstr += message; - /* Calling std::terminate in a catch block hopefully causes the system to - * provide info about the caught exception in the error dialog. At least on - * Linux, this results in the process printing - * - * terminate called after throwing an instance of 'std::runtime_error' - * what(): - * - * before terminating from a SIGABRT. Hopefully Windows and Mac will do the - * appropriate things with the message for an abnormal termination. - */ - try { - throw std::runtime_error{errstr}; - } - catch(...) { - std::terminate(); - } + + throw_error(errstr); } } /* namespace al */ diff --git a/Engine/lib/openal-soft/common/albit.h b/Engine/lib/openal-soft/common/albit.h index 98544fa52..b5c10d718 100644 --- a/Engine/lib/openal-soft/common/albit.h +++ b/Engine/lib/openal-soft/common/albit.h @@ -1,6 +1,7 @@ #ifndef AL_BIT_H #define AL_BIT_H +#include #include #ifndef __GNUC__ #include @@ -25,6 +26,16 @@ To> bit_cast(const From &src) noexcept return *std::launder(reinterpret_cast(dst.data())); } +template +std::enable_if_t, +T> byteswap(T value) noexcept +{ + static_assert(std::has_unique_object_representations_v); + auto bytes = al::bit_cast>(value); + std::reverse(bytes.begin(), bytes.end()); + return al::bit_cast(bytes); +} + #ifdef __BYTE_ORDER__ enum class endian { little = __ORDER_LITTLE_ENDIAN__, diff --git a/Engine/lib/openal-soft/common/alcomplex.cpp b/Engine/lib/openal-soft/common/alcomplex.cpp index 954c5fadb..ae8bc41ce 100644 --- a/Engine/lib/openal-soft/common/alcomplex.cpp +++ b/Engine/lib/openal-soft/common/alcomplex.cpp @@ -20,7 +20,7 @@ namespace { using ushort = unsigned short; -using ushort2 = std::pair; +using ushort2 = std::array; using complex_d = std::complex; constexpr std::size_t BitReverseCounter(std::size_t log2_size) noexcept @@ -56,8 +56,8 @@ struct BitReverser { if(idx < revidx) { - mData[ret_i].first = static_cast(idx); - mData[ret_i].second = static_cast(revidx); + mData[ret_i][0] = static_cast(idx); + mData[ret_i][1] = static_cast(revidx); ++ret_i; } } @@ -122,7 +122,7 @@ void complex_fft(const al::span> buffer, const double sign) if(log2_size < gBitReverses.size()) LIKELY { for(auto &rev : gBitReverses[log2_size]) - std::swap(buffer[rev.first], buffer[rev.second]); + std::swap(buffer[rev[0]], buffer[rev[1]]); /* Iterative form of Danielson-Lanczos lemma */ for(std::size_t i{0};i < log2_size;++i) diff --git a/Engine/lib/openal-soft/common/almalloc.h b/Engine/lib/openal-soft/common/almalloc.h index e3a7bb521..bd30fe156 100644 --- a/Engine/lib/openal-soft/common/almalloc.h +++ b/Engine/lib/openal-soft/common/almalloc.h @@ -123,31 +123,74 @@ class out_ptr_t { static_assert(!std::is_same_v); SP &mRes; - std::variant mPtr{}; + std::variant mPtr; public: - out_ptr_t(SP &res) : mRes{res} { } - ~out_ptr_t() - { - auto set_res = [this](auto &ptr) - { mRes.reset(static_cast(ptr)); }; - std::visit(set_res, mPtr); - } + explicit out_ptr_t(SP &res) : mRes{res} { } + ~out_ptr_t() { std::visit([this](auto &ptr) { mRes.reset(static_cast(ptr)); }, mPtr); } + + out_ptr_t() = delete; out_ptr_t(const out_ptr_t&) = delete; out_ptr_t& operator=(const out_ptr_t&) = delete; - operator PT*() noexcept + operator PT*() noexcept /* NOLINT(google-explicit-constructor) */ { return &std::get(mPtr); } - operator void**() noexcept + operator void**() noexcept /* NOLINT(google-explicit-constructor) */ { return &mPtr.template emplace(); } }; template -auto out_ptr(SP &res) +auto out_ptr(SP &res, Args&& ...args) { - using ptype = typename SP::element_type*; - return out_ptr_t{res}; + static_assert(sizeof...(args) == 0); + if constexpr(std::is_same_v) + { + using ptype = typename SP::element_type*; + return out_ptr_t{res}; + } + else + return out_ptr_t{res}; +} + + +template +class inout_ptr_t { + static_assert(!std::is_same_v); + + SP &mRes; + std::variant mPtr; + +public: + explicit inout_ptr_t(SP &res) : mRes{res}, mPtr{res.get()} { } + ~inout_ptr_t() + { + mRes.release(); + std::visit([this](auto &ptr) { mRes.reset(static_cast(ptr)); }, mPtr); + } + + inout_ptr_t() = delete; + inout_ptr_t(const inout_ptr_t&) = delete; + inout_ptr_t& operator=(const inout_ptr_t&) = delete; + + operator PT*() noexcept /* NOLINT(google-explicit-constructor) */ + { return &std::get(mPtr); } + + operator void**() noexcept /* NOLINT(google-explicit-constructor) */ + { return &mPtr.template emplace(mRes.get()); } +}; + +template +auto inout_ptr(SP &res, Args&& ...args) +{ + static_assert(sizeof...(args) == 0); + if constexpr(std::is_same_v) + { + using ptype = typename SP::element_type*; + return inout_ptr_t{res}; + } + else + return inout_ptr_t{res}; } } // namespace al diff --git a/Engine/lib/openal-soft/common/alnumeric.h b/Engine/lib/openal-soft/common/alnumeric.h index 0d091166b..cd9c55cfc 100644 --- a/Engine/lib/openal-soft/common/alnumeric.h +++ b/Engine/lib/openal-soft/common/alnumeric.h @@ -1,17 +1,19 @@ #ifndef AL_NUMERIC_H #define AL_NUMERIC_H +#include "config_simd.h" + #include #include #include #include #include -#include +#include #include #ifdef HAVE_INTRIN_H #include #endif -#ifdef HAVE_SSE_INTRINSICS +#if HAVE_SSE_INTRINSICS #include #endif @@ -29,19 +31,27 @@ constexpr auto operator "" _uz(unsigned long long n) noexcept { return static_ca constexpr auto operator "" _zu(unsigned long long n) noexcept { return static_cast(n); } -constexpr auto GetCounterSuffix(size_t count) noexcept -> const char* +template,bool> = true> +constexpr auto as_unsigned(T value) noexcept { - auto &suffix = (((count%100)/10) == 1) ? "th" : - ((count%10) == 1) ? "st" : - ((count%10) == 2) ? "nd" : - ((count%10) == 3) ? "rd" : "th"; - return std::data(suffix); + using UT = std::make_unsigned_t; + return static_cast(value); } -constexpr inline float lerpf(float val1, float val2, float mu) noexcept +constexpr auto GetCounterSuffix(size_t count) noexcept -> std::string_view +{ + using namespace std::string_view_literals; + return (((count%100)/10) == 1) ? "th"sv : + ((count%10) == 1) ? "st"sv : + ((count%10) == 2) ? "nd"sv : + ((count%10) == 3) ? "rd"sv : "th"sv; +} + + +constexpr auto lerpf(float val1, float val2, float mu) noexcept -> float { return val1 + (val2-val1)*mu; } -constexpr inline double lerpd(double val1, double val2, double mu) noexcept +constexpr auto lerpd(double val1, double val2, double mu) noexcept -> double { return val1 + (val2-val1)*mu; } @@ -84,7 +94,7 @@ constexpr T RoundUp(T value, al::type_identity_t r) noexcept */ inline int fastf2i(float f) noexcept { -#if defined(HAVE_SSE_INTRINSICS) +#if HAVE_SSE_INTRINSICS return _mm_cvt_ss2si(_mm_set_ss(f)); #elif defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0 @@ -112,7 +122,7 @@ inline unsigned int fastf2u(float f) noexcept /** Converts float-to-int using standard behavior (truncation). */ inline int float2int(float f) noexcept { -#if defined(HAVE_SSE_INTRINSICS) +#if HAVE_SSE_INTRINSICS return _mm_cvtt_ss2si(_mm_set_ss(f)); #elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0) \ @@ -143,7 +153,7 @@ inline unsigned int float2uint(float f) noexcept /** Converts double-to-int using standard behavior (truncation). */ inline int double2int(double d) noexcept { -#if defined(HAVE_SSE_INTRINSICS) +#if HAVE_SSE_INTRINSICS return _mm_cvttsd_si32(_mm_set_sd(d)); #elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2) \ @@ -241,7 +251,7 @@ inline float level_mb_to_gain(float x) // Converts gain to level (mB). inline float gain_to_level_mb(float x) { - if (x <= 0.0f) + if(x <= 1e-05f) return -10'000.0f; return std::max(std::log10(x) * 2'000.0f, -10'000.0f); } diff --git a/Engine/lib/openal-soft/common/alsem.h b/Engine/lib/openal-soft/common/alsem.h index 90b393190..b23a54a17 100644 --- a/Engine/lib/openal-soft/common/alsem.h +++ b/Engine/lib/openal-soft/common/alsem.h @@ -27,7 +27,7 @@ class semaphore { native_type mSem{}; public: - semaphore(unsigned int initial=0); + explicit semaphore(unsigned int initial=0); semaphore(const semaphore&) = delete; ~semaphore(); diff --git a/Engine/lib/openal-soft/common/alspan.h b/Engine/lib/openal-soft/common/alspan.h index c60aeeb8d..33587e6e1 100644 --- a/Engine/lib/openal-soft/common/alspan.h +++ b/Engine/lib/openal-soft/common/alspan.h @@ -142,6 +142,9 @@ namespace detail_ { #define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true +/* NOLINTBEGIN(google-explicit-constructor) This largely follows std::span's + * constructor behavior, and should be replaced once C++20 is used. + */ template class span { public: @@ -212,8 +215,10 @@ public: [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator { return const_iterator{mData+E}; } - [[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); } - [[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); } + [[nodiscard]] constexpr + auto rbegin() const noexcept -> reverse_iterator { return reverse_iterator{end()}; } + [[nodiscard]] constexpr + auto rend() const noexcept -> reverse_iterator { return reverse_iterator{begin()}; } [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator { return cend(); } [[nodiscard]] constexpr @@ -336,8 +341,10 @@ public: [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator { return const_iterator{mData+mDataLength}; } - [[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); } - [[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); } + [[nodiscard]] constexpr + auto rbegin() const noexcept -> reverse_iterator { return reverse_iterator{end()}; } + [[nodiscard]] constexpr + auto rend() const noexcept -> reverse_iterator { return reverse_iterator{begin()}; } [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator { return cend(); } [[nodiscard]] constexpr @@ -432,7 +439,7 @@ auto span::subspan(std::size_t offset, std::size_t count) const noexcept } return span{mData+offset, size()-offset}; } - +/* NOLINTEND(google-explicit-constructor) */ template span(T, EndOrSize) -> span())>>; diff --git a/Engine/lib/openal-soft/common/alstring.cpp b/Engine/lib/openal-soft/common/alstring.cpp index 945dbc3ae..d609823df 100644 --- a/Engine/lib/openal-soft/common/alstring.cpp +++ b/Engine/lib/openal-soft/common/alstring.cpp @@ -7,7 +7,6 @@ #include #include #include -#include namespace al { @@ -52,13 +51,4 @@ int case_compare(const std::wstring_view str0, const std::wstring_view str1) noe return 0; } -int strcasecmp(const char *str0, const char *str1) noexcept -{ return case_compare(str0, str1); } - -int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept -{ - return case_compare(std::string_view{str0, std::min(std::strlen(str0), len)}, - std::string_view{str1, std::min(std::strlen(str1), len)}); -} - } // namespace al diff --git a/Engine/lib/openal-soft/common/alstring.h b/Engine/lib/openal-soft/common/alstring.h index 3182889d2..c03936136 100644 --- a/Engine/lib/openal-soft/common/alstring.h +++ b/Engine/lib/openal-soft/common/alstring.h @@ -10,11 +10,17 @@ namespace al { -template +template [[nodiscard]] constexpr -auto sizei(const std::basic_string_view str) noexcept -> int +auto sizei(const std::basic_string_view str) noexcept -> int { return static_cast(std::min(str.size(), std::numeric_limits::max())); } +template +[[nodiscard]] constexpr +auto sizei(const std::basic_string &str) noexcept -> int +{ return static_cast(std::min(str.size(), std::numeric_limits::max())); } + + [[nodiscard]] constexpr bool contains(const std::string_view str0, const std::string_view str1) noexcept { return str0.find(str1) != std::string_view::npos; } @@ -33,10 +39,20 @@ int case_compare(const std::string_view str0, const std::string_view str1) noexc [[nodiscard]] int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept; -[[nodiscard]] -int strcasecmp(const char *str0, const char *str1) noexcept; -[[nodiscard]] -int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept; +/* C++20 changes path::u8string() to return a string using a new/distinct + * char8_t type for UTF-8 strings. However, support for this with standard + * string functions is totally inadequate, and we already hold UTF-8 with plain + * char strings. So this function is used to reinterpret a char8_t string as a + * char string_view. + */ +#if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201907L +inline auto u8_as_char(const std::u8string_view str) -> std::string_view +#else +inline auto u8_as_char(const std::string_view str) -> std::string_view +#endif +{ + return std::string_view{reinterpret_cast(str.data()), str.size()}; +} } // namespace al diff --git a/Engine/lib/openal-soft/common/althreads.h b/Engine/lib/openal-soft/common/althreads.h index d84917067..66e62e555 100644 --- a/Engine/lib/openal-soft/common/althreads.h +++ b/Engine/lib/openal-soft/common/althreads.h @@ -9,7 +9,7 @@ #define WIN32_LEAN_AND_MEAN #include -#elif defined(__APPLE__) +#elif defined(__STDC_NO_THREADS__) || !__has_include() #include @@ -79,7 +79,7 @@ public: [[nodiscard]] auto get() const noexcept -> T { return from_ptr(TlsGetValue(mTss)); } -#elif defined(__APPLE__) +#elif defined(__STDC_NO_THREADS__) || !__has_include() pthread_key_t mTss{}; diff --git a/Engine/lib/openal-soft/common/comptr.h b/Engine/lib/openal-soft/common/comptr.h index d3294396c..c592f1d05 100644 --- a/Engine/lib/openal-soft/common/comptr.h +++ b/Engine/lib/openal-soft/common/comptr.h @@ -1,11 +1,9 @@ #ifndef COMMON_COMPTR_H #define COMMON_COMPTR_H +#ifdef _WIN32 #include -#include -#include #include -#include #define WIN32_LEAN_AND_MEAN #include @@ -17,7 +15,7 @@ struct ComWrapper { ComWrapper(void *reserved, DWORD coinit) : mStatus{CoInitializeEx(reserved, coinit)} { } - ComWrapper(DWORD coinit=COINIT_APARTMENTTHREADED) + explicit ComWrapper(DWORD coinit=COINIT_APARTMENTTHREADED) : mStatus{CoInitializeEx(nullptr, coinit)} { } ComWrapper(ComWrapper&& rhs) { mStatus = std::exchange(rhs.mStatus, E_FAIL); } @@ -46,7 +44,7 @@ struct ComWrapper { }; -template +template /* NOLINTNEXTLINE(clazy-rule-of-three) False positive */ struct ComPtr { using element_type = T; @@ -57,7 +55,7 @@ struct ComPtr { ComPtr(const ComPtr &rhs) noexcept(RefIsNoexcept) : mPtr{rhs.mPtr} { if(mPtr) mPtr->AddRef(); } ComPtr(ComPtr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; } - ComPtr(std::nullptr_t) noexcept { } + ComPtr(std::nullptr_t) noexcept { } /* NOLINT(google-explicit-constructor) */ explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { } ~ComPtr() { if(mPtr) mPtr->Release(); } @@ -109,5 +107,6 @@ struct ComPtr { private: T *mPtr{nullptr}; }; +#endif /* _WIN32 */ #endif diff --git a/Engine/lib/openal-soft/common/dynload.h b/Engine/lib/openal-soft/common/dynload.h index bd9e86f79..ce7c8f030 100644 --- a/Engine/lib/openal-soft/common/dynload.h +++ b/Engine/lib/openal-soft/common/dynload.h @@ -3,12 +3,16 @@ #if defined(_WIN32) || defined(HAVE_DLFCN_H) -#define HAVE_DYNLOAD +#define HAVE_DYNLOAD 1 void *LoadLib(const char *name); void CloseLib(void *handle); void *GetSymbol(void *handle, const char *name); +#else + +#define HAVE_DYNLOAD 0 + #endif #endif /* AL_DYNLOAD_H */ diff --git a/Engine/lib/openal-soft/common/filesystem.cpp b/Engine/lib/openal-soft/common/filesystem.cpp new file mode 100644 index 000000000..58da22a7d --- /dev/null +++ b/Engine/lib/openal-soft/common/filesystem.cpp @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------------------------- +// +// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14 +// +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2018, Steffen Schümann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//--------------------------------------------------------------------------------------- +// fs_std_impl.hpp - The implementation header for the header/implementation separated usage of +// ghc::filesystem that does nothing if std::filesystem is detected. +// This file can be used to hide the implementation of ghc::filesystem into a single cpp. +// The cpp has to include this before including fs_std_fwd.hpp directly or via a different +// header to work. +//--------------------------------------------------------------------------------------- +#if defined(_MSVC_LANG) && _MSVC_LANG >= 201703L || __cplusplus >= 201703L && defined(__has_include) + // ^ Supports MSVC prior to 15.7 without setting /Zc:__cplusplus to fix __cplusplus + // _MSVC_LANG works regardless. But without the switch, the compiler always reported 199711L: https://blogs.msdn.microsoft.com/vcblog/2018/04/09/msvc-now-correctly-reports-__cplusplus/ + #if __has_include() // Two stage __has_include needed for MSVC 2015 and per https://gcc.gnu.org/onlinedocs/cpp/_005f_005fhas_005finclude.html + #define GHC_USE_STD_FS + + // Old Apple OSs don't support std::filesystem, though the header is available at compile + // time. In particular, std::filesystem is unavailable before macOS 10.15, iOS/tvOS 13.0, + // and watchOS 6.0. + #ifdef __APPLE__ + #include + // Note: This intentionally uses std::filesystem on any new Apple OS, like visionOS + // released after std::filesystem, where std::filesystem is always available. + // (All other ___VERSION_MIN_REQUIREDs will be undefined and thus 0.) + #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 \ + || defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 \ + || defined(__TV_OS_VERSION_MIN_REQUIRED) && __TV_OS_VERSION_MIN_REQUIRED < 130000 \ + || defined(__WATCH_OS_VERSION_MAX_ALLOWED) && __WATCH_OS_VERSION_MAX_ALLOWED < 60000 + #undef GHC_USE_STD_FS + #endif + #endif + #endif +#endif + +#ifndef GHC_USE_STD_FS + #define GHC_FILESYSTEM_IMPLEMENTATION + #include "ghc_filesystem.h" +#endif diff --git a/Engine/lib/openal-soft/common/filesystem.h b/Engine/lib/openal-soft/common/filesystem.h new file mode 100644 index 000000000..8d556e87f --- /dev/null +++ b/Engine/lib/openal-soft/common/filesystem.h @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------------------------- +// +// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14 +// +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2018, Steffen Schümann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//--------------------------------------------------------------------------------------- +// fs_std_fwd.hpp - The forwarding header for the header/implementation separated usage of +// ghc::filesystem that uses std::filesystem if it detects it. +// This file can be include at any place, where fs::filesystem api is needed while +// not bleeding implementation details (e.g. system includes) into the global namespace, +// as long as one cpp includes fs_std_impl.hpp to deliver the matching implementations. +//--------------------------------------------------------------------------------------- +#ifndef GHC_FILESYSTEM_STD_FWD_H +#define GHC_FILESYSTEM_STD_FWD_H + +#if defined(_MSVC_LANG) && _MSVC_LANG >= 201703L || __cplusplus >= 201703L && defined(__has_include) + // ^ Supports MSVC prior to 15.7 without setting /Zc:__cplusplus to fix __cplusplus + // _MSVC_LANG works regardless. But without the switch, the compiler always reported 199711L: https://blogs.msdn.microsoft.com/vcblog/2018/04/09/msvc-now-correctly-reports-__cplusplus/ + #if __has_include() // Two stage __has_include needed for MSVC 2015 and per https://gcc.gnu.org/onlinedocs/cpp/_005f_005fhas_005finclude.html + #define GHC_USE_STD_FS + + // Old Apple OSs don't support std::filesystem, though the header is available at compile + // time. In particular, std::filesystem is unavailable before macOS 10.15, iOS/tvOS 13.0, + // and watchOS 6.0. + #ifdef __APPLE__ + #include + // Note: This intentionally uses std::filesystem on any new Apple OS, like visionOS + // released after std::filesystem, where std::filesystem is always available. + // (All other ___VERSION_MIN_REQUIREDs will be undefined and thus 0.) + #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 \ + || defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 \ + || defined(__TV_OS_VERSION_MIN_REQUIRED) && __TV_OS_VERSION_MIN_REQUIRED < 130000 \ + || defined(__WATCH_OS_VERSION_MAX_ALLOWED) && __WATCH_OS_VERSION_MAX_ALLOWED < 60000 + #undef GHC_USE_STD_FS + #endif + #endif + #endif +#endif + +#ifdef GHC_USE_STD_FS + #include + namespace fs { + using namespace std::filesystem; + using ifstream = std::ifstream; + using ofstream = std::ofstream; + using fstream = std::fstream; + } +#else + #define GHC_FILESYSTEM_FWD + #include "ghc_filesystem.h" + + namespace fs { + using namespace ghc::filesystem; + using ifstream = ghc::filesystem::ifstream; + using ofstream = ghc::filesystem::ofstream; + using fstream = ghc::filesystem::fstream; + } +#endif + +#endif // GHC_FILESYSTEM_STD_FWD_H diff --git a/Engine/lib/openal-soft/common/flexarray.h b/Engine/lib/openal-soft/common/flexarray.h index afd6eaeee..b6f09c20d 100644 --- a/Engine/lib/openal-soft/common/flexarray.h +++ b/Engine/lib/openal-soft/common/flexarray.h @@ -30,7 +30,7 @@ struct alignas(alignment) FlexArrayStorage : al::span { * arrays store their payloads after the end of the object, which must be * the last in the whole parent chain. */ - FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v) + explicit FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v) : al::span{::new(static_cast(this+1)) T[size], size} { } /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ @@ -46,7 +46,7 @@ struct alignas(alignment) FlexArrayStorage : al::span { { return sizeof(FlexArrayStorage) + sizeof(T)*count + base; } /* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ - FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v) + explicit FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v) : al::span{::new(static_cast(this+1)) T[size], size} { } /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ @@ -88,7 +88,8 @@ struct FlexArray { static std::unique_ptr Create(index_type count) { return std::unique_ptr{new(FamCount{count}) FlexArray{count}}; } - FlexArray(index_type size) noexcept(std::is_nothrow_constructible_v) + explicit FlexArray(index_type size) + noexcept(std::is_nothrow_constructible_v) : mStore{size} { } ~FlexArray() = default; diff --git a/Engine/lib/openal-soft/common/ghc_filesystem.h b/Engine/lib/openal-soft/common/ghc_filesystem.h new file mode 100644 index 000000000..b4848b36e --- /dev/null +++ b/Engine/lib/openal-soft/common/ghc_filesystem.h @@ -0,0 +1,6076 @@ +//--------------------------------------------------------------------------------------- +// +// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14/C++17/C++20 +// +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2018, Steffen Schümann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//--------------------------------------------------------------------------------------- +#ifndef GHC_FILESYSTEM_H +#define GHC_FILESYSTEM_H + +// #define BSD manifest constant only in +// sys/param.h +#ifndef _WIN32 +#include +#endif + +#ifndef GHC_OS_DETECTED +#if defined(__APPLE__) && defined(__MACH__) +#define GHC_OS_APPLE +#elif defined(__linux__) +#define GHC_OS_LINUX +#if defined(__ANDROID__) +#define GHC_OS_ANDROID +#endif +#elif defined(_WIN64) +#define GHC_OS_WINDOWS +#define GHC_OS_WIN64 +#elif defined(_WIN32) +#define GHC_OS_WINDOWS +#define GHC_OS_WIN32 +#elif defined(__CYGWIN__) +#define GHC_OS_CYGWIN +#elif defined(__sun) && defined(__SVR4) +#define GHC_OS_SOLARIS +#elif defined(__svr4__) +#define GHC_OS_SYS5R4 +#elif defined(BSD) +#define GHC_OS_BSD +#elif defined(__EMSCRIPTEN__) +#define GHC_OS_WEB +#include +#elif defined(__QNX__) +#define GHC_OS_QNX +#elif defined(__HAIKU__) +#define GHC_OS_HAIKU +#else +#error "Operating system currently not supported!" +#endif +#define GHC_OS_DETECTED +#if (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#if _MSVC_LANG == 201703L +#define GHC_FILESYSTEM_RUNNING_CPP17 +#else +#define GHC_FILESYSTEM_RUNNING_CPP20 +#endif +#elif (defined(__cplusplus) && __cplusplus >= 201703L) +#if __cplusplus == 201703L +#define GHC_FILESYSTEM_RUNNING_CPP17 +#else +#define GHC_FILESYSTEM_RUNNING_CPP20 +#endif +#endif +#endif + +#if defined(GHC_FILESYSTEM_IMPLEMENTATION) +#define GHC_EXPAND_IMPL +#define GHC_INLINE +#ifdef GHC_OS_WINDOWS +#ifndef GHC_FS_API +#define GHC_FS_API +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS +#endif +#else +#ifndef GHC_FS_API +#define GHC_FS_API __attribute__((visibility("default"))) +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS __attribute__((visibility("default"))) +#endif +#endif +#elif defined(GHC_FILESYSTEM_FWD) +#define GHC_INLINE +#ifdef GHC_OS_WINDOWS +#ifndef GHC_FS_API +#define GHC_FS_API extern +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS +#endif +#else +#ifndef GHC_FS_API +#define GHC_FS_API extern +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS +#endif +#endif +#else +#define GHC_EXPAND_IMPL +#define GHC_INLINE inline +#ifndef GHC_FS_API +#define GHC_FS_API +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS +#endif +#endif + +#ifdef GHC_EXPAND_IMPL + +#ifdef GHC_OS_WINDOWS +#include +// additional includes +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef GHC_OS_ANDROID +#include +#if __ANDROID_API__ < 12 +#include +#endif +#include +#define statvfs statfs +#else +#include +#endif +#ifdef GHC_OS_CYGWIN +#include +#endif +#if !defined(__ANDROID__) || __ANDROID_API__ >= 26 +#include +#endif +#endif +#ifdef GHC_OS_APPLE +#include +#endif + +#if defined(__cpp_impl_three_way_comparison) && defined(__has_include) +#if __has_include() +#define GHC_HAS_THREEWAY_COMP +#include +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#else // GHC_EXPAND_IMPL + +#if defined(__cpp_impl_three_way_comparison) && defined(__has_include) +#if __has_include() +#define GHC_HAS_THREEWAY_COMP +#include +#endif +#endif +#include +#include +#include +#include +#include +#include +#include +#ifdef GHC_OS_WINDOWS +#include +#endif +#endif // GHC_EXPAND_IMPL + +// After standard library includes. +// Standard library support for std::string_view. +#if defined(__cpp_lib_string_view) +#define GHC_HAS_STD_STRING_VIEW +#elif defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 4000) && (__cplusplus >= 201402) +#define GHC_HAS_STD_STRING_VIEW +#elif defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE >= 7) && (__cplusplus >= 201703) +#define GHC_HAS_STD_STRING_VIEW +#elif defined(_MSC_VER) && (_MSC_VER >= 1910 && _MSVC_LANG >= 201703) +#define GHC_HAS_STD_STRING_VIEW +#endif + +// Standard library support for std::experimental::string_view. +#if defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 3700 && _LIBCPP_VERSION < 7000) && (__cplusplus >= 201402) +#define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW +#elif defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 9)) || (__GNUC__ > 4)) && (__cplusplus >= 201402) +#define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW +#elif defined(__GLIBCXX__) && defined(_GLIBCXX_USE_DUAL_ABI) && (__cplusplus >= 201402) +// macro _GLIBCXX_USE_DUAL_ABI is always defined in libstdc++ from gcc-5 and newer +#define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW +#endif + +#if defined(GHC_HAS_STD_STRING_VIEW) +#include +#elif defined(GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW) +#include +#endif + +#if !defined(GHC_OS_WINDOWS) && !defined(PATH_MAX) +#define PATH_MAX 4096 +#endif + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Behaviour Switches (see README.md, should match the config in test/filesystem_test.cpp): +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Enforce C++17 API where possible when compiling for C++20, handles the following cases: +// * fs::path::u8string() returns std::string instead of std::u8string +// #define GHC_FILESYSTEM_ENFORCE_CPP17_API +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2682 disables the since then invalid use of the copy option create_symlinks on directories +// configure LWG conformance () +#define LWG_2682_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2395 makes crate_directory/create_directories not emit an error if there is a regular +// file with that name, it is superseded by P1164R1, so only activate if really needed +// #define LWG_2935_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2936 enables new element wise (more expensive) path comparison +// * if this->root_name().native().compare(p.root_name().native()) != 0 return result +// * if this->has_root_directory() and !p.has_root_directory() return -1 +// * if !this->has_root_directory() and p.has_root_directory() return -1 +// * else result of element wise comparison of path iteration where first comparison is != 0 or 0 +// if all comparisons are 0 (on Windows this implementation does case-insensitive root_name() +// comparison) +#define LWG_2936_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2937 enforces that fs::equivalent emits an error, if !fs::exists(p1)||!exists(p2) +#define LWG_2937_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// UTF8-Everywhere is the original behaviour of ghc::filesystem. But since v1.5 the Windows +// version defaults to std::wstring storage backend. Still all std::string will be interpreted +// as UTF-8 encoded. With this define you can enforce the old behavior on Windows, using +// std::string as backend and for fs::path::native() and char for fs::path::c_str(). This +// needs more conversions, so it is (and was before v1.5) slower, bot might help keeping source +// homogeneous in a multi-platform project. +// #define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Raise errors/exceptions when invalid unicode codepoints or UTF-8 sequences are found, +// instead of replacing them with the unicode replacement character (U+FFFD). +// #define GHC_RAISE_UNICODE_ERRORS +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Automatic prefix windows path with "\\?\" if they would break the MAX_PATH length. +// instead of replacing them with the unicode replacement character (U+FFFD). +#ifndef GHC_WIN_DISABLE_AUTO_PREFIXES +#define GHC_WIN_AUTO_PREFIX_LONG_PATH +#endif // GHC_WIN_DISABLE_AUTO_PREFIXES +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// ghc::filesystem version in decimal (major * 10000 + minor * 100 + patch) +#define GHC_FILESYSTEM_VERSION 10515L + +#if !defined(GHC_WITH_EXCEPTIONS) && (defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND)) +#define GHC_WITH_EXCEPTIONS +#endif +#if !defined(GHC_WITH_EXCEPTIONS) && defined(GHC_RAISE_UNICODE_ERRORS) +#error "Can't raise unicode errors with exception support disabled" +#endif + +namespace ghc { +namespace filesystem { + +#if defined(GHC_HAS_CUSTOM_STRING_VIEW) +#define GHC_WITH_STRING_VIEW +#elif defined(GHC_HAS_STD_STRING_VIEW) +#define GHC_WITH_STRING_VIEW +using std::basic_string_view; +#elif defined(GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW) +#define GHC_WITH_STRING_VIEW +using std::experimental::basic_string_view; +#endif + +// temporary existing exception type for yet unimplemented parts +class GHC_FS_API_CLASS not_implemented_exception : public std::logic_error +{ +public: + not_implemented_exception() + : std::logic_error("function not implemented yet.") + { + } +}; + +template +class path_helper_base +{ +public: + using value_type = char_type; +#ifdef GHC_OS_WINDOWS + static constexpr value_type preferred_separator = '\\'; +#else + static constexpr value_type preferred_separator = '/'; +#endif +}; + +#if __cplusplus < 201703L +template +constexpr char_type path_helper_base::preferred_separator; +#endif + +#ifdef GHC_OS_WINDOWS +class path; +namespace detail { +bool has_executable_extension(const path& p); +} +#endif + +// [fs.class.path] class path +class GHC_FS_API_CLASS path +#if defined(GHC_OS_WINDOWS) && !defined(GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE) +#define GHC_USE_WCHAR_T +#define GHC_NATIVEWP(p) p.c_str() +#define GHC_PLATFORM_LITERAL(str) L##str + : private path_helper_base +{ +public: + using path_helper_base::value_type; +#else +#define GHC_NATIVEWP(p) p.wstring().c_str() +#define GHC_PLATFORM_LITERAL(str) str + : private path_helper_base +{ +public: + using path_helper_base::value_type; +#endif + using string_type = std::basic_string; + using path_helper_base::preferred_separator; + + // [fs.enum.path.format] enumeration format + /// The path format in which the constructor argument is given. + enum format { + generic_format, ///< The generic format, internally used by + ///< ghc::filesystem::path with slashes + native_format, ///< The format native to the current platform this code + ///< is build for + auto_format, ///< Try to auto-detect the format, fallback to native + }; + + template + struct _is_basic_string : std::false_type + { + }; + template + struct _is_basic_string> : std::true_type + { + }; + template + struct _is_basic_string, std::allocator>> : std::true_type + { + }; +#ifdef GHC_WITH_STRING_VIEW + template + struct _is_basic_string> : std::true_type + { + }; + template + struct _is_basic_string>> : std::true_type + { + }; +#endif + + template + using path_type = typename std::enable_if::value, path>::type; + template +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) + using path_from_string = + typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value, + path>::type; + template + using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value, path>::type; +#else + using path_from_string = + typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value, + path>::type; + template + using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, path>::type; +#endif + // [fs.path.construct] constructors and destructor + path() noexcept; + path(const path& p); + path(path&& p) noexcept; + path(string_type&& source, format fmt = auto_format); + template > + path(const Source& source, format fmt = auto_format); + template + path(InputIterator first, InputIterator last, format fmt = auto_format); +#ifdef GHC_WITH_EXCEPTIONS + template > + path(const Source& source, const std::locale& loc, format fmt = auto_format); + template + path(InputIterator first, InputIterator last, const std::locale& loc, format fmt = auto_format); +#endif + ~path(); + + // [fs.path.assign] assignments + path& operator=(const path& p); + path& operator=(path&& p) noexcept; + path& operator=(string_type&& source); + path& assign(string_type&& source); + template + path& operator=(const Source& source); + template + path& assign(const Source& source); + template + path& assign(InputIterator first, InputIterator last); + + // [fs.path.append] appends + path& operator/=(const path& p); + template + path& operator/=(const Source& source); + template + path& append(const Source& source); + template + path& append(InputIterator first, InputIterator last); + + // [fs.path.concat] concatenation + path& operator+=(const path& x); + path& operator+=(const string_type& x); +#ifdef GHC_WITH_STRING_VIEW + path& operator+=(basic_string_view x); +#endif + path& operator+=(const value_type* x); + path& operator+=(value_type x); + template + path_from_string& operator+=(const Source& x); + template + path_type_EcharT& operator+=(EcharT x); + template + path& concat(const Source& x); + template + path& concat(InputIterator first, InputIterator last); + + // [fs.path.modifiers] modifiers + void clear() noexcept; + path& make_preferred(); + path& remove_filename(); + path& replace_filename(const path& replacement); + path& replace_extension(const path& replacement = path()); + void swap(path& rhs) noexcept; + + // [fs.path.native.obs] native format observers + const string_type& native() const noexcept; + const value_type* c_str() const noexcept; + operator string_type() const; + template , class Allocator = std::allocator> + std::basic_string string(const Allocator& a = Allocator()) const; + std::string string() const; + std::wstring wstring() const; +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) + std::u8string u8string() const; +#else + std::string u8string() const; +#endif + std::u16string u16string() const; + std::u32string u32string() const; + + // [fs.path.generic.obs] generic format observers + template , class Allocator = std::allocator> + std::basic_string generic_string(const Allocator& a = Allocator()) const; + std::string generic_string() const; + std::wstring generic_wstring() const; +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) + std::u8string generic_u8string() const; +#else + std::string generic_u8string() const; +#endif + std::u16string generic_u16string() const; + std::u32string generic_u32string() const; + + // [fs.path.compare] compare + int compare(const path& p) const noexcept; + int compare(const string_type& s) const; +#ifdef GHC_WITH_STRING_VIEW + int compare(basic_string_view s) const; +#endif + int compare(const value_type* s) const; + + // [fs.path.decompose] decomposition + path root_name() const; + path root_directory() const; + path root_path() const; + path relative_path() const; + path parent_path() const; + path filename() const; + path stem() const; + path extension() const; + + // [fs.path.query] query + bool empty() const noexcept; + bool has_root_name() const; + bool has_root_directory() const; + bool has_root_path() const; + bool has_relative_path() const; + bool has_parent_path() const; + bool has_filename() const; + bool has_stem() const; + bool has_extension() const; + bool is_absolute() const; + bool is_relative() const; + + // [fs.path.gen] generation + path lexically_normal() const; + path lexically_relative(const path& base) const; + path lexically_proximate(const path& base) const; + + // [fs.path.itr] iterators + class iterator; + using const_iterator = iterator; + iterator begin() const; + iterator end() const; + +private: + using impl_value_type = value_type; + using impl_string_type = std::basic_string; + friend class directory_iterator; + void append_name(const value_type* name); + static constexpr impl_value_type generic_separator = '/'; + template + class input_iterator_range + { + public: + typedef InputIterator iterator; + typedef InputIterator const_iterator; + typedef typename InputIterator::difference_type difference_type; + + input_iterator_range(const InputIterator& first, const InputIterator& last) + : _first(first) + , _last(last) + { + } + + InputIterator begin() const { return _first; } + InputIterator end() const { return _last; } + + private: + InputIterator _first; + InputIterator _last; + }; + friend void swap(path& lhs, path& rhs) noexcept; + friend size_t hash_value(const path& p) noexcept; + friend path canonical(const path& p, std::error_code& ec); + friend bool create_directories(const path& p, std::error_code& ec) noexcept; + string_type::size_type root_name_length() const noexcept; + void postprocess_path_with_format(format fmt); + void check_long_path(); + impl_string_type _path; +#ifdef GHC_OS_WINDOWS + void handle_prefixes(); + friend bool detail::has_executable_extension(const path& p); +#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH + string_type::size_type _prefixLength{0}; +#else // GHC_WIN_AUTO_PREFIX_LONG_PATH + static const string_type::size_type _prefixLength{0}; +#endif // GHC_WIN_AUTO_PREFIX_LONG_PATH +#else + static const string_type::size_type _prefixLength{0}; +#endif +}; + +// [fs.path.nonmember] path non-member functions +GHC_FS_API void swap(path& lhs, path& rhs) noexcept; +GHC_FS_API size_t hash_value(const path& p) noexcept; +#ifdef GHC_HAS_THREEWAY_COMP +GHC_FS_API std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept; +#endif +GHC_FS_API bool operator==(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator!=(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator<(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator<=(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator>(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator>=(const path& lhs, const path& rhs) noexcept; +GHC_FS_API path operator/(const path& lhs, const path& rhs); + +// [fs.path.io] path inserter and extractor +template +std::basic_ostream& operator<<(std::basic_ostream& os, const path& p); +template +std::basic_istream& operator>>(std::basic_istream& is, path& p); + +// [pfs.path.factory] path factory functions +template > +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +[[deprecated("use ghc::filesystem::path::path() with std::u8string instead")]] +#endif +path u8path(const Source& source); +template +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +[[deprecated("use ghc::filesystem::path::path() with std::u8string instead")]] +#endif +path u8path(InputIterator first, InputIterator last); + +// [fs.class.filesystem_error] class filesystem_error +class GHC_FS_API_CLASS filesystem_error : public std::system_error +{ +public: + filesystem_error(const std::string& what_arg, std::error_code ec); + filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec); + filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec); + const path& path1() const noexcept; + const path& path2() const noexcept; + const char* what() const noexcept override; + +private: + std::string _what_arg; + std::error_code _ec; + path _p1, _p2; +}; + +class GHC_FS_API_CLASS path::iterator +{ +public: + using value_type = const path; + using difference_type = std::ptrdiff_t; + using pointer = const path*; + using reference = const path&; + using iterator_category = std::bidirectional_iterator_tag; + + iterator(); + iterator(const path& p, const impl_string_type::const_iterator& pos); + iterator& operator++(); + iterator operator++(int); + iterator& operator--(); + iterator operator--(int); + bool operator==(const iterator& other) const; + bool operator!=(const iterator& other) const; + reference operator*() const; + pointer operator->() const; + +private: + friend class path; + impl_string_type::const_iterator increment(const impl_string_type::const_iterator& pos) const; + impl_string_type::const_iterator decrement(const impl_string_type::const_iterator& pos) const; + void updateCurrent(); + impl_string_type::const_iterator _first; + impl_string_type::const_iterator _last; + impl_string_type::const_iterator _prefix; + impl_string_type::const_iterator _root; + impl_string_type::const_iterator _iter; + path _current; +}; + +struct space_info +{ + uintmax_t capacity; + uintmax_t free; + uintmax_t available; +}; + +// [fs.enum] enumerations +// [fs.enum.file_type] +enum class file_type { + none, + not_found, + regular, + directory, + symlink, + block, + character, + fifo, + socket, + unknown, +}; + +// [fs.enum.perms] +enum class perms : uint16_t { + none = 0, + + owner_read = 0400, + owner_write = 0200, + owner_exec = 0100, + owner_all = 0700, + + group_read = 040, + group_write = 020, + group_exec = 010, + group_all = 070, + + others_read = 04, + others_write = 02, + others_exec = 01, + others_all = 07, + + all = 0777, + set_uid = 04000, + set_gid = 02000, + sticky_bit = 01000, + + mask = 07777, + unknown = 0xffff +}; + +// [fs.enum.perm.opts] +enum class perm_options : uint16_t { + replace = 3, + add = 1, + remove = 2, + nofollow = 4, +}; + +// [fs.enum.copy.opts] +enum class copy_options : uint16_t { + none = 0, + + skip_existing = 1, + overwrite_existing = 2, + update_existing = 4, + + recursive = 8, + + copy_symlinks = 0x10, + skip_symlinks = 0x20, + + directories_only = 0x40, + create_symlinks = 0x80, +#ifndef GHC_OS_WEB + create_hard_links = 0x100 +#endif +}; + +// [fs.enum.dir.opts] +enum class directory_options : uint16_t { + none = 0, + follow_directory_symlink = 1, + skip_permission_denied = 2, +}; + +// [fs.class.file_status] class file_status +class GHC_FS_API_CLASS file_status +{ +public: + // [fs.file_status.cons] constructors and destructor + file_status() noexcept; + explicit file_status(file_type ft, perms prms = perms::unknown) noexcept; + file_status(const file_status&) noexcept; + file_status(file_status&&) noexcept; + ~file_status(); + // assignments: + file_status& operator=(const file_status&) noexcept; + file_status& operator=(file_status&&) noexcept; + // [fs.file_status.mods] modifiers + void type(file_type ft) noexcept; + void permissions(perms prms) noexcept; + // [fs.file_status.obs] observers + file_type type() const noexcept; + perms permissions() const noexcept; + friend bool operator==(const file_status& lhs, const file_status& rhs) noexcept { return lhs.type() == rhs.type() && lhs.permissions() == rhs.permissions(); } + +private: + file_type _type; + perms _perms; +}; + +using file_time_type = std::chrono::time_point; + +// [fs.class.directory_entry] Class directory_entry +class GHC_FS_API_CLASS directory_entry +{ +public: + // [fs.dir.entry.cons] constructors and destructor + directory_entry() noexcept = default; + directory_entry(const directory_entry&) = default; + directory_entry(directory_entry&&) noexcept = default; +#ifdef GHC_WITH_EXCEPTIONS + explicit directory_entry(const path& p); +#endif + directory_entry(const path& p, std::error_code& ec); + ~directory_entry(); + + // assignments: + directory_entry& operator=(const directory_entry&) = default; + directory_entry& operator=(directory_entry&&) noexcept = default; + + // [fs.dir.entry.mods] modifiers +#ifdef GHC_WITH_EXCEPTIONS + void assign(const path& p); + void replace_filename(const path& p); + void refresh(); +#endif + void assign(const path& p, std::error_code& ec); + void replace_filename(const path& p, std::error_code& ec); + void refresh(std::error_code& ec) noexcept; + + // [fs.dir.entry.obs] observers + const filesystem::path& path() const noexcept; + operator const filesystem::path&() const noexcept; +#ifdef GHC_WITH_EXCEPTIONS + bool exists() const; + bool is_block_file() const; + bool is_character_file() const; + bool is_directory() const; + bool is_fifo() const; + bool is_other() const; + bool is_regular_file() const; + bool is_socket() const; + bool is_symlink() const; + uintmax_t file_size() const; + file_time_type last_write_time() const; + file_status status() const; + file_status symlink_status() const; +#endif + bool exists(std::error_code& ec) const noexcept; + bool is_block_file(std::error_code& ec) const noexcept; + bool is_character_file(std::error_code& ec) const noexcept; + bool is_directory(std::error_code& ec) const noexcept; + bool is_fifo(std::error_code& ec) const noexcept; + bool is_other(std::error_code& ec) const noexcept; + bool is_regular_file(std::error_code& ec) const noexcept; + bool is_socket(std::error_code& ec) const noexcept; + bool is_symlink(std::error_code& ec) const noexcept; + uintmax_t file_size(std::error_code& ec) const noexcept; + file_time_type last_write_time(std::error_code& ec) const noexcept; + file_status status(std::error_code& ec) const noexcept; + file_status symlink_status(std::error_code& ec) const noexcept; + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS + uintmax_t hard_link_count() const; +#endif + uintmax_t hard_link_count(std::error_code& ec) const noexcept; +#endif + +#ifdef GHC_HAS_THREEWAY_COMP + std::strong_ordering operator<=>(const directory_entry& rhs) const noexcept; +#endif + bool operator<(const directory_entry& rhs) const noexcept; + bool operator==(const directory_entry& rhs) const noexcept; + bool operator!=(const directory_entry& rhs) const noexcept; + bool operator<=(const directory_entry& rhs) const noexcept; + bool operator>(const directory_entry& rhs) const noexcept; + bool operator>=(const directory_entry& rhs) const noexcept; + +private: + friend class directory_iterator; +#ifdef GHC_WITH_EXCEPTIONS + file_type status_file_type() const; +#endif + file_type status_file_type(std::error_code& ec) const noexcept; + filesystem::path _path; + file_status _status; + file_status _symlink_status; + uintmax_t _file_size = static_cast(-1); +#ifndef GHC_OS_WINDOWS + uintmax_t _hard_link_count = static_cast(-1); +#endif + time_t _last_write_time = 0; +}; + +// [fs.class.directory.iterator] Class directory_iterator +class GHC_FS_API_CLASS directory_iterator +{ +public: + class GHC_FS_API_CLASS proxy + { + public: + const directory_entry& operator*() const& noexcept { return _dir_entry; } + directory_entry operator*() && noexcept { return std::move(_dir_entry); } + + private: + explicit proxy(const directory_entry& dir_entry) + : _dir_entry(dir_entry) + { + } + friend class directory_iterator; + friend class recursive_directory_iterator; + directory_entry _dir_entry; + }; + using iterator_category = std::input_iterator_tag; + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const directory_entry*; + using reference = const directory_entry&; + + // [fs.dir.itr.members] member functions + directory_iterator() noexcept; +#ifdef GHC_WITH_EXCEPTIONS + explicit directory_iterator(const path& p); + directory_iterator(const path& p, directory_options options); +#endif + directory_iterator(const path& p, std::error_code& ec) noexcept; + directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept; + directory_iterator(const directory_iterator& rhs); + directory_iterator(directory_iterator&& rhs) noexcept; + ~directory_iterator(); + directory_iterator& operator=(const directory_iterator& rhs); + directory_iterator& operator=(directory_iterator&& rhs) noexcept; + const directory_entry& operator*() const; + const directory_entry* operator->() const; +#ifdef GHC_WITH_EXCEPTIONS + directory_iterator& operator++(); +#endif + directory_iterator& increment(std::error_code& ec) noexcept; + + // other members as required by [input.iterators] +#ifdef GHC_WITH_EXCEPTIONS + proxy operator++(int) + { + proxy p{**this}; + ++*this; + return p; + } +#endif + bool operator==(const directory_iterator& rhs) const; + bool operator!=(const directory_iterator& rhs) const; + +private: + friend class recursive_directory_iterator; + class impl; + std::shared_ptr _impl; +}; + +// [fs.dir.itr.nonmembers] directory_iterator non-member functions +GHC_FS_API directory_iterator begin(directory_iterator iter) noexcept; +GHC_FS_API directory_iterator end(const directory_iterator&) noexcept; + +// [fs.class.re.dir.itr] class recursive_directory_iterator +class GHC_FS_API_CLASS recursive_directory_iterator +{ +public: + using iterator_category = std::input_iterator_tag; + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const directory_entry*; + using reference = const directory_entry&; + + // [fs.rec.dir.itr.members] constructors and destructor + recursive_directory_iterator() noexcept; +#ifdef GHC_WITH_EXCEPTIONS + explicit recursive_directory_iterator(const path& p); + recursive_directory_iterator(const path& p, directory_options options); +#endif + recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept; + recursive_directory_iterator(const path& p, std::error_code& ec) noexcept; + recursive_directory_iterator(const recursive_directory_iterator& rhs); + recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept; + ~recursive_directory_iterator(); + + // [fs.rec.dir.itr.members] observers + directory_options options() const; + int depth() const; + bool recursion_pending() const; + + const directory_entry& operator*() const; + const directory_entry* operator->() const; + + // [fs.rec.dir.itr.members] modifiers recursive_directory_iterator& + recursive_directory_iterator& operator=(const recursive_directory_iterator& rhs); + recursive_directory_iterator& operator=(recursive_directory_iterator&& rhs) noexcept; +#ifdef GHC_WITH_EXCEPTIONS + recursive_directory_iterator& operator++(); +#endif + recursive_directory_iterator& increment(std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS + void pop(); +#endif + void pop(std::error_code& ec); + void disable_recursion_pending(); + + // other members as required by [input.iterators] +#ifdef GHC_WITH_EXCEPTIONS + directory_iterator::proxy operator++(int) + { + directory_iterator::proxy proxy{**this}; + ++*this; + return proxy; + } +#endif + bool operator==(const recursive_directory_iterator& rhs) const; + bool operator!=(const recursive_directory_iterator& rhs) const; + +private: + struct recursive_directory_iterator_impl + { + directory_options _options; + bool _recursion_pending; + std::stack _dir_iter_stack; + recursive_directory_iterator_impl(directory_options options, bool recursion_pending) + : _options(options) + , _recursion_pending(recursion_pending) + { + } + }; + std::shared_ptr _impl; +}; + +// [fs.rec.dir.itr.nonmembers] directory_iterator non-member functions +GHC_FS_API recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept; +GHC_FS_API recursive_directory_iterator end(const recursive_directory_iterator&) noexcept; + +// [fs.op.funcs] filesystem operations +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API path absolute(const path& p); +GHC_FS_API path canonical(const path& p); +GHC_FS_API void copy(const path& from, const path& to); +GHC_FS_API void copy(const path& from, const path& to, copy_options options); +GHC_FS_API bool copy_file(const path& from, const path& to); +GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option); +GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink); +GHC_FS_API bool create_directories(const path& p); +GHC_FS_API bool create_directory(const path& p); +GHC_FS_API bool create_directory(const path& p, const path& attributes); +GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink); +GHC_FS_API void create_symlink(const path& to, const path& new_symlink); +GHC_FS_API path current_path(); +GHC_FS_API void current_path(const path& p); +GHC_FS_API bool exists(const path& p); +GHC_FS_API bool equivalent(const path& p1, const path& p2); +GHC_FS_API uintmax_t file_size(const path& p); +GHC_FS_API bool is_block_file(const path& p); +GHC_FS_API bool is_character_file(const path& p); +GHC_FS_API bool is_directory(const path& p); +GHC_FS_API bool is_empty(const path& p); +GHC_FS_API bool is_fifo(const path& p); +GHC_FS_API bool is_other(const path& p); +GHC_FS_API bool is_regular_file(const path& p); +GHC_FS_API bool is_socket(const path& p); +GHC_FS_API bool is_symlink(const path& p); +GHC_FS_API file_time_type last_write_time(const path& p); +GHC_FS_API void last_write_time(const path& p, file_time_type new_time); +GHC_FS_API void permissions(const path& p, perms prms, perm_options opts = perm_options::replace); +GHC_FS_API path proximate(const path& p, const path& base = current_path()); +GHC_FS_API path read_symlink(const path& p); +GHC_FS_API path relative(const path& p, const path& base = current_path()); +GHC_FS_API bool remove(const path& p); +GHC_FS_API uintmax_t remove_all(const path& p); +GHC_FS_API void rename(const path& from, const path& to); +GHC_FS_API void resize_file(const path& p, uintmax_t size); +GHC_FS_API space_info space(const path& p); +GHC_FS_API file_status status(const path& p); +GHC_FS_API file_status symlink_status(const path& p); +GHC_FS_API path temp_directory_path(); +GHC_FS_API path weakly_canonical(const path& p); +#endif +GHC_FS_API path absolute(const path& p, std::error_code& ec); +GHC_FS_API path canonical(const path& p, std::error_code& ec); +GHC_FS_API void copy(const path& from, const path& to, std::error_code& ec) noexcept; +GHC_FS_API void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept; +GHC_FS_API bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept; +GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option, std::error_code& ec) noexcept; +GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept; +GHC_FS_API bool create_directories(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool create_directory(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept; +GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept; +GHC_FS_API void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept; +GHC_FS_API path current_path(std::error_code& ec); +GHC_FS_API void current_path(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool exists(file_status s) noexcept; +GHC_FS_API bool exists(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept; +GHC_FS_API uintmax_t file_size(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_block_file(file_status s) noexcept; +GHC_FS_API bool is_block_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_character_file(file_status s) noexcept; +GHC_FS_API bool is_character_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_directory(file_status s) noexcept; +GHC_FS_API bool is_directory(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_empty(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_fifo(file_status s) noexcept; +GHC_FS_API bool is_fifo(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_other(file_status s) noexcept; +GHC_FS_API bool is_other(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_regular_file(file_status s) noexcept; +GHC_FS_API bool is_regular_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_socket(file_status s) noexcept; +GHC_FS_API bool is_socket(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_symlink(file_status s) noexcept; +GHC_FS_API bool is_symlink(const path& p, std::error_code& ec) noexcept; +GHC_FS_API file_time_type last_write_time(const path& p, std::error_code& ec) noexcept; +GHC_FS_API void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept; +GHC_FS_API void permissions(const path& p, perms prms, std::error_code& ec) noexcept; +GHC_FS_API void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) noexcept; +GHC_FS_API path proximate(const path& p, std::error_code& ec); +GHC_FS_API path proximate(const path& p, const path& base, std::error_code& ec); +GHC_FS_API path read_symlink(const path& p, std::error_code& ec); +GHC_FS_API path relative(const path& p, std::error_code& ec); +GHC_FS_API path relative(const path& p, const path& base, std::error_code& ec); +GHC_FS_API bool remove(const path& p, std::error_code& ec) noexcept; +GHC_FS_API uintmax_t remove_all(const path& p, std::error_code& ec) noexcept; +GHC_FS_API void rename(const path& from, const path& to, std::error_code& ec) noexcept; +GHC_FS_API void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept; +GHC_FS_API space_info space(const path& p, std::error_code& ec) noexcept; +GHC_FS_API file_status status(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool status_known(file_status s) noexcept; +GHC_FS_API file_status symlink_status(const path& p, std::error_code& ec) noexcept; +GHC_FS_API path temp_directory_path(std::error_code& ec) noexcept; +GHC_FS_API path weakly_canonical(const path& p, std::error_code& ec) noexcept; + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link); +GHC_FS_API uintmax_t hard_link_count(const path& p); +#endif +GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept; +GHC_FS_API uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept; +#endif + +#if defined(GHC_OS_WINDOWS) && (!defined(__GLIBCXX__) || (defined(_GLIBCXX_HAVE__WFOPEN) && defined(_GLIBCXX_USE_WCHAR_T))) +#define GHC_HAS_FSTREAM_OPEN_WITH_WCHAR +#endif + +// Non-C++17 add-on std::fstream wrappers with path +template > +class basic_filebuf : public std::basic_filebuf +{ +public: + basic_filebuf() {} + ~basic_filebuf() override {} + basic_filebuf(const basic_filebuf&) = delete; + const basic_filebuf& operator=(const basic_filebuf&) = delete; + basic_filebuf* open(const path& p, std::ios_base::openmode mode) + { +#ifdef GHC_HAS_FSTREAM_OPEN_WITH_WCHAR + return std::basic_filebuf::open(p.wstring().c_str(), mode) ? this : 0; +#else + return std::basic_filebuf::open(p.string().c_str(), mode) ? this : 0; +#endif + } +}; + +template > +class basic_ifstream : public std::basic_ifstream +{ +public: + basic_ifstream() {} +#ifdef GHC_HAS_FSTREAM_OPEN_WITH_WCHAR + explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in) + : std::basic_ifstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in) + : std::basic_ifstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::open(p.string().c_str(), mode); } +#endif + basic_ifstream(const basic_ifstream&) = delete; + const basic_ifstream& operator=(const basic_ifstream&) = delete; + ~basic_ifstream() override {} +}; + +template > +class basic_ofstream : public std::basic_ofstream +{ +public: + basic_ofstream() {} +#ifdef GHC_HAS_FSTREAM_OPEN_WITH_WCHAR + explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out) + : std::basic_ofstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out) + : std::basic_ofstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::open(p.string().c_str(), mode); } +#endif + basic_ofstream(const basic_ofstream&) = delete; + const basic_ofstream& operator=(const basic_ofstream&) = delete; + ~basic_ofstream() override {} +}; + +template > +class basic_fstream : public std::basic_fstream +{ +public: + basic_fstream() {} +#ifdef GHC_HAS_FSTREAM_OPEN_WITH_WCHAR + explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) + : std::basic_fstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) + : std::basic_fstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::open(p.string().c_str(), mode); } +#endif + basic_fstream(const basic_fstream&) = delete; + const basic_fstream& operator=(const basic_fstream&) = delete; + ~basic_fstream() override {} +}; + +typedef basic_filebuf filebuf; +typedef basic_filebuf wfilebuf; +typedef basic_ifstream ifstream; +typedef basic_ifstream wifstream; +typedef basic_ofstream ofstream; +typedef basic_ofstream wofstream; +typedef basic_fstream fstream; +typedef basic_fstream wfstream; + +class GHC_FS_API_CLASS u8arguments +{ +public: + u8arguments(int& argc, char**& argv); + ~u8arguments() + { + _refargc = _argc; + _refargv = _argv; + } + + bool valid() const { return _isvalid; } + +private: + int _argc; + char** _argv; + int& _refargc; + char**& _refargv; + bool _isvalid; +#ifdef GHC_OS_WINDOWS + std::vector _args; + std::vector _argp; +#endif +}; + +//------------------------------------------------------------------------------------------------- +// Implementation +//------------------------------------------------------------------------------------------------- + +namespace detail { +enum utf8_states_t { S_STRT = 0, S_RJCT = 8 }; +GHC_FS_API void appendUTF8(std::string& str, uint32_t unicode); +GHC_FS_API bool is_surrogate(uint32_t c); +GHC_FS_API bool is_high_surrogate(uint32_t c); +GHC_FS_API bool is_low_surrogate(uint32_t c); +GHC_FS_API unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint); +enum class portable_error { + none = 0, + exists, + not_found, + not_supported, + not_implemented, + invalid_argument, + is_a_directory, +}; +GHC_FS_API std::error_code make_error_code(portable_error err); +#ifdef GHC_OS_WINDOWS +GHC_FS_API std::error_code make_system_error(uint32_t err = 0); +#else +GHC_FS_API std::error_code make_system_error(int err = 0); + +template +struct has_d_type : std::false_type{}; + +template +struct has_d_type : std::true_type {}; + +template +GHC_INLINE file_type file_type_from_dirent_impl(const T&, std::false_type) +{ + return file_type::none; +} + +template +GHC_INLINE file_type file_type_from_dirent_impl(const T& t, std::true_type) +{ + switch (t.d_type) { +#ifdef DT_BLK + case DT_BLK: + return file_type::block; +#endif +#ifdef DT_CHR + case DT_CHR: + return file_type::character; +#endif +#ifdef DT_DIR + case DT_DIR: + return file_type::directory; +#endif +#ifdef DT_FIFO + case DT_FIFO: + return file_type::fifo; +#endif +#ifdef DT_LNK + case DT_LNK: + return file_type::symlink; +#endif +#ifdef DT_REG + case DT_REG: + return file_type::regular; +#endif +#ifdef DT_SOCK + case DT_SOCK: + return file_type::socket; +#endif +#ifdef DT_UNKNOWN + case DT_UNKNOWN: + return file_type::none; +#endif + default: + return file_type::unknown; + } +} + +template +GHC_INLINE file_type file_type_from_dirent(const T& t) +{ + return file_type_from_dirent_impl(t, has_d_type{}); +} +#endif +} // namespace detail + +namespace detail { + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE std::error_code make_error_code(portable_error err) +{ +#ifdef GHC_OS_WINDOWS + switch (err) { + case portable_error::none: + return std::error_code(); + case portable_error::exists: + return std::error_code(ERROR_ALREADY_EXISTS, std::system_category()); + case portable_error::not_found: + return std::error_code(ERROR_PATH_NOT_FOUND, std::system_category()); + case portable_error::not_supported: + return std::error_code(ERROR_NOT_SUPPORTED, std::system_category()); + case portable_error::not_implemented: + return std::error_code(ERROR_CALL_NOT_IMPLEMENTED, std::system_category()); + case portable_error::invalid_argument: + return std::error_code(ERROR_INVALID_PARAMETER, std::system_category()); + case portable_error::is_a_directory: +#ifdef ERROR_DIRECTORY_NOT_SUPPORTED + return std::error_code(ERROR_DIRECTORY_NOT_SUPPORTED, std::system_category()); +#else + return std::error_code(ERROR_NOT_SUPPORTED, std::system_category()); +#endif + } +#else + switch (err) { + case portable_error::none: + return std::error_code(); + case portable_error::exists: + return std::error_code(EEXIST, std::system_category()); + case portable_error::not_found: + return std::error_code(ENOENT, std::system_category()); + case portable_error::not_supported: + return std::error_code(ENOTSUP, std::system_category()); + case portable_error::not_implemented: + return std::error_code(ENOSYS, std::system_category()); + case portable_error::invalid_argument: + return std::error_code(EINVAL, std::system_category()); + case portable_error::is_a_directory: + return std::error_code(EISDIR, std::system_category()); + } +#endif + return std::error_code(); +} + +#ifdef GHC_OS_WINDOWS +GHC_INLINE std::error_code make_system_error(uint32_t err) +{ + return std::error_code(err ? static_cast(err) : static_cast(::GetLastError()), std::system_category()); +} +#else +GHC_INLINE std::error_code make_system_error(int err) +{ + return std::error_code(err ? err : errno, std::system_category()); +} +#endif + +#endif // GHC_EXPAND_IMPL + +template +using EnableBitmask = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, Enum>::type; +} // namespace detail + +template +constexpr detail::EnableBitmask operator&(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) & static_cast(Y)); +} + +template +constexpr detail::EnableBitmask operator|(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) | static_cast(Y)); +} + +template +constexpr detail::EnableBitmask operator^(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) ^ static_cast(Y)); +} + +template +constexpr detail::EnableBitmask operator~(Enum X) +{ + using underlying = typename std::underlying_type::type; + return static_cast(~static_cast(X)); +} + +template +detail::EnableBitmask& operator&=(Enum& X, Enum Y) +{ + X = X & Y; + return X; +} + +template +detail::EnableBitmask& operator|=(Enum& X, Enum Y) +{ + X = X | Y; + return X; +} + +template +detail::EnableBitmask& operator^=(Enum& X, Enum Y) +{ + X = X ^ Y; + return X; +} + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +GHC_INLINE bool in_range(uint32_t c, uint32_t lo, uint32_t hi) +{ + return (static_cast(c - lo) < (hi - lo + 1)); +} + +GHC_INLINE bool is_surrogate(uint32_t c) +{ + return in_range(c, 0xd800, 0xdfff); +} + +GHC_INLINE bool is_high_surrogate(uint32_t c) +{ + return (c & 0xfffffc00) == 0xd800; +} + +GHC_INLINE bool is_low_surrogate(uint32_t c) +{ + return (c & 0xfffffc00) == 0xdc00; +} + +GHC_INLINE void appendUTF8(std::string& str, uint32_t unicode) +{ + if (unicode <= 0x7f) { + str.push_back(static_cast(unicode)); + } + else if (unicode >= 0x80 && unicode <= 0x7ff) { + str.push_back(static_cast((unicode >> 6) + 192)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else if ((unicode >= 0x800 && unicode <= 0xd7ff) || (unicode >= 0xe000 && unicode <= 0xffff)) { + str.push_back(static_cast((unicode >> 12) + 224)); + str.push_back(static_cast(((unicode & 0xfff) >> 6) + 128)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else if (unicode >= 0x10000 && unicode <= 0x10ffff) { + str.push_back(static_cast((unicode >> 18) + 240)); + str.push_back(static_cast(((unicode & 0x3ffff) >> 12) + 128)); + str.push_back(static_cast(((unicode & 0xfff) >> 6) + 128)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal code point for unicode character.", str, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + appendUTF8(str, 0xfffd); +#endif + } +} + +// Thanks to Bjoern Hoehrmann (https://bjoern.hoehrmann.de/utf-8/decoder/dfa/) +// and Taylor R Campbell for the ideas to this DFA approach of UTF-8 decoding; +// Generating debugging and shrinking my own DFA from scratch was a day of fun! +GHC_INLINE unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint) +{ + static const uint32_t utf8_state_info[] = { + // encoded states + 0x11111111u, 0x11111111u, 0x77777777u, 0x77777777u, 0x88888888u, 0x88888888u, 0x88888888u, 0x88888888u, 0x22222299u, 0x22222222u, 0x22222222u, 0x22222222u, 0x3333333au, 0x33433333u, 0x9995666bu, 0x99999999u, + 0x88888880u, 0x22818108u, 0x88888881u, 0x88888882u, 0x88888884u, 0x88888887u, 0x88888886u, 0x82218108u, 0x82281108u, 0x88888888u, 0x88888883u, 0x88888885u, 0u, 0u, 0u, 0u, + }; + uint8_t category = fragment < 128 ? 0 : (utf8_state_info[(fragment >> 3) & 0xf] >> ((fragment & 7) << 2)) & 0xf; + codepoint = (state ? (codepoint << 6) | (fragment & 0x3fu) : (0xffu >> category) & fragment); + return state == S_RJCT ? static_cast(S_RJCT) : static_cast((utf8_state_info[category + 16] >> (state << 2)) & 0xf); +} + +GHC_INLINE bool validUtf8(const std::string& utf8String) +{ + std::string::const_iterator iter = utf8String.begin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.end()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_RJCT) { + return false; + } + } + if (utf8_state) { + return false; + } + return true; +} + +} // namespace detail + +#endif + +namespace detail { + +template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 1)>::type* = nullptr> +inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + return StringType(utf8String.begin(), utf8String.end(), alloc); +} + +template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 2)>::type* = nullptr> +inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + StringType result(alloc); + result.reserve(utf8String.length()); + auto iter = utf8String.cbegin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.cend()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_STRT) { + if (codepoint <= 0xffff) { + result += static_cast(codepoint); + } + else { + codepoint -= 0x10000; + result += static_cast((codepoint >> 10) + 0xd800); + result += static_cast((codepoint & 0x3ff) + 0xdc00); + } + codepoint = 0; + } + else if (utf8_state == S_RJCT) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); + utf8_state = S_STRT; + codepoint = 0; +#endif + } + } + if (utf8_state) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); +#endif + } + return result; +} + +template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 4)>::type* = nullptr> +inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + StringType result(alloc); + result.reserve(utf8String.length()); + auto iter = utf8String.cbegin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.cend()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_STRT) { + result += static_cast(codepoint); + codepoint = 0; + } + else if (utf8_state == S_RJCT) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); + utf8_state = S_STRT; + codepoint = 0; +#endif + } + } + if (utf8_state) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); +#endif + } + return result; +} + +template +inline StringType fromUtf8(const charT (&utf8String)[N]) +{ +#ifdef GHC_WITH_STRING_VIEW + return fromUtf8(basic_string_view(utf8String, N - 1)); +#else + return fromUtf8(std::basic_string(utf8String, N - 1)); +#endif +} + +template ::value && (sizeof(typename strT::value_type) == 1), int>::type size = 1> +inline std::string toUtf8(const strT& unicodeString) +{ + return std::string(unicodeString.begin(), unicodeString.end()); +} + +template ::value && (sizeof(typename strT::value_type) == 2), int>::type size = 2> +inline std::string toUtf8(const strT& unicodeString) +{ + std::string result; + for (auto iter = unicodeString.begin(); iter != unicodeString.end(); ++iter) { + char32_t c = *iter; + if (is_surrogate(c)) { + ++iter; + if (iter != unicodeString.end() && is_high_surrogate(c) && is_low_surrogate(*iter)) { + appendUTF8(result, (char32_t(c) << 10) + *iter - 0x35fdc00); + } + else { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal code point for unicode character.", result, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + appendUTF8(result, 0xfffd); + if (iter == unicodeString.end()) { + break; + } +#endif + } + } + else { + appendUTF8(result, c); + } + } + return result; +} + +template ::value && (sizeof(typename strT::value_type) == 4), int>::type size = 4> +inline std::string toUtf8(const strT& unicodeString) +{ + std::string result; + for (auto c : unicodeString) { + appendUTF8(result, static_cast(c)); + } + return result; +} + +template +inline std::string toUtf8(const charT* unicodeString) +{ +#ifdef GHC_WITH_STRING_VIEW + return toUtf8(basic_string_view>(unicodeString)); +#else + return toUtf8(std::basic_string>(unicodeString)); +#endif +} + +#ifdef GHC_USE_WCHAR_T +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 1), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + auto temp = toUtf8(wString); + return StringType(temp.begin(), temp.end(), alloc); +} + +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 2), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + return StringType(wString.begin(), wString.end(), alloc); +} + +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 4), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + auto temp = toUtf8(wString); + return fromUtf8(temp, alloc); +} + +template ::value && (sizeof(typename strT::value_type) == 1), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + return fromUtf8(unicodeString); +} + +template ::value && (sizeof(typename strT::value_type) == 2), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + return std::wstring(unicodeString.begin(), unicodeString.end()); +} + +template ::value && (sizeof(typename strT::value_type) == 4), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + auto temp = toUtf8(unicodeString); + return fromUtf8(temp); +} + +template +inline std::wstring toWChar(const charT* unicodeString) +{ +#ifdef GHC_WITH_STRING_VIEW + return toWChar(basic_string_view>(unicodeString)); +#else + return toWChar(std::basic_string>(unicodeString)); +#endif +} +#endif // GHC_USE_WCHAR_T + +} // namespace detail + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +template ::value, bool>::type = true> +GHC_INLINE bool startsWith(const strT& what, const strT& with) +{ + return with.length() <= what.length() && equal(with.begin(), with.end(), what.begin()); +} + +template ::value, bool>::type = true> +GHC_INLINE bool endsWith(const strT& what, const strT& with) +{ + return with.length() <= what.length() && what.compare(what.length() - with.length(), with.size(), with) == 0; +} + +} // namespace detail + +GHC_INLINE void path::check_long_path() +{ +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) { + postprocess_path_with_format(native_format); + } +#endif +} + +GHC_INLINE void path::postprocess_path_with_format(path::format fmt) +{ +#ifdef GHC_RAISE_UNICODE_ERRORS + if (!detail::validUtf8(_path)) { + path t; + t._path = _path; + throw filesystem_error("Illegal byte sequence for unicode character.", t, std::make_error_code(std::errc::illegal_byte_sequence)); + } +#endif + switch (fmt) { +#ifdef GHC_OS_WINDOWS + case path::native_format: + case path::auto_format: + case path::generic_format: + for (auto& c : _path) { + if (c == generic_separator) { + c = preferred_separator; + } + } +#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH + if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) { + _path = GHC_PLATFORM_LITERAL("\\\\?\\") + _path; + } +#endif + handle_prefixes(); + break; +#else + case path::auto_format: + case path::native_format: + case path::generic_format: + // nothing to do + break; +#endif + } + if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator) { + impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast(_prefixLength) + 2, _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; }); + _path.erase(new_end, _path.end()); + } + else { + impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast(_prefixLength), _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; }); + _path.erase(new_end, _path.end()); + } +} + +#endif // GHC_EXPAND_IMPL + +template +inline path::path(const Source& source, format fmt) +#ifdef GHC_USE_WCHAR_T + : _path(detail::toWChar(source)) +#else + : _path(detail::toUtf8(source)) +#endif +{ + postprocess_path_with_format(fmt); +} + +template +inline path u8path(const Source& source) +{ + return path(source); +} +template +inline path u8path(InputIterator first, InputIterator last) +{ + return path(first, last); +} + +template +inline path::path(InputIterator first, InputIterator last, format fmt) + : path(std::basic_string::value_type>(first, last), fmt) +{ + // delegated +} + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +GHC_INLINE bool equals_simple_insensitive(const path::value_type* str1, const path::value_type* str2) +{ +#ifdef GHC_OS_WINDOWS +#ifdef __GNUC__ + while (::tolower((unsigned char)*str1) == ::tolower((unsigned char)*str2++)) { + if (*str1++ == 0) + return true; + } + return false; +#else // __GNUC__ +#ifdef GHC_USE_WCHAR_T + return 0 == ::_wcsicmp(str1, str2); +#else // GHC_USE_WCHAR_T + return 0 == ::_stricmp(str1, str2); +#endif // GHC_USE_WCHAR_T +#endif // __GNUC__ +#else // GHC_OS_WINDOWS + return 0 == ::strcasecmp(str1, str2); +#endif // GHC_OS_WINDOWS +} + +GHC_INLINE int compare_simple_insensitive(const path::value_type* str1, size_t len1, const path::value_type* str2, size_t len2) +{ + while (len1 > 0 && len2 > 0 && ::tolower(static_cast(*str1)) == ::tolower(static_cast(*str2))) { + --len1; + --len2; + ++str1; + ++str2; + } + if (len1 && len2) { + return *str1 < *str2 ? -1 : 1; + } + if (len1 == 0 && len2 == 0) { + return 0; + } + return len1 == 0 ? -1 : 1; +} + +GHC_INLINE const char* strerror_adapter(char* gnu, char*) +{ + return gnu; +} + +GHC_INLINE const char* strerror_adapter(int posix, char* buffer) +{ + if (posix) { + return "Error in strerror_r!"; + } + return buffer; +} + +template +GHC_INLINE std::string systemErrorText(ErrorNumber code = 0) +{ +#if defined(GHC_OS_WINDOWS) + LPVOID msgBuf; + DWORD dw = code ? static_cast(code) : ::GetLastError(); + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&msgBuf, 0, NULL); + std::string msg = toUtf8(std::wstring((LPWSTR)msgBuf)); + LocalFree(msgBuf); + return msg; +#else + char buffer[512]; + return strerror_adapter(strerror_r(code ? code : errno, buffer, sizeof(buffer)), buffer); +#endif +} + +#ifdef GHC_OS_WINDOWS +using CreateSymbolicLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, DWORD); +using CreateHardLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); + +GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool to_directory, std::error_code& ec) +{ + std::error_code tec; + auto fs = status(target_name, tec); + if ((fs.type() == file_type::directory && !to_directory) || (fs.type() == file_type::regular && to_directory)) { + ec = detail::make_error_code(detail::portable_error::not_supported); + return; + } +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#elif defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) +#pragma warning(push) +#pragma warning(disable : 4191) +#endif + static CreateSymbolicLinkW_fp api_call = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateSymbolicLinkW")); +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) +#pragma warning(pop) +#endif + if (api_call) { + if (api_call(GHC_NATIVEWP(new_symlink), GHC_NATIVEWP(target_name), to_directory ? 1 : 0) == 0) { + auto result = ::GetLastError(); + if (result == ERROR_PRIVILEGE_NOT_HELD && api_call(GHC_NATIVEWP(new_symlink), GHC_NATIVEWP(target_name), to_directory ? 3 : 2) != 0) { + return; + } + ec = detail::make_system_error(result); + } + } + else { + ec = detail::make_system_error(ERROR_NOT_SUPPORTED); + } +} + +GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec) +{ +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#elif defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) +#pragma warning(push) +#pragma warning(disable : 4191) +#endif + static CreateHardLinkW_fp api_call = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateHardLinkW")); +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) +#pragma warning(pop) +#endif + if (api_call) { + if (api_call(GHC_NATIVEWP(new_hardlink), GHC_NATIVEWP(target_name), NULL) == 0) { + ec = detail::make_system_error(); + } + } + else { + ec = detail::make_system_error(ERROR_NOT_SUPPORTED); + } +} + +GHC_INLINE path getFullPathName(const wchar_t* p, std::error_code& ec) +{ + ULONG size = ::GetFullPathNameW(p, 0, 0, 0); + if (size) { + std::vector buf(size, 0); + ULONG s2 = GetFullPathNameW(p, size, buf.data(), nullptr); + if (s2 && s2 < size) { + return path(std::wstring(buf.data(), s2)); + } + } + ec = detail::make_system_error(); + return path(); +} + +#else +GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool, std::error_code& ec) +{ + if (::symlink(target_name.c_str(), new_symlink.c_str()) != 0) { + ec = detail::make_system_error(); + } +} + +#ifndef GHC_OS_WEB +GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec) +{ + if (::link(target_name.c_str(), new_hardlink.c_str()) != 0) { + ec = detail::make_system_error(); + } +} +#endif +#endif + +template +GHC_INLINE file_status file_status_from_st_mode(T mode) +{ +#ifdef GHC_OS_WINDOWS + file_type ft = file_type::unknown; + if ((mode & _S_IFDIR) == _S_IFDIR) { + ft = file_type::directory; + } + else if ((mode & _S_IFREG) == _S_IFREG) { + ft = file_type::regular; + } + else if ((mode & _S_IFCHR) == _S_IFCHR) { + ft = file_type::character; + } + perms prms = static_cast(mode & 0xfff); + return file_status(ft, prms); +#else + file_type ft = file_type::unknown; + if (S_ISDIR(mode)) { + ft = file_type::directory; + } + else if (S_ISREG(mode)) { + ft = file_type::regular; + } + else if (S_ISCHR(mode)) { + ft = file_type::character; + } + else if (S_ISBLK(mode)) { + ft = file_type::block; + } + else if (S_ISFIFO(mode)) { + ft = file_type::fifo; + } + else if (S_ISLNK(mode)) { + ft = file_type::symlink; + } + else if (S_ISSOCK(mode)) { + ft = file_type::socket; + } + perms prms = static_cast(mode & 0xfff); + return file_status(ft, prms); +#endif +} + +#ifdef GHC_OS_WINDOWS + +class unique_handle +{ +public: + typedef HANDLE element_type; + + unique_handle() noexcept + : _handle(INVALID_HANDLE_VALUE) + { + } + explicit unique_handle(element_type h) noexcept + : _handle(h) + { + } + unique_handle(unique_handle&& u) noexcept + : _handle(u.release()) + { + } + ~unique_handle() { reset(); } + unique_handle& operator=(unique_handle&& u) noexcept + { + reset(u.release()); + return *this; + } + element_type get() const noexcept { return _handle; } + explicit operator bool() const noexcept { return _handle != INVALID_HANDLE_VALUE; } + element_type release() noexcept + { + element_type tmp = _handle; + _handle = INVALID_HANDLE_VALUE; + return tmp; + } + void reset(element_type h = INVALID_HANDLE_VALUE) noexcept + { + element_type tmp = _handle; + _handle = h; + if (tmp != INVALID_HANDLE_VALUE) { + CloseHandle(tmp); + } + } + void swap(unique_handle& u) noexcept { std::swap(_handle, u._handle); } + +private: + element_type _handle; +}; + +#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE +typedef struct _REPARSE_DATA_BUFFER +{ + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union + { + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct + { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + } DUMMYUNIONNAME; +} REPARSE_DATA_BUFFER; +#ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE +#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE (16 * 1024) +#endif +#endif + +template +struct free_deleter +{ + void operator()(T* p) const { std::free(p); } +}; + +GHC_INLINE std::unique_ptr> getReparseData(const path& p, std::error_code& ec) +{ + unique_handle file(CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0)); + if (!file) { + ec = detail::make_system_error(); + return nullptr; + } + + std::unique_ptr> reparseData(reinterpret_cast(std::calloc(1, MAXIMUM_REPARSE_DATA_BUFFER_SIZE))); + ULONG bufferUsed; + if (DeviceIoControl(file.get(), FSCTL_GET_REPARSE_POINT, 0, 0, reparseData.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bufferUsed, 0)) { + return reparseData; + } + else { + ec = detail::make_system_error(); + } + return nullptr; +} +#endif + +GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec) +{ +#ifdef GHC_OS_WINDOWS + path result; + auto reparseData = detail::getReparseData(p, ec); + if (!ec) { + if (reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag)) { + switch (reparseData->ReparseTag) { + case IO_REPARSE_TAG_SYMLINK: { + auto printName = std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(WCHAR)); + auto substituteName = + std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); + if (detail::endsWith(substituteName, printName) && detail::startsWith(substituteName, std::wstring(L"\\??\\"))) { + result = printName; + } + else { + result = substituteName; + } + if (reparseData->SymbolicLinkReparseBuffer.Flags & 0x1 /*SYMLINK_FLAG_RELATIVE*/) { + result = p.parent_path() / result; + } + break; + } + case IO_REPARSE_TAG_MOUNT_POINT: + result = detail::getFullPathName(GHC_NATIVEWP(p), ec); + // result = std::wstring(&reparseData->MountPointReparseBuffer.PathBuffer[reparseData->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); + break; + default: + break; + } + } + } + return result; +#else + size_t bufferSize = 256; + while (true) { + std::vector buffer(bufferSize, static_cast(0)); + auto rc = ::readlink(p.c_str(), buffer.data(), buffer.size()); + if (rc < 0) { + ec = detail::make_system_error(); + return path(); + } + else if (rc < static_cast(bufferSize)) { + return path(std::string(buffer.data(), static_cast(rc))); + } + bufferSize *= 2; + } + return path(); +#endif +} + +#ifdef GHC_OS_WINDOWS +GHC_INLINE time_t timeFromFILETIME(const FILETIME& ft) +{ + ULARGE_INTEGER ull; + ull.LowPart = ft.dwLowDateTime; + ull.HighPart = ft.dwHighDateTime; + return static_cast(ull.QuadPart / 10000000ULL - 11644473600ULL); +} + +GHC_INLINE void timeToFILETIME(time_t t, FILETIME& ft) +{ + ULARGE_INTEGER ull; + ull.QuadPart = static_cast((t * 10000000LL) + 116444736000000000LL); + ft.dwLowDateTime = ull.LowPart; + ft.dwHighDateTime = ull.HighPart; +} + +template +GHC_INLINE uintmax_t hard_links_from_INFO(const INFO* info) +{ + return static_cast(-1); +} + +template <> +GHC_INLINE uintmax_t hard_links_from_INFO(const BY_HANDLE_FILE_INFORMATION* info) +{ + return info->nNumberOfLinks; +} + +template +GHC_INLINE bool is_symlink_from_INFO(const path &p, const INFO* info, std::error_code& ec) +{ + if ((info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { + auto reparseData = detail::getReparseData(p, ec); + if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) { + return true; + } + } + return false; +} + +template <> +GHC_INLINE bool is_symlink_from_INFO(const path &, const WIN32_FIND_DATAW* info, std::error_code&) +{ + // dwReserved0 is undefined unless dwFileAttributes includes the + // FILE_ATTRIBUTE_REPARSE_POINT attribute according to microsoft + // documentation. In practice, dwReserved0 is not reset which + // causes it to report the incorrect symlink status. + // Note that microsoft documentation does not say whether there is + // a null value for dwReserved0, so we test for symlink directly + // instead of returning the tag which requires returning a null + // value for non-reparse-point files. + return (info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && info->dwReserved0 == IO_REPARSE_TAG_SYMLINK; +} + +template +GHC_INLINE file_status status_from_INFO(const path& p, const INFO* info, std::error_code& ec, uintmax_t* sz = nullptr, time_t* lwt = nullptr) +{ + file_type ft = file_type::unknown; + if (is_symlink_from_INFO(p, info, ec)) { + ft = file_type::symlink; + } + else if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + ft = file_type::directory; + } + else { + ft = file_type::regular; + } + perms prms = perms::owner_read | perms::group_read | perms::others_read; + if (!(info->dwFileAttributes & FILE_ATTRIBUTE_READONLY)) { + prms = prms | perms::owner_write | perms::group_write | perms::others_write; + } + if (has_executable_extension(p)) { + prms = prms | perms::owner_exec | perms::group_exec | perms::others_exec; + } + if (sz) { + *sz = static_cast(info->nFileSizeHigh) << (sizeof(info->nFileSizeHigh) * 8) | info->nFileSizeLow; + } + if (lwt) { + *lwt = detail::timeFromFILETIME(info->ftLastWriteTime); + } + return file_status(ft, prms); +} + +#endif + +GHC_INLINE bool is_not_found_error(std::error_code& ec) +{ +#ifdef GHC_OS_WINDOWS + return ec.value() == ERROR_FILE_NOT_FOUND || ec.value() == ERROR_PATH_NOT_FOUND || ec.value() == ERROR_INVALID_NAME; +#else + return ec.value() == ENOENT || ec.value() == ENOTDIR; +#endif +} + +GHC_INLINE file_status symlink_status_ex(const path& p, std::error_code& ec, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr) noexcept +{ +#ifdef GHC_OS_WINDOWS + file_status fs; + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + } + else { + ec.clear(); + fs = detail::status_from_INFO(p, &attr, ec, sz, lwt); + if (nhl) { + *nhl = 0; + } + } + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found); + } + return ec ? file_status(file_type::none) : fs; +#else + (void)sz; + (void)nhl; + (void)lwt; + struct ::stat fs; + auto result = ::lstat(p.c_str(), &fs); + if (result == 0) { + ec.clear(); + file_status f_s = detail::file_status_from_st_mode(fs.st_mode); + return f_s; + } + ec = detail::make_system_error(); + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found, perms::unknown); + } + return file_status(file_type::none); +#endif +} + +GHC_INLINE file_status status_ex(const path& p, std::error_code& ec, file_status* sls = nullptr, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr, int recurse_count = 0) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (recurse_count > 16) { + ec = detail::make_system_error(0x2A9 /*ERROR_STOPPED_ON_SYMLINK*/); + return file_status(file_type::unknown); + } + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!::GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + } + else if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + auto reparseData = detail::getReparseData(p, ec); + if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) { + path target = resolveSymlink(p, ec); + file_status result; + if (!ec && !target.empty()) { + if (sls) { + *sls = status_from_INFO(p, &attr, ec); + } + return detail::status_ex(target, ec, nullptr, sz, nhl, lwt, recurse_count + 1); + } + return file_status(file_type::unknown); + } + } + if (ec) { + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found); + } + return file_status(file_type::none); + } + if (nhl) { + *nhl = 0; + } + return detail::status_from_INFO(p, &attr, ec, sz, lwt); +#else + (void)recurse_count; + struct ::stat st; + auto result = ::lstat(p.c_str(), &st); + if (result == 0) { + ec.clear(); + file_status fs = detail::file_status_from_st_mode(st.st_mode); + if (sls) { + *sls = fs; + } + if (fs.type() == file_type::symlink) { + result = ::stat(p.c_str(), &st); + if (result == 0) { + fs = detail::file_status_from_st_mode(st.st_mode); + } + else { + ec = detail::make_system_error(); + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found, perms::unknown); + } + return file_status(file_type::none); + } + } + if (sz) { + *sz = static_cast(st.st_size); + } + if (nhl) { + *nhl = st.st_nlink; + } + if (lwt) { + *lwt = st.st_mtime; + } + return fs; + } + else { + ec = detail::make_system_error(); + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found, perms::unknown); + } + return file_status(file_type::none); + } +#endif +} + +} // namespace detail + +GHC_INLINE u8arguments::u8arguments(int& argc, char**& argv) + : _argc(argc) + , _argv(argv) + , _refargc(argc) + , _refargv(argv) + , _isvalid(false) +{ +#ifdef GHC_OS_WINDOWS + LPWSTR* p; + p = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + _args.reserve(static_cast(argc)); + _argp.reserve(static_cast(argc)); + for (size_t i = 0; i < static_cast(argc); ++i) { + _args.push_back(detail::toUtf8(std::wstring(p[i]))); + _argp.push_back((char*)_args[i].data()); + } + argv = _argp.data(); + ::LocalFree(p); + _isvalid = true; +#else + std::setlocale(LC_ALL, ""); +#if defined(__ANDROID__) && __ANDROID_API__ < 26 + _isvalid = true; +#else + if (detail::equals_simple_insensitive(::nl_langinfo(CODESET), "UTF-8")) { + _isvalid = true; + } +#endif +#endif +} + +//----------------------------------------------------------------------------- +// [fs.path.construct] constructors and destructor + +GHC_INLINE path::path() noexcept {} + +GHC_INLINE path::path(const path& p) + : _path(p._path) +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + , _prefixLength(p._prefixLength) +#endif +{ +} + +GHC_INLINE path::path(path&& p) noexcept + : _path(std::move(p._path)) +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + , _prefixLength(p._prefixLength) +#endif +{ +} + +GHC_INLINE path::path(string_type&& source, format fmt) + : _path(std::move(source)) +{ + postprocess_path_with_format(fmt); +} + +#endif // GHC_EXPAND_IMPL + +#ifdef GHC_WITH_EXCEPTIONS +template +inline path::path(const Source& source, const std::locale& loc, format fmt) + : path(source, fmt) +{ + std::string locName = loc.name(); + if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) { + throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported)); + } +} + +template +inline path::path(InputIterator first, InputIterator last, const std::locale& loc, format fmt) + : path(std::basic_string::value_type>(first, last), fmt) +{ + std::string locName = loc.name(); + if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) { + throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported)); + } +} +#endif + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE path::~path() {} + +//----------------------------------------------------------------------------- +// [fs.path.assign] assignments + +GHC_INLINE path& path::operator=(const path& p) +{ + _path = p._path; +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = p._prefixLength; +#endif + return *this; +} + +GHC_INLINE path& path::operator=(path&& p) noexcept +{ + _path = std::move(p._path); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = p._prefixLength; +#endif + return *this; +} + +GHC_INLINE path& path::operator=(path::string_type&& source) +{ + return assign(source); +} + +GHC_INLINE path& path::assign(path::string_type&& source) +{ + _path = std::move(source); + postprocess_path_with_format(native_format); + return *this; +} + +#endif // GHC_EXPAND_IMPL + +template +inline path& path::operator=(const Source& source) +{ + return assign(source); +} + +template +inline path& path::assign(const Source& source) +{ +#ifdef GHC_USE_WCHAR_T + _path.assign(detail::toWChar(source)); +#else + _path.assign(detail::toUtf8(source)); +#endif + postprocess_path_with_format(native_format); + return *this; +} + +template <> +inline path& path::assign(const path& source) +{ + _path = source._path; +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = source._prefixLength; +#endif + return *this; +} + +template +inline path& path::assign(InputIterator first, InputIterator last) +{ + _path.assign(first, last); + postprocess_path_with_format(native_format); + return *this; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.append] appends + +GHC_INLINE path& path::operator/=(const path& p) +{ + if (p.empty()) { + // was: if ((!has_root_directory() && is_absolute()) || has_filename()) + if (!_path.empty() && _path[_path.length() - 1] != preferred_separator && _path[_path.length() - 1] != ':') { + _path += preferred_separator; + } + return *this; + } + if ((p.is_absolute() && (_path != root_name()._path || p._path != "/")) || (p.has_root_name() && p.root_name() != root_name())) { + assign(p); + return *this; + } + if (p.has_root_directory()) { + assign(root_name()); + } + else if ((!has_root_directory() && is_absolute()) || has_filename()) { + _path += preferred_separator; + } + auto iter = p.begin(); + bool first = true; + if (p.has_root_name()) { + ++iter; + } + while (iter != p.end()) { + if (!first && !(!_path.empty() && _path[_path.length() - 1] == preferred_separator)) { + _path += preferred_separator; + } + first = false; + _path += (*iter++).native(); + } + check_long_path(); + return *this; +} + +GHC_INLINE void path::append_name(const value_type* name) +{ + if (_path.empty()) { + this->operator/=(path(name)); + } + else { + if (_path.back() != path::preferred_separator) { + _path.push_back(path::preferred_separator); + } + _path += name; + check_long_path(); + } +} + +#endif // GHC_EXPAND_IMPL + +template +inline path& path::operator/=(const Source& source) +{ + return append(source); +} + +template +inline path& path::append(const Source& source) +{ + return this->operator/=(path(source)); +} + +template <> +inline path& path::append(const path& p) +{ + return this->operator/=(p); +} + +template +inline path& path::append(InputIterator first, InputIterator last) +{ + std::basic_string::value_type> part(first, last); + return append(part); +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.concat] concatenation + +GHC_INLINE path& path::operator+=(const path& x) +{ + return concat(x._path); +} + +GHC_INLINE path& path::operator+=(const string_type& x) +{ + return concat(x); +} + +#ifdef GHC_WITH_STRING_VIEW +GHC_INLINE path& path::operator+=(basic_string_view x) +{ + return concat(x); +} +#endif + +GHC_INLINE path& path::operator+=(const value_type* x) +{ +#ifdef GHC_WITH_STRING_VIEW + basic_string_view part(x); +#else + string_type part(x); +#endif + return concat(part); +} + +GHC_INLINE path& path::operator+=(value_type x) +{ +#ifdef GHC_OS_WINDOWS + if (x == generic_separator) { + x = preferred_separator; + } +#endif + if (_path.empty() || _path.back() != preferred_separator) { + _path += x; + } + check_long_path(); + return *this; +} + +#endif // GHC_EXPAND_IMPL + +template +inline path::path_from_string& path::operator+=(const Source& x) +{ + return concat(x); +} + +template +inline path::path_type_EcharT& path::operator+=(EcharT x) +{ +#ifdef GHC_WITH_STRING_VIEW + basic_string_view part(&x, 1); +#else + std::basic_string part(1, x); +#endif + concat(part); + return *this; +} + +template +inline path& path::concat(const Source& x) +{ + path p(x); + _path += p._path; + postprocess_path_with_format(native_format); + return *this; +} +template +inline path& path::concat(InputIterator first, InputIterator last) +{ + _path.append(first, last); + postprocess_path_with_format(native_format); + return *this; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.modifiers] modifiers +GHC_INLINE void path::clear() noexcept +{ + _path.clear(); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = 0; +#endif +} + +GHC_INLINE path& path::make_preferred() +{ + // as this filesystem implementation only uses generic_format + // internally, this must be a no-op + return *this; +} + +GHC_INLINE path& path::remove_filename() +{ + if (has_filename()) { + _path.erase(_path.size() - filename()._path.size()); + } + return *this; +} + +GHC_INLINE path& path::replace_filename(const path& replacement) +{ + remove_filename(); + return append(replacement); +} + +GHC_INLINE path& path::replace_extension(const path& replacement) +{ + if (has_extension()) { + _path.erase(_path.size() - extension()._path.size()); + } + if (!replacement.empty() && replacement._path[0] != '.') { + _path += '.'; + } + return concat(replacement); +} + +GHC_INLINE void path::swap(path& rhs) noexcept +{ + _path.swap(rhs._path); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + std::swap(_prefixLength, rhs._prefixLength); +#endif +} + +//----------------------------------------------------------------------------- +// [fs.path.native.obs] native format observers +GHC_INLINE const path::string_type& path::native() const noexcept +{ + return _path; +} + +GHC_INLINE const path::value_type* path::c_str() const noexcept +{ + return native().c_str(); +} + +GHC_INLINE path::operator path::string_type() const +{ + return native(); +} + +#endif // GHC_EXPAND_IMPL + +template +inline std::basic_string path::string(const Allocator& a) const +{ +#ifdef GHC_USE_WCHAR_T + return detail::fromWChar>(_path, a); +#else + return detail::fromUtf8>(_path, a); +#endif +} + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE std::string path::string() const +{ +#ifdef GHC_USE_WCHAR_T + return detail::toUtf8(native()); +#else + return native(); +#endif +} + +GHC_INLINE std::wstring path::wstring() const +{ +#ifdef GHC_USE_WCHAR_T + return native(); +#else + return detail::fromUtf8(native()); +#endif +} + +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +GHC_INLINE std::u8string path::u8string() const +{ +#ifdef GHC_USE_WCHAR_T + return std::u8string(reinterpret_cast(detail::toUtf8(native()).c_str())); +#else + return std::u8string(reinterpret_cast(c_str())); +#endif +} +#else +GHC_INLINE std::string path::u8string() const +{ +#ifdef GHC_USE_WCHAR_T + return detail::toUtf8(native()); +#else + return native(); +#endif +} +#endif + +GHC_INLINE std::u16string path::u16string() const +{ + // TODO: optimize + return detail::fromUtf8(string()); +} + +GHC_INLINE std::u32string path::u32string() const +{ + // TODO: optimize + return detail::fromUtf8(string()); +} + +#endif // GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.generic.obs] generic format observers +template +inline std::basic_string path::generic_string(const Allocator& a) const +{ +#ifdef GHC_OS_WINDOWS +#ifdef GHC_USE_WCHAR_T + auto result = detail::fromWChar, path::string_type>(_path, a); +#else + auto result = detail::fromUtf8>(_path, a); +#endif + for (auto& c : result) { + if (c == preferred_separator) { + c = generic_separator; + } + } + return result; +#else + return detail::fromUtf8>(_path, a); +#endif +} + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE std::string path::generic_string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return _path; +#endif +} + +GHC_INLINE std::wstring path::generic_wstring() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return detail::fromUtf8(_path); +#endif +} // namespace filesystem + +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +GHC_INLINE std::u8string path::generic_u8string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return std::u8string(reinterpret_cast(_path.c_str())); +#endif +} +#else +GHC_INLINE std::string path::generic_u8string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return _path; +#endif +} +#endif + +GHC_INLINE std::u16string path::generic_u16string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return detail::fromUtf8(_path); +#endif +} + +GHC_INLINE std::u32string path::generic_u32string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return detail::fromUtf8(_path); +#endif +} + +//----------------------------------------------------------------------------- +// [fs.path.compare] compare +GHC_INLINE int path::compare(const path& p) const noexcept +{ +#ifdef LWG_2936_BEHAVIOUR + auto rnl1 = root_name_length(); + auto rnl2 = p.root_name_length(); +#ifdef GHC_OS_WINDOWS + auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); +#else + auto rnc = _path.compare(0, rnl1, p._path, 0, (std::min(rnl1, rnl2))); +#endif + if (rnc) { + return rnc; + } + bool hrd1 = has_root_directory(), hrd2 = p.has_root_directory(); + if (hrd1 != hrd2) { + return hrd1 ? 1 : -1; + } + if (hrd1) { + ++rnl1; + ++rnl2; + } + auto iter1 = _path.begin() + static_cast(rnl1); + auto iter2 = p._path.begin() + static_cast(rnl2); + while (iter1 != _path.end() && iter2 != p._path.end() && *iter1 == *iter2) { + ++iter1; + ++iter2; + } + if (iter1 == _path.end()) { + return iter2 == p._path.end() ? 0 : -1; + } + if (iter2 == p._path.end()) { + return 1; + } + if (*iter1 == preferred_separator) { + return -1; + } + if (*iter2 == preferred_separator) { + return 1; + } + return *iter1 < *iter2 ? -1 : 1; +#else // LWG_2936_BEHAVIOUR +#ifdef GHC_OS_WINDOWS + auto rnl1 = root_name_length(); + auto rnl2 = p.root_name_length(); + auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); + if (rnc) { + return rnc; + } + return _path.compare(rnl1, std::string::npos, p._path, rnl2, std::string::npos); +#else + return _path.compare(p._path); +#endif +#endif +} + +GHC_INLINE int path::compare(const string_type& s) const +{ + return compare(path(s)); +} + +#ifdef GHC_WITH_STRING_VIEW +GHC_INLINE int path::compare(basic_string_view s) const +{ + return compare(path(s)); +} +#endif + +GHC_INLINE int path::compare(const value_type* s) const +{ + return compare(path(s)); +} + +//----------------------------------------------------------------------------- +// [fs.path.decompose] decomposition +#ifdef GHC_OS_WINDOWS +GHC_INLINE void path::handle_prefixes() +{ +#if defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = 0; + if (_path.length() >= 6 && _path[2] == '?' && std::toupper(static_cast(_path[4])) >= 'A' && std::toupper(static_cast(_path[4])) <= 'Z' && _path[5] == ':') { + if (detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\"))) || detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\??\\")))) { + _prefixLength = 4; + } + } +#endif // GHC_WIN_AUTO_PREFIX_LONG_PATH +} +#endif + +GHC_INLINE path::string_type::size_type path::root_name_length() const noexcept +{ +#ifdef GHC_OS_WINDOWS + if (_path.length() >= _prefixLength + 2 && std::toupper(static_cast(_path[_prefixLength])) >= 'A' && std::toupper(static_cast(_path[_prefixLength])) <= 'Z' && _path[_prefixLength + 1] == ':') { + return 2; + } +#endif + if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator && std::isprint(_path[_prefixLength + 2])) { + impl_string_type::size_type pos = _path.find(preferred_separator, _prefixLength + 3); + if (pos == impl_string_type::npos) { + return _path.length(); + } + else { + return pos; + } + } + return 0; +} + +GHC_INLINE path path::root_name() const +{ + return path(_path.substr(_prefixLength, root_name_length()), native_format); +} + +GHC_INLINE path path::root_directory() const +{ + if (has_root_directory()) { + static const path _root_dir(std::string(1, preferred_separator), native_format); + return _root_dir; + } + return path(); +} + +GHC_INLINE path path::root_path() const +{ + return path(root_name().string() + root_directory().string(), native_format); +} + +GHC_INLINE path path::relative_path() const +{ + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); + return path(_path.substr((std::min)(rootPathLen, _path.length())), generic_format); +} + +GHC_INLINE path path::parent_path() const +{ + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); + if (rootPathLen < _path.length()) { + if (empty()) { + return path(); + } + else { + auto piter = end(); + auto iter = piter.decrement(_path.end()); + if (iter > _path.begin() + static_cast(rootPathLen) && *iter != preferred_separator) { + --iter; + } + return path(_path.begin(), iter, native_format); + } + } + else { + return *this; + } +} + +GHC_INLINE path path::filename() const +{ + return !has_relative_path() ? path() : path(*--end()); +} + +GHC_INLINE path path::stem() const +{ + impl_string_type fn = filename().native(); + if (fn != "." && fn != "..") { + impl_string_type::size_type pos = fn.rfind('.'); + if (pos != impl_string_type::npos && pos > 0) { + return path{fn.substr(0, pos), native_format}; + } + } + return path{fn, native_format}; +} + +GHC_INLINE path path::extension() const +{ + if (has_relative_path()) { + auto iter = end(); + const auto& fn = *--iter; + impl_string_type::size_type pos = fn._path.rfind('.'); + if (pos != std::string::npos && pos > 0 && fn._path != "..") { + return path(fn._path.substr(pos), native_format); + } + } + return path(); +} + +#ifdef GHC_OS_WINDOWS +namespace detail { +GHC_INLINE bool has_executable_extension(const path& p) +{ + if (p.has_relative_path()) { + auto iter = p.end(); + const auto& fn = *--iter; + auto pos = fn._path.find_last_of('.'); + if (pos == std::string::npos || pos == 0 || fn._path.length() - pos != 3) { + return false; + } + const path::value_type* ext = fn._path.c_str() + pos + 1; + if (detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("exe")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("cmd")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("bat")) || + detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("com"))) { + return true; + } + } + return false; +} +} // namespace detail +#endif + +//----------------------------------------------------------------------------- +// [fs.path.query] query +GHC_INLINE bool path::empty() const noexcept +{ + return _path.empty(); +} + +GHC_INLINE bool path::has_root_name() const +{ + return root_name_length() > 0; +} + +GHC_INLINE bool path::has_root_directory() const +{ + auto rootLen = _prefixLength + root_name_length(); + return (_path.length() > rootLen && _path[rootLen] == preferred_separator); +} + +GHC_INLINE bool path::has_root_path() const +{ + return has_root_name() || has_root_directory(); +} + +GHC_INLINE bool path::has_relative_path() const +{ + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); + return rootPathLen < _path.length(); +} + +GHC_INLINE bool path::has_parent_path() const +{ + return !parent_path().empty(); +} + +GHC_INLINE bool path::has_filename() const +{ + return has_relative_path() && !filename().empty(); +} + +GHC_INLINE bool path::has_stem() const +{ + return !stem().empty(); +} + +GHC_INLINE bool path::has_extension() const +{ + return !extension().empty(); +} + +GHC_INLINE bool path::is_absolute() const +{ +#ifdef GHC_OS_WINDOWS + return has_root_name() && has_root_directory(); +#else + return has_root_directory(); +#endif +} + +GHC_INLINE bool path::is_relative() const +{ + return !is_absolute(); +} + +//----------------------------------------------------------------------------- +// [fs.path.gen] generation +GHC_INLINE path path::lexically_normal() const +{ + path dest; + bool lastDotDot = false; + for (string_type s : *this) { + if (s == ".") { + dest /= ""; + continue; + } + else if (s == ".." && !dest.empty()) { + auto root = root_path(); + if (dest == root) { + continue; + } + else if (*(--dest.end()) != "..") { + if (dest._path.back() == preferred_separator) { + dest._path.pop_back(); + } + dest.remove_filename(); + continue; + } + } + if (!(s.empty() && lastDotDot)) { + dest /= s; + } + lastDotDot = s == ".."; + } + if (dest.empty()) { + dest = "."; + } + return dest; +} + +GHC_INLINE path path::lexically_relative(const path& base) const +{ + if (root_name() != base.root_name() || is_absolute() != base.is_absolute() || (!has_root_directory() && base.has_root_directory())) { + return path(); + } + const_iterator a = begin(), b = base.begin(); + while (a != end() && b != base.end() && *a == *b) { + ++a; + ++b; + } + if (a == end() && b == base.end()) { + return path("."); + } + int count = 0; + for (const auto& element : input_iterator_range(b, base.end())) { + if (element != "." && element != "" && element != "..") { + ++count; + } + else if (element == "..") { + --count; + } + } + if (count == 0 && (a == end() || a->empty())) { + return path("."); + } + if (count < 0) { + return path(); + } + path result; + for (int i = 0; i < count; ++i) { + result /= ".."; + } + for (const auto& element : input_iterator_range(a, end())) { + result /= element; + } + return result; +} + +GHC_INLINE path path::lexically_proximate(const path& base) const +{ + path result = lexically_relative(base); + return result.empty() ? *this : result; +} + +//----------------------------------------------------------------------------- +// [fs.path.itr] iterators +GHC_INLINE path::iterator::iterator() {} + +GHC_INLINE path::iterator::iterator(const path& p, const impl_string_type::const_iterator& pos) + : _first(p._path.begin()) + , _last(p._path.end()) + , _prefix(_first + static_cast(p._prefixLength)) + , _root(p.has_root_directory() ? _first + static_cast(p._prefixLength + p.root_name_length()) : _last) + , _iter(pos) +{ + if (pos != _last) { + updateCurrent(); + } +} + +GHC_INLINE path::impl_string_type::const_iterator path::iterator::increment(const path::impl_string_type::const_iterator& pos) const +{ + path::impl_string_type::const_iterator i = pos; + bool fromStart = i == _first || i == _prefix; + if (i != _last) { + if (fromStart && i == _first && _prefix > _first) { + i = _prefix; + } + else if (*i++ == preferred_separator) { + // we can only sit on a slash if it is a network name or a root + if (i != _last && *i == preferred_separator) { + if (fromStart && !(i + 1 != _last && *(i + 1) == preferred_separator)) { + // leadind double slashes detected, treat this and the + // following until a slash as one unit + i = std::find(++i, _last, preferred_separator); + } + else { + // skip redundant slashes + while (i != _last && *i == preferred_separator) { + ++i; + } + } + } + } + else { +#ifdef GHC_OS_WINDOWS + if (fromStart && i != _last && *i == ':') { + ++i; + } + else { +#else + { +#endif + i = std::find(i, _last, preferred_separator); + } + } + } + return i; +} + +GHC_INLINE path::impl_string_type::const_iterator path::iterator::decrement(const path::impl_string_type::const_iterator& pos) const +{ + path::impl_string_type::const_iterator i = pos; + if (i != _first) { + --i; + // if this is now the root slash or the trailing slash, we are done, + // else check for network name + if (i != _root && (pos != _last || *i != preferred_separator)) { +#ifdef GHC_OS_WINDOWS + static const impl_string_type seps = GHC_PLATFORM_LITERAL("\\:"); + i = std::find_first_of(std::reverse_iterator(i), std::reverse_iterator(_first), seps.begin(), seps.end()).base(); + if (i > _first && *i == ':') { + i++; + } +#else + i = std::find(std::reverse_iterator(i), std::reverse_iterator(_first), preferred_separator).base(); +#endif + // Now we have to check if this is a network name + if (i - _first == 2 && *_first == preferred_separator && *(_first + 1) == preferred_separator) { + i -= 2; + } + } + } + return i; +} + +GHC_INLINE void path::iterator::updateCurrent() +{ + if ((_iter == _last) || (_iter != _first && _iter != _last && (*_iter == preferred_separator && _iter != _root) && (_iter + 1 == _last))) { + _current.clear(); + } + else { + _current.assign(_iter, increment(_iter)); + } +} + +GHC_INLINE path::iterator& path::iterator::operator++() +{ + _iter = increment(_iter); + while (_iter != _last && // we didn't reach the end + _iter != _root && // this is not a root position + *_iter == preferred_separator && // we are on a separator + (_iter + 1) != _last // the slash is not the last char + ) { + ++_iter; + } + updateCurrent(); + return *this; +} + +GHC_INLINE path::iterator path::iterator::operator++(int) +{ + path::iterator i{*this}; + ++(*this); + return i; +} + +GHC_INLINE path::iterator& path::iterator::operator--() +{ + _iter = decrement(_iter); + updateCurrent(); + return *this; +} + +GHC_INLINE path::iterator path::iterator::operator--(int) +{ + auto i = *this; + --(*this); + return i; +} + +GHC_INLINE bool path::iterator::operator==(const path::iterator& other) const +{ + return _iter == other._iter; +} + +GHC_INLINE bool path::iterator::operator!=(const path::iterator& other) const +{ + return _iter != other._iter; +} + +GHC_INLINE path::iterator::reference path::iterator::operator*() const +{ + return _current; +} + +GHC_INLINE path::iterator::pointer path::iterator::operator->() const +{ + return &_current; +} + +GHC_INLINE path::iterator path::begin() const +{ + return iterator(*this, _path.begin()); +} + +GHC_INLINE path::iterator path::end() const +{ + return iterator(*this, _path.end()); +} + +//----------------------------------------------------------------------------- +// [fs.path.nonmember] path non-member functions +GHC_INLINE void swap(path& lhs, path& rhs) noexcept +{ + swap(lhs._path, rhs._path); +} + +GHC_INLINE size_t hash_value(const path& p) noexcept +{ + return std::hash()(p.generic_string()); +} + +#ifdef GHC_HAS_THREEWAY_COMP +GHC_INLINE std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) <=> 0; +} +#endif + +GHC_INLINE bool operator==(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) == 0; +} + +GHC_INLINE bool operator!=(const path& lhs, const path& rhs) noexcept +{ + return !(lhs == rhs); +} + +GHC_INLINE bool operator<(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) < 0; +} + +GHC_INLINE bool operator<=(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) <= 0; +} + +GHC_INLINE bool operator>(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) > 0; +} + +GHC_INLINE bool operator>=(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) >= 0; +} + +GHC_INLINE path operator/(const path& lhs, const path& rhs) +{ + path result(lhs); + result /= rhs; + return result; +} + +#endif // GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.io] path inserter and extractor +template +inline std::basic_ostream& operator<<(std::basic_ostream& os, const path& p) +{ + os << "\""; + auto ps = p.string(); + for (auto c : ps) { + if (c == '"' || c == '\\') { + os << '\\'; + } + os << c; + } + os << "\""; + return os; +} + +template +inline std::basic_istream& operator>>(std::basic_istream& is, path& p) +{ + std::basic_string tmp; + charT c; + is >> c; + if (c == '"') { + auto sf = is.flags(); + is >> std::noskipws; + while (is) { + auto c2 = is.get(); + if (is) { + if (c2 == '\\') { + c2 = is.get(); + if (is) { + tmp += static_cast(c2); + } + } + else if (c2 == '"') { + break; + } + else { + tmp += static_cast(c2); + } + } + } + if ((sf & std::ios_base::skipws) == std::ios_base::skipws) { + is >> std::skipws; + } + p = path(tmp); + } + else { + is >> tmp; + p = path(static_cast(c) + tmp); + } + return is; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.class.filesystem_error] Class filesystem_error +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) +{ +} + +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) + , _p1(p1) +{ + if (!_p1.empty()) { + _what_arg += ": '" + _p1.string() + "'"; + } +} + +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) + , _p1(p1) + , _p2(p2) +{ + if (!_p1.empty()) { + _what_arg += ": '" + _p1.string() + "'"; + } + if (!_p2.empty()) { + _what_arg += ", '" + _p2.string() + "'"; + } +} + +GHC_INLINE const path& filesystem_error::path1() const noexcept +{ + return _p1; +} + +GHC_INLINE const path& filesystem_error::path2() const noexcept +{ + return _p2; +} + +GHC_INLINE const char* filesystem_error::what() const noexcept +{ + return _what_arg.c_str(); +} + +//----------------------------------------------------------------------------- +// [fs.op.funcs] filesystem operations +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path absolute(const path& p) +{ + std::error_code ec; + path result = absolute(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path absolute(const path& p, std::error_code& ec) +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (p.empty()) { + return absolute(current_path(ec), ec) / ""; + } + ULONG size = ::GetFullPathNameW(GHC_NATIVEWP(p), 0, 0, 0); + if (size) { + std::vector buf(size, 0); + ULONG s2 = GetFullPathNameW(GHC_NATIVEWP(p), size, buf.data(), nullptr); + if (s2 && s2 < size) { + path result = path(std::wstring(buf.data(), s2)); + if (p.filename() == ".") { + result /= "."; + } + return result; + } + } + ec = detail::make_system_error(); + return path(); +#else + path base = current_path(ec); + if (!ec) { + if (p.empty()) { + return base / p; + } + if (p.has_root_name()) { + if (p.has_root_directory()) { + return p; + } + else { + return p.root_name() / base.root_directory() / base.relative_path() / p.relative_path(); + } + } + else { + if (p.has_root_directory()) { + return base.root_name() / p; + } + else { + return base / p; + } + } + } + ec = detail::make_system_error(); + return path(); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path canonical(const path& p) +{ + std::error_code ec; + auto result = canonical(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path canonical(const path& p, std::error_code& ec) +{ + if (p.empty()) { + ec = detail::make_error_code(detail::portable_error::not_found); + return path(); + } + path work = p.is_absolute() ? p : absolute(p, ec); + path result; + + auto fs = status(work, ec); + if (ec) { + return path(); + } + if (fs.type() == file_type::not_found) { + ec = detail::make_error_code(detail::portable_error::not_found); + return path(); + } + bool redo; + do { + auto rootPathLen = work._prefixLength + work.root_name_length() + (work.has_root_directory() ? 1 : 0); + redo = false; + result.clear(); + for (auto pe : work) { + if (pe.empty() || pe == ".") { + continue; + } + else if (pe == "..") { + result = result.parent_path(); + continue; + } + else if ((result / pe).string().length() <= rootPathLen) { + result /= pe; + continue; + } + auto sls = symlink_status(result / pe, ec); + if (ec) { + return path(); + } + if (is_symlink(sls)) { + redo = true; + auto target = read_symlink(result / pe, ec); + if (ec) { + return path(); + } + if (target.is_absolute()) { + result = target; + continue; + } + else { + result /= target; + continue; + } + } + else { + result /= pe; + } + } + work = result; + } while (redo); + ec.clear(); + return result; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void copy(const path& from, const path& to) +{ + copy(from, to, copy_options::none); +} + +GHC_INLINE void copy(const path& from, const path& to, copy_options options) +{ + std::error_code ec; + copy(from, to, options, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } +} +#endif + +GHC_INLINE void copy(const path& from, const path& to, std::error_code& ec) noexcept +{ + copy(from, to, copy_options::none, ec); +} + +GHC_INLINE void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept +{ + std::error_code tec; + file_status fs_from, fs_to; + ec.clear(); + if ((options & (copy_options::skip_symlinks | copy_options::copy_symlinks | copy_options::create_symlinks)) != copy_options::none) { + fs_from = symlink_status(from, ec); + } + else { + fs_from = status(from, ec); + } + if (!exists(fs_from)) { + if (!ec) { + ec = detail::make_error_code(detail::portable_error::not_found); + } + return; + } + if ((options & (copy_options::skip_symlinks | copy_options::create_symlinks)) != copy_options::none) { + fs_to = symlink_status(to, tec); + } + else { + fs_to = status(to, tec); + } + if (is_other(fs_from) || is_other(fs_to) || (is_directory(fs_from) && is_regular_file(fs_to)) || (exists(fs_to) && equivalent(from, to, ec))) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + } + else if (is_symlink(fs_from)) { + if ((options & copy_options::skip_symlinks) == copy_options::none) { + if (!exists(fs_to) && (options & copy_options::copy_symlinks) != copy_options::none) { + copy_symlink(from, to, ec); + } + else { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + } + } + } + else if (is_regular_file(fs_from)) { + if ((options & copy_options::directories_only) == copy_options::none) { + if ((options & copy_options::create_symlinks) != copy_options::none) { + create_symlink(from.is_absolute() ? from : canonical(from, ec), to, ec); + } +#ifndef GHC_OS_WEB + else if ((options & copy_options::create_hard_links) != copy_options::none) { + create_hard_link(from, to, ec); + } +#endif + else if (is_directory(fs_to)) { + copy_file(from, to / from.filename(), options, ec); + } + else { + copy_file(from, to, options, ec); + } + } + } +#ifdef LWG_2682_BEHAVIOUR + else if (is_directory(fs_from) && (options & copy_options::create_symlinks) != copy_options::none) { + ec = detail::make_error_code(detail::portable_error::is_a_directory); + } +#endif + else if (is_directory(fs_from) && (options == copy_options::none || (options & copy_options::recursive) != copy_options::none)) { + if (!exists(fs_to)) { + create_directory(to, from, ec); + if (ec) { + return; + } + } + for (auto iter = directory_iterator(from, ec); iter != directory_iterator(); iter.increment(ec)) { + if (!ec) { + copy(iter->path(), to / iter->path().filename(), options | static_cast(0x8000), ec); + } + if (ec) { + return; + } + } + } + return; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool copy_file(const path& from, const path& to) +{ + return copy_file(from, to, copy_options::none); +} + +GHC_INLINE bool copy_file(const path& from, const path& to, copy_options option) +{ + std::error_code ec; + auto result = copy_file(from, to, option, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } + return result; +} +#endif + +GHC_INLINE bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept +{ + return copy_file(from, to, copy_options::none, ec); +} + +GHC_INLINE bool copy_file(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept +{ + std::error_code tecf, tect; + auto sf = status(from, tecf); + auto st = status(to, tect); + bool overwrite = false; + ec.clear(); + if (!is_regular_file(sf)) { + ec = tecf; + return false; + } + if (exists(st)) { + if ((options & copy_options::skip_existing) == copy_options::skip_existing) { + return false; + } + if (!is_regular_file(st) || equivalent(from, to, ec) || (options & (copy_options::overwrite_existing | copy_options::update_existing)) == copy_options::none) { + ec = tect ? tect : detail::make_error_code(detail::portable_error::exists); + return false; + } + if ((options & copy_options::update_existing) == copy_options::update_existing) { + auto from_time = last_write_time(from, ec); + if (ec) { + ec = detail::make_system_error(); + return false; + } + auto to_time = last_write_time(to, ec); + if (ec) { + ec = detail::make_system_error(); + return false; + } + if (from_time <= to_time) { + return false; + } + } + overwrite = true; + } +#ifdef GHC_OS_WINDOWS + if (!::CopyFileW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), !overwrite)) { + ec = detail::make_system_error(); + return false; + } + return true; +#else + std::vector buffer(16384, '\0'); + int in = -1, out = -1; + if ((in = ::open(from.c_str(), O_RDONLY)) < 0) { + ec = detail::make_system_error(); + return false; + } + int mode = O_CREAT | O_WRONLY | O_TRUNC; + if (!overwrite) { + mode |= O_EXCL; + } + if ((out = ::open(to.c_str(), mode, static_cast(sf.permissions() & perms::all))) < 0) { + ec = detail::make_system_error(); + ::close(in); + return false; + } + if (st.permissions() != sf.permissions()) { + if (::fchmod(out, static_cast(sf.permissions() & perms::all)) != 0) { + ec = detail::make_system_error(); + ::close(in); + ::close(out); + return false; + } + } + ssize_t br, bw; + while (true) { + do { br = ::read(in, buffer.data(), buffer.size()); } while(errno == EINTR && !br); + if(!br) { + break; + } + if(br < 0) { + ec = detail::make_system_error(); + ::close(in); + ::close(out); + return false; + } + ssize_t offset = 0; + do { + if ((bw = ::write(out, buffer.data() + offset, static_cast(br))) > 0) { + br -= bw; + offset += bw; + } + else if (bw < 0 && errno != EINTR) { + ec = detail::make_system_error(); + ::close(in); + ::close(out); + return false; + } + } while (br); + } + ::close(in); + ::close(out); + return true; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink) +{ + std::error_code ec; + copy_symlink(existing_symlink, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), existing_symlink, new_symlink, ec); + } +} +#endif + +GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept +{ + ec.clear(); + auto to = read_symlink(existing_symlink, ec); + if (!ec) { + if (exists(to, ec) && is_directory(to, ec)) { + create_directory_symlink(to, new_symlink, ec); + } + else { + create_symlink(to, new_symlink, ec); + } + } +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directories(const path& p) +{ + std::error_code ec; + auto result = create_directories(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directories(const path& p, std::error_code& ec) noexcept +{ + path current; + ec.clear(); + bool didCreate = false; + auto rootPathLen = p._prefixLength + p.root_name_length() + (p.has_root_directory() ? 1 : 0); + current = p.native().substr(0, rootPathLen); + path folders(p._path.substr(rootPathLen)); + for (path::string_type part : folders) { + current /= part; + std::error_code tec; + auto fs = status(current, tec); + if (tec && fs.type() != file_type::not_found) { + ec = tec; + return false; + } + if (!exists(fs)) { + create_directory(current, ec); + if (ec) { + std::error_code tmp_ec; + if (is_directory(current, tmp_ec)) { + ec.clear(); + } + else { + return false; + } + } + didCreate = true; + } +#ifndef LWG_2935_BEHAVIOUR + else if (!is_directory(fs)) { + ec = detail::make_error_code(detail::portable_error::exists); + return false; + } +#endif + } + return didCreate; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directory(const path& p) +{ + std::error_code ec; + auto result = create_directory(p, path(), ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directory(const path& p, std::error_code& ec) noexcept +{ + return create_directory(p, path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directory(const path& p, const path& attributes) +{ + std::error_code ec; + auto result = create_directory(p, attributes, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept +{ + std::error_code tec; + ec.clear(); + auto fs = status(p, tec); +#ifdef LWG_2935_BEHAVIOUR + if (status_known(fs) && exists(fs)) { + return false; + } +#else + if (status_known(fs) && exists(fs) && is_directory(fs)) { + return false; + } +#endif +#ifdef GHC_OS_WINDOWS + if (!attributes.empty()) { + if (!::CreateDirectoryExW(GHC_NATIVEWP(attributes), GHC_NATIVEWP(p), NULL)) { + ec = detail::make_system_error(); + return false; + } + } + else if (!::CreateDirectoryW(GHC_NATIVEWP(p), NULL)) { + ec = detail::make_system_error(); + return false; + } +#else + ::mode_t attribs = static_cast(perms::all); + if (!attributes.empty()) { + struct ::stat fileStat; + if (::stat(attributes.c_str(), &fileStat) != 0) { + ec = detail::make_system_error(); + return false; + } + attribs = fileStat.st_mode; + } + if (::mkdir(p.c_str(), attribs) != 0) { + ec = detail::make_system_error(); + return false; + } +#endif + return true; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink) +{ + std::error_code ec; + create_directory_symlink(to, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec); + } +} +#endif + +GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept +{ + detail::create_symlink(to, new_symlink, true, ec); +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link) +{ + std::error_code ec; + create_hard_link(to, new_hard_link, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_hard_link, ec); + } +} +#endif + +GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept +{ + detail::create_hardlink(to, new_hard_link, ec); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_symlink(const path& to, const path& new_symlink) +{ + std::error_code ec; + create_symlink(to, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec); + } +} +#endif + +GHC_INLINE void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept +{ + detail::create_symlink(to, new_symlink, false, ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path current_path() +{ + std::error_code ec; + auto result = current_path(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE path current_path(std::error_code& ec) +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + DWORD pathlen = ::GetCurrentDirectoryW(0, 0); + std::unique_ptr buffer(new wchar_t[size_t(pathlen) + 1]); + if (::GetCurrentDirectoryW(pathlen, buffer.get()) == 0) { + ec = detail::make_system_error(); + return path(); + } + return path(std::wstring(buffer.get()), path::native_format); +#elif defined(__GLIBC__) + std::unique_ptr buffer { ::getcwd(NULL, 0), std::free }; + if (buffer == nullptr) { + ec = detail::make_system_error(); + return path(); + } + return path(buffer.get()); +#else + size_t pathlen = static_cast(std::max(int(::pathconf(".", _PC_PATH_MAX)), int(PATH_MAX))); + std::unique_ptr buffer(new char[pathlen + 1]); + if (::getcwd(buffer.get(), pathlen) == nullptr) { + ec = detail::make_system_error(); + return path(); + } + return path(buffer.get()); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void current_path(const path& p) +{ + std::error_code ec; + current_path(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void current_path(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (!::SetCurrentDirectoryW(GHC_NATIVEWP(p))) { + ec = detail::make_system_error(); + } +#else + if (::chdir(p.string().c_str()) == -1) { + ec = detail::make_system_error(); + } +#endif +} + +GHC_INLINE bool exists(file_status s) noexcept +{ + return status_known(s) && s.type() != file_type::not_found; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool exists(const path& p) +{ + return exists(status(p)); +} +#endif + +GHC_INLINE bool exists(const path& p, std::error_code& ec) noexcept +{ + file_status s = status(p, ec); + if (status_known(s)) { + ec.clear(); + } + return exists(s); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool equivalent(const path& p1, const path& p2) +{ + std::error_code ec; + bool result = equivalent(p1, p2, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p1, p2, ec); + } + return result; +} +#endif + +GHC_INLINE bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + detail::unique_handle file1(::CreateFileW(GHC_NATIVEWP(p1), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); + auto e1 = ::GetLastError(); + detail::unique_handle file2(::CreateFileW(GHC_NATIVEWP(p2), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); + if (!file1 || !file2) { +#ifdef LWG_2937_BEHAVIOUR + ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); +#else + if (file1 == file2) { + ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); + } +#endif + return false; + } + BY_HANDLE_FILE_INFORMATION inf1, inf2; + if (!::GetFileInformationByHandle(file1.get(), &inf1)) { + ec = detail::make_system_error(); + return false; + } + if (!::GetFileInformationByHandle(file2.get(), &inf2)) { + ec = detail::make_system_error(); + return false; + } + return inf1.ftLastWriteTime.dwLowDateTime == inf2.ftLastWriteTime.dwLowDateTime && inf1.ftLastWriteTime.dwHighDateTime == inf2.ftLastWriteTime.dwHighDateTime && inf1.nFileIndexHigh == inf2.nFileIndexHigh && inf1.nFileIndexLow == inf2.nFileIndexLow && + inf1.nFileSizeHigh == inf2.nFileSizeHigh && inf1.nFileSizeLow == inf2.nFileSizeLow && inf1.dwVolumeSerialNumber == inf2.dwVolumeSerialNumber; +#else + struct ::stat s1, s2; + auto rc1 = ::stat(p1.c_str(), &s1); + auto e1 = errno; + auto rc2 = ::stat(p2.c_str(), &s2); + if (rc1 || rc2) { +#ifdef LWG_2937_BEHAVIOUR + ec = detail::make_system_error(e1 ? e1 : errno); +#else + if (rc1 && rc2) { + ec = detail::make_system_error(e1 ? e1 : errno); + } +#endif + return false; + } + return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino && s1.st_size == s2.st_size && s1.st_mtime == s2.st_mtime; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t file_size(const path& p) +{ + std::error_code ec; + auto result = file_size(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t file_size(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + return static_cast(-1); + } + return static_cast(attr.nFileSizeHigh) << (sizeof(attr.nFileSizeHigh) * 8) | attr.nFileSizeLow; +#else + struct ::stat fileStat; + if (::stat(p.c_str(), &fileStat) == -1) { + ec = detail::make_system_error(); + return static_cast(-1); + } + return static_cast(fileStat.st_size); +#endif +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t hard_link_count(const path& p) +{ + std::error_code ec; + auto result = hard_link_count(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + uintmax_t result = static_cast(-1); + detail::unique_handle file(::CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); + BY_HANDLE_FILE_INFORMATION inf; + if (!file) { + ec = detail::make_system_error(); + } + else { + if (!::GetFileInformationByHandle(file.get(), &inf)) { + ec = detail::make_system_error(); + } + else { + result = inf.nNumberOfLinks; + } + } + return result; +#else + uintmax_t result = 0; + file_status fs = detail::status_ex(p, ec, nullptr, nullptr, &result, nullptr); + if (fs.type() == file_type::not_found) { + ec = detail::make_error_code(detail::portable_error::not_found); + } + return ec ? static_cast(-1) : result; +#endif +} +#endif + +GHC_INLINE bool is_block_file(file_status s) noexcept +{ + return s.type() == file_type::block; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_block_file(const path& p) +{ + return is_block_file(status(p)); +} +#endif + +GHC_INLINE bool is_block_file(const path& p, std::error_code& ec) noexcept +{ + return is_block_file(status(p, ec)); +} + +GHC_INLINE bool is_character_file(file_status s) noexcept +{ + return s.type() == file_type::character; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_character_file(const path& p) +{ + return is_character_file(status(p)); +} +#endif + +GHC_INLINE bool is_character_file(const path& p, std::error_code& ec) noexcept +{ + return is_character_file(status(p, ec)); +} + +GHC_INLINE bool is_directory(file_status s) noexcept +{ + return s.type() == file_type::directory; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_directory(const path& p) +{ + return is_directory(status(p)); +} +#endif + +GHC_INLINE bool is_directory(const path& p, std::error_code& ec) noexcept +{ + return is_directory(status(p, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_empty(const path& p) +{ + if (is_directory(p)) { + return directory_iterator(p) == directory_iterator(); + } + else { + return file_size(p) == 0; + } +} +#endif + +GHC_INLINE bool is_empty(const path& p, std::error_code& ec) noexcept +{ + auto fs = status(p, ec); + if (ec) { + return false; + } + if (is_directory(fs)) { + directory_iterator iter(p, ec); + if (ec) { + return false; + } + return iter == directory_iterator(); + } + else { + auto sz = file_size(p, ec); + if (ec) { + return false; + } + return sz == 0; + } +} + +GHC_INLINE bool is_fifo(file_status s) noexcept +{ + return s.type() == file_type::fifo; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_fifo(const path& p) +{ + return is_fifo(status(p)); +} +#endif + +GHC_INLINE bool is_fifo(const path& p, std::error_code& ec) noexcept +{ + return is_fifo(status(p, ec)); +} + +GHC_INLINE bool is_other(file_status s) noexcept +{ + return exists(s) && !is_regular_file(s) && !is_directory(s) && !is_symlink(s); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_other(const path& p) +{ + return is_other(status(p)); +} +#endif + +GHC_INLINE bool is_other(const path& p, std::error_code& ec) noexcept +{ + return is_other(status(p, ec)); +} + +GHC_INLINE bool is_regular_file(file_status s) noexcept +{ + return s.type() == file_type::regular; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_regular_file(const path& p) +{ + return is_regular_file(status(p)); +} +#endif + +GHC_INLINE bool is_regular_file(const path& p, std::error_code& ec) noexcept +{ + return is_regular_file(status(p, ec)); +} + +GHC_INLINE bool is_socket(file_status s) noexcept +{ + return s.type() == file_type::socket; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_socket(const path& p) +{ + return is_socket(status(p)); +} +#endif + +GHC_INLINE bool is_socket(const path& p, std::error_code& ec) noexcept +{ + return is_socket(status(p, ec)); +} + +GHC_INLINE bool is_symlink(file_status s) noexcept +{ + return s.type() == file_type::symlink; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_symlink(const path& p) +{ + return is_symlink(symlink_status(p)); +} +#endif + +GHC_INLINE bool is_symlink(const path& p, std::error_code& ec) noexcept +{ + return is_symlink(symlink_status(p, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_time_type last_write_time(const path& p) +{ + std::error_code ec; + auto result = last_write_time(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE file_time_type last_write_time(const path& p, std::error_code& ec) noexcept +{ + time_t result = 0; + ec.clear(); + file_status fs = detail::status_ex(p, ec, nullptr, nullptr, nullptr, &result); + return ec ? (file_time_type::min)() : std::chrono::system_clock::from_time_t(result); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void last_write_time(const path& p, file_time_type new_time) +{ + std::error_code ec; + last_write_time(p, new_time, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept +{ + ec.clear(); + auto d = new_time.time_since_epoch(); +#ifdef GHC_OS_WINDOWS + detail::unique_handle file(::CreateFileW(GHC_NATIVEWP(p), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL)); + FILETIME ft; + auto tt = std::chrono::duration_cast(d).count() * 10 + 116444736000000000; + ft.dwLowDateTime = static_cast(tt); + ft.dwHighDateTime = static_cast(tt >> 32); + if (!::SetFileTime(file.get(), 0, 0, &ft)) { + ec = detail::make_system_error(); + } +#elif defined(GHC_OS_APPLE) && \ + (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101300 \ + || defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 110000 \ + || defined(__TV_OS_VERSION_MIN_REQUIRED) && __TVOS_VERSION_MIN_REQUIRED < 110000 \ + || defined(__WATCH_OS_VERSION_MIN_REQUIRED) && __WATCHOS_VERSION_MIN_REQUIRED < 40000) + struct ::stat fs; + if (::stat(p.c_str(), &fs) == 0) { + struct ::timeval tv[2]; + tv[0].tv_sec = fs.st_atimespec.tv_sec; + tv[0].tv_usec = static_cast(fs.st_atimespec.tv_nsec / 1000); + tv[1].tv_sec = std::chrono::duration_cast(d).count(); + tv[1].tv_usec = static_cast(std::chrono::duration_cast(d).count() % 1000000); + if (::utimes(p.c_str(), tv) == 0) { + return; + } + } + ec = detail::make_system_error(); + return; +#else +#ifndef UTIME_OMIT +#define UTIME_OMIT ((1l << 30) - 2l) +#endif + struct ::timespec times[2]; + times[0].tv_sec = 0; + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_sec = static_cast(std::chrono::duration_cast(d).count()); + times[1].tv_nsec = static_cast(std::chrono::duration_cast(d).count() % 1000000000); +#if defined(__ANDROID_API__) && __ANDROID_API__ < 12 + if (syscall(__NR_utimensat, AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { +#else + if (::utimensat(static_cast(AT_FDCWD), p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { +#endif + ec = detail::make_system_error(); + } + return; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void permissions(const path& p, perms prms, perm_options opts) +{ + std::error_code ec; + permissions(p, prms, opts, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void permissions(const path& p, perms prms, std::error_code& ec) noexcept +{ + permissions(p, prms, perm_options::replace, ec); +} + +GHC_INLINE void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) noexcept +{ + if (static_cast(opts & (perm_options::replace | perm_options::add | perm_options::remove)) == 0) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + return; + } + auto fs = symlink_status(p, ec); + if ((opts & perm_options::replace) != perm_options::replace) { + if ((opts & perm_options::add) == perm_options::add) { + prms = fs.permissions() | prms; + } + else { + prms = fs.permissions() & ~prms; + } + } +#ifdef GHC_OS_WINDOWS +#ifdef __GNUC__ + auto oldAttr = GetFileAttributesW(GHC_NATIVEWP(p)); + if (oldAttr != INVALID_FILE_ATTRIBUTES) { + DWORD newAttr = ((prms & perms::owner_write) == perms::owner_write) ? oldAttr & ~(static_cast(FILE_ATTRIBUTE_READONLY)) : oldAttr | FILE_ATTRIBUTE_READONLY; + if (oldAttr == newAttr || SetFileAttributesW(GHC_NATIVEWP(p), newAttr)) { + return; + } + } + ec = detail::make_system_error(); +#else + int mode = 0; + if ((prms & perms::owner_read) == perms::owner_read) { + mode |= _S_IREAD; + } + if ((prms & perms::owner_write) == perms::owner_write) { + mode |= _S_IWRITE; + } + if (::_wchmod(p.wstring().c_str(), mode) != 0) { + ec = detail::make_system_error(); + } +#endif +#else + if ((opts & perm_options::nofollow) != perm_options::nofollow) { + if (::chmod(p.c_str(), static_cast(prms)) != 0) { + ec = detail::make_system_error(); + } + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path proximate(const path& p, std::error_code& ec) +{ + auto cp = current_path(ec); + if (!ec) { + return proximate(p, cp, ec); + } + return path(); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path proximate(const path& p, const path& base) +{ + return weakly_canonical(p).lexically_proximate(weakly_canonical(base)); +} +#endif + +GHC_INLINE path proximate(const path& p, const path& base, std::error_code& ec) +{ + return weakly_canonical(p, ec).lexically_proximate(weakly_canonical(base, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path read_symlink(const path& p) +{ + std::error_code ec; + auto result = read_symlink(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path read_symlink(const path& p, std::error_code& ec) +{ + file_status fs = symlink_status(p, ec); + if (fs.type() != file_type::symlink) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + return path(); + } + auto result = detail::resolveSymlink(p, ec); + return ec ? path() : result; +} + +GHC_INLINE path relative(const path& p, std::error_code& ec) +{ + return relative(p, current_path(ec), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path relative(const path& p, const path& base) +{ + return weakly_canonical(p).lexically_relative(weakly_canonical(base)); +} +#endif + +GHC_INLINE path relative(const path& p, const path& base, std::error_code& ec) +{ + return weakly_canonical(p, ec).lexically_relative(weakly_canonical(base, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool remove(const path& p) +{ + std::error_code ec; + auto result = remove(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool remove(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS +#ifdef GHC_USE_WCHAR_T + auto cstr = p.c_str(); +#else + std::wstring np = detail::fromUtf8(p.u8string()); + auto cstr = np.c_str(); +#endif + DWORD attr = GetFileAttributesW(cstr); + if (attr == INVALID_FILE_ATTRIBUTES) { + auto error = ::GetLastError(); + if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) { + return false; + } + ec = detail::make_system_error(error); + } + else if (attr & FILE_ATTRIBUTE_READONLY) { + auto new_attr = attr & ~static_cast(FILE_ATTRIBUTE_READONLY); + if (!SetFileAttributesW(cstr, new_attr)) { + auto error = ::GetLastError(); + ec = detail::make_system_error(error); + } + } + if (!ec) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + if (!RemoveDirectoryW(cstr)) { + ec = detail::make_system_error(); + } + } + else { + if (!DeleteFileW(cstr)) { + ec = detail::make_system_error(); + } + } + } +#else + if (::remove(p.c_str()) == -1) { + auto error = errno; + if (error == ENOENT) { + return false; + } + ec = detail::make_system_error(); + } +#endif + return ec ? false : true; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t remove_all(const path& p) +{ + std::error_code ec; + auto result = remove_all(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t remove_all(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); + uintmax_t count = 0; + if (p == "/") { + ec = detail::make_error_code(detail::portable_error::not_supported); + return static_cast(-1); + } + std::error_code tec; + auto fs = symlink_status(p, tec); + if (exists(fs) && is_directory(fs)) { + for (auto iter = directory_iterator(p, ec); iter != directory_iterator(); iter.increment(ec)) { + if (ec && !detail::is_not_found_error(ec)) { + break; + } + bool is_symlink_result = iter->is_symlink(ec); + if (ec) + return static_cast(-1); + if (!is_symlink_result && iter->is_directory(ec)) { + count += remove_all(iter->path(), ec); + if (ec) { + return static_cast(-1); + } + } + else { + if (!ec) { + remove(iter->path(), ec); + } + if (ec) { + return static_cast(-1); + } + ++count; + } + } + } + if (!ec) { + if (remove(p, ec)) { + ++count; + } + } + if (ec) { + return static_cast(-1); + } + return count; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void rename(const path& from, const path& to) +{ + std::error_code ec; + rename(from, to, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } +} +#endif + +GHC_INLINE void rename(const path& from, const path& to, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (from != to) { + if (!MoveFileExW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), (DWORD)MOVEFILE_REPLACE_EXISTING)) { + ec = detail::make_system_error(); + } + } +#else + if (from != to) { + if (::rename(from.c_str(), to.c_str()) != 0) { + ec = detail::make_system_error(); + } + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void resize_file(const path& p, uintmax_t size) +{ + std::error_code ec; + resize_file(p, size, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + LARGE_INTEGER lisize; + lisize.QuadPart = static_cast(size); + if (lisize.QuadPart < 0) { +#ifdef ERROR_FILE_TOO_LARGE + ec = detail::make_system_error(ERROR_FILE_TOO_LARGE); +#else + ec = detail::make_system_error(223); +#endif + return; + } + detail::unique_handle file(CreateFileW(GHC_NATIVEWP(p), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL)); + if (!file) { + ec = detail::make_system_error(); + } + else if (SetFilePointerEx(file.get(), lisize, NULL, FILE_BEGIN) == 0 || SetEndOfFile(file.get()) == 0) { + ec = detail::make_system_error(); + } +#else + if (::truncate(p.c_str(), static_cast(size)) != 0) { + ec = detail::make_system_error(); + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE space_info space(const path& p) +{ + std::error_code ec; + auto result = space(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE space_info space(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + ULARGE_INTEGER freeBytesAvailableToCaller = {{ 0, 0 }}; + ULARGE_INTEGER totalNumberOfBytes = {{ 0, 0 }}; + ULARGE_INTEGER totalNumberOfFreeBytes = {{ 0, 0 }}; + if (!GetDiskFreeSpaceExW(GHC_NATIVEWP(p), &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes)) { + ec = detail::make_system_error(); + return {static_cast(-1), static_cast(-1), static_cast(-1)}; + } + return {static_cast(totalNumberOfBytes.QuadPart), static_cast(totalNumberOfFreeBytes.QuadPart), static_cast(freeBytesAvailableToCaller.QuadPart)}; +#else + struct ::statvfs sfs; + if (::statvfs(p.c_str(), &sfs) != 0) { + ec = detail::make_system_error(); + return {static_cast(-1), static_cast(-1), static_cast(-1)}; + } + return {static_cast(sfs.f_blocks) * static_cast(sfs.f_frsize), static_cast(sfs.f_bfree) * static_cast(sfs.f_frsize), static_cast(sfs.f_bavail) * static_cast(sfs.f_frsize)}; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status status(const path& p) +{ + std::error_code ec; + auto result = status(p, ec); + if (result.type() == file_type::none) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE file_status status(const path& p, std::error_code& ec) noexcept +{ + return detail::status_ex(p, ec); +} + +GHC_INLINE bool status_known(file_status s) noexcept +{ + return s.type() != file_type::none; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status symlink_status(const path& p) +{ + std::error_code ec; + auto result = symlink_status(p, ec); + if (result.type() == file_type::none) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE file_status symlink_status(const path& p, std::error_code& ec) noexcept +{ + return detail::symlink_status_ex(p, ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path temp_directory_path() +{ + std::error_code ec; + path result = temp_directory_path(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE path temp_directory_path(std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + wchar_t buffer[512]; + auto rc = GetTempPathW(511, buffer); + if (!rc || rc > 511) { + ec = detail::make_system_error(); + return path(); + } + return path(std::wstring(buffer)); +#else + static const char* temp_vars[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR", nullptr}; + const char* temp_path = nullptr; + for (auto temp_name = temp_vars; *temp_name != nullptr; ++temp_name) { + temp_path = std::getenv(*temp_name); + if (temp_path) { + return path(temp_path); + } + } + return path("/tmp"); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path weakly_canonical(const path& p) +{ + std::error_code ec; + auto result = weakly_canonical(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path weakly_canonical(const path& p, std::error_code& ec) noexcept +{ + path result; + ec.clear(); + bool scan = true; + for (auto pe : p) { + if (scan) { + std::error_code tec; + if (exists(result / pe, tec)) { + result /= pe; + } + else { + if (ec) { + return path(); + } + scan = false; + if (!result.empty()) { + result = canonical(result, ec) / pe; + if (ec) { + break; + } + } + else { + result /= pe; + } + } + } + else { + result /= pe; + } + } + if (scan) { + if (!result.empty()) { + result = canonical(result, ec); + } + } + return ec ? path() : result.lexically_normal(); +} + +//----------------------------------------------------------------------------- +// [fs.class.file_status] class file_status +// [fs.file_status.cons] constructors and destructor +GHC_INLINE file_status::file_status() noexcept + : file_status(file_type::none) +{ +} + +GHC_INLINE file_status::file_status(file_type ft, perms prms) noexcept + : _type(ft) + , _perms(prms) +{ +} + +GHC_INLINE file_status::file_status(const file_status& other) noexcept + : _type(other._type) + , _perms(other._perms) +{ +} + +GHC_INLINE file_status::file_status(file_status&& other) noexcept + : _type(other._type) + , _perms(other._perms) +{ +} + +GHC_INLINE file_status::~file_status() {} + +// assignments: +GHC_INLINE file_status& file_status::operator=(const file_status& rhs) noexcept +{ + _type = rhs._type; + _perms = rhs._perms; + return *this; +} + +GHC_INLINE file_status& file_status::operator=(file_status&& rhs) noexcept +{ + _type = rhs._type; + _perms = rhs._perms; + return *this; +} + +// [fs.file_status.mods] modifiers +GHC_INLINE void file_status::type(file_type ft) noexcept +{ + _type = ft; +} + +GHC_INLINE void file_status::permissions(perms prms) noexcept +{ + _perms = prms; +} + +// [fs.file_status.obs] observers +GHC_INLINE file_type file_status::type() const noexcept +{ + return _type; +} + +GHC_INLINE perms file_status::permissions() const noexcept +{ + return _perms; +} + +//----------------------------------------------------------------------------- +// [fs.class.directory_entry] class directory_entry +// [fs.dir.entry.cons] constructors and destructor +// directory_entry::directory_entry() noexcept = default; +// directory_entry::directory_entry(const directory_entry&) = default; +// directory_entry::directory_entry(directory_entry&&) noexcept = default; +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_entry::directory_entry(const filesystem::path& p) + : _path(p) + , _file_size(static_cast(-1)) +#ifndef GHC_OS_WINDOWS + , _hard_link_count(static_cast(-1)) +#endif + , _last_write_time(0) +{ + refresh(); +} +#endif + +GHC_INLINE directory_entry::directory_entry(const filesystem::path& p, std::error_code& ec) + : _path(p) + , _file_size(static_cast(-1)) +#ifndef GHC_OS_WINDOWS + , _hard_link_count(static_cast(-1)) +#endif + , _last_write_time(0) +{ + refresh(ec); +} + +GHC_INLINE directory_entry::~directory_entry() {} + +// assignments: +// directory_entry& directory_entry::operator=(const directory_entry&) = default; +// directory_entry& directory_entry::operator=(directory_entry&&) noexcept = default; + +// [fs.dir.entry.mods] directory_entry modifiers +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::assign(const filesystem::path& p) +{ + _path = p; + refresh(); +} +#endif + +GHC_INLINE void directory_entry::assign(const filesystem::path& p, std::error_code& ec) +{ + _path = p; + refresh(ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p) +{ + _path.replace_filename(p); + refresh(); +} +#endif + +GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p, std::error_code& ec) +{ + _path.replace_filename(p); + refresh(ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::refresh() +{ + std::error_code ec; + refresh(ec); + if (ec && (_status.type() == file_type::none || _symlink_status.type() != file_type::symlink)) { + throw filesystem_error(detail::systemErrorText(ec.value()), _path, ec); + } +} +#endif + +GHC_INLINE void directory_entry::refresh(std::error_code& ec) noexcept +{ +#ifdef GHC_OS_WINDOWS + _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, nullptr, &_last_write_time); +#else + _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, &_hard_link_count, &_last_write_time); +#endif +} + +// [fs.dir.entry.obs] directory_entry observers +GHC_INLINE const filesystem::path& directory_entry::path() const noexcept +{ + return _path; +} + +GHC_INLINE directory_entry::operator const filesystem::path&() const noexcept +{ + return _path; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_type directory_entry::status_file_type() const +{ + return _status.type() != file_type::none ? _status.type() : filesystem::status(path()).type(); +} +#endif + +GHC_INLINE file_type directory_entry::status_file_type(std::error_code& ec) const noexcept +{ + if (_status.type() != file_type::none) { + ec.clear(); + return _status.type(); + } + return filesystem::status(path(), ec).type(); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::exists() const +{ + return status_file_type() != file_type::not_found; +} +#endif + +GHC_INLINE bool directory_entry::exists(std::error_code& ec) const noexcept +{ + return status_file_type(ec) != file_type::not_found; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_block_file() const +{ + return status_file_type() == file_type::block; +} +#endif +GHC_INLINE bool directory_entry::is_block_file(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::block; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_character_file() const +{ + return status_file_type() == file_type::character; +} +#endif + +GHC_INLINE bool directory_entry::is_character_file(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::character; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_directory() const +{ + return status_file_type() == file_type::directory; +} +#endif + +GHC_INLINE bool directory_entry::is_directory(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::directory; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_fifo() const +{ + return status_file_type() == file_type::fifo; +} +#endif + +GHC_INLINE bool directory_entry::is_fifo(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::fifo; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_other() const +{ + auto ft = status_file_type(); + return ft != file_type::none && ft != file_type::not_found && ft != file_type::regular && ft != file_type::directory && !is_symlink(); +} +#endif + +GHC_INLINE bool directory_entry::is_other(std::error_code& ec) const noexcept +{ + auto ft = status_file_type(ec); + bool other = ft != file_type::none && ft != file_type::not_found && ft != file_type::regular && ft != file_type::directory && !is_symlink(ec); + return !ec && other; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_regular_file() const +{ + return status_file_type() == file_type::regular; +} +#endif + +GHC_INLINE bool directory_entry::is_regular_file(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::regular; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_socket() const +{ + return status_file_type() == file_type::socket; +} +#endif + +GHC_INLINE bool directory_entry::is_socket(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::socket; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_symlink() const +{ + return _symlink_status.type() != file_type::none ? _symlink_status.type() == file_type::symlink : filesystem::is_symlink(symlink_status()); +} +#endif + +GHC_INLINE bool directory_entry::is_symlink(std::error_code& ec) const noexcept +{ + if (_symlink_status.type() != file_type::none) { + ec.clear(); + return _symlink_status.type() == file_type::symlink; + } + return filesystem::is_symlink(symlink_status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t directory_entry::file_size() const +{ + if (_file_size != static_cast(-1)) { + return _file_size; + } + return filesystem::file_size(path()); +} +#endif + +GHC_INLINE uintmax_t directory_entry::file_size(std::error_code& ec) const noexcept +{ + if (_file_size != static_cast(-1)) { + ec.clear(); + return _file_size; + } + return filesystem::file_size(path(), ec); +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t directory_entry::hard_link_count() const +{ +#ifndef GHC_OS_WINDOWS + if (_hard_link_count != static_cast(-1)) { + return _hard_link_count; + } +#endif + return filesystem::hard_link_count(path()); +} +#endif + +GHC_INLINE uintmax_t directory_entry::hard_link_count(std::error_code& ec) const noexcept +{ +#ifndef GHC_OS_WINDOWS + if (_hard_link_count != static_cast(-1)) { + ec.clear(); + return _hard_link_count; + } +#endif + return filesystem::hard_link_count(path(), ec); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_time_type directory_entry::last_write_time() const +{ + if (_last_write_time != 0) { + return std::chrono::system_clock::from_time_t(_last_write_time); + } + return filesystem::last_write_time(path()); +} +#endif + +GHC_INLINE file_time_type directory_entry::last_write_time(std::error_code& ec) const noexcept +{ + if (_last_write_time != 0) { + ec.clear(); + return std::chrono::system_clock::from_time_t(_last_write_time); + } + return filesystem::last_write_time(path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status directory_entry::status() const +{ + if (_status.type() != file_type::none && _status.permissions() != perms::unknown) { + return _status; + } + return filesystem::status(path()); +} +#endif + +GHC_INLINE file_status directory_entry::status(std::error_code& ec) const noexcept +{ + if (_status.type() != file_type::none && _status.permissions() != perms::unknown) { + ec.clear(); + return _status; + } + return filesystem::status(path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status directory_entry::symlink_status() const +{ + if (_symlink_status.type() != file_type::none && _symlink_status.permissions() != perms::unknown) { + return _symlink_status; + } + return filesystem::symlink_status(path()); +} +#endif + +GHC_INLINE file_status directory_entry::symlink_status(std::error_code& ec) const noexcept +{ + if (_symlink_status.type() != file_type::none && _symlink_status.permissions() != perms::unknown) { + ec.clear(); + return _symlink_status; + } + return filesystem::symlink_status(path(), ec); +} + +#ifdef GHC_HAS_THREEWAY_COMP +GHC_INLINE std::strong_ordering directory_entry::operator<=>(const directory_entry& rhs) const noexcept +{ + return _path <=> rhs._path; +} +#endif + +GHC_INLINE bool directory_entry::operator<(const directory_entry& rhs) const noexcept +{ + return _path < rhs._path; +} + +GHC_INLINE bool directory_entry::operator==(const directory_entry& rhs) const noexcept +{ + return _path == rhs._path; +} + +GHC_INLINE bool directory_entry::operator!=(const directory_entry& rhs) const noexcept +{ + return _path != rhs._path; +} + +GHC_INLINE bool directory_entry::operator<=(const directory_entry& rhs) const noexcept +{ + return _path <= rhs._path; +} + +GHC_INLINE bool directory_entry::operator>(const directory_entry& rhs) const noexcept +{ + return _path > rhs._path; +} + +GHC_INLINE bool directory_entry::operator>=(const directory_entry& rhs) const noexcept +{ + return _path >= rhs._path; +} + +//----------------------------------------------------------------------------- +// [fs.class.directory_iterator] class directory_iterator + +#ifdef GHC_OS_WINDOWS +class directory_iterator::impl +{ +public: + impl(const path& p, directory_options options) + : _base(p) + , _options(options) + , _dirHandle(INVALID_HANDLE_VALUE) + { + if (!_base.empty()) { + ZeroMemory(&_findData, sizeof(WIN32_FIND_DATAW)); + if ((_dirHandle = FindFirstFileW(GHC_NATIVEWP((_base / "*")), &_findData)) != INVALID_HANDLE_VALUE) { + if (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L"..") { + increment(_ec); + } + else { + _dir_entry._path = _base / std::wstring(_findData.cFileName); + copyToDirEntry(_ec); + } + } + else { + auto error = ::GetLastError(); + _base = filesystem::path(); + if (error != ERROR_ACCESS_DENIED || (options & directory_options::skip_permission_denied) == directory_options::none) { + _ec = detail::make_system_error(); + } + } + } + } + impl(const impl& other) = delete; + ~impl() + { + if (_dirHandle != INVALID_HANDLE_VALUE) { + FindClose(_dirHandle); + _dirHandle = INVALID_HANDLE_VALUE; + } + } + void increment(std::error_code& ec) + { + if (_dirHandle != INVALID_HANDLE_VALUE) { + do { + if (FindNextFileW(_dirHandle, &_findData)) { + _dir_entry._path = _base; +#ifdef GHC_USE_WCHAR_T + _dir_entry._path.append_name(_findData.cFileName); +#else +#ifdef GHC_RAISE_UNICODE_ERRORS + try { + _dir_entry._path.append_name(detail::toUtf8(_findData.cFileName).c_str()); + } + catch (filesystem_error& fe) { + ec = fe.code(); + return; + } +#else + _dir_entry._path.append_name(detail::toUtf8(_findData.cFileName).c_str()); +#endif +#endif + copyToDirEntry(ec); + } + else { + auto err = ::GetLastError(); + if (err != ERROR_NO_MORE_FILES) { + _ec = ec = detail::make_system_error(err); + } + FindClose(_dirHandle); + _dirHandle = INVALID_HANDLE_VALUE; + _dir_entry._path.clear(); + break; + } + } while (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L".."); + } + else { + ec = _ec; + } + } + void copyToDirEntry(std::error_code& ec) + { + if (_findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + _dir_entry._status = detail::status_ex(_dir_entry._path, ec, &_dir_entry._symlink_status, &_dir_entry._file_size, nullptr, &_dir_entry._last_write_time); + } + else { + _dir_entry._status = detail::status_from_INFO(_dir_entry._path, &_findData, ec, &_dir_entry._file_size, &_dir_entry._last_write_time); + _dir_entry._symlink_status = _dir_entry._status; + } + if (ec) { + if (_dir_entry._status.type() != file_type::none && _dir_entry._symlink_status.type() != file_type::none) { + ec.clear(); + } + else { + _dir_entry._file_size = static_cast(-1); + _dir_entry._last_write_time = 0; + } + } + } + path _base; + directory_options _options; + WIN32_FIND_DATAW _findData; + HANDLE _dirHandle; + directory_entry _dir_entry; + std::error_code _ec; +}; +#else +// POSIX implementation +class directory_iterator::impl +{ +public: + impl(const path& path, directory_options options) + : _base(path) + , _options(options) + , _dir(nullptr) + , _entry(nullptr) + { + if (!path.empty()) { + do { _dir = ::opendir(path.native().c_str()); } while(errno == EINTR && !_dir); + if (!_dir) { + auto error = errno; + _base = filesystem::path(); + if ((error != EACCES && error != EPERM) || (options & directory_options::skip_permission_denied) == directory_options::none) { + _ec = detail::make_system_error(); + } + } + else { + increment(_ec); + } + } + } + impl(const impl& other) = delete; + ~impl() + { + if (_dir) { + ::closedir(_dir); + } + } + void increment(std::error_code& ec) + { + if (_dir) { + bool skip; + do { + skip = false; + errno = 0; + do { _entry = ::readdir(_dir); } while(errno == EINTR && !_entry); + if (_entry) { + _dir_entry._path = _base; + _dir_entry._path.append_name(_entry->d_name); + copyToDirEntry(); + if (ec && (ec.value() == EACCES || ec.value() == EPERM) && (_options & directory_options::skip_permission_denied) == directory_options::skip_permission_denied) { + ec.clear(); + skip = true; + } + } + else { + ::closedir(_dir); + _dir = nullptr; + _dir_entry._path.clear(); + if (errno && errno != EINTR) { + ec = detail::make_system_error(); + } + break; + } + } while (skip || std::strcmp(_entry->d_name, ".") == 0 || std::strcmp(_entry->d_name, "..") == 0); + } + } + + void copyToDirEntry() + { + _dir_entry._symlink_status.permissions(perms::unknown); + auto ft = detail::file_type_from_dirent(*_entry); + _dir_entry._symlink_status.type(ft); + if (ft != file_type::symlink) { + _dir_entry._status = _dir_entry._symlink_status; + } + else { + _dir_entry._status.type(file_type::none); + _dir_entry._status.permissions(perms::unknown); + } + _dir_entry._file_size = static_cast(-1); + _dir_entry._hard_link_count = static_cast(-1); + _dir_entry._last_write_time = 0; + } + path _base; + directory_options _options; + DIR* _dir; + struct ::dirent* _entry; + directory_entry _dir_entry; + std::error_code _ec; +}; +#endif + +// [fs.dir.itr.members] member functions +GHC_INLINE directory_iterator::directory_iterator() noexcept + : _impl(new impl(path(), directory_options::none)) +{ +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_iterator::directory_iterator(const path& p) + : _impl(new impl(p, directory_options::none)) +{ + if (_impl->_ec) { + throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec); + } + _impl->_ec.clear(); +} + +GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options) + : _impl(new impl(p, options)) +{ + if (_impl->_ec) { + throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec); + } +} +#endif + +GHC_INLINE directory_iterator::directory_iterator(const path& p, std::error_code& ec) noexcept + : _impl(new impl(p, directory_options::none)) +{ + if (_impl->_ec) { + ec = _impl->_ec; + } +} + +GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept + : _impl(new impl(p, options)) +{ + if (_impl->_ec) { + ec = _impl->_ec; + } +} + +GHC_INLINE directory_iterator::directory_iterator(const directory_iterator& rhs) + : _impl(rhs._impl) +{ +} + +GHC_INLINE directory_iterator::directory_iterator(directory_iterator&& rhs) noexcept + : _impl(std::move(rhs._impl)) +{ +} + +GHC_INLINE directory_iterator::~directory_iterator() {} + +GHC_INLINE directory_iterator& directory_iterator::operator=(const directory_iterator& rhs) +{ + _impl = rhs._impl; + return *this; +} + +GHC_INLINE directory_iterator& directory_iterator::operator=(directory_iterator&& rhs) noexcept +{ + _impl = std::move(rhs._impl); + return *this; +} + +GHC_INLINE const directory_entry& directory_iterator::operator*() const +{ + return _impl->_dir_entry; +} + +GHC_INLINE const directory_entry* directory_iterator::operator->() const +{ + return &_impl->_dir_entry; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_iterator& directory_iterator::operator++() +{ + std::error_code ec; + _impl->increment(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_entry._path, ec); + } + return *this; +} +#endif + +GHC_INLINE directory_iterator& directory_iterator::increment(std::error_code& ec) noexcept +{ + _impl->increment(ec); + return *this; +} + +GHC_INLINE bool directory_iterator::operator==(const directory_iterator& rhs) const +{ + return _impl->_dir_entry._path == rhs._impl->_dir_entry._path; +} + +GHC_INLINE bool directory_iterator::operator!=(const directory_iterator& rhs) const +{ + return _impl->_dir_entry._path != rhs._impl->_dir_entry._path; +} + +// [fs.dir.itr.nonmembers] directory_iterator non-member functions + +GHC_INLINE directory_iterator begin(directory_iterator iter) noexcept +{ + return iter; +} + +GHC_INLINE directory_iterator end(const directory_iterator&) noexcept +{ + return directory_iterator(); +} + +//----------------------------------------------------------------------------- +// [fs.class.rec.dir.itr] class recursive_directory_iterator + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator() noexcept + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator()); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p) + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options) + : _impl(new recursive_directory_iterator_impl(options, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, options)); +} +#endif + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept + : _impl(new recursive_directory_iterator_impl(options, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, options, ec)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, std::error_code& ec) noexcept + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, ec)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const recursive_directory_iterator& rhs) + : _impl(rhs._impl) +{ +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept + : _impl(std::move(rhs._impl)) +{ +} + +GHC_INLINE recursive_directory_iterator::~recursive_directory_iterator() {} + +// [fs.rec.dir.itr.members] observers +GHC_INLINE directory_options recursive_directory_iterator::options() const +{ + return _impl->_options; +} + +GHC_INLINE int recursive_directory_iterator::depth() const +{ + return static_cast(_impl->_dir_iter_stack.size() - 1); +} + +GHC_INLINE bool recursive_directory_iterator::recursion_pending() const +{ + return _impl->_recursion_pending; +} + +GHC_INLINE const directory_entry& recursive_directory_iterator::operator*() const +{ + return *(_impl->_dir_iter_stack.top()); +} + +GHC_INLINE const directory_entry* recursive_directory_iterator::operator->() const +{ + return &(*(_impl->_dir_iter_stack.top())); +} + +// [fs.rec.dir.itr.members] modifiers recursive_directory_iterator& +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(const recursive_directory_iterator& rhs) +{ + _impl = rhs._impl; + return *this; +} + +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(recursive_directory_iterator&& rhs) noexcept +{ + _impl = std::move(rhs._impl); + return *this; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator++() +{ + std::error_code ec; + increment(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec); + } + return *this; +} +#endif + +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::increment(std::error_code& ec) noexcept +{ + bool isSymLink = (*this)->is_symlink(ec); + bool isDir = !ec && (*this)->is_directory(ec); + if (isSymLink && detail::is_not_found_error(ec)) { + ec.clear(); + } + if (!ec) { + if (recursion_pending() && isDir && (!isSymLink || (options() & directory_options::follow_directory_symlink) != directory_options::none)) { + _impl->_dir_iter_stack.push(directory_iterator((*this)->path(), _impl->_options, ec)); + } + else { + _impl->_dir_iter_stack.top().increment(ec); + } + if (!ec) { + while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()) { + _impl->_dir_iter_stack.pop(); + _impl->_dir_iter_stack.top().increment(ec); + } + } + else if (!_impl->_dir_iter_stack.empty()) { + _impl->_dir_iter_stack.pop(); + } + _impl->_recursion_pending = true; + } + return *this; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void recursive_directory_iterator::pop() +{ + std::error_code ec; + pop(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec); + } +} +#endif + +GHC_INLINE void recursive_directory_iterator::pop(std::error_code& ec) +{ + if (depth() == 0) { + *this = recursive_directory_iterator(); + } + else { + do { + _impl->_dir_iter_stack.pop(); + _impl->_dir_iter_stack.top().increment(ec); + } while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()); + } +} + +GHC_INLINE void recursive_directory_iterator::disable_recursion_pending() +{ + _impl->_recursion_pending = false; +} + +// other members as required by [input.iterators] +GHC_INLINE bool recursive_directory_iterator::operator==(const recursive_directory_iterator& rhs) const +{ + return _impl->_dir_iter_stack.top() == rhs._impl->_dir_iter_stack.top(); +} + +GHC_INLINE bool recursive_directory_iterator::operator!=(const recursive_directory_iterator& rhs) const +{ + return _impl->_dir_iter_stack.top() != rhs._impl->_dir_iter_stack.top(); +} + +// [fs.rec.dir.itr.nonmembers] directory_iterator non-member functions +GHC_INLINE recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept +{ + return iter; +} + +GHC_INLINE recursive_directory_iterator end(const recursive_directory_iterator&) noexcept +{ + return recursive_directory_iterator(); +} + +#endif // GHC_EXPAND_IMPL + +} // namespace filesystem +} // namespace ghc + +// cleanup some macros +#undef GHC_INLINE +#undef GHC_EXPAND_IMPL + +#endif // GHC_FILESYSTEM_H + diff --git a/Engine/lib/openal-soft/common/intrusive_ptr.h b/Engine/lib/openal-soft/common/intrusive_ptr.h index b1fa742f0..1a446f44b 100644 --- a/Engine/lib/openal-soft/common/intrusive_ptr.h +++ b/Engine/lib/openal-soft/common/intrusive_ptr.h @@ -15,6 +15,9 @@ template class intrusive_ref { std::atomic mRef{1u}; +protected: + ~intrusive_ref() = default; + public: unsigned int add_ref() noexcept { return IncrementRef(mRef); } unsigned int dec_ref() noexcept @@ -48,7 +51,7 @@ public: }; -template +template /* NOLINTNEXTLINE(clazy-rule-of-three) False positive */ class intrusive_ptr { T *mPtr{nullptr}; @@ -58,7 +61,7 @@ public: { if(mPtr) mPtr->add_ref(); } intrusive_ptr(intrusive_ptr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; } - intrusive_ptr(std::nullptr_t) noexcept { } + intrusive_ptr(std::nullptr_t) noexcept { } /* NOLINT(google-explicit-constructor) */ explicit intrusive_ptr(T *ptr) noexcept : mPtr{ptr} { } ~intrusive_ptr() { if(mPtr) mPtr->dec_ref(); } diff --git a/Engine/lib/openal-soft/common/opthelpers.h b/Engine/lib/openal-soft/common/opthelpers.h index ae2611dad..e22f220fe 100644 --- a/Engine/lib/openal-soft/common/opthelpers.h +++ b/Engine/lib/openal-soft/common/opthelpers.h @@ -28,6 +28,24 @@ #define NOINLINE #endif +#if defined(__MINGW32__) && defined(__i386__) +/* 32-bit MinGW targets have a bug where __STDCPP_DEFAULT_NEW_ALIGNMENT__ + * reports 16, despite the default operator new calling standard malloc which + * only guarantees 8-byte alignment. As a result, structs that need and specify + * 16-byte alignment only get 8-byte alignment. Explicitly specifying 32-byte + * alignment forces the over-aligned operator new to be called, giving the + * correct (if larger than necessary) alignment. + * + * Technically this bug affects 32-bit GCC more generally, but typically only + * with fairly old glibc versions as newer versions do guarantee the 16-byte + * alignment as specified. MinGW is reliant on msvcrt.dll's malloc however, + * which can't be updated to give that guarantee. + */ +#define SIMDALIGN alignas(32) +#else +#define SIMDALIGN +#endif + /* 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 @@ -56,6 +74,12 @@ #define UNLIKELY #endif +#if !defined(_WIN32) && HAS_ATTRIBUTE(gnu::visibility) +#define DECL_HIDDEN [[gnu::visibility("hidden")]] +#else +#define DECL_HIDDEN +#endif + namespace al { template diff --git a/Engine/lib/openal-soft/common/pffft.cpp b/Engine/lib/openal-soft/common/pffft.cpp index 6cddcf09a..07b6fbccf 100644 --- a/Engine/lib/openal-soft/common/pffft.cpp +++ b/Engine/lib/openal-soft/common/pffft.cpp @@ -73,6 +73,8 @@ #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" +#include "fmt/core.h" +#include "fmt/ranges.h" #include "opthelpers.h" @@ -98,7 +100,8 @@ namespace { /* * Altivec support macros */ -#if defined(__ppc__) || defined(__ppc64__) || defined(__powerpc__) || defined(__powerpc64__) +#if (defined(__ppc__) || defined(__ppc64__) || defined(__powerpc__) || defined(__powerpc64__)) \ + && (defined(__VEC__) || defined(__ALTIVEC__)) #include using v4sf = vector float; constexpr uint SimdSize{4}; @@ -127,7 +130,6 @@ force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noex { out1 = vec_perm(in1, in2, (vector unsigned char){0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27}); out2 = vec_perm(in1, in2, (vector unsigned char){4,5,6,7,12,13,14,15,20,21,22,23,28,29,30,31}); - out1 = tmp; } force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept @@ -324,7 +326,7 @@ force_inline constexpr v4sf ld_ps1(float a) noexcept { return a; } #else -[[maybe_unused]] inline +[[maybe_unused, nodiscard]] inline auto valigned(const float *ptr) noexcept -> bool { static constexpr uintptr_t alignmask{SimdSize*sizeof(float) - 1}; @@ -358,61 +360,59 @@ constexpr auto make_float_array(std::integer_sequence) { return std::array{static_cast(N)...}; } /* detect bugs with the vector support macros */ -[[maybe_unused]] void validate_pffft_simd() +[[maybe_unused]] auto validate_pffft_simd() -> bool { using float4 = std::array; static constexpr auto f = make_float_array(std::make_index_sequence<16>{}); - v4sf a0_v{vset4(f[ 0], f[ 1], f[ 2], f[ 3])}; - v4sf a1_v{vset4(f[ 4], f[ 5], f[ 6], f[ 7])}; - v4sf a2_v{vset4(f[ 8], f[ 9], f[10], f[11])}; - v4sf a3_v{vset4(f[12], f[13], f[14], f[15])}; - v4sf u_v{}; + auto a0_v = vset4(f[ 0], f[ 1], f[ 2], f[ 3]); + auto a1_v = vset4(f[ 4], f[ 5], f[ 6], f[ 7]); + auto a2_v = vset4(f[ 8], f[ 9], f[10], f[11]); + auto a3_v = vset4(f[12], f[13], f[14], f[15]); auto t_v = vzero(); auto t_f = al::bit_cast(t_v); - printf("VZERO=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + fmt::println("VZERO={}", t_f); assertv4(t_f, 0, 0, 0, 0); t_v = vadd(a1_v, a2_v); t_f = al::bit_cast(t_v); - printf("VADD(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + fmt::println("VADD(4:7,8:11)={}", t_f); assertv4(t_f, 12, 14, 16, 18); t_v = vmul(a1_v, a2_v); t_f = al::bit_cast(t_v); - printf("VMUL(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + fmt::println("VMUL(4:7,8:11)={}", t_f); assertv4(t_f, 32, 45, 60, 77); t_v = vmadd(a1_v, a2_v, a0_v); t_f = al::bit_cast(t_v); - printf("VMADD(4:7,8:11,0:3)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + fmt::println("VMADD(4:7,8:11,0:3)={}", t_f); assertv4(t_f, 32, 46, 62, 80); + auto u_v = v4sf{}; interleave2(a1_v, a2_v, t_v, u_v); t_f = al::bit_cast(t_v); auto u_f = al::bit_cast(u_v); - printf("INTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", - t_f[0], t_f[1], t_f[2], t_f[3], u_f[0], u_f[1], u_f[2], u_f[3]); + fmt::println("INTERLEAVE2(4:7,8:11)={} {}", t_f, u_f); assertv4(t_f, 4, 8, 5, 9); assertv4(u_f, 6, 10, 7, 11); uninterleave2(a1_v, a2_v, t_v, u_v); t_f = al::bit_cast(t_v); u_f = al::bit_cast(u_v); - printf("UNINTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", - t_f[0], t_f[1], t_f[2], t_f[3], u_f[0], u_f[1], u_f[2], u_f[3]); + fmt::println("UNINTERLEAVE2(4:7,8:11)={} {}", t_f, u_f); assertv4(t_f, 4, 6, 8, 10); assertv4(u_f, 5, 7, 9, 11); t_v = ld_ps1(f[15]); t_f = al::bit_cast(t_v); - printf("LD_PS1(15)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + fmt::println("LD_PS1(15)={}", t_f); assertv4(t_f, 15, 15, 15, 15); t_v = vswaphl(a1_v, a2_v); t_f = al::bit_cast(t_v); - printf("VSWAPHL(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + fmt::println("VSWAPHL(4:7,8:11)={}", t_f); assertv4(t_f, 8, 9, 6, 7); vtranspose4(a0_v, a1_v, a2_v, a3_v); @@ -420,13 +420,13 @@ constexpr auto make_float_array(std::integer_sequence) auto a1_f = al::bit_cast(a1_v); auto a2_f = al::bit_cast(a2_v); auto a3_f = al::bit_cast(a3_v); - printf("VTRANSPOSE4(0:3,4:7,8:11,12:15)=[%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", - a0_f[0], a0_f[1], a0_f[2], a0_f[3], a1_f[0], a1_f[1], a1_f[2], a1_f[3], - a2_f[0], a2_f[1], a2_f[2], a2_f[3], a3_f[0], a3_f[1], a3_f[2], a3_f[3]); + fmt::println("VTRANSPOSE4(0:3,4:7,8:11,12:15)={} {} {} {}", a0_f, a1_f, a2_f, a3_f); assertv4(a0_f, 0, 4, 8, 12); assertv4(a1_f, 1, 5, 9, 13); assertv4(a2_f, 2, 6, 10, 14); assertv4(a3_f, 3, 7, 11, 15); + + return true; } #endif //!PFFFT_SIMD_DISABLE @@ -1059,7 +1059,7 @@ void radf5_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf * ch_ref(1, 3, k) = vmadd(ti11, ci5, vmul(ti12, ci4)); ch_ref(ido, 4, k) = vadd(cc_ref(1, k, 1), vmadd(tr12, cr2, vmul(tr11, cr3))); ch_ref(1, 5, k) = vsub(vmul(ti12, ci5), vmul(ti11, ci4)); - //printf("pffft: radf5, k=%d ch_ref=%f, ci4=%f\n", k, ch_ref(1, 5, k), ci4); + //fmt::println("pffft: radf5, k={} ch_ref={:f}, ci4={:f}", k, ch_ref(1, 5, k), ci4); } if(ido == 1) return; @@ -1534,10 +1534,10 @@ PFFFTSetupPtr pffft_new_setup(unsigned int N, pffft_transform_t transform) } -void pffft_destroy_setup(gsl::owner s) noexcept +void PFFFTSetupDeleter::operator()(gsl::owner setup) const noexcept { - std::destroy_at(s); - ::operator delete[](gsl::owner{s}, V4sfAlignVal); + std::destroy_at(setup); + ::operator delete[](gsl::owner{setup}, V4sfAlignVal); } #if !defined(PFFFT_SIMD_DISABLE) diff --git a/Engine/lib/openal-soft/common/pffft.h b/Engine/lib/openal-soft/common/pffft.h index 0eb321285..4c8fa587b 100644 --- a/Engine/lib/openal-soft/common/pffft.h +++ b/Engine/lib/openal-soft/common/pffft.h @@ -96,9 +96,8 @@ enum pffft_direction_t { PFFFT_FORWARD, PFFFT_BACKWARD }; /* type of transform */ enum pffft_transform_t { PFFFT_REAL, PFFFT_COMPLEX }; -void pffft_destroy_setup(gsl::owner setup) noexcept; struct PFFFTSetupDeleter { - void operator()(gsl::owner setup) const noexcept { pffft_destroy_setup(setup); } + void operator()(gsl::owner setup) const noexcept; }; using PFFFTSetupPtr = std::unique_ptr; @@ -175,7 +174,7 @@ void pffft_zconvolve_accumulate(const PFFFT_Setup *setup, const float *dft_a, co struct PFFFTSetup { - PFFFTSetupPtr mSetup{}; + PFFFTSetupPtr mSetup; PFFFTSetup() = default; PFFFTSetup(const PFFFTSetup&) = delete; diff --git a/Engine/lib/openal-soft/common/phase_shifter.h b/Engine/lib/openal-soft/common/phase_shifter.h index 7febf3447..b06e92757 100644 --- a/Engine/lib/openal-soft/common/phase_shifter.h +++ b/Engine/lib/openal-soft/common/phase_shifter.h @@ -1,9 +1,11 @@ #ifndef PHASE_SHIFTER_H #define PHASE_SHIFTER_H -#ifdef HAVE_SSE_INTRINSICS +#include "config_simd.h" + +#if HAVE_SSE_INTRINSICS #include -#elif defined(HAVE_NEON) +#elif HAVE_NEON #include #endif @@ -21,7 +23,7 @@ * signal delay (FilterSize/2) to properly align. */ template -struct PhaseShifterT { +struct SIMDALIGN PhaseShifterT { static_assert(FilterSize >= 16, "FilterSize needs to be at least 16"); static_assert((FilterSize&(FilterSize-1)) == 0, "FilterSize needs to be power-of-two"); @@ -36,15 +38,14 @@ struct PhaseShifterT { */ for(std::size_t i{0};i < FilterSize/2;++i) { - const int k{static_cast(i*2 + 1) - int{FilterSize/2}}; + const auto k = static_cast(i*2 + 1) - int{FilterSize/2}; /* Calculate the Blackman window value for this coefficient. */ - const double w{2.0*al::numbers::pi * static_cast(i*2 + 1) - / double{FilterSize}}; - const double window{0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w) - - 0.0106411*std::cos(3.0*w)}; + const auto w = 2.0*al::numbers::pi/double{FilterSize} * static_cast(i*2 + 1); + const auto window = 0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w) + - 0.0106411*std::cos(3.0*w); - const double pk{al::numbers::pi * static_cast(k)}; + const auto pk = al::numbers::pi * static_cast(k); mCoeffs[i] = static_cast(window * (1.0-std::cos(pk)) / pk); } } @@ -52,17 +53,7 @@ struct PhaseShifterT { void process(const al::span dst, const al::span src) const; private: -#if defined(HAVE_NEON) - static auto unpacklo(float32x4_t a, float32x4_t b) - { - float32x2x2_t result{vzip_f32(vget_low_f32(a), vget_low_f32(b))}; - return vcombine_f32(result.val[0], result.val[1]); - } - static auto unpackhi(float32x4_t a, float32x4_t b) - { - float32x2x2_t result{vzip_f32(vget_high_f32(a), vget_high_f32(b))}; - return vcombine_f32(result.val[0], result.val[1]); - } +#if HAVE_NEON static auto load4(float32_t a, float32_t b, float32_t c, float32_t d) { float32x4_t ret{vmovq_n_f32(a)}; @@ -90,7 +81,7 @@ NOINLINE inline void PhaseShifterT::process(const al::span dst, const al::span src) const { auto in = src.begin(); -#ifdef HAVE_SSE_INTRINSICS +#if HAVE_SSE_INTRINSICS if(const std::size_t todo{dst.size()>>2}) { auto out = al::span{reinterpret_cast<__m128*>(dst.data()), todo}; @@ -146,7 +137,7 @@ void PhaseShifterT::process(const al::span dst, const al::span>2}) { diff --git a/Engine/lib/openal-soft/common/polyphase_resampler.cpp b/Engine/lib/openal-soft/common/polyphase_resampler.cpp index 6aed84edb..d9f727ef9 100644 --- a/Engine/lib/openal-soft/common/polyphase_resampler.cpp +++ b/Engine/lib/openal-soft/common/polyphase_resampler.cpp @@ -17,10 +17,6 @@ namespace { constexpr double Epsilon{1e-9}; -#if __cpp_lib_math_special_functions >= 201603L -using std::cyl_bessel_i; - -#else /* The zero-order modified Bessel function of the first kind, used for the * Kaiser window. @@ -33,7 +29,7 @@ using std::cyl_bessel_i; * compounding the rounding and precision error), but it's good enough. */ template -U cyl_bessel_i(T nu, U x) +constexpr auto cyl_bessel_i(T nu, U x) -> U { if(nu != T{0}) throw std::runtime_error{"cyl_bessel_i: nu != 0"}; @@ -57,7 +53,6 @@ U cyl_bessel_i(T nu, U x) } while(sum != last_sum); return static_cast(sum); } -#endif /* This is the normalized cardinal sine (sinc) function. * @@ -89,7 +84,7 @@ double Kaiser(const double beta, const double k, const double besseli_0_beta) { if(!(k >= -1.0 && k <= 1.0)) return 0.0; - return cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; + return ::cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; } /* Calculates the size (order) of the Kaiser window. Rejection is in dB and @@ -130,10 +125,10 @@ constexpr double CalcKaiserBeta(const double rejection) * p -- gain compensation factor when sampling * f_t -- normalized center frequency (or cutoff; 0.5 is nyquist) */ -double SincFilter(const uint l, const double beta, const double besseli_0_beta, const double gain, - const double cutoff, const uint i) +auto SincFilter(const uint l, const double beta, const double besseli_0_beta, const double gain, + const double cutoff, const uint i) -> double { - const double x{static_cast(i) - l}; + const auto x = static_cast(i) - l; return Kaiser(beta, x/l, besseli_0_beta) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x); } @@ -143,77 +138,83 @@ double SincFilter(const uint l, const double beta, const double besseli_0_beta, // that's used to cut frequencies above the destination nyquist. void PPhaseResampler::init(const uint srcRate, const uint dstRate) { - const uint gcd{std::gcd(srcRate, dstRate)}; + const auto gcd = std::gcd(srcRate, dstRate); mP = dstRate / gcd; mQ = srcRate / gcd; - /* The cutoff is adjusted by half the transition width, so the transition - * ends before the nyquist (0.5). Both are scaled by the downsampling - * factor. + /* The cutoff is adjusted by the transition width, so the transition ends + * at nyquist (0.5). Both are scaled by the downsampling factor. */ - const auto [cutoff, width] = (mP > mQ) ? std::make_tuple(0.475 / mP, 0.05 / mP) - : std::make_tuple(0.475 / mQ, 0.05 / mQ); + const auto [cutoff, width] = (mP > mQ) ? std::make_tuple(0.47 / mP, 0.03 / mP) + : std::make_tuple(0.47 / mQ, 0.03 / mQ); // A rejection of -180 dB is used for the stop band. Round up when // calculating the left offset to avoid increasing the transition width. - const uint l{(CalcKaiserOrder(180.0, width)+1) / 2}; - const double beta{CalcKaiserBeta(180.0)}; - const double besseli_0_beta{cyl_bessel_i(0, beta)}; - mM = l*2 + 1; + static constexpr auto rejection = 180.0; + const auto l = (CalcKaiserOrder(rejection, width)+1u) / 2u; + const auto beta = CalcKaiserBeta(rejection); + const auto besseli_0_beta = ::cyl_bessel_i(0, beta); + mM = l*2u + 1u; mL = l; mF.resize(mM); for(uint i{0};i < mM;i++) - mF[i] = SincFilter(l, beta, besseli_0_beta, mP, cutoff, i); + mF[i] = SincFilter(mL, beta, besseli_0_beta, mP, cutoff, i); } // Perform the upsample-filter-downsample resampling operation using a // polyphase filter implementation. -void PPhaseResampler::process(const al::span in, const al::span out) +void PPhaseResampler::process(const al::span in, const al::span out) const { if(out.empty()) UNLIKELY return; // Handle in-place operation. - std::vector workspace; - al::span work{out}; + auto workspace = std::vector{}; + auto work = al::span{out}; if(work.data() == in.data()) UNLIKELY { workspace.resize(out.size()); work = workspace; } - // Resample the input. - const uint p{mP}, q{mQ}, m{mM}, l{mL}; - const al::span f{mF}; - for(uint i{0};i < out.size();i++) + const auto f = al::span{mF}; + const auto p = size_t{mP}; + const auto q = size_t{mQ}; + const auto m = size_t{mM}; + /* Input starts at l to compensate for the filter delay. This will drop any + * build-up from the first half of the filter. + */ + auto l = size_t{mL}; + std::generate(work.begin(), work.end(), [in,f,p,q,m,&l] { - // Input starts at l to compensate for the filter delay. This will - // drop any build-up from the first half of the filter. - std::size_t j_f{(l + q*i) % p}; - std::size_t j_s{(l + q*i) / p}; + auto j_s = l / p; + auto j_f = l % p; + l += q; // Only take input when 0 <= j_s < in.size(). - double r{0.0}; - if(j_f < m) LIKELY + if(j_f >= m) UNLIKELY + return 0.0; + + auto filt_len = (m - j_f - 1)/p + 1; + if(j_s+1 > in.size()) LIKELY { - std::size_t filt_len{(m-j_f+p-1) / p}; - if(j_s+1 > in.size()) LIKELY - { - std::size_t skip{std::min(j_s+1 - in.size(), filt_len)}; - j_f += p*skip; - j_s -= skip; - filt_len -= skip; - } - std::size_t todo{std::min(j_s+1, filt_len)}; - while(todo) - { - r += f[j_f] * in[j_s]; - j_f += p; --j_s; - --todo; - } + const auto skip = std::min(j_s+1-in.size(), filt_len); + j_f += p*skip; + j_s -= skip; + filt_len -= skip; } - work[i] = r; - } + /* Get the range of input samples being used for this output sample. + * j_s is the first sample and iterates backwards toward 0. + */ + const auto src = in.first(j_s+1).last(std::min(j_s+1, filt_len)); + return std::accumulate(src.rbegin(), src.rend(), 0.0, [p,f,&j_f](const double cur, + const double smp) -> double + { + const auto ret = cur + f[j_f]*smp; + j_f += p; + return ret; + }); + }); // Clean up after in-place operation. if(work.data() != out.data()) std::copy(work.cbegin(), work.cend(), out.begin()); diff --git a/Engine/lib/openal-soft/common/polyphase_resampler.h b/Engine/lib/openal-soft/common/polyphase_resampler.h index 0795c80d8..53fb6f81c 100644 --- a/Engine/lib/openal-soft/common/polyphase_resampler.h +++ b/Engine/lib/openal-soft/common/polyphase_resampler.h @@ -37,7 +37,7 @@ using uint = unsigned int; struct PPhaseResampler { void init(const uint srcRate, const uint dstRate); - void process(const al::span in, const al::span out); + void process(const al::span in, const al::span out) const; explicit operator bool() const noexcept { return !mF.empty(); } diff --git a/Engine/lib/openal-soft/common/ringbuffer.cpp b/Engine/lib/openal-soft/common/ringbuffer.cpp index f4fa49bf9..23c48806a 100644 --- a/Engine/lib/openal-soft/common/ringbuffer.cpp +++ b/Engine/lib/openal-soft/common/ringbuffer.cpp @@ -23,12 +23,13 @@ #include "ringbuffer.h" #include +#include #include #include #include -#include #include "alnumeric.h" +#include "alspan.h" auto RingBuffer::Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> RingBufferPtr @@ -76,8 +77,8 @@ auto RingBuffer::read(void *dest, std::size_t count) noexcept -> std::size_t const std::size_t read_idx{r & mSizeMask}; const std::size_t rdend{read_idx + to_read}; - const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz) - : std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask); + const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::array{to_read, 0_uz} + : std::array{mSizeMask+1 - read_idx, rdend&mSizeMask}; auto dstbytes = al::span{static_cast(dest), count*mElemSize}; auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize, @@ -99,8 +100,8 @@ auto RingBuffer::peek(void *dest, std::size_t count) const noexcept -> std::size const std::size_t read_idx{r & mSizeMask}; const std::size_t rdend{read_idx + to_read}; - const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz) - : std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask); + const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::array{to_read, 0_uz} + : std::array{mSizeMask+1 - read_idx, rdend&mSizeMask}; auto dstbytes = al::span{static_cast(dest), count*mElemSize}; auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize, @@ -121,8 +122,8 @@ auto RingBuffer::write(const void *src, std::size_t count) noexcept -> std::size const std::size_t write_idx{w & mSizeMask}; const std::size_t wrend{write_idx + to_write}; - const auto [n1, n2] = (wrend <= mSizeMask+1) ? std::make_tuple(to_write, 0_uz) - : std::make_tuple(mSizeMask+1 - write_idx, wrend&mSizeMask); + const auto [n1, n2] = (wrend <= mSizeMask+1) ? std::array{to_write, 0_uz} + : std::array{mSizeMask+1 - write_idx, wrend&mSizeMask}; auto srcbytes = al::span{static_cast(src), count*mElemSize}; std::copy_n(srcbytes.cbegin(), n1*mElemSize, mBuffer.begin() + ptrdiff_t(write_idx*mElemSize)); @@ -146,10 +147,10 @@ auto RingBuffer::getReadVector() noexcept -> DataPair /* Two part vector: the rest of the buffer after the current read ptr, * plus some from the start of the buffer. */ - return DataPair{{mBuffer.data() + read_idx*mElemSize, mSizeMask+1 - read_idx}, - {mBuffer.data(), rdend&mSizeMask}}; + return DataPair{{{mBuffer.data() + read_idx*mElemSize, mSizeMask+1 - read_idx}, + {mBuffer.data(), rdend&mSizeMask}}}; } - return DataPair{{mBuffer.data() + read_idx*mElemSize, readable}, {}}; + return DataPair{{{mBuffer.data() + read_idx*mElemSize, readable}, {}}}; } auto RingBuffer::getWriteVector() noexcept -> DataPair @@ -165,8 +166,8 @@ auto RingBuffer::getWriteVector() noexcept -> DataPair /* Two part vector: the rest of the buffer after the current write ptr, * plus some from the start of the buffer. */ - return DataPair{{mBuffer.data() + write_idx*mElemSize, mSizeMask+1 - write_idx}, - {mBuffer.data(), wrend&mSizeMask}}; + return DataPair{{{mBuffer.data() + write_idx*mElemSize, mSizeMask+1 - write_idx}, + {mBuffer.data(), wrend&mSizeMask}}}; } - return DataPair{{mBuffer.data() + write_idx*mElemSize, writable}, {}}; + return DataPair{{{mBuffer.data() + write_idx*mElemSize, writable}, {}}}; } diff --git a/Engine/lib/openal-soft/common/ringbuffer.h b/Engine/lib/openal-soft/common/ringbuffer.h index 4493474cd..59b0a0ac7 100644 --- a/Engine/lib/openal-soft/common/ringbuffer.h +++ b/Engine/lib/openal-soft/common/ringbuffer.h @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "almalloc.h" @@ -40,7 +39,7 @@ public: std::byte *buf; std::size_t len; }; - using DataPair = std::pair; + using DataPair = std::array; RingBuffer(const std::size_t writesize, const std::size_t mask, const std::size_t elemsize, const std::size_t numbytes) diff --git a/Engine/lib/openal-soft/common/strutils.cpp b/Engine/lib/openal-soft/common/strutils.cpp index 4bcd41194..5b1b6af19 100644 --- a/Engine/lib/openal-soft/common/strutils.cpp +++ b/Engine/lib/openal-soft/common/strutils.cpp @@ -11,6 +11,7 @@ #include "alstring.h" +/* NOLINTBEGIN(bugprone-suspicious-stringview-data-usage) */ std::string wstr_to_utf8(std::wstring_view wstr) { std::string ret; @@ -40,6 +41,7 @@ std::wstring utf8_to_wstr(std::string_view str) return ret; } +/* NOLINTEND(bugprone-suspicious-stringview-data-usage) */ #endif namespace al { diff --git a/Engine/lib/openal-soft/common/vecmat.h b/Engine/lib/openal-soft/common/vecmat.h index 90b5d1469..7ac6afe32 100644 --- a/Engine/lib/openal-soft/common/vecmat.h +++ b/Engine/lib/openal-soft/common/vecmat.h @@ -45,13 +45,12 @@ public: mVals[2] - rhs.mVals[2], mVals[3] - rhs.mVals[3]}; } - constexpr auto normalize(float limit = std::numeric_limits::epsilon()) -> float + constexpr auto normalize() -> float { - limit = std::max(limit, std::numeric_limits::epsilon()); const auto length_sqr = float{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]}; - if(length_sqr > limit*limit) + if(length_sqr > std::numeric_limits::epsilon()) { - const auto length = float{std::sqrt(length_sqr)}; + const auto length = std::sqrt(length_sqr); auto inv_length = float{1.0f / length}; mVals[0] *= inv_length; mVals[1] *= inv_length; diff --git a/Engine/lib/openal-soft/config.h.in b/Engine/lib/openal-soft/config.h.in index 9013c4731..fb61fcf28 100644 --- a/Engine/lib/openal-soft/config.h.in +++ b/Engine/lib/openal-soft/config.h.in @@ -7,66 +7,6 @@ /* Define if we have the proc_pidpath function */ #cmakedefine HAVE_PROC_PIDPATH -/* Define if we have DBus/RTKit */ -#cmakedefine HAVE_RTKIT - -/* Define if we have SSE CPU extensions */ -#cmakedefine HAVE_SSE -#cmakedefine HAVE_SSE2 -#cmakedefine HAVE_SSE3 -#cmakedefine HAVE_SSE4_1 - -/* Define if we have ARM Neon CPU extensions */ -#cmakedefine HAVE_NEON - -/* Define if we have the ALSA backend */ -#cmakedefine HAVE_ALSA - -/* Define if we have the OSS backend */ -#cmakedefine HAVE_OSS - -/* Define if we have the PipeWire backend */ -#cmakedefine HAVE_PIPEWIRE - -/* Define if we have the Solaris backend */ -#cmakedefine HAVE_SOLARIS - -/* Define if we have the SndIO backend */ -#cmakedefine HAVE_SNDIO - -/* Define if we have the WASAPI backend */ -#cmakedefine HAVE_WASAPI - -/* Define if we have the DSound backend */ -#cmakedefine HAVE_DSOUND - -/* Define if we have the Windows Multimedia backend */ -#cmakedefine HAVE_WINMM - -/* Define if we have the PortAudio backend */ -#cmakedefine HAVE_PORTAUDIO - -/* Define if we have the PulseAudio backend */ -#cmakedefine HAVE_PULSEAUDIO - -/* Define if we have the JACK backend */ -#cmakedefine HAVE_JACK - -/* Define if we have the CoreAudio backend */ -#cmakedefine HAVE_COREAUDIO - -/* Define if we have the OpenSL backend */ -#cmakedefine HAVE_OPENSL - -/* Define if we have the Oboe backend */ -#cmakedefine HAVE_OBOE - -/* Define if we have the Wave Writer backend */ -#cmakedefine HAVE_WAVE - -/* Define if we have the SDL2 backend */ -#cmakedefine HAVE_SDL2 - /* Define if we have dlfcn.h */ #cmakedefine HAVE_DLFCN_H @@ -88,9 +28,6 @@ /* Define if we have the __cpuid() intrinsic */ #cmakedefine HAVE_CPUID_INTRINSIC -/* Define if we have SSE intrinsics */ -#cmakedefine HAVE_SSE_INTRINSICS - /* Define if we have pthread_setschedparam() */ #cmakedefine HAVE_PTHREAD_SETSCHEDPARAM @@ -103,5 +40,11 @@ /* Define the installation data directory */ #cmakedefine ALSOFT_INSTALL_DATADIR "@ALSOFT_INSTALL_DATADIR@" -/* Define whether build alsoft for winuwp */ -#cmakedefine ALSOFT_UWP +/* Define to 1 if we have DBus/RTKit, else 0 */ +#cmakedefine01 HAVE_RTKIT + +/* Define to 1 if building for winuwp, else 0 */ +#cmakedefine01 ALSOFT_UWP + +/* Define to 1 if building with legacy EAX API support, else 0 */ +#cmakedefine01 ALSOFT_EAX diff --git a/Engine/lib/openal-soft/config_backends.h.in b/Engine/lib/openal-soft/config_backends.h.in new file mode 100644 index 000000000..96eb79ca7 --- /dev/null +++ b/Engine/lib/openal-soft/config_backends.h.in @@ -0,0 +1,37 @@ +/* Define to 1 if the given backend is enabled, else 0 */ + +#cmakedefine01 HAVE_ALSA + +#cmakedefine01 HAVE_OSS + +#cmakedefine01 HAVE_PIPEWIRE + +#cmakedefine01 HAVE_SOLARIS + +#cmakedefine01 HAVE_SNDIO + +#cmakedefine01 HAVE_WASAPI + +#cmakedefine01 HAVE_DSOUND + +#cmakedefine01 HAVE_WINMM + +#cmakedefine01 HAVE_PORTAUDIO + +#cmakedefine01 HAVE_PULSEAUDIO + +#cmakedefine01 HAVE_JACK + +#cmakedefine01 HAVE_COREAUDIO + +#cmakedefine01 HAVE_OPENSL + +#cmakedefine01 HAVE_OBOE + +#cmakedefine01 HAVE_OTHERIO + +#cmakedefine01 HAVE_WAVE + +#cmakedefine01 HAVE_SDL3 + +#cmakedefine01 HAVE_SDL2 diff --git a/Engine/lib/openal-soft/config_simd.h.in b/Engine/lib/openal-soft/config_simd.h.in new file mode 100644 index 000000000..3d284cc7b --- /dev/null +++ b/Engine/lib/openal-soft/config_simd.h.in @@ -0,0 +1,10 @@ +/* Define to 1 if we have SSE CPU extensions, else 0 */ +#cmakedefine01 HAVE_SSE +#cmakedefine01 HAVE_SSE2 +#cmakedefine01 HAVE_SSE3 +#cmakedefine01 HAVE_SSE4_1 + +#cmakedefine01 HAVE_SSE_INTRINSICS + +/* Define to 1 if we have ARM Neon CPU extensions, else 0 */ +#cmakedefine01 HAVE_NEON diff --git a/Engine/lib/openal-soft/configs/HRTF+WASAPI Exclusive/alsoft.ini b/Engine/lib/openal-soft/configs/HRTF+WASAPI Exclusive/alsoft.ini new file mode 100644 index 000000000..ab44bab5b --- /dev/null +++ b/Engine/lib/openal-soft/configs/HRTF+WASAPI Exclusive/alsoft.ini @@ -0,0 +1,8 @@ +[General] +channels=stereo +stereo-mode=headphones +stereo-encoding=hrtf +period_size=1 + +[wasapi] +exclusive-mode=true \ No newline at end of file diff --git a/Engine/lib/openal-soft/configs/HRTF/alsoft.ini b/Engine/lib/openal-soft/configs/HRTF/alsoft.ini new file mode 100644 index 000000000..a02f0b38b --- /dev/null +++ b/Engine/lib/openal-soft/configs/HRTF/alsoft.ini @@ -0,0 +1,4 @@ +[general] +channels=stereo +stereo-mode=headphones +stereo-encoding=hrtf diff --git a/Engine/lib/openal-soft/configs/WASAPI Exclusive/alsoft.ini b/Engine/lib/openal-soft/configs/WASAPI Exclusive/alsoft.ini new file mode 100644 index 000000000..3a70ee343 --- /dev/null +++ b/Engine/lib/openal-soft/configs/WASAPI Exclusive/alsoft.ini @@ -0,0 +1,5 @@ +[General] +period_size=1 + +[wasapi] +exclusive-mode=true \ No newline at end of file diff --git a/Engine/lib/openal-soft/core/ambdec.cpp b/Engine/lib/openal-soft/core/ambdec.cpp index ba3a4b8b3..c1ba4c31b 100644 --- a/Engine/lib/openal-soft/core/ambdec.cpp +++ b/Engine/lib/openal-soft/core/ambdec.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -16,7 +15,8 @@ #include "albit.h" #include "alspan.h" -#include "opthelpers.h" +#include "filesystem.h" +#include "fmt/core.h" namespace { @@ -43,35 +43,13 @@ enum class ReaderScope { HFMatrix, }; -#ifdef __MINGW32__ -[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]] -#else -[[gnu::format(printf,2,3)]] -#endif -std::optional make_error(size_t linenum, const char *fmt, ...) +template +auto make_error(size_t linenum, fmt::format_string fmt, Args&& ...args) + -> std::optional { std::optional ret; - auto &str = ret.emplace(); - - str.resize(256); - int printed{std::snprintf(str.data(), str.length(), "Line %zu: ", linenum)}; - if(printed < 0) printed = 0; - auto plen = std::min(static_cast(printed), str.length()); - - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - std::va_list args, args2; - va_start(args, fmt); - va_copy(args2, args); - const int msglen{std::vsnprintf(&str[plen], str.size()-plen, fmt, args)}; - if(msglen >= 0 && static_cast(msglen) >= str.size()-plen) - { - str.resize(static_cast(msglen) + plen + 1u); - std::vsnprintf(&str[plen], str.size()-plen, fmt, args2); - } - va_end(args2); - va_end(args); - /* NOLINTEND(*-array-to-pointer-decay) */ - + auto &str = ret.emplace(fmt::format("Line {}: ", linenum)); + str += fmt::format(std::move(fmt), std::forward(args)...); return ret; } @@ -82,7 +60,7 @@ AmbDecConf::~AmbDecConf() = default; std::optional AmbDecConf::load(const char *fname) noexcept { - std::ifstream f{std::filesystem::u8path(fname)}; + fs::ifstream f{fs::u8path(fname)}; if(!f.is_open()) return std::string("Failed to open file \"")+fname+"\""; @@ -105,7 +83,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept if(command == "/}") { if(scope == ReaderScope::Global) - return make_error(linenum, "Unexpected /} in global scope"); + return make_error(linenum, "Unexpected /}} in global scope"); scope = ReaderScope::Global; continue; } @@ -125,7 +103,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept istr >> spkr.Connection; } else - return make_error(linenum, "Unexpected speakers command: %s", command.c_str()); + return make_error(linenum, "Unexpected speakers command: {}", command); } else if(scope == ReaderScope::LFMatrix || scope == ReaderScope::HFMatrix) { @@ -168,7 +146,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept } } else - return make_error(linenum, "Unexpected matrix command: %s", command.c_str()); + return make_error(linenum, "Unexpected matrix command: {}", command); } // Global scope commands else if(command == "/description") @@ -185,7 +163,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept return make_error(linenum, "Duplicate version definition"); istr >> Version; if(Version != 3) - return make_error(linenum, "Unsupported version: %d", Version); + return make_error(linenum, "Unsupported version: {}", Version); } else if(command == "/dec/chan_mask") { @@ -194,7 +172,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept istr >> std::hex >> ChanMask >> std::dec; if(!ChanMask || ChanMask > Ambi4OrderMask) - return make_error(linenum, "Invalid chan_mask: 0x%x", ChanMask); + return make_error(linenum, "Invalid chan_mask: {:#x}", ChanMask); if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa) return make_error(linenum, "FuMa not compatible with over third-order"); } @@ -204,7 +182,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept return make_error(linenum, "Duplicate freq_bands"); istr >> FreqBands; if(FreqBands != 1 && FreqBands != 2) - return make_error(linenum, "Invalid freq_bands: %u", FreqBands); + return make_error(linenum, "Invalid freq_bands: {}", FreqBands); } else if(command == "/dec/speakers") { @@ -213,7 +191,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept size_t numspeakers{}; istr >> numspeakers; if(!numspeakers) - return make_error(linenum, "Invalid speakers: %zu", numspeakers); + return make_error(linenum, "Invalid speakers: {}", numspeakers); Speakers.resize(numspeakers); } else if(command == "/dec/coeff_scale") @@ -226,7 +204,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D; else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa; else - return make_error(linenum, "Unexpected coeff_scale: %s", scale.c_str()); + return make_error(linenum, "Unexpected coeff_scale: {}", scale); if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa) return make_error(linenum, "FuMa not compatible with over third-order"); @@ -268,8 +246,8 @@ std::optional AmbDecConf::load(const char *fname) noexcept if(FreqBands == 1) { if(command != "/matrix/{") - return make_error(linenum, "Unexpected \"%s\" for a single-band decoder", - command.c_str()); + return make_error(linenum, "Unexpected \"{}\" for a single-band decoder", + command); scope = ReaderScope::HFMatrix; } else @@ -279,15 +257,16 @@ std::optional AmbDecConf::load(const char *fname) noexcept else if(command == "/hfmatrix/{") scope = ReaderScope::HFMatrix; else - return make_error(linenum, "Unexpected \"%s\" for a dual-band decoder", - command.c_str()); + return make_error(linenum, "Unexpected \"{}\" for a dual-band decoder", + command); } } else if(command == "/end") { const auto endpos = static_cast(istr.tellg()); if(!is_at_end(buffer, endpos)) - return make_error(linenum, "Extra junk on end: %s", buffer.substr(endpos).c_str()); + return make_error(linenum, "Extra junk on end: {}", + std::string_view{buffer}.substr(endpos)); if(speaker_pos < Speakers.size() || hfmatrix_pos < Speakers.size() || (FreqBands == 2 && lfmatrix_pos < Speakers.size())) @@ -298,12 +277,13 @@ std::optional AmbDecConf::load(const char *fname) noexcept return std::nullopt; } else - return make_error(linenum, "Unexpected command: %s", command.c_str()); + return make_error(linenum, "Unexpected command: {}", command); istr.clear(); const auto endpos = static_cast(istr.tellg()); if(!is_at_end(buffer, endpos)) - return make_error(linenum, "Extra junk on line: %s", buffer.substr(endpos).c_str()); + return make_error(linenum, "Extra junk on line: {}", + std::string_view{buffer}.substr(endpos)); buffer.clear(); } return make_error(linenum, "Unexpected end of file"); diff --git a/Engine/lib/openal-soft/core/ambidefs.cpp b/Engine/lib/openal-soft/core/ambidefs.cpp index 2389ce6bd..4a72e34f3 100644 --- a/Engine/lib/openal-soft/core/ambidefs.cpp +++ b/Engine/lib/openal-soft/core/ambidefs.cpp @@ -10,7 +10,6 @@ namespace { 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); @@ -76,16 +75,20 @@ static_assert(FirstOrderDecoder.size() == FirstOrderEncoder.size(), "First-order * content. */ constexpr std::array FirstOrder2DDecoder{ - std::array{2.500000000e-01f, 2.041241452e-01f, 0.0f, 2.041241452e-01f}, - std::array{2.500000000e-01f, 2.041241452e-01f, 0.0f, -2.041241452e-01f}, - std::array{2.500000000e-01f, -2.041241452e-01f, 0.0f, 2.041241452e-01f}, - std::array{2.500000000e-01f, -2.041241452e-01f, 0.0f, -2.041241452e-01f}, + std::array{1.666666667e-01f, -9.622504486e-02f, 0.0f, 1.666666667e-01f}, + std::array{1.666666667e-01f, -1.924500897e-01f, 0.0f, 0.000000000e+00f}, + std::array{1.666666667e-01f, -9.622504486e-02f, 0.0f, -1.666666667e-01f}, + std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, -1.666666667e-01f}, + std::array{1.666666667e-01f, 1.924500897e-01f, 0.0f, 0.000000000e+00f}, + std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, 1.666666667e-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), + 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(FirstOrder2DDecoder.size() == FirstOrder2DEncoder.size(), "First-order 2D mismatch"); diff --git a/Engine/lib/openal-soft/core/ambidefs.h b/Engine/lib/openal-soft/core/ambidefs.h index 1faf8eb68..2e7774a95 100644 --- a/Engine/lib/openal-soft/core/ambidefs.h +++ b/Engine/lib/openal-soft/core/ambidefs.h @@ -14,9 +14,9 @@ using uint = unsigned int; * needed will be (o+1)**2, thus zero-order has 1, first-order has 4, second- * order has 9, third-order has 16, and fourth-order has 25. */ -inline constexpr auto MaxAmbiOrder = std::uint8_t{3}; -inline constexpr auto AmbiChannelsFromOrder(std::size_t order) noexcept -> std::size_t +constexpr auto AmbiChannelsFromOrder(std::size_t order) noexcept -> std::size_t { return (order+1) * (order+1); } +inline constexpr auto MaxAmbiOrder = std::uint8_t{3}; inline constexpr auto MaxAmbiChannels = size_t{AmbiChannelsFromOrder(MaxAmbiOrder)}; /* A bitmask of ambisonic channels for 0 to 4th order. This only specifies up @@ -39,20 +39,20 @@ inline constexpr uint AmbiPeriphonicMask{0xfe7ce4}; * representation. This is 2 per each order above zero-order, plus 1 for zero- * order. Or simply, o*2 + 1. */ -inline constexpr auto Ambi2DChannelsFromOrder(std::size_t order) noexcept -> std::size_t +constexpr auto Ambi2DChannelsFromOrder(std::size_t order) noexcept -> std::size_t { return order*2 + 1; } -inline constexpr auto MaxAmbi2DChannels = std::size_t{Ambi2DChannelsFromOrder(MaxAmbiOrder)}; +inline constexpr auto MaxAmbi2DChannels = Ambi2DChannelsFromOrder(MaxAmbiOrder); /* NOTE: These are scale factors as applied to Ambisonics content. Decoder * coefficients should be divided by these values to get proper scalings. */ struct AmbiScale { - static inline constexpr std::array FromN3D{{ + static constexpr auto FromN3D = std::array{ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f - }}; - static inline constexpr std::array FromSN3D{{ + }; + static constexpr auto FromSN3D = std::array{ 1.000000000f, /* ACN 0, sqrt(1) */ 1.732050808f, /* ACN 1, sqrt(3) */ 1.732050808f, /* ACN 2, sqrt(3) */ @@ -69,8 +69,8 @@ struct AmbiScale { 2.645751311f, /* ACN 13, sqrt(7) */ 2.645751311f, /* ACN 14, sqrt(7) */ 2.645751311f, /* ACN 15, sqrt(7) */ - }}; - static inline constexpr std::array FromFuMa{{ + }; + static constexpr auto FromFuMa = std::array{ 1.414213562f, /* ACN 0 (W), sqrt(2) */ 1.732050808f, /* ACN 1 (Y), sqrt(3) */ 1.732050808f, /* ACN 2 (Z), sqrt(3) */ @@ -87,15 +87,15 @@ struct AmbiScale { 2.231093404f, /* ACN 13 (L), sqrt(224/45) */ 1.972026594f, /* ACN 14 (N), sqrt(35)/3 */ 2.091650066f, /* ACN 15 (P), sqrt(35/8) */ - }}; - static inline constexpr std::array FromUHJ{{ + }; + static constexpr auto FromUHJ = std::array{ 1.000000000f, /* ACN 0 (W), sqrt(1) */ 1.224744871f, /* ACN 1 (Y), sqrt(3/2) */ 1.224744871f, /* ACN 2 (Z), sqrt(3/2) */ 1.224744871f, /* ACN 3 (X), sqrt(3/2) */ /* Higher orders not relevant for UHJ. */ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, - }}; + }; /* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */ static std::array GetHFOrderScales(const uint src_order, @@ -111,7 +111,7 @@ struct AmbiScale { }; struct AmbiIndex { - static inline constexpr std::array FromFuMa{{ + static constexpr auto FromFuMa = std::array{ 0, /* W */ 3, /* X */ 1, /* Y */ @@ -128,8 +128,8 @@ struct AmbiIndex { 10, /* O */ 15, /* P */ 9, /* Q */ - }}; - static inline constexpr std::array FromFuMa2D{{ + }; + static constexpr auto FromFuMa2D = std::array{ 0, /* W */ 3, /* X */ 1, /* Y */ @@ -137,23 +137,23 @@ struct AmbiIndex { 4, /* V */ 15, /* P */ 9, /* Q */ - }}; + }; - static inline constexpr std::array FromACN{{ + static constexpr auto FromACN = std::array{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 - }}; - static inline constexpr std::array FromACN2D{{ + }; + static constexpr auto FromACN2D = std::array{ 0, 1,3, 4,8, 9,15 - }}; + }; - static inline constexpr std::array OrderFromChannel{{ + static constexpr auto OrderFromChannel = std::array{ 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3, - }}; - static inline constexpr std::array OrderFrom2DChannel{{ + }; + static constexpr auto OrderFrom2DChannel = std::array{ 0, 1,1, 2,2, 3,3, - }}; + }; }; diff --git a/Engine/lib/openal-soft/core/bformatdec.cpp b/Engine/lib/openal-soft/core/bformatdec.cpp index f572d4569..1e46e9508 100644 --- a/Engine/lib/openal-soft/core/bformatdec.cpp +++ b/Engine/lib/openal-soft/core/bformatdec.cpp @@ -102,8 +102,8 @@ void BFormatDec::processStablize(const al::span OutBuffer, */ const auto leftout = al::span{OutBuffer[lidx]}.first(SamplesToDo); const auto rightout = al::span{OutBuffer[ridx]}.first(SamplesToDo); - const al::span mid{al::assume_aligned<16>(mStablizer->MidDirect.data()), SamplesToDo}; - const al::span side{al::assume_aligned<16>(mStablizer->Side.data()), SamplesToDo}; + const auto mid = al::span{mStablizer->MidDirect}.first(SamplesToDo); + const auto side = al::span{mStablizer->Side}.first(SamplesToDo); std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), mid.begin(),std::plus{}); std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), side.begin(),std::minus{}); std::fill_n(leftout.begin(), leftout.size(), 0.0f); diff --git a/Engine/lib/openal-soft/core/bformatdec.h b/Engine/lib/openal-soft/core/bformatdec.h index 5bb2d3b40..b86f771da 100644 --- a/Engine/lib/openal-soft/core/bformatdec.h +++ b/Engine/lib/openal-soft/core/bformatdec.h @@ -13,11 +13,12 @@ #include "devformat.h" #include "filters/splitter.h" #include "front_stablizer.h" +#include "opthelpers.h" using ChannelDec = std::array; -class BFormatDec { +class SIMDALIGN BFormatDec { static constexpr size_t sHFBand{0}; static constexpr size_t sLFBand{1}; static constexpr size_t sNumBands{2}; diff --git a/Engine/lib/openal-soft/core/bs2b.cpp b/Engine/lib/openal-soft/core/bs2b.cpp index 6e292b147..68b0d575f 100644 --- a/Engine/lib/openal-soft/core/bs2b.cpp +++ b/Engine/lib/openal-soft/core/bs2b.cpp @@ -126,34 +126,36 @@ void bs2b::clear() history.fill(bs2b::t_last_sample{}); } -void bs2b::cross_feed(float *Left, float *Right, size_t SamplesToDo) +void bs2b::cross_feed(const al::span Left, const al::span Right) { - const float a0lo{a0_lo}; - const float b1lo{b1_lo}; - const float a0hi{a0_hi}; - const float a1hi{a1_hi}; - const float b1hi{b1_hi}; - std::array,128> samples{}; - al::span lsamples{Left, SamplesToDo}; - al::span rsamples{Right, SamplesToDo}; + const auto a0lo = a0_lo; + const auto b1lo = b1_lo; + const auto a0hi = a0_hi; + const auto a1hi = a1_hi; + const auto b1hi = b1_hi; + auto lsamples = Left.first(std::min(Left.size(), Right.size())); + auto rsamples = Right.first(lsamples.size()); + auto samples = std::array,128>{}; - while(!lsamples.empty()) + auto leftio = lsamples.begin(); + auto rightio = rsamples.begin(); + while(auto rem = std::distance(leftio, lsamples.end())) { - const size_t todo{std::min(samples.size(), lsamples.size())}; + const auto todo = std::min(samples.size(), rem); /* Process left input */ - float z_lo{history[0].lo}; - float z_hi{history[0].hi}; - std::transform(lsamples.cbegin(), lsamples.cbegin()+ptrdiff_t(todo), samples.begin(), - [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x) -> std::array + auto z_lo = history[0].lo; + auto z_hi = history[0].hi; + std::transform(leftio, leftio+todo, samples.begin(), + [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x) noexcept { - float y0{a0hi*x + z_hi}; + const auto y0 = a0hi*x + z_hi; z_hi = a1hi*x + b1hi*y0; - float y1{a0lo*x + z_lo}; + const auto y1 = a0lo*x + z_lo; z_lo = b1lo*y1; - return {y0, y1}; + return std::array{y0, y1}; }); history[0].lo = z_lo; history[0].hi = z_hi; @@ -161,28 +163,24 @@ void bs2b::cross_feed(float *Left, float *Right, size_t SamplesToDo) /* Process right input */ z_lo = history[1].lo; z_hi = history[1].hi; - std::transform(rsamples.cbegin(), rsamples.cbegin()+ptrdiff_t(todo), samples.begin(), - samples.begin(), - [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x, const std::array out) -> std::array + std::transform(rightio, rightio+todo, samples.cbegin(), samples.begin(), + [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x, const std::array &out) noexcept { - float y0{a0lo*x + z_lo}; + const auto y0 = a0lo*x + z_lo; z_lo = b1lo*y0; - float y1{a0hi*x + z_hi}; + const auto y1 = a0hi*x + z_hi; z_hi = a1hi*x + b1hi*y1; - return {out[0]+y0, out[1]+y1}; + return std::array{out[0]+y0, out[1]+y1}; }); history[1].lo = z_lo; history[1].hi = z_hi; - auto iter = std::transform(samples.cbegin(), samples.cbegin()+todo, lsamples.begin(), + leftio = std::transform(samples.cbegin(), samples.cbegin()+todo, leftio, [](const std::array &in) { return in[0]; }); - lsamples = {iter, lsamples.end()}; - - iter = std::transform(samples.cbegin(), samples.cbegin()+todo, rsamples.begin(), + rightio = std::transform(samples.cbegin(), samples.cbegin()+todo, rightio, [](const std::array &in) { return in[1]; }); - rsamples = {iter, rsamples.end()}; } } diff --git a/Engine/lib/openal-soft/core/bs2b.h b/Engine/lib/openal-soft/core/bs2b.h index 32c77e101..dd6d3ce24 100644 --- a/Engine/lib/openal-soft/core/bs2b.h +++ b/Engine/lib/openal-soft/core/bs2b.h @@ -27,6 +27,8 @@ #include #include +#include "alspan.h" + namespace Bs2b { enum { @@ -81,7 +83,7 @@ struct bs2b { /* Clear buffer */ void clear(); - void cross_feed(float *Left, float *Right, std::size_t SamplesToDo); + void cross_feed(const al::span Left, const al::span Right); }; } // namespace Bs2b diff --git a/Engine/lib/openal-soft/core/bsinc_tables.cpp b/Engine/lib/openal-soft/core/bsinc_tables.cpp index 74f47a129..35b197a85 100644 --- a/Engine/lib/openal-soft/core/bsinc_tables.cpp +++ b/Engine/lib/openal-soft/core/bsinc_tables.cpp @@ -14,6 +14,7 @@ #include "alnumeric.h" #include "alspan.h" #include "bsinc_defs.h" +#include "opthelpers.h" #include "resampler_limits.h" @@ -21,10 +22,6 @@ namespace { using uint = unsigned int; -#if __cpp_lib_math_special_functions >= 201603L -using std::cyl_bessel_i; - -#else /* The zero-order modified Bessel function of the first kind, used for the * Kaiser window. @@ -37,7 +34,7 @@ using std::cyl_bessel_i; * compounding the rounding and precision error), but it's good enough. */ template -U cyl_bessel_i(T nu, U x) +constexpr auto cyl_bessel_i(T nu, U x) -> U { if(nu != T{0}) throw std::runtime_error{"cyl_bessel_i: nu != 0"}; @@ -61,7 +58,6 @@ U cyl_bessel_i(T nu, U x) } while(sum != last_sum); return static_cast(sum); } -#endif /* This is the normalized cardinal sine (sinc) function. * @@ -94,7 +90,7 @@ constexpr double Kaiser(const double beta, const double k, const double besseli_ { if(!(k >= -1.0 && k <= 1.0)) return 0.0; - return cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; + return ::cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; } /* Calculates the (normalized frequency) transition width of the Kaiser window. @@ -120,73 +116,139 @@ constexpr double CalcKaiserBeta(const double rejection) struct BSincHeader { - double width{}; double beta{}; double scaleBase{}; + double scaleLimit{}; - std::array a{}; + std::array a{}; + std::array m{}; uint total_size{}; - constexpr BSincHeader(uint Rejection, uint Order) noexcept - : width{CalcKaiserWidth(Rejection, Order)}, beta{CalcKaiserBeta(Rejection)} - , scaleBase{width / 2.0} + constexpr BSincHeader(uint rejection, uint order, uint maxScale) noexcept + : beta{CalcKaiserBeta(rejection)}, scaleBase{CalcKaiserWidth(rejection, order) / 2.0} + , scaleLimit{1.0 / maxScale} { - uint num_points{Order+1}; + const auto base_a = (order+1.0) / 2.0; for(uint si{0};si < BSincScaleCount;++si) { - const double scale{lerpd(scaleBase, 1.0, (si+1) / double{BSincScaleCount})}; - const uint a_{std::min(static_cast(num_points / 2.0 / scale), num_points)}; - const uint m{2 * a_}; + const auto scale = lerpd(scaleBase, 1.0, (si+1u) / double{BSincScaleCount}); + a[si] = std::min(base_a/scale, base_a*maxScale); + /* std::ceil() isn't constexpr until C++23, this should behave the + * same. + */ + auto a_ = static_cast(a[si]); + a_ += (static_cast(a_) != a[si]); + m[si] = a_ * 2u; - a[si] = a_; - total_size += 4 * BSincPhaseCount * ((m+3) & ~3u); + total_size += 4u * BSincPhaseCount * ((m[si]+3u) & ~3u); } } }; /* 11th and 23rd order filters (12 and 24-point respectively) with a 60dB drop - * at nyquist. Each filter will scale up the order when downsampling, to 23rd - * and 47th order respectively. + * at nyquist. Each filter will scale up to double size when downsampling, to + * 23rd and 47th order respectively. */ -constexpr BSincHeader bsinc12_hdr{60, 11}; -constexpr BSincHeader bsinc24_hdr{60, 23}; +constexpr auto bsinc12_hdr = BSincHeader{60, 11, 2}; +constexpr auto bsinc24_hdr = BSincHeader{60, 23, 2}; +/* 47th order filter (48-point) with an 80dB drop at nyquist. The filter order + * doesn't increase when downsampling. + */ +constexpr auto bsinc48_hdr = BSincHeader{80, 47, 1}; template -struct BSincFilterArray { +struct SIMDALIGN BSincFilterArray { alignas(16) std::array mTable{}; BSincFilterArray() { - static constexpr uint BSincPointsMax{(hdr.a[0]*2u + 3u) & ~3u}; + static constexpr auto BSincPointsMax = (hdr.m[0]+3u) & ~3u; static_assert(BSincPointsMax <= MaxResamplerPadding, "MaxResamplerPadding is too small"); using filter_type = std::array,BSincPhaseCount>; auto filter = std::vector(BSincScaleCount); - const double besseli_0_beta{cyl_bessel_i(0, hdr.beta)}; + static constexpr auto besseli_0_beta = ::cyl_bessel_i(0, hdr.beta); /* Calculate the Kaiser-windowed Sinc filter coefficients for each * scale and phase index. */ for(uint si{0};si < BSincScaleCount;++si) { - const uint m{hdr.a[si] * 2}; - const size_t o{(BSincPointsMax-m) / 2}; - const double scale{lerpd(hdr.scaleBase, 1.0, (si+1) / double{BSincScaleCount})}; - const double cutoff{scale - (hdr.scaleBase * std::max(1.0, scale*2.0))}; - const auto a = static_cast(hdr.a[si]); - const double l{a - 1.0/BSincPhaseCount}; + const auto a = hdr.a[si]; + const auto m = hdr.m[si]; + const auto l = std::floor(m*0.5) - 1.0; + const auto o = size_t{BSincPointsMax-m} / 2u; + const auto scale = lerpd(hdr.scaleBase, 1.0, (si+1u) / double{BSincScaleCount}); + + /* Calculate an appropriate cutoff frequency. An explanation may be + * in order here. + * + * When up-sampling, or down-sampling by less than the max scaling + * factor (when scale >= scaleLimit), the filter order increases as + * the down-sampling factor is reduced, enabling a consistent + * filter response output. + * + * When down-sampling by more than the max scale factor, the filter + * order stays constant to avoid further increasing the processing + * cost, causing the transition width to increase. This would + * normally be compensated for by reducing the cutoff frequency, + * to keep the transition band under the nyquist frequency and + * avoid aliasing. However, this has the side-effect of attenuating + * more of the original high frequency content, which can be + * significant with more extreme down-sampling scales. + * + * To combat this, we can allow for some aliasing to keep the + * cutoff frequency higher than it would otherwise be. We can allow + * the transition band to "wrap around" the nyquist frequency, so + * the output would have some low-level aliasing that overlays with + * the attenuated frequencies in the transition band. This allows + * the cutoff frequency to remain fixed as the transition width + * increases, until the stop frequency aliases back to the cutoff + * frequency and the transition band becomes fully wrapped over + * itself, at which point the cutoff frequency will lower at half + * the rate the transition width increases. + * + * This has an additional benefit when dealing with typical output + * rates like 44 or 48khz. Since human hearing maxes out at 20khz, + * and these rates handle frequencies up to 22 or 24khz, this lets + * some aliasing get masked. For example, the bsinc24 filter with + * 48khz output has a cutoff of 20khz when down-sampling, and a + * 4khz transition band. When down-sampling by more extreme scales, + * the cutoff frequency can stay at 20khz while the transition + * width doubles before any aliasing noise may become audible. + * + * This is what we do here. + * + * 'max_cutoff` is the upper bound normalized cutoff frequency for + * this scale factor, that aligns with the same absolute frequency + * as nominal resample factors. When up-sampling (scale == 1), the + * cutoff can't be raised further than this, or else it would + * prematurely add audible aliasing noise. + * + * 'width' is the normalized transition width for this scale + * factor. + * + * '(scale - width)*0.5' calculates the cutoff frequency necessary + * for the transition band to fully wrap on itself around the + * nyquist frequency. If this is larger than max_cutoff, the + * transition band is not fully wrapped at this scale and the + * cutoff doesn't need adjustment. + */ + const auto max_cutoff = (0.5 - hdr.scaleBase)*scale; + const auto width = hdr.scaleBase * std::max(hdr.scaleLimit, scale); + const auto cutoff2 = std::min(max_cutoff, (scale - width)*0.5) * 2.0; for(uint pi{0};pi < BSincPhaseCount;++pi) { - const double phase{std::floor(l) + (pi/double{BSincPhaseCount})}; + const auto phase = l + (pi/double{BSincPhaseCount}); for(uint i{0};i < m;++i) { - const double x{i - phase}; - filter[si][pi][o+i] = Kaiser(hdr.beta, x/l, besseli_0_beta) * cutoff * - Sinc(cutoff*x); + const auto x = static_cast(i) - phase; + filter[si][pi][o+i] = Kaiser(hdr.beta, x/a, besseli_0_beta) * cutoff2 * + Sinc(cutoff2*x); } } } @@ -194,8 +256,8 @@ struct BSincFilterArray { size_t idx{0}; for(size_t si{0};si < BSincScaleCount;++si) { - const size_t m{((hdr.a[si]*2) + 3) & ~3u}; - const size_t o{(BSincPointsMax-m) / 2}; + const auto m = (hdr.m[si]+3_uz) & ~3_uz; + const auto o = size_t{BSincPointsMax-m} / 2u; /* Write out each phase index's filter and phase delta for this * quality scale. @@ -282,8 +344,9 @@ struct BSincFilterArray { [[nodiscard]] constexpr auto getTable() const noexcept { return al::span{mTable}; } }; -const BSincFilterArray bsinc12_filter{}; -const BSincFilterArray bsinc24_filter{}; +const auto bsinc12_filter = BSincFilterArray{}; +const auto bsinc24_filter = BSincFilterArray{}; +const auto bsinc48_filter = BSincFilterArray{}; template constexpr BSincTable GenerateBSincTable(const T &filter) @@ -293,7 +356,7 @@ constexpr BSincTable GenerateBSincTable(const T &filter) ret.scaleBase = static_cast(hdr.scaleBase); ret.scaleRange = static_cast(1.0 / (1.0 - hdr.scaleBase)); for(size_t i{0};i < BSincScaleCount;++i) - ret.m[i] = ((hdr.a[i]*2) + 3) & ~3u; + ret.m[i] = (hdr.m[i]+3u) & ~3u; ret.filterOffset[0] = 0; for(size_t i{1};i < BSincScaleCount;++i) ret.filterOffset[i] = ret.filterOffset[i-1] + ret.m[i-1]*4*BSincPhaseCount; @@ -305,3 +368,4 @@ constexpr BSincTable GenerateBSincTable(const T &filter) const BSincTable gBSinc12{GenerateBSincTable(bsinc12_filter)}; const BSincTable gBSinc24{GenerateBSincTable(bsinc24_filter)}; +const BSincTable gBSinc48{GenerateBSincTable(bsinc48_filter)}; diff --git a/Engine/lib/openal-soft/core/bsinc_tables.h b/Engine/lib/openal-soft/core/bsinc_tables.h index eab894ce9..516e1ed0f 100644 --- a/Engine/lib/openal-soft/core/bsinc_tables.h +++ b/Engine/lib/openal-soft/core/bsinc_tables.h @@ -5,7 +5,7 @@ #include "alspan.h" #include "bsinc_defs.h" - +#include "opthelpers.h" struct BSincTable { float scaleBase, scaleRange; @@ -14,7 +14,8 @@ struct BSincTable { al::span Tab; }; -extern const BSincTable gBSinc12; -extern const BSincTable gBSinc24; +DECL_HIDDEN extern const BSincTable gBSinc12; +DECL_HIDDEN extern const BSincTable gBSinc24; +DECL_HIDDEN extern const BSincTable gBSinc48; #endif /* CORE_BSINC_TABLES_H */ diff --git a/Engine/lib/openal-soft/core/buffer_storage.h b/Engine/lib/openal-soft/core/buffer_storage.h index ab097cf12..3ffc1f0be 100644 --- a/Engine/lib/openal-soft/core/buffer_storage.h +++ b/Engine/lib/openal-soft/core/buffer_storage.h @@ -1,10 +1,8 @@ #ifndef CORE_BUFFER_STORAGE_H #define CORE_BUFFER_STORAGE_H -#include #include -#include "alnumeric.h" #include "alspan.h" #include "ambidefs.h" #include "storage_formats.h" @@ -34,7 +32,7 @@ constexpr bool Is2DAmbisonic(FmtChannels chans) noexcept } -using CallbackType = int(*)(void*, void*, int); +using CallbackType = int(*)(void*, void*, int) noexcept; struct BufferStorage { CallbackType mCallback{nullptr}; diff --git a/Engine/lib/openal-soft/core/context.cpp b/Engine/lib/openal-soft/core/context.cpp index cff0e40cf..68f6dc38d 100644 --- a/Engine/lib/openal-soft/core/context.cpp +++ b/Engine/lib/openal-soft/core/context.cpp @@ -33,21 +33,16 @@ ContextBase::~ContextBase() if(mAsyncEvents) { size_t count{0}; - auto evt_vec = mAsyncEvents->getReadVector(); - if(evt_vec.first.len > 0) + for(auto &evt : mAsyncEvents->getReadVector()) { - std::destroy_n(std::launder(reinterpret_cast(evt_vec.first.buf)), - evt_vec.first.len); - count += evt_vec.first.len; - } - if(evt_vec.second.len > 0) - { - std::destroy_n(std::launder(reinterpret_cast(evt_vec.second.buf)), - evt_vec.second.len); - count += evt_vec.second.len; + if(evt.len > 0) + { + std::destroy_n(std::launder(reinterpret_cast(evt.buf)), evt.len); + count += evt.len; + } } if(count > 0) - TRACE("Destructed %zu orphaned event%s\n", count, (count==1)?"":"s"); + TRACE("Destructed {} orphaned event{}", count, (count==1)?"":"s"); mAsyncEvents->readAdvance(count); } } @@ -72,7 +67,7 @@ void ContextBase::allocVoiceProps() { static constexpr size_t clustersize{std::tuple_size_v}; - TRACE("Increasing allocated voice properties to %zu\n", + TRACE("Increasing allocated voice properties to {}", (mVoicePropClusters.size()+1) * clustersize); auto clusterptr = std::make_unique(); @@ -104,7 +99,7 @@ void ContextBase::allocVoices(size_t addcount) if(addcount >= std::numeric_limits::max()/clustersize - mVoiceClusters.size()) throw std::runtime_error{"Allocating too many voices"}; const size_t totalcount{(mVoiceClusters.size()+addcount) * clustersize}; - TRACE("Increasing allocated voices to %zu\n", totalcount); + TRACE("Increasing allocated voices to {}", totalcount); while(addcount) { @@ -127,7 +122,7 @@ void ContextBase::allocEffectSlotProps() { static constexpr size_t clustersize{std::tuple_size_v}; - TRACE("Increasing allocated effect slot properties to %zu\n", + TRACE("Increasing allocated effect slot properties to {}", (mEffectSlotPropClusters.size()+1) * clustersize); auto clusterptr = std::make_unique(); @@ -157,7 +152,7 @@ EffectSlot *ContextBase::getEffectSlot() if(1 >= std::numeric_limits::max()/clusterptr->size() - mEffectSlotClusters.size()) throw std::runtime_error{"Allocating too many effect slots"}; const size_t totalcount{(mEffectSlotClusters.size()+1) * clusterptr->size()}; - TRACE("Increasing allocated effect slots to %zu\n", totalcount); + TRACE("Increasing allocated effect slots to {}", totalcount); mEffectSlotClusters.emplace_back(std::move(clusterptr)); return mEffectSlotClusters.back()->data(); @@ -168,7 +163,7 @@ void ContextBase::allocContextProps() { static constexpr size_t clustersize{std::tuple_size_v}; - TRACE("Increasing allocated context properties to %zu\n", + TRACE("Increasing allocated context properties to {}", (mContextPropClusters.size()+1) * clustersize); auto clusterptr = std::make_unique(); diff --git a/Engine/lib/openal-soft/core/context.h b/Engine/lib/openal-soft/core/context.h index 1a1852e33..7d98ee8f7 100644 --- a/Engine/lib/openal-soft/core/context.h +++ b/Engine/lib/openal-soft/core/context.h @@ -1,6 +1,8 @@ #ifndef CORE_CONTEXT_H #define CORE_CONTEXT_H +#include "config.h" + #include #include #include @@ -9,7 +11,6 @@ #include #include -#include "almalloc.h" #include "alsem.h" #include "alspan.h" #include "async_event.h" @@ -53,19 +54,22 @@ struct ContextProps { float DopplerFactor; float DopplerVelocity; float SpeedOfSound; +#if ALSOFT_EAX + float DistanceFactor; +#endif bool SourceDistanceModel; DistanceModel mDistanceModel; - std::atomic next; + std::atomic next{}; }; struct ContextParams { /* Pointer to the most recent property values that are awaiting an update. */ std::atomic ContextUpdate{nullptr}; - alu::Vector Position{}; + alu::Vector Position; alu::Matrix Matrix{alu::Matrix::Identity()}; - alu::Vector Velocity{}; + alu::Vector Velocity; float Gain{1.0f}; float MetersPerUnit{1.0f}; @@ -113,7 +117,7 @@ struct ContextBase { ContextParams mParams; using VoiceArray = al::FlexArray; - al::atomic_unique_ptr mVoices{}; + al::atomic_unique_ptr mVoices; std::atomic mActiveVoiceCount{}; void allocVoices(size_t addcount); @@ -172,10 +176,10 @@ struct ContextBase { std::vector mContextPropClusters; - ContextBase(DeviceBase *device); + explicit ContextBase(DeviceBase *device); ContextBase(const ContextBase&) = delete; ContextBase& operator=(const ContextBase&) = delete; - ~ContextBase(); + virtual ~ContextBase(); }; #endif /* CORE_CONTEXT_H */ diff --git a/Engine/lib/openal-soft/core/converter.cpp b/Engine/lib/openal-soft/core/converter.cpp index 97102536f..97fffc161 100644 --- a/Engine/lib/openal-soft/core/converter.cpp +++ b/Engine/lib/openal-soft/core/converter.cpp @@ -169,10 +169,11 @@ void Multi2Mono(uint chanmask, const size_t step, const float scale, const al::s SampleConverterPtr SampleConverter::Create(DevFmtType srcType, DevFmtType dstType, size_t numchans, uint srcRate, uint dstRate, Resampler resampler) { + SampleConverterPtr converter; if(numchans < 1 || srcRate < 1 || dstRate < 1) - return nullptr; + return converter; - SampleConverterPtr converter{new(FamCount(numchans)) SampleConverter{numchans}}; + converter = SampleConverterPtr{new(FamCount(numchans)) SampleConverter{numchans}}; converter->mSrcType = srcType; converter->mDstType = dstType; converter->mSrcTypeSize = BytesFromDevFmt(srcType); diff --git a/Engine/lib/openal-soft/core/converter.h b/Engine/lib/openal-soft/core/converter.h index 01649c361..45dc4aa98 100644 --- a/Engine/lib/openal-soft/core/converter.h +++ b/Engine/lib/openal-soft/core/converter.h @@ -24,7 +24,7 @@ struct SampleConverter { uint mFracOffset{}; uint mIncrement{}; - InterpState mState{}; + InterpState mState; ResamplerFunc mResample{}; alignas(16) FloatBufferLine mSrcSamples{}; @@ -35,7 +35,7 @@ struct SampleConverter { }; al::FlexArray mChan; - SampleConverter(size_t numchans) : mChan{numchans} { } + explicit SampleConverter(size_t numchans) : mChan{numchans} { } [[nodiscard]] auto convert(const void **src, uint *srcframes, void *dst, uint dstframes) -> uint; [[nodiscard]] auto convertPlanar(const void **src, uint *srcframes, void *const*dst, uint dstframes) -> uint; diff --git a/Engine/lib/openal-soft/core/cpu_caps.cpp b/Engine/lib/openal-soft/core/cpu_caps.cpp index 1a064cf46..6f901d24e 100644 --- a/Engine/lib/openal-soft/core/cpu_caps.cpp +++ b/Engine/lib/openal-soft/core/cpu_caps.cpp @@ -1,5 +1,6 @@ #include "config.h" +#include "config_simd.h" #include "cpu_caps.h" @@ -23,8 +24,6 @@ #include -int CPUCapFlags{0}; - namespace { #if defined(HAVE_GCC_GET_CPUID) \ @@ -111,22 +110,22 @@ std::optional GetCPUInfo() #else /* Assume support for whatever's supported if we can't check for it */ -#if defined(HAVE_SSE4_1) +#if HAVE_SSE4_1 #warning "Assuming SSE 4.1 run-time support!" ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1; -#elif defined(HAVE_SSE3) +#elif HAVE_SSE3 #warning "Assuming SSE 3 run-time support!" ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3; -#elif defined(HAVE_SSE2) +#elif HAVE_SSE2 #warning "Assuming SSE 2 run-time support!" ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2; -#elif defined(HAVE_SSE) +#elif HAVE_SSE #warning "Assuming SSE run-time support!" ret.mCaps |= CPU_CAP_SSE; #endif #endif /* CAN_GET_CPUID */ -#ifdef HAVE_NEON +#if HAVE_NEON #ifdef __ARM_NEON ret.mCaps |= CPU_CAP_NEON; #elif defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64)) diff --git a/Engine/lib/openal-soft/core/cpu_caps.h b/Engine/lib/openal-soft/core/cpu_caps.h index 0826a49bd..31096e015 100644 --- a/Engine/lib/openal-soft/core/cpu_caps.h +++ b/Engine/lib/openal-soft/core/cpu_caps.h @@ -5,7 +5,7 @@ #include -extern int CPUCapFlags; +inline int CPUCapFlags{0}; enum { CPU_CAP_SSE = 1<<0, CPU_CAP_SSE2 = 1<<1, diff --git a/Engine/lib/openal-soft/core/cubic_tables.cpp b/Engine/lib/openal-soft/core/cubic_tables.cpp index cf469f9c0..a7cb10191 100644 --- a/Engine/lib/openal-soft/core/cubic_tables.cpp +++ b/Engine/lib/openal-soft/core/cubic_tables.cpp @@ -15,7 +15,7 @@ * * * - * Additional changes were made here, the most obvious being that is has full + * Additional changes were made here, the most obvious being that it has full * floating-point precision instead of 11-bit fixed-point, but also an offset * adjustment for the coefficients to better preserve phase. */ @@ -26,9 +26,9 @@ auto GetCoeff(double idx) noexcept -> double { const double k{0.5 + idx}; if(k > 512.0) return 0.0; - const double s{ std::sin(al::numbers::pi*1.280/1024 * k)}; - const double t{(std::cos(al::numbers::pi*2.000/1023 * k) - 1.0) * 0.50}; - const double u{(std::cos(al::numbers::pi*4.000/1023 * k) - 1.0) * 0.08}; + const double s{ std::sin(al::numbers::pi*1.280/1024.0 * k)}; + const double t{(std::cos(al::numbers::pi*2.000/1023.0 * k) - 1.0) * 0.50}; + const double u{(std::cos(al::numbers::pi*4.000/1023.0 * k) - 1.0) * 0.08}; return s * (t + u + 1.0) / k; } @@ -70,17 +70,20 @@ GaussianTable::GaussianTable() SplineTable::SplineTable() { + static constexpr auto third = 1.0/3.0; + static constexpr auto sixth = 1.0/6.0; /* This filter table is based on a Catmull-Rom spline. It retains more of * the original high-frequency content, at the cost of increased harmonics. */ for(std::size_t pi{0};pi < CubicPhaseCount;++pi) { - const double mu{static_cast(pi) / double{CubicPhaseCount}}; - const double mu2{mu*mu}, mu3{mu2*mu}; - mTable[pi].mCoeffs[0] = static_cast(-0.5*mu3 + mu2 + -0.5*mu); - mTable[pi].mCoeffs[1] = static_cast( 1.5*mu3 + -2.5*mu2 + 1.0); - mTable[pi].mCoeffs[2] = static_cast(-1.5*mu3 + 2.0*mu2 + 0.5*mu); - mTable[pi].mCoeffs[3] = static_cast( 0.5*mu3 + -0.5*mu2); + const auto mu = static_cast(pi) / double{CubicPhaseCount}; + const auto mu2 = mu*mu; + const auto mu3 = mu*mu2; + mTable[pi].mCoeffs[0] = static_cast( -third*mu + 0.5*mu2 - sixth*mu3); + mTable[pi].mCoeffs[1] = static_cast(1.0 - 0.5*mu - mu2 + 0.5*mu3); + mTable[pi].mCoeffs[2] = static_cast( mu + 0.5*mu2 - 0.5*mu3); + mTable[pi].mCoeffs[3] = static_cast( -sixth*mu + sixth*mu3); } for(std::size_t pi{0};pi < CubicPhaseCount-1;++pi) @@ -91,7 +94,7 @@ SplineTable::SplineTable() mTable[pi].mDeltas[3] = mTable[pi+1].mCoeffs[3] - mTable[pi].mCoeffs[3]; } - const std::size_t pi{CubicPhaseCount - 1}; + static constexpr auto pi = std::size_t{CubicPhaseCount - 1}; mTable[pi].mDeltas[0] = 0.0f - mTable[pi].mCoeffs[0]; mTable[pi].mDeltas[1] = mTable[0].mCoeffs[0] - mTable[pi].mCoeffs[1]; mTable[pi].mDeltas[2] = mTable[0].mCoeffs[1] - mTable[pi].mCoeffs[2]; diff --git a/Engine/lib/openal-soft/core/cubic_tables.h b/Engine/lib/openal-soft/core/cubic_tables.h index 2106a1331..b32764451 100644 --- a/Engine/lib/openal-soft/core/cubic_tables.h +++ b/Engine/lib/openal-soft/core/cubic_tables.h @@ -5,9 +5,10 @@ #include #include "cubic_defs.h" +#include "opthelpers.h" -struct CubicTable { +struct SIMDALIGN CubicTable { std::array mTable{}; }; diff --git a/Engine/lib/openal-soft/core/dbus_wrap.cpp b/Engine/lib/openal-soft/core/dbus_wrap.cpp index 05d9fc063..eccfb2c0b 100644 --- a/Engine/lib/openal-soft/core/dbus_wrap.cpp +++ b/Engine/lib/openal-soft/core/dbus_wrap.cpp @@ -3,7 +3,7 @@ #include "dbus_wrap.h" -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD #include #include @@ -18,7 +18,7 @@ void PrepareDBus() dbus_handle = LoadLib(libname); if(!dbus_handle) { - WARN("Failed to load %s\n", libname); + WARN("Failed to load {}", libname); return; } @@ -28,7 +28,7 @@ void PrepareDBus() load_func(p##x, #x); \ if(!p##x) \ { \ - WARN("Failed to load function %s\n", #x); \ + WARN("Failed to load function {}", #x); \ CloseLib(dbus_handle); \ dbus_handle = nullptr; \ return; \ diff --git a/Engine/lib/openal-soft/core/dbus_wrap.h b/Engine/lib/openal-soft/core/dbus_wrap.h index afc27d842..85a996eb3 100644 --- a/Engine/lib/openal-soft/core/dbus_wrap.h +++ b/Engine/lib/openal-soft/core/dbus_wrap.h @@ -7,7 +7,7 @@ #include "dynload.h" -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD #include @@ -63,7 +63,7 @@ inline auto HasDBus() #else constexpr bool HasDBus() noexcept { return true; } -#endif /* HAVE_DYNLOAD */ +#endif namespace dbus { diff --git a/Engine/lib/openal-soft/core/devformat.cpp b/Engine/lib/openal-soft/core/devformat.cpp index 57d1d8ea7..fc4a490ae 100644 --- a/Engine/lib/openal-soft/core/devformat.cpp +++ b/Engine/lib/openal-soft/core/devformat.cpp @@ -3,6 +3,11 @@ #include "devformat.h" +#include + +namespace { +using namespace std::string_view_literals; +} // namespace uint BytesFromDevFmt(DevFmtType type) noexcept { @@ -36,34 +41,34 @@ uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept return 0; } -const char *DevFmtTypeString(DevFmtType type) noexcept +auto DevFmtTypeString(DevFmtType type) noexcept -> std::string_view { switch(type) { - case DevFmtByte: return "Int8"; - case DevFmtUByte: return "UInt8"; - case DevFmtShort: return "Int16"; - case DevFmtUShort: return "UInt16"; - case DevFmtInt: return "Int32"; - case DevFmtUInt: return "UInt32"; - case DevFmtFloat: return "Float32"; + case DevFmtByte: return "Int8"sv; + case DevFmtUByte: return "UInt8"sv; + case DevFmtShort: return "Int16"sv; + case DevFmtUShort: return "UInt16"sv; + case DevFmtInt: return "Int32"sv; + case DevFmtUInt: return "UInt32"sv; + case DevFmtFloat: return "Float32"sv; } - return "(unknown type)"; + return "(unknown type)"sv; } -const char *DevFmtChannelsString(DevFmtChannels chans) noexcept +auto DevFmtChannelsString(DevFmtChannels chans) noexcept -> std::string_view { switch(chans) { - case DevFmtMono: return "Mono"; - case DevFmtStereo: return "Stereo"; - case DevFmtQuad: return "Quadraphonic"; - 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 DevFmtX7144: return "7.1.4.4 Surround"; - case DevFmtX3D71: return "3D7.1 Surround"; - case DevFmtAmbi3D: return "Ambisonic 3D"; + case DevFmtMono: return "Mono"sv; + case DevFmtStereo: return "Stereo"sv; + case DevFmtQuad: return "Quadraphonic"sv; + case DevFmtX51: return "5.1 Surround"sv; + case DevFmtX61: return "6.1 Surround"sv; + case DevFmtX71: return "7.1 Surround"sv; + case DevFmtX714: return "7.1.4 Surround"sv; + case DevFmtX7144: return "7.1.4.4 Surround"sv; + case DevFmtX3D71: return "3D7.1 Surround"sv; + case DevFmtAmbi3D: return "Ambisonic 3D"sv; } - return "(unknown channels)"; + return "(unknown channels)"sv; } diff --git a/Engine/lib/openal-soft/core/devformat.h b/Engine/lib/openal-soft/core/devformat.h index ca59df5e5..beff40de3 100644 --- a/Engine/lib/openal-soft/core/devformat.h +++ b/Engine/lib/openal-soft/core/devformat.h @@ -3,6 +3,7 @@ #include #include +#include using uint = unsigned int; @@ -108,8 +109,8 @@ uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept; inline uint FrameSizeFromDevFmt(DevFmtChannels chans, DevFmtType type, uint ambiorder) noexcept { return ChannelsFromDevFmt(chans, ambiorder) * BytesFromDevFmt(type); } -const char *DevFmtTypeString(DevFmtType type) noexcept; -const char *DevFmtChannelsString(DevFmtChannels chans) noexcept; +auto DevFmtTypeString(DevFmtType type) noexcept -> std::string_view; +auto DevFmtChannelsString(DevFmtChannels chans) noexcept -> std::string_view; enum class DevAmbiLayout : bool { FuMa, diff --git a/Engine/lib/openal-soft/core/device.cpp b/Engine/lib/openal-soft/core/device.cpp index 795a96016..4c2dc2a38 100644 --- a/Engine/lib/openal-soft/core/device.cpp +++ b/Engine/lib/openal-soft/core/device.cpp @@ -9,9 +9,6 @@ #include "mastering.h" -static_assert(std::atomic::is_always_lock_free); - - DeviceBase::DeviceBase(DeviceType type) : Type{type}, mContexts{al::FlexArray::Create(0)} { diff --git a/Engine/lib/openal-soft/core/device.h b/Engine/lib/openal-soft/core/device.h index a62863189..c01888c8e 100644 --- a/Engine/lib/openal-soft/core/device.h +++ b/Engine/lib/openal-soft/core/device.h @@ -18,6 +18,7 @@ #include "devformat.h" #include "filters/nfc.h" #include "flexarray.h" +#include "fmt/core.h" #include "intrusive_ptr.h" #include "mixer/hrtfdefs.h" #include "opthelpers.h" @@ -80,14 +81,14 @@ struct DistanceComp { static constexpr uint MaxDelay{1024}; struct ChanData { - al::span Buffer{}; /* Valid size is [0...MaxDelay). */ + al::span Buffer; /* Valid size is [0...MaxDelay). */ float Gain{1.0f}; }; std::array mChannels; al::FlexArray mSamples; - DistanceComp(std::size_t count) : mSamples{count} { } + explicit DistanceComp(std::size_t count) : mSamples{count} { } static std::unique_ptr Create(std::size_t numsamples) { return std::unique_ptr{new(FamCount(numsamples)) DistanceComp{numsamples}}; } @@ -179,13 +180,16 @@ enum class DeviceState : std::uint8_t { Playing }; -struct DeviceBase { +/* NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) */ +struct SIMDALIGN DeviceBase { std::atomic Connected{true}; const DeviceType Type{}; - uint Frequency{}; - uint UpdateSize{}; - uint BufferSize{}; + std::string mDeviceName; + + uint mSampleRate{}; + uint mUpdateSize{}; + uint mBufferSize{}; DevFmtChannels FmtChans{}; DevFmtType FmtType{}; @@ -199,10 +203,8 @@ struct DeviceBase { DevAmbiLayout mAmbiLayout{DevAmbiLayout::Default}; DevAmbiScaling mAmbiScale{DevAmbiScaling::Default}; - std::string DeviceName; - // Device flags - std::bitset Flags{}; + std::bitset Flags; DeviceState mDeviceState{DeviceState::Unprepared}; uint NumAuxSends{}; @@ -220,8 +222,13 @@ struct DeviceBase { */ NfcFilter mNFCtrlFilter{}; + using seconds32 = std::chrono::duration; + using nanoseconds32 = std::chrono::duration; + std::atomic mSamplesDone{0u}; - std::atomic mClockBase{std::chrono::nanoseconds{}}; + /* Split the clock to avoid a 64-bit atomic for certain 32-bit targets. */ + std::atomic mClockBaseSec{seconds32{}}; + std::atomic mClockBaseNSec{nanoseconds32{}}; std::chrono::nanoseconds FixedLatency{0}; AmbiRotateMatrix mAmbiRotateMatrix{}; @@ -288,11 +295,6 @@ struct DeviceBase { al::atomic_unique_ptr> mContexts; - DeviceBase(DeviceType type); - DeviceBase(const DeviceBase&) = delete; - DeviceBase& operator=(const DeviceBase&) = delete; - ~DeviceBase(); - [[nodiscard]] auto bytesFromFmt() const noexcept -> uint { return BytesFromDevFmt(FmtType); } [[nodiscard]] auto channelsFromFmt() const noexcept -> uint { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); } [[nodiscard]] auto frameSizeFromFmt() const noexcept -> uint { return bytesFromFmt() * channelsFromFmt(); } @@ -314,9 +316,8 @@ struct DeviceBase { /* Increment the mix count at the start of mixing and writing clock * info (lsb should be 1). */ - auto mixCount = mMixCount.load(std::memory_order_relaxed); - mMixCount.store(++mixCount, std::memory_order_release); - return MixLock{this, ++mixCount}; + const auto oldCount = mMixCount.fetch_add(1u, std::memory_order_acq_rel); + return MixLock{this, oldCount+2}; } /** Waits for the mixer to not be mixing or updating the clock. */ @@ -337,8 +338,9 @@ struct DeviceBase { using std::chrono::seconds; using std::chrono::nanoseconds; - auto ns = nanoseconds{seconds{mSamplesDone.load(std::memory_order_relaxed)}} / Frequency; - return mClockBase.load(std::memory_order_relaxed) + ns; + auto ns = nanoseconds{seconds{mSamplesDone.load(std::memory_order_relaxed)}} / mSampleRate; + return nanoseconds{mClockBaseNSec.load(std::memory_order_relaxed)} + + mClockBaseSec.load(std::memory_order_relaxed) + ns; } void ProcessHrtf(const std::size_t SamplesToDo); @@ -347,19 +349,18 @@ struct DeviceBase { void ProcessUhj(const std::size_t SamplesToDo); void ProcessBs2b(const std::size_t SamplesToDo); - inline void postProcess(const std::size_t SamplesToDo) + void postProcess(const std::size_t SamplesToDo) { if(PostProcess) LIKELY (this->*PostProcess)(SamplesToDo); } - void renderSamples(const al::span outBuffers, const uint numSamples); + void renderSamples(const al::span outBuffers, const uint numSamples); void renderSamples(void *outBuffer, const uint numSamples, const std::size_t frameStep); /* Caller must lock the device state, and the mixer must not be running. */ -#ifdef __MINGW32__ - [[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]] -#else - [[gnu::format(printf,2,3)]] -#endif - void handleDisconnect(const char *msg, ...); + void doDisconnect(std::string msg); + + template + void handleDisconnect(fmt::format_string fmt, Args&& ...args) + { doDisconnect(fmt::format(std::move(fmt), std::forward(args)...)); } /** * Returns the index for the given channel name (e.g. FrontCenter), or @@ -370,6 +371,14 @@ struct DeviceBase { private: uint renderSamples(const uint numSamples); + +protected: + explicit DeviceBase(DeviceType type); + ~DeviceBase(); + +public: + DeviceBase(const DeviceBase&) = delete; + DeviceBase& operator=(const DeviceBase&) = delete; }; /* Must be less than 15 characters (16 including terminating null) for diff --git a/Engine/lib/openal-soft/core/effects/base.h b/Engine/lib/openal-soft/core/effects/base.h index b0d267167..879f83900 100644 --- a/Engine/lib/openal-soft/core/effects/base.h +++ b/Engine/lib/openal-soft/core/effects/base.h @@ -8,6 +8,7 @@ #include "alspan.h" #include "core/bufferline.h" #include "intrusive_ptr.h" +#include "opthelpers.h" struct BufferStorage; struct ContextBase; @@ -193,7 +194,7 @@ struct EffectTarget { RealMixParams *RealOut; }; -struct EffectState : public al::intrusive_ref { +struct SIMDALIGN EffectState : public al::intrusive_ref { al::span mOutTarget; diff --git a/Engine/lib/openal-soft/core/effectslot.h b/Engine/lib/openal-soft/core/effectslot.h index 70dbbbad4..4825670a5 100644 --- a/Engine/lib/openal-soft/core/effectslot.h +++ b/Engine/lib/openal-soft/core/effectslot.h @@ -43,7 +43,7 @@ struct EffectSlotProps { al::intrusive_ptr State; - std::atomic next; + std::atomic next{}; }; @@ -64,7 +64,7 @@ struct EffectSlot { EffectSlot *Target{nullptr}; EffectSlotType EffectType{EffectSlotType::None}; - EffectProps mEffectProps{}; + EffectProps mEffectProps; al::intrusive_ptr mEffectState; float RoomRolloff{0.0f}; /* Added to the source's room rolloff, not multiplied. */ diff --git a/Engine/lib/openal-soft/core/except.cpp b/Engine/lib/openal-soft/core/except.cpp index f338e9aeb..50c085e55 100644 --- a/Engine/lib/openal-soft/core/except.cpp +++ b/Engine/lib/openal-soft/core/except.cpp @@ -3,30 +3,9 @@ #include "except.h" -#include -#include - -#include "opthelpers.h" - namespace al { base_exception::~base_exception() = default; -void base_exception::setMessage(const char *msg, std::va_list args) -{ - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - std::va_list args2; - va_copy(args2, args); - int msglen{std::vsnprintf(nullptr, 0, msg, args)}; - if(msglen > 0) LIKELY - { - mMessage.resize(static_cast(msglen)+1); - std::vsnprintf(mMessage.data(), mMessage.length(), msg, args2); - mMessage.pop_back(); - } - va_end(args2); - /* NOLINTEND(*-array-to-pointer-decay) */ -} - } // namespace al diff --git a/Engine/lib/openal-soft/core/except.h b/Engine/lib/openal-soft/core/except.h index 1b3d634fc..53e6a1c9f 100644 --- a/Engine/lib/openal-soft/core/except.h +++ b/Engine/lib/openal-soft/core/except.h @@ -1,10 +1,9 @@ #ifndef CORE_EXCEPT_H #define CORE_EXCEPT_H -#include #include #include -#include +#include namespace al { @@ -12,11 +11,10 @@ namespace al { class base_exception : public std::exception { std::string mMessage; -protected: - auto setMessage(const char *msg, std::va_list args) -> void; - public: base_exception() = default; + template,bool> = true> + explicit base_exception(T&& msg) : mMessage{std::forward(msg)} { } base_exception(const base_exception&) = default; base_exception(base_exception&&) = default; ~base_exception() override; diff --git a/Engine/lib/openal-soft/core/filters/biquad.h b/Engine/lib/openal-soft/core/filters/biquad.h index 19b10ebbb..9ced0d00c 100644 --- a/Engine/lib/openal-soft/core/filters/biquad.h +++ b/Engine/lib/openal-soft/core/filters/biquad.h @@ -120,7 +120,7 @@ public: const al::span dst); /* Rather hacky. It's just here to support "manual" processing. */ - [[nodiscard]] auto getComponents() const noexcept -> std::pair { return {mZ1, mZ2}; } + [[nodiscard]] auto getComponents() const noexcept -> std::array { return {{mZ1,mZ2}}; } void setComponents(Real z1, Real z2) noexcept { mZ1 = z1; mZ2 = z2; } [[nodiscard]] auto processOne(const Real in, Real &z1, Real &z2) const noexcept -> Real { diff --git a/Engine/lib/openal-soft/core/filters/nfc.cpp b/Engine/lib/openal-soft/core/filters/nfc.cpp index c3c313db4..fe6509897 100644 --- a/Engine/lib/openal-soft/core/filters/nfc.cpp +++ b/Engine/lib/openal-soft/core/filters/nfc.cpp @@ -5,8 +5,6 @@ #include -#include "opthelpers.h" - /* Near-field control filters are the basis for handling the near-field effect. * The near-field effect is a bass-boost present in the directional components @@ -48,29 +46,26 @@ namespace { -constexpr std::array B{ - std::array{ 0.0f, 0.0f, 0.0f, 0.0f}, - std::array{ 1.0f, 0.0f, 0.0f, 0.0f}, - std::array{ 3.0f, 3.0f, 0.0f, 0.0f}, - std::array{3.6778f, 6.4595f, 2.3222f, 0.0f}, - std::array{4.2076f, 11.4877f, 5.7924f, 9.1401f} -}; +constexpr auto B1 = std::array{ 1.0f}; +constexpr auto B2 = std::array{ 3.0f, 3.0f}; +constexpr auto B3 = std::array{3.6778f, 6.4595f, 2.3222f}; +constexpr auto B4 = std::array{4.2076f, 11.4877f, 5.7924f, 9.1401f}; NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept { - NfcFilter1 nfc{}; + auto nfc = NfcFilter1{}; /* Calculate bass-cut coefficients. */ - float r{0.5f * w1}; - float b_00{B[1][0] * r}; - float g_0{1.0f + b_00}; + auto r = 0.5f * w1; + auto b_00 = B1[0] * r; + auto g_0 = 1.0f + b_00; nfc.base_gain = 1.0f / g_0; nfc.a1 = 2.0f * b_00 / g_0; /* Calculate bass-boost coefficients. */ r = 0.5f * w0; - b_00 = B[1][0] * r; + b_00 = B1[0] * r; g_0 = 1.0f + b_00; nfc.gain = nfc.base_gain * g_0; @@ -81,9 +76,9 @@ NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept { - const float r{0.5f * w0}; - const float b_00{B[1][0] * r}; - const float g_0{1.0f + b_00}; + const auto r = 0.5f * w0; + const auto b_00 = B1[0] * r; + const auto g_0 = 1.0f + b_00; nfc->gain = nfc->base_gain * g_0; nfc->b1 = 2.0f * b_00 / g_0; @@ -92,13 +87,13 @@ void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept { - NfcFilter2 nfc{}; + auto nfc = NfcFilter2{}; /* Calculate bass-cut coefficients. */ - float r{0.5f * w1}; - float b_10{B[2][0] * r}; - float b_11{B[2][1] * r * r}; - float g_1{1.0f + b_10 + b_11}; + auto r = 0.5f * w1; + auto b_10 = B2[0] * r; + auto b_11 = B2[1] * (r*r); + auto g_1 = 1.0f + b_10 + b_11; nfc.base_gain = 1.0f / g_1; nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -106,8 +101,8 @@ NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept /* Calculate bass-boost coefficients. */ r = 0.5f * w0; - b_10 = B[2][0] * r; - b_11 = B[2][1] * r * r; + b_10 = B2[0] * r; + b_11 = B2[1] * r * r; g_1 = 1.0f + b_10 + b_11; nfc.gain = nfc.base_gain * g_1; @@ -119,10 +114,10 @@ NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept { - const float r{0.5f * w0}; - const float b_10{B[2][0] * r}; - const float b_11{B[2][1] * r * r}; - const float g_1{1.0f + b_10 + b_11}; + const auto r = 0.5f * w0; + const auto b_10 = B2[0] * r; + const auto b_11 = B2[1] * (r*r); + const auto g_1 = 1.0f + b_10 + b_11; nfc->gain = nfc->base_gain * g_1; nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -132,15 +127,15 @@ void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept { - NfcFilter3 nfc{}; + auto nfc = NfcFilter3{}; /* Calculate bass-cut coefficients. */ - float r{0.5f * w1}; - float b_10{B[3][0] * r}; - float b_11{B[3][1] * r * r}; - float b_00{B[3][2] * r}; - float g_1{1.0f + b_10 + b_11}; - float g_0{1.0f + b_00}; + auto r = 0.5f * w1; + auto b_10 = B3[0] * r; + auto b_11 = B3[1] * (r*r); + auto b_00 = B3[2] * r; + auto g_1 = 1.0f + b_10 + b_11; + auto g_0 = 1.0f + b_00; nfc.base_gain = 1.0f / (g_1 * g_0); nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -149,9 +144,9 @@ NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept /* Calculate bass-boost coefficients. */ r = 0.5f * w0; - b_10 = B[3][0] * r; - b_11 = B[3][1] * r * r; - b_00 = B[3][2] * r; + b_10 = B3[0] * r; + b_11 = B3[1] * (r*r); + b_00 = B3[2] * r; g_1 = 1.0f + b_10 + b_11; g_0 = 1.0f + b_00; @@ -165,12 +160,12 @@ NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept { - const float r{0.5f * w0}; - const float b_10{B[3][0] * r}; - const float b_11{B[3][1] * r * r}; - const float b_00{B[3][2] * r}; - const float g_1{1.0f + b_10 + b_11}; - const float g_0{1.0f + b_00}; + const auto r = 0.5f * w0; + const auto b_10 = B3[0] * r; + const auto b_11 = B3[1] * (r*r); + const auto b_00 = B3[2] * r; + const auto g_1 = 1.0f + b_10 + b_11; + const auto g_0 = 1.0f + b_00; nfc->gain = nfc->base_gain * (g_1 * g_0); nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -181,16 +176,16 @@ void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept { - NfcFilter4 nfc{}; + auto nfc = NfcFilter4{}; /* Calculate bass-cut coefficients. */ - float r{0.5f * w1}; - float b_10{B[4][0] * r}; - float b_11{B[4][1] * r * r}; - float b_00{B[4][2] * r}; - float b_01{B[4][3] * r * r}; - float g_1{1.0f + b_10 + b_11}; - float g_0{1.0f + b_00 + b_01}; + auto r = 0.5f * w1; + auto b_10 = B4[0] * r; + auto b_11 = B4[1] * (r*r); + auto b_00 = B4[2] * r; + auto b_01 = B4[3] * (r*r); + auto g_1 = 1.0f + b_10 + b_11; + auto g_0 = 1.0f + b_00 + b_01; nfc.base_gain = 1.0f / (g_1 * g_0); nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -200,10 +195,10 @@ NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept /* Calculate bass-boost coefficients. */ r = 0.5f * w0; - b_10 = B[4][0] * r; - b_11 = B[4][1] * r * r; - b_00 = B[4][2] * r; - b_01 = B[4][3] * r * r; + b_10 = B4[0] * r; + b_11 = B4[1] * (r*r); + b_00 = B4[2] * r; + b_01 = B4[3] * (r*r); g_1 = 1.0f + b_10 + b_11; g_0 = 1.0f + b_00 + b_01; @@ -218,13 +213,13 @@ NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept { - const float r{0.5f * w0}; - const float b_10{B[4][0] * r}; - const float b_11{B[4][1] * r * r}; - const float b_00{B[4][2] * r}; - const float b_01{B[4][3] * r * r}; - const float g_1{1.0f + b_10 + b_11}; - const float g_0{1.0f + b_00 + b_01}; + const auto r = 0.5f * w0; + const auto b_10 = B4[0] * r; + const auto b_11 = B4[1] * (r*r); + const auto b_00 = B4[2] * r; + const auto b_01 = B4[3] * (r*r); + const auto g_1 = 1.0f + b_10 + b_11; + const auto g_0 = 1.0f + b_00 + b_01; nfc->gain = nfc->base_gain * (g_1 * g_0); nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; diff --git a/Engine/lib/openal-soft/core/filters/splitter.h b/Engine/lib/openal-soft/core/filters/splitter.h index e3658c19a..fdbf07f74 100644 --- a/Engine/lib/openal-soft/core/filters/splitter.h +++ b/Engine/lib/openal-soft/core/filters/splitter.h @@ -17,7 +17,7 @@ class BandSplitterR { public: BandSplitterR() = default; BandSplitterR(const BandSplitterR&) = default; - BandSplitterR(Real f0norm) { init(f0norm); } + explicit BandSplitterR(Real f0norm) { init(f0norm); } BandSplitterR& operator=(const BandSplitterR&) = default; void init(Real f0norm); diff --git a/Engine/lib/openal-soft/core/fmt_traits.cpp b/Engine/lib/openal-soft/core/fmt_traits.cpp deleted file mode 100644 index 9d79287db..000000000 --- a/Engine/lib/openal-soft/core/fmt_traits.cpp +++ /dev/null @@ -1,79 +0,0 @@ - -#include "config.h" - -#include "fmt_traits.h" - - -namespace al { - -const std::array muLawDecompressionTable{{ - -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, - -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, - -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, - -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316, - -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, - -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, - -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, - -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, - -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, - -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, - -876, -844, -812, -780, -748, -716, -684, -652, - -620, -588, -556, -524, -492, -460, -428, -396, - -372, -356, -340, -324, -308, -292, -276, -260, - -244, -228, -212, -196, -180, -164, -148, -132, - -120, -112, -104, -96, -88, -80, -72, -64, - -56, -48, -40, -32, -24, -16, -8, 0, - 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, - 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, - 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, - 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, - 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, - 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, - 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, - 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, - 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, - 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, - 876, 844, 812, 780, 748, 716, 684, 652, - 620, 588, 556, 524, 492, 460, 428, 396, - 372, 356, 340, 324, 308, 292, 276, 260, - 244, 228, 212, 196, 180, 164, 148, 132, - 120, 112, 104, 96, 88, 80, 72, 64, - 56, 48, 40, 32, 24, 16, 8, 0 -}}; - -const std::array aLawDecompressionTable{{ - -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, - -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, - -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, - -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, - -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944, - -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136, - -11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472, - -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568, - -344, -328, -376, -360, -280, -264, -312, -296, - -472, -456, -504, -488, -408, -392, -440, -424, - -88, -72, -120, -104, -24, -8, -56, -40, - -216, -200, -248, -232, -152, -136, -184, -168, - -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, - -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, - -688, -656, -752, -720, -560, -528, -624, -592, - -944, -912, -1008, -976, -816, -784, -880, -848, - 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, - 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, - 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, - 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, - 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, - 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, - 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, - 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, - 344, 328, 376, 360, 280, 264, 312, 296, - 472, 456, 504, 488, 408, 392, 440, 424, - 88, 72, 120, 104, 24, 8, 56, 40, - 216, 200, 248, 232, 152, 136, 184, 168, - 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, - 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, - 688, 656, 752, 720, 560, 528, 624, 592, - 944, 912, 1008, 976, 816, 784, 880, 848 -}}; - -} // namespace al diff --git a/Engine/lib/openal-soft/core/fmt_traits.h b/Engine/lib/openal-soft/core/fmt_traits.h index e87ea57ce..b16f4a3d1 100644 --- a/Engine/lib/openal-soft/core/fmt_traits.h +++ b/Engine/lib/openal-soft/core/fmt_traits.h @@ -9,8 +9,75 @@ namespace al { -extern const std::array muLawDecompressionTable; -extern const std::array aLawDecompressionTable; +inline constexpr auto muLawDecompressionTable = std::array{{ + -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, + -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, + -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, + -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, 0, + 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, + 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, + 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, + 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, + 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, + 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, + 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, + 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, + 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, + 876, 844, 812, 780, 748, 716, 684, 652, + 620, 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, 260, + 244, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0 +}}; + +inline constexpr auto aLawDecompressionTable = std::array{{ + -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, + -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, + -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, + -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, + -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944, + -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136, + -11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472, + -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568, + -344, -328, -376, -360, -280, -264, -312, -296, + -472, -456, -504, -488, -408, -392, -440, -424, + -88, -72, -120, -104, -24, -8, -56, -40, + -216, -200, -248, -232, -152, -136, -184, -168, + -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, + -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, + -688, -656, -752, -720, -560, -528, -624, -592, + -944, -912, -1008, -976, -816, -784, -880, -848, + 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, + 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, + 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, + 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, + 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, + 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, + 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, + 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, + 344, 328, 376, 360, 280, 264, 312, 296, + 472, 456, 504, 488, 408, 392, 440, 424, + 88, 72, 120, 104, 24, 8, 56, 40, + 216, 200, 248, 232, 152, 136, 184, 168, + 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, + 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, + 688, 656, 752, 720, 560, 528, 624, 592, + 944, 912, 1008, 976, 816, 784, 880, 848 +}}; template diff --git a/Engine/lib/openal-soft/core/fpu_ctrl.cpp b/Engine/lib/openal-soft/core/fpu_ctrl.cpp index 28e60c042..7a4f147fa 100644 --- a/Engine/lib/openal-soft/core/fpu_ctrl.cpp +++ b/Engine/lib/openal-soft/core/fpu_ctrl.cpp @@ -1,24 +1,27 @@ #include "config.h" +#include "config_simd.h" #include "fpu_ctrl.h" #ifdef HAVE_INTRIN_H #include #endif -#ifdef HAVE_SSE_INTRINSICS +#if HAVE_SSE_INTRINSICS #include -#elif defined(HAVE_SSE) +#elif HAVE_SSE #include #endif -#if defined(HAVE_SSE) && !defined(_MM_DENORMALS_ZERO_MASK) +#if HAVE_SSE && !defined(_MM_DENORMALS_ZERO_MASK) /* Some headers seem to be missing these? */ #define _MM_DENORMALS_ZERO_MASK 0x0040u #define _MM_DENORMALS_ZERO_ON 0x0040u #endif +#if !HAVE_SSE_INTRINSICS && HAVE_SSE #include "cpu_caps.h" +#endif namespace { @@ -28,14 +31,14 @@ namespace { [[maybe_unused]] void disable_denormals(unsigned int *state [[maybe_unused]]) { -#if defined(HAVE_SSE_INTRINSICS) +#if HAVE_SSE_INTRINSICS *state = _mm_getcsr(); unsigned int sseState{*state}; sseState &= ~(_MM_FLUSH_ZERO_MASK | _MM_DENORMALS_ZERO_MASK); sseState |= _MM_FLUSH_ZERO_ON | _MM_DENORMALS_ZERO_ON; _mm_setcsr(sseState); -#elif defined(HAVE_SSE) +#elif HAVE_SSE *state = _mm_getcsr(); unsigned int sseState{*state}; @@ -56,7 +59,7 @@ void disable_denormals(unsigned int *state [[maybe_unused]]) [[maybe_unused]] void reset_fpu(unsigned int state [[maybe_unused]]) { -#if defined(HAVE_SSE_INTRINSICS) || defined(HAVE_SSE) +#if HAVE_SSE_INTRINSICS || HAVE_SSE _mm_setcsr(state); #endif } @@ -67,9 +70,9 @@ void reset_fpu(unsigned int state [[maybe_unused]]) unsigned int FPUCtl::Set() noexcept { unsigned int state{}; -#if defined(HAVE_SSE_INTRINSICS) +#if HAVE_SSE_INTRINSICS disable_denormals(&state); -#elif defined(HAVE_SSE) +#elif HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) disable_denormals(&state); #endif @@ -78,9 +81,9 @@ unsigned int FPUCtl::Set() noexcept void FPUCtl::Reset(unsigned int state [[maybe_unused]]) noexcept { -#if defined(HAVE_SSE_INTRINSICS) +#if HAVE_SSE_INTRINSICS reset_fpu(state); -#elif defined(HAVE_SSE) +#elif HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) reset_fpu(state); #endif diff --git a/Engine/lib/openal-soft/core/front_stablizer.h b/Engine/lib/openal-soft/core/front_stablizer.h index 8eeb6d747..62652699d 100644 --- a/Engine/lib/openal-soft/core/front_stablizer.h +++ b/Engine/lib/openal-soft/core/front_stablizer.h @@ -11,7 +11,7 @@ struct FrontStablizer { - FrontStablizer(size_t numchans) : ChannelFilters{numchans} { } + explicit FrontStablizer(size_t numchans) : ChannelFilters{numchans} { } alignas(16) std::array MidDirect{}; alignas(16) std::array Side{}; diff --git a/Engine/lib/openal-soft/core/helpers.cpp b/Engine/lib/openal-soft/core/helpers.cpp index 5e973bf28..96fca1065 100644 --- a/Engine/lib/openal-soft/core/helpers.cpp +++ b/Engine/lib/openal-soft/core/helpers.cpp @@ -11,17 +11,18 @@ #include #include #include -#include #include #include #include #include +#include #include #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "alstring.h" +#include "filesystem.h" #include "logging.h" #include "strutils.h" @@ -32,11 +33,9 @@ using namespace std::string_view_literals; std::mutex gSearchLock; -void DirectorySearch(const std::filesystem::path &path, const std::string_view ext, +void DirectorySearch(const fs::path &path, const std::string_view ext, std::vector *const results) { - namespace fs = std::filesystem; - const auto base = results->size(); try { @@ -44,26 +43,28 @@ void DirectorySearch(const std::filesystem::path &path, const std::string_view e if(!fs::exists(fpath)) return; - TRACE("Searching %s for *%.*s\n", fpath.u8string().c_str(), al::sizei(ext), ext.data()); + TRACE("Searching {} for *{}", al::u8_as_char(fpath.u8string()), ext); for(auto&& dirent : fs::directory_iterator{fpath}) { auto&& entrypath = dirent.path(); if(!entrypath.has_extension()) continue; - if(fs::status(entrypath).type() == fs::file_type::regular - && al::case_compare(entrypath.extension().u8string(), ext) == 0) - results->emplace_back(entrypath.u8string()); + if(fs::status(entrypath).type() != fs::file_type::regular) + continue; + const auto u8ext = entrypath.extension().u8string(); + if(al::case_compare(al::u8_as_char(u8ext), ext) == 0) + results->emplace_back(al::u8_as_char(entrypath.u8string())); } } catch(std::exception& e) { - ERR("Exception enumerating files: %s\n", e.what()); + ERR("Exception enumerating files: {}", e.what()); } const auto newlist = al::span{*results}.subspan(base); std::sort(newlist.begin(), newlist.end()); for(const auto &name : newlist) - TRACE(" got %s\n", name.c_str()); + TRACE(" got {}", name); } } // namespace @@ -77,7 +78,7 @@ const PathNamePair &GetProcBinary() { auto get_procbin = [] { -#if !defined(ALSOFT_UWP) +#if !ALSOFT_UWP DWORD pathlen{256}; auto fullpath = std::wstring(pathlen, L'\0'); DWORD len{GetModuleFileNameW(nullptr, fullpath.data(), pathlen)}; @@ -95,16 +96,22 @@ const PathNamePair &GetProcBinary() } if(len == 0) { - ERR("Failed to get process name: error %lu\n", GetLastError()); + ERR("Failed to get process name: error {}", GetLastError()); return PathNamePair{}; } fullpath.resize(len); #else + if(__argc < 1 || !__wargv) + { + ERR("Failed to get process name: __argc = {}, __wargv = {}", __argc, + static_cast(__wargv)); + return PathNamePair{}; + } const WCHAR *exePath{__wargv[0]}; if(!exePath) { - ERR("Failed to get process name: __wargv[0] == nullptr\n"); + ERR("Failed to get process name: __wargv[0] == nullptr"); return PathNamePair{}; } std::wstring fullpath{exePath}; @@ -120,7 +127,7 @@ const PathNamePair &GetProcBinary() else res.fname = wstr_to_utf8(fullpath); - TRACE("Got binary: %s, %s\n", res.path.c_str(), res.fname.c_str()); + TRACE("Got binary: {}, {}", res.path, res.fname); return res; }; static const PathNamePair procbin{get_procbin()}; @@ -129,7 +136,7 @@ const PathNamePair &GetProcBinary() namespace { -#if !defined(ALSOFT_UWP) && !defined(_GAMING_XBOX) +#if !ALSOFT_UWP && !defined(_GAMING_XBOX) struct CoTaskMemDeleter { void operator()(void *mem) const { CoTaskMemFree(mem); } }; @@ -137,26 +144,35 @@ struct CoTaskMemDeleter { } // namespace -std::vector SearchDataFiles(const std::string_view ext, const std::string_view subdir) +auto SearchDataFiles(const std::string_view ext) -> std::vector +{ + auto srchlock = std::lock_guard{gSearchLock}; + + /* Search the app-local directory. */ + auto results = std::vector{}; + if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH")) + DirectorySearch(*localpath, ext, &results); + else if(auto curpath = fs::current_path(); !curpath.empty()) + DirectorySearch(curpath, ext, &results); + + return results; +} + +auto SearchDataFiles(const std::string_view ext, const std::string_view subdir) + -> std::vector { std::lock_guard srchlock{gSearchLock}; /* If the path is absolute, use it directly. */ std::vector results; - auto path = std::filesystem::u8path(subdir); + auto path = fs::u8path(subdir); if(path.is_absolute()) { DirectorySearch(path, ext, &results); return results; } - /* Search the app-local directory. */ - if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH")) - DirectorySearch(*localpath, ext, &results); - else if(auto curpath = std::filesystem::current_path(); !curpath.empty()) - DirectorySearch(curpath, ext, &results); - -#if !defined(ALSOFT_UWP) && !defined(_GAMING_XBOX) +#if !ALSOFT_UWP && !defined(_GAMING_XBOX) /* Search the local and global data dirs. */ for(const auto &folderid : std::array{FOLDERID_RoamingAppData, FOLDERID_ProgramData}) { @@ -166,7 +182,7 @@ std::vector SearchDataFiles(const std::string_view ext, const std:: if(FAILED(hr) || !buffer || !*buffer) continue; - DirectorySearch(std::filesystem::path{buffer.get()}/path, ext, &results); + DirectorySearch(fs::path{buffer.get()}/path, ext, &results); } #endif @@ -175,11 +191,11 @@ std::vector SearchDataFiles(const std::string_view ext, const std:: void SetRTPriority() { -#if !defined(ALSOFT_UWP) +#if !ALSOFT_UWP if(RTPrioLevel > 0) { if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) - ERR("Failed to set priority level for thread\n"); + ERR("Failed to set priority level for thread"); } #endif } @@ -202,7 +218,7 @@ void SetRTPriority() #include #include #endif -#ifdef HAVE_RTKIT +#if HAVE_RTKIT #include #include "dbus_wrap.h" @@ -221,8 +237,8 @@ const PathNamePair &GetProcBinary() size_t pathlen{}; std::array mib{{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}}; if(sysctl(mib.data(), mib.size(), nullptr, &pathlen, nullptr, 0) == -1) - WARN("Failed to sysctl kern.proc.pathname: %s\n", - std::generic_category().message(errno).c_str()); + WARN("Failed to sysctl kern.proc.pathname: {}", + std::generic_category().message(errno)); else { auto procpath = std::vector(pathlen+1, '\0'); @@ -236,8 +252,8 @@ const PathNamePair &GetProcBinary() std::array procpath{}; const pid_t pid{getpid()}; if(proc_pidpath(pid, procpath.data(), procpath.size()) < 1) - ERR("proc_pidpath(%d, ...) failed: %s\n", pid, - std::generic_category().message(errno).c_str()); + ERR("proc_pidpath({}, ...) failed: {}", pid, + std::generic_category().message(errno)); else pathname = procpath.data(); } @@ -263,17 +279,16 @@ const PathNamePair &GetProcBinary() for(const std::string_view name : SelfLinkNames) { try { - if(!std::filesystem::exists(name)) + if(!fs::exists(name)) continue; - if(auto path = std::filesystem::read_symlink(name); !path.empty()) + if(auto path = fs::read_symlink(name); !path.empty()) { - pathname = path.u8string(); + pathname = al::u8_as_char(path.u8string()); break; } } catch(std::exception& e) { - WARN("Exception getting symlink %.*s: %s\n", al::sizei(name), name.data(), - e.what()); + WARN("Exception getting symlink {}: {}", name, e.what()); } } } @@ -288,36 +303,45 @@ const PathNamePair &GetProcBinary() else res.fname = pathname; - TRACE("Got binary: \"%s\", \"%s\"\n", res.path.c_str(), res.fname.c_str()); + TRACE("Got binary: \"{}\", \"{}\"", res.path, res.fname); return res; }; static const PathNamePair procbin{get_procbin()}; return procbin; } -std::vector SearchDataFiles(const std::string_view ext, const std::string_view subdir) +auto SearchDataFiles(const std::string_view ext) -> std::vector +{ + auto srchlock = std::lock_guard{gSearchLock}; + + /* Search the app-local directory. */ + auto results = std::vector{}; + if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH")) + DirectorySearch(*localpath, ext, &results); + else if(auto curpath = fs::current_path(); !curpath.empty()) + DirectorySearch(curpath, ext, &results); + + return results; +} + +auto SearchDataFiles(const std::string_view ext, const std::string_view subdir) + -> std::vector { std::lock_guard srchlock{gSearchLock}; std::vector results; - auto path = std::filesystem::u8path(subdir); + auto path = fs::u8path(subdir); if(path.is_absolute()) { DirectorySearch(path, ext, &results); return results; } - /* Search the app-local directory. */ - if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH")) - DirectorySearch(*localpath, ext, &results); - else if(auto curpath = std::filesystem::current_path(); !curpath.empty()) - DirectorySearch(curpath, ext, &results); - /* Search local data dir */ if(auto datapath = al::getenv("XDG_DATA_HOME")) - DirectorySearch(std::filesystem::path{*datapath}/path, ext, &results); + DirectorySearch(fs::path{*datapath}/path, ext, &results); else if(auto homepath = al::getenv("HOME")) - DirectorySearch(std::filesystem::path{*homepath}/".local/share"/path, ext, &results); + DirectorySearch(fs::path{*homepath}/".local/share"/path, ext, &results); /* Search global data dirs */ std::string datadirs{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")}; @@ -333,12 +357,12 @@ std::vector SearchDataFiles(const std::string_view ext, const std:: curpos = nextpos; if(!pathname.empty()) - DirectorySearch(std::filesystem::path{pathname}/path, ext, &results); + DirectorySearch(fs::path{pathname}/path, ext, &results); } #ifdef ALSOFT_INSTALL_DATADIR /* Search the installation data directory */ - if(auto instpath = std::filesystem::path{ALSOFT_INSTALL_DATADIR}; !instpath.empty()) + if(auto instpath = fs::path{ALSOFT_INSTALL_DATADIR}; !instpath.empty()) DirectorySearch(instpath/path, ext, &results); #endif @@ -368,24 +392,23 @@ bool SetRTPriorityPthread(int prio [[maybe_unused]]) err = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); if(err == 0) return true; #endif - WARN("pthread_setschedparam failed: %s (%d)\n", std::generic_category().message(err).c_str(), - err); + WARN("pthread_setschedparam failed: {} ({})", std::generic_category().message(err), err); return false; } bool SetRTPriorityRTKit(int prio [[maybe_unused]]) { -#ifdef HAVE_RTKIT +#if HAVE_RTKIT if(!HasDBus()) { - WARN("D-Bus not available\n"); + WARN("D-Bus not available"); return false; } dbus::Error error; dbus::ConnectionPtr conn{dbus_bus_get(DBUS_BUS_SYSTEM, &error.get())}; if(!conn) { - WARN("D-Bus connection failed with %s: %s\n", error->name, error->message); + WARN("D-Bus connection failed with {}: {}", error->name, error->message); return false; } @@ -397,11 +420,11 @@ bool SetRTPriorityRTKit(int prio [[maybe_unused]]) if(err == -ENOENT) { err = std::abs(err); - ERR("Could not query RTKit: %s (%d)\n", std::generic_category().message(err).c_str(), err); + ERR("Could not query RTKit: {} ({})", std::generic_category().message(err), err); return false; } int rtmax{rtkit_get_max_realtime_priority(conn.get())}; - TRACE("Maximum real-time priority: %d, minimum niceness: %d\n", rtmax, nicemin); + TRACE("Maximum real-time priority: {}, minimum niceness: {}", rtmax, nicemin); auto limit_rttime = [](DBusConnection *c) -> int { @@ -414,8 +437,7 @@ bool SetRTPriorityRTKit(int prio [[maybe_unused]]) if(getrlimit(RLIMIT_RTTIME, &rlim) != 0) return errno; - TRACE("RTTime max: %llu (hard: %llu, soft: %llu)\n", umaxtime, - static_cast(rlim.rlim_max), static_cast(rlim.rlim_cur)); + TRACE("RTTime max: {} (hard: {}, soft: {})", umaxtime, rlim.rlim_max, rlim.rlim_cur); if(rlim.rlim_max > umaxtime) { rlim.rlim_max = static_cast(std::min(umaxtime, @@ -432,21 +454,21 @@ bool SetRTPriorityRTKit(int prio [[maybe_unused]]) { err = limit_rttime(conn.get()); if(err != 0) - WARN("Failed to set RLIMIT_RTTIME for RTKit: %s (%d)\n", - std::generic_category().message(err).c_str(), err); + WARN("Failed to set RLIMIT_RTTIME for RTKit: {} ({})", + std::generic_category().message(err), err); } /* Limit the maximum real-time priority to half. */ rtmax = (rtmax+1)/2; prio = std::clamp(prio, 1, rtmax); - TRACE("Making real-time with priority %d (max: %d)\n", prio, rtmax); + TRACE("Making real-time with priority {} (max: {})", prio, rtmax); err = rtkit_make_realtime(conn.get(), 0, prio); if(err == 0) return true; err = std::abs(err); - WARN("Failed to set real-time priority: %s (%d)\n", - std::generic_category().message(err).c_str(), err); + WARN("Failed to set real-time priority: {} ({})", + std::generic_category().message(err), err); } /* Don't try to set the niceness for non-Linux systems. Standard POSIX has * niceness as a per-process attribute, while the intent here is for the @@ -456,19 +478,18 @@ bool SetRTPriorityRTKit(int prio [[maybe_unused]]) #ifdef __linux__ if(nicemin < 0) { - TRACE("Making high priority with niceness %d\n", nicemin); + TRACE("Making high priority with niceness {}", nicemin); err = rtkit_make_high_priority(conn.get(), 0, nicemin); if(err == 0) return true; err = std::abs(err); - WARN("Failed to set high priority: %s (%d)\n", - std::generic_category().message(err).c_str(), err); + WARN("Failed to set high priority: {} ({})", std::generic_category().message(err), err); } #endif /* __linux__ */ #else - WARN("D-Bus not supported\n"); + WARN("D-Bus not supported"); #endif return false; } diff --git a/Engine/lib/openal-soft/core/helpers.h b/Engine/lib/openal-soft/core/helpers.h index 3a987c9d1..96987a2e8 100644 --- a/Engine/lib/openal-soft/core/helpers.h +++ b/Engine/lib/openal-soft/core/helpers.h @@ -19,6 +19,8 @@ inline bool AllowRTTimeLimit{true}; void SetRTPriority(); -std::vector SearchDataFiles(const std::string_view ext, const std::string_view subdir); +auto SearchDataFiles(const std::string_view ext) -> std::vector; +auto SearchDataFiles(const std::string_view ext, const std::string_view subdir) + -> std::vector; #endif /* CORE_HELPERS_H */ diff --git a/Engine/lib/openal-soft/core/hrtf.cpp b/Engine/lib/openal-soft/core/hrtf.cpp index e2f0d893d..ccd0a11a6 100644 --- a/Engine/lib/openal-soft/core/hrtf.cpp +++ b/Engine/lib/openal-soft/core/hrtf.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -31,7 +30,9 @@ #include "alspan.h" #include "alstring.h" #include "ambidefs.h" +#include "filesystem.h" #include "filters/splitter.h" +#include "fmt/core.h" #include "helpers.h" #include "logging.h" #include "mixer/hrtfdefs.h" @@ -103,11 +104,16 @@ constexpr uint MaxSampleRate{0xff'ff'ff}; static_assert(MaxHrirDelay*HrirDelayFracOne < 256, "MAX_HRIR_DELAY or DELAY_FRAC too large"); +constexpr auto HeaderMarkerSize = 8_uz; [[nodiscard]] constexpr auto GetMarker00Name() noexcept { return "MinPHR00"sv; } [[nodiscard]] constexpr auto GetMarker01Name() noexcept { return "MinPHR01"sv; } [[nodiscard]] constexpr auto GetMarker02Name() noexcept { return "MinPHR02"sv; } [[nodiscard]] constexpr auto GetMarker03Name() noexcept { return "MinPHR03"sv; } +static_assert(GetMarker00Name().size() == HeaderMarkerSize); +static_assert(GetMarker01Name().size() == HeaderMarkerSize); +static_assert(GetMarker02Name().size() == HeaderMarkerSize); +static_assert(GetMarker03Name().size() == HeaderMarkerSize); /* First value for pass-through coefficients (remaining are 0), used for omni- * directional sounds. */ @@ -176,7 +182,7 @@ class databuf final : public std::streambuf { } public: - databuf(const al::span data) noexcept + explicit databuf(const al::span data) noexcept { setg(data.data(), data.data(), al::to_address(data.end())); } @@ -187,7 +193,7 @@ class idstream final : public std::istream { databuf mStreamBuf; public: - idstream(const al::span data) : std::istream{nullptr}, mStreamBuf{data} + explicit idstream(const al::span data) : std::istream{nullptr}, mStreamBuf{data} { init(&mStreamBuf); } }; @@ -198,10 +204,9 @@ 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::inv_pi_v; - uint idx{float2uint(ev)}; + ev = (al::numbers::inv_pi_v*ev + 0.5f) * static_cast(evcount-1); + const auto idx = float2uint(ev); return IdxBlend{std::min(idx, evcount-1u), ev-static_cast(idx)}; } @@ -210,10 +215,9 @@ 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::inv_pi_v*0.5f); - uint idx{float2uint(az)}; + az = (al::numbers::inv_pi_v*0.5f*az + 1.0f) * static_cast(azcount); + const auto idx = float2uint(az); return IdxBlend{idx%azcount, az-static_cast(idx)}; } @@ -348,7 +352,7 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool auto hrir_delay_round = [](const uint d) noexcept -> uint { return (d+HrirDelayFracHalf) >> HrirDelayFracBits; }; - TRACE("Min delay: %.2f, max delay: %.2f, FIR length: %u\n", + TRACE("Min delay: {:.2f}, max delay: {:.2f}, FIR length: {}", min_delay/double{HrirDelayFracOne}, max_delay/double{HrirDelayFracOne}, irSize); auto tmpres = std::vector>(mChannels.size()); @@ -389,7 +393,7 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool tmpres.clear(); const uint max_length{std::min(hrir_delay_round(max_delay) + irSize, HrirLength)}; - TRACE("New max delay: %.2f, FIR length: %u\n", max_delay/double{HrirDelayFracOne}, + TRACE("New max delay: {:.2f}, FIR length: {}", max_delay/double{HrirDelayFracOne}, max_length); mIrSize = max_length; } @@ -544,13 +548,13 @@ std::unique_ptr LoadHrtf00(std::istream &data) if(irSize < MinIrLength || irSize > HrirLength) { - ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength); + ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength); return nullptr; } if(evCount < MinEvCount || evCount > MaxEvCount) { - ERR("Unsupported elevation count: evCount=%d (%d to %d)\n", - evCount, MinEvCount, MaxEvCount); + ERR("Unsupported elevation count: evCount={} ({} to {})", evCount, MinEvCount, + MaxEvCount); return nullptr; } @@ -564,15 +568,15 @@ std::unique_ptr LoadHrtf00(std::istream &data) { if(elevs[i].irOffset <= elevs[i-1].irOffset) { - ERR("Invalid evOffset: evOffset[%zu]=%d (last=%d)\n", i, elevs[i].irOffset, + ERR("Invalid evOffset: evOffset[{}]={} (last={})", i, elevs[i].irOffset, elevs[i-1].irOffset); return nullptr; } } if(irCount <= elevs.back().irOffset) { - ERR("Invalid evOffset: evOffset[%zu]=%d (irCount=%d)\n", - elevs.size()-1, elevs.back().irOffset, irCount); + ERR("Invalid evOffset: evOffset[{}]={} (irCount={})", elevs.size()-1, + elevs.back().irOffset, irCount); return nullptr; } @@ -581,16 +585,16 @@ std::unique_ptr LoadHrtf00(std::istream &data) elevs[i-1].azCount = static_cast(elevs[i].irOffset - elevs[i-1].irOffset); if(elevs[i-1].azCount < MinAzCount || elevs[i-1].azCount > MaxAzCount) { - ERR("Unsupported azimuth count: azCount[%zd]=%d (%d to %d)\n", - i-1, elevs[i-1].azCount, MinAzCount, MaxAzCount); + ERR("Unsupported azimuth count: azCount[{}]={} ({} to {})", i-1, elevs[i-1].azCount, + MinAzCount, MaxAzCount); return nullptr; } } elevs.back().azCount = static_cast(irCount - elevs.back().irOffset); if(elevs.back().azCount < MinAzCount || elevs.back().azCount > MaxAzCount) { - ERR("Unsupported azimuth count: azCount[%zu]=%d (%d to %d)\n", - elevs.size()-1, elevs.back().azCount, MinAzCount, MaxAzCount); + ERR("Unsupported azimuth count: azCount[{}]={} ({} to {})", elevs.size()-1, + elevs.back().azCount, MinAzCount, MaxAzCount); return nullptr; } @@ -610,7 +614,7 @@ std::unique_ptr LoadHrtf00(std::istream &data) { if(delays[i][0] > MaxHrirDelay) { - ERR("Invalid delays[%zd]: %d (%d)\n", i, delays[i][0], MaxHrirDelay); + ERR("Invalid delays[{}]: {} ({})", i, delays[i][0], MaxHrirDelay); return nullptr; } delays[i][0] <<= HrirDelayFracBits; @@ -634,13 +638,13 @@ std::unique_ptr LoadHrtf01(std::istream &data) if(irSize < MinIrLength || irSize > HrirLength) { - ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength); + ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength); return nullptr; } if(evCount < MinEvCount || evCount > MaxEvCount) { - ERR("Unsupported elevation count: evCount=%d (%d to %d)\n", - evCount, MinEvCount, MaxEvCount); + ERR("Unsupported elevation count: evCount={} ({} to {})", evCount, MinEvCount, + MaxEvCount); return nullptr; } @@ -654,7 +658,7 @@ std::unique_ptr LoadHrtf01(std::istream &data) { if(elevs[i].azCount < MinAzCount || elevs[i].azCount > MaxAzCount) { - ERR("Unsupported azimuth count: azCount[%zd]=%d (%d to %d)\n", i, elevs[i].azCount, + ERR("Unsupported azimuth count: azCount[{}]={} ({} to {})", i, elevs[i].azCount, MinAzCount, MaxAzCount); return nullptr; } @@ -681,7 +685,7 @@ std::unique_ptr LoadHrtf01(std::istream &data) { if(delays[i][0] > MaxHrirDelay) { - ERR("Invalid delays[%zd]: %d (%d)\n", i, delays[i][0], MaxHrirDelay); + ERR("Invalid delays[{}]: {} ({})", i, delays[i][0], MaxHrirDelay); return nullptr; } delays[i][0] <<= HrirDelayFracBits; @@ -711,23 +715,23 @@ std::unique_ptr LoadHrtf02(std::istream &data) if(sampleType > SampleType_S24) { - ERR("Unsupported sample type: %d\n", sampleType); + ERR("Unsupported sample type: {}", sampleType); return nullptr; } if(channelType > ChanType_LeftRight) { - ERR("Unsupported channel type: %d\n", channelType); + ERR("Unsupported channel type: {}", channelType); return nullptr; } if(irSize < MinIrLength || irSize > HrirLength) { - ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength); + ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength); return nullptr; } if(fdCount < 1 || fdCount > MaxFdCount) { - ERR("Unsupported number of field-depths: fdCount=%d (%d to %d)\n", fdCount, MinFdCount, + ERR("Unsupported number of field-depths: fdCount={} ({} to {})", fdCount, MinFdCount, MaxFdCount); return nullptr; } @@ -743,13 +747,13 @@ std::unique_ptr LoadHrtf02(std::istream &data) if(distance < MinFdDistance || distance > MaxFdDistance) { - ERR("Unsupported field distance[%zu]=%d (%d to %d millimeters)\n", f, distance, + ERR("Unsupported field distance[{}]={} ({} to {} millimeters)", f, distance, MinFdDistance, MaxFdDistance); return nullptr; } if(evCount < MinEvCount || evCount > MaxEvCount) { - ERR("Unsupported elevation count: evCount[%zu]=%d (%d to %d)\n", f, evCount, + ERR("Unsupported elevation count: evCount[{}]={} ({} to {})", f, evCount, MinEvCount, MaxEvCount); return nullptr; } @@ -758,7 +762,7 @@ std::unique_ptr LoadHrtf02(std::istream &data) fields[f].evCount = evCount; if(f > 0 && fields[f].distance <= fields[f-1].distance) { - ERR("Field distance[%zu] is not after previous (%f > %f)\n", f, fields[f].distance, + ERR("Field distance[{}] is not after previous ({:f} > {:f})", f, fields[f].distance, fields[f-1].distance); return nullptr; } @@ -774,7 +778,7 @@ std::unique_ptr LoadHrtf02(std::istream &data) { if(elevs[ebase+e].azCount < MinAzCount || elevs[ebase+e].azCount > MaxAzCount) { - ERR("Unsupported azimuth count: azCount[%zu][%zu]=%d (%d to %d)\n", f, e, + ERR("Unsupported azimuth count: azCount[{}][{}]={} ({} to {})", f, e, elevs[ebase+e].azCount, MinAzCount, MaxAzCount); return nullptr; } @@ -820,7 +824,7 @@ std::unique_ptr LoadHrtf02(std::istream &data) { if(delays[i][0] > MaxHrirDelay) { - ERR("Invalid delays[%zu][0]: %d (%d)\n", i, delays[i][0], MaxHrirDelay); + ERR("Invalid delays[{}][0]: {} ({})", i, delays[i][0], MaxHrirDelay); return nullptr; } delays[i][0] <<= HrirDelayFracBits; @@ -865,12 +869,12 @@ std::unique_ptr LoadHrtf02(std::istream &data) { if(delays[i][0] > MaxHrirDelay) { - ERR("Invalid delays[%zu][0]: %d (%d)\n", i, delays[i][0], MaxHrirDelay); + ERR("Invalid delays[{}][0]: {} ({})", i, delays[i][0], MaxHrirDelay); return nullptr; } if(delays[i][1] > MaxHrirDelay) { - ERR("Invalid delays[%zu][1]: %d (%d)\n", i, delays[i][1], MaxHrirDelay); + ERR("Invalid delays[{}][1]: {} ({})", i, delays[i][1], MaxHrirDelay); return nullptr; } delays[i][0] <<= HrirDelayFracBits; @@ -963,18 +967,18 @@ std::unique_ptr LoadHrtf03(std::istream &data) if(channelType > ChanType_LeftRight) { - ERR("Unsupported channel type: %d\n", channelType); + ERR("Unsupported channel type: {}", channelType); return nullptr; } if(irSize < MinIrLength || irSize > HrirLength) { - ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength); + ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength); return nullptr; } if(fdCount < 1 || fdCount > MaxFdCount) { - ERR("Unsupported number of field-depths: fdCount=%d (%d to %d)\n", fdCount, MinFdCount, + ERR("Unsupported number of field-depths: fdCount={} ({} to {})", fdCount, MinFdCount, MaxFdCount); return nullptr; } @@ -990,13 +994,13 @@ std::unique_ptr LoadHrtf03(std::istream &data) if(distance < MinFdDistance || distance > MaxFdDistance) { - ERR("Unsupported field distance[%zu]=%d (%d to %d millimeters)\n", f, distance, + ERR("Unsupported field distance[{}]={} ({} to {} millimeters)", f, distance, MinFdDistance, MaxFdDistance); return nullptr; } if(evCount < MinEvCount || evCount > MaxEvCount) { - ERR("Unsupported elevation count: evCount[%zu]=%d (%d to %d)\n", f, evCount, + ERR("Unsupported elevation count: evCount[{}]={} ({} to {})", f, evCount, MinEvCount, MaxEvCount); return nullptr; } @@ -1005,8 +1009,8 @@ std::unique_ptr LoadHrtf03(std::istream &data) fields[f].evCount = evCount; if(f > 0 && fields[f].distance > fields[f-1].distance) { - ERR("Field distance[%zu] is not before previous (%f <= %f)\n", f, fields[f].distance, - fields[f-1].distance); + ERR("Field distance[{}] is not before previous ({:f} <= {:f})", f, + fields[f].distance, fields[f-1].distance); return nullptr; } @@ -1021,7 +1025,7 @@ std::unique_ptr LoadHrtf03(std::istream &data) { if(elevs[ebase+e].azCount < MinAzCount || elevs[ebase+e].azCount > MaxAzCount) { - ERR("Unsupported azimuth count: azCount[%zu][%zu]=%d (%d to %d)\n", f, e, + ERR("Unsupported azimuth count: azCount[{}][{}]={} ({} to {})", f, e, elevs[ebase+e].azCount, MinAzCount, MaxAzCount); return nullptr; } @@ -1056,8 +1060,8 @@ std::unique_ptr LoadHrtf03(std::istream &data) { if(delays[i][0] > MaxHrirDelay< LoadHrtf03(std::istream &data) { if(delays[i][0] > MaxHrirDelay< MaxHrirDelay< bool - { return entry.mFilename == filename; }); + [filename](const HrtfEntry &entry) -> bool { return entry.mFilename == filename; }); if(enum_iter != EnumeratedHrtfs.cend()) { - TRACE("Skipping duplicate file entry %.*s\n", al::sizei(filename), filename.data()); + TRACE("Skipping duplicate file entry {}", filename); return; } /* TODO: Get a human-readable name from the HRTF data (possibly coming in a - * format update). */ - size_t namepos{filename.rfind('/')+1}; - if(!namepos) namepos = filename.rfind('\\')+1; + * format update). + */ + const auto namepos = std::max(filename.rfind('/')+1, filename.rfind('\\')+1); + const auto extpos = filename.substr(namepos).rfind('.'); - size_t extpos{filename.rfind('.')}; - if(extpos <= namepos) extpos = std::string::npos; + const auto basename = (extpos == std::string::npos) ? + filename.substr(namepos) : filename.substr(namepos, extpos); - const std::string_view basename{(extpos == std::string::npos) ? - filename.substr(namepos) : filename.substr(namepos, extpos-namepos)}; - std::string newname{basename}; - int count{1}; + auto count = 1; + auto newname = std::string{basename}; while(checkName(newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - const HrtfEntry &entry = EnumeratedHrtfs.emplace_back(newname, filename); + newname = fmt::format("{} #{}", basename, ++count); - TRACE("Adding file entry \"%s\"\n", entry.mFilename.c_str()); + const auto &entry = EnumeratedHrtfs.emplace_back(newname, filename); + TRACE("Adding file entry \"{}\"", entry.mFilename); } /* Unfortunate that we have to duplicate AddFileEntry to take a memory buffer @@ -1151,32 +1149,26 @@ void AddFileEntry(const std::string_view filename) */ void AddBuiltInEntry(const std::string_view dispname, uint residx) { - std::string filename{'!'+std::to_string(residx)+'_'}; - filename += dispname; + auto filename = fmt::format("!{}_{}", residx, dispname); auto enum_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(), - [&filename](const HrtfEntry &entry) -> bool - { return entry.mFilename == filename; }); + [&filename](const HrtfEntry &entry) -> bool { return entry.mFilename == filename; }); if(enum_iter != EnumeratedHrtfs.cend()) { - TRACE("Skipping duplicate file entry %s\n", filename.c_str()); + TRACE("Skipping duplicate file entry {}", filename); return; } /* TODO: Get a human-readable name from the HRTF data (possibly coming in a * format update). */ - std::string newname{dispname}; - int count{1}; + auto count = 1; + auto newname = std::string{dispname}; while(checkName(newname)) - { - newname = dispname; - newname += " #"; - newname += std::to_string(++count); - } - const HrtfEntry &entry = EnumeratedHrtfs.emplace_back(std::move(newname), std::move(filename)); + newname = fmt::format("{} #{}", dispname, ++count); - TRACE("Adding built-in entry \"%s\"\n", entry.mFilename.c_str()); + const auto &entry = EnumeratedHrtfs.emplace_back(std::move(newname), std::move(filename)); + TRACE("Adding built-in entry \"{}\"", entry.mFilename); } @@ -1210,6 +1202,9 @@ std::vector EnumerateHrtf(std::optional pathopt) std::lock_guard enumlock{EnumeratedHrtfLock}; EnumeratedHrtfs.clear(); + for(const auto &fname : SearchDataFiles(".mhr"sv)) + AddFileEntry(fname); + bool usedefaults{true}; if(pathopt) { @@ -1262,7 +1257,7 @@ HrtfStorePtr GetLoadedHrtf(const std::string_view name, const uint devrate) try { if(devrate > MaxSampleRate) { - WARN("Device sample rate too large for HRTF (%uhz > %uhz)\n", devrate, MaxSampleRate); + WARN("Device sample rate too large for HRTF ({}hz > {}hz)", devrate, MaxSampleRate); return nullptr; } std::lock_guard enumlock{EnumeratedHrtfLock}; @@ -1292,13 +1287,14 @@ try { std::unique_ptr stream; int residx{}; char ch{}; + /* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */ if(sscanf(fname.c_str(), "!%d%c", &residx, &ch) == 2 && ch == '_') { - TRACE("Loading %s...\n", fname.c_str()); + TRACE("Loading {}...", fname); al::span res{GetResource(residx)}; if(res.empty()) { - ERR("Could not get resource %u, %.*s\n", residx, al::sizei(name), name.data()); + ERR("Could not get resource {}, {}", residx, name); return nullptr; } /* NOLINTNEXTLINE(*-const-cast) */ @@ -1306,44 +1302,44 @@ try { } else { - TRACE("Loading %s...\n", fname.c_str()); - auto fstr = std::make_unique(std::filesystem::u8path(fname), + TRACE("Loading {}...", fname); + auto fstr = std::make_unique(fs::u8path(fname), std::ios::binary); if(!fstr->is_open()) { - ERR("Could not open %s\n", fname.c_str()); + ERR("Could not open {}", fname); return nullptr; } stream = std::move(fstr); } - std::unique_ptr hrtf; - std::array magic{}; + auto hrtf = std::unique_ptr{}; + auto magic = std::array{}; stream->read(magic.data(), magic.size()); - if(stream->gcount() < static_cast(GetMarker03Name().size())) - ERR("%.*s data is too short (%zu bytes)\n", al::sizei(name),name.data(), stream->gcount()); + if(stream->gcount() < std::streamsize{magic.size()}) + ERR("{} data is too short ({} bytes)", name, stream->gcount()); else if(GetMarker03Name() == std::string_view{magic.data(), magic.size()}) { - TRACE("Detected data set format v3\n"); + TRACE("Detected data set format v3"); hrtf = LoadHrtf03(*stream); } else if(GetMarker02Name() == std::string_view{magic.data(), magic.size()}) { - TRACE("Detected data set format v2\n"); + TRACE("Detected data set format v2"); hrtf = LoadHrtf02(*stream); } else if(GetMarker01Name() == std::string_view{magic.data(), magic.size()}) { - TRACE("Detected data set format v1\n"); + TRACE("Detected data set format v1"); hrtf = LoadHrtf01(*stream); } else if(GetMarker00Name() == std::string_view{magic.data(), magic.size()}) { - TRACE("Detected data set format v0\n"); + TRACE("Detected data set format v0"); hrtf = LoadHrtf00(*stream); } else - ERR("Invalid header in %.*s: \"%.8s\"\n", al::sizei(name), name.data(), magic.data()); + ERR("Invalid header in {}: \"{}\"", name, std::string_view{magic.data(), magic.size()}); stream.reset(); if(!hrtf) @@ -1351,8 +1347,7 @@ try { if(hrtf->mSampleRate != devrate) { - TRACE("Resampling HRTF %.*s (%uhz -> %uhz)\n", al::sizei(name), name.data(), - hrtf->mSampleRate, devrate); + TRACE("Resampling HRTF {} ({}hz -> {}hz)", name, uint{hrtf->mSampleRate}, devrate); /* Calculate the last elevation's index and get the total IR count. */ const size_t lastEv{std::accumulate(hrtf->mFields.begin(), hrtf->mFields.end(), 0_uz, @@ -1402,7 +1397,7 @@ try { float delay_scale{HrirDelayFracOne}; if(max_delay > MaxHrirDelay) { - WARN("Resampled delay exceeds max (%.2f > %d)\n", max_delay, MaxHrirDelay); + WARN("Resampled delay exceeds max ({:.2f} > {})", max_delay, MaxHrirDelay); delay_scale *= float{MaxHrirDelay} / max_delay; } @@ -1424,13 +1419,13 @@ try { } handle = LoadedHrtfs.emplace(handle, fname, devrate, std::move(hrtf)); - TRACE("Loaded HRTF %.*s for sample rate %uhz, %u-sample filter\n", al::sizei(name),name.data(), - handle->mEntry->mSampleRate, handle->mEntry->mIrSize); + TRACE("Loaded HRTF {} for sample rate {}hz, {}-sample filter", name, + uint{handle->mEntry->mSampleRate}, uint{handle->mEntry->mIrSize}); return HrtfStorePtr{handle->mEntry.get()}; } catch(std::exception& e) { - ERR("Failed to load %.*s: %s\n", al::sizei(name), name.data(), e.what()); + ERR("Failed to load {}: {}", name, e.what()); return nullptr; } @@ -1438,13 +1433,13 @@ catch(std::exception& e) { void HrtfStore::add_ref() { auto ref = IncrementRef(mRef); - TRACE("HrtfStore %p increasing refcount to %u\n", decltype(std::declval()){this}, ref); + TRACE("HrtfStore {} increasing refcount to {}", decltype(std::declval()){this}, ref); } void HrtfStore::dec_ref() { auto ref = DecrementRef(mRef); - TRACE("HrtfStore %p decreasing refcount to %u\n", decltype(std::declval()){this}, ref); + TRACE("HrtfStore {} decreasing refcount to {}", decltype(std::declval()){this}, ref); if(ref == 0) { std::lock_guard loadlock{LoadedHrtfLock}; @@ -1455,7 +1450,7 @@ void HrtfStore::dec_ref() HrtfStore *entry{hrtf.mEntry.get()}; if(entry && entry->mRef.load() == 0) { - TRACE("Unloading unused HRTF %s\n", hrtf.mFilename.c_str()); + TRACE("Unloading unused HRTF {}", hrtf.mFilename); hrtf.mEntry = nullptr; return true; } diff --git a/Engine/lib/openal-soft/core/hrtf.h b/Engine/lib/openal-soft/core/hrtf.h index e93fddaab..020e3077a 100644 --- a/Engine/lib/openal-soft/core/hrtf.h +++ b/Engine/lib/openal-soft/core/hrtf.h @@ -11,7 +11,6 @@ #include "almalloc.h" #include "alspan.h" -#include "atomic.h" #include "ambidefs.h" #include "bufferline.h" #include "flexarray.h" @@ -20,7 +19,7 @@ struct alignas(16) HrtfStore { - std::atomic mRef; + std::atomic mRef{}; uint mSampleRate : 24; uint mIrSize : 8; @@ -75,7 +74,7 @@ struct DirectHrtfState { uint mIrSize{0}; al::FlexArray mChannels; - DirectHrtfState(size_t numchans) : mChannels{numchans} { } + explicit DirectHrtfState(size_t numchans) : mChannels{numchans} { } /** * Produces HRTF filter coefficients for decoding B-Format, given a set of * virtual speaker positions, a matching decoding matrix, and per-order diff --git a/Engine/lib/openal-soft/core/logging.cpp b/Engine/lib/openal-soft/core/logging.cpp index c0ff45c02..ba40b3686 100644 --- a/Engine/lib/openal-soft/core/logging.cpp +++ b/Engine/lib/openal-soft/core/logging.cpp @@ -3,7 +3,6 @@ #include "logging.h" -#include #include #include #include @@ -11,10 +10,10 @@ #include #include #include -#include +#include +#include -#include "alspan.h" -#include "opthelpers.h" +#include "alstring.h" #include "strutils.h" @@ -36,6 +35,8 @@ LogLevel gLogLevel{LogLevel::Error}; namespace { +using namespace std::string_view_literals; + enum class LogState : uint8_t { FirstRun, Ready, @@ -77,57 +78,23 @@ void al_set_log_callback(LogCallbackFunc callback, void *userptr) } } -void al_print(LogLevel level, const char *fmt, ...) noexcept -try { - /* Kind of ugly since string literals are const char arrays with a size - * that includes the null terminator, which we want to exclude from the - * span. - */ - auto prefix = al::span{"[ALSOFT] (--) "}.first<14>(); +void al_print_impl(LogLevel level, const fmt::string_view fmt, fmt::format_args args) +{ + const auto msg = fmt::vformat(fmt, std::move(args)); + + auto prefix = "[ALSOFT] (--) "sv; switch(level) { case LogLevel::Disable: break; - case LogLevel::Error: prefix = al::span{"[ALSOFT] (EE) "}.first<14>(); break; - case LogLevel::Warning: prefix = al::span{"[ALSOFT] (WW) "}.first<14>(); break; - case LogLevel::Trace: prefix = al::span{"[ALSOFT] (II) "}.first<14>(); break; + case LogLevel::Error: prefix = "[ALSOFT] (EE) "sv; break; + case LogLevel::Warning: prefix = "[ALSOFT] (WW) "sv; break; + case LogLevel::Trace: prefix = "[ALSOFT] (II) "sv; break; } - std::vector dynmsg; - std::array stcmsg{}; - - char *str{stcmsg.data()}; - auto prefend1 = std::copy_n(prefix.begin(), prefix.size(), stcmsg.begin()); - al::span msg{prefend1, stcmsg.end()}; - - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - std::va_list args, args2; - va_start(args, fmt); - va_copy(args2, args); - const int msglen{std::vsnprintf(msg.data(), msg.size(), fmt, args)}; - if(msglen >= 0) - { - if(static_cast(msglen) >= msg.size()) UNLIKELY - { - dynmsg.resize(static_cast(msglen)+prefix.size() + 1u); - - str = dynmsg.data(); - auto prefend2 = std::copy_n(prefix.begin(), prefix.size(), dynmsg.begin()); - msg = {prefend2, dynmsg.end()}; - - std::vsnprintf(msg.data(), msg.size(), fmt, args2); - } - msg = msg.first(static_cast(msglen)); - } - else - msg = {msg.data(), std::strlen(msg.data())}; - va_end(args2); - va_end(args); - /* NOLINTEND(*-array-to-pointer-decay) */ - if(gLogLevel >= level) { auto logfile = gLogFile; - fputs(str, logfile); + fmt::println(logfile, "{}{}", prefix, msg); fflush(logfile); } #if defined(_WIN32) && !defined(NDEBUG) @@ -135,8 +102,7 @@ try { * informational, warning, or error debug messages. So only print them for * non-Release builds. */ - std::wstring wstr{utf8_to_wstr(str)}; - OutputDebugStringW(wstr.c_str()); + OutputDebugStringW(utf8_to_wstr(fmt::format("{}{}\n", prefix, msg)).c_str()); #elif defined(__ANDROID__) auto android_severity = [](LogLevel l) noexcept { @@ -151,26 +117,20 @@ try { } return ANDROID_LOG_ERROR; }; - __android_log_print(android_severity(level), "openal", "%s", str); + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) */ + __android_log_print(android_severity(level), "openal", "%.*s%s", al::sizei(prefix), + prefix.data(), msg.c_str()); #endif auto cblock = std::lock_guard{LogCallbackMutex}; if(gLogState != LogState::Disable) { - while(!msg.empty() && std::isspace(msg.back())) - { - msg.back() = '\0'; - msg = msg.first(msg.size()-1); - } - if(auto logcode = GetLevelCode(level); logcode && !msg.empty()) + if(auto logcode = GetLevelCode(level)) { if(gLogCallback) - gLogCallback(gLogCallbackPtr, *logcode, msg.data(), static_cast(msg.size())); + gLogCallback(gLogCallbackPtr, *logcode, msg.data(), al::sizei(msg)); else if(gLogState == LogState::FirstRun) gLogState = LogState::Disable; } } } -catch(...) { - /* Swallow any exceptions */ -} diff --git a/Engine/lib/openal-soft/core/logging.h b/Engine/lib/openal-soft/core/logging.h index 527e79540..f32fd43e8 100644 --- a/Engine/lib/openal-soft/core/logging.h +++ b/Engine/lib/openal-soft/core/logging.h @@ -3,6 +3,9 @@ #include +#include "fmt/core.h" +#include "opthelpers.h" + enum class LogLevel { Disable, @@ -10,9 +13,9 @@ enum class LogLevel { Warning, Trace }; -extern LogLevel gLogLevel; +DECL_HIDDEN extern LogLevel gLogLevel; -extern FILE *gLogFile; +DECL_HIDDEN extern FILE *gLogFile; using LogCallbackFunc = void(*)(void *userptr, char level, const char *message, int length) noexcept; @@ -20,12 +23,13 @@ using LogCallbackFunc = void(*)(void *userptr, char level, const char *message, void al_set_log_callback(LogCallbackFunc callback, void *userptr); -#ifdef __MINGW32__ -[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]] -#else -[[gnu::format(printf,2,3)]] -#endif -void al_print(LogLevel level, const char *fmt, ...) noexcept; +void al_print_impl(LogLevel level, const fmt::string_view fmt, fmt::format_args args); + +template +void al_print(LogLevel level, fmt::format_string fmt, Args&& ...args) noexcept +try { + al_print_impl(level, fmt, fmt::make_format_args(args...)); +} catch(...) { } #define TRACE(...) al_print(LogLevel::Trace, __VA_ARGS__) diff --git a/Engine/lib/openal-soft/core/mastering.cpp b/Engine/lib/openal-soft/core/mastering.cpp index 069a21cea..4da708ab8 100644 --- a/Engine/lib/openal-soft/core/mastering.cpp +++ b/Engine/lib/openal-soft/core/mastering.cpp @@ -19,7 +19,7 @@ /* These structures assume BufferLineSize is a power of 2. */ static_assert((BufferLineSize & (BufferLineSize-1)) == 0, "BufferLineSize is not a power of 2"); -struct SlidingHold { +struct SIMDALIGN SlidingHold { alignas(16) FloatBufferLine mValues; std::array mExpiries; uint mLowerIndex; @@ -119,7 +119,8 @@ void Compressor::linkChannels(const uint SamplesToDo, std::transform(sideChain.begin(), sideChain.end(), buffer.begin(), sideChain.begin(), max_abs); }; - std::for_each(OutBuffer.begin(), OutBuffer.end(), fill_max); + for(const FloatBufferLine &input : OutBuffer) + fill_max(input); } /* This calculates the squared crest factor of the control signal for the @@ -322,10 +323,9 @@ void Compressor::signalDelay(const uint SamplesToDo, const al::span Compressor::Create(const size_t NumChans, const float SampleRate, - const bool AutoKnee, const bool AutoAttack, const bool AutoRelease, const bool AutoPostGain, - const bool AutoDeclip, const float LookAheadTime, const float HoldTime, const float PreGainDb, - const float PostGainDb, const float ThresholdDb, const float Ratio, const float KneeDb, - const float AttackTime, const float ReleaseTime) + const FlagBits autoflags, const float LookAheadTime, const float HoldTime, + const float PreGainDb, const float PostGainDb, const float ThresholdDb, const float Ratio, + const float KneeDb, const float AttackTime, const float ReleaseTime) { const auto lookAhead = static_cast(std::clamp(std::round(LookAheadTime*SampleRate), 0.0f, BufferLineSize-1.0f)); @@ -333,12 +333,11 @@ std::unique_ptr Compressor::Create(const size_t NumChans, const floa BufferLineSize-1.0f)); auto Comp = CompressorPtr{new Compressor{}}; - Comp->mNumChans = NumChans; - Comp->mAuto.Knee = AutoKnee; - Comp->mAuto.Attack = AutoAttack; - Comp->mAuto.Release = AutoRelease; - Comp->mAuto.PostGain = AutoPostGain; - Comp->mAuto.Declip = AutoPostGain && AutoDeclip; + Comp->mAuto.Knee = autoflags.test(AutoKnee); + Comp->mAuto.Attack = autoflags.test(AutoAttack); + Comp->mAuto.Release = autoflags.test(AutoRelease); + Comp->mAuto.PostGain = autoflags.test(AutoPostGain); + Comp->mAuto.Declip = autoflags.test(AutoPostGain) && autoflags.test(AutoDeclip); Comp->mLookAhead = lookAhead; Comp->mPreGain = std::pow(10.0f, PreGainDb / 20.0f); Comp->mPostGain = std::log(10.0f)/20.0f * PostGainDb; @@ -381,15 +380,11 @@ std::unique_ptr Compressor::Create(const size_t NumChans, const floa Compressor::~Compressor() = default; -void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer) +void Compressor::process(const uint SamplesToDo, const al::span InOut) { - const size_t numChans{mNumChans}; - ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); - ASSUME(numChans > 0); - const auto output = al::span{OutBuffer, numChans}; const float preGain{mPreGain}; if(preGain != 1.0f) { @@ -399,10 +394,10 @@ void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer) std::transform(buffer.cbegin(), buffer.cend(), buffer.begin(), [preGain](const float s) noexcept { return s * preGain; }); }; - std::for_each(output.begin(), output.end(), apply_gain); + std::for_each(InOut.begin(), InOut.end(), apply_gain); } - linkChannels(SamplesToDo, output); + linkChannels(SamplesToDo, InOut); if(mAuto.Attack || mAuto.Release) crestDetector(SamplesToDo); @@ -415,16 +410,17 @@ void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer) gainCompressor(SamplesToDo); if(!mDelay.empty()) - signalDelay(SamplesToDo, output); + signalDelay(SamplesToDo, InOut); const auto gains = assume_aligned_span<16>(al::span{mSideChain}.first(SamplesToDo)); - auto apply_comp = [gains](const FloatBufferSpan input) noexcept -> void + auto apply_comp = [gains](const FloatBufferSpan inout) noexcept -> void { - const auto buffer = assume_aligned_span<16>(input); + const auto buffer = assume_aligned_span<16>(inout); std::transform(gains.cbegin(), gains.cend(), buffer.cbegin(), buffer.begin(), std::multiplies{}); }; - std::for_each(output.begin(), output.end(), apply_comp); + for(const FloatBufferSpan inout : InOut) + apply_comp(inout); const auto delayedGains = al::span{mSideChain}.subspan(SamplesToDo, mLookAhead); std::copy(delayedGains.begin(), delayedGains.end(), mSideChain.begin()); diff --git a/Engine/lib/openal-soft/core/mastering.h b/Engine/lib/openal-soft/core/mastering.h index 032eae082..32f581dbc 100644 --- a/Engine/lib/openal-soft/core/mastering.h +++ b/Engine/lib/openal-soft/core/mastering.h @@ -2,12 +2,13 @@ #define CORE_MASTERING_H #include +#include #include -#include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "bufferline.h" +#include "opthelpers.h" #include "vector.h" struct SlidingHold; @@ -25,9 +26,7 @@ using uint = unsigned int; * * http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/ */ -class Compressor { - size_t mNumChans{0u}; - +class SIMDALIGN Compressor { struct AutoFlags { bool Knee : 1; bool Attack : 1; @@ -75,8 +74,13 @@ class Compressor { void signalDelay(const uint SamplesToDo, const al::span OutBuffer); public: + enum { + AutoKnee, AutoAttack, AutoRelease, AutoPostGain, AutoDeclip, FlagsCount + }; + using FlagBits = std::bitset; + ~Compressor(); - void process(const uint SamplesToDo, FloatBufferLine *OutBuffer); + void process(const uint SamplesToDo, al::span InOut); [[nodiscard]] auto getLookAhead() const noexcept -> uint { return mLookAhead; } /** @@ -106,11 +110,9 @@ public: * automating release time. */ static std::unique_ptr Create(const size_t NumChans, const float SampleRate, - const bool AutoKnee, const bool AutoAttack, const bool AutoRelease, - const bool AutoPostGain, const bool AutoDeclip, const float LookAheadTime, - const float HoldTime, const float PreGainDb, const float PostGainDb, - const float ThresholdDb, const float Ratio, const float KneeDb, const float AttackTime, - const float ReleaseTime); + const FlagBits autoflags, const float LookAheadTime, const float HoldTime, + const float PreGainDb, const float PostGainDb, const float ThresholdDb, const float Ratio, + const float KneeDb, const float AttackTime, const float ReleaseTime); }; using CompressorPtr = std::unique_ptr; diff --git a/Engine/lib/openal-soft/core/mixer.h b/Engine/lib/openal-soft/core/mixer.h index b5f1b9aa1..ec6c3271d 100644 --- a/Engine/lib/openal-soft/core/mixer.h +++ b/Engine/lib/openal-soft/core/mixer.h @@ -8,6 +8,7 @@ #include "alspan.h" #include "ambidefs.h" #include "bufferline.h" +#include "opthelpers.h" struct MixParams; @@ -16,7 +17,7 @@ using MixerOutFunc = void(*)(const al::span InSamples, const al::span OutBuffer, const al::span CurrentGains, const al::span TargetGains, const std::size_t Counter, const std::size_t OutPos); -extern MixerOutFunc MixSamplesOut; +DECL_HIDDEN extern MixerOutFunc MixSamplesOut; inline void MixSamples(const al::span InSamples, const al::span OutBuffer, const al::span CurrentGains, const al::span TargetGains, const std::size_t Counter, const std::size_t OutPos) @@ -26,7 +27,7 @@ inline void MixSamples(const al::span InSamples, using MixerOneFunc = void(*)(const al::span InSamples,const al::span OutBuffer, float &CurrentGain, const float TargetGain, const std::size_t Counter); -extern MixerOneFunc MixSamplesOne; +DECL_HIDDEN extern MixerOneFunc MixSamplesOne; inline void MixSamples(const al::span InSamples, const al::span OutBuffer, float &CurrentGain, const float TargetGain, const std::size_t Counter) { MixSamplesOne(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); } diff --git a/Engine/lib/openal-soft/core/mixer/defs.h b/Engine/lib/openal-soft/core/mixer/defs.h index f19217c80..4506dde67 100644 --- a/Engine/lib/openal-soft/core/mixer/defs.h +++ b/Engine/lib/openal-soft/core/mixer/defs.h @@ -36,8 +36,10 @@ enum class Resampler : std::uint8_t { BSinc12, FastBSinc24, BSinc24, + FastBSinc48, + BSinc48, - Max = BSinc24 + Max = BSinc48 }; /* Interpolator state. Kind of a misnomer since the interpolator itself is @@ -60,7 +62,7 @@ struct CubicState { * each subsequent phase index follows contiguously. */ al::span filter; - CubicState(al::span f) : filter{f} { } + explicit CubicState(al::span f) : filter{f} { } }; using InterpState = std::variant; diff --git a/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp b/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp index bbbe44708..600c014b7 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp +++ b/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp @@ -19,6 +19,7 @@ #include "hrtfbase.h" #include "opthelpers.h" +struct CTag; struct NEONTag; struct LerpTag; struct CubicTag; @@ -76,13 +77,16 @@ inline void ApplyCoeffs(const al::span Values, const size_t IrSize, return vcombine_f32(leftright2, leftright2); }; const auto leftright4 = dup_samples(); - const auto count4 = size_t{(IrSize+1) >> 1}; - const auto vals4 = al::span{reinterpret_cast(Values[0].data()), count4}; - const auto coeffs4 = al::span{reinterpret_cast(Coeffs[0].data()), count4}; - std::transform(vals4.cbegin(), vals4.cend(), coeffs4.cbegin(), vals4.begin(), - [leftright4](const float32x4_t &val, const float32x4_t &coeff) -> float32x4_t - { return vmlaq_f32(val, coeff, leftright4); }); + /* Using a loop here instead of std::transform since some builds seem to + * have an issue with accessing an array/span of float32x4_t. + */ + for(size_t c{0};c < IrSize;c += 2) + { + auto vals = vld1q_f32(&Values[c][0]); + vals = vmlaq_f32(vals, vld1q_f32(&Coeffs[c][0]), leftright4); + vst1q_f32(&Values[c][0], vals); + } } force_inline void MixLine(const al::span InSamples, const al::span dst, @@ -461,6 +465,9 @@ void Mix_(const al::span InSamples,const al::span CurrentGains, const al::span TargetGains, const size_t Counter, const size_t OutPos) { + if((OutPos&3) != 0) UNLIKELY + return Mix_(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); + const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; const auto fade_len = std::min(Counter, InSamples.size()); const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; @@ -476,6 +483,9 @@ template<> void Mix_(const al::span InSamples, const al::span OutBuffer, float &CurrentGain, const float TargetGain, const size_t Counter) { + if((reinterpret_cast(OutBuffer.data())&15) != 0) UNLIKELY + return Mix_(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); + const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; const auto fade_len = std::min(Counter, InSamples.size()); const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; diff --git a/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp b/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp index df42823a7..097cd9333 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp +++ b/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp @@ -21,6 +21,7 @@ #include "hrtfbase.h" #include "opthelpers.h" +struct CTag; struct SSETag; struct CubicTag; struct BSincTag; @@ -105,8 +106,8 @@ force_inline void MixLine(const al::span InSamples, const al::span< size_t pos{0}; if(std::abs(step) > std::numeric_limits::epsilon()) { - const auto gain = float{CurrentGain}; - auto step_count = float{0.0f}; + const auto gain = CurrentGain; + auto step_count = 0.0f; /* Mix with applying gain steps in aligned multiples of 4. */ if(const size_t todo{fade_len >> 2}) { @@ -363,6 +364,9 @@ void Mix_(const al::span InSamples, const al::span CurrentGains, const al::span TargetGains, const size_t Counter, const size_t OutPos) { + if((OutPos&3) != 0) UNLIKELY + return Mix_(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); + const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; const auto fade_len = std::min(Counter, InSamples.size()); const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; @@ -378,6 +382,9 @@ template<> void Mix_(const al::span InSamples, const al::span OutBuffer, float &CurrentGain, const float TargetGain, const size_t Counter) { + if((reinterpret_cast(OutBuffer.data())&15) != 0) UNLIKELY + return Mix_(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); + const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; const auto fade_len = std::min(Counter, InSamples.size()); const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; diff --git a/Engine/lib/openal-soft/core/rtkit.cpp b/Engine/lib/openal-soft/core/rtkit.cpp index 70b5e12bb..717059ab8 100644 --- a/Engine/lib/openal-soft/core/rtkit.cpp +++ b/Engine/lib/openal-soft/core/rtkit.cpp @@ -69,6 +69,7 @@ namespace { inline pid_t _gettid() { #ifdef __linux__ + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) */ return static_cast(syscall(SYS_gettid)); #elif defined(__FreeBSD__) long pid{}; diff --git a/Engine/lib/openal-soft/core/storage_formats.cpp b/Engine/lib/openal-soft/core/storage_formats.cpp index 51f64644c..b62f91a0a 100644 --- a/Engine/lib/openal-soft/core/storage_formats.cpp +++ b/Engine/lib/openal-soft/core/storage_formats.cpp @@ -4,45 +4,49 @@ #include "storage_formats.h" #include +#include +namespace { +using namespace std::string_view_literals; +} // namespace -const char *NameFromFormat(FmtType type) noexcept +auto NameFromFormat(FmtType type) noexcept -> std::string_view { switch(type) { - case FmtUByte: return "UInt8"; - case FmtShort: return "Int16"; - case FmtInt: return "Int32"; - case FmtFloat: return "Float"; - case FmtDouble: return "Double"; - case FmtMulaw: return "muLaw"; - case FmtAlaw: return "aLaw"; - case FmtIMA4: return "IMA4 ADPCM"; - case FmtMSADPCM: return "MS ADPCM"; + case FmtUByte: return "UInt8"sv; + case FmtShort: return "Int16"sv; + case FmtInt: return "Int32"sv; + case FmtFloat: return "Float"sv; + case FmtDouble: return "Double"sv; + case FmtMulaw: return "muLaw"sv; + case FmtAlaw: return "aLaw"sv; + case FmtIMA4: return "IMA4 ADPCM"sv; + case FmtMSADPCM: return "MS ADPCM"sv; } - return ""; + return ""sv; } -const char *NameFromFormat(FmtChannels channels) noexcept +auto NameFromFormat(FmtChannels channels) noexcept -> std::string_view { switch(channels) { - case FmtMono: return "Mono"; - case FmtStereo: return "Stereo"; - case FmtRear: return "Rear"; - case FmtQuad: return "Quadraphonic"; - case FmtX51: return "Surround 5.1"; - case FmtX61: return "Surround 6.1"; - case FmtX71: return "Surround 7.1"; - case FmtBFormat2D: return "B-Format 2D"; - case FmtBFormat3D: return "B-Format 3D"; - case FmtUHJ2: return "UHJ2"; - case FmtUHJ3: return "UHJ3"; - case FmtUHJ4: return "UHJ4"; - case FmtSuperStereo: return "Super Stereo"; - case FmtMonoDup: return "Mono (dup)"; + case FmtMono: return "Mono"sv; + case FmtStereo: return "Stereo"sv; + case FmtRear: return "Rear"sv; + case FmtQuad: return "Quadraphonic"sv; + case FmtX51: return "Surround 5.1"sv; + case FmtX61: return "Surround 6.1"sv; + case FmtX71: return "Surround 7.1"sv; + case FmtBFormat2D: return "B-Format 2D"sv; + case FmtBFormat3D: return "B-Format 3D"sv; + case FmtUHJ2: return "UHJ2"sv; + case FmtUHJ3: return "UHJ3"sv; + case FmtUHJ4: return "UHJ4"sv; + case FmtSuperStereo: return "Super Stereo"sv; + case FmtMonoDup: return "Mono (dup)"sv; } - return ""; + return ""sv; } uint BytesFromFmt(FmtType type) noexcept diff --git a/Engine/lib/openal-soft/core/storage_formats.h b/Engine/lib/openal-soft/core/storage_formats.h index acced258a..fab0a04ce 100644 --- a/Engine/lib/openal-soft/core/storage_formats.h +++ b/Engine/lib/openal-soft/core/storage_formats.h @@ -1,6 +1,8 @@ #ifndef CORE_STORAGE_FORMATS_H #define CORE_STORAGE_FORMATS_H +#include + using uint = unsigned int; /* Storable formats */ @@ -43,8 +45,8 @@ enum class AmbiScaling : unsigned char { UHJ, }; -const char *NameFromFormat(FmtType type) noexcept; -const char *NameFromFormat(FmtChannels channels) noexcept; +auto NameFromFormat(FmtType type) noexcept -> std::string_view; +auto NameFromFormat(FmtChannels channels) noexcept -> std::string_view; uint BytesFromFmt(FmtType type) noexcept; uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept; diff --git a/Engine/lib/openal-soft/core/uhjfilter.cpp b/Engine/lib/openal-soft/core/uhjfilter.cpp index a2c3cd459..c432acc4e 100644 --- a/Engine/lib/openal-soft/core/uhjfilter.cpp +++ b/Engine/lib/openal-soft/core/uhjfilter.cpp @@ -70,14 +70,13 @@ struct SegmentedFilter { auto tmpBuffer = std::vector(fft_size, 0.0); for(std::size_t i{0};i < fft_size/2;++i) { - const int k{int{fft_size/2} - static_cast(i*2 + 1)}; + const auto k = int{fft_size/2} - static_cast(i*2 + 1); - const double w{2.0*al::numbers::pi * static_cast(i*2 + 1) - / double{fft_size}}; - const double window{0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w) - - 0.0106411*std::cos(3.0*w)}; + const auto w = 2.0*al::numbers::pi/double{fft_size} * static_cast(i*2 + 1); + const auto window = 0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w) + - 0.0106411*std::cos(3.0*w); - const double pk{al::numbers::pi * static_cast(k)}; + const auto pk = al::numbers::pi * static_cast(k); tmpBuffer[i*2 + 1] = window * (1.0-std::cos(pk)) / pk; } @@ -131,11 +130,10 @@ constexpr std::array Filter2Coeff{{ 0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156684f }}; -} // namespace -void UhjAllPassFilter::processOne(const al::span coeffs, float x) +void processOne(UhjAllPassFilter &self, const al::span coeffs, float x) { - auto state = mState; + auto state = self.mState; for(size_t i{0};i < 4;++i) { const float y{x*coeffs[i] + state[i].z[0]}; @@ -143,13 +141,13 @@ void UhjAllPassFilter::processOne(const al::span coeffs, float x state[i].z[1] = y*coeffs[i] - x; x = y; } - mState = state; + self.mState = state; } -void UhjAllPassFilter::process(const al::span coeffs, +void process(UhjAllPassFilter &self, const al::span coeffs, const al::span src, const bool updateState, const al::span dst) { - auto state = mState; + auto state = self.mState; auto proc_sample = [&state,coeffs](float x) noexcept -> float { @@ -163,9 +161,10 @@ void UhjAllPassFilter::process(const al::span coeffs, return x; }; std::transform(src.begin(), src.end(), dst.begin(), proc_sample); - if(updateState) LIKELY mState = state; + if(updateState) LIKELY self.mState = state; } +} // namespace /* Encoding UHJ from B-Format is done as: * @@ -216,7 +215,9 @@ void UhjEncoder::encode(float *LeftOut, float *RightOut, const size_t todo{std::min(sSegmentSize-mFifoPos, SamplesToDo-base)}; auto wseg = winput.subspan(base, todo); auto xseg = xinput.subspan(base, todo); - auto wxio = al::span{mWXInOut}.subspan(mFifoPos, todo); + /* Some Clang versions don't like calling subspan on an rvalue here. */ + const auto wxio_ = al::span{mWXInOut}; + auto wxio = wxio_.subspan(mFifoPos, todo); /* Copy out the samples that were previously processed by the FFT. */ dstore = std::copy_n(wxio.begin(), todo, dstore); @@ -351,17 +352,17 @@ void UhjEncoderIIR::encode(float *LeftOut, float *RightOut, /* S = 0.9396926*W + 0.1855740*X */ std::transform(winput.begin(), winput.end(), xinput.begin(), mTemp.begin(), [](const float w, const float x) noexcept { return 0.9396926f*w + 0.1855740f*x; }); - mFilter1WX.process(Filter1Coeff, al::span{mTemp}.first(SamplesToDo), true, + process(mFilter1WX, Filter1Coeff, al::span{mTemp}.first(SamplesToDo), true, al::span{mS}.subspan(1)); mS[0] = mDelayWX; mDelayWX = mS[SamplesToDo]; /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mWX. */ std::transform(winput.begin(), winput.end(), xinput.begin(), mTemp.begin(), [](const float w, const float x) noexcept { return -0.3420201f*w + 0.5098604f*x; }); - mFilter2WX.process(Filter2Coeff, al::span{mTemp}.first(SamplesToDo), true, mWX); + process(mFilter2WX, Filter2Coeff, al::span{mTemp}.first(SamplesToDo), true, mWX); /* Apply filter1 to Y and store in mD. */ - mFilter1Y.process(Filter1Coeff, yinput, true, al::span{mD}.subspan(1)); + process(mFilter1Y, Filter1Coeff, yinput, true, al::span{mD}.subspan(1)); mD[0] = mDelayY; mDelayY = mD[SamplesToDo]; /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */ @@ -371,21 +372,19 @@ void UhjEncoderIIR::encode(float *LeftOut, float *RightOut, /* Apply the base filter to the existing output to align with the processed * signal. */ - mFilter1Direct[0].process(Filter1Coeff, {LeftOut, SamplesToDo}, true, - al::span{mTemp}.subspan(1)); + const auto left = al::span{al::assume_aligned<16>(LeftOut), SamplesToDo}; + process(mFilter1Direct[0], Filter1Coeff, left, true, al::span{mTemp}.subspan(1)); mTemp[0] = mDirectDelay[0]; mDirectDelay[0] = mTemp[SamplesToDo]; /* Left = (S + D)/2.0 */ - const auto left = al::span{al::assume_aligned<16>(LeftOut), SamplesToDo}; for(size_t i{0};i < SamplesToDo;++i) left[i] = (mS[i] + mD[i])*0.5f + mTemp[i]; - mFilter1Direct[1].process(Filter1Coeff, {RightOut, SamplesToDo}, true, - al::span{mTemp}.subspan(1)); + const auto right = al::span{al::assume_aligned<16>(RightOut), SamplesToDo}; + process(mFilter1Direct[1], Filter1Coeff, right, true, al::span{mTemp}.subspan(1)); mTemp[0] = mDirectDelay[1]; mDirectDelay[1] = mTemp[SamplesToDo]; /* Right = (S - D)/2.0 */ - const auto right = al::span{al::assume_aligned<16>(RightOut), SamplesToDo}; for(size_t i{0};i < SamplesToDo;++i) right[i] = (mS[i] - mD[i])*0.5f + mTemp[i]; } @@ -497,11 +496,12 @@ void UhjDecoderIIR::decode(const al::span samples, const size_t samplesT std::transform(mD.cbegin(), mD.cbegin()+sInputPadding+samplesToDo, youtput.begin(), mTemp.begin(), [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); - if(mFirstRun) mFilter2DT.processOne(Filter2Coeff, mTemp[0]); - mFilter2DT.process(Filter2Coeff, al::span{mTemp}.subspan(1,samplesToDo), updateState, xoutput); + if(mFirstRun) processOne(mFilter2DT, Filter2Coeff, mTemp[0]); + process(mFilter2DT, Filter2Coeff, al::span{mTemp}.subspan(1, samplesToDo), updateState, + xoutput); /* Apply filter1 to S and store in mTemp. */ - mFilter1S.process(Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp); + process(mFilter1S, Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp); /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), woutput.begin(), @@ -514,11 +514,11 @@ void UhjDecoderIIR::decode(const al::span samples, const size_t samplesT /* Apply filter1 to (0.795968*D - 0.676392*T) and store in mTemp. */ std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput.begin(), youtput.begin(), [](const float d, const float t) noexcept { return 0.795968f*d - 0.676392f*t; }); - mFilter1DT.process(Filter1Coeff, youtput.first(samplesToDo), updateState, mTemp); + process(mFilter1DT, Filter1Coeff, youtput.first(samplesToDo), updateState, mTemp); /* Precompute j*S and store in youtput. */ - if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]); - mFilter2S.process(Filter2Coeff, al::span{mS}.subspan(1, samplesToDo), updateState, youtput); + if(mFirstRun) processOne(mFilter2S, Filter2Coeff, mS[0]); + process(mFilter2S, Filter2Coeff, al::span{mS}.subspan(1, samplesToDo), updateState, youtput); /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, youtput.begin(), youtput.begin(), @@ -529,7 +529,7 @@ void UhjDecoderIIR::decode(const al::span samples, const size_t samplesT const auto zoutput = al::span{al::assume_aligned<16>(samples[3]), samplesToDo}; /* Apply filter1 to Q and store in mTemp. */ - mFilter1Q.process(Filter1Coeff, zoutput, updateState, mTemp); + process(mFilter1Q, Filter1Coeff, zoutput, updateState, mTemp); /* Z = 1.023332*Q */ std::transform(mTemp.begin(), mTemp.end(), zoutput.begin(), @@ -545,9 +545,9 @@ void UhjDecoderIIR::decode(const al::span samples, const size_t samplesT * S = Left + Right * D = Left - Right * - * W = 0.6098637*S - 0.6896511*j*w*D - * X = 0.8624776*S + 0.7626955*j*w*D - * Y = 1.6822415*w*D - 0.2156194*j*S + * W = 0.6098637*S + 0.6896511*j*w*D + * X = 0.8624776*S - 0.7626955*j*w*D + * Y = 1.6822415*w*D + 0.2156194*j*S * * 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. @@ -613,12 +613,12 @@ void UhjStereoDecoder::decode(const al::span samples, const size_t sa std::copy_n(mTemp.cbegin()+samplesToDo, mDTHistory.size(), mDTHistory.begin()); PShift.process(xoutput, mTemp); - /* W = 0.6098637*S - 0.6896511*j*w*D */ + /* W = 0.6098637*S + 0.6896511*j*w*D */ std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), woutput.begin(), - [](const float s, const float jd) noexcept { return 0.6098637f*s - 0.6896511f*jd; }); - /* X = 0.8624776*S + 0.7626955*j*w*D */ + [](const float s, const float jd) noexcept { return 0.6098637f*s + 0.6896511f*jd; }); + /* X = 0.8624776*S - 0.7626955*j*w*D */ std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), - [](const float s, const float jd) noexcept { return 0.8624776f*s + 0.7626955f*jd; }); + [](const float s, const float jd) noexcept { return 0.8624776f*s - 0.7626955f*jd; }); /* Precompute j*S and store in youtput. */ tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); @@ -627,9 +627,9 @@ void UhjStereoDecoder::decode(const al::span samples, const size_t sa std::copy_n(mTemp.cbegin()+samplesToDo, mSHistory.size(), mSHistory.begin()); PShift.process(youtput, mTemp); - /* Y = 1.6822415*w*D - 0.2156194*j*S */ + /* Y = 1.6822415*w*D + 0.2156194*j*S */ std::transform(mD.begin(), mD.begin()+samplesToDo, youtput.begin(), youtput.begin(), - [](const float d, const float js) noexcept { return 1.6822415f*d - 0.2156194f*js; }); + [](const float d, const float js) noexcept { return 1.6822415f*d + 0.2156194f*js; }); } void UhjStereoDecoderIIR::decode(const al::span samples, const size_t samplesToDo, @@ -685,29 +685,29 @@ void UhjStereoDecoderIIR::decode(const al::span samples, const size_t sa const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo}; /* Apply filter1 to S and store in mTemp. */ - mFilter1S.process(Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp); + process(mFilter1S, Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp); /* Precompute j*D and store in xoutput. */ - if(mFirstRun) mFilter2D.processOne(Filter2Coeff, mD[0]); - mFilter2D.process(Filter2Coeff, al::span{mD}.subspan(1, samplesToDo), updateState, xoutput); + if(mFirstRun) processOne(mFilter2D, Filter2Coeff, mD[0]); + process(mFilter2D, Filter2Coeff, al::span{mD}.subspan(1, samplesToDo), updateState, xoutput); - /* W = 0.6098637*S - 0.6896511*j*w*D */ + /* W = 0.6098637*S + 0.6896511*j*w*D */ std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), woutput.begin(), - [](const float s, const float jd) noexcept { return 0.6098637f*s - 0.6896511f*jd; }); - /* X = 0.8624776*S + 0.7626955*j*w*D */ + [](const float s, const float jd) noexcept { return 0.6098637f*s + 0.6896511f*jd; }); + /* X = 0.8624776*S - 0.7626955*j*w*D */ std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), - [](const float s, const float jd) noexcept { return 0.8624776f*s + 0.7626955f*jd; }); + [](const float s, const float jd) noexcept { return 0.8624776f*s - 0.7626955f*jd; }); /* Precompute j*S and store in youtput. */ - if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]); - mFilter2S.process(Filter2Coeff, al::span{mS}.subspan(1, samplesToDo), updateState, youtput); + if(mFirstRun) processOne(mFilter2S, Filter2Coeff, mS[0]); + process(mFilter2S, Filter2Coeff, al::span{mS}.subspan(1, samplesToDo), updateState, youtput); /* Apply filter1 to D and store in mTemp. */ - mFilter1D.process(Filter1Coeff, al::span{mD}.first(samplesToDo), updateState, mTemp); + process(mFilter1D, Filter1Coeff, al::span{mD}.first(samplesToDo), updateState, mTemp); - /* Y = 1.6822415*w*D - 0.2156194*j*S */ + /* Y = 1.6822415*w*D + 0.2156194*j*S */ std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, youtput.begin(), youtput.begin(), - [](const float d, const float js) noexcept { return 1.6822415f*d - 0.2156194f*js; }); + [](const float d, const float js) noexcept { return 1.6822415f*d + 0.2156194f*js; }); mFirstRun = false; } diff --git a/Engine/lib/openal-soft/core/uhjfilter.h b/Engine/lib/openal-soft/core/uhjfilter.h index 55be31b22..34ea1dfa0 100644 --- a/Engine/lib/openal-soft/core/uhjfilter.h +++ b/Engine/lib/openal-soft/core/uhjfilter.h @@ -7,6 +7,7 @@ #include "alspan.h" #include "bufferline.h" +#include "opthelpers.h" inline constexpr std::size_t UhjLength256{256}; @@ -29,14 +30,10 @@ struct UhjAllPassFilter { std::array z{}; }; std::array mState; - - void processOne(const al::span coeffs, float x); - void process(const al::span coeffs, const al::span src, - const bool update, const al::span dst); }; -struct UhjEncoderBase { +struct SIMDALIGN UhjEncoderBase { UhjEncoderBase() = default; UhjEncoderBase(const UhjEncoderBase&) = delete; UhjEncoderBase(UhjEncoderBase&&) = delete; @@ -120,7 +117,7 @@ struct UhjEncoderIIR final : public UhjEncoderBase { }; -struct DecoderBase { +struct SIMDALIGN DecoderBase { static constexpr std::size_t sMaxPadding{256}; /* For 2-channel UHJ, shelf filters should use these LF responses. */ diff --git a/Engine/lib/openal-soft/core/uiddefs.cpp b/Engine/lib/openal-soft/core/uiddefs.cpp index e93376763..e52a9ae33 100644 --- a/Engine/lib/openal-soft/core/uiddefs.cpp +++ b/Engine/lib/openal-soft/core/uiddefs.cpp @@ -1,6 +1,6 @@ #include "config.h" - +#include "config_backends.h" #ifndef AL_NO_UID_DEFS @@ -16,7 +16,7 @@ DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf,0x08, 0x0 DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e,0x3d, 0xc4,0x57,0x92,0x91,0x69,0x2e); -#if defined(HAVE_WASAPI) && !defined(ALSOFT_UWP) +#if HAVE_WASAPI && !ALSOFT_UWP #include #include #include diff --git a/Engine/lib/openal-soft/core/voice.cpp b/Engine/lib/openal-soft/core/voice.cpp index 9d2b34a07..31214f0e2 100644 --- a/Engine/lib/openal-soft/core/voice.cpp +++ b/Engine/lib/openal-soft/core/voice.cpp @@ -1,5 +1,6 @@ #include "config.h" +#include "config_simd.h" #include "voice.h" @@ -42,10 +43,10 @@ #include "voice_change.h" struct CTag; -#ifdef HAVE_SSE +#if HAVE_SSE struct SSETag; #endif -#ifdef HAVE_NEON +#if HAVE_NEON struct NEONTag; #endif @@ -75,11 +76,11 @@ HrtfMixerBlendFunc MixHrtfBlendSamples{MixHrtfBlend_}; inline MixerOutFunc SelectMixer() { -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Mix_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Mix_; #endif @@ -88,11 +89,11 @@ inline MixerOutFunc SelectMixer() inline MixerOneFunc SelectMixerOne() { -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Mix_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Mix_; #endif @@ -101,11 +102,11 @@ inline MixerOneFunc SelectMixerOne() inline HrtfMixerFunc SelectHrtfMixer() { -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return MixHrtf_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return MixHrtf_; #endif @@ -114,11 +115,11 @@ inline HrtfMixerFunc SelectHrtfMixer() inline HrtfMixerBlendFunc SelectHrtfBlendMixer() { -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return MixHrtfBlend_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return MixHrtfBlend_; #endif @@ -145,19 +146,26 @@ void Voice::InitMixer(std::optional resopt) ResamplerEntry{"fast_bsinc12"sv, Resampler::FastBSinc12}, ResamplerEntry{"bsinc24"sv, Resampler::BSinc24}, ResamplerEntry{"fast_bsinc24"sv, Resampler::FastBSinc24}, + ResamplerEntry{"bsinc48"sv, Resampler::BSinc48}, + ResamplerEntry{"fast_bsinc48"sv, Resampler::FastBSinc48}, }; std::string_view resampler{*resopt}; - if(al::case_compare(resampler, "cubic"sv) == 0 - || al::case_compare(resampler, "sinc4"sv) == 0 + + if (al::case_compare(resampler, "cubic"sv) == 0) + { + WARN("Resampler option \"{}\" is deprecated, using spline", *resopt); + resampler = "spline"sv; + } + else if(al::case_compare(resampler, "sinc4"sv) == 0 || al::case_compare(resampler, "sinc8"sv) == 0) { - WARN("Resampler option \"%s\" is deprecated, using gaussian\n", resopt->c_str()); + WARN("Resampler option \"{}\" is deprecated, using gaussian", *resopt); resampler = "gaussian"sv; } else if(al::case_compare(resampler, "bsinc"sv) == 0) { - WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", resopt->c_str()); + WARN("Resampler option \"{}\" is deprecated, using bsinc12", *resopt); resampler = "bsinc12"sv; } @@ -165,7 +173,7 @@ void Voice::InitMixer(std::optional resopt) [resampler](const ResamplerEntry &entry) -> bool { return al::case_compare(resampler, entry.name) == 0; }); if(iter == ResamplerList.end()) - ERR("Invalid resampler: %s\n", resopt->c_str()); + ERR("Invalid resampler: {}", *resopt); else ResamplerDefault = iter->resampler; } @@ -199,7 +207,7 @@ constexpr std::array IMA4Codeword{{ }}; /* IMA4 ADPCM Step index adjust decode table */ -constexpr std::arrayIMA4Index_adjust{{ +constexpr std::array IMA4Index_adjust{{ -1,-1,-1,-1, 2, 4, 6, 8, -1,-1,-1,-1, 2, 4, 6, 8 }}; @@ -226,9 +234,9 @@ void SendSourceStoppedEvent(ContextBase *context, uint id) { RingBuffer *ring{context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); - if(evt_vec.first.len < 1) return; + if(evt_vec[0].len < 1) return; - auto &evt = InitAsyncEvent(evt_vec.first.buf); + auto &evt = InitAsyncEvent(evt_vec[0].buf); evt.mId = id; evt.mState = AsyncSrcState::Stop; @@ -270,18 +278,17 @@ inline void LoadSamples(const al::span dstSamples, const al::span; using SampleType = typename TypeTraits::Type; - static constexpr size_t sampleSize{sizeof(SampleType)}; assert(srcChan < srcStep); auto converter = TypeTraits{}; al::span src{reinterpret_cast(srcData.data()), - srcData.size()/sampleSize}; - auto ssrc = src.cbegin() + ptrdiff_t(srcOffset*srcStep); - std::generate(dstSamples.begin(), dstSamples.end(), [&ssrc,srcChan,srcStep,converter] + srcData.size()/sizeof(SampleType)}; + auto ssrc = src.cbegin() + ptrdiff_t(srcOffset*srcStep + srcChan); + dstSamples.front() = converter(*ssrc); + std::generate(dstSamples.begin()+1, dstSamples.end(), [&ssrc,srcStep,converter] { - auto ret = converter(ssrc[srcChan]); ssrc += ptrdiff_t(srcStep); - return ret; + return converter(*ssrc); }); } @@ -303,46 +310,53 @@ inline void LoadSamples(al::span dstSamples, al::span(sample) / 32768.0f; - dstSamples = dstSamples.subspan<1>(); - if(dstSamples.empty()) return; + *dst = static_cast(prevSample) / 32768.0f; + if(++dst == dstSamples.end()) return; } else --skip; - auto decode_sample = [&sample,&index](const uint nibble) - { - sample += IMA4Codeword[nibble] * IMAStep_size[static_cast(index)] / 8; - sample = std::clamp(sample, -32768, 32767); - - index += IMA4Index_adjust[nibble]; - index = std::clamp(index, 0, MaxStepIndex); - - return sample; - }; - /* The rest of the block is arranged as a series of nibbles, contained * in 4 *bytes* per channel interleaved. So every 8 nibbles we need to * skip 4 bytes per channel to get the next nibbles for this channel. - * - * First, decode the samples that we need to skip in the block (will + */ + auto decode_nibble = [&prevSample,&prevIndex,srcStep,nibbleData](const size_t nibbleOffset) + noexcept -> int + { + static constexpr auto NibbleMask = std::byte{0xf}; + const auto byteShift = (nibbleOffset&1) * 4; + const auto wordOffset = (nibbleOffset>>1) & ~3_uz; + const auto byteOffset = wordOffset*srcStep + ((nibbleOffset>>1)&3); + + const auto nibble = al::to_underlying((nibbleData[byteOffset]>>byteShift)&NibbleMask); + + prevSample += IMA4Codeword[nibble] * IMAStep_size[static_cast(prevIndex)] / 8; + prevSample = std::clamp(prevSample, -32768, 32767); + + prevIndex += IMA4Index_adjust[nibble]; + prevIndex = std::clamp(prevIndex, 0, MaxStepIndex); + + return prevSample; + }; + + /* First, decode the samples that we need to skip in the block (will * always be less than the block size). They need to be decoded despite * being ignored for proper state on the remaining samples. */ @@ -350,29 +364,22 @@ inline void LoadSamples(al::span dstSamples, al::span>1) & ~3_uz}; - const size_t byteOffset{wordOffset*srcStep + ((nibbleOffset>>1)&3u)}; + std::ignore = decode_nibble(nibbleOffset); ++nibbleOffset; - - std::ignore = decode_sample(uint(nibbleData[byteOffset]>>byteShift) & 15u); } /* Second, decode the rest of the block and write to the output, until * the end of the block or the end of output. */ - const size_t todo{std::min(samplesPerBlock-startOffset, dstSamples.size())}; - std::generate_n(dstSamples.begin(), todo, [&] + const auto todo = std::min(samplesPerBlock - startOffset, + size_t(std::distance(dst, dstSamples.end()))); + dst = std::generate_n(dst, todo, [&] { - const size_t byteShift{(nibbleOffset&1) * 4}; - const size_t wordOffset{(nibbleOffset>>1) & ~3_uz}; - const size_t byteOffset{wordOffset*srcStep + ((nibbleOffset>>1)&3u)}; + const auto sample = decode_nibble(nibbleOffset); ++nibbleOffset; - const int result{decode_sample(uint(nibbleData[byteOffset]>>byteShift) & 15u)}; - return static_cast(result) / 32768.0f; + return static_cast(sample) / 32768.0f; }); - dstSamples = dstSamples.subspan(todo); } } @@ -389,30 +396,27 @@ inline void LoadSamples(al::span dstSamples, al::span sampleHistory{}; - sampleHistory[0] = int(input[2*srcChan + 0]) | (int(input[2*srcChan + 1])<<8); - input += ptrdiff_t(srcStep*2); - sampleHistory[1] = int(input[2*srcChan + 0]) | (int(input[2*srcChan + 1])<<8); - input += ptrdiff_t(srcStep*2); + auto sampleHistory = std::array{ + int(src[3*srcStep + 2*srcChan + 0]) | (int(src[3*srcStep + 2*srcChan + 1])<<8), + int(src[5*srcStep + 2*srcChan + 0]) | (int(src[5*srcStep + 2*srcChan + 1])<<8)}; + const auto nibbleData = src.subspan(7*srcStep); + src = src.subspan(blockBytes); - const al::span coeffs{MSADPCMAdaptionCoeff[blockpred]}; - delta = (delta^0x8000) - 32768; + const auto coeffs = al::span{MSADPCMAdaptionCoeff[blockpred]}; + scale = (scale^0x8000) - 32768; sampleHistory[0] = (sampleHistory[0]^0x8000) - 32768; sampleHistory[1] = (sampleHistory[1]^0x8000) - 32768; @@ -421,66 +425,66 @@ inline void LoadSamples(al::span dstSamples, al::span(sampleHistory[1]) / 32768.0f; - dstSamples = dstSamples.subspan<1>(); - if(dstSamples.empty()) return; - dstSamples[0] = static_cast(sampleHistory[0]) / 32768.0f; - dstSamples = dstSamples.subspan<1>(); - if(dstSamples.empty()) return; + *dst = static_cast(sampleHistory[1]) / 32768.0f; + if(++dst == dstSamples.end()) return; + *dst = static_cast(sampleHistory[0]) / 32768.0f; + if(++dst == dstSamples.end()) return; } else if(skip == 1) { --skip; - dstSamples[0] = static_cast(sampleHistory[0]) / 32768.0f; - dstSamples = dstSamples.subspan<1>(); - if(dstSamples.empty()) return; + *dst = static_cast(sampleHistory[0]) / 32768.0f; + if(++dst == dstSamples.end()) return; } else skip -= 2; - auto decode_sample = [&sampleHistory,&delta,coeffs](const int nibble) + /* The rest of the block is a series of nibbles, interleaved per- + * channel. + */ + auto decode_nibble = [&sampleHistory,&scale,coeffs,nibbleData](const size_t nibbleOffset) + noexcept -> int { - int pred{(sampleHistory[0]*coeffs[0] + sampleHistory[1]*coeffs[1]) / 256}; - pred += ((nibble^0x08) - 0x08) * delta; - pred = std::clamp(pred, -32768, 32767); + static constexpr auto NibbleMask = std::byte{0xf}; + const auto byteOffset = nibbleOffset>>1; + const auto byteShift = ((nibbleOffset&1)^1) * 4; + + const auto nibble = al::to_underlying((nibbleData[byteOffset]>>byteShift)&NibbleMask); + + const auto pred = ((nibble^0x08) - 0x08) * scale; + const auto diff = (sampleHistory[0]*coeffs[0] + sampleHistory[1]*coeffs[1]) / 256; + const auto sample = std::clamp(pred + diff, -32768, 32767); sampleHistory[1] = sampleHistory[0]; - sampleHistory[0] = pred; + sampleHistory[0] = sample; - delta = (MSADPCMAdaption[static_cast(nibble)] * delta) / 256; - delta = std::max(16, delta); + scale = MSADPCMAdaption[nibble] * scale / 256; + scale = std::max(16, scale); - return pred; + return sample; }; - /* The rest of the block is a series of nibbles, interleaved per- - * channel. First, skip samples. - */ + /* First, skip samples. */ const size_t startOffset{skip + 2}; size_t nibbleOffset{srcChan}; for(;skip;--skip) { - const size_t byteOffset{nibbleOffset>>1}; - const size_t byteShift{((nibbleOffset&1)^1) * 4}; + std::ignore = decode_nibble(nibbleOffset); nibbleOffset += srcStep; - - std::ignore = decode_sample(int(input[byteOffset]>>byteShift) & 15); } /* Now decode the rest of the block, until the end of the block or the * dst buffer is filled. */ - const size_t todo{std::min(samplesPerBlock-startOffset, dstSamples.size())}; - std::generate_n(dstSamples.begin(), todo, [&] + const auto todo = std::min(samplesPerBlock - startOffset, + size_t(std::distance(dst, dstSamples.end()))); + dst = std::generate_n(dst, todo, [&] { - const size_t byteOffset{nibbleOffset>>1}; - const size_t byteShift{((nibbleOffset&1)^1) * 4}; + const auto sample = decode_nibble(nibbleOffset); nibbleOffset += srcStep; - const int sample{decode_sample(int(input[byteOffset]>>byteShift) & 15)}; return static_cast(sample) / 32768.0f; }); - dstSamples = dstSamples.subspan(todo); } } @@ -692,25 +696,24 @@ void DoNfcMix(const al::span samples, al::span Out static constexpr std::array NfcProcess{{ nullptr, &NfcFilter::process1, &NfcFilter::process2, &NfcFilter::process3}}; - auto CurrentGains = al::span{parms.Gains.Current}.subspan(0); - auto TargetGains = OutGains.subspan(0); - MixSamples(samples, OutBuffer.first(1), CurrentGains, TargetGains, Counter, OutPos); + MixSamples(samples, al::span{OutBuffer[0]}.subspan(OutPos), parms.Gains.Current[0], + OutGains[0], Counter); OutBuffer = OutBuffer.subspan(1); - CurrentGains = CurrentGains.subspan(1); - TargetGains = TargetGains.subspan(1); + auto CurrentGains = al::span{parms.Gains.Current}.subspan(1); + auto TargetGains = OutGains.subspan(1); - const auto nfcsamples = al::span{Device->ExtraSampleData}.subspan(samples.size()); + const auto nfcsamples = al::span{Device->ExtraSampleData}.first(samples.size()); size_t order{1}; while(const size_t chancount{Device->NumChannelsPerOrder[order]}) { (parms.NFCtrlFilter.*NfcProcess[order])(samples, nfcsamples); MixSamples(nfcsamples, OutBuffer.first(chancount), CurrentGains, TargetGains, Counter, OutPos); + if(++order == MaxAmbiOrder+1) + break; OutBuffer = OutBuffer.subspan(chancount); CurrentGains = CurrentGains.subspan(chancount); TargetGains = TargetGains.subspan(chancount); - if(++order == MaxAmbiOrder+1) - break; } } @@ -773,17 +776,9 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi /* 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); + OutPos = static_cast(round(diff * Device->mSampleRate).count()); + if(OutPos >= SamplesToDo) return; } /* Calculate the number of samples to mix, and the number of (resampled) @@ -854,7 +849,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi dataSize64 += ext + MaxResamplerEdge; if(dataSize64 <= srcSizeMax) - return std::make_pair(dstBufferSize, static_cast(dataSize64)); + return std::array{dstBufferSize, static_cast(dataSize64)}; /* If the source size got saturated, we can't fill the desired * dst size. Figure out how many dst samples we can fill. @@ -869,7 +864,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi */ dstBufferSize = static_cast(dataSize64) & ~3u; } - return std::make_pair(dstBufferSize, srcSizeMax); + return std::array{dstBufferSize, srcSizeMax}; }; const auto [dstBufferSize, srcBufferSize] = calc_buffer_sizes( samplesToLoad - samplesLoaded); @@ -1193,9 +1188,9 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi { RingBuffer *ring{Context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); - if(evt_vec.first.len > 0) + if(evt_vec[0].len > 0) { - auto &evt = InitAsyncEvent(evt_vec.first.buf); + auto &evt = InitAsyncEvent(evt_vec[0].buf); evt.mId = SourceID; evt.mCount = buffers_done; ring->writeAdvance(1); @@ -1223,7 +1218,7 @@ void Voice::prepare(DeviceBase *device) : ChannelsFromFmt(mFmtChannels, std::min(mAmbiOrder, device->mAmbiOrder))}; if(num_channels > device->MixerChannelsMax) UNLIKELY { - ERR("Unexpected channel count: %u (limit: %zu, %s : %d)\n", num_channels, + ERR("Unexpected channel count: {} (limit: {}, {} : {})", num_channels, device->MixerChannelsMax, NameFromFormat(mFmtChannels), mAmbiOrder); num_channels = device->MixerChannelsMax; } @@ -1298,7 +1293,7 @@ void Voice::prepare(DeviceBase *device) * 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)}; + const BandSplitter splitter{device->mXOverFreq / static_cast(device->mSampleRate)}; for(auto &chandata : mChans) { chandata.mAmbiHFScale = 1.0f; @@ -1325,7 +1320,7 @@ void Voice::prepare(DeviceBase *device) const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder, device->m2DMixing); - const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; + const BandSplitter splitter{device->mXOverFreq / static_cast(device->mSampleRate)}; for(auto &chandata : mChans) { chandata.mAmbiHFScale = scales[*(OrderFromChan++)]; diff --git a/Engine/lib/openal-soft/core/voice.h b/Engine/lib/openal-soft/core/voice.h index c983c3e9f..bb7c6e249 100644 --- a/Engine/lib/openal-soft/core/voice.h +++ b/Engine/lib/openal-soft/core/voice.h @@ -10,7 +10,6 @@ #include #include -#include "almalloc.h" #include "alspan.h" #include "bufferline.h" #include "buffer_storage.h" @@ -20,6 +19,7 @@ #include "filters/splitter.h" #include "mixer/defs.h" #include "mixer/hrtfdefs.h" +#include "opthelpers.h" #include "resampler_limits.h" #include "uhjfilter.h" #include "vector.h" @@ -102,7 +102,10 @@ struct VoiceBufferItem { uint mLoopStart{0u}; uint mLoopEnd{0u}; - al::span mSamples{}; + al::span mSamples; + +protected: + ~VoiceBufferItem() = default; }; @@ -180,7 +183,7 @@ enum : uint { VoiceFlagCount }; -struct Voice { +struct SIMDALIGN Voice { enum State { Stopped, Playing, @@ -233,9 +236,9 @@ struct Voice { ResamplerFunc mResampler{}; - InterpState mResampleState{}; + InterpState mResampleState; - std::bitset mFlags{}; + std::bitset mFlags; uint mNumCallbackBlocks{0}; uint mCallbackBlockBase{0}; @@ -277,6 +280,6 @@ struct Voice { static void InitMixer(std::optional resopt); }; -inline Resampler ResamplerDefault{Resampler::Gaussian}; +inline Resampler ResamplerDefault{Resampler::Spline}; #endif /* CORE_VOICE_H */ diff --git a/Engine/lib/openal-soft/docs/env-vars.txt b/Engine/lib/openal-soft/docs/env-vars.txt index 0c15cbe99..97e5882d4 100644 --- a/Engine/lib/openal-soft/docs/env-vars.txt +++ b/Engine/lib/openal-soft/docs/env-vars.txt @@ -50,6 +50,10 @@ easier to track the cause. ALSOFT_TRAP_ERROR Set to "true" or "1" to force trapping both ALC and AL errors. +ALSOFT_EAX_TRACE_COMMITS +Overrides the EAX trace-commits config option. This specifies whether EAX +property commits are logged with trace messages. + *** Compatibility *** __ALSOFT_HALF_ANGLE_CONES @@ -78,6 +82,18 @@ Same as for __ALSOFT_REVERSE_Z, but for Y (up/down) panning. __ALSOFT_REVERSE_X Same as for __ALSOFT_REVERSE_Z, but for X (left/right) panning. +__ALSOFT_VENDOR_OVERRIDE +Overrides the value returned by alGetString(AL_VENDOR), for apps that misbehave +without particular values. + +__ALSOFT_VERSION_OVERRIDE +Overrides the value returned by alGetString(AL_VERSION), for apps that +misbehave without particular values. + +__ALSOFT_RENDERER_OVERRIDE +Overrides the value returned by alGetString(AL_RENDERER), for apps that +misbehave without particular values. + __ALSOFT_DEFAULT_ERROR Applications that erroneously call alGetError prior to setting a context as current may not like that OpenAL Soft returns 0xA004 (AL_INVALID_OPERATION), diff --git a/Engine/lib/openal-soft/examples/aldebug.cpp b/Engine/lib/openal-soft/examples/aldebug.cpp new file mode 100644 index 000000000..7669634fd --- /dev/null +++ b/Engine/lib/openal-soft/examples/aldebug.cpp @@ -0,0 +1,311 @@ +/* + * OpenAL Debug Context Example + * + * Copyright (c) 2024 by Chris Robinson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* This file contains an example for using the debug extension. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "alnumeric.h" +#include "alspan.h" +#include "fmt/core.h" + +#include "win_main_utf8.h" + +namespace { + +using namespace std::string_view_literals; + +struct DeviceCloser { + void operator()(ALCdevice *device) const noexcept { alcCloseDevice(device); } +}; +using DevicePtr = std::unique_ptr; + +struct ContextDestroyer { + void operator()(ALCcontext *context) const noexcept { alcDestroyContext(context); } +}; +using ContextPtr = std::unique_ptr; + + +constexpr auto GetDebugSourceName(ALenum source) noexcept -> std::string_view +{ + switch(source) + { + case AL_DEBUG_SOURCE_API_EXT: return "API"sv; + case AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT: return "Audio System"sv; + case AL_DEBUG_SOURCE_THIRD_PARTY_EXT: return "Third Party"sv; + case AL_DEBUG_SOURCE_APPLICATION_EXT: return "Application"sv; + case AL_DEBUG_SOURCE_OTHER_EXT: return "Other"sv; + } + return ""sv; +} + +constexpr auto GetDebugTypeName(ALenum type) noexcept -> std::string_view +{ + switch(type) + { + case AL_DEBUG_TYPE_ERROR_EXT: return "Error"sv; + case AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT: return "Deprecated Behavior"sv; + case AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT: return "Undefined Behavior"sv; + case AL_DEBUG_TYPE_PORTABILITY_EXT: return "Portability"sv; + case AL_DEBUG_TYPE_PERFORMANCE_EXT: return "Performance"sv; + case AL_DEBUG_TYPE_MARKER_EXT: return "Marker"sv; + case AL_DEBUG_TYPE_PUSH_GROUP_EXT: return "Push Group"sv; + case AL_DEBUG_TYPE_POP_GROUP_EXT: return "Pop Group"sv; + case AL_DEBUG_TYPE_OTHER_EXT: return "Other"sv; + } + return ""sv; +} + +constexpr auto GetDebugSeverityName(ALenum severity) noexcept -> std::string_view +{ + switch(severity) + { + case AL_DEBUG_SEVERITY_HIGH_EXT: return "High"sv; + case AL_DEBUG_SEVERITY_MEDIUM_EXT: return "Medium"sv; + case AL_DEBUG_SEVERITY_LOW_EXT: return "Low"sv; + case AL_DEBUG_SEVERITY_NOTIFICATION_EXT: return "Notification"sv; + } + return ""sv; +} + +auto alDebugMessageCallbackEXT = LPALDEBUGMESSAGECALLBACKEXT{}; +auto alDebugMessageInsertEXT = LPALDEBUGMESSAGEINSERTEXT{}; +auto alDebugMessageControlEXT = LPALDEBUGMESSAGECONTROLEXT{}; +auto alPushDebugGroupEXT = LPALPUSHDEBUGGROUPEXT{}; +auto alPopDebugGroupEXT = LPALPOPDEBUGGROUPEXT{}; +auto alGetDebugMessageLogEXT = LPALGETDEBUGMESSAGELOGEXT{}; +auto alObjectLabelEXT = LPALOBJECTLABELEXT{}; +auto alGetObjectLabelEXT = LPALGETOBJECTLABELEXT{}; +auto alGetPointerEXT = LPALGETPOINTEREXT{}; +auto alGetPointervEXT = LPALGETPOINTERVEXT{}; + + +int main(al::span args) +{ + /* Print out usage if -h was specified */ + if(args.size() > 1 && (args[1] == "-h" || args[1] == "--help")) + { + fmt::println(stderr, "Usage: {} [-device ] [-nodebug]", args[0]); + return 1; + } + + /* Initialize OpenAL. */ + args = args.subspan(1); + + auto device = DevicePtr{}; + if(args.size() > 1 && args[0] == "-device") + { + device = DevicePtr{alcOpenDevice(std::string{args[1]}.c_str())}; + if(!device) + fmt::println(stderr, "Failed to open \"{}\", trying default", args[1]); + args = args.subspan(2); + } + if(!device) + device = DevicePtr{alcOpenDevice(nullptr)}; + if(!device) + { + fmt::println(stderr, "Could not open a device!"); + return 1; + } + + if(!alcIsExtensionPresent(device.get(), "ALC_EXT_debug")) + { + fmt::println(stderr, "ALC_EXT_debug not supported on device"); + return 1; + } + + /* Load the Debug API functions we're using. */ +#define LOAD_PROC(N) N = reinterpret_cast(alcGetProcAddress(device.get(), #N)) + LOAD_PROC(alDebugMessageCallbackEXT); + LOAD_PROC(alDebugMessageInsertEXT); + LOAD_PROC(alDebugMessageControlEXT); + LOAD_PROC(alPushDebugGroupEXT); + LOAD_PROC(alPopDebugGroupEXT); + LOAD_PROC(alGetDebugMessageLogEXT); + LOAD_PROC(alObjectLabelEXT); + LOAD_PROC(alGetObjectLabelEXT); + LOAD_PROC(alGetPointerEXT); + LOAD_PROC(alGetPointervEXT); +#undef LOAD_PROC + + /* Create a debug context and set it as current. If -nodebug was specified, + * create a non-debug context (to see how debug messages react). + */ + auto flags = ALCint{ALC_CONTEXT_DEBUG_BIT_EXT}; + if(!args.empty() && args[0] == "-nodebug") + flags &= ~ALC_CONTEXT_DEBUG_BIT_EXT; + + const auto attribs = std::array{{ + ALC_CONTEXT_FLAGS_EXT, flags, + 0 /* end-of-list */ + }}; + auto context = ContextPtr{alcCreateContext(device.get(), attribs.data())}; + if(!context || alcMakeContextCurrent(context.get()) == ALC_FALSE) + { + fmt::println(stderr, "Could not create and set a context!"); + return 1; + } + + /* Enable low-severity debug messages, which are disabled by default. */ + alDebugMessageControlEXT(AL_DONT_CARE_EXT, AL_DONT_CARE_EXT, AL_DEBUG_SEVERITY_LOW_EXT, 0, + nullptr, AL_TRUE); + + fmt::println("Context flags: {:#010x}", as_unsigned(alGetInteger(AL_CONTEXT_FLAGS_EXT))); + + /* A debug context has debug output enabled by default. But in case this + * isn't a debug context, explicitly enable it (probably won't get much, if + * anything, in that case). + */ + fmt::println("Default debug state is: {}", + alIsEnabled(AL_DEBUG_OUTPUT_EXT) ? "enabled"sv : "disabled"sv); + alEnable(AL_DEBUG_OUTPUT_EXT); + + /* The max debug message length property will allow us to define message + * storage of sufficient length. This includes space for the null + * terminator. + */ + const auto maxloglength = alGetInteger(AL_MAX_DEBUG_MESSAGE_LENGTH_EXT); + fmt::println("Max debug message length: {}", maxloglength); + + fmt::println(""); + + /* Doppler Velocity is deprecated since AL 1.1, so this should generate a + * deprecation debug message. We'll first handle debug messages through the + * message log, meaning we'll query for and read it afterward. + */ + fmt::println("Calling alDopplerVelocity(0.5f)..."); + alDopplerVelocity(0.5f); + + for(auto numlogs = alGetInteger(AL_DEBUG_LOGGED_MESSAGES_EXT);numlogs > 0;--numlogs) + { + auto message = std::vector(static_cast(maxloglength), '\0'); + auto source = ALenum{}; + auto type = ALenum{}; + auto id = ALuint{}; + auto severity = ALenum{}; + auto msglength = ALsizei{}; + + /* Getting the message removes it from the log. */ + const auto read = alGetDebugMessageLogEXT(1, maxloglength, &source, &type, &id, &severity, + &msglength, message.data()); + if(read != 1) + { + fmt::println(stderr, "Read {} debug messages, expected to read 1", read); + break; + } + + /* The message lengths returned by alGetDebugMessageLogEXT include the + * null terminator, so subtract one for the string_view length. If we + * read more than one message at a time, the length could be used as + * the offset to the next message. + */ + const auto msgstr = std::string_view{message.data(), + static_cast(msglength ? msglength-1 : 0)}; + fmt::println("Got message from log:\n" + " Source: {}\n" + " Type: {}\n" + " ID: {}\n" + " Severity: {}\n" + " Message: \"{}\"", GetDebugSourceName(source), GetDebugTypeName(type), id, + GetDebugSeverityName(severity), msgstr); + } + fmt::println(""); + + /* Now set up a callback function. This lets us print the debug messages as + * they happen without having to explicitly query and get them. + */ + static constexpr auto debug_callback = [](ALenum source, ALenum type, ALuint id, + ALenum severity, ALsizei length, const ALchar *message, void *userParam [[maybe_unused]]) + noexcept -> void + { + /* The message length provided to the callback does not include the + * null terminator. + */ + const auto msgstr = std::string_view{message, static_cast(length)}; + fmt::println("Got message from callback:\n" + " Source: {}\n" + " Type: {}\n" + " ID: {}\n" + " Severity: {}\n" + " Message: \"{}\"", GetDebugSourceName(source), GetDebugTypeName(type), id, + GetDebugSeverityName(severity), msgstr); + }; + alDebugMessageCallbackEXT(debug_callback, nullptr); + + if(const auto numlogs = alGetInteger(AL_DEBUG_LOGGED_MESSAGES_EXT)) + fmt::println(stderr, "{} left over logged message{}!", numlogs, (numlogs==1)?"":"s"); + + /* This should also generate a deprecation debug message, which will now go + * through the callback. + */ + fmt::println("Calling alGetInteger(AL_DOPPLER_VELOCITY)..."); + auto dv [[maybe_unused]] = alGetInteger(AL_DOPPLER_VELOCITY); + fmt::println(""); + + /* These functions are notoriously unreliable for their behavior, they will + * likely generate portability debug messages. + */ + fmt::println("Calling alcSuspendContext and alcProcessContext..."); + alcSuspendContext(context.get()); + alcProcessContext(context.get()); + fputs("\n", stdout); + + fmt::println("Pushing a debug group, making some invalid calls, and popping the debug group..."); + alPushDebugGroupEXT(AL_DEBUG_SOURCE_APPLICATION_EXT, 0, -1, "Error test group"); + alSpeedOfSound(0.0f); + /* Can't set the label of the null buffer. */ + alObjectLabelEXT(AL_BUFFER, 0, -1, "The null buffer"); + alPopDebugGroupEXT(); + fmt::println(""); + + /* All done, insert a custom message and unset the callback. The context + * and device will clean themselves up. + */ + alDebugMessageInsertEXT(AL_DEBUG_SOURCE_APPLICATION_EXT, AL_DEBUG_TYPE_MARKER_EXT, 0, + AL_DEBUG_SEVERITY_NOTIFICATION_EXT, -1, "End of run, cleaning up"); + alDebugMessageCallbackEXT(nullptr, nullptr); + + return 0; +} + +} // namespace + +int main(int argc, char **argv) +{ + assert(argc >= 0); + auto args = std::vector(static_cast(argc)); + std::copy_n(argv, args.size(), args.begin()); + return main(al::span{args}); +} diff --git a/Engine/lib/openal-soft/examples/aldirect.cpp b/Engine/lib/openal-soft/examples/aldirect.cpp index d7964adda..04de857f8 100644 --- a/Engine/lib/openal-soft/examples/aldirect.cpp +++ b/Engine/lib/openal-soft/examples/aldirect.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -45,6 +44,7 @@ #include "alspan.h" #include "common/alhelpers.h" +#include "fmt/core.h" #include "win_main_utf8.h" @@ -102,12 +102,13 @@ ALuint LoadSound(ALCcontext *context, const std::string_view filename) SndFilePtr sndfile{sf_open(std::string{filename}.c_str(), SFM_READ, &sfinfo)}; if(!sndfile) { - std::cerr<< "Could not open audio in "< sf_count_t{std::numeric_limits::max()}/byteblockalign) { - std::cerr<< "Too many sample frames in "<(num_frames / splblockalign * byteblockalign); - std::cout<< "Loading: "< args) /* Print out usage if no arguments were specified */ if(args.size() < 2) { - std::cerr<< "Usage: "<] \n"; + fmt::println(stderr, "Usage: {} [-device ] ", args[0]); return 1; } @@ -330,20 +330,20 @@ int main(al::span args) { device = p_alcOpenDevice(std::string{args[1]}.c_str()); if(!device) - std::cerr<< "Failed to open \""< args) if(!context) { p_alcCloseDevice(device); - std::cerr<< "Could not create a context!\n"; + fmt::println(stderr, "Could not create a context!"); return 1; } @@ -446,10 +446,10 @@ int main(al::span args) /* Get the source offset. */ ALfloat offset{}; alGetSourcefDirect(context, source, AL_SEC_OFFSET, &offset); - printf("\rOffset: %f ", offset); + fmt::print(" \rOffset: {:.02f}", offset); fflush(stdout); } while(alGetErrorDirect(context) == AL_NO_ERROR && state == AL_PLAYING); - printf("\n"); + fmt::println(""); /* All done. Delete resources, and close down OpenAL. */ alDeleteSourcesDirect(context, 1, &source); diff --git a/Engine/lib/openal-soft/examples/alffplay.cpp b/Engine/lib/openal-soft/examples/alffplay.cpp index e51515e7e..283959999 100644 --- a/Engine/lib/openal-soft/examples/alffplay.cpp +++ b/Engine/lib/openal-soft/examples/alffplay.cpp @@ -19,8 +19,6 @@ #include #include #include -#include -#include #include #include #include @@ -39,7 +37,6 @@ extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libavformat/avio.h" -#include "libavformat/version.h" #include "libavutil/avutil.h" #include "libavutil/error.h" #include "libavutil/frame.h" @@ -48,19 +45,27 @@ extern "C" { #include "libavutil/rational.h" #include "libavutil/samplefmt.h" #include "libavutil/time.h" -#include "libavutil/version.h" #include "libavutil/channel_layout.h" #include "libswscale/swscale.h" #include "libswresample/swresample.h" -constexpr auto AVNoPtsValue = AV_NOPTS_VALUE; -constexpr auto AVErrorEOF = AVERROR_EOF; - struct SwsContext; } #define SDL_MAIN_HANDLED -#include "SDL.h" +#include "SDL3/SDL_events.h" +#include "SDL3/SDL_main.h" +#include "SDL3/SDL_render.h" +#include "SDL3/SDL_video.h" + +namespace { +constexpr auto DefineSDLColorspace(SDL_ColorType type, SDL_ColorRange range, + SDL_ColorPrimaries primaries, SDL_TransferCharacteristics transfer, + SDL_MatrixCoefficients matrix, SDL_ChromaLocation chromaloc) noexcept +{ + return SDL_DEFINE_COLORSPACE(type, range, primaries, transfer, matrix, chromaloc); +} +} // namespace #ifdef __GNUC__ _Pragma("GCC diagnostic pop") #endif @@ -69,14 +74,18 @@ _Pragma("GCC diagnostic pop") #include "AL/al.h" #include "AL/alext.h" +#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "common/alhelpers.h" +#include "fmt/core.h" +#include "fmt/format.h" namespace { +using voidp = void*; using fixed32 = std::chrono::duration>; using nanoseconds = std::chrono::nanoseconds; using microseconds = std::chrono::microseconds; @@ -85,6 +94,16 @@ using seconds = std::chrono::seconds; using seconds_d64 = std::chrono::duration; using std::chrono::duration_cast; +#ifdef __GNUC__ +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") +#endif +constexpr auto AVNoPtsValue = AV_NOPTS_VALUE; +constexpr auto AVErrorEOF = AVERROR_EOF; +#ifdef __GNUC__ +_Pragma("GCC diagnostic pop") +#endif + const std::string AppName{"alffplay"}; ALenum DirectOutMode{AL_FALSE}; @@ -115,7 +134,7 @@ constexpr milliseconds AudioBufferTotalTime{800}; constexpr auto AudioBufferCount = AudioBufferTotalTime / AudioBufferTime; enum { - FF_MOVIE_DONE_EVENT = SDL_USEREVENT + FF_MOVIE_DONE_EVENT = SDL_EVENT_USER }; enum class SyncMaster { @@ -167,6 +186,57 @@ struct SwsContextDeleter { using SwsContextPtr = std::unique_ptr; +struct SDLProps { + SDL_PropertiesID mProperties{}; + + SDLProps() : mProperties{SDL_CreateProperties()} { } + ~SDLProps() { SDL_DestroyProperties(mProperties); } + + SDLProps(const SDLProps&) = delete; + auto operator=(const SDLProps&) -> SDLProps& = delete; + + [[nodiscard]] + auto getid() const noexcept -> SDL_PropertiesID { return mProperties; } + + auto setPointer(const char *name, void *value) const + { return SDL_SetPointerProperty(mProperties, name, value); } + + auto setString(const char *name, const char *value) const + { return SDL_SetStringProperty(mProperties, name, value); } + + auto setInt(const char *name, Sint64 value) const + { return SDL_SetNumberProperty(mProperties, name, value); } +}; + +struct TextureFormatEntry { + AVPixelFormat avformat; + SDL_PixelFormat sdlformat; +}; +constexpr auto TextureFormatMap = std::array{ + TextureFormatEntry{AV_PIX_FMT_RGB8, SDL_PIXELFORMAT_RGB332}, + TextureFormatEntry{AV_PIX_FMT_RGB444, SDL_PIXELFORMAT_XRGB4444}, + TextureFormatEntry{AV_PIX_FMT_RGB555, SDL_PIXELFORMAT_XRGB1555}, + TextureFormatEntry{AV_PIX_FMT_BGR555, SDL_PIXELFORMAT_XBGR1555}, + TextureFormatEntry{AV_PIX_FMT_RGB565, SDL_PIXELFORMAT_RGB565}, + TextureFormatEntry{AV_PIX_FMT_BGR565, SDL_PIXELFORMAT_BGR565}, + TextureFormatEntry{AV_PIX_FMT_RGB24, SDL_PIXELFORMAT_RGB24}, + TextureFormatEntry{AV_PIX_FMT_BGR24, SDL_PIXELFORMAT_BGR24}, + TextureFormatEntry{AV_PIX_FMT_0RGB32, SDL_PIXELFORMAT_XRGB8888}, + TextureFormatEntry{AV_PIX_FMT_0BGR32, SDL_PIXELFORMAT_XBGR8888}, + TextureFormatEntry{AV_PIX_FMT_NE(RGB0, 0BGR), SDL_PIXELFORMAT_RGBX8888}, + TextureFormatEntry{AV_PIX_FMT_NE(BGR0, 0RGB), SDL_PIXELFORMAT_BGRX8888}, + TextureFormatEntry{AV_PIX_FMT_RGB32, SDL_PIXELFORMAT_ARGB8888}, + TextureFormatEntry{AV_PIX_FMT_RGB32_1, SDL_PIXELFORMAT_RGBA8888}, + TextureFormatEntry{AV_PIX_FMT_BGR32, SDL_PIXELFORMAT_ABGR8888}, + TextureFormatEntry{AV_PIX_FMT_BGR32_1, SDL_PIXELFORMAT_BGRA8888}, + TextureFormatEntry{AV_PIX_FMT_YUV420P, SDL_PIXELFORMAT_IYUV}, + TextureFormatEntry{AV_PIX_FMT_YUYV422, SDL_PIXELFORMAT_YUY2}, + TextureFormatEntry{AV_PIX_FMT_UYVY422, SDL_PIXELFORMAT_UYVY}, + TextureFormatEntry{AV_PIX_FMT_NV12, SDL_PIXELFORMAT_NV12}, + TextureFormatEntry{AV_PIX_FMT_NV21, SDL_PIXELFORMAT_NV21}, +}; + + struct ChannelLayout : public AVChannelLayout { ChannelLayout() : AVChannelLayout{} { } ChannelLayout(const ChannelLayout &rhs) : AVChannelLayout{} @@ -188,11 +258,10 @@ class DataQueue { size_t mTotalSize{0}; bool mFinished{false}; - AVPacketPtr getPacket() + auto getPacket() -> AVPacketPtr { - std::unique_lock plock{mPacketMutex}; - while(mPackets.empty() && !mFinished) - mPacketCond.wait(plock); + auto plock = std::unique_lock{mPacketMutex}; + mPacketCond.wait(plock, [this] { return !mPackets.empty() || mFinished; }); if(mPackets.empty()) return nullptr; @@ -203,38 +272,48 @@ class DataQueue { } public: - DataQueue(size_t size_limit) : mSizeLimit{size_limit} { } + explicit DataQueue(size_t size_limit) : mSizeLimit{size_limit} { } int sendPacket(AVCodecContext *codecctx) { - AVPacketPtr packet{getPacket()}; + auto packet = getPacket(); - int ret{}; + auto ret = int{}; { - std::unique_lock flock{mFrameMutex}; - while((ret=avcodec_send_packet(codecctx, packet.get())) == AVERROR(EAGAIN)) - mInFrameCond.wait_for(flock, milliseconds{50}); + auto flock = std::unique_lock{mFrameMutex}; + mInFrameCond.wait(flock, [this,codecctx,pkt=packet.get(),&ret] + { + ret = avcodec_send_packet(codecctx, pkt); + if(ret != AVERROR(EAGAIN)) return true; + mOutFrameCond.notify_one(); + return false; + }); } mOutFrameCond.notify_one(); if(!packet) { if(!ret) return AVErrorEOF; - std::cerr<< "Failed to send flush packet: "< flock{mFrameMutex}; - while((ret=avcodec_receive_frame(codecctx, frame)) == AVERROR(EAGAIN)) - mOutFrameCond.wait_for(flock, milliseconds{50}); + auto flock = std::unique_lock{mFrameMutex}; + mOutFrameCond.wait(flock, [this,codecctx,frame,&ret] + { + ret = avcodec_receive_frame(codecctx, frame); + if(ret != AVERROR(EAGAIN)) return true; + mInFrameCond.notify_one(); + return false; + }); } mInFrameCond.notify_one(); return ret; @@ -243,7 +322,7 @@ public: void setFinished() { { - std::lock_guard packetlock{mPacketMutex}; + auto plock = std::lock_guard{mPacketMutex}; mFinished = true; } mPacketCond.notify_one(); @@ -252,7 +331,7 @@ public: void flush() { { - std::lock_guard packetlock{mPacketMutex}; + auto plock = std::lock_guard{mPacketMutex}; mFinished = true; mPackets.clear(); @@ -261,21 +340,18 @@ public: mPacketCond.notify_one(); } - bool put(const AVPacket *pkt) + auto put(const AVPacket *pkt) -> bool { { - std::lock_guard packet_lock{mPacketMutex}; + auto plock = std::lock_guard{mPacketMutex}; if(mTotalSize >= mSizeLimit || mFinished) return false; - mPackets.push_back(AVPacketPtr{av_packet_alloc()}); - if(av_packet_ref(mPackets.back().get(), pkt) != 0) - { + auto *newpkt = mPackets.emplace_back(AVPacketPtr{av_packet_alloc()}).get(); + if(av_packet_ref(newpkt, pkt) == 0) + mTotalSize += static_cast(newpkt->size); + else mPackets.pop_back(); - return true; - } - - mTotalSize += static_cast(mPackets.back()->size); } mPacketCond.notify_one(); return true; @@ -312,7 +388,7 @@ struct AudioState { /* Storage of converted samples */ std::array mSamples{}; - al::span mSamplesSpan{}; + al::span mSamplesSpan; int mSamplesLen{0}; /* In samples */ int mSamplesPos{0}; int mSamplesMax{0}; @@ -332,7 +408,7 @@ struct AudioState { std::array mBuffers{}; ALuint mBufferIdx{0}; - AudioState(MovieState &movie) : mMovie(movie) + explicit AudioState(MovieState &movie) : mMovie(movie) { mConnected.test_and_set(std::memory_order_relaxed); } ~AudioState() { @@ -341,7 +417,7 @@ struct AudioState { if(mBuffers[0]) alDeleteBuffers(static_cast(mBuffers.size()), mBuffers.data()); - av_freep(mSamples.data()); + av_freep(static_cast(mSamples.data())); } static void AL_APIENTRY eventCallbackC(ALenum eventType, ALuint object, ALuint param, @@ -390,7 +466,7 @@ struct VideoState { SwsContextPtr mSwscaleCtx; struct Picture { - AVFramePtr mFrame{}; + AVFramePtr mFrame; nanoseconds mPts{nanoseconds::min()}; }; std::array mPictQ; @@ -400,12 +476,14 @@ struct VideoState { SDL_Texture *mImage{nullptr}; int mWidth{0}, mHeight{0}; /* Full texture size */ + unsigned int mSDLFormat{SDL_PIXELFORMAT_UNKNOWN}; + int mAVFormat{AV_PIX_FMT_NONE}; bool mFirstUpdate{true}; std::atomic mEOS{false}; std::atomic mFinalUpdate{false}; - VideoState(MovieState &movie) : mMovie(movie) { } + explicit VideoState(MovieState &movie) : mMovie(movie) { } ~VideoState() { if(mImage) @@ -415,7 +493,7 @@ struct VideoState { nanoseconds getClock(); - void display(SDL_Window *screen, SDL_Renderer *renderer, AVFrame *frame) const; + void display(SDL_Renderer *renderer, AVFrame *frame) const; void updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool redraw); int handler(); }; @@ -443,7 +521,7 @@ struct MovieState { std::string mFilename; - MovieState(std::string_view fname) : mAudio{*this}, mVideo{*this}, mFilename{fname} + explicit MovieState(std::string_view fname) : mAudio{*this}, mVideo{*this}, mFilename{fname} { } ~MovieState() { @@ -670,7 +748,7 @@ int AudioState::decodeFrame() while(int ret{mQueue.receiveFrame(mCodecCtx.get(), mDecodedFrame.get())}) { if(ret == AVErrorEOF) return 0; - std::cerr<< "Failed to receive frame: "<nb_samples <= 0); @@ -681,19 +759,15 @@ int AudioState::decodeFrame() if(mDecodedFrame->nb_samples > mSamplesMax) { - av_freep(mSamples.data()); + av_freep(static_cast(mSamples.data())); av_samples_alloc(mSamples.data(), nullptr, mCodecCtx->ch_layout.nb_channels, mDecodedFrame->nb_samples, mDstSampleFmt, 0); mSamplesMax = mDecodedFrame->nb_samples; mSamplesSpan = {mSamples[0], static_cast(mSamplesMax)*mFrameSize}; } - /* Copy to a local to mark const. Don't know why this can't be implicit. */ - using data_t = decltype(decltype(mDecodedFrame)::element_type::data); - std::array> cdata{}; - std::copy(std::begin(mDecodedFrame->data), std::end(mDecodedFrame->data), cdata.begin()); /* Return the amount of sample frames converted */ const int data_size{swr_convert(mSwresCtx.get(), mSamples.data(), mDecodedFrame->nb_samples, - cdata.data(), mDecodedFrame->nb_samples)}; + mDecodedFrame->extended_data, mDecodedFrame->nb_samples)}; av_frame_unref(mDecodedFrame.get()); return data_size; @@ -743,8 +817,7 @@ bool AudioState::readAudio(al::span samples, unsigned int length, int & unsigned int rem{length - audio_size}; if(mSamplesPos >= 0) { - const auto len = static_cast(mSamplesLen - mSamplesPos); - if(rem > len) rem = len; + rem = std::min(rem, static_cast(mSamplesLen - mSamplesPos)); const size_t boffset{static_cast(mSamplesPos) * size_t{mFrameSize}}; std::copy_n(mSamplesSpan.cbegin()+ptrdiff_t(boffset), rem*size_t{mFrameSize}, samples.begin()); @@ -860,21 +933,19 @@ void AL_APIENTRY AudioState::eventCallback(ALenum eventType, ALuint object, ALui return; } - std::cout<< "\n---- AL Event on AudioState "<(length)}<<"\n----"<< - std::endl; + fmt::println("\n" + "Object ID: {}\n" + "Parameter: {}\n" + "Message: {}\n----", + object, param, std::string_view{message, static_cast(length)}); if(eventType == AL_EVENT_TYPE_DISCONNECTED_SOFT) { @@ -923,7 +994,7 @@ int AudioState::handler() AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, AL_EVENT_TYPE_DISCONNECTED_SOFT}}; - EventControlManager(milliseconds &sleep_time) + explicit EventControlManager(milliseconds &sleep_time) { if(alEventControlSOFT) { @@ -948,6 +1019,18 @@ int AudioState::handler() std::vector samples; ALsizei buffer_len{0}; + /* Note that ffmpeg assumes AmbiX (ACN layout, SN3D normalization). Only + * support HOA when OpenAL can take AmbiX natively (if AmbiX -> FuMa + * conversion is needed, we don't bother with higher order channels). + */ + const auto has_bfmt = bool{alIsExtensionPresent("AL_EXT_BFORMAT") != AL_FALSE}; + const auto has_bfmt_ex = bool{alIsExtensionPresent("AL_SOFT_bformat_ex") != AL_FALSE}; + const auto has_bfmt_hoa = bool{has_bfmt_ex + && alIsExtensionPresent("AL_SOFT_bformat_hoa") != AL_FALSE}; + /* AL_SOFT_bformat_hoa supports up to 14th order (225 channels). */ + const auto max_ambi_order = has_bfmt_hoa ? 14 : 1; + auto ambi_order = 0; + /* Find a suitable format for OpenAL. */ const auto layoutmask = mCodecCtx->ch_layout.u.mask; /* NOLINT(*-union-access) */ mDstChanLayout = 0; @@ -983,7 +1066,8 @@ int AudioState::handler() { mDstChanLayout = layoutmask; mFrameSize *= 4; - mFormat = alGetEnumValue("AL_FORMAT_QUAD32"); + mFormat = EnableUhj ? AL_FORMAT_UHJ4CHN_FLOAT32_SOFT + : alGetEnumValue("AL_FORMAT_QUAD32"); } } if(layoutmask == AV_CH_LAYOUT_MONO) @@ -993,8 +1077,7 @@ int AudioState::handler() mFormat = AL_FORMAT_MONO_FLOAT32; } } - else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC - && alIsExtensionPresent("AL_EXT_BFORMAT")) + else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC && has_bfmt) { /* Calculate what should be the ambisonic order from the number of * channels, and confirm that's the number of channels. Opus allows @@ -1002,14 +1085,16 @@ int AudioState::handler() * which we can ignore, so check for that too. */ auto order = static_cast(std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1; - int channels{(order+1) * (order+1)}; - if(channels == mCodecCtx->ch_layout.nb_channels - || channels+2 == mCodecCtx->ch_layout.nb_channels) + auto channels = ALuint(order+1) * ALuint(order+1); + if(channels == ALuint(mCodecCtx->ch_layout.nb_channels) + || channels+2 == ALuint(mCodecCtx->ch_layout.nb_channels)) { /* OpenAL only supports first-order with AL_EXT_BFORMAT, which - * is 4 channels for 3D buffers. + * is 4 channels for 3D buffers, unless AL_SOFT_bformat_hoa is + * also supported. */ - mFrameSize *= 4; + ambi_order = std::min(order, max_ambi_order); + mFrameSize *= ALuint(ambi_order+1) * ALuint(ambi_order+1); mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_FLOAT32"); } } @@ -1044,7 +1129,8 @@ int AudioState::handler() { mDstChanLayout = layoutmask; mFrameSize *= 4; - mFormat = alGetEnumValue("AL_FORMAT_QUAD8"); + mFormat = EnableUhj ? AL_FORMAT_UHJ4CHN8_SOFT + : alGetEnumValue("AL_FORMAT_QUAD8"); } } if(layoutmask == AV_CH_LAYOUT_MONO) @@ -1054,15 +1140,15 @@ int AudioState::handler() mFormat = AL_FORMAT_MONO8; } } - else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC - && alIsExtensionPresent("AL_EXT_BFORMAT")) + else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC && has_bfmt) { auto order = static_cast(std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1; - int channels{(order+1) * (order+1)}; + auto channels = (order+1) * (order+1); if(channels == mCodecCtx->ch_layout.nb_channels || channels+2 == mCodecCtx->ch_layout.nb_channels) { - mFrameSize *= 4; + ambi_order = std::min(order, max_ambi_order); + mFrameSize *= ALuint(ambi_order+1) * ALuint(ambi_order+1); mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_8"); } } @@ -1097,7 +1183,8 @@ int AudioState::handler() { mDstChanLayout = layoutmask; mFrameSize *= 4; - mFormat = alGetEnumValue("AL_FORMAT_QUAD16"); + mFormat = EnableUhj ? AL_FORMAT_UHJ4CHN16_SOFT + : alGetEnumValue("AL_FORMAT_QUAD16"); } } if(layoutmask == AV_CH_LAYOUT_MONO) @@ -1107,15 +1194,15 @@ int AudioState::handler() mFormat = AL_FORMAT_MONO16; } } - else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC - && alIsExtensionPresent("AL_EXT_BFORMAT")) + else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC && has_bfmt) { auto order = static_cast(std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1; - int channels{(order+1) * (order+1)}; + auto channels = (order+1) * (order+1); if(channels == mCodecCtx->ch_layout.nb_channels || channels+2 == mCodecCtx->ch_layout.nb_channels) { - mFrameSize *= 4; + ambi_order = std::min(order, max_ambi_order); + mFrameSize *= ALuint(ambi_order+1) * ALuint(ambi_order+1); mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_16"); } } @@ -1136,40 +1223,33 @@ int AudioState::handler() mDecodedFrame.reset(av_frame_alloc()); if(!mDecodedFrame) { - std::cerr<< "Failed to allocate audio frame" <sample_rate, - &mCodecCtx->ch_layout, mCodecCtx->sample_fmt, mCodecCtx->sample_rate, 0, nullptr)}; - mSwresCtx.reset(ps); + const auto err = swr_alloc_set_opts2(al::out_ptr(mSwresCtx), &layout, mDstSampleFmt, + mCodecCtx->sample_rate, &mCodecCtx->ch_layout, mCodecCtx->sample_fmt, + mCodecCtx->sample_rate, 0, nullptr); if(err != 0) { std::array errstr{}; - std::cerr<< "Failed to allocate SwrContext: " - < 1) + fmt::println("Found AL_SOFT_bformat_hoa (order {})", ambi_order); + else if(has_bfmt_ex) + fmt::println("Found AL_SOFT_bformat_ex"); else { - std::cout<< "Found AL_EXT_BFORMAT" <sample_rate, - &mCodecCtx->ch_layout, mCodecCtx->sample_fmt, mCodecCtx->sample_rate, 0, nullptr)}; - mSwresCtx.reset(ps); + int err{swr_alloc_set_opts2(al::out_ptr(mSwresCtx), &layout, mDstSampleFmt, + mCodecCtx->sample_rate, &mCodecCtx->ch_layout, mCodecCtx->sample_fmt, + mCodecCtx->sample_rate, 0, nullptr)}; if(err != 0) { std::array errstr{}; - std::cerr<< "Failed to allocate SwrContext: " - < errstr{}; - std::cerr<< "Failed to initialize audio converter: " - < 1) + { + for(ALuint bufid : mBuffers) + alBufferi(bufid, AL_UNPACK_AMBISONIC_ORDER_SOFT, ambi_order); + } #ifdef AL_SOFT_UHJ if(EnableSuperStereo) alSourcei(mSource, AL_STEREO_MODE_SOFT, AL_SUPER_STEREO_SOFT); @@ -1241,7 +1325,7 @@ int AudioState::handler() alSourcei(mSource, AL_BUFFER, static_cast(mBuffers[0])); if(alGetError() != AL_NO_ERROR) { - fprintf(stderr, "Failed to set buffer callback\n"); + fmt::println(stderr, "Failed to set buffer callback"); alSourcei(mSource, AL_BUFFER, 0); } else @@ -1274,7 +1358,7 @@ int AudioState::handler() if(ret == AVErrorEOF) break; } }; - auto sender = std::async(std::launch::async, packet_sender); + auto sender [[maybe_unused]] = std::async(std::launch::async, packet_sender); srclock.lock(); if(alcGetInteger64vSOFT) @@ -1383,8 +1467,7 @@ int AudioState::handler() break; } if(ALenum err{alGetError()}) - std::cerr<< "Got AL error: 0x"<width - static_cast(frame->crop_left + frame->crop_right); + auto frame_height = frame->height - static_cast(frame->crop_top + frame->crop_bottom); - int frame_width{frame->width - static_cast(frame->crop_left + frame->crop_right)}; - int frame_height{frame->height - static_cast(frame->crop_top + frame->crop_bottom)}; - if(frame->sample_aspect_ratio.num == 0) - aspect_ratio = 0.0; - else - { - aspect_ratio = av_q2d(frame->sample_aspect_ratio) * frame_width / - frame_height; - } - if(aspect_ratio <= 0.0) - aspect_ratio = static_cast(frame_width) / frame_height; + const auto src_rect = SDL_FRect{ static_cast(frame->crop_left), + static_cast(frame->crop_top), static_cast(frame_width), + static_cast(frame_height) }; - SDL_GetWindowSize(screen, &win_w, &win_h); - h = win_h; - w = (static_cast(std::rint(h * aspect_ratio)) + 3) & ~3; - if(w > win_w) - { - w = win_w; - h = (static_cast(std::rint(w / aspect_ratio)) + 3) & ~3; - } - x = (win_w - w) / 2; - y = (win_h - h) / 2; - - SDL_Rect src_rect{ static_cast(frame->crop_left), static_cast(frame->crop_top), - frame_width, frame_height }; - SDL_Rect dst_rect{ x, y, w, h }; - SDL_RenderCopy(renderer, mImage, &src_rect, &dst_rect); + SDL_RenderTexture(renderer, mImage, &src_rect, nullptr); SDL_RenderPresent(renderer); } @@ -1493,18 +1553,174 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re mPictQCond.notify_one(); /* allocate or resize the buffer! */ - bool fmt_updated{false}; - if(!mImage || mWidth != frame->width || mHeight != frame->height) + if(!mImage || mWidth != frame->width || mHeight != frame->height + || frame->format != mAVFormat) { - fmt_updated = true; if(mImage) SDL_DestroyTexture(mImage); - mImage = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, - frame->width, frame->height); - if(!mImage) - std::cerr<< "Failed to create YV12 texture!" <width; - mHeight = frame->height; + mImage = nullptr; + mSwscaleCtx = nullptr; + + auto fmtiter = std::find_if(TextureFormatMap.begin(), TextureFormatMap.end(), + [frame](const TextureFormatEntry &entry) noexcept + { return frame->format == entry.avformat; }); + if(fmtiter != TextureFormatMap.end()) + { + auto props = SDLProps{}; + props.setInt(SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, fmtiter->sdlformat); + props.setInt(SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING); + props.setInt(SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, frame->width); + props.setInt(SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, frame->height); + + /* Should be a better way to check YCbCr vs RGB. */ + const auto ctype = (frame->format == AV_PIX_FMT_YUV420P + || frame->format == AV_PIX_FMT_YUYV422 + || frame->format == AV_PIX_FMT_UYVY422 || frame->format == AV_PIX_FMT_NV12 + || frame->format == AV_PIX_FMT_NV21) ? SDL_COLOR_TYPE_YCBCR + : SDL_COLOR_TYPE_RGB; + const auto crange = std::invoke([frame] + { + switch(frame->color_range) + { + case AVCOL_RANGE_UNSPECIFIED: return SDL_COLOR_RANGE_UNKNOWN; + case AVCOL_RANGE_MPEG: return SDL_COLOR_RANGE_LIMITED; + case AVCOL_RANGE_JPEG: return SDL_COLOR_RANGE_FULL; + case AVCOL_RANGE_NB: break; + } + return SDL_COLOR_RANGE_UNKNOWN; + }); + const auto cprims = std::invoke([frame] + { + switch(frame->color_primaries) + { + case AVCOL_PRI_RESERVED0: break; + case AVCOL_PRI_BT709: return SDL_COLOR_PRIMARIES_BT709; + case AVCOL_PRI_UNSPECIFIED: return SDL_COLOR_PRIMARIES_UNSPECIFIED; + case AVCOL_PRI_RESERVED: break; + case AVCOL_PRI_BT470M: return SDL_COLOR_PRIMARIES_BT470M; + case AVCOL_PRI_BT470BG: return SDL_COLOR_PRIMARIES_BT470BG; + case AVCOL_PRI_SMPTE170M: return SDL_COLOR_PRIMARIES_BT601; + case AVCOL_PRI_SMPTE240M: return SDL_COLOR_PRIMARIES_SMPTE240; + case AVCOL_PRI_FILM: return SDL_COLOR_PRIMARIES_GENERIC_FILM; + case AVCOL_PRI_BT2020: return SDL_COLOR_PRIMARIES_BT2020; + case AVCOL_PRI_SMPTE428: return SDL_COLOR_PRIMARIES_XYZ; + case AVCOL_PRI_SMPTE431: return SDL_COLOR_PRIMARIES_SMPTE431; + case AVCOL_PRI_SMPTE432: return SDL_COLOR_PRIMARIES_SMPTE432; + case AVCOL_PRI_EBU3213: return SDL_COLOR_PRIMARIES_EBU3213; + case AVCOL_PRI_NB: break; + } + return SDL_COLOR_PRIMARIES_UNKNOWN; + }); + const auto ctransfer = std::invoke([frame] + { + switch(frame->color_trc) + { + case AVCOL_TRC_RESERVED0: break; + case AVCOL_TRC_BT709: return SDL_TRANSFER_CHARACTERISTICS_BT709; + case AVCOL_TRC_UNSPECIFIED: return SDL_TRANSFER_CHARACTERISTICS_UNSPECIFIED; + case AVCOL_TRC_RESERVED: break; + case AVCOL_TRC_GAMMA22: return SDL_TRANSFER_CHARACTERISTICS_GAMMA22; + case AVCOL_TRC_GAMMA28: return SDL_TRANSFER_CHARACTERISTICS_GAMMA28; + case AVCOL_TRC_SMPTE170M: return SDL_TRANSFER_CHARACTERISTICS_BT601; + case AVCOL_TRC_SMPTE240M: return SDL_TRANSFER_CHARACTERISTICS_SMPTE240; + case AVCOL_TRC_LINEAR: return SDL_TRANSFER_CHARACTERISTICS_LINEAR; + case AVCOL_TRC_LOG: return SDL_TRANSFER_CHARACTERISTICS_LOG100; + case AVCOL_TRC_LOG_SQRT: return SDL_TRANSFER_CHARACTERISTICS_LOG100_SQRT10; + case AVCOL_TRC_IEC61966_2_4: return SDL_TRANSFER_CHARACTERISTICS_IEC61966; + case AVCOL_TRC_BT1361_ECG: return SDL_TRANSFER_CHARACTERISTICS_BT1361; + case AVCOL_TRC_IEC61966_2_1: return SDL_TRANSFER_CHARACTERISTICS_SRGB; + case AVCOL_TRC_BT2020_10: return SDL_TRANSFER_CHARACTERISTICS_BT2020_10BIT; + case AVCOL_TRC_BT2020_12: return SDL_TRANSFER_CHARACTERISTICS_BT2020_12BIT; + case AVCOL_TRC_SMPTE2084: return SDL_TRANSFER_CHARACTERISTICS_PQ; + case AVCOL_TRC_SMPTE428: return SDL_TRANSFER_CHARACTERISTICS_SMPTE428; + case AVCOL_TRC_ARIB_STD_B67: return SDL_TRANSFER_CHARACTERISTICS_HLG; + case AVCOL_TRC_NB: break; + } + return SDL_TRANSFER_CHARACTERISTICS_UNKNOWN; + }); + const auto cmatrix = std::invoke([frame] + { + switch(frame->colorspace) + { + case AVCOL_SPC_RGB: return SDL_MATRIX_COEFFICIENTS_IDENTITY; + case AVCOL_SPC_BT709: return SDL_MATRIX_COEFFICIENTS_BT709; + case AVCOL_SPC_UNSPECIFIED: return SDL_MATRIX_COEFFICIENTS_UNSPECIFIED; + case AVCOL_SPC_RESERVED: break; + case AVCOL_SPC_FCC: return SDL_MATRIX_COEFFICIENTS_FCC; + case AVCOL_SPC_BT470BG: return SDL_MATRIX_COEFFICIENTS_BT470BG; + case AVCOL_SPC_SMPTE170M: return SDL_MATRIX_COEFFICIENTS_BT601; + case AVCOL_SPC_SMPTE240M: return SDL_MATRIX_COEFFICIENTS_SMPTE240; + case AVCOL_SPC_YCGCO: return SDL_MATRIX_COEFFICIENTS_YCGCO; + case AVCOL_SPC_BT2020_NCL: return SDL_MATRIX_COEFFICIENTS_BT2020_NCL; + case AVCOL_SPC_BT2020_CL: return SDL_MATRIX_COEFFICIENTS_BT2020_CL; + case AVCOL_SPC_SMPTE2085: return SDL_MATRIX_COEFFICIENTS_SMPTE2085; + case AVCOL_SPC_CHROMA_DERIVED_NCL: return SDL_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL; + case AVCOL_SPC_CHROMA_DERIVED_CL: return SDL_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL; + case AVCOL_SPC_ICTCP: return SDL_MATRIX_COEFFICIENTS_ICTCP; + case AVCOL_SPC_IPT_C2: break; // ??? + case AVCOL_SPC_YCGCO_RE: return SDL_MATRIX_COEFFICIENTS_YCGCO; // ??? + case AVCOL_SPC_YCGCO_RO: return SDL_MATRIX_COEFFICIENTS_YCGCO; // ??? + case AVCOL_SPC_NB: break; + } + return SDL_MATRIX_COEFFICIENTS_UNSPECIFIED; + }); + const auto cchromaloc = std::invoke([frame] + { + switch(frame->chroma_location) + { + case AVCHROMA_LOC_UNSPECIFIED: return SDL_CHROMA_LOCATION_NONE; + case AVCHROMA_LOC_LEFT: return SDL_CHROMA_LOCATION_LEFT; + case AVCHROMA_LOC_CENTER: return SDL_CHROMA_LOCATION_CENTER; + case AVCHROMA_LOC_TOPLEFT: return SDL_CHROMA_LOCATION_TOPLEFT; + case AVCHROMA_LOC_TOP: return SDL_CHROMA_LOCATION_TOPLEFT; // ??? + case AVCHROMA_LOC_BOTTOMLEFT: return SDL_CHROMA_LOCATION_LEFT; // ??? + case AVCHROMA_LOC_BOTTOM: return SDL_CHROMA_LOCATION_CENTER; // ??? + case AVCHROMA_LOC_NB: break; + } + return SDL_CHROMA_LOCATION_NONE; + }); + + const auto colorspace = DefineSDLColorspace(ctype, crange, cprims, ctransfer, + cmatrix, cchromaloc); + props.setInt(SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, colorspace); + + mImage = SDL_CreateTextureWithProperties(renderer, props.getid()); + if(!mImage) + fmt::println(stderr, "Failed to create texture!"); + mWidth = frame->width; + mHeight = frame->height; + mSDLFormat = fmtiter->sdlformat; + mAVFormat = fmtiter->avformat; + } + else + { + /* If there's no matching format, convert to RGB24. */ + fmt::println(stderr, "Could not find SDL texture format for pix_fmt {0:#x} ({0})", + as_unsigned(frame->format)); + + auto props = SDLProps{}; + props.setInt(SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, SDL_PIXELFORMAT_RGB24); + props.setInt(SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING); + props.setInt(SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, frame->width); + props.setInt(SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, frame->height); + + mImage = SDL_CreateTextureWithProperties(renderer, props.getid()); + if(!mImage) + fmt::println(stderr, "Failed to create texture!"); + mWidth = frame->width; + mHeight = frame->height; + mSDLFormat = SDL_PIXELFORMAT_RGB24; + mAVFormat = frame->format; + + mSwscaleCtx = SwsContextPtr{sws_getContext( + frame->width, frame->height, static_cast(frame->format), + frame->width, frame->height, AV_PIX_FMT_RGB24, 0, + nullptr, nullptr, nullptr)}; + + sws_setColorspaceDetails(mSwscaleCtx.get(), sws_getCoefficients(frame->colorspace), + (frame->color_range==AVCOL_RANGE_JPEG), sws_getCoefficients(SWS_CS_DEFAULT), 1, + 0<<16, 1<<16, 1<<16); + } } int frame_width{frame->width - static_cast(frame->crop_left + frame->crop_right)}; @@ -1522,50 +1738,45 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re else if(aspect_ratio > 0.0) frame_height = static_cast(std::lround(frame_height / aspect_ratio)); } - SDL_SetWindowSize(screen, frame_width, frame_height); + if(SDL_SetWindowSize(screen, frame_width, frame_height)) + SDL_SyncWindow(screen); + SDL_SetRenderLogicalPresentation(renderer, frame_width, frame_height, + SDL_LOGICAL_PRESENTATION_LETTERBOX); } if(mImage) { - void *pixels{nullptr}; - int pitch{0}; - - if(mCodecCtx->pix_fmt == AV_PIX_FMT_YUV420P) + if(mSDLFormat == SDL_PIXELFORMAT_IYUV || mSDLFormat == SDL_PIXELFORMAT_YV12) SDL_UpdateYUVTexture(mImage, nullptr, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], - frame->data[2], frame->linesize[2] - ); - else if(SDL_LockTexture(mImage, nullptr, &pixels, &pitch) != 0) - std::cerr<< "Failed to lock texture" <data[2], frame->linesize[2]); + else if(mSDLFormat == SDL_PIXELFORMAT_NV12 || mSDLFormat == SDL_PIXELFORMAT_NV21) + SDL_UpdateNVTexture(mImage, nullptr, + frame->data[0], frame->linesize[0], + frame->data[1], frame->linesize[1]); + else if(mSwscaleCtx) { - // Convert the image into YUV format that SDL uses - int w{frame->width}; - int h{frame->height}; - if(!mSwscaleCtx || fmt_updated) + auto pixels = voidp{}; + auto pitch = int{}; + if(!SDL_LockTexture(mImage, nullptr, &pixels, &pitch)) + fmt::println(stderr, "Failed to lock texture: {}", SDL_GetError()); + else { - mSwscaleCtx.reset(sws_getContext( - w, h, mCodecCtx->pix_fmt, - w, h, AV_PIX_FMT_YUV420P, 0, - nullptr, nullptr, nullptr - )); + /* Formats passing through mSwscaleCtx are converted to + * 24-bit RGB, which is interleaved/non-planar. + */ + const auto pict_data = std::array{static_cast(pixels)}; + const auto pict_linesize = std::array{pitch}; + + sws_scale(mSwscaleCtx.get(), std::data(frame->data), + std::data(frame->linesize), 0, frame->height, pict_data.data(), + pict_linesize.data()); + SDL_UnlockTexture(mImage); } - - /* point pict at the queue */ - const auto framesize = static_cast(w)*static_cast(h); - const auto pixelspan = al::span{static_cast(pixels), framesize*3/2}; - const std::array pict_data{ - al::to_address(pixelspan.begin()), - al::to_address(pixelspan.begin() + ptrdiff_t{w}*h), - al::to_address(pixelspan.begin() + ptrdiff_t{w}*h + ptrdiff_t{w}*h/4) - }; - const std::array pict_linesize{pitch, pitch/2, pitch/2}; - - sws_scale(mSwscaleCtx.get(), std::data(frame->data), std::data(frame->linesize), - 0, h, pict_data.data(), pict_linesize.data()); - SDL_UnlockTexture(mImage); } + else + SDL_UpdateTexture(mImage, nullptr, frame->data[0], frame->linesize[0]); redraw = true; } @@ -1574,7 +1785,7 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re if(redraw) { /* Show the picture! */ - display(screen, renderer, frame); + display(renderer, frame); } if(updated) @@ -1603,15 +1814,14 @@ int VideoState::handler() { pict.mFrame = AVFramePtr{av_frame_alloc()}; }); /* Prefill the codec buffer. */ - auto packet_sender = [this]() + auto sender [[maybe_unused]] = std::async(std::launch::async, [this]() { while(true) { const int ret{mQueue.sendPacket(mCodecCtx.get())}; if(ret == AVErrorEOF) break; } - }; - auto sender = std::async(std::launch::async, packet_sender); + }); { std::lock_guard displock{mDispPtsMutex}; @@ -1625,12 +1835,17 @@ int VideoState::handler() Picture *vp{&mPictQ[write_idx]}; /* Retrieve video frame. */ - AVFrame *decoded_frame{vp->mFrame.get()}; - while(int ret{mQueue.receiveFrame(mCodecCtx.get(), decoded_frame)}) + auto get_frame = [this](AVFrame *frame) -> AVFrame* { - if(ret == AVErrorEOF) goto finish; - std::cerr<< "Failed to receive frame: "<mFrame.get()); + if(!decoded_frame) break; /* Get the PTS for this frame. */ if(decoded_frame->best_effort_timestamp != AVNoPtsValue) @@ -1652,16 +1867,16 @@ int VideoState::handler() if(write_idx == mPictQRead.load(std::memory_order_acquire)) { /* Wait until we have space for a new pic */ - std::unique_lock lock{mPictQMutex}; - while(write_idx == mPictQRead.load(std::memory_order_acquire)) - mPictQCond.wait(lock); + auto lock = std::unique_lock{mPictQMutex}; + mPictQCond.wait(lock, [write_idx,this]() noexcept + { return write_idx != mPictQRead.load(std::memory_order_acquire); }); } } -finish: + mEOS = true; - std::unique_lock lock{mPictQMutex}; - while(!mFinalUpdate) mPictQCond.wait(lock); + auto lock = std::unique_lock{mPictQMutex}; + mPictQCond.wait(lock, [this]() noexcept { return mFinalUpdate.load(); }); return 0; } @@ -1674,39 +1889,36 @@ int MovieState::decode_interrupt_cb(void *ctx) bool MovieState::prepare() { - AVIOContext *avioctx{nullptr}; - AVIOInterruptCB intcb{decode_interrupt_cb, this}; - if(avio_open2(&avioctx, mFilename.c_str(), AVIO_FLAG_READ, &intcb, nullptr)) + auto intcb = AVIOInterruptCB{decode_interrupt_cb, this}; + if(avio_open2(al::out_ptr(mIOContext), mFilename.c_str(), AVIO_FLAG_READ, &intcb, nullptr) < 0) { - std::cerr<< "Failed to open "<pb = mIOContext.get(); - fmtctx->interrupt_callback = intcb; - if(avformat_open_input(&fmtctx, mFilename.c_str(), nullptr, nullptr) != 0) + mFormatCtx.reset(avformat_alloc_context()); + mFormatCtx->pb = mIOContext.get(); + mFormatCtx->interrupt_callback = intcb; + if(avformat_open_input(al::inout_ptr(mFormatCtx), mFilename.c_str(), nullptr, nullptr) < 0) { - std::cerr<< "Failed to open "< slock{mStartupMutex}; while(!mStartupDone) mStartupCond.wait(slock); @@ -1715,12 +1927,12 @@ bool MovieState::prepare() void MovieState::setTitle(SDL_Window *window) const { - auto pos1 = mFilename.rfind('/'); - auto pos2 = mFilename.rfind('\\'); - auto fpos = ((pos1 == std::string::npos) ? pos2 : - (pos2 == std::string::npos) ? pos1 : - std::max(pos1, pos2)) + 1; - SDL_SetWindowTitle(window, (mFilename.substr(fpos)+" - "+AppName).c_str()); + /* rfind returns npos if the char isn't found, and npos+1==0, which will + * give the desired result for finding the filename portion. + */ + const auto fpos = std::max(mFilename.rfind('/')+1, mFilename.rfind('\\')+1); + const auto title = fmt::format("{} - {}", std::string_view{mFilename}.substr(fpos), AppName); + SDL_SetWindowTitle(window, title.c_str()); } nanoseconds MovieState::getClock() const @@ -1756,8 +1968,8 @@ bool MovieState::streamComponentOpen(AVStream *stream) const AVCodec *codec{avcodec_find_decoder(avctx->codec_id)}; if(!codec || avcodec_open2(avctx.get(), codec, nullptr) < 0) { - std::cerr<< "Unsupported codec: "<codec_id) - << " (0x"<codec_id<codec_id), + al::to_underlying(avctx->codec_id)); return false; } @@ -1810,7 +2022,7 @@ int MovieState::parse_handler() if(video_index < 0 && audio_index < 0) { - std::cerr<< mFilename<<": could not open codecs" <= 0) - mAudioThread = std::thread{std::mem_fn(&AudioState::handler), &mAudio}; + mAudioThread = std::thread{&AudioState::handler, &mAudio}; if(video_index >= 0) - mVideoThread = std::thread{std::mem_fn(&VideoState::handler), &mVideo}; + mVideoThread = std::thread{&VideoState::handler, &mVideo}; /* Main packet reading/dispatching loop */ AVPacketPtr packet{av_packet_alloc()}; @@ -1874,97 +2086,58 @@ void MovieState::stop() } -// Helper class+method to print the time with human-readable formatting. -struct PrettyTime { - seconds mTime; -}; -std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs) +// Helper method to print the time with human-readable formatting. +auto PrettyTime(seconds t) -> std::string { - using hours = std::chrono::hours; using minutes = std::chrono::minutes; + using hours = std::chrono::hours; - seconds t{rhs.mTime}; if(t.count() < 0) - { - os << '-'; - t *= -1; - } + return "0s"; // Only handle up to hour formatting if(t >= hours{1}) - os << duration_cast(t).count() << 'h' << std::setfill('0') << std::setw(2) - << (duration_cast(t).count() % 60) << 'm'; - else - os << duration_cast(t).count() << 'm' << std::setfill('0'); - os << std::setw(2) << (duration_cast(t).count() % 60) << 's' << std::setw(0) - << std::setfill(' '); - return os; + return fmt::format("{}h{:02}m{:02}s", duration_cast(t).count(), + duration_cast(t).count()%60, t.count()%60); + return fmt::format("{}m{:02}s", duration_cast(t).count(), t.count()%60); } - int main(al::span args) { SDL_SetMainReady(); - std::unique_ptr movState; - if(args.size() < 2) { - std::cerr<< "Usage: "<] [-direct] " <] [-direct] ", args[0]); return 1; } - /* Register all formats and codecs */ -#if !(LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)) - av_register_all(); -#endif + /* Initialize networking protocols */ avformat_network_init(); - if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) + if(!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { - std::cerr<< "Could not initialize SDL - <<"< args) ALCdevice *device{alcGetContextsDevice(alcGetCurrentContext())}; if(alcIsExtensionPresent(device,"ALC_SOFT_device_clock")) { - std::cout<< "Found ALC_SOFT_device_clock" <( alcGetProcAddress(device, "alcGetInteger64vSOFT")); } @@ -1986,13 +2159,13 @@ int main(al::span args) if(alIsExtensionPresent("AL_SOFT_source_latency")) { - std::cout<< "Found AL_SOFT_source_latency" <( alGetProcAddress("alGetSourcei64vSOFT")); } if(alIsExtensionPresent("AL_SOFT_events")) { - std::cout<< "Found AL_SOFT_events" <( alGetProcAddress("alEventControlSOFT")); alEventCallbackSOFT = reinterpret_cast( @@ -2000,7 +2173,7 @@ int main(al::span args) } if(alIsExtensionPresent("AL_SOFT_callback_buffer")) { - std::cout<< "Found AL_SOFT_callback_buffer" <( alGetProcAddress("alBufferCallbackSOFT")); } @@ -2012,44 +2185,44 @@ int main(al::span args) { if(alIsExtensionPresent("AL_SOFT_direct_channels_remix")) { - std::cout<< "Found AL_SOFT_direct_channels_remix" < args) break; } + auto movState = std::unique_ptr{}; while(fileidx < args.size() && !movState) { movState = std::make_unique(args[fileidx++]); @@ -2066,7 +2240,7 @@ int main(al::span args) } if(!movState) { - std::cerr<< "Could not start a video" <setTitle(screen); @@ -2078,32 +2252,32 @@ int main(al::span args) seconds last_time{seconds::min()}; while(true) { - /* SDL_WaitEventTimeout is broken, just force a 10ms sleep. */ - std::this_thread::sleep_for(milliseconds{10}); + auto event = SDL_Event{}; + auto have_event = SDL_WaitEventTimeout(&event, 10); - auto cur_time = std::chrono::duration_cast(movState->getMasterClock()); + const auto cur_time = duration_cast(movState->getMasterClock()); if(cur_time != last_time) { - auto end_time = std::chrono::duration_cast(movState->getDuration()); - std::cout<< " \r "<(movState->getDuration()); + fmt::print(" \r {} / {}", PrettyTime(cur_time), PrettyTime(end_time)); + fflush(stdout); last_time = cur_time; } - bool force_redraw{false}; - SDL_Event event{}; - while(SDL_PollEvent(&event) != 0) + auto force_redraw = false; + while(have_event) { switch(event.type) { - case SDL_KEYDOWN: - switch(event.key.keysym.sym) + case SDL_EVENT_KEY_DOWN: + switch(event.key.key) { case SDLK_ESCAPE: movState->stop(); eom_action = EomAction::Quit; break; - case SDLK_n: + case SDLK_N: movState->stop(); eom_action = EomAction::Next; break; @@ -2113,31 +2287,24 @@ int main(al::span args) } break; - case SDL_WINDOWEVENT: - switch(event.window.event) - { - case SDL_WINDOWEVENT_RESIZED: - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); - SDL_RenderFillRect(renderer, nullptr); - force_redraw = true; - break; - - case SDL_WINDOWEVENT_EXPOSED: - force_redraw = true; - break; - - default: - break; - } + case SDL_EVENT_WINDOW_SHOWN: + case SDL_EVENT_WINDOW_EXPOSED: + case SDL_EVENT_WINDOW_RESIZED: + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: + case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: + case SDL_EVENT_RENDER_TARGETS_RESET: + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderFillRect(renderer, nullptr); + force_redraw = true; break; - case SDL_QUIT: + case SDL_EVENT_QUIT: movState->stop(); eom_action = EomAction::Quit; break; case FF_MOVIE_DONE_EVENT: - std::cout<<'\n'; + fmt::println(""); last_time = seconds::min(); if(eom_action != EomAction::Quit) { @@ -2164,18 +2331,19 @@ int main(al::span args) SDL_DestroyWindow(screen); screen = nullptr; - SDL_Quit(); + SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS); exit(0); default: break; } + have_event = SDL_PollEvent(&event); } movState->mVideo.updateVideo(screen, renderer, force_redraw); } - std::cerr<< "SDL_WaitEvent error - "< + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* This file contains an example for playback of Limitless Audio Format files. + * + * Some current shortcomings: + * + * - 256 track limit. Could be made higher, but making it too flexible would + * necessitate more micro-allocations. + * + * - "Objects" mode only supports sample rates that are a multiple of 48. Since + * positions are specified as samples in extra channels/tracks, and 3*16 + * samples are needed per track to specify the full set of positions, and + * each chunk is exactly one second long, other sample rates would result in + * the positions being split across chunks, causing the source playback + * offset to go out of sync with the offset used to look up the current + * spatial positions. Fixing this will require slightly more work to update + * and synchronize the spatial position arrays against the playback offset. + * + * - Updates are specified as fast as the app can detect and react to the + * reported source offset (that in turn depends on how often OpenAL renders). + * This can cause some positions to be a touch late and lose some granular + * temporal movement. In practice, this should probably be good enough for + * most use-cases. Fixing this would need either a new extension to queue + * position changes to apply when needed, or use a separate loopback device + * to render with and control the number of samples rendered between updates + * (with a second device to do the actual playback). + * + * - The LAF documentation doesn't prohibit object position tracks from being + * separated with audio tracks in between, or from being the first tracks + * followed by the audio tracks. It's not known if this is intended to be + * allowed, but it's not supported. Object position tracks must be last. + * + * Some remaining issues: + * + * - There are bursts of static on some channels. This doesn't appear to be a + * parsing error since the bursts last less than the chunk size, and it never + * loses sync with the remaining chunks. Might be an encoding error with the + * files tested. + * + * - Positions are specified in left-handed coordinates, despite the LAF + * documentation saying it's right-handed. Might be an encoding error with + * the files tested, or might be a misunderstanding about which is which. How + * to proceed may depend on how wide-spread this issue ends up being, but for + * now, they're treated as left-handed here. + * + * - The LAF documentation doesn't specify the range or direction for the + * channels' X and Y axis rotation in Channels mode. Presumably X rotation + * (elevation) goes from -pi/2...+pi/2 and Y rotation (azimuth) goes from + * either -pi...+pi or 0...pi*2, but the direction of movement isn't + * specified. Currently positive azimuth moves from center rightward and + * positive elevation moves from head-level upward. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AL/alc.h" +#include "AL/al.h" +#include "AL/alext.h" + +#include "albit.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" +#include "alstring.h" +#include "common/alhelpers.h" +#include "filesystem.h" +#include "fmt/core.h" +#include "fmt/std.h" + +#include "win_main_utf8.h" + +namespace { + +/* Filter object functions */ +auto alGenFilters = LPALGENFILTERS{}; +auto alDeleteFilters = LPALDELETEFILTERS{}; +auto alIsFilter = LPALISFILTER{}; +auto alFilteri = LPALFILTERI{}; +auto alFilteriv = LPALFILTERIV{}; +auto alFilterf = LPALFILTERF{}; +auto alFilterfv = LPALFILTERFV{}; +auto alGetFilteri = LPALGETFILTERI{}; +auto alGetFilteriv = LPALGETFILTERIV{}; +auto alGetFilterf = LPALGETFILTERF{}; +auto alGetFilterfv = LPALGETFILTERFV{}; + +/* Effect object functions */ +auto alGenEffects = LPALGENEFFECTS{}; +auto alDeleteEffects = LPALDELETEEFFECTS{}; +auto alIsEffect = LPALISEFFECT{}; +auto alEffecti = LPALEFFECTI{}; +auto alEffectiv = LPALEFFECTIV{}; +auto alEffectf = LPALEFFECTF{}; +auto alEffectfv = LPALEFFECTFV{}; +auto alGetEffecti = LPALGETEFFECTI{}; +auto alGetEffectiv = LPALGETEFFECTIV{}; +auto alGetEffectf = LPALGETEFFECTF{}; +auto alGetEffectfv = LPALGETEFFECTFV{}; + +/* Auxiliary Effect Slot object functions */ +auto alGenAuxiliaryEffectSlots = LPALGENAUXILIARYEFFECTSLOTS{}; +auto alDeleteAuxiliaryEffectSlots = LPALDELETEAUXILIARYEFFECTSLOTS{}; +auto alIsAuxiliaryEffectSlot = LPALISAUXILIARYEFFECTSLOT{}; +auto alAuxiliaryEffectSloti = LPALAUXILIARYEFFECTSLOTI{}; +auto alAuxiliaryEffectSlotiv = LPALAUXILIARYEFFECTSLOTIV{}; +auto alAuxiliaryEffectSlotf = LPALAUXILIARYEFFECTSLOTF{}; +auto alAuxiliaryEffectSlotfv = LPALAUXILIARYEFFECTSLOTFV{}; +auto alGetAuxiliaryEffectSloti = LPALGETAUXILIARYEFFECTSLOTI{}; +auto alGetAuxiliaryEffectSlotiv = LPALGETAUXILIARYEFFECTSLOTIV{}; +auto alGetAuxiliaryEffectSlotf = LPALGETAUXILIARYEFFECTSLOTF{}; +auto alGetAuxiliaryEffectSlotfv = LPALGETAUXILIARYEFFECTSLOTFV{}; + + +auto MuteFilterID = ALuint{}; +auto LowFrequencyEffectID = ALuint{}; +auto LfeSlotID = ALuint{}; + + +using namespace std::string_view_literals; + +[[noreturn]] +void do_assert(const char *message, int linenum, const char *filename, const char *funcname) +{ + auto errstr = fmt::format("{}:{}: {}: {}", filename, linenum, funcname, message); + throw std::runtime_error{errstr}; +} + +#define MyAssert(cond) do { \ + if(!(cond)) UNLIKELY \ + do_assert("Assertion '" #cond "' failed", __LINE__, __FILE__, \ + std::data(__func__)); \ +} while(0) + + +enum class Quality : std::uint8_t { + s8, s16, f32, s24 +}; +enum class Mode : bool { + Channels, Objects +}; + +auto GetQualityName(Quality quality) noexcept -> std::string_view +{ + switch(quality) + { + case Quality::s8: return "8-bit int"sv; + case Quality::s16: return "16-bit int"sv; + case Quality::f32: return "32-bit float"sv; + case Quality::s24: return "24-bit int"sv; + } + return ""sv; +} + +auto GetModeName(Mode mode) noexcept -> std::string_view +{ + switch(mode) + { + case Mode::Channels: return "channels"sv; + case Mode::Objects: return "objects"sv; + } + return ""sv; +} + +auto BytesFromQuality(Quality quality) noexcept -> size_t +{ + switch(quality) + { + case Quality::s8: return 1; + case Quality::s16: return 2; + case Quality::f32: return 4; + case Quality::s24: return 3; + } + return 4; +} + +auto BufferBytesFromQuality(Quality quality) noexcept -> size_t +{ + switch(quality) + { + case Quality::s8: return 1; + case Quality::s16: return 2; + case Quality::f32: return 4; + /* 24-bit samples are converted to 32-bit for OpenAL. */ + case Quality::s24: return 4; + } + return 4; +} + + +/* Helper class for reading little-endian samples on big-endian targets, or + * convert 24-bit samples. + */ +template +struct SampleReader; + +template<> +struct SampleReader { + using src_t = int8_t; + using dst_t = int8_t; + + [[nodiscard]] static + auto read(const src_t &in) noexcept -> dst_t { return in; } +}; + +template<> +struct SampleReader { + using src_t = int16_t; + using dst_t = int16_t; + + [[nodiscard]] static + auto read(const src_t &in) noexcept -> dst_t + { + if constexpr(al::endian::native == al::endian::little) + return in; + else + return al::byteswap(in); + } +}; + +template<> +struct SampleReader { + /* 32-bit float samples are read as 32-bit integer on big-endian systems, + * so that they can be byteswapped before being reinterpreted as float. + */ + using src_t = std::conditional_t; + using dst_t = float; + + [[nodiscard]] static + auto read(const src_t &in) noexcept -> dst_t + { + if constexpr(al::endian::native == al::endian::little) + return in; + else + return al::bit_cast(al::byteswap(static_cast(in))); + } +}; + +template<> +struct SampleReader { + /* 24-bit samples are converted to 32-bit integer. */ + using src_t = std::array; + using dst_t = int32_t; + + [[nodiscard]] static + auto read(const src_t &in) noexcept -> dst_t + { + return static_cast((uint32_t{in[0]}<<8) | (uint32_t{in[1]}<<16) + | (uint32_t{in[2]}<<24)); + } +}; + + +/* Each track with position data consists of a set of 3 samples per 16 audio + * channels, resulting in a full set of positions being specified over 48 + * sample frames. + */ +constexpr auto FramesPerPos = 48_uz; + +struct Channel { + ALuint mSource{}; + std::array mBuffers{}; + float mAzimuth{}; + float mElevation{}; + bool mIsLfe{}; + + Channel() = default; + Channel(const Channel&) = delete; + Channel(Channel&& rhs) + : mSource{rhs.mSource}, mBuffers{rhs.mBuffers}, mAzimuth{rhs.mAzimuth} + , mElevation{rhs.mElevation}, mIsLfe{rhs.mIsLfe} + { + rhs.mSource = 0; + rhs.mBuffers.fill(0); + } + ~Channel() + { + if(mSource) alDeleteSources(1, &mSource); + if(mBuffers[0]) alDeleteBuffers(ALsizei(mBuffers.size()), mBuffers.data()); + } + + auto operator=(const Channel&) -> Channel& = delete; + auto operator=(Channel&& rhs) -> Channel& + { + std::swap(mSource, rhs.mSource); + std::swap(mBuffers, rhs.mBuffers); + std::swap(mAzimuth, rhs.mAzimuth); + std::swap(mElevation, rhs.mElevation); + std::swap(mIsLfe, rhs.mIsLfe); + return *this; + } +}; + +struct LafStream { + std::filebuf mInFile; + + Quality mQuality{}; + Mode mMode{}; + uint32_t mNumTracks{}; + uint32_t mSampleRate{}; + ALenum mALFormat{}; + uint64_t mSampleCount{}; + + uint64_t mCurrentSample{}; + + std::array mEnabledTracks{}; + uint32_t mNumEnabled{}; + std::vector mSampleChunk; + al::span mSampleLine; + + std::vector mChannels; + std::vector> mPosTracks; + + LafStream() = default; + LafStream(const LafStream&) = delete; + ~LafStream() = default; + auto operator=(const LafStream&) -> LafStream& = delete; + + [[nodiscard]] + auto readChunk() -> uint32_t; + + void convertSamples(const al::span samples) const; + + void convertPositions(const al::span dst, const al::span src) const; + + template + void copySamples(char *dst, const char *src, size_t idx, size_t count) const; + + [[nodiscard]] + auto prepareTrack(size_t trackidx, size_t count) -> al::span; + + [[nodiscard]] + auto isAtEnd() const noexcept -> bool { return mCurrentSample >= mSampleCount; } +}; + +auto LafStream::readChunk() -> uint32_t +{ + mEnabledTracks.fill(0); + + mInFile.sgetn(reinterpret_cast(mEnabledTracks.data()), (mNumTracks+7_z)>>3); + mNumEnabled = std::accumulate(mEnabledTracks.cbegin(), mEnabledTracks.cend(), 0u, + [](const unsigned int val, const uint8_t in) + { return val + unsigned(al::popcount(unsigned(in))); }); + + /* Make sure enable bits aren't set for non-existent tracks. */ + if(mEnabledTracks[((mNumTracks+7_uz)>>3) - 1] >= (1u<<(mNumTracks&7))) + throw std::runtime_error{"Invalid channel enable bits"}; + + /* Each chunk is exactly one second long, with samples interleaved for each + * enabled track. The last chunk may be shorter if there isn't enough time + * remaining for a full second. + */ + const auto numsamples = std::min(uint64_t{mSampleRate}, mSampleCount-mCurrentSample); + + const auto toread = std::streamsize(numsamples * BytesFromQuality(mQuality) * mNumEnabled); + if(mInFile.sgetn(mSampleChunk.data(), toread) != toread) + throw std::runtime_error{"Failed to read sample chunk"}; + + std::fill(mSampleChunk.begin()+toread, mSampleChunk.end(), char{}); + + mCurrentSample += numsamples; + return static_cast(numsamples); +} + +void LafStream::convertSamples(const al::span samples) const +{ + /* OpenAL uses unsigned 8-bit samples (0...255), so signed 8-bit samples + * (-128...+127) need conversion. The other formats are fine. + */ + if(mQuality == Quality::s8) + std::transform(samples.begin(), samples.end(), samples.begin(), + [](const char sample) noexcept { return char(sample^0x80); }); +} + +void LafStream::convertPositions(const al::span dst, const al::span src) const +{ + switch(mQuality) + { + case Quality::s8: + std::transform(src.begin(), src.end(), dst.begin(), + [](const int8_t in) { return float(in) / 127.0f; }); + break; + case Quality::s16: + { + auto i16src = al::span{reinterpret_cast(src.data()), + src.size()/sizeof(int16_t)}; + std::transform(i16src.begin(), i16src.end(), dst.begin(), + [](const int16_t in) { return float(in) / 32767.0f; }); + } + break; + case Quality::f32: + { + auto f32src = al::span{reinterpret_cast(src.data()), + src.size()/sizeof(float)}; + std::copy(f32src.begin(), f32src.end(), dst.begin()); + } + break; + case Quality::s24: + { + /* 24-bit samples are converted to 32-bit in copySamples. */ + auto i32src = al::span{reinterpret_cast(src.data()), + src.size()/sizeof(int32_t)}; + std::transform(i32src.begin(), i32src.end(), dst.begin(), + [](const int32_t in) { return float(in>>8) / 8388607.0f; }); + } + break; + } +} + +template +void LafStream::copySamples(char *dst, const char *src, const size_t idx, const size_t count) const +{ + using reader_t = SampleReader; + using src_t = typename reader_t::src_t; + using dst_t = typename reader_t::dst_t; + + const auto step = size_t{mNumEnabled}; + assert(idx < step); + + auto input = al::span{reinterpret_cast(src), count*step}; + auto output = al::span{reinterpret_cast(dst), count}; + + auto inptr = input.begin(); + std::generate_n(output.begin(), output.size(), [&inptr,idx,step] + { + auto ret = reader_t::read(inptr[idx]); + inptr += ptrdiff_t(step); + return ret; + }); +} + +auto LafStream::prepareTrack(const size_t trackidx, const size_t count) -> al::span +{ + const auto todo = std::min(size_t{mSampleRate}, count); + if((mEnabledTracks[trackidx>>3] & (1_uz<<(trackidx&7)))) + { + /* If the track is enabled, get the real index (skipping disabled + * tracks), and deinterlace it into the mono line. + */ + const auto idx = [this,trackidx]() -> unsigned int + { + const auto bits = al::span{mEnabledTracks}.first(trackidx>>3); + const auto res = std::accumulate(bits.begin(), bits.end(), 0u, + [](const unsigned int val, const uint8_t in) + { return val + unsigned(al::popcount(unsigned(in))); }); + return unsigned(al::popcount(mEnabledTracks[trackidx>>3] & ((1u<<(trackidx&7))-1))) + + res; + }(); + + switch(mQuality) + { + case Quality::s8: + copySamples(mSampleLine.data(), mSampleChunk.data(), idx, todo); + break; + case Quality::s16: + copySamples(mSampleLine.data(), mSampleChunk.data(), idx, todo); + break; + case Quality::f32: + copySamples(mSampleLine.data(), mSampleChunk.data(), idx, todo); + break; + case Quality::s24: + copySamples(mSampleLine.data(), mSampleChunk.data(), idx, todo); + break; + } + } + else + { + /* If the track is disabled, provide silence. */ + std::fill_n(mSampleLine.begin(), mSampleLine.size(), char{}); + } + + return mSampleLine.first(todo * BufferBytesFromQuality(mQuality)); +} + + +auto LoadLAF(const fs::path &fname) -> std::unique_ptr +{ + auto laf = std::make_unique(); + if(!laf->mInFile.open(fname, std::ios_base::binary | std::ios_base::in)) + throw std::runtime_error{"Could not open file"}; + + auto marker = std::array{}; + if(laf->mInFile.sgetn(marker.data(), marker.size()) != marker.size()) + throw std::runtime_error{"Failed to read file marker"}; + if(std::string_view{marker.data(), marker.size()} != "LIMITLESS"sv) + throw std::runtime_error{"Not an LAF file"}; + + auto header = std::array{}; + if(laf->mInFile.sgetn(header.data(), header.size()) != header.size()) + throw std::runtime_error{"Failed to read header"}; + while(std::string_view{header.data(), 4} != "HEAD"sv) + { + auto headview = std::string_view{header.data(), header.size()}; + auto hiter = header.begin(); + if(const auto hpos = std::min(headview.find("HEAD"sv), headview.size()); + hpos < headview.size()) + { + /* Found the HEAD marker. Copy what was read of the header to the + * front, fill in the rest of the header, and continue loading. + */ + hiter = std::copy(header.begin()+hpos, header.end(), hiter); + } + else if(al::ends_with(headview, "HEA"sv)) + { + /* Found what might be the HEAD marker at the end. Copy it to the + * front, refill the header, and check again. + */ + hiter = std::copy_n(header.end()-3, 3, hiter); + } + else if(al::ends_with(headview, "HE"sv)) + hiter = std::copy_n(header.end()-2, 2, hiter); + else if(headview.back() == 'H') + hiter = std::copy_n(header.end()-1, 1, hiter); + + const auto toread = std::distance(hiter, header.end()); + if(laf->mInFile.sgetn(al::to_address(hiter), toread) != toread) + throw std::runtime_error{"Failed to read header"}; + } + + laf->mQuality = [stype=int{header[4]}] { + if(stype == 0) return Quality::s8; + if(stype == 1) return Quality::s16; + if(stype == 2) return Quality::f32; + if(stype == 3) return Quality::s24; + throw std::runtime_error{fmt::format("Invalid quality type: {}", stype)}; + }(); + + laf->mMode = [mode=int{header[5]}] { + if(mode == 0) return Mode::Channels; + if(mode == 1) return Mode::Objects; + throw std::runtime_error{fmt::format("Invalid mode: {}", mode)}; + }(); + + laf->mNumTracks = [input=al::span{header}.subspan<6,4>()] { + return uint32_t{uint8_t(input[0])} | (uint32_t{uint8_t(input[1])}<<8u) + | (uint32_t{uint8_t(input[2])}<<16u) | (uint32_t{uint8_t(input[3])}<<24u); + }(); + + fmt::println("Filename: {}", fname.string()); + fmt::println(" quality: {}", GetQualityName(laf->mQuality)); + fmt::println(" mode: {}", GetModeName(laf->mMode)); + fmt::println(" track count: {}", laf->mNumTracks); + + if(laf->mNumTracks == 0) + throw std::runtime_error{"No tracks"}; + if(laf->mNumTracks > 256) + throw std::runtime_error{fmt::format("Too many tracks: {}", laf->mNumTracks)}; + + auto chandata = std::vector(laf->mNumTracks*9_uz); + auto headersize = std::streamsize(chandata.size()); + if(laf->mInFile.sgetn(chandata.data(), headersize) != headersize) + throw std::runtime_error{"Failed to read channel header data"}; + + if(laf->mMode == Mode::Channels) + laf->mChannels.reserve(laf->mNumTracks); + else + { + if(laf->mNumTracks < 2) + throw std::runtime_error{"Not enough tracks"}; + + auto numchans = uint32_t{laf->mNumTracks - 1}; + auto numpostracks = uint32_t{1}; + while(numpostracks*16 < numchans) + { + --numchans; + ++numpostracks; + } + laf->mChannels.reserve(numchans); + laf->mPosTracks.reserve(numpostracks); + } + + for(uint32_t i{0};i < laf->mNumTracks;++i) + { + static constexpr auto read_float = [](al::span input) + { + const auto value = uint32_t{uint8_t(input[0])} | (uint32_t{uint8_t(input[1])}<<8u) + | (uint32_t{uint8_t(input[2])}<<16u) | (uint32_t{uint8_t(input[3])}<<24u); + return al::bit_cast(value); + }; + + auto chan = al::span{chandata}.subspan(i*9_uz, 9); + auto x_axis = read_float(chan.first<4>()); + auto y_axis = read_float(chan.subspan<4,4>()); + auto lfe_flag = int{chan[8]}; + + fmt::println("Track {}: E={:f}, A={:f} (LFE: {})", i, x_axis, y_axis, lfe_flag); + + if(x_axis != x_axis && y_axis == 0.0) + { + MyAssert(laf->mMode == Mode::Objects); + MyAssert(i != 0); + laf->mPosTracks.emplace_back(); + } + else + { + MyAssert(laf->mPosTracks.empty()); + MyAssert(std::isfinite(x_axis) && std::isfinite(y_axis)); + auto &channel = laf->mChannels.emplace_back(); + channel.mAzimuth = y_axis; + channel.mElevation = x_axis; + channel.mIsLfe = lfe_flag != 0; + } + } + fmt::println("Channels: {}", laf->mChannels.size()); + + /* For "objects" mode, ensure there's enough tracks with position data to + * handle the audio channels. + */ + if(laf->mMode == Mode::Objects) + MyAssert(((laf->mChannels.size()-1)>>4) == laf->mPosTracks.size()-1); + + auto footer = std::array{}; + if(laf->mInFile.sgetn(footer.data(), footer.size()) != footer.size()) + throw std::runtime_error{"Failed to read sample header data"}; + + laf->mSampleRate = [input=al::span{footer}.first<4>()] { + return uint32_t{uint8_t(input[0])} | (uint32_t{uint8_t(input[1])}<<8u) + | (uint32_t{uint8_t(input[2])}<<16u) | (uint32_t{uint8_t(input[3])}<<24u); + }(); + laf->mSampleCount = [input=al::span{footer}.last<8>()] { + return uint64_t{uint8_t(input[0])} | (uint64_t{uint8_t(input[1])}<<8) + | (uint64_t{uint8_t(input[2])}<<16u) | (uint64_t{uint8_t(input[3])}<<24u) + | (uint64_t{uint8_t(input[4])}<<32u) | (uint64_t{uint8_t(input[5])}<<40u) + | (uint64_t{uint8_t(input[6])}<<48u) | (uint64_t{uint8_t(input[7])}<<56u); + }(); + fmt::println("Sample rate: {}", laf->mSampleRate); + fmt::println("Length: {} samples ({:.2f} sec)", laf->mSampleCount, + static_cast(laf->mSampleCount)/static_cast(laf->mSampleRate)); + + /* Position vectors get split across the PCM chunks if the sample rate + * isn't a multiple of 48. Each PCM chunk is exactly one second (the sample + * rate in sample frames). Each track with position data consists of a set + * of 3 samples for 16 audio channels, resuling in 48 sample frames for a + * full set of positions. Extra logic will be needed to manage the position + * frame offset separate from each chunk. + */ + MyAssert(laf->mMode == Mode::Channels || (laf->mSampleRate%FramesPerPos) == 0); + + for(size_t i{0};i < laf->mPosTracks.size();++i) + laf->mPosTracks[i].resize(laf->mSampleRate*2_uz, 0.0f); + + laf->mSampleChunk.resize(laf->mSampleRate*BytesFromQuality(laf->mQuality)*laf->mNumTracks + + laf->mSampleRate*BufferBytesFromQuality(laf->mQuality)); + laf->mSampleLine = al::span{laf->mSampleChunk}.last(laf->mSampleRate + * BufferBytesFromQuality(laf->mQuality)); + + return laf; +} + +void PlayLAF(std::string_view fname) +try { + auto laf = LoadLAF(fs::u8path(fname)); + + switch(laf->mQuality) + { + case Quality::s8: + laf->mALFormat = AL_FORMAT_MONO8; + break; + case Quality::s16: + laf->mALFormat = AL_FORMAT_MONO16; + break; + case Quality::f32: + if(alIsExtensionPresent("AL_EXT_FLOAT32")) + laf->mALFormat = AL_FORMAT_MONO_FLOAT32; + break; + case Quality::s24: + laf->mALFormat = alGetEnumValue("AL_FORMAT_MONO32"); + if(!laf->mALFormat || laf->mALFormat == -1) + laf->mALFormat = alGetEnumValue("AL_FORMAT_MONO_I32"); + break; + } + if(!laf->mALFormat || laf->mALFormat == -1) + throw std::runtime_error{fmt::format("No supported format for {} samples", + GetQualityName(laf->mQuality))}; + + static constexpr auto alloc_channel = [](Channel &channel) + { + alGenSources(1, &channel.mSource); + alGenBuffers(ALsizei(channel.mBuffers.size()), channel.mBuffers.data()); + + /* Disable distance attenuation, and make sure the source stays locked + * relative to the listener. + */ + alSourcef(channel.mSource, AL_ROLLOFF_FACTOR, 0.0f); + alSourcei(channel.mSource, AL_SOURCE_RELATIVE, AL_TRUE); + + /* FIXME: Is the Y rotation/azimuth clockwise or counter-clockwise? + * Does +azimuth move a front sound right or left? + */ + const auto x = std::sin(channel.mAzimuth) * std::cos(channel.mElevation); + const auto y = std::sin(channel.mElevation); + const auto z = -std::cos(channel.mAzimuth) * std::cos(channel.mElevation); + alSource3f(channel.mSource, AL_POSITION, x, y, z); + + if(channel.mIsLfe) + { + if(LfeSlotID) + { + /* For LFE, silence the direct/dry path and connect the LFE aux + * slot on send 0. + */ + alSourcei(channel.mSource, AL_DIRECT_FILTER, ALint(MuteFilterID)); + alSource3i(channel.mSource, AL_AUXILIARY_SEND_FILTER, ALint(LfeSlotID), 0, + AL_FILTER_NULL); + } + else + { + /* If AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT isn't available, + * silence LFE channels since they may not be appropriate to + * play normally. + */ + alSourcef(channel.mSource, AL_GAIN, 0.0f); + } + } + + if(auto err=alGetError()) + throw std::runtime_error{fmt::format("OpenAL error: {}", alGetString(err))}; + }; + std::for_each(laf->mChannels.begin(), laf->mChannels.end(), alloc_channel); + + while(!laf->isAtEnd()) + { + auto state = ALenum{}; + auto offset = ALint{}; + auto processed = ALint{}; + /* All sources are played in sync, so they'll all be at the same offset + * with the same state and number of processed buffers. Query the back + * source just in case the previous update ran really late and missed + * updating only some sources on time (in which case, the latter ones + * will underrun, which this will detect and restart them all as + * needed). + */ + alGetSourcei(laf->mChannels.back().mSource, AL_BUFFERS_PROCESSED, &processed); + alGetSourcei(laf->mChannels.back().mSource, AL_SAMPLE_OFFSET, &offset); + alGetSourcei(laf->mChannels.back().mSource, AL_SOURCE_STATE, &state); + + if(state == AL_PLAYING || state == AL_PAUSED) + { + if(!laf->mPosTracks.empty()) + { + alcSuspendContext(alcGetCurrentContext()); + for(size_t i{0};i < laf->mChannels.size();++i) + { + const auto trackidx = i>>4; + + const auto posoffset = unsigned(offset)/FramesPerPos*16_uz + (i&15); + const auto x = laf->mPosTracks[trackidx][posoffset*3 + 0]; + const auto y = laf->mPosTracks[trackidx][posoffset*3 + 1]; + const auto z = laf->mPosTracks[trackidx][posoffset*3 + 2]; + + /* Contrary to the docs, the position is left-handed and + * needs to be converted to right-handed. + */ + alSource3f(laf->mChannels[i].mSource, AL_POSITION, x, y, -z); + } + alcProcessContext(alcGetCurrentContext()); + } + + if(processed > 0) + { + const auto numsamples = laf->readChunk(); + for(size_t i{0};i < laf->mChannels.size();++i) + { + const auto samples = laf->prepareTrack(i, numsamples); + laf->convertSamples(samples); + + auto bufid = ALuint{}; + alSourceUnqueueBuffers(laf->mChannels[i].mSource, 1, &bufid); + alBufferData(bufid, laf->mALFormat, samples.data(), ALsizei(samples.size()), + ALsizei(laf->mSampleRate)); + alSourceQueueBuffers(laf->mChannels[i].mSource, 1, &bufid); + } + for(size_t i{0};i < laf->mPosTracks.size();++i) + { + std::copy(laf->mPosTracks[i].begin() + ptrdiff_t(laf->mSampleRate), + laf->mPosTracks[i].end(), laf->mPosTracks[i].begin()); + + const auto positions = laf->prepareTrack(laf->mChannels.size()+i, numsamples); + laf->convertPositions(al::span{laf->mPosTracks[i]}.last(laf->mSampleRate), + positions); + } + } + else + std::this_thread::sleep_for(std::chrono::milliseconds{10}); + } + else if(state == AL_STOPPED) + { + auto sources = std::array{}; + for(size_t i{0};i < laf->mChannels.size();++i) + sources[i] = laf->mChannels[i].mSource; + alSourcePlayv(ALsizei(laf->mChannels.size()), sources.data()); + } + else if(state == AL_INITIAL) + { + auto sources = std::array{}; + auto numsamples = laf->readChunk(); + for(size_t i{0};i < laf->mChannels.size();++i) + { + const auto samples = laf->prepareTrack(i, numsamples); + laf->convertSamples(samples); + alBufferData(laf->mChannels[i].mBuffers[0], laf->mALFormat, samples.data(), + ALsizei(samples.size()), ALsizei(laf->mSampleRate)); + } + for(size_t i{0};i < laf->mPosTracks.size();++i) + { + const auto positions = laf->prepareTrack(laf->mChannels.size()+i, numsamples); + laf->convertPositions(al::span{laf->mPosTracks[i]}.first(laf->mSampleRate), + positions); + } + + numsamples = laf->readChunk(); + for(size_t i{0};i < laf->mChannels.size();++i) + { + const auto samples = laf->prepareTrack(i, numsamples); + laf->convertSamples(samples); + alBufferData(laf->mChannels[i].mBuffers[1], laf->mALFormat, samples.data(), + ALsizei(samples.size()), ALsizei(laf->mSampleRate)); + alSourceQueueBuffers(laf->mChannels[i].mSource, + ALsizei(laf->mChannels[i].mBuffers.size()), laf->mChannels[i].mBuffers.data()); + sources[i] = laf->mChannels[i].mSource; + } + for(size_t i{0};i < laf->mPosTracks.size();++i) + { + const auto positions = laf->prepareTrack(laf->mChannels.size()+i, numsamples); + laf->convertPositions(al::span{laf->mPosTracks[i]}.last(laf->mSampleRate), + positions); + } + + if(!laf->mPosTracks.empty()) + { + for(size_t i{0};i < laf->mChannels.size();++i) + { + const auto trackidx = i>>4; + + const auto x = laf->mPosTracks[trackidx][(i&15)*3 + 0]; + const auto y = laf->mPosTracks[trackidx][(i&15)*3 + 1]; + const auto z = laf->mPosTracks[trackidx][(i&15)*3 + 2]; + + alSource3f(laf->mChannels[i].mSource, AL_POSITION, x, y, -z); + } + } + + alSourcePlayv(ALsizei(laf->mChannels.size()), sources.data()); + } + else + break; + } + + auto state = ALenum{}; + auto offset = ALint{}; + alGetSourcei(laf->mChannels.back().mSource, AL_SAMPLE_OFFSET, &offset); + alGetSourcei(laf->mChannels.back().mSource, AL_SOURCE_STATE, &state); + while(alGetError() == AL_NO_ERROR && state == AL_PLAYING) + { + if(!laf->mPosTracks.empty()) + { + alcSuspendContext(alcGetCurrentContext()); + for(size_t i{0};i < laf->mChannels.size();++i) + { + const auto trackidx = i>>4; + + const auto posoffset = unsigned(offset)/FramesPerPos*16_uz + (i&15); + const auto x = laf->mPosTracks[trackidx][posoffset*3 + 0]; + const auto y = laf->mPosTracks[trackidx][posoffset*3 + 1]; + const auto z = laf->mPosTracks[trackidx][posoffset*3 + 2]; + + alSource3f(laf->mChannels[i].mSource, AL_POSITION, x, y, -z); + } + alcProcessContext(alcGetCurrentContext()); + } + std::this_thread::sleep_for(std::chrono::milliseconds{10}); + alGetSourcei(laf->mChannels.back().mSource, AL_SAMPLE_OFFSET, &offset); + alGetSourcei(laf->mChannels.back().mSource, AL_SOURCE_STATE, &state); + } +} +catch(std::exception& e) { + fmt::println(stderr, "Error playing {}:\n {}", fname, e.what()); +} + +auto main(al::span args) -> int +{ + /* Print out usage if no arguments were specified */ + if(args.size() < 2) + { + fmt::println(stderr, "Usage: {} [-device ] \n", args[0]); + return 1; + } + args = args.subspan(1); + + if(InitAL(args) != 0) + throw std::runtime_error{"Failed to initialize OpenAL"}; + /* A simple RAII container for automating OpenAL shutdown. */ + struct AudioManager { + AudioManager() = default; + AudioManager(const AudioManager&) = delete; + auto operator=(const AudioManager&) -> AudioManager& = delete; + ~AudioManager() + { + if(LfeSlotID) + { + alDeleteAuxiliaryEffectSlots(1, &LfeSlotID); + alDeleteEffects(1, &LowFrequencyEffectID); + alDeleteFilters(1, &MuteFilterID); + } + CloseAL(); + } + }; + AudioManager almgr; + + if(auto *device = alcGetContextsDevice(alcGetCurrentContext()); + alcIsExtensionPresent(device, "ALC_EXT_EFX") + && alcIsExtensionPresent(device, "ALC_EXT_DEDICATED")) + { +#define LOAD_PROC(x) do { \ + x = reinterpret_cast(alGetProcAddress(#x)); \ + if(!x) fmt::println(stderr, "Failed to find function '{}'\n", #x##sv);\ + } while(0) + LOAD_PROC(alGenFilters); + LOAD_PROC(alDeleteFilters); + LOAD_PROC(alIsFilter); + LOAD_PROC(alFilterf); + LOAD_PROC(alFilterfv); + LOAD_PROC(alFilteri); + LOAD_PROC(alFilteriv); + LOAD_PROC(alGetFilterf); + LOAD_PROC(alGetFilterfv); + LOAD_PROC(alGetFilteri); + LOAD_PROC(alGetFilteriv); + LOAD_PROC(alGenEffects); + LOAD_PROC(alDeleteEffects); + LOAD_PROC(alIsEffect); + LOAD_PROC(alEffectf); + LOAD_PROC(alEffectfv); + LOAD_PROC(alEffecti); + LOAD_PROC(alEffectiv); + LOAD_PROC(alGetEffectf); + LOAD_PROC(alGetEffectfv); + LOAD_PROC(alGetEffecti); + LOAD_PROC(alGetEffectiv); + LOAD_PROC(alGenAuxiliaryEffectSlots); + LOAD_PROC(alDeleteAuxiliaryEffectSlots); + LOAD_PROC(alIsAuxiliaryEffectSlot); + LOAD_PROC(alAuxiliaryEffectSlotf); + LOAD_PROC(alAuxiliaryEffectSlotfv); + LOAD_PROC(alAuxiliaryEffectSloti); + LOAD_PROC(alAuxiliaryEffectSlotiv); + LOAD_PROC(alGetAuxiliaryEffectSlotf); + LOAD_PROC(alGetAuxiliaryEffectSlotfv); + LOAD_PROC(alGetAuxiliaryEffectSloti); + LOAD_PROC(alGetAuxiliaryEffectSlotiv); +#undef LOAD_PROC + + alGenFilters(1, &MuteFilterID); + alFilteri(MuteFilterID, AL_FILTER_TYPE, AL_FILTER_LOWPASS); + alFilterf(MuteFilterID, AL_LOWPASS_GAIN, 0.0f); + MyAssert(alGetError() == AL_NO_ERROR); + + alGenEffects(1, &LowFrequencyEffectID); + alEffecti(LowFrequencyEffectID, AL_EFFECT_TYPE, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT); + MyAssert(alGetError() == AL_NO_ERROR); + + alGenAuxiliaryEffectSlots(1, &LfeSlotID); + alAuxiliaryEffectSloti(LfeSlotID, AL_EFFECTSLOT_EFFECT, ALint(LowFrequencyEffectID)); + MyAssert(alGetError() == AL_NO_ERROR); + } + + std::for_each(args.begin(), args.end(), PlayLAF); + + return 0; +} + +} // namespace + +int main(int argc, char **argv) +{ + MyAssert(argc >= 0); + auto args = std::vector(static_cast(argc)); + std::copy_n(argv, args.size(), args.begin()); + return main(al::span{args}); +} diff --git a/Engine/lib/openal-soft/examples/alloopback.c b/Engine/lib/openal-soft/examples/alloopback.c index 106d0d461..62f215123 100644 --- a/Engine/lib/openal-soft/examples/alloopback.c +++ b/Engine/lib/openal-soft/examples/alloopback.c @@ -29,13 +29,14 @@ #include #include #include +#include #include #define SDL_MAIN_HANDLED -#include "SDL.h" -#include "SDL_audio.h" -#include "SDL_error.h" -#include "SDL_stdinc.h" +#include "SDL3/SDL_audio.h" +#include "SDL3/SDL_error.h" +#include "SDL3/SDL_main.h" +#include "SDL3/SDL_stdinc.h" #include "AL/al.h" #include "AL/alc.h" @@ -43,13 +44,6 @@ #include "common/alhelpers.h" -#ifndef SDL_AUDIO_MASK_BITSIZE -#define SDL_AUDIO_MASK_BITSIZE (0xFF) -#endif -#ifndef SDL_AUDIO_BITSIZE -#define SDL_AUDIO_BITSIZE(x) (x & SDL_AUDIO_MASK_BITSIZE) -#endif - #ifndef M_PI #define M_PI (3.14159265358979323846) #endif @@ -59,6 +53,8 @@ typedef struct { ALCcontext *Context; ALCsizei FrameSize; + void *Buffer; + int BufferSize; } PlaybackInfo; static LPALCLOOPBACKOPENDEVICESOFT alcLoopbackOpenDeviceSOFT; @@ -66,10 +62,26 @@ static LPALCISRENDERFORMATSUPPORTEDSOFT alcIsRenderFormatSupportedSOFT; static LPALCRENDERSAMPLESSOFT alcRenderSamplesSOFT; -void SDLCALL RenderSDLSamples(void *userdata, Uint8 *stream, int len) +void SDLCALL RenderSDLSamples(void *userdata, SDL_AudioStream *stream, int additional_amount, + int total_amount) { PlaybackInfo *playback = (PlaybackInfo*)userdata; - alcRenderSamplesSOFT(playback->Device, stream, len/playback->FrameSize); + + if(additional_amount < 0) + additional_amount = total_amount; + if(additional_amount <= 0) + return; + + if(additional_amount > playback->BufferSize) + { + free(playback->Buffer); + playback->Buffer = malloc((unsigned int)additional_amount); + playback->BufferSize = additional_amount; + } + alcRenderSamplesSOFT(playback->Device, playback->Buffer, + additional_amount/playback->FrameSize); + + SDL_PutAudioStreamData(stream, playback->Buffer, additional_amount); } @@ -135,8 +147,9 @@ static ALuint CreateSineWave(void) int main(int argc, char *argv[]) { - PlaybackInfo playback = { NULL, NULL, 0 }; - SDL_AudioSpec desired, obtained; + PlaybackInfo playback = { NULL, NULL, 0, NULL, 0 }; + SDL_AudioStream *stream = NULL; + SDL_AudioSpec obtained; ALuint source, buffer; ALCint attrs[16]; ALenum state; @@ -159,25 +172,25 @@ int main(int argc, char *argv[]) LOAD_PROC(LPALCRENDERSAMPLESSOFT, alcRenderSamplesSOFT); #undef LOAD_PROC - if(SDL_Init(SDL_INIT_AUDIO) == -1) + if(!SDL_Init(SDL_INIT_AUDIO)) { fprintf(stderr, "Failed to init SDL audio: %s\n", SDL_GetError()); return 1; } - /* Set up SDL audio with our requested format and callback. */ - desired.channels = 2; - desired.format = AUDIO_S16SYS; - desired.freq = 44100; - desired.padding = 0; - desired.samples = 4096; - desired.callback = RenderSDLSamples; - desired.userdata = &playback; - if(SDL_OpenAudio(&desired, &obtained) != 0) + /* Set up SDL audio with our callback, and get the stream format. */ + stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL, RenderSDLSamples, + &playback); + if(!stream) { - SDL_Quit(); fprintf(stderr, "Failed to open SDL audio: %s\n", SDL_GetError()); - return 1; + goto error; + } + + if(!SDL_GetAudioStreamFormat(stream, &obtained, NULL)) + { + fprintf(stderr, "Failed to query SDL audio format: %s\n", SDL_GetError()); + goto error; } /* Set up our OpenAL attributes based on what we got from SDL. */ @@ -186,6 +199,14 @@ int main(int argc, char *argv[]) attrs[1] = ALC_MONO_SOFT; else if(obtained.channels == 2) attrs[1] = ALC_STEREO_SOFT; + else if(obtained.channels == 4) + attrs[1] = ALC_QUAD_SOFT; + else if(obtained.channels == 6) + attrs[1] = ALC_5POINT1_SOFT; + else if(obtained.channels == 7) + attrs[1] = ALC_6POINT1_SOFT; + else if(obtained.channels == 8) + attrs[1] = ALC_7POINT1_SOFT; else { fprintf(stderr, "Unhandled SDL channel count: %d\n", obtained.channels); @@ -193,17 +214,15 @@ int main(int argc, char *argv[]) } attrs[2] = ALC_FORMAT_TYPE_SOFT; - if(obtained.format == AUDIO_U8) + if(obtained.format == SDL_AUDIO_U8) attrs[3] = ALC_UNSIGNED_BYTE_SOFT; - else if(obtained.format == AUDIO_S8) + else if(obtained.format == SDL_AUDIO_S8) attrs[3] = ALC_BYTE_SOFT; - else if(obtained.format == AUDIO_U16SYS) - attrs[3] = ALC_UNSIGNED_SHORT_SOFT; - else if(obtained.format == AUDIO_S16SYS) + else if(obtained.format == SDL_AUDIO_S16) attrs[3] = ALC_SHORT_SOFT; - else if(obtained.format == AUDIO_S32SYS) + else if(obtained.format == SDL_AUDIO_S32) attrs[3] = ALC_INT_SOFT; - else if(obtained.format == AUDIO_F32SYS) + else if(obtained.format == SDL_AUDIO_F32) attrs[3] = ALC_FLOAT_SOFT; else { @@ -216,7 +235,7 @@ int main(int argc, char *argv[]) attrs[6] = 0; /* end of list */ - playback.FrameSize = obtained.channels * SDL_AUDIO_BITSIZE(obtained.format) / 8; + playback.FrameSize = obtained.channels * (int)SDL_AUDIO_BITSIZE(obtained.format) / 8; /* Initialize OpenAL loopback device, using our format attributes. */ playback.Device = alcLoopbackOpenDeviceSOFT(NULL); @@ -229,7 +248,7 @@ int main(int argc, char *argv[]) if(alcIsRenderFormatSupportedSOFT(playback.Device, attrs[5], attrs[1], attrs[3]) == ALC_FALSE) { fprintf(stderr, "Render format not supported: %s, %s, %dhz\n", - ChannelsName(attrs[1]), TypeName(attrs[3]), attrs[5]); + ChannelsName(attrs[1]), TypeName(attrs[3]), attrs[5]); goto error; } playback.Context = alcCreateContext(playback.Device, attrs); @@ -239,20 +258,18 @@ int main(int argc, char *argv[]) goto error; } + printf("Got render format from SDL stream: %s, %s, %dhz\n", ChannelsName(attrs[1]), + TypeName(attrs[3]), attrs[5]); + /* Start SDL playing. Our callback (thus alcRenderSamplesSOFT) will now - * start being called regularly to update the AL playback state. */ - SDL_PauseAudio(0); + * start being called regularly to update the AL playback state. + */ + SDL_ResumeAudioStreamDevice(stream); /* Load the sound into a buffer. */ buffer = CreateSineWave(); if(!buffer) - { - SDL_CloseAudio(); - alcDestroyContext(playback.Context); - alcCloseDevice(playback.Device); - SDL_Quit(); - return 1; - } + goto error; /* Create the source to play the sound with. */ source = 0; @@ -272,23 +289,25 @@ int main(int argc, char *argv[]) alDeleteBuffers(1, &buffer); /* Stop SDL playing. */ - SDL_PauseAudio(1); + SDL_PauseAudioStreamDevice(stream); /* Close up OpenAL and SDL. */ - SDL_CloseAudio(); + SDL_DestroyAudioStream(stream); alcDestroyContext(playback.Context); alcCloseDevice(playback.Device); - SDL_Quit(); + + SDL_QuitSubSystem(SDL_INIT_AUDIO); return 0; error: - SDL_CloseAudio(); + if(stream) + SDL_DestroyAudioStream(stream); if(playback.Context) alcDestroyContext(playback.Context); if(playback.Device) alcCloseDevice(playback.Device); - SDL_Quit(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); return 1; } diff --git a/Engine/lib/openal-soft/examples/alstreamcb.cpp b/Engine/lib/openal-soft/examples/alstreamcb.cpp index dae340482..e799c0645 100644 --- a/Engine/lib/openal-soft/examples/alstreamcb.cpp +++ b/Engine/lib/openal-soft/examples/alstreamcb.cpp @@ -46,9 +46,10 @@ #include "AL/alc.h" #include "AL/alext.h" +#include "alnumeric.h" #include "alspan.h" -#include "alstring.h" #include "common/alhelpers.h" +#include "fmt/core.h" #include "win_main_utf8.h" @@ -129,7 +130,7 @@ struct StreamPlayer { mSndfile = sf_open(filename.c_str(), SFM_READ, &mSfInfo); if(!mSndfile) { - fprintf(stderr, "Could not open audio in %s: %s\n", filename.c_str(), + fmt::println(stderr, "Could not open audio in {}: {}", filename, sf_strerror(mSndfile)); return false; } @@ -260,7 +261,7 @@ struct StreamPlayer { } if(!mFormat) { - fprintf(stderr, "Unsupported channel count: %d\n", mSfInfo.channels); + fmt::println(stderr, "Unsupported channel count: {}", mSfInfo.channels); sf_close(mSndfile); mSndfile = nullptr; @@ -287,7 +288,9 @@ struct StreamPlayer { { return static_cast(userptr)->bufferCallback(data, size); } ALsizei bufferCallback(void *data, ALsizei size) noexcept { - auto dst = al::span{static_cast(data), static_cast(size)}; + const auto output = al::span{static_cast(data), static_cast(size)}; + auto dst = output.begin(); + /* NOTE: The callback *MUST* be real-time safe! That means no blocking, * no allocations or deallocations, no I/O, no page faults, or calls to * functions that could do these things (this includes calling to @@ -295,10 +298,9 @@ struct StreamPlayer { * unexpectedly stall this call since the audio has to get to the * device on time. */ - ALsizei got{0}; size_t roffset{mReadPos.load(std::memory_order_acquire)}; - while(!dst.empty()) + while(const auto remaining = static_cast(std::distance(dst, output.end()))) { /* If the write offset == read offset, there's nothing left in the * ring-buffer. Break from the loop and give what has been written. @@ -312,15 +314,14 @@ struct StreamPlayer { * amount to copy given how much is remaining to write. */ size_t todo{((woffset < roffset) ? mBufferData.size() : woffset) - roffset}; - todo = std::min(todo, dst.size()); + todo = std::min(todo, remaining); /* Copy from the ring buffer to the provided output buffer. Wrap * the resulting read offset if it reached the end of the ring- * buffer. */ - std::copy_n(mBufferData.cbegin()+ptrdiff_t(roffset), todo, dst.begin()); - dst = dst.subspan(todo); - got += static_cast(todo); + const auto input = al::span{mBufferData}.subspan(roffset, todo); + dst = std::copy_n(input.begin(), input.size(), dst); roffset += todo; if(roffset == mBufferData.size()) @@ -331,7 +332,7 @@ struct StreamPlayer { */ mReadPos.store(roffset, std::memory_order_release); - return got; + return static_cast(std::distance(output.begin(), dst)); } bool prepare() @@ -342,7 +343,8 @@ struct StreamPlayer { alSourcei(mSource, AL_BUFFER, static_cast(mBuffer)); if(ALenum err{alGetError()}) { - fprintf(stderr, "Failed to set callback: %s (0x%04x)\n", alGetString(err), err); + fmt::println(stderr, "Failed to set callback: {} ({:#x})", alGetString(err), + as_unsigned(err)); return false; } return true; @@ -366,95 +368,72 @@ struct StreamPlayer { * For a playing/paused source, it's the source's offset including * the playback offset the source was started with. */ - const size_t curtime{((state == AL_STOPPED) + const auto curtime = ((state == AL_STOPPED) ? (mDecoderOffset-readable) / mBytesPerBlock * mSamplesPerBlock - : (static_cast(pos) + mStartOffset/mBytesPerBlock*mSamplesPerBlock)) - / static_cast(mSfInfo.samplerate)}; - printf("\r%3zus (%3zu%% full)", curtime, readable * 100 / mBufferData.size()); + : (size_t{static_cast(pos)} + mStartOffset)) + / static_cast(mSfInfo.samplerate); + fmt::print("\r {}m{:02}s ({:3}% full)", curtime/60, curtime%60, + readable * 100 / mBufferData.size()); } else - fputs("Starting...", stdout); + fmt::println("Starting..."); fflush(stdout); while(!sf_error(mSndfile)) { - size_t read_bytes; - const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - if(roffset > woffset) + const auto roffset = mReadPos.load(std::memory_order_relaxed); + auto get_writable = [this,roffset,woffset]() noexcept -> size_t { - /* Note that the ring buffer's writable space is one byte less - * than the available area because the write offset ending up - * at the read offset would be interpreted as being empty - * instead of full. - */ - const size_t writable{(roffset-woffset-1) / mBytesPerBlock}; - if(!writable) break; - - if(mSampleFormat == SampleType::Int16) + if(roffset > woffset) { - sf_count_t num_frames{sf_readf_short(mSndfile, - reinterpret_cast(&mBufferData[woffset]), - static_cast(writable*mSamplesPerBlock))}; - if(num_frames < 1) break; - read_bytes = static_cast(num_frames) * mBytesPerBlock; - } - else if(mSampleFormat == SampleType::Float) - { - sf_count_t num_frames{sf_readf_float(mSndfile, - reinterpret_cast(&mBufferData[woffset]), - static_cast(writable*mSamplesPerBlock))}; - if(num_frames < 1) break; - read_bytes = static_cast(num_frames) * mBytesPerBlock; - } - else - { - sf_count_t numbytes{sf_read_raw(mSndfile, &mBufferData[woffset], - static_cast(writable*mBytesPerBlock))}; - if(numbytes < 1) break; - read_bytes = static_cast(numbytes); + /* Note that the ring buffer's writable space is one byte + * less than the available area because the write offset + * ending up at the read offset would be interpreted as + * being empty instead of full. + */ + return roffset - woffset - 1; } - woffset += read_bytes; - } - else - { /* If the read offset is at or behind the write offset, the * writeable area (might) wrap around. Make sure the sample * data can fit, and calculate how much can go in front before * wrapping. */ - const size_t writable{(!roffset ? mBufferData.size()-woffset-1 : - (mBufferData.size()-woffset)) / mBytesPerBlock}; - if(!writable) break; + return mBufferData.size() - (!roffset ? woffset+1 : woffset); + }; - if(mSampleFormat == SampleType::Int16) - { - sf_count_t num_frames{sf_readf_short(mSndfile, - reinterpret_cast(&mBufferData[woffset]), - static_cast(writable*mSamplesPerBlock))}; - if(num_frames < 1) break; - read_bytes = static_cast(num_frames) * mBytesPerBlock; - } - else if(mSampleFormat == SampleType::Float) - { - sf_count_t num_frames{sf_readf_float(mSndfile, - reinterpret_cast(&mBufferData[woffset]), - static_cast(writable*mSamplesPerBlock))}; - if(num_frames < 1) break; - read_bytes = static_cast(num_frames) * mBytesPerBlock; - } - else - { - sf_count_t numbytes{sf_read_raw(mSndfile, &mBufferData[woffset], - static_cast(writable*mBytesPerBlock))}; - if(numbytes < 1) break; - read_bytes = static_cast(numbytes); - } + const auto writable = get_writable() / mBytesPerBlock; + if(!writable) break; - woffset += read_bytes; - if(woffset == mBufferData.size()) - woffset = 0; + auto read_bytes = size_t{}; + if(mSampleFormat == SampleType::Int16) + { + const auto num_frames = sf_readf_short(mSndfile, + reinterpret_cast(&mBufferData[woffset]), + static_cast(writable*mSamplesPerBlock)); + if(num_frames < 1) break; + read_bytes = static_cast(num_frames) * mBytesPerBlock; } + else if(mSampleFormat == SampleType::Float) + { + const auto num_frames = sf_readf_float(mSndfile, + reinterpret_cast(&mBufferData[woffset]), + static_cast(writable*mSamplesPerBlock)); + if(num_frames < 1) break; + read_bytes = static_cast(num_frames) * mBytesPerBlock; + } + else + { + const auto numbytes = sf_read_raw(mSndfile, &mBufferData[woffset], + static_cast(writable*mBytesPerBlock)); + if(numbytes < 1) break; + read_bytes = static_cast(numbytes); + } + + woffset += read_bytes; + if(woffset == mBufferData.size()) + woffset = 0; + mWritePos.store(woffset, std::memory_order_release); mDecoderOffset += read_bytes; } @@ -466,16 +445,16 @@ struct StreamPlayer { * ring buffer is empty, it's done, otherwise play the source with * what's available. */ - const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - const size_t readable{((woffset >= roffset) ? woffset : (mBufferData.size()+woffset)) - - roffset}; + const auto roffset = mReadPos.load(std::memory_order_relaxed); + const auto readable = ((woffset < roffset) ? mBufferData.size()+woffset : woffset) - + roffset; if(readable == 0) return false; /* Store the playback offset that the source will start reading * from, so it can be tracked during playback. */ - mStartOffset = mDecoderOffset - readable; + mStartOffset = (mDecoderOffset-readable) / mBytesPerBlock * mSamplesPerBlock; alSourcePlay(mSource); if(alGetError() != AL_NO_ERROR) return false; @@ -486,30 +465,28 @@ struct StreamPlayer { int main(al::span args) { - /* A simple RAII container for OpenAL startup and shutdown. */ - struct AudioManager { - AudioManager(al::span &args_) - { - if(InitAL(args_) != 0) - throw std::runtime_error{"Failed to initialize OpenAL"}; - } - ~AudioManager() { CloseAL(); } - }; - /* Print out usage if no arguments were specified */ if(args.size() < 2) { - fprintf(stderr, "Usage: %.*s [-device ] \n", al::sizei(args[0]), - args[0].data()); + fmt::println(stderr, "Usage: {} [-device ] ", args[0]); return 1; } - args = args.subspan(1); - AudioManager almgr{args}; + + if(InitAL(args) != 0) + throw std::runtime_error{"Failed to initialize OpenAL"}; + /* A simple RAII container for automating OpenAL shutdown. */ + struct AudioManager { + AudioManager() = default; + AudioManager(const AudioManager&) = delete; + auto operator=(const AudioManager&) -> AudioManager& = delete; + ~AudioManager() { CloseAL(); } + }; + AudioManager almgr; if(!alIsExtensionPresent("AL_SOFT_callback_buffer")) { - fprintf(stderr, "AL_SOFT_callback_buffer extension not available\n"); + fmt::println(stderr, "AL_SOFT_callback_buffer extension not available"); return 1; } @@ -528,14 +505,11 @@ int main(al::span args) continue; /* Get the name portion, without the path, for display. */ - auto namepart = args[i]; - if(auto sep = namepart.rfind('/'); sep < namepart.size()) - namepart = namepart.substr(sep+1); - else if(sep = namepart.rfind('\\'); sep < namepart.size()) - namepart = namepart.substr(sep+1); + const auto fpos = std::max(args[i].rfind('/')+1, args[i].rfind('\\')+1); + const auto namepart = args[i].substr(fpos); - printf("Playing: %.*s (%s, %dhz)\n", al::sizei(namepart), namepart.data(), - FormatName(player->mFormat), player->mSfInfo.samplerate); + fmt::println("Playing: {} ({}, {}hz)", namepart, FormatName(player->mFormat), + player->mSfInfo.samplerate); fflush(stdout); if(!player->prepare()) @@ -552,7 +526,7 @@ int main(al::span args) player->close(); } /* All done. */ - printf("Done.\n"); + fmt::println("Done."); return 0; } diff --git a/Engine/lib/openal-soft/examples/altonegen.c b/Engine/lib/openal-soft/examples/altonegen.c index 52a57875a..c171e576e 100644 --- a/Engine/lib/openal-soft/examples/altonegen.c +++ b/Engine/lib/openal-soft/examples/altonegen.c @@ -185,9 +185,9 @@ int main(int argc, char *argv[]) const char *appname = argv[0]; ALuint source, buffer; ALint last_pos; - ALint seconds = 4; + ALuint seconds = 4; ALint srate = -1; - ALint tone_freq = 1000; + ALuint tone_freq = 1000; ALCint dev_rate; ALenum state; ALfloat gain = 1.0f; @@ -229,9 +229,10 @@ int main(int argc, char *argv[]) if(i+1 < argc && strcmp(argv[i], "-t") == 0) { + char *endptr = NULL; i++; - seconds = atoi(argv[i]); - if(seconds <= 0) + seconds = (ALuint)strtoul(argv[i], &endptr, 0); + if(!endptr || *endptr != '\0' || seconds <= 0) seconds = 4; } else if(i+1 < argc && (strcmp(argv[i], "--waveform") == 0 || strcmp(argv[i], "-w") == 0)) @@ -254,9 +255,10 @@ int main(int argc, char *argv[]) } else if(i+1 < argc && (strcmp(argv[i], "--freq") == 0 || strcmp(argv[i], "-f") == 0)) { + char *endptr = NULL; i++; - tone_freq = atoi(argv[i]); - if(tone_freq < 1) + tone_freq = (ALuint)strtoul(argv[i], &endptr, 0); + if(!endptr || *endptr != '\0' || tone_freq < 1) { fprintf(stderr, "Invalid tone frequency: %s (min: 1hz)\n", argv[i]); tone_freq = 1; @@ -264,9 +266,10 @@ int main(int argc, char *argv[]) } else if(i+1 < argc && (strcmp(argv[i], "--gain") == 0 || strcmp(argv[i], "-g") == 0)) { + char *endptr = NULL; i++; - gain = (ALfloat)atof(argv[i]); - if(gain < 0.0f || gain > 1.0f) + gain = strtof(argv[i], &endptr); + if(!endptr || *endptr != '\0' || gain < 0.0f || gain > 1.0f) { fprintf(stderr, "Invalid gain: %s (min: 0.0, max 1.0)\n", argv[i]); gain = 1.0f; @@ -274,9 +277,10 @@ int main(int argc, char *argv[]) } else if(i+1 < argc && (strcmp(argv[i], "--srate") == 0 || strcmp(argv[i], "-s") == 0)) { + char *endptr = NULL; i++; - srate = atoi(argv[i]); - if(srate < 40) + srate = (ALint)strtol(argv[i], &endptr, 0); + if(!endptr || *endptr != '\0' || srate < 40) { fprintf(stderr, "Invalid sample rate: %s (min: 40hz)\n", argv[i]); srate = 40; @@ -293,15 +297,15 @@ int main(int argc, char *argv[]) srate = dev_rate; /* Load the sound into a buffer. */ - buffer = CreateWave(wavetype, (ALuint)seconds, (ALuint)tone_freq, (ALuint)srate, gain); + buffer = CreateWave(wavetype, seconds, tone_freq, (ALuint)srate, gain); if(!buffer) { CloseAL(); return 1; } - printf("Playing %dhz %s-wave tone with %dhz sample rate and %dhz output, for %d second%s...\n", - tone_freq, GetWaveTypeName(wavetype), srate, dev_rate, seconds, (seconds!=1)?"s":""); + printf("Playing %uhz %s-wave tone with %dhz sample rate and %dhz output, for %u second%s...\n", + tone_freq, GetWaveTypeName(wavetype), srate, dev_rate, seconds, (seconds!=1)?"s":""); fflush(stdout); /* Create the source to play the sound with. */ @@ -322,7 +326,7 @@ int main(int argc, char *argv[]) if(pos > last_pos) { - printf("%d...\n", seconds - pos); + printf("%u...\n", seconds - (ALuint)pos); fflush(stdout); } last_pos = pos; diff --git a/Engine/lib/openal-soft/examples/common/alhelpers.h b/Engine/lib/openal-soft/examples/common/alhelpers.h index 97f553657..e8e0b2772 100644 --- a/Engine/lib/openal-soft/examples/common/alhelpers.h +++ b/Engine/lib/openal-soft/examples/common/alhelpers.h @@ -24,7 +24,7 @@ void al_nssleep(unsigned long nsec); * still needs to use a normal cast and live with the warning (C++ is fine with * a regular reinterpret_cast). */ -#if __STDC_VERSION__ >= 199901L +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L #define FUNCTION_CAST(T, ptr) (union{void *p; T f;}){ptr}.f #elif defined(__cplusplus) #define FUNCTION_CAST(T, ptr) reinterpret_cast(ptr) @@ -36,11 +36,11 @@ void al_nssleep(unsigned long nsec); } // extern "C" #include -#include #include #include #include "alspan.h" +#include "fmt/core.h" int InitAL(al::span &args) { @@ -51,14 +51,14 @@ int InitAL(al::span &args) { device = alcOpenDevice(std::string{args[1]}.c_str()); if(!device) - std::cerr<< "Failed to open \""< &args) if(ctx) alcDestroyContext(ctx); alcCloseDevice(device); - std::fprintf(stderr, "Could not set a context!\n"); + fmt::println(stderr, "Could not set a context!"); return 1; } @@ -77,7 +77,7 @@ int InitAL(al::span &args) name = alcGetString(device, ALC_ALL_DEVICES_SPECIFIER); if(!name || alcGetError(device) != AL_NO_ERROR) name = alcGetString(device, ALC_DEVICE_SPECIFIER); - std::printf("Opened \"%s\"\n", name); + fmt::println("Opened \"{}\"", name); return 0; } diff --git a/Engine/lib/openal-soft/fmt-11.1.1/.clang-format b/Engine/lib/openal-soft/fmt-11.1.1/.clang-format new file mode 100644 index 000000000..31f8c343e --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/.clang-format @@ -0,0 +1,14 @@ +# Run manually to reformat a file: +# clang-format -i --style=file +Language: Cpp +BasedOnStyle: Google +IndentPPDirectives: AfterHash +IndentCaseLabels: false +AlwaysBreakTemplateDeclarations: false +DerivePointerAlignment: false +AllowShortCaseLabelsOnASingleLine: true +AlignConsecutiveShortCaseStatements: + Enabled: true + AcrossEmptyLines: true + AcrossComments: true + AlignCaseColons: false \ No newline at end of file diff --git a/Engine/lib/openal-soft/fmt-11.1.1/.github/dependabot.yml b/Engine/lib/openal-soft/fmt-11.1.1/.github/dependabot.yml new file mode 100644 index 000000000..f74783d97 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" # Necessary to update action hashes. + directory: "/" + schedule: + interval: "monthly" + # Allow up to 3 opened pull requests for github-actions versions. + open-pull-requests-limit: 3 diff --git a/Engine/lib/openal-soft/fmt-11.1.1/.github/issue_template.md b/Engine/lib/openal-soft/fmt-11.1.1/.github/issue_template.md new file mode 100644 index 000000000..8e39c5ba7 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/.github/issue_template.md @@ -0,0 +1,6 @@ + diff --git a/Engine/lib/openal-soft/fmt-11.1.1/.github/pull_request_template.md b/Engine/lib/openal-soft/fmt-11.1.1/.github/pull_request_template.md new file mode 100644 index 000000000..e87741339 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/.github/pull_request_template.md @@ -0,0 +1,7 @@ + diff --git a/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/cifuzz.yml b/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/cifuzz.yml new file mode 100644 index 000000000..50182ee97 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/cifuzz.yml @@ -0,0 +1,32 @@ +name: CIFuzz +on: [pull_request] + +permissions: + contents: read + +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@92182553173581f871130c71c71b17f003d47b0a + with: + oss-fuzz-project-name: 'fmt' + dry-run: false + language: c++ + + - name: Run fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@92182553173581f871130c71c71b17f003d47b0a + with: + oss-fuzz-project-name: 'fmt' + fuzz-seconds: 300 + dry-run: false + language: c++ + + - name: Upload crash + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts diff --git a/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/doc.yml b/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/doc.yml new file mode 100644 index 000000000..019a85f12 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/doc.yml @@ -0,0 +1,43 @@ +name: doc + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + # Use Ubuntu 20.04 because doxygen 1.8.13 from Ubuntu 18.04 is broken. + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Add Ubuntu mirrors + run: | + # Github Actions caching proxy is at times unreliable + # see https://github.com/actions/runner-images/issues/7048 + printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt + curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt + sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list + + - name: Create build environment + run: | + sudo apt update + sudo apt install doxygen + pip install mkdocs-material==9.5.25 mkdocstrings==0.25.1 mike==2.1.1 + cmake -E make_directory ${{runner.workspace}}/build + # Workaround https://github.com/actions/checkout/issues/13: + git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)" + git config --global user.email "$(git --no-pager log --format=format:'%ae' -n 1)" + + - name: Build + working-directory: ${{runner.workspace}}/build + run: $GITHUB_WORKSPACE/support/mkdocs deploy dev + + - name: Deploy + env: + KEY: "${{secrets.KEY}}" + if: env.KEY != '' && github.ref == 'refs/heads/master' + working-directory: ${{runner.workspace}}/fmt/build/fmt.dev + run: git push https://$KEY@github.com/fmtlib/fmt.dev.git diff --git a/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/lint.yml b/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/lint.yml new file mode 100644 index 000000000..cbef56a41 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/lint.yml @@ -0,0 +1,28 @@ +name: lint + +on: + pull_request: + paths: + - '**.h' + - '**.cc' + +permissions: + contents: read + +jobs: + format_code: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Install clang-format + run: | + wget https://apt.llvm.org/llvm.sh + sudo bash ./llvm.sh 17 + sudo apt install clang-format-17 + + - name: Run clang-format + run: | + find include src -name '*.h' -o -name '*.cc' | \ + xargs clang-format-17 -i -style=file -fallback-style=none + git diff --exit-code diff --git a/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/linux.yml b/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/linux.yml new file mode 100644 index 000000000..97150467f --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/linux.yml @@ -0,0 +1,118 @@ +name: linux + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-20.04 + strategy: + matrix: + cxx: [g++-4.9, g++-10, clang++-9] + build_type: [Debug, Release] + std: [11] + shared: [""] + include: + - cxx: g++-4.9 + install: sudo apt install g++-4.9 + - cxx: g++-8 + build_type: Debug + std: 14 + install: sudo apt install g++-8 + - cxx: g++-8 + build_type: Debug + std: 17 + install: sudo apt install g++-8 + - cxx: g++-9 + build_type: Debug + std: 17 + - cxx: g++-10 + build_type: Debug + std: 17 + - cxx: g++-11 + build_type: Debug + std: 20 + install: sudo apt install g++-11 + - cxx: clang++-8 + build_type: Debug + std: 17 + cxxflags: -stdlib=libc++ + install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev + - cxx: clang++-9 + install: sudo apt install clang-9 + - cxx: clang++-9 + build_type: Debug + fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON + std: 17 + install: sudo apt install clang-9 + - cxx: clang++-11 + build_type: Debug + std: 20 + - cxx: clang++-11 + build_type: Debug + std: 20 + cxxflags: -stdlib=libc++ + install: sudo apt install libc++-11-dev libc++abi-11-dev + - cxx: g++-13 + build_type: Release + std: 23 + install: sudo apt install g++-13 + shared: -DBUILD_SHARED_LIBS=ON + + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Set timezone + run: sudo timedatectl set-timezone 'Asia/Yekaterinburg' + + - name: Add repositories for older GCC + run: | + # Below repo provides GCC 4.9. + sudo apt-add-repository 'deb http://dk.archive.ubuntu.com/ubuntu/ xenial main' + sudo apt-add-repository 'deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe' + if: ${{ matrix.cxx == 'g++-4.9' }} + + - name: Add repositories for newer GCC + run: | + sudo apt-add-repository ppa:ubuntu-toolchain-r/test + if: ${{ matrix.cxx == 'g++-11' || matrix.cxx == 'g++-13' }} + + - name: Add Ubuntu mirrors + run: | + # Github Actions caching proxy is at times unreliable + # see https://github.com/actions/runner-images/issues/7048 + printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt + curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt + sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list + + - name: Create Build Environment + run: | + sudo apt update + ${{matrix.install}} + sudo apt install locales-all + cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + working-directory: ${{runner.workspace}}/build + env: + CXX: ${{matrix.cxx}} + CXXFLAGS: ${{matrix.cxxflags}} + run: | + cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.fuzz}} ${{matrix.shared}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} -DFMT_DOC=OFF \ + -DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ + -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE + + - name: Build + working-directory: ${{runner.workspace}}/build + run: | + threads=`nproc` + cmake --build . --config ${{matrix.build_type}} --parallel $threads + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} + env: + CTEST_OUTPUT_ON_FAILURE: True diff --git a/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/macos.yml b/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/macos.yml new file mode 100644 index 000000000..3543ef57b --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/macos.yml @@ -0,0 +1,58 @@ +name: macos + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + strategy: + matrix: + os: [macos-13, macos-14] + build_type: [Debug, Release] + std: [11, 17, 20] + shared: [""] + exclude: + - { os: macos-13, std: 11 } + - { os: macos-13, std: 17 } + include: + - os: macos-14 + std: 23 + build_type: Release + shared: -DBUILD_SHARED_LIBS=ON + + runs-on: '${{ matrix.os }}' + + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Set timezone + run: sudo systemsetup -settimezone 'Asia/Yekaterinburg' + + - name: Select Xcode 14.3 (macOS 13) + run: sudo xcode-select -s "/Applications/Xcode_14.3.app" + if: ${{ matrix.os == 'macos-13' }} + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + working-directory: ${{runner.workspace}}/build + run: | + cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} \ + -DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ + -DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE + + - name: Build + working-directory: ${{runner.workspace}}/build + run: | + threads=`sysctl -n hw.logicalcpu` + cmake --build . --config ${{matrix.build_type}} --parallel $threads + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} + env: + CTEST_OUTPUT_ON_FAILURE: True diff --git a/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/scorecard.yml b/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/scorecard.yml new file mode 100644 index 000000000..b363a6f84 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/scorecard.yml @@ -0,0 +1,65 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '26 14 * * 5' + push: + branches: [ "master" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + + steps: + - name: "Checkout code" + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + with: + sarif_file: results.sarif diff --git a/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/windows.yml b/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/windows.yml new file mode 100644 index 000000000..5403e652e --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/.github/workflows/windows.yml @@ -0,0 +1,95 @@ +name: windows + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + runs-on: ${{matrix.os}} + strategy: + matrix: + # windows-2019 has MSVC 2019 installed; + # windows-2022 has MSVC 2022 installed: + # https://github.com/actions/virtual-environments. + os: [windows-2019] + platform: [Win32, x64] + toolset: [v141, v142] + standard: [14, 17, 20] + shared: ["", -DBUILD_SHARED_LIBS=ON] + build_type: [Debug, Release] + exclude: + - { toolset: v141, standard: 20 } + - { toolset: v142, standard: 14 } + - { platform: Win32, toolset: v141 } + - { platform: Win32, standard: 14 } + - { platform: Win32, standard: 20 } + - { platform: x64, toolset: v141, shared: -DBUILD_SHARED_LIBS=ON } + - { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON } + - { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON } + include: + - os: windows-2022 + platform: x64 + toolset: v143 + build_type: Debug + standard: 20 + + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Set timezone + run: tzutil /s "Ekaterinburg Standard Time" + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + # Use a bash shell for $GITHUB_WORKSPACE. + shell: bash + working-directory: ${{runner.workspace}}/build + run: | + cmake -A ${{matrix.platform}} -T ${{matrix.toolset}} \ + -DCMAKE_CXX_STANDARD=${{matrix.standard}} \ + ${{matrix.shared}} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ + $GITHUB_WORKSPACE + + - name: Build + working-directory: ${{runner.workspace}}/build + run: | + $threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors + cmake --build . --config ${{matrix.build_type}} --parallel $threads + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} -V + env: + CTEST_OUTPUT_ON_FAILURE: True + + mingw: + runs-on: windows-latest + defaults: + run: + shell: msys2 {0} + strategy: + matrix: + sys: [ mingw64, ucrt64 ] + steps: + - name: Set timezone + run: tzutil /s "Ekaterinburg Standard Time" + shell: cmd + - uses: msys2/setup-msys2@c52d1fa9c7492275e60fe763540fb601f5f232a1 # v2.25.0 + with: + release: false + msystem: ${{matrix.sys}} + pacboy: cc:p cmake:p ninja:p lld:p + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - name: Configure + run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug + env: { LDFLAGS: -fuse-ld=lld } + - name: Build + run: cmake --build ../build + - name: Test + run: ctest -j `nproc` --test-dir ../build + env: + CTEST_OUTPUT_ON_FAILURE: True diff --git a/Engine/lib/openal-soft/fmt-11.1.1/.gitignore b/Engine/lib/openal-soft/fmt-11.1.1/.gitignore new file mode 100644 index 000000000..2635026a6 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/.gitignore @@ -0,0 +1,20 @@ +*.a +*.so* +*.xcodeproj +*~ +.vscode/ +/CMakeScripts +/Testing +/_CPack_Packages +/install_manifest.txt +CMakeCache.txt +CMakeFiles +CPack*.cmake +CTestTestfile.cmake +FMT.build +Makefile +bin/ +build/ +cmake_install.cmake +fmt-*.cmake +fmt.pc diff --git a/Engine/lib/openal-soft/fmt-11.1.1/CMakeLists.txt b/Engine/lib/openal-soft/fmt-11.1.1/CMakeLists.txt new file mode 100644 index 000000000..acaf99e6b --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/CMakeLists.txt @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 3.8...3.28) + +# [CR] Heavily modified for OpenAL Soft to avoid clashes if used as a sub- +# project with other uses of fmt. + +# Fallback for using newer policies on CMake <3.12. +if (${CMAKE_VERSION} VERSION_LESS 3.12) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +endif () + +# Joins arguments and places the results in ${result_var}. +function(join result_var) + set(result "") + foreach (arg ${ARGN}) + set(result "${result}${arg}") + endforeach () + set(${result_var} "${result}" PARENT_SCOPE) +endfunction() + +include(CMakeParseArguments) + +project(ALSOFT_FMT CXX) + +# Get version from base.h +file(READ include/fmt/base.h base_h) +if (NOT base_h MATCHES "FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])") + message(FATAL_ERROR "Cannot get FMT_VERSION from base.h.") +endif () +# Use math to skip leading zeros if any. +math(EXPR CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1}) +math(EXPR CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2}) +math(EXPR CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3}) +join(FMT_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}. + ${CPACK_PACKAGE_VERSION_PATCH}) +message(STATUS "{fmt} version: ${FMT_VERSION}") + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} + "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake") + +include(CheckCXXCompilerFlag) +include(JoinPaths) + +function(add_headers VAR) + set(headers ${${VAR}}) + foreach (header ${ARGN}) + set(headers ${headers} include/fmt/${header}) + endforeach() + set(${VAR} ${headers} PARENT_SCOPE) +endfunction() + +set(ALSOFT_FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.") + +# Define the fmt library, its includes and the needed defines. +add_headers(FMT_HEADERS args.h base.h chrono.h color.h compile.h core.h format.h + format-inl.h os.h ostream.h printf.h ranges.h std.h + xchar.h) +set(FMT_SOURCES src/format.cc src/os.cc) + +add_library(alsoft.fmt OBJECT ${FMT_SOURCES} ${FMT_HEADERS} README.md ChangeLog.md) +add_library(alsoft::fmt ALIAS alsoft.fmt) + +if (cxx_std_11 IN_LIST CMAKE_CXX_COMPILE_FEATURES) + target_compile_features(alsoft.fmt PUBLIC cxx_std_11) +else () + message(WARNING "Feature cxx_std_11 is unknown for the CXX compiler") +endif () + +target_include_directories(alsoft.fmt PUBLIC + $) + +set_target_properties(alsoft.fmt PROPERTIES ${ALSOFT_STD_VERSION_PROPS} + VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} + DEBUG_POSTFIX "${ALSOFT_FMT_DEBUG_POSTFIX}" + POSITION_INDEPENDENT_CODE TRUE + C_VISIBILITY_PRESET hidden + CXX_VISIBILITY_PRESET hidden + EXCLUDE_FROM_ALL TRUE) + +if (MSVC) + # Unicode support requires compiling with /utf-8. + target_compile_options(alsoft.fmt PUBLIC $<$:/utf-8>) +endif () diff --git a/Engine/lib/openal-soft/fmt-11.1.1/CONTRIBUTING.md b/Engine/lib/openal-soft/fmt-11.1.1/CONTRIBUTING.md new file mode 100644 index 000000000..b82f14506 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/CONTRIBUTING.md @@ -0,0 +1,20 @@ +Contributing to {fmt} +===================== + +By submitting a pull request or a patch, you represent that you have the right +to license your contribution to the {fmt} project owners and the community, +agree that your contributions are licensed under the {fmt} license, and agree +to future changes to the licensing. + +All C++ code must adhere to [Google C++ Style Guide]( +https://google.github.io/styleguide/cppguide.html) with the following +exceptions: + +* Exceptions are permitted +* snake_case should be used instead of UpperCamelCase for function and type + names + +All documentation must adhere to the [Google Developer Documentation Style +Guide](https://developers.google.com/style). + +Thanks for contributing! diff --git a/Engine/lib/openal-soft/fmt-11.1.1/ChangeLog.md b/Engine/lib/openal-soft/fmt-11.1.1/ChangeLog.md new file mode 100644 index 000000000..09ebaed65 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/ChangeLog.md @@ -0,0 +1,2866 @@ +# 11.1.1 - 2024-12-27 + +- Fixed ABI compatibility with earlier 11.x versions + (https://github.com/fmtlib/fmt/issues/4278). + +- Defined CMake components (`core` and `doc`) to allow docs to be installed + separately (https://github.com/fmtlib/fmt/pull/4276). + Thanks @carlsmedstad. + +# 11.1.0 - 2024-12-25 + +- Improved C++20 module support + (https://github.com/fmtlib/fmt/issues/4081, + https://github.com/fmtlib/fmt/pull/4083, + https://github.com/fmtlib/fmt/pull/4084, + https://github.com/fmtlib/fmt/pull/4152, + https://github.com/fmtlib/fmt/issues/4153, + https://github.com/fmtlib/fmt/pull/4169, + https://github.com/fmtlib/fmt/issues/4190, + https://github.com/fmtlib/fmt/issues/4234, + https://github.com/fmtlib/fmt/pull/4239). + Thanks @kamrann and @Arghnews. + +- Reduced debug (unoptimized) binary code size and the number of template + instantiations when passing formatting arguments. For example, unoptimized + binary code size for `fmt::print("{}", 42)` was reduced by ~40% on GCC and + ~60% on clang (x86-64). + + GCC: + - Before: 161 instructions of which 105 are in reusable functions + ([godbolt](https://www.godbolt.org/z/s9bGoo4ze)). + - After: 116 instructions of which 60 are in reusable functions + ([godbolt](https://www.godbolt.org/z/r7GGGxMs6)). + + Clang: + - Before: 310 instructions of which 251 are in reusable functions + ([godbolt](https://www.godbolt.org/z/Ts88b7M9o)). + - After: 194 instructions of which 135 are in reusable functions + ([godbolt](https://www.godbolt.org/z/vcrjP8ceW)). + +- Added an experimental `fmt::writer` API that can be used for writing to + different destinations such as files or strings + (https://github.com/fmtlib/fmt/issues/2354). + For example ([godbolt](https://www.godbolt.org/z/rWoKfbP7e)): + + ```c++ + #include + + void write_text(fmt::writer w) { + w.print("The answer is {}.", 42); + } + + int main() { + // Write to FILE. + write_text(stdout); + + // Write to fmt::ostream. + auto f = fmt::output_file("myfile"); + write_text(f); + + // Write to std::string. + auto sb = fmt::string_buffer(); + write_text(sb); + std::string s = sb.str(); + } + ``` + +- Added width and alignment support to the formatter of `std::error_code`. + +- Made `std::expected` formattable + (https://github.com/fmtlib/fmt/issues/4145, + https://github.com/fmtlib/fmt/pull/4148). + For example ([godbolt](https://www.godbolt.org/z/hrj5c6G86)): + + ```c++ + fmt::print("{}", std::expected()); + ``` + + prints + + ``` + expected() + ``` + + Thanks @phprus. + +- Made `fmt::is_formattable` SFINAE-friendly + (https://github.com/fmtlib/fmt/issues/4147). + +- Added support for `_BitInt` formatting when using clang + (https://github.com/fmtlib/fmt/issues/4007, + https://github.com/fmtlib/fmt/pull/4072, + https://github.com/fmtlib/fmt/issues/4140, + https://github.com/fmtlib/fmt/issues/4173, + https://github.com/fmtlib/fmt/pull/4176). + For example ([godbolt](https://www.godbolt.org/z/KWjbWec5z)): + + ```c++ + using int42 = _BitInt(42); + fmt::print("{}", int42(100)); + ``` + + Thanks @Arghnews. + +- Added the `n` specifier for tuples and pairs + (https://github.com/fmtlib/fmt/pull/4107). Thanks @someonewithpc. + +- Added support for tuple-like types to `fmt::join` + (https://github.com/fmtlib/fmt/issues/4226, + https://github.com/fmtlib/fmt/pull/4230). Thanks @phprus. + +- Made more types formattable at compile time + (https://github.com/fmtlib/fmt/pull/4127). Thanks @AnthonyVH. + +- Implemented a more efficient compile-time `fmt::formatted_size` + (https://github.com/fmtlib/fmt/issues/4102, + https://github.com/fmtlib/fmt/pull/4103). Thanks @phprus. + +- Fixed compile-time formatting of some string types + (https://github.com/fmtlib/fmt/pull/4065). Thanks @torshepherd. + +- Made compiled version of `fmt::format_to` work with + `std::back_insert_iterator>` + (https://github.com/fmtlib/fmt/issues/4206, + https://github.com/fmtlib/fmt/pull/4211). Thanks @phprus. + +- Added a formatter for `std::reference_wrapper` + (https://github.com/fmtlib/fmt/pull/4163, + https://github.com/fmtlib/fmt/pull/4164). Thanks @yfeldblum and @phprus. + +- Added experimental padding support (glibc `strftime` extension) to `%m`, `%j` + and `%Y` (https://github.com/fmtlib/fmt/pull/4161). Thanks @KKhanhH. + +- Made microseconds formatted as `us` instead of `µs` if the Unicode support is + disabled (https://github.com/fmtlib/fmt/issues/4088). + +- Fixed an unreleased regression in transcoding of surrogate pairs + (https://github.com/fmtlib/fmt/issues/4094, + https://github.com/fmtlib/fmt/pull/4095). Thanks @phprus. + +- Made `fmt::appender` satisfy `std::output_iterator` concept + (https://github.com/fmtlib/fmt/issues/4092, + https://github.com/fmtlib/fmt/pull/4093). Thanks @phprus. + +- Made `std::iterator_traits` standard-conforming + (https://github.com/fmtlib/fmt/pull/4185). Thanks @CaseyCarter. + +- Made it easier to reuse `fmt::formatter` for types with + an implicit conversion to `std::string_view` + (https://github.com/fmtlib/fmt/issues/4036, + https://github.com/fmtlib/fmt/pull/4055). Thanks @Arghnews. + +- Made it possible to disable `` use via `FMT_CPP_LIB_FILESYSTEM` + for compatibility with some video game console SDKs, e.g. Nintendo Switch SDK + (https://github.com/fmtlib/fmt/issues/4257, + https://github.com/fmtlib/fmt/pull/4258, + https://github.com/fmtlib/fmt/pull/4259). Thanks @W4RH4WK and @phprus. + +- Fixed compatibility with platforms that use 80-bit `long double` + (https://github.com/fmtlib/fmt/issues/4245, + https://github.com/fmtlib/fmt/pull/4246). Thanks @jsirpoma. + +- Added support for UTF-32 code units greater than `0xFFFF` in fill + (https://github.com/fmtlib/fmt/issues/4201). + +- Fixed handling of legacy encodings on Windows with GCC + (https://github.com/fmtlib/fmt/issues/4162). + +- Made `fmt::to_string` take `fmt::basic_memory_buffer` by const reference + (https://github.com/fmtlib/fmt/issues/4261, + https://github.com/fmtlib/fmt/pull/4262). Thanks @sascha-devel. + +- Added `fmt::dynamic_format_arg_store::size` + (https://github.com/fmtlib/fmt/pull/4270). Thanks @hannes-harnisch. + +- Removed the ability to control locale usage via an undocumented + `FMT_STATIC_THOUSANDS_SEPARATOR` in favor of `FMT_USE_LOCALE`. + +- Renamed `FMT_EXCEPTIONS` to `FMT_USE_EXCEPTIONS` for consistency with other + similar macros. + +- Improved include directory ordering to reduce the chance of including + incorrect headers when using multiple versions of {fmt} + (https://github.com/fmtlib/fmt/pull/4116). Thanks @cdzhan. + +- Made it possible to compile a subset of {fmt} without the C++ runtime. + +- Improved documentation and README + (https://github.com/fmtlib/fmt/pull/4066, + https://github.com/fmtlib/fmt/issues/4117, + https://github.com/fmtlib/fmt/issues/4203, + https://github.com/fmtlib/fmt/pull/4235). Thanks @zyctree and @nikola-sh. + +- Improved the documentation generator (https://github.com/fmtlib/fmt/pull/4110, + https://github.com/fmtlib/fmt/pull/4115). Thanks @rturrado. + +- Improved CI (https://github.com/fmtlib/fmt/pull/4155, + https://github.com/fmtlib/fmt/pull/4151). Thanks @phprus. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/2708, + https://github.com/fmtlib/fmt/issues/4091, + https://github.com/fmtlib/fmt/issues/4109, + https://github.com/fmtlib/fmt/issues/4113, + https://github.com/fmtlib/fmt/issues/4125, + https://github.com/fmtlib/fmt/issues/4129, + https://github.com/fmtlib/fmt/pull/4130, + https://github.com/fmtlib/fmt/pull/4131, + https://github.com/fmtlib/fmt/pull/4132, + https://github.com/fmtlib/fmt/issues/4133, + https://github.com/fmtlib/fmt/issues/4144, + https://github.com/fmtlib/fmt/issues/4150, + https://github.com/fmtlib/fmt/issues/4158, + https://github.com/fmtlib/fmt/pull/4159, + https://github.com/fmtlib/fmt/issues/4160, + https://github.com/fmtlib/fmt/pull/4170, + https://github.com/fmtlib/fmt/issues/4177, + https://github.com/fmtlib/fmt/pull/4187, + https://github.com/fmtlib/fmt/pull/4188, + https://github.com/fmtlib/fmt/pull/4194, + https://github.com/fmtlib/fmt/pull/4200, + https://github.com/fmtlib/fmt/issues/4205, + https://github.com/fmtlib/fmt/issues/4207, + https://github.com/fmtlib/fmt/pull/4208, + https://github.com/fmtlib/fmt/pull/4210, + https://github.com/fmtlib/fmt/issues/4220, + https://github.com/fmtlib/fmt/issues/4231, + https://github.com/fmtlib/fmt/issues/4232, + https://github.com/fmtlib/fmt/pull/4233, + https://github.com/fmtlib/fmt/pull/4236, + https://github.com/fmtlib/fmt/pull/4267, + https://github.com/fmtlib/fmt/pull/4271). + Thanks @torsten48, @Arghnews, @tinfoilboy, @aminya, @Ottani, @zeroomega, + @c4v4, @kongy, @vinayyadav3016, @sergio-nsk, @phprus and @YexuanXiao. + +# 11.0.2 - 2024-07-20 + +- Fixed compatibility with non-POSIX systems + (https://github.com/fmtlib/fmt/issues/4054, + https://github.com/fmtlib/fmt/issues/4060). + +- Fixed performance regressions when using `std::back_insert_iterator` with + `fmt::format_to` (https://github.com/fmtlib/fmt/issues/4070). + +- Fixed handling of `std::generator` and move-only iterators + (https://github.com/fmtlib/fmt/issues/4053, + https://github.com/fmtlib/fmt/pull/4057). Thanks @Arghnews. + +- Made `formatter::parse` work with types convertible to + `std::string_view` (https://github.com/fmtlib/fmt/issues/4036, + https://github.com/fmtlib/fmt/pull/4055). Thanks @Arghnews. + +- Made `volatile void*` formattable + (https://github.com/fmtlib/fmt/issues/4049, + https://github.com/fmtlib/fmt/pull/4056). Thanks @Arghnews. + +- Made `Glib::ustring` not be confused with `std::string` + (https://github.com/fmtlib/fmt/issues/4052). + +- Made `fmt::context` iterator compatible with STL algorithms that rely on + iterator category (https://github.com/fmtlib/fmt/issues/4079). + +# 11.0.1 - 2024-07-05 + +- Fixed version number in the inline namespace + (https://github.com/fmtlib/fmt/issues/4047). + +- Fixed disabling Unicode support via CMake + (https://github.com/fmtlib/fmt/issues/4051). + +- Fixed deprecated `visit_format_arg` (https://github.com/fmtlib/fmt/pull/4043). + Thanks @nebkat. + +- Fixed handling of a sign and improved the `std::complex` formater + (https://github.com/fmtlib/fmt/pull/4034, + https://github.com/fmtlib/fmt/pull/4050). Thanks @tesch1 and @phprus. + +- Fixed ADL issues in `fmt::printf` when using C++20 + (https://github.com/fmtlib/fmt/pull/4042). Thanks @toge. + +- Removed a redundant check in the formatter for `std::expected` + (https://github.com/fmtlib/fmt/pull/4040). Thanks @phprus. + +# 11.0.0 - 2024-07-01 + +- Added `fmt/base.h` which provides a subset of the API with minimal include + dependencies and enough functionality to replace all uses of the `printf` + family of functions. This brings the compile time of code using {fmt} much + closer to the equivalent `printf` code as shown on the following benchmark + that compiles 100 source files: + + | Method | Compile Time (s) | + |--------------|------------------| + | printf | 1.6 | + | IOStreams | 25.9 | + | fmt 10.x | 19.0 | + | fmt 11.0 | 4.8 | + | tinyformat | 29.1 | + | Boost Format | 55.0 | + + This gives almost 4x improvement in build speed compared to version 10. + Note that the benchmark is purely formatting code and includes. In real + projects the difference from `printf` will be smaller partly because common + standard headers will be included in almost any translation unit (TU) anyway. + In particular, in every case except `printf` above ~1s is spent in total on + including `` in all TUs. + +- Optimized includes in other headers such as `fmt/format.h` which is now + roughly equivalent to the old `fmt/core.h` in terms of build speed. + +- Migrated the documentation at https://fmt.dev/ from Sphinx to MkDocs. + +- Improved C++20 module support + (https://github.com/fmtlib/fmt/issues/3990, + https://github.com/fmtlib/fmt/pull/3991, + https://github.com/fmtlib/fmt/issues/3993, + https://github.com/fmtlib/fmt/pull/3994, + https://github.com/fmtlib/fmt/pull/3997, + https://github.com/fmtlib/fmt/pull/3998, + https://github.com/fmtlib/fmt/pull/4004, + https://github.com/fmtlib/fmt/pull/4005, + https://github.com/fmtlib/fmt/pull/4006, + https://github.com/fmtlib/fmt/pull/4013, + https://github.com/fmtlib/fmt/pull/4027, + https://github.com/fmtlib/fmt/pull/4029). In particular, native CMake support + for modules is now used if available. Thanks @yujincheng08 and @matt77hias. + +- Added an option to replace standard includes with `import std` enabled via + the `FMT_IMPORT_STD` macro (https://github.com/fmtlib/fmt/issues/3921, + https://github.com/fmtlib/fmt/pull/3928). Thanks @matt77hias. + +- Exported `fmt::range_format`, `fmt::range_format_kind` and + `fmt::compiled_string` from the `fmt` module + (https://github.com/fmtlib/fmt/pull/3970, + https://github.com/fmtlib/fmt/pull/3999). + Thanks @matt77hias and @yujincheng08. + +- Improved integration with stdio in `fmt::print`, enabling direct writes + into a C stream buffer in common cases. This may give significant + performance improvements ranging from tens of percent to [2x]( + https://stackoverflow.com/a/78457454/471164) and eliminates dynamic memory + allocations on the buffer level. It is currently enabled for built-in and + string types with wider availability coming up in future releases. + + For example, it gives ~24% improvement on a [simple benchmark]( + https://isocpp.org/files/papers/P3107R5.html#perf) compiled with Apple clang + version 15.0.0 (clang-1500.1.0.2.5) and run on macOS 14.2.1: + + ``` + ------------------------------------------------------- + Benchmark Time CPU Iterations + ------------------------------------------------------- + printf 81.8 ns 81.5 ns 8496899 + fmt::print (10.x) 63.8 ns 61.9 ns 11524151 + fmt::print (11.0) 51.3 ns 51.0 ns 13846580 + ``` + +- Improved safety of `fmt::format_to` when writing to an array + (https://github.com/fmtlib/fmt/pull/3805). + For example ([godbolt](https://www.godbolt.org/z/cYrn8dWY8)): + + ```c++ + auto volkswagen = char[4]; + auto result = fmt::format_to(volkswagen, "elephant"); + ``` + + no longer results in a buffer overflow. Instead the output will be truncated + and you can get the end iterator and whether truncation occurred from the + `result` object. Thanks @ThePhD. + +- Enabled Unicode support by default in MSVC, bringing it on par with other + compilers and making it unnecessary for users to enable it explicitly. + Most of {fmt} is encoding-agnostic but this prevents mojibake in places + where encoding matters such as path formatting and terminal output. + You can control the Unicode support via the CMake `FMT_UNICODE` option. + Note that some {fmt} packages such as the one in vcpkg have already been + compiled with Unicode enabled. + +- Added a formatter for `std::expected` + (https://github.com/fmtlib/fmt/pull/3834). Thanks @dominicpoeschko. + +- Added a formatter for `std::complex` + (https://github.com/fmtlib/fmt/issues/1467, + https://github.com/fmtlib/fmt/issues/3886, + https://github.com/fmtlib/fmt/pull/3892, + https://github.com/fmtlib/fmt/pull/3900). Thanks @phprus. + +- Added a formatter for `std::type_info` + (https://github.com/fmtlib/fmt/pull/3978). Thanks @matt77hias. + +- Specialized `formatter` for `std::basic_string` types with custom traits + and allocators (https://github.com/fmtlib/fmt/issues/3938, + https://github.com/fmtlib/fmt/pull/3943). Thanks @dieram3. + +- Added formatters for `std::chrono::day`, `std::chrono::month`, + `std::chrono::year` and `std::chrono::year_month_day` + (https://github.com/fmtlib/fmt/issues/3758, + https://github.com/fmtlib/fmt/issues/3772, + https://github.com/fmtlib/fmt/pull/3906, + https://github.com/fmtlib/fmt/pull/3913). For example: + + ```c++ + #include + #include + + int main() { + fmt::print(fg(fmt::color::green), "{}\n", std::chrono::day(7)); + } + ``` + + prints a green day: + + image + + Thanks @zivshek. + +- Fixed handling of precision in `%S` (https://github.com/fmtlib/fmt/issues/3794, + https://github.com/fmtlib/fmt/pull/3814). Thanks @js324. + +- Added support for the `-` specifier (glibc `strftime` extension) to day of + the month (`%d`) and week of the year (`%W`, `%U`, `%V`) specifiers + (https://github.com/fmtlib/fmt/pull/3976). Thanks @ZaheenJ. + +- Fixed the scope of the `-` extension in chrono formatting so that it doesn't + apply to subsequent specifiers (https://github.com/fmtlib/fmt/issues/3811, + https://github.com/fmtlib/fmt/pull/3812). Thanks @phprus. + +- Improved handling of `time_point::min()` + (https://github.com/fmtlib/fmt/issues/3282). + +- Added support for character range formatting + (https://github.com/fmtlib/fmt/issues/3857, + https://github.com/fmtlib/fmt/pull/3863). Thanks @js324. + +- Added `string` and `debug_string` range formatters + (https://github.com/fmtlib/fmt/pull/3973, + https://github.com/fmtlib/fmt/pull/4024). Thanks @matt77hias. + +- Enabled ADL for `begin` and `end` in `fmt::join` + (https://github.com/fmtlib/fmt/issues/3813, + https://github.com/fmtlib/fmt/pull/3824). Thanks @bbolli. + +- Made contiguous iterator optimizations apply to `std::basic_string` iterators + (https://github.com/fmtlib/fmt/pull/3798). Thanks @phprus. + +- Added support for ranges with mutable `begin` and `end` + (https://github.com/fmtlib/fmt/issues/3752, + https://github.com/fmtlib/fmt/pull/3800, + https://github.com/fmtlib/fmt/pull/3955). Thanks @tcbrindle and @Arghnews. + +- Added support for move-only iterators to `fmt::join` + (https://github.com/fmtlib/fmt/issues/3802, + https://github.com/fmtlib/fmt/pull/3946). Thanks @Arghnews. + +- Moved range and iterator overloads of `fmt::join` to `fmt/ranges.h`, next + to other overloads. + +- Fixed handling of types with `begin` returning `void` such as Eigen matrices + (https://github.com/fmtlib/fmt/issues/3839, + https://github.com/fmtlib/fmt/pull/3964). Thanks @Arghnews. + +- Added an `fmt::formattable` concept (https://github.com/fmtlib/fmt/pull/3974). + Thanks @matt77hias. + +- Added support for `__float128` (https://github.com/fmtlib/fmt/issues/3494). + +- Fixed rounding issues when formatting `long double` with fixed precision + (https://github.com/fmtlib/fmt/issues/3539). + +- Made `fmt::isnan` not trigger floating-point exception for NaN values + (https://github.com/fmtlib/fmt/issues/3948, + https://github.com/fmtlib/fmt/pull/3951). Thanks @alexdewar. + +- Removed dependency on `` for `std::allocator_traits` when possible + (https://github.com/fmtlib/fmt/pull/3804). Thanks @phprus. + +- Enabled compile-time checks in formatting functions that take text colors and + styles. + +- Deprecated wide stream overloads of `fmt::print` that take text styles. + +- Made format string compilation work with clang 12 and later despite + only partial non-type template parameter support + (https://github.com/fmtlib/fmt/issues/4000, + https://github.com/fmtlib/fmt/pull/4001). Thanks @yujincheng08. + +- Made `fmt::iterator_buffer`'s move constructor `noexcept` + (https://github.com/fmtlib/fmt/pull/3808). Thanks @waywardmonkeys. + +- Started enforcing that `formatter::format` is const for compatibility + with `std::format` (https://github.com/fmtlib/fmt/issues/3447). + +- Added `fmt::basic_format_arg::visit` and deprecated `fmt::visit_format_arg`. + +- Made `fmt::basic_string_view` not constructible from `nullptr` for + consistency with `std::string_view` in C++23 + (https://github.com/fmtlib/fmt/pull/3846). Thanks @dalle. + +- Fixed `fmt::group_digits` for negative integers + (https://github.com/fmtlib/fmt/issues/3891, + https://github.com/fmtlib/fmt/pull/3901). Thanks @phprus. + +- Fixed handling of negative ids in `fmt::basic_format_args::get` + (https://github.com/fmtlib/fmt/pull/3945). Thanks @marlenecota. + +- Fixed handling of a buffer boundary on flush + (https://github.com/fmtlib/fmt/issues/4229). + +- Improved named argument validation + (https://github.com/fmtlib/fmt/issues/3817). + +- Disabled copy construction/assignment for `fmt::format_arg_store` and + fixed moved construction (https://github.com/fmtlib/fmt/pull/3833). + Thanks @ivafanas. + +- Worked around a locale issue in RHEL/devtoolset + (https://github.com/fmtlib/fmt/issues/3858, + https://github.com/fmtlib/fmt/pull/3859). Thanks @g199209. + +- Added RTTI detection for MSVC (https://github.com/fmtlib/fmt/pull/3821, + https://github.com/fmtlib/fmt/pull/3963). Thanks @edo9300. + +- Migrated the documentation from Sphinx to MkDocs. + +- Improved documentation and README + (https://github.com/fmtlib/fmt/issues/3775, + https://github.com/fmtlib/fmt/pull/3784, + https://github.com/fmtlib/fmt/issues/3788, + https://github.com/fmtlib/fmt/pull/3789, + https://github.com/fmtlib/fmt/pull/3793, + https://github.com/fmtlib/fmt/issues/3818, + https://github.com/fmtlib/fmt/pull/3820, + https://github.com/fmtlib/fmt/pull/3822, + https://github.com/fmtlib/fmt/pull/3843, + https://github.com/fmtlib/fmt/pull/3890, + https://github.com/fmtlib/fmt/issues/3894, + https://github.com/fmtlib/fmt/pull/3895, + https://github.com/fmtlib/fmt/pull/3905, + https://github.com/fmtlib/fmt/issues/3942, + https://github.com/fmtlib/fmt/pull/4008). + Thanks @zencatalyst, WolleTD, @tupaschoal, @Dobiasd, @frank-weinberg, @bbolli, + @phprus, @waywardmonkeys, @js324 and @tchaikov. + +- Improved CI and tests + (https://github.com/fmtlib/fmt/issues/3878, + https://github.com/fmtlib/fmt/pull/3883, + https://github.com/fmtlib/fmt/issues/3897, + https://github.com/fmtlib/fmt/pull/3979, + https://github.com/fmtlib/fmt/pull/3980, + https://github.com/fmtlib/fmt/pull/3988, + https://github.com/fmtlib/fmt/pull/4010, + https://github.com/fmtlib/fmt/pull/4012, + https://github.com/fmtlib/fmt/pull/4038). + Thanks @vgorrX, @waywardmonkeys, @tchaikov and @phprus. + +- Fixed buffer overflow when using format string compilation with debug format + and `std::back_insert_iterator` (https://github.com/fmtlib/fmt/issues/3795, + https://github.com/fmtlib/fmt/pull/3797). Thanks @phprus. + +- Improved Bazel support + (https://github.com/fmtlib/fmt/pull/3792, + https://github.com/fmtlib/fmt/pull/3801, + https://github.com/fmtlib/fmt/pull/3962, + https://github.com/fmtlib/fmt/pull/3965). Thanks @Vertexwahn. + +- Improved/fixed the CMake config + (https://github.com/fmtlib/fmt/issues/3777, + https://github.com/fmtlib/fmt/pull/3783, + https://github.com/fmtlib/fmt/issues/3847, + https://github.com/fmtlib/fmt/pull/3907). Thanks @phprus and @xTachyon. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/3685, + https://github.com/fmtlib/fmt/issues/3769, + https://github.com/fmtlib/fmt/issues/3796, + https://github.com/fmtlib/fmt/issues/3803, + https://github.com/fmtlib/fmt/pull/3806, + https://github.com/fmtlib/fmt/pull/3807, + https://github.com/fmtlib/fmt/issues/3809, + https://github.com/fmtlib/fmt/pull/3810, + https://github.com/fmtlib/fmt/issues/3830, + https://github.com/fmtlib/fmt/pull/3832, + https://github.com/fmtlib/fmt/issues/3835, + https://github.com/fmtlib/fmt/pull/3844, + https://github.com/fmtlib/fmt/issues/3854, + https://github.com/fmtlib/fmt/pull/3856, + https://github.com/fmtlib/fmt/pull/3865, + https://github.com/fmtlib/fmt/pull/3866, + https://github.com/fmtlib/fmt/pull/3880, + https://github.com/fmtlib/fmt/issues/3881, + https://github.com/fmtlib/fmt/issues/3884, + https://github.com/fmtlib/fmt/issues/3898, + https://github.com/fmtlib/fmt/pull/3899, + https://github.com/fmtlib/fmt/pull/3909, + https://github.com/fmtlib/fmt/pull/3917, + https://github.com/fmtlib/fmt/pull/3923, + https://github.com/fmtlib/fmt/pull/3924, + https://github.com/fmtlib/fmt/issues/3925, + https://github.com/fmtlib/fmt/pull/3930, + https://github.com/fmtlib/fmt/pull/3931, + https://github.com/fmtlib/fmt/pull/3933, + https://github.com/fmtlib/fmt/issues/3935, + https://github.com/fmtlib/fmt/pull/3937, + https://github.com/fmtlib/fmt/pull/3967, + https://github.com/fmtlib/fmt/pull/3968, + https://github.com/fmtlib/fmt/pull/3972, + https://github.com/fmtlib/fmt/pull/3983, + https://github.com/fmtlib/fmt/issues/3992, + https://github.com/fmtlib/fmt/pull/3995, + https://github.com/fmtlib/fmt/pull/4009, + https://github.com/fmtlib/fmt/pull/4023). + Thanks @hmbj, @phprus, @res2k, @Baardi, @matt77hias, @waywardmonkeys, @hmbj, + @yakra, @prlw1, @Arghnews, @mtillmann0, @ShifftC, @eepp, @jimmy-park and + @ChristianGebhardt. + +# 10.2.1 - 2024-01-04 + +- Fixed ABI compatibility with earlier 10.x versions + (https://github.com/fmtlib/fmt/issues/3785, + https://github.com/fmtlib/fmt/pull/3786). Thanks @saraedum. + +# 10.2.0 - 2024-01-01 + +- Added support for the `%j` specifier (the number of days) for + `std::chrono::duration` (https://github.com/fmtlib/fmt/issues/3643, + https://github.com/fmtlib/fmt/pull/3732). Thanks @intelfx. + +- Added support for the chrono suffix for days and changed + the suffix for minutes from "m" to the correct "min" + (https://github.com/fmtlib/fmt/issues/3662, + https://github.com/fmtlib/fmt/pull/3664). + For example ([godbolt](https://godbolt.org/z/9KhMnq9ba)): + + ```c++ + #include + + int main() { + fmt::print("{}\n", std::chrono::days(42)); // prints "42d" + } + ``` + + Thanks @Richardk2n. + +- Fixed an overflow in `std::chrono::time_point` formatting with large dates + (https://github.com/fmtlib/fmt/issues/3725, + https://github.com/fmtlib/fmt/pull/3727). Thanks @cschreib. + +- Added a formatter for `std::source_location` + (https://github.com/fmtlib/fmt/pull/3730). + For example ([godbolt](https://godbolt.org/z/YajfKjhhr)): + + ```c++ + #include + #include + + int main() { + fmt::print("{}\n", std::source_location::current()); + } + ``` + + prints + + ``` + /app/example.cpp:5:51: int main() + ``` + + Thanks @felix642. + +- Added a formatter for `std::bitset` + (https://github.com/fmtlib/fmt/pull/3660). + For example ([godbolt](https://godbolt.org/z/bdEaGeYxe)): + + ```c++ + #include + #include + + int main() { + fmt::print("{}\n", std::bitset<6>(42)); // prints "101010" + } + ``` + + Thanks @muggenhor. + +- Added an experimental `nested_formatter` that provides an easy way of + applying a formatter to one or more subobjects while automatically handling + width, fill and alignment. For example: + + ```c++ + #include + + struct point { + double x, y; + }; + + template <> + struct fmt::formatter : nested_formatter { + auto format(point p, format_context& ctx) const { + return write_padded(ctx, [=](auto out) { + return format_to(out, "({}, {})", nested(p.x), nested(p.y)); + }); + } + }; + + int main() { + fmt::print("[{:>20.2f}]", point{1, 2}); + } + ``` + + prints + + ``` + [ (1.00, 2.00)] + ``` + +- Added the generic representation (`g`) to `std::filesystem::path` + (https://github.com/fmtlib/fmt/issues/3715, + https://github.com/fmtlib/fmt/pull/3729). For example: + + ```c++ + #include + #include + + int main() { + fmt::print("{:g}\n", std::filesystem::path("C:\\foo")); + } + ``` + + prints `"C:/foo"` on Windows. + + Thanks @js324. + +- Made `format_as` work with references + (https://github.com/fmtlib/fmt/pull/3739). Thanks @tchaikov. + +- Fixed formatting of invalid UTF-8 with precision + (https://github.com/fmtlib/fmt/issues/3284). + +- Fixed an inconsistency between `fmt::to_string` and `fmt::format` + (https://github.com/fmtlib/fmt/issues/3684). + +- Disallowed unsafe uses of `fmt::styled` + (https://github.com/fmtlib/fmt/issues/3625): + + ```c++ + auto s = fmt::styled(std::string("dangle"), fmt::emphasis::bold); + fmt::print("{}\n", s); // compile error + ``` + + Pass `fmt::styled(...)` as a parameter instead. + +- Added a null check when formatting a C string with the `s` specifier + (https://github.com/fmtlib/fmt/issues/3706). + +- Disallowed the `c` specifier for `bool` + (https://github.com/fmtlib/fmt/issues/3726, + https://github.com/fmtlib/fmt/pull/3734). Thanks @js324. + +- Made the default formatting unlocalized in `fmt::ostream_formatter` for + consistency with the rest of the library + (https://github.com/fmtlib/fmt/issues/3460). + +- Fixed localized formatting in bases other than decimal + (https://github.com/fmtlib/fmt/issues/3693, + https://github.com/fmtlib/fmt/pull/3750). Thanks @js324. + +- Fixed a performance regression in experimental `fmt::ostream::print` + (https://github.com/fmtlib/fmt/issues/3674). + +- Added synchronization with the underlying output stream when writing to + the Windows console + (https://github.com/fmtlib/fmt/pull/3668, + https://github.com/fmtlib/fmt/issues/3688, + https://github.com/fmtlib/fmt/pull/3689). + Thanks @Roman-Koshelev and @dimztimz. + +- Changed to only export `format_error` when {fmt} is built as a shared + library (https://github.com/fmtlib/fmt/issues/3626, + https://github.com/fmtlib/fmt/pull/3627). Thanks @phprus. + +- Made `fmt::streamed` `constexpr`. + (https://github.com/fmtlib/fmt/pull/3650). Thanks @muggenhor. + +- Made `fmt::format_int` `constexpr` + (https://github.com/fmtlib/fmt/issues/4031, + https://github.com/fmtlib/fmt/pull/4032). Thanks @dixlorenz. + +- Enabled `consteval` on older versions of MSVC + (https://github.com/fmtlib/fmt/pull/3757). Thanks @phprus. + +- Added an option to build without `wchar_t` support on Windows + (https://github.com/fmtlib/fmt/issues/3631, + https://github.com/fmtlib/fmt/pull/3636). Thanks @glebm. + +- Improved build and CI configuration + (https://github.com/fmtlib/fmt/pull/3679, + https://github.com/fmtlib/fmt/issues/3701, + https://github.com/fmtlib/fmt/pull/3702, + https://github.com/fmtlib/fmt/pull/3749). + Thanks @jcar87, @pklima and @tchaikov. + +- Fixed various warnings, compilation and test issues + (https://github.com/fmtlib/fmt/issues/3607, + https://github.com/fmtlib/fmt/pull/3610, + https://github.com/fmtlib/fmt/pull/3624, + https://github.com/fmtlib/fmt/pull/3630, + https://github.com/fmtlib/fmt/pull/3634, + https://github.com/fmtlib/fmt/pull/3638, + https://github.com/fmtlib/fmt/issues/3645, + https://github.com/fmtlib/fmt/issues/3646, + https://github.com/fmtlib/fmt/pull/3647, + https://github.com/fmtlib/fmt/pull/3652, + https://github.com/fmtlib/fmt/issues/3654, + https://github.com/fmtlib/fmt/pull/3663, + https://github.com/fmtlib/fmt/issues/3670, + https://github.com/fmtlib/fmt/pull/3680, + https://github.com/fmtlib/fmt/issues/3694, + https://github.com/fmtlib/fmt/pull/3695, + https://github.com/fmtlib/fmt/pull/3699, + https://github.com/fmtlib/fmt/issues/3705, + https://github.com/fmtlib/fmt/issues/3710, + https://github.com/fmtlib/fmt/issues/3712, + https://github.com/fmtlib/fmt/pull/3713, + https://github.com/fmtlib/fmt/issues/3714, + https://github.com/fmtlib/fmt/pull/3716, + https://github.com/fmtlib/fmt/pull/3723, + https://github.com/fmtlib/fmt/issues/3738, + https://github.com/fmtlib/fmt/issues/3740, + https://github.com/fmtlib/fmt/pull/3741, + https://github.com/fmtlib/fmt/pull/3743, + https://github.com/fmtlib/fmt/issues/3745, + https://github.com/fmtlib/fmt/pull/3747, + https://github.com/fmtlib/fmt/pull/3748, + https://github.com/fmtlib/fmt/pull/3751, + https://github.com/fmtlib/fmt/pull/3754, + https://github.com/fmtlib/fmt/pull/3755, + https://github.com/fmtlib/fmt/issues/3760, + https://github.com/fmtlib/fmt/pull/3762, + https://github.com/fmtlib/fmt/issues/3763, + https://github.com/fmtlib/fmt/pull/3764, + https://github.com/fmtlib/fmt/issues/3774, + https://github.com/fmtlib/fmt/pull/3779). + Thanks @danakj, @vinayyadav3016, @cyyever, @phprus, @qimiko, @saschasc, + @gsjaardema, @lazka, @Zhaojun-Liu, @carlsmedstad, @hotwatermorning, + @cptFracassa, @kuguma, @PeterJohnson, @H1X4Dev, @asantoni, @eltociear, + @msimberg, @tchaikov, @waywardmonkeys. + +- Improved documentation and README + (https://github.com/fmtlib/fmt/issues/2086, + https://github.com/fmtlib/fmt/issues/3637, + https://github.com/fmtlib/fmt/pull/3642, + https://github.com/fmtlib/fmt/pull/3653, + https://github.com/fmtlib/fmt/pull/3655, + https://github.com/fmtlib/fmt/pull/3661, + https://github.com/fmtlib/fmt/issues/3673, + https://github.com/fmtlib/fmt/pull/3677, + https://github.com/fmtlib/fmt/pull/3737, + https://github.com/fmtlib/fmt/issues/3742, + https://github.com/fmtlib/fmt/pull/3744). + Thanks @idzm, @perlun, @joycebrum, @fennewald, @reinhardt1053, @GeorgeLS. + +- Updated CI dependencies + (https://github.com/fmtlib/fmt/pull/3615, + https://github.com/fmtlib/fmt/pull/3622, + https://github.com/fmtlib/fmt/pull/3623, + https://github.com/fmtlib/fmt/pull/3666, + https://github.com/fmtlib/fmt/pull/3696, + https://github.com/fmtlib/fmt/pull/3697, + https://github.com/fmtlib/fmt/pull/3759, + https://github.com/fmtlib/fmt/pull/3782). + +# 10.1.1 - 2023-08-28 + +- Added formatters for `std::atomic` and `atomic_flag` + (https://github.com/fmtlib/fmt/pull/3574, + https://github.com/fmtlib/fmt/pull/3594). + Thanks @wangzw and @AlexGuteniev. +- Fixed an error about partial specialization of `formatter` + after instantiation when compiled with gcc and C++20 + (https://github.com/fmtlib/fmt/issues/3584). +- Fixed compilation as a C++20 module with gcc and clang + (https://github.com/fmtlib/fmt/issues/3587, + https://github.com/fmtlib/fmt/pull/3597, + https://github.com/fmtlib/fmt/pull/3605). + Thanks @MathewBensonCode. +- Made `fmt::to_string` work with types that have `format_as` + overloads (https://github.com/fmtlib/fmt/pull/3575). Thanks @phprus. +- Made `formatted_size` work with integral format specifiers at + compile time (https://github.com/fmtlib/fmt/pull/3591). + Thanks @elbeno. +- Fixed a warning about the `no_unique_address` attribute on clang-cl + (https://github.com/fmtlib/fmt/pull/3599). Thanks @lukester1975. +- Improved compatibility with the legacy GBK encoding + (https://github.com/fmtlib/fmt/issues/3598, + https://github.com/fmtlib/fmt/pull/3599). Thanks @YuHuanTin. +- Added OpenSSF Scorecard analysis + (https://github.com/fmtlib/fmt/issues/3530, + https://github.com/fmtlib/fmt/pull/3571). Thanks @joycebrum. +- Updated CI dependencies + (https://github.com/fmtlib/fmt/pull/3591, + https://github.com/fmtlib/fmt/pull/3592, + https://github.com/fmtlib/fmt/pull/3593, + https://github.com/fmtlib/fmt/pull/3602). + +# 10.1.0 - 2023-08-12 + +- Optimized format string compilation resulting in up to 40% speed up + in compiled `format_to` and \~4x speed up in compiled `format_to_n` + on a concatenation benchmark + (https://github.com/fmtlib/fmt/issues/3133, + https://github.com/fmtlib/fmt/issues/3484). + + {fmt} 10.0: + + --------------------------------------------------------- + Benchmark Time CPU Iterations + --------------------------------------------------------- + BM_format_to 78.9 ns 78.9 ns 8881746 + BM_format_to_n 568 ns 568 ns 1232089 + + {fmt} 10.1: + + --------------------------------------------------------- + Benchmark Time CPU Iterations + --------------------------------------------------------- + BM_format_to 54.9 ns 54.9 ns 12727944 + BM_format_to_n 133 ns 133 ns 5257795 + +- Optimized storage of an empty allocator in `basic_memory_buffer` + (https://github.com/fmtlib/fmt/pull/3485). Thanks @Minty-Meeo. + +- Added formatters for proxy references to elements of + `std::vector` and `std::bitset` + (https://github.com/fmtlib/fmt/issues/3567, + https://github.com/fmtlib/fmt/pull/3570). For example + ([godbolt](https://godbolt.org/z/zYb79Pvn8)): + + ```c++ + #include + #include + + int main() { + auto v = std::vector{true}; + fmt::print("{}", v[0]); + } + ``` + + Thanks @phprus and @felix642. + +- Fixed an ambiguous formatter specialization for containers that look + like container adaptors such as `boost::flat_set` + (https://github.com/fmtlib/fmt/issues/3556, + https://github.com/fmtlib/fmt/pull/3561). Thanks @5chmidti. + +- Fixed compilation when formatting durations not convertible from + `std::chrono::seconds` + (https://github.com/fmtlib/fmt/pull/3430). Thanks @patlkli. + +- Made the `formatter` specialization for `char*` const-correct + (https://github.com/fmtlib/fmt/pull/3432). Thanks @timsong-cpp. + +- Made `{}` and `{:}` handled consistently during compile-time checks + (https://github.com/fmtlib/fmt/issues/3526). + +- Disallowed passing temporaries to `make_format_args` to improve API + safety by preventing dangling references. + +- Improved the compile-time error for unformattable types + (https://github.com/fmtlib/fmt/pull/3478). Thanks @BRevzin. + +- Improved the floating-point formatter + (https://github.com/fmtlib/fmt/pull/3448, + https://github.com/fmtlib/fmt/pull/3450). + Thanks @florimond-collette. + +- Fixed handling of precision for `long double` larger than 64 bits. + (https://github.com/fmtlib/fmt/issues/3539, + https://github.com/fmtlib/fmt/issues/3564). + +- Made floating-point and chrono tests less platform-dependent + (https://github.com/fmtlib/fmt/issues/3337, + https://github.com/fmtlib/fmt/issues/3433, + https://github.com/fmtlib/fmt/pull/3434). Thanks @phprus. + +- Removed the remnants of the Grisu floating-point formatter that has + been replaced by Dragonbox in earlier versions. + +- Added `throw_format_error` to the public API + (https://github.com/fmtlib/fmt/pull/3551). Thanks @mjerabek. + +- Made `FMT_THROW` assert even if assertions are disabled when + compiling with exceptions disabled + (https://github.com/fmtlib/fmt/issues/3418, + https://github.com/fmtlib/fmt/pull/3439). Thanks @BRevzin. + +- Made `format_as` and `std::filesystem::path` formatter work with + exotic code unit types. + (https://github.com/fmtlib/fmt/pull/3457, + https://github.com/fmtlib/fmt/pull/3476). Thanks @gix and @hmbj. + +- Added support for the `?` format specifier to + `std::filesystem::path` and made the default unescaped for + consistency with strings. + +- Deprecated the wide stream overload of `printf`. + +- Removed unused `basic_printf_parse_context`. + +- Improved RTTI detection used when formatting exceptions + (https://github.com/fmtlib/fmt/pull/3468). Thanks @danakj. + +- Improved compatibility with VxWorks7 + (https://github.com/fmtlib/fmt/pull/3467). Thanks @wenshan1. + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/3174, + https://github.com/fmtlib/fmt/issues/3423, + https://github.com/fmtlib/fmt/pull/3454, + https://github.com/fmtlib/fmt/issues/3458, + https://github.com/fmtlib/fmt/pull/3461, + https://github.com/fmtlib/fmt/issues/3487, + https://github.com/fmtlib/fmt/pull/3515). + Thanks @zencatalyst, @rlalik and @mikecrowe. + +- Improved build and CI configurations + (https://github.com/fmtlib/fmt/issues/3449, + https://github.com/fmtlib/fmt/pull/3451, + https://github.com/fmtlib/fmt/pull/3452, + https://github.com/fmtlib/fmt/pull/3453, + https://github.com/fmtlib/fmt/pull/3459, + https://github.com/fmtlib/fmt/issues/3481, + https://github.com/fmtlib/fmt/pull/3486, + https://github.com/fmtlib/fmt/issues/3489, + https://github.com/fmtlib/fmt/pull/3496, + https://github.com/fmtlib/fmt/issues/3517, + https://github.com/fmtlib/fmt/pull/3523, + https://github.com/fmtlib/fmt/pull/3563). + Thanks @joycebrum, @glebm, @phprus, @petrmanek, @setoye and @abouvier. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/3408, + https://github.com/fmtlib/fmt/issues/3424, + https://github.com/fmtlib/fmt/issues/3444, + https://github.com/fmtlib/fmt/pull/3446, + https://github.com/fmtlib/fmt/pull/3475, + https://github.com/fmtlib/fmt/pull/3482, + https://github.com/fmtlib/fmt/issues/3492, + https://github.com/fmtlib/fmt/pull/3493, + https://github.com/fmtlib/fmt/pull/3508, + https://github.com/fmtlib/fmt/issues/3509, + https://github.com/fmtlib/fmt/issues/3533, + https://github.com/fmtlib/fmt/pull/3542, + https://github.com/fmtlib/fmt/issues/3543, + https://github.com/fmtlib/fmt/issues/3540, + https://github.com/fmtlib/fmt/pull/3544, + https://github.com/fmtlib/fmt/issues/3548, + https://github.com/fmtlib/fmt/pull/3549, + https://github.com/fmtlib/fmt/pull/3550, + https://github.com/fmtlib/fmt/pull/3552). + Thanks @adesitter, @hmbj, @Minty-Meeo, @phprus, @TobiSchluter, + @kieranclancy, @alexeedm, @jurihock, @Ozomahtli and @razaqq. + +# 10.0.0 - 2023-05-09 + +- Replaced Grisu with a new floating-point formatting algorithm for + given precision (https://github.com/fmtlib/fmt/issues/3262, + https://github.com/fmtlib/fmt/issues/2750, + https://github.com/fmtlib/fmt/pull/3269, + https://github.com/fmtlib/fmt/pull/3276). The new algorithm + is based on Dragonbox already used for the shortest representation + and gives substantial performance improvement: + + ![](https://user-images.githubusercontent.com/33922675/211956670-84891a09-6867-47d9-82fc-3230da7abe0f.png) + + - Red: new algorithm + - Green: new algorithm with `FMT_USE_FULL_CACHE_DRAGONBOX` defined + to 1 + - Blue: old algorithm + + Thanks @jk-jeon. + +- Replaced `snprintf`-based hex float formatter with an internal + implementation (https://github.com/fmtlib/fmt/pull/3179, + https://github.com/fmtlib/fmt/pull/3203). This removes the + last usage of `s(n)printf` in {fmt}. Thanks @phprus. + +- Fixed alignment of floating-point numbers with localization + (https://github.com/fmtlib/fmt/issues/3263, + https://github.com/fmtlib/fmt/pull/3272). Thanks @ShawnZhong. + +- Made handling of `#` consistent with `std::format`. + +- Improved C++20 module support + (https://github.com/fmtlib/fmt/pull/3134, + https://github.com/fmtlib/fmt/pull/3254, + https://github.com/fmtlib/fmt/pull/3386, + https://github.com/fmtlib/fmt/pull/3387, + https://github.com/fmtlib/fmt/pull/3388, + https://github.com/fmtlib/fmt/pull/3392, + https://github.com/fmtlib/fmt/pull/3397, + https://github.com/fmtlib/fmt/pull/3399, + https://github.com/fmtlib/fmt/pull/3400). + Thanks @laitingsheng, @Orvid and @DanielaE. + +- Switched to the [modules CMake library](https://github.com/vitaut/modules) + which allows building {fmt} as a C++20 module with clang: + + CXX=clang++ cmake -DFMT_MODULE=ON . + make + +- Made `format_as` work with any user-defined type and not just enums. + For example ([godbolt](https://godbolt.org/z/b7rqhq5Kh)): + + ```c++ + #include + + struct floaty_mc_floatface { + double value; + }; + + auto format_as(floaty_mc_floatface f) { return f.value; } + + int main() { + fmt::print("{:8}\n", floaty_mc_floatface{0.42}); // prints " 0.42" + } + ``` + +- Removed deprecated implicit conversions for enums and conversions to + primitive types for compatibility with `std::format` and to prevent + potential ODR violations. Use `format_as` instead. + +- Added support for fill, align and width to the time point formatter + (https://github.com/fmtlib/fmt/issues/3237, + https://github.com/fmtlib/fmt/pull/3260, + https://github.com/fmtlib/fmt/pull/3275). For example + ([godbolt](https://godbolt.org/z/rKP6MGz6c)): + + ```c++ + #include + + int main() { + // prints " 2023" + fmt::print("{:>8%Y}\n", std::chrono::system_clock::now()); + } + ``` + + Thanks @ShawnZhong. + +- Implemented formatting of subseconds + (https://github.com/fmtlib/fmt/issues/2207, + https://github.com/fmtlib/fmt/issues/3117, + https://github.com/fmtlib/fmt/pull/3115, + https://github.com/fmtlib/fmt/pull/3143, + https://github.com/fmtlib/fmt/pull/3144, + https://github.com/fmtlib/fmt/pull/3349). For example + ([godbolt](https://godbolt.org/z/45738oGEo)): + + ```c++ + #include + + int main() { + // prints 01.234567 + fmt::print("{:%S}\n", std::chrono::microseconds(1234567)); + } + ``` + + Thanks @patrickroocks @phprus and @BRevzin. + +- Added precision support to `%S` + (https://github.com/fmtlib/fmt/pull/3148). Thanks @SappyJoy + +- Added support for `std::utc_time` + (https://github.com/fmtlib/fmt/issues/3098, + https://github.com/fmtlib/fmt/pull/3110). Thanks @patrickroocks. + +- Switched formatting of `std::chrono::system_clock` from local time + to UTC for compatibility with the standard + (https://github.com/fmtlib/fmt/issues/3199, + https://github.com/fmtlib/fmt/pull/3230). Thanks @ned14. + +- Added support for `%Ez` and `%Oz` to chrono formatters. + (https://github.com/fmtlib/fmt/issues/3220, + https://github.com/fmtlib/fmt/pull/3222). Thanks @phprus. + +- Improved validation of format specifiers for `std::chrono::duration` + (https://github.com/fmtlib/fmt/issues/3219, + https://github.com/fmtlib/fmt/pull/3232). Thanks @ShawnZhong. + +- Fixed formatting of time points before the epoch + (https://github.com/fmtlib/fmt/issues/3117, + https://github.com/fmtlib/fmt/pull/3261). For example + ([godbolt](https://godbolt.org/z/f7bcznb3W)): + + ```c++ + #include + + int main() { + auto t = std::chrono::system_clock::from_time_t(0) - + std::chrono::milliseconds(250); + fmt::print("{:%S}\n", t); // prints 59.750000000 + } + ``` + + Thanks @ShawnZhong. + +- Experimental: implemented glibc extension for padding seconds, + minutes and hours + (https://github.com/fmtlib/fmt/issues/2959, + https://github.com/fmtlib/fmt/pull/3271). Thanks @ShawnZhong. + +- Added a formatter for `std::exception` + (https://github.com/fmtlib/fmt/issues/2977, + https://github.com/fmtlib/fmt/issues/3012, + https://github.com/fmtlib/fmt/pull/3062, + https://github.com/fmtlib/fmt/pull/3076, + https://github.com/fmtlib/fmt/pull/3119). For example + ([godbolt](https://godbolt.org/z/8xoWGs9e4)): + + ```c++ + #include + #include + + int main() { + try { + std::vector().at(0); + } catch(const std::exception& e) { + fmt::print("{}", e); + } + } + ``` + + prints: + + vector::_M_range_check: __n (which is 0) >= this->size() (which is 0) + + on libstdc++. Thanks @zach2good and @phprus. + +- Moved `std::error_code` formatter from `fmt/os.h` to `fmt/std.h`. + (https://github.com/fmtlib/fmt/pull/3125). Thanks @phprus. + +- Added formatters for standard container adapters: + `std::priority_queue`, `std::queue` and `std::stack` + (https://github.com/fmtlib/fmt/issues/3215, + https://github.com/fmtlib/fmt/pull/3279). For example + ([godbolt](https://godbolt.org/z/74h1xY9qK)): + + ```c++ + #include + #include + #include + + int main() { + auto s = std::stack>(); + for (auto b: {true, false, true}) s.push(b); + fmt::print("{}\n", s); // prints [true, false, true] + } + ``` + + Thanks @ShawnZhong. + +- Added a formatter for `std::optional` to `fmt/std.h` + (https://github.com/fmtlib/fmt/issues/1367, + https://github.com/fmtlib/fmt/pull/3303). + Thanks @tom-huntington. + +- Fixed formatting of valueless by exception variants + (https://github.com/fmtlib/fmt/pull/3347). Thanks @TheOmegaCarrot. + +- Made `fmt::ptr` accept `unique_ptr` with a custom deleter + (https://github.com/fmtlib/fmt/pull/3177). Thanks @hmbj. + +- Fixed formatting of noncopyable ranges and nested ranges of chars + (https://github.com/fmtlib/fmt/pull/3158 + https://github.com/fmtlib/fmt/issues/3286, + https://github.com/fmtlib/fmt/pull/3290). Thanks @BRevzin. + +- Fixed issues with formatting of paths and ranges of paths + (https://github.com/fmtlib/fmt/issues/3319, + https://github.com/fmtlib/fmt/pull/3321 + https://github.com/fmtlib/fmt/issues/3322). Thanks @phprus. + +- Improved handling of invalid Unicode in paths. + +- Enabled compile-time checks on Apple clang 14 and later + (https://github.com/fmtlib/fmt/pull/3331). Thanks @cloyce. + +- Improved compile-time checks of named arguments + (https://github.com/fmtlib/fmt/issues/3105, + https://github.com/fmtlib/fmt/pull/3214). Thanks @rbrich. + +- Fixed formatting when both alignment and `0` are given + (https://github.com/fmtlib/fmt/issues/3236, + https://github.com/fmtlib/fmt/pull/3248). Thanks @ShawnZhong. + +- Improved Unicode support in the experimental file API on Windows + (https://github.com/fmtlib/fmt/issues/3234, + https://github.com/fmtlib/fmt/pull/3293). Thanks @Fros1er. + +- Unified UTF transcoding + (https://github.com/fmtlib/fmt/pull/3416). Thanks @phprus. + +- Added support for UTF-8 digit separators via an experimental locale + facet (https://github.com/fmtlib/fmt/issues/1861). For + example ([godbolt](https://godbolt.org/z/f7bcznb3W)): + + ```c++ + auto loc = std::locale( + std::locale(), new fmt::format_facet("’")); + auto s = fmt::format(loc, "{:L}", 1000); + ``` + + where `’` is U+2019 used as a digit separator in the de_CH locale. + +- Added an overload of `formatted_size` that takes a locale + (https://github.com/fmtlib/fmt/issues/3084, + https://github.com/fmtlib/fmt/pull/3087). Thanks @gerboengels. + +- Removed the deprecated `FMT_DEPRECATED_OSTREAM`. + +- Fixed a UB when using a null `std::string_view` with + `fmt::to_string` or format string compilation + (https://github.com/fmtlib/fmt/issues/3241, + https://github.com/fmtlib/fmt/pull/3244). Thanks @phprus. + +- Added `starts_with` to the fallback `string_view` implementation + (https://github.com/fmtlib/fmt/pull/3080). Thanks @phprus. + +- Added `fmt::basic_format_string::get()` for compatibility with + `basic_format_string` + (https://github.com/fmtlib/fmt/pull/3111). Thanks @huangqinjin. + +- Added `println` for compatibility with C++23 + (https://github.com/fmtlib/fmt/pull/3267). Thanks @ShawnZhong. + +- Renamed the `FMT_EXPORT` macro for shared library usage to + `FMT_LIB_EXPORT`. + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/3108, + https://github.com/fmtlib/fmt/issues/3169, + https://github.com/fmtlib/fmt/pull/3243). + https://github.com/fmtlib/fmt/pull/3404, + https://github.com/fmtlib/fmt/pull/4002). + Thanks @Cleroth, @Vertexwahn and @yujincheng08. + +- Improved build configuration and tests + (https://github.com/fmtlib/fmt/pull/3118, + https://github.com/fmtlib/fmt/pull/3120, + https://github.com/fmtlib/fmt/pull/3188, + https://github.com/fmtlib/fmt/issues/3189, + https://github.com/fmtlib/fmt/pull/3198, + https://github.com/fmtlib/fmt/pull/3205, + https://github.com/fmtlib/fmt/pull/3207, + https://github.com/fmtlib/fmt/pull/3210, + https://github.com/fmtlib/fmt/pull/3240, + https://github.com/fmtlib/fmt/pull/3256, + https://github.com/fmtlib/fmt/pull/3264, + https://github.com/fmtlib/fmt/issues/3299, + https://github.com/fmtlib/fmt/pull/3302, + https://github.com/fmtlib/fmt/pull/3312, + https://github.com/fmtlib/fmt/issues/3317, + https://github.com/fmtlib/fmt/pull/3328, + https://github.com/fmtlib/fmt/pull/3333, + https://github.com/fmtlib/fmt/pull/3369, + https://github.com/fmtlib/fmt/issues/3373, + https://github.com/fmtlib/fmt/pull/3395, + https://github.com/fmtlib/fmt/pull/3406, + https://github.com/fmtlib/fmt/pull/3411). + Thanks @dimztimz, @phprus, @DavidKorczynski, @ChrisThrasher, + @FrancoisCarouge, @kennyweiss, @luzpaz, @codeinred, @Mixaill, @joycebrum, + @kevinhwang and @Vertexwahn. + +- Fixed a regression in handling empty format specifiers after a colon + (`{:}`) (https://github.com/fmtlib/fmt/pull/3086). Thanks @oxidase. + +- Worked around a broken implementation of + `std::is_constant_evaluated` in some versions of libstdc++ on clang + (https://github.com/fmtlib/fmt/issues/3247, + https://github.com/fmtlib/fmt/pull/3281). Thanks @phprus. + +- Fixed formatting of volatile variables + (https://github.com/fmtlib/fmt/pull/3068). + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/pull/3057, + https://github.com/fmtlib/fmt/pull/3066, + https://github.com/fmtlib/fmt/pull/3072, + https://github.com/fmtlib/fmt/pull/3082, + https://github.com/fmtlib/fmt/pull/3091, + https://github.com/fmtlib/fmt/issues/3092, + https://github.com/fmtlib/fmt/pull/3093, + https://github.com/fmtlib/fmt/pull/3095, + https://github.com/fmtlib/fmt/issues/3096, + https://github.com/fmtlib/fmt/pull/3097, + https://github.com/fmtlib/fmt/issues/3128, + https://github.com/fmtlib/fmt/pull/3129, + https://github.com/fmtlib/fmt/pull/3137, + https://github.com/fmtlib/fmt/pull/3139, + https://github.com/fmtlib/fmt/issues/3140, + https://github.com/fmtlib/fmt/pull/3142, + https://github.com/fmtlib/fmt/issues/3149, + https://github.com/fmtlib/fmt/pull/3150, + https://github.com/fmtlib/fmt/issues/3154, + https://github.com/fmtlib/fmt/issues/3163, + https://github.com/fmtlib/fmt/issues/3178, + https://github.com/fmtlib/fmt/pull/3184, + https://github.com/fmtlib/fmt/pull/3196, + https://github.com/fmtlib/fmt/issues/3204, + https://github.com/fmtlib/fmt/pull/3206, + https://github.com/fmtlib/fmt/pull/3208, + https://github.com/fmtlib/fmt/issues/3213, + https://github.com/fmtlib/fmt/pull/3216, + https://github.com/fmtlib/fmt/issues/3224, + https://github.com/fmtlib/fmt/issues/3226, + https://github.com/fmtlib/fmt/issues/3228, + https://github.com/fmtlib/fmt/pull/3229, + https://github.com/fmtlib/fmt/pull/3259, + https://github.com/fmtlib/fmt/issues/3274, + https://github.com/fmtlib/fmt/issues/3287, + https://github.com/fmtlib/fmt/pull/3288, + https://github.com/fmtlib/fmt/issues/3292, + https://github.com/fmtlib/fmt/pull/3295, + https://github.com/fmtlib/fmt/pull/3296, + https://github.com/fmtlib/fmt/issues/3298, + https://github.com/fmtlib/fmt/issues/3325, + https://github.com/fmtlib/fmt/pull/3326, + https://github.com/fmtlib/fmt/issues/3334, + https://github.com/fmtlib/fmt/issues/3342, + https://github.com/fmtlib/fmt/pull/3343, + https://github.com/fmtlib/fmt/issues/3351, + https://github.com/fmtlib/fmt/pull/3352, + https://github.com/fmtlib/fmt/pull/3362, + https://github.com/fmtlib/fmt/issues/3365, + https://github.com/fmtlib/fmt/pull/3366, + https://github.com/fmtlib/fmt/pull/3374, + https://github.com/fmtlib/fmt/issues/3377, + https://github.com/fmtlib/fmt/pull/3378, + https://github.com/fmtlib/fmt/issues/3381, + https://github.com/fmtlib/fmt/pull/3398, + https://github.com/fmtlib/fmt/pull/3413, + https://github.com/fmtlib/fmt/issues/3415). + Thanks @phprus, @gsjaardema, @NewbieOrange, @EngineLessCC, @asmaloney, + @HazardyKnusperkeks, @sergiud, @Youw, @thesmurph, @czudziakm, + @Roman-Koshelev, @chronoxor, @ShawnZhong, @russelltg, @glebm, @tmartin-gh, + @Zhaojun-Liu, @louiswins and @mogemimi. + +# 9.1.0 - 2022-08-27 + +- `fmt::formatted_size` now works at compile time + (https://github.com/fmtlib/fmt/pull/3026). For example + ([godbolt](https://godbolt.org/z/1MW5rMdf8)): + + ```c++ + #include + + int main() { + using namespace fmt::literals; + constexpr size_t n = fmt::formatted_size("{}"_cf, 42); + fmt::print("{}\n", n); // prints 2 + } + ``` + + Thanks @marksantaniello. + +- Fixed handling of invalid UTF-8 + (https://github.com/fmtlib/fmt/pull/3038, + https://github.com/fmtlib/fmt/pull/3044, + https://github.com/fmtlib/fmt/pull/3056). + Thanks @phprus and @skeeto. + +- Improved Unicode support in `ostream` overloads of `print` + (https://github.com/fmtlib/fmt/pull/2994, + https://github.com/fmtlib/fmt/pull/3001, + https://github.com/fmtlib/fmt/pull/3025). Thanks @dimztimz. + +- Fixed handling of the sign specifier in localized formatting on + systems with 32-bit `wchar_t` + (https://github.com/fmtlib/fmt/issues/3041). + +- Added support for wide streams to `fmt::streamed` + (https://github.com/fmtlib/fmt/pull/2994). Thanks @phprus. + +- Added the `n` specifier that disables the output of delimiters when + formatting ranges (https://github.com/fmtlib/fmt/pull/2981, + https://github.com/fmtlib/fmt/pull/2983). For example + ([godbolt](https://godbolt.org/z/roKqGdj8c)): + + ```c++ + #include + #include + + int main() { + auto v = std::vector{1, 2, 3}; + fmt::print("{:n}\n", v); // prints 1, 2, 3 + } + ``` + + Thanks @BRevzin. + +- Worked around problematic `std::string_view` constructors introduced + in C++23 (https://github.com/fmtlib/fmt/issues/3030, + https://github.com/fmtlib/fmt/issues/3050). Thanks @strega-nil-ms. + +- Improve handling (exclusion) of recursive ranges + (https://github.com/fmtlib/fmt/issues/2968, + https://github.com/fmtlib/fmt/pull/2974). Thanks @Dani-Hub. + +- Improved error reporting in format string compilation + (https://github.com/fmtlib/fmt/issues/3055). + +- Improved the implementation of + [Dragonbox](https://github.com/jk-jeon/dragonbox), the algorithm + used for the default floating-point formatting + (https://github.com/fmtlib/fmt/pull/2984). Thanks @jk-jeon. + +- Fixed issues with floating-point formatting on exotic platforms. + +- Improved the implementation of chrono formatting + (https://github.com/fmtlib/fmt/pull/3010). Thanks @phprus. + +- Improved documentation + (https://github.com/fmtlib/fmt/pull/2966, + https://github.com/fmtlib/fmt/pull/3009, + https://github.com/fmtlib/fmt/issues/3020, + https://github.com/fmtlib/fmt/pull/3037). + Thanks @mwinterb, @jcelerier and @remiburtin. + +- Improved build configuration + (https://github.com/fmtlib/fmt/pull/2991, + https://github.com/fmtlib/fmt/pull/2995, + https://github.com/fmtlib/fmt/issues/3004, + https://github.com/fmtlib/fmt/pull/3007, + https://github.com/fmtlib/fmt/pull/3040). + Thanks @dimztimz and @hwhsu1231. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/2969, + https://github.com/fmtlib/fmt/pull/2971, + https://github.com/fmtlib/fmt/issues/2975, + https://github.com/fmtlib/fmt/pull/2982, + https://github.com/fmtlib/fmt/pull/2985, + https://github.com/fmtlib/fmt/issues/2988, + https://github.com/fmtlib/fmt/issues/2989, + https://github.com/fmtlib/fmt/issues/3000, + https://github.com/fmtlib/fmt/issues/3006, + https://github.com/fmtlib/fmt/issues/3014, + https://github.com/fmtlib/fmt/issues/3015, + https://github.com/fmtlib/fmt/pull/3021, + https://github.com/fmtlib/fmt/issues/3023, + https://github.com/fmtlib/fmt/pull/3024, + https://github.com/fmtlib/fmt/pull/3029, + https://github.com/fmtlib/fmt/pull/3043, + https://github.com/fmtlib/fmt/issues/3052, + https://github.com/fmtlib/fmt/pull/3053, + https://github.com/fmtlib/fmt/pull/3054). + Thanks @h-friederich, @dimztimz, @olupton, @bernhardmgruber and @phprus. + +# 9.0.0 - 2022-07-04 + +- Switched to the internal floating point formatter for all decimal + presentation formats. In particular this results in consistent + rounding on all platforms and removing the `s[n]printf` fallback for + decimal FP formatting. + +- Compile-time floating point formatting no longer requires the + header-only mode. For example + ([godbolt](https://godbolt.org/z/G37PTeG3b)): + + ```c++ + #include + #include + + consteval auto compile_time_dtoa(double value) -> std::array { + auto result = std::array(); + fmt::format_to(result.data(), FMT_COMPILE("{}"), value); + return result; + } + + constexpr auto answer = compile_time_dtoa(0.42); + ``` + + works with the default settings. + +- Improved the implementation of + [Dragonbox](https://github.com/jk-jeon/dragonbox), the algorithm + used for the default floating-point formatting + (https://github.com/fmtlib/fmt/pull/2713, + https://github.com/fmtlib/fmt/pull/2750). Thanks @jk-jeon. + +- Made `fmt::to_string` work with `__float128`. This uses the internal + FP formatter and works even on system without `__float128` support + in `[s]printf`. + +- Disabled automatic `std::ostream` insertion operator (`operator<<`) + discovery when `fmt/ostream.h` is included to prevent ODR + violations. You can get the old behavior by defining + `FMT_DEPRECATED_OSTREAM` but this will be removed in the next major + release. Use `fmt::streamed` or `fmt::ostream_formatter` to enable + formatting via `std::ostream` instead. + +- Added `fmt::ostream_formatter` that can be used to write `formatter` + specializations that perform formatting via `std::ostream`. For + example ([godbolt](https://godbolt.org/z/5sEc5qMsf)): + + ```c++ + #include + + struct date { + int year, month, day; + + friend std::ostream& operator<<(std::ostream& os, const date& d) { + return os << d.year << '-' << d.month << '-' << d.day; + } + }; + + template <> struct fmt::formatter : ostream_formatter {}; + + std::string s = fmt::format("The date is {}", date{2012, 12, 9}); + // s == "The date is 2012-12-9" + ``` + +- Added the `fmt::streamed` function that takes an object and formats + it via `std::ostream`. For example + ([godbolt](https://godbolt.org/z/5G3346G1f)): + + ```c++ + #include + #include + + int main() { + fmt::print("Current thread id: {}\n", + fmt::streamed(std::this_thread::get_id())); + } + ``` + + Note that `fmt/std.h` provides a `formatter` specialization for + `std::thread::id` so you don\'t need to format it via + `std::ostream`. + +- Deprecated implicit conversions of unscoped enums to integers for + consistency with scoped enums. + +- Added an argument-dependent lookup based `format_as` extension API + to simplify formatting of enums. + +- Added experimental `std::variant` formatting support + (https://github.com/fmtlib/fmt/pull/2941). For example + ([godbolt](https://godbolt.org/z/KG9z6cq68)): + + ```c++ + #include + #include + + int main() { + auto v = std::variant(42); + fmt::print("{}\n", v); + } + ``` + + prints: + + variant(42) + + Thanks @jehelset. + +- Added experimental `std::filesystem::path` formatting support + (https://github.com/fmtlib/fmt/issues/2865, + https://github.com/fmtlib/fmt/pull/2902, + https://github.com/fmtlib/fmt/issues/2917, + https://github.com/fmtlib/fmt/pull/2918). For example + ([godbolt](https://godbolt.org/z/o44dMexEb)): + + ```c++ + #include + #include + + int main() { + fmt::print("There is no place like {}.", std::filesystem::path("/home")); + } + ``` + + prints: + + There is no place like "/home". + + Thanks @phprus. + +- Added a `std::thread::id` formatter to `fmt/std.h`. For example + ([godbolt](https://godbolt.org/z/j1azbYf3E)): + + ```c++ + #include + #include + + int main() { + fmt::print("Current thread id: {}\n", std::this_thread::get_id()); + } + ``` + +- Added `fmt::styled` that applies a text style to an individual + argument (https://github.com/fmtlib/fmt/pull/2793). For + example ([godbolt](https://godbolt.org/z/vWGW7v5M6)): + + ```c++ + #include + #include + + int main() { + auto now = std::chrono::system_clock::now(); + fmt::print( + "[{}] {}: {}\n", + fmt::styled(now, fmt::emphasis::bold), + fmt::styled("error", fg(fmt::color::red)), + "something went wrong"); + } + ``` + + prints + + ![](https://user-images.githubusercontent.com/576385/175071215-12809244-dab0-4005-96d8-7cd911c964d5.png) + + Thanks @rbrugo. + +- Made `fmt::print` overload for text styles correctly handle UTF-8 + (https://github.com/fmtlib/fmt/issues/2681, + https://github.com/fmtlib/fmt/pull/2701). Thanks @AlexGuteniev. + +- Fixed Unicode handling when writing to an ostream. + +- Added support for nested specifiers to range formatting + (https://github.com/fmtlib/fmt/pull/2673). For example + ([godbolt](https://godbolt.org/z/xd3Gj38cf)): + + ```c++ + #include + #include + + int main() { + fmt::print("{::#x}\n", std::vector{10, 20, 30}); + } + ``` + + prints `[0xa, 0x14, 0x1e]`. + + Thanks @BRevzin. + +- Implemented escaping of wide strings in ranges + (https://github.com/fmtlib/fmt/pull/2904). Thanks @phprus. + +- Added support for ranges with `begin` / `end` found via the + argument-dependent lookup + (https://github.com/fmtlib/fmt/pull/2807). Thanks @rbrugo. + +- Fixed formatting of certain kinds of ranges of ranges + (https://github.com/fmtlib/fmt/pull/2787). Thanks @BRevzin. + +- Fixed handling of maps with element types other than `std::pair` + (https://github.com/fmtlib/fmt/pull/2944). Thanks @BrukerJWD. + +- Made tuple formatter enabled only if elements are formattable + (https://github.com/fmtlib/fmt/issues/2939, + https://github.com/fmtlib/fmt/pull/2940). Thanks @jehelset. + +- Made `fmt::join` compatible with format string compilation + (https://github.com/fmtlib/fmt/issues/2719, + https://github.com/fmtlib/fmt/pull/2720). Thanks @phprus. + +- Made compile-time checks work with named arguments of custom types + and `std::ostream` `print` overloads + (https://github.com/fmtlib/fmt/issues/2816, + https://github.com/fmtlib/fmt/issues/2817, + https://github.com/fmtlib/fmt/pull/2819). Thanks @timsong-cpp. + +- Removed `make_args_checked` because it is no longer needed for + compile-time checks + (https://github.com/fmtlib/fmt/pull/2760). Thanks @phprus. + +- Removed the following deprecated APIs: `_format`, `arg_join`, the + `format_to` overload that takes a memory buffer, `[v]fprintf` that + takes an `ostream`. + +- Removed the deprecated implicit conversion of `[const] signed char*` + and `[const] unsigned char*` to C strings. + +- Removed the deprecated `fmt/locale.h`. + +- Replaced the deprecated `fileno()` with `descriptor()` in + `buffered_file`. + +- Moved `to_string_view` to the `detail` namespace since it\'s an + implementation detail. + +- Made access mode of a created file consistent with `fopen` by + setting `S_IWGRP` and `S_IWOTH` + (https://github.com/fmtlib/fmt/pull/2733). Thanks @arogge. + +- Removed a redundant buffer resize when formatting to `std::ostream` + (https://github.com/fmtlib/fmt/issues/2842, + https://github.com/fmtlib/fmt/pull/2843). Thanks @jcelerier. + +- Made precision computation for strings consistent with width + (https://github.com/fmtlib/fmt/issues/2888). + +- Fixed handling of locale separators in floating point formatting + (https://github.com/fmtlib/fmt/issues/2830). + +- Made sign specifiers work with `__int128_t` + (https://github.com/fmtlib/fmt/issues/2773). + +- Improved support for systems such as CHERI with extra data stored in + pointers (https://github.com/fmtlib/fmt/pull/2932). + Thanks @davidchisnall. + +- Improved documentation + (https://github.com/fmtlib/fmt/pull/2706, + https://github.com/fmtlib/fmt/pull/2712, + https://github.com/fmtlib/fmt/pull/2789, + https://github.com/fmtlib/fmt/pull/2803, + https://github.com/fmtlib/fmt/pull/2805, + https://github.com/fmtlib/fmt/pull/2815, + https://github.com/fmtlib/fmt/pull/2924). + Thanks @BRevzin, @Pokechu22, @setoye, @rtobar, @rbrugo, @anoonD and + @leha-bot. + +- Improved build configuration + (https://github.com/fmtlib/fmt/pull/2766, + https://github.com/fmtlib/fmt/pull/2772, + https://github.com/fmtlib/fmt/pull/2836, + https://github.com/fmtlib/fmt/pull/2852, + https://github.com/fmtlib/fmt/pull/2907, + https://github.com/fmtlib/fmt/pull/2913, + https://github.com/fmtlib/fmt/pull/2914). + Thanks @kambala-decapitator, @mattiasljungstrom, @kieselnb, @nathannaveen + and @Vertexwahn. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/2408, + https://github.com/fmtlib/fmt/issues/2507, + https://github.com/fmtlib/fmt/issues/2697, + https://github.com/fmtlib/fmt/issues/2715, + https://github.com/fmtlib/fmt/issues/2717, + https://github.com/fmtlib/fmt/pull/2722, + https://github.com/fmtlib/fmt/pull/2724, + https://github.com/fmtlib/fmt/pull/2725, + https://github.com/fmtlib/fmt/issues/2726, + https://github.com/fmtlib/fmt/pull/2728, + https://github.com/fmtlib/fmt/pull/2732, + https://github.com/fmtlib/fmt/issues/2738, + https://github.com/fmtlib/fmt/pull/2742, + https://github.com/fmtlib/fmt/issues/2744, + https://github.com/fmtlib/fmt/issues/2745, + https://github.com/fmtlib/fmt/issues/2746, + https://github.com/fmtlib/fmt/issues/2754, + https://github.com/fmtlib/fmt/pull/2755, + https://github.com/fmtlib/fmt/issues/2757, + https://github.com/fmtlib/fmt/pull/2758, + https://github.com/fmtlib/fmt/issues/2761, + https://github.com/fmtlib/fmt/pull/2762, + https://github.com/fmtlib/fmt/issues/2763, + https://github.com/fmtlib/fmt/pull/2765, + https://github.com/fmtlib/fmt/issues/2769, + https://github.com/fmtlib/fmt/pull/2770, + https://github.com/fmtlib/fmt/issues/2771, + https://github.com/fmtlib/fmt/issues/2777, + https://github.com/fmtlib/fmt/pull/2779, + https://github.com/fmtlib/fmt/pull/2782, + https://github.com/fmtlib/fmt/pull/2783, + https://github.com/fmtlib/fmt/issues/2794, + https://github.com/fmtlib/fmt/issues/2796, + https://github.com/fmtlib/fmt/pull/2797, + https://github.com/fmtlib/fmt/pull/2801, + https://github.com/fmtlib/fmt/pull/2802, + https://github.com/fmtlib/fmt/issues/2808, + https://github.com/fmtlib/fmt/issues/2818, + https://github.com/fmtlib/fmt/pull/2819, + https://github.com/fmtlib/fmt/issues/2829, + https://github.com/fmtlib/fmt/issues/2835, + https://github.com/fmtlib/fmt/issues/2848, + https://github.com/fmtlib/fmt/issues/2860, + https://github.com/fmtlib/fmt/pull/2861, + https://github.com/fmtlib/fmt/pull/2882, + https://github.com/fmtlib/fmt/issues/2886, + https://github.com/fmtlib/fmt/issues/2891, + https://github.com/fmtlib/fmt/pull/2892, + https://github.com/fmtlib/fmt/issues/2895, + https://github.com/fmtlib/fmt/issues/2896, + https://github.com/fmtlib/fmt/pull/2903, + https://github.com/fmtlib/fmt/issues/2906, + https://github.com/fmtlib/fmt/issues/2908, + https://github.com/fmtlib/fmt/pull/2909, + https://github.com/fmtlib/fmt/issues/2920, + https://github.com/fmtlib/fmt/pull/2922, + https://github.com/fmtlib/fmt/pull/2927, + https://github.com/fmtlib/fmt/pull/2929, + https://github.com/fmtlib/fmt/issues/2936, + https://github.com/fmtlib/fmt/pull/2937, + https://github.com/fmtlib/fmt/pull/2938, + https://github.com/fmtlib/fmt/pull/2951, + https://github.com/fmtlib/fmt/issues/2954, + https://github.com/fmtlib/fmt/pull/2957, + https://github.com/fmtlib/fmt/issues/2958, + https://github.com/fmtlib/fmt/pull/2960). + Thanks @matrackif @Tobi823, @ivan-volnov, @VasiliPupkin256, + @federico-busato, @barcharcraz, @jk-jeon, @HazardyKnusperkeks, @dalboris, + @seanm, @gsjaardema, @timsong-cpp, @seanm, @frithrah, @chronoxor, @Agga, + @madmaxoft, @JurajX, @phprus and @Dani-Hub. + +# 8.1.1 - 2022-01-06 + +- Restored ABI compatibility with version 8.0.x + (https://github.com/fmtlib/fmt/issues/2695, + https://github.com/fmtlib/fmt/pull/2696). Thanks @saraedum. +- Fixed chrono formatting on big endian systems + (https://github.com/fmtlib/fmt/issues/2698, + https://github.com/fmtlib/fmt/pull/2699). + Thanks @phprus and @xvitaly. +- Fixed a linkage error with mingw + (https://github.com/fmtlib/fmt/issues/2691, + https://github.com/fmtlib/fmt/pull/2692). Thanks @rbberger. + +# 8.1.0 - 2022-01-02 + +- Optimized chrono formatting + (https://github.com/fmtlib/fmt/pull/2500, + https://github.com/fmtlib/fmt/pull/2537, + https://github.com/fmtlib/fmt/issues/2541, + https://github.com/fmtlib/fmt/pull/2544, + https://github.com/fmtlib/fmt/pull/2550, + https://github.com/fmtlib/fmt/pull/2551, + https://github.com/fmtlib/fmt/pull/2576, + https://github.com/fmtlib/fmt/issues/2577, + https://github.com/fmtlib/fmt/pull/2586, + https://github.com/fmtlib/fmt/pull/2591, + https://github.com/fmtlib/fmt/pull/2594, + https://github.com/fmtlib/fmt/pull/2602, + https://github.com/fmtlib/fmt/pull/2617, + https://github.com/fmtlib/fmt/issues/2628, + https://github.com/fmtlib/fmt/pull/2633, + https://github.com/fmtlib/fmt/issues/2670, + https://github.com/fmtlib/fmt/pull/2671). + + Processing of some specifiers such as `%z` and `%Y` is now up to + 10-20 times faster, for example on GCC 11 with libstdc++: + + ---------------------------------------------------------------------------- + Benchmark Before After + ---------------------------------------------------------------------------- + FMTFormatter_z 261 ns 26.3 ns + FMTFormatterCompile_z 246 ns 11.6 ns + FMTFormatter_Y 263 ns 26.1 ns + FMTFormatterCompile_Y 244 ns 10.5 ns + ---------------------------------------------------------------------------- + + Thanks @phprus and @toughengineer. + +- Implemented subsecond formatting for chrono durations + (https://github.com/fmtlib/fmt/pull/2623). For example + ([godbolt](https://godbolt.org/z/es7vWTETe)): + + ```c++ + #include + + int main() { + fmt::print("{:%S}", std::chrono::milliseconds(1234)); + } + ``` + + prints \"01.234\". + + Thanks @matrackif. + +- Fixed handling of precision 0 when formatting chrono durations + (https://github.com/fmtlib/fmt/issues/2587, + https://github.com/fmtlib/fmt/pull/2588). Thanks @lukester1975. + +- Fixed an overflow on invalid inputs in the `tm` formatter + (https://github.com/fmtlib/fmt/pull/2564). Thanks @phprus. + +- Added `fmt::group_digits` that formats integers with a non-localized + digit separator (comma) for groups of three digits. For example + ([godbolt](https://godbolt.org/z/TxGxG9Poq)): + + ```c++ + #include + + int main() { + fmt::print("{} dollars", fmt::group_digits(1000000)); + } + ``` + + prints \"1,000,000 dollars\". + +- Added support for faint, conceal, reverse and blink text styles + (https://github.com/fmtlib/fmt/pull/2394): + + + + Thanks @benit8 and @data-man. + +- Added experimental support for compile-time floating point + formatting (https://github.com/fmtlib/fmt/pull/2426, + https://github.com/fmtlib/fmt/pull/2470). It is currently + limited to the header-only mode. Thanks @alexezeder. + +- Added UDL-based named argument support to compile-time format string + checks (https://github.com/fmtlib/fmt/issues/2640, + https://github.com/fmtlib/fmt/pull/2649). For example + ([godbolt](https://godbolt.org/z/ohGbbvonv)): + + ```c++ + #include + + int main() { + using namespace fmt::literals; + fmt::print("{answer:s}", "answer"_a=42); + } + ``` + + gives a compile-time error on compilers with C++20 `consteval` and + non-type template parameter support (gcc 10+) because `s` is not a + valid format specifier for an integer. + + Thanks @alexezeder. + +- Implemented escaping of string range elements. For example + ([godbolt](https://godbolt.org/z/rKvM1vKf3)): + + ```c++ + #include + #include + + int main() { + fmt::print("{}", std::vector{"\naan"}); + } + ``` + + is now printed as: + + ["\naan"] + + instead of: + + [" + aan"] + +- Added an experimental `?` specifier for escaping strings. + (https://github.com/fmtlib/fmt/pull/2674). Thanks @BRevzin. + +- Switched to JSON-like representation of maps and sets for + consistency with Python\'s `str.format`. For example + ([godbolt](https://godbolt.org/z/seKjoY9W5)): + + ```c++ + #include + #include + + int main() { + fmt::print("{}", std::map{{"answer", 42}}); + } + ``` + + is now printed as: + + {"answer": 42} + +- Extended `fmt::join` to support C++20-only ranges + (https://github.com/fmtlib/fmt/pull/2549). Thanks @BRevzin. + +- Optimized handling of non-const-iterable ranges and implemented + initial support for non-const-formattable types. + +- Disabled implicit conversions of scoped enums to integers that was + accidentally introduced in earlier versions + (https://github.com/fmtlib/fmt/pull/1841). + +- Deprecated implicit conversion of `[const] signed char*` and + `[const] unsigned char*` to C strings. + +- Deprecated `_format`, a legacy UDL-based format API + (https://github.com/fmtlib/fmt/pull/2646). Thanks @alexezeder. + +- Marked `format`, `formatted_size` and `to_string` as `[[nodiscard]]` + (https://github.com/fmtlib/fmt/pull/2612). @0x8000-0000. + +- Added missing diagnostic when trying to format function and member + pointers as well as objects convertible to pointers which is + explicitly disallowed + (https://github.com/fmtlib/fmt/issues/2598, + https://github.com/fmtlib/fmt/pull/2609, + https://github.com/fmtlib/fmt/pull/2610). Thanks @AlexGuteniev. + +- Optimized writing to a contiguous buffer with `format_to_n` + (https://github.com/fmtlib/fmt/pull/2489). Thanks @Roman-Koshelev. + +- Optimized writing to non-`char` buffers + (https://github.com/fmtlib/fmt/pull/2477). Thanks @Roman-Koshelev. + +- Decimal point is now localized when using the `L` specifier. + +- Improved floating point formatter implementation + (https://github.com/fmtlib/fmt/pull/2498, + https://github.com/fmtlib/fmt/pull/2499). Thanks @Roman-Koshelev. + +- Fixed handling of very large precision in fixed format + (https://github.com/fmtlib/fmt/pull/2616). + +- Made a table of cached powers used in FP formatting static + (https://github.com/fmtlib/fmt/pull/2509). Thanks @jk-jeon. + +- Resolved a lookup ambiguity with C++20 format-related functions due + to ADL (https://github.com/fmtlib/fmt/issues/2639, + https://github.com/fmtlib/fmt/pull/2641). Thanks @mkurdej. + +- Removed unnecessary inline namespace qualification + (https://github.com/fmtlib/fmt/issues/2642, + https://github.com/fmtlib/fmt/pull/2643). Thanks @mkurdej. + +- Implemented argument forwarding in `format_to_n` + (https://github.com/fmtlib/fmt/issues/2462, + https://github.com/fmtlib/fmt/pull/2463). Thanks @owent. + +- Fixed handling of implicit conversions in `fmt::to_string` and + format string compilation + (https://github.com/fmtlib/fmt/issues/2565). + +- Changed the default access mode of files created by + `fmt::output_file` to `-rw-r--r--` for consistency with `fopen` + (https://github.com/fmtlib/fmt/issues/2530). + +- Make `fmt::ostream::flush` public + (https://github.com/fmtlib/fmt/issues/2435). + +- Improved C++14/17 attribute detection + (https://github.com/fmtlib/fmt/pull/2615). Thanks @AlexGuteniev. + +- Improved `consteval` detection for MSVC + (https://github.com/fmtlib/fmt/pull/2559). Thanks @DanielaE. + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/2406, + https://github.com/fmtlib/fmt/pull/2446, + https://github.com/fmtlib/fmt/issues/2493, + https://github.com/fmtlib/fmt/issues/2513, + https://github.com/fmtlib/fmt/pull/2515, + https://github.com/fmtlib/fmt/issues/2522, + https://github.com/fmtlib/fmt/pull/2562, + https://github.com/fmtlib/fmt/pull/2575, + https://github.com/fmtlib/fmt/pull/2606, + https://github.com/fmtlib/fmt/pull/2620, + https://github.com/fmtlib/fmt/issues/2676). + Thanks @sobolevn, @UnePierre, @zhsj, @phprus, @ericcurtin and @Lounarok. + +- Improved fuzzers and added a fuzzer for chrono timepoint formatting + (https://github.com/fmtlib/fmt/pull/2461, + https://github.com/fmtlib/fmt/pull/2469). @pauldreik, + +- Added the `FMT_SYSTEM_HEADERS` CMake option setting which marks + {fmt}\'s headers as system. It can be used to suppress warnings + (https://github.com/fmtlib/fmt/issues/2644, + https://github.com/fmtlib/fmt/pull/2651). Thanks @alexezeder. + +- Added the Bazel build system support + (https://github.com/fmtlib/fmt/pull/2505, + https://github.com/fmtlib/fmt/pull/2516). Thanks @Vertexwahn. + +- Improved build configuration and tests + (https://github.com/fmtlib/fmt/issues/2437, + https://github.com/fmtlib/fmt/pull/2558, + https://github.com/fmtlib/fmt/pull/2648, + https://github.com/fmtlib/fmt/pull/2650, + https://github.com/fmtlib/fmt/pull/2663, + https://github.com/fmtlib/fmt/pull/2677). + Thanks @DanielaE, @alexezeder and @phprus. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/pull/2353, + https://github.com/fmtlib/fmt/pull/2356, + https://github.com/fmtlib/fmt/pull/2399, + https://github.com/fmtlib/fmt/issues/2408, + https://github.com/fmtlib/fmt/pull/2414, + https://github.com/fmtlib/fmt/pull/2427, + https://github.com/fmtlib/fmt/pull/2432, + https://github.com/fmtlib/fmt/pull/2442, + https://github.com/fmtlib/fmt/pull/2434, + https://github.com/fmtlib/fmt/issues/2439, + https://github.com/fmtlib/fmt/pull/2447, + https://github.com/fmtlib/fmt/pull/2450, + https://github.com/fmtlib/fmt/issues/2455, + https://github.com/fmtlib/fmt/issues/2465, + https://github.com/fmtlib/fmt/issues/2472, + https://github.com/fmtlib/fmt/issues/2474, + https://github.com/fmtlib/fmt/pull/2476, + https://github.com/fmtlib/fmt/issues/2478, + https://github.com/fmtlib/fmt/issues/2479, + https://github.com/fmtlib/fmt/issues/2481, + https://github.com/fmtlib/fmt/pull/2482, + https://github.com/fmtlib/fmt/pull/2483, + https://github.com/fmtlib/fmt/issues/2490, + https://github.com/fmtlib/fmt/pull/2491, + https://github.com/fmtlib/fmt/pull/2510, + https://github.com/fmtlib/fmt/pull/2518, + https://github.com/fmtlib/fmt/issues/2528, + https://github.com/fmtlib/fmt/pull/2529, + https://github.com/fmtlib/fmt/pull/2539, + https://github.com/fmtlib/fmt/issues/2540, + https://github.com/fmtlib/fmt/pull/2545, + https://github.com/fmtlib/fmt/pull/2555, + https://github.com/fmtlib/fmt/issues/2557, + https://github.com/fmtlib/fmt/issues/2570, + https://github.com/fmtlib/fmt/pull/2573, + https://github.com/fmtlib/fmt/pull/2582, + https://github.com/fmtlib/fmt/issues/2605, + https://github.com/fmtlib/fmt/pull/2611, + https://github.com/fmtlib/fmt/pull/2647, + https://github.com/fmtlib/fmt/issues/2627, + https://github.com/fmtlib/fmt/pull/2630, + https://github.com/fmtlib/fmt/issues/2635, + https://github.com/fmtlib/fmt/issues/2638, + https://github.com/fmtlib/fmt/issues/2653, + https://github.com/fmtlib/fmt/issues/2654, + https://github.com/fmtlib/fmt/issues/2661, + https://github.com/fmtlib/fmt/pull/2664, + https://github.com/fmtlib/fmt/pull/2684). + Thanks @DanielaE, @mwinterb, @cdacamar, @TrebledJ, @bodomartin, @cquammen, + @white238, @mmarkeloff, @palacaze, @jcelerier, @mborn-adi, @BrukerJWD, + @spyridon97, @phprus, @oliverlee, @joshessman-llnl, @akohlmey, @timkalu, + @olupton, @Acretock, @alexezeder, @andrewcorrigan, @lucpelletier and + @HazardyKnusperkeks. + +# 8.0.1 - 2021-07-02 + +- Fixed the version number in the inline namespace + (https://github.com/fmtlib/fmt/issues/2374). +- Added a missing presentation type check for `std::string` + (https://github.com/fmtlib/fmt/issues/2402). +- Fixed a linkage error when mixing code built with clang and gcc + (https://github.com/fmtlib/fmt/issues/2377). +- Fixed documentation issues + (https://github.com/fmtlib/fmt/pull/2396, + https://github.com/fmtlib/fmt/issues/2403, + https://github.com/fmtlib/fmt/issues/2406). Thanks @mkurdej. +- Removed dead code in FP formatter ( + https://github.com/fmtlib/fmt/pull/2398). Thanks @javierhonduco. +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/2351, + https://github.com/fmtlib/fmt/issues/2359, + https://github.com/fmtlib/fmt/pull/2365, + https://github.com/fmtlib/fmt/issues/2368, + https://github.com/fmtlib/fmt/pull/2370, + https://github.com/fmtlib/fmt/pull/2376, + https://github.com/fmtlib/fmt/pull/2381, + https://github.com/fmtlib/fmt/pull/2382, + https://github.com/fmtlib/fmt/issues/2386, + https://github.com/fmtlib/fmt/pull/2389, + https://github.com/fmtlib/fmt/pull/2395, + https://github.com/fmtlib/fmt/pull/2397, + https://github.com/fmtlib/fmt/issues/2400, + https://github.com/fmtlib/fmt/issues/2401, + https://github.com/fmtlib/fmt/pull/2407). + Thanks @zx2c4, @AidanSun05, @mattiasljungstrom, @joemmett, @erengy, + @patlkli, @gsjaardema and @phprus. + +# 8.0.0 - 2021-06-21 + +- Enabled compile-time format string checks by default. For example + ([godbolt](https://godbolt.org/z/sMxcohGjz)): + + ```c++ + #include + + int main() { + fmt::print("{:d}", "I am not a number"); + } + ``` + + gives a compile-time error on compilers with C++20 `consteval` + support (gcc 10+, clang 11+) because `d` is not a valid format + specifier for a string. + + To pass a runtime string wrap it in `fmt::runtime`: + + ```c++ + fmt::print(fmt::runtime("{:d}"), "I am not a number"); + ``` + +- Added compile-time formatting + (https://github.com/fmtlib/fmt/pull/2019, + https://github.com/fmtlib/fmt/pull/2044, + https://github.com/fmtlib/fmt/pull/2056, + https://github.com/fmtlib/fmt/pull/2072, + https://github.com/fmtlib/fmt/pull/2075, + https://github.com/fmtlib/fmt/issues/2078, + https://github.com/fmtlib/fmt/pull/2129, + https://github.com/fmtlib/fmt/pull/2326). For example + ([godbolt](https://godbolt.org/z/Mxx9d89jM)): + + ```c++ + #include + + consteval auto compile_time_itoa(int value) -> std::array { + auto result = std::array(); + fmt::format_to(result.data(), FMT_COMPILE("{}"), value); + return result; + } + + constexpr auto answer = compile_time_itoa(42); + ``` + + Most of the formatting functionality is available at compile time + with a notable exception of floating-point numbers and pointers. + Thanks @alexezeder. + +- Optimized handling of format specifiers during format string + compilation. For example, hexadecimal formatting (`"{:x}"`) is now + 3-7x faster than before when using `format_to` with format string + compilation and a stack-allocated buffer + (https://github.com/fmtlib/fmt/issues/1944). + + Before (7.1.3): + + ---------------------------------------------------------------------------- + Benchmark Time CPU Iterations + ---------------------------------------------------------------------------- + FMTCompileOld/0 15.5 ns 15.5 ns 43302898 + FMTCompileOld/42 16.6 ns 16.6 ns 43278267 + FMTCompileOld/273123 18.7 ns 18.6 ns 37035861 + FMTCompileOld/9223372036854775807 19.4 ns 19.4 ns 35243000 + ---------------------------------------------------------------------------- + + After (8.x): + + ---------------------------------------------------------------------------- + Benchmark Time CPU Iterations + ---------------------------------------------------------------------------- + FMTCompileNew/0 1.99 ns 1.99 ns 360523686 + FMTCompileNew/42 2.33 ns 2.33 ns 279865664 + FMTCompileNew/273123 3.72 ns 3.71 ns 190230315 + FMTCompileNew/9223372036854775807 5.28 ns 5.26 ns 130711631 + ---------------------------------------------------------------------------- + + It is even faster than `std::to_chars` from libc++ compiled with + clang on macOS: + + ---------------------------------------------------------------------------- + Benchmark Time CPU Iterations + ---------------------------------------------------------------------------- + ToChars/0 4.42 ns 4.41 ns 160196630 + ToChars/42 5.00 ns 4.98 ns 140735201 + ToChars/273123 7.26 ns 7.24 ns 95784130 + ToChars/9223372036854775807 8.77 ns 8.75 ns 75872534 + ---------------------------------------------------------------------------- + + In other cases, especially involving `std::string` construction, the + speed up is usually lower because handling format specifiers takes a + smaller fraction of the total time. + +- Added the `_cf` user-defined literal to represent a compiled format + string. It can be used instead of the `FMT_COMPILE` macro + (https://github.com/fmtlib/fmt/pull/2043, + https://github.com/fmtlib/fmt/pull/2242): + + ```c++ + #include + + using namespace fmt::literals; + auto s = fmt::format(FMT_COMPILE("{}"), 42); // 🙁 not modern + auto s = fmt::format("{}"_cf, 42); // 🙂 modern as hell + ``` + + It requires compiler support for class types in non-type template + parameters (a C++20 feature) which is available in GCC 9.3+. + Thanks @alexezeder. + +- Format string compilation now requires `format` functions of + `formatter` specializations for user-defined types to be `const`: + + ```c++ + template <> struct fmt::formatter: formatter { + template + auto format(my_type obj, FormatContext& ctx) const { // Note const here. + // ... + } + }; + ``` + +- Added UDL-based named argument support to format string compilation + (https://github.com/fmtlib/fmt/pull/2243, + https://github.com/fmtlib/fmt/pull/2281). For example: + + ```c++ + #include + + using namespace fmt::literals; + auto s = fmt::format(FMT_COMPILE("{answer}"), "answer"_a = 42); + ``` + + Here the argument named \"answer\" is resolved at compile time with + no runtime overhead. Thanks @alexezeder. + +- Added format string compilation support to `fmt::print` + (https://github.com/fmtlib/fmt/issues/2280, + https://github.com/fmtlib/fmt/pull/2304). Thanks @alexezeder. + +- Added initial support for compiling {fmt} as a C++20 module + (https://github.com/fmtlib/fmt/pull/2235, + https://github.com/fmtlib/fmt/pull/2240, + https://github.com/fmtlib/fmt/pull/2260, + https://github.com/fmtlib/fmt/pull/2282, + https://github.com/fmtlib/fmt/pull/2283, + https://github.com/fmtlib/fmt/pull/2288, + https://github.com/fmtlib/fmt/pull/2298, + https://github.com/fmtlib/fmt/pull/2306, + https://github.com/fmtlib/fmt/pull/2307, + https://github.com/fmtlib/fmt/pull/2309, + https://github.com/fmtlib/fmt/pull/2318, + https://github.com/fmtlib/fmt/pull/2324, + https://github.com/fmtlib/fmt/pull/2332, + https://github.com/fmtlib/fmt/pull/2340). Thanks @DanielaE. + +- Made symbols private by default reducing shared library size + (https://github.com/fmtlib/fmt/pull/2301). For example + there was a \~15% reported reduction on one platform. Thanks @sergiud. + +- Optimized includes making the result of preprocessing `fmt/format.h` + \~20% smaller with libstdc++/C++20 and slightly improving build + times (https://github.com/fmtlib/fmt/issues/1998). + +- Added support of ranges with non-const `begin` / `end` + (https://github.com/fmtlib/fmt/pull/1953). Thanks @kitegi. + +- Added support of `std::byte` and other formattable types to + `fmt::join` (https://github.com/fmtlib/fmt/issues/1981, + https://github.com/fmtlib/fmt/issues/2040, + https://github.com/fmtlib/fmt/pull/2050, + https://github.com/fmtlib/fmt/issues/2262). For example: + + ```c++ + #include + #include + #include + + int main() { + auto bytes = std::vector{std::byte(4), std::byte(2)}; + fmt::print("{}", fmt::join(bytes, "")); + } + ``` + + prints \"42\". + + Thanks @kamibo. + +- Implemented the default format for `std::chrono::system_clock` + (https://github.com/fmtlib/fmt/issues/2319, + https://github.com/fmtlib/fmt/pull/2345). For example: + + ```c++ + #include + + int main() { + fmt::print("{}", std::chrono::system_clock::now()); + } + ``` + + prints \"2021-06-18 15:22:00\" (the output depends on the current + date and time). Thanks @sunmy2019. + +- Made more chrono specifiers locale independent by default. Use the + `'L'` specifier to get localized formatting. For example: + + ```c++ + #include + + int main() { + std::locale::global(std::locale("ru_RU.UTF-8")); + auto monday = std::chrono::weekday(1); + fmt::print("{}\n", monday); // prints "Mon" + fmt::print("{:L}\n", monday); // prints "пн" + } + ``` + +- Improved locale handling in chrono formatting + (https://github.com/fmtlib/fmt/issues/2337, + https://github.com/fmtlib/fmt/pull/2349, + https://github.com/fmtlib/fmt/pull/2350). Thanks @phprus. + +- Deprecated `fmt/locale.h` moving the formatting functions that take + a locale to `fmt/format.h` (`char`) and `fmt/xchar` (other + overloads). This doesn\'t introduce a dependency on `` so + there is virtually no compile time effect. + +- Deprecated an undocumented `format_to` overload that takes + `basic_memory_buffer`. + +- Made parameter order in `vformat_to` consistent with `format_to` + (https://github.com/fmtlib/fmt/issues/2327). + +- Added support for time points with arbitrary durations + (https://github.com/fmtlib/fmt/issues/2208). For example: + + ```c++ + #include + + int main() { + using tp = std::chrono::time_point< + std::chrono::system_clock, std::chrono::seconds>; + fmt::print("{:%S}", tp(std::chrono::seconds(42))); + } + ``` + + prints \"42\". + +- Formatting floating-point numbers no longer produces trailing zeros + by default for consistency with `std::format`. For example: + + ```c++ + #include + + int main() { + fmt::print("{0:.3}", 1.1); + } + ``` + + prints \"1.1\". Use the `'#'` specifier to keep trailing zeros. + +- Dropped a limit on the number of elements in a range and replaced + `{}` with `[]` as range delimiters for consistency with Python\'s + `str.format`. + +- The `'L'` specifier for locale-specific numeric formatting can now + be combined with presentation specifiers as in `std::format`. For + example: + + ```c++ + #include + #include + + int main() { + std::locale::global(std::locale("fr_FR.UTF-8")); + fmt::print("{0:.2Lf}", 0.42); + } + ``` + + prints \"0,42\". The deprecated `'n'` specifier has been removed. + +- Made the `0` specifier ignored for infinity and NaN + (https://github.com/fmtlib/fmt/issues/2305, + https://github.com/fmtlib/fmt/pull/2310). Thanks @Liedtke. + +- Made the hexfloat formatting use the right alignment by default + (https://github.com/fmtlib/fmt/issues/2308, + https://github.com/fmtlib/fmt/pull/2317). Thanks @Liedtke. + +- Removed the deprecated numeric alignment (`'='`). Use the `'0'` + specifier instead. + +- Removed the deprecated `fmt/posix.h` header that has been replaced + with `fmt/os.h`. + +- Removed the deprecated `format_to_n_context`, `format_to_n_args` and + `make_format_to_n_args`. They have been replaced with + `format_context`, `` format_args` and ``make_format_args\`\` + respectively. + +- Moved `wchar_t`-specific functions and types to `fmt/xchar.h`. You + can define `FMT_DEPRECATED_INCLUDE_XCHAR` to automatically include + `fmt/xchar.h` from `fmt/format.h` but this will be disabled in the + next major release. + +- Fixed handling of the `'+'` specifier in localized formatting + (https://github.com/fmtlib/fmt/issues/2133). + +- Added support for the `'s'` format specifier that gives textual + representation of `bool` + (https://github.com/fmtlib/fmt/issues/2094, + https://github.com/fmtlib/fmt/pull/2109). For example: + + ```c++ + #include + + int main() { + fmt::print("{:s}", true); + } + ``` + + prints \"true\". Thanks @powercoderlol. + +- Made `fmt::ptr` work with function pointers + (https://github.com/fmtlib/fmt/pull/2131). For example: + + ```c++ + #include + + int main() { + fmt::print("My main: {}\n", fmt::ptr(main)); + } + ``` + + Thanks @mikecrowe. + +- The undocumented support for specializing `formatter` for pointer + types has been removed. + +- Fixed `fmt::formatted_size` with format string compilation + (https://github.com/fmtlib/fmt/pull/2141, + https://github.com/fmtlib/fmt/pull/2161). Thanks @alexezeder. + +- Fixed handling of empty format strings during format string + compilation (https://github.com/fmtlib/fmt/issues/2042): + + ```c++ + auto s = fmt::format(FMT_COMPILE("")); + ``` + + Thanks @alexezeder. + +- Fixed handling of enums in `fmt::to_string` + (https://github.com/fmtlib/fmt/issues/2036). + +- Improved width computation + (https://github.com/fmtlib/fmt/issues/2033, + https://github.com/fmtlib/fmt/issues/2091). For example: + + ```c++ + #include + + int main() { + fmt::print("{:-<10}{}\n", "你好", "世界"); + fmt::print("{:-<10}{}\n", "hello", "world"); + } + ``` + + prints + + ![](https://user-images.githubusercontent.com/576385/119840373-cea3ca80-beb9-11eb-91e0-54266c48e181.png) + + on a modern terminal. + +- The experimental fast output stream (`fmt::ostream`) is now + truncated by default for consistency with `fopen` + (https://github.com/fmtlib/fmt/issues/2018). For example: + + ```c++ + #include + + int main() { + fmt::ostream out1 = fmt::output_file("guide"); + out1.print("Zaphod"); + out1.close(); + fmt::ostream out2 = fmt::output_file("guide"); + out2.print("Ford"); + } + ``` + + writes \"Ford\" to the file \"guide\". To preserve the old file + content if any pass `fmt::file::WRONLY | fmt::file::CREATE` flags to + `fmt::output_file`. + +- Fixed moving of `fmt::ostream` that holds buffered data + (https://github.com/fmtlib/fmt/issues/2197, + https://github.com/fmtlib/fmt/pull/2198). Thanks @vtta. + +- Replaced the `fmt::system_error` exception with a function of the + same name that constructs `std::system_error` + (https://github.com/fmtlib/fmt/issues/2266). + +- Replaced the `fmt::windows_error` exception with a function of the + same name that constructs `std::system_error` with the category + returned by `fmt::system_category()` + (https://github.com/fmtlib/fmt/issues/2274, + https://github.com/fmtlib/fmt/pull/2275). The latter is + similar to `std::system_category` but correctly handles UTF-8. + Thanks @phprus. + +- Replaced `fmt::error_code` with `std::error_code` and made it + formattable (https://github.com/fmtlib/fmt/issues/2269, + https://github.com/fmtlib/fmt/pull/2270, + https://github.com/fmtlib/fmt/pull/2273). Thanks @phprus. + +- Added speech synthesis support + (https://github.com/fmtlib/fmt/pull/2206). + +- Made `format_to` work with a memory buffer that has a custom + allocator (https://github.com/fmtlib/fmt/pull/2300). + Thanks @voxmea. + +- Added `Allocator::max_size` support to `basic_memory_buffer`. + (https://github.com/fmtlib/fmt/pull/1960). Thanks @phprus. + +- Added wide string support to `fmt::join` + (https://github.com/fmtlib/fmt/pull/2236). Thanks @crbrz. + +- Made iterators passed to `formatter` specializations via a format + context satisfy C++20 `std::output_iterator` requirements + (https://github.com/fmtlib/fmt/issues/2156, + https://github.com/fmtlib/fmt/pull/2158, + https://github.com/fmtlib/fmt/issues/2195, + https://github.com/fmtlib/fmt/pull/2204). Thanks @randomnetcat. + +- Optimized the `printf` implementation + (https://github.com/fmtlib/fmt/pull/1982, + https://github.com/fmtlib/fmt/pull/1984, + https://github.com/fmtlib/fmt/pull/2016, + https://github.com/fmtlib/fmt/pull/2164). + Thanks @rimathia and @moiwi. + +- Improved detection of `constexpr` `char_traits` + (https://github.com/fmtlib/fmt/pull/2246, + https://github.com/fmtlib/fmt/pull/2257). Thanks @phprus. + +- Fixed writing to `stdout` when it is redirected to `NUL` on Windows + (https://github.com/fmtlib/fmt/issues/2080). + +- Fixed exception propagation from iterators + (https://github.com/fmtlib/fmt/issues/2097). + +- Improved `strftime` error handling + (https://github.com/fmtlib/fmt/issues/2238, + https://github.com/fmtlib/fmt/pull/2244). Thanks @yumeyao. + +- Stopped using deprecated GCC UDL template extension. + +- Added `fmt/args.h` to the install target + (https://github.com/fmtlib/fmt/issues/2096). + +- Error messages are now passed to assert when exceptions are disabled + (https://github.com/fmtlib/fmt/pull/2145). Thanks @NobodyXu. + +- Added the `FMT_MASTER_PROJECT` CMake option to control build and + install targets when {fmt} is included via `add_subdirectory` + (https://github.com/fmtlib/fmt/issues/2098, + https://github.com/fmtlib/fmt/pull/2100). + Thanks @randomizedthinking. + +- Improved build configuration + (https://github.com/fmtlib/fmt/pull/2026, + https://github.com/fmtlib/fmt/pull/2122). + Thanks @luncliff and @ibaned. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/1947, + https://github.com/fmtlib/fmt/pull/1959, + https://github.com/fmtlib/fmt/pull/1963, + https://github.com/fmtlib/fmt/pull/1965, + https://github.com/fmtlib/fmt/issues/1966, + https://github.com/fmtlib/fmt/pull/1974, + https://github.com/fmtlib/fmt/pull/1975, + https://github.com/fmtlib/fmt/pull/1990, + https://github.com/fmtlib/fmt/issues/2000, + https://github.com/fmtlib/fmt/pull/2001, + https://github.com/fmtlib/fmt/issues/2002, + https://github.com/fmtlib/fmt/issues/2004, + https://github.com/fmtlib/fmt/pull/2006, + https://github.com/fmtlib/fmt/pull/2009, + https://github.com/fmtlib/fmt/pull/2010, + https://github.com/fmtlib/fmt/issues/2038, + https://github.com/fmtlib/fmt/issues/2039, + https://github.com/fmtlib/fmt/issues/2047, + https://github.com/fmtlib/fmt/pull/2053, + https://github.com/fmtlib/fmt/issues/2059, + https://github.com/fmtlib/fmt/pull/2065, + https://github.com/fmtlib/fmt/pull/2067, + https://github.com/fmtlib/fmt/pull/2068, + https://github.com/fmtlib/fmt/pull/2073, + https://github.com/fmtlib/fmt/issues/2103, + https://github.com/fmtlib/fmt/issues/2105, + https://github.com/fmtlib/fmt/pull/2106, + https://github.com/fmtlib/fmt/pull/2107, + https://github.com/fmtlib/fmt/issues/2116, + https://github.com/fmtlib/fmt/pull/2117, + https://github.com/fmtlib/fmt/issues/2118, + https://github.com/fmtlib/fmt/pull/2119, + https://github.com/fmtlib/fmt/issues/2127, + https://github.com/fmtlib/fmt/pull/2128, + https://github.com/fmtlib/fmt/issues/2140, + https://github.com/fmtlib/fmt/issues/2142, + https://github.com/fmtlib/fmt/pull/2143, + https://github.com/fmtlib/fmt/pull/2144, + https://github.com/fmtlib/fmt/issues/2147, + https://github.com/fmtlib/fmt/issues/2148, + https://github.com/fmtlib/fmt/issues/2149, + https://github.com/fmtlib/fmt/pull/2152, + https://github.com/fmtlib/fmt/pull/2160, + https://github.com/fmtlib/fmt/issues/2170, + https://github.com/fmtlib/fmt/issues/2175, + https://github.com/fmtlib/fmt/issues/2176, + https://github.com/fmtlib/fmt/pull/2177, + https://github.com/fmtlib/fmt/issues/2178, + https://github.com/fmtlib/fmt/pull/2179, + https://github.com/fmtlib/fmt/issues/2180, + https://github.com/fmtlib/fmt/issues/2181, + https://github.com/fmtlib/fmt/pull/2183, + https://github.com/fmtlib/fmt/issues/2184, + https://github.com/fmtlib/fmt/issues/2185, + https://github.com/fmtlib/fmt/pull/2186, + https://github.com/fmtlib/fmt/pull/2187, + https://github.com/fmtlib/fmt/pull/2190, + https://github.com/fmtlib/fmt/pull/2192, + https://github.com/fmtlib/fmt/pull/2194, + https://github.com/fmtlib/fmt/pull/2205, + https://github.com/fmtlib/fmt/issues/2210, + https://github.com/fmtlib/fmt/pull/2211, + https://github.com/fmtlib/fmt/pull/2215, + https://github.com/fmtlib/fmt/pull/2216, + https://github.com/fmtlib/fmt/pull/2218, + https://github.com/fmtlib/fmt/pull/2220, + https://github.com/fmtlib/fmt/issues/2228, + https://github.com/fmtlib/fmt/pull/2229, + https://github.com/fmtlib/fmt/pull/2230, + https://github.com/fmtlib/fmt/issues/2233, + https://github.com/fmtlib/fmt/pull/2239, + https://github.com/fmtlib/fmt/issues/2248, + https://github.com/fmtlib/fmt/issues/2252, + https://github.com/fmtlib/fmt/pull/2253, + https://github.com/fmtlib/fmt/pull/2255, + https://github.com/fmtlib/fmt/issues/2261, + https://github.com/fmtlib/fmt/issues/2278, + https://github.com/fmtlib/fmt/issues/2284, + https://github.com/fmtlib/fmt/pull/2287, + https://github.com/fmtlib/fmt/pull/2289, + https://github.com/fmtlib/fmt/pull/2290, + https://github.com/fmtlib/fmt/pull/2293, + https://github.com/fmtlib/fmt/issues/2295, + https://github.com/fmtlib/fmt/pull/2296, + https://github.com/fmtlib/fmt/pull/2297, + https://github.com/fmtlib/fmt/issues/2311, + https://github.com/fmtlib/fmt/pull/2313, + https://github.com/fmtlib/fmt/pull/2315, + https://github.com/fmtlib/fmt/issues/2320, + https://github.com/fmtlib/fmt/pull/2321, + https://github.com/fmtlib/fmt/pull/2323, + https://github.com/fmtlib/fmt/issues/2328, + https://github.com/fmtlib/fmt/pull/2329, + https://github.com/fmtlib/fmt/pull/2333, + https://github.com/fmtlib/fmt/pull/2338, + https://github.com/fmtlib/fmt/pull/2341). + Thanks @darklukee, @fagg, @killerbot242, @jgopel, @yeswalrus, @Finkman, + @HazardyKnusperkeks, @dkavolis, @concatime, @chronoxor, @summivox, @yNeo, + @Apache-HB, @alexezeder, @toojays, @Brainy0207, @vadz, @imsherlock, @phprus, + @white238, @yafshar, @BillyDonahue, @jstaahl, @denchat, @DanielaE, + @ilyakurdyukov, @ilmai, @JessyDL, @sergiud, @mwinterb, @sven-herrmann, + @jmelas, @twoixter, @crbrz and @upsj. + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/1986, + https://github.com/fmtlib/fmt/pull/2051, + https://github.com/fmtlib/fmt/issues/2057, + https://github.com/fmtlib/fmt/pull/2081, + https://github.com/fmtlib/fmt/issues/2084, + https://github.com/fmtlib/fmt/pull/2312). + Thanks @imba-tjd, @0x416c69 and @mordante. + +- Continuous integration and test improvements + (https://github.com/fmtlib/fmt/issues/1969, + https://github.com/fmtlib/fmt/pull/1991, + https://github.com/fmtlib/fmt/pull/2020, + https://github.com/fmtlib/fmt/pull/2110, + https://github.com/fmtlib/fmt/pull/2114, + https://github.com/fmtlib/fmt/issues/2196, + https://github.com/fmtlib/fmt/pull/2217, + https://github.com/fmtlib/fmt/pull/2247, + https://github.com/fmtlib/fmt/pull/2256, + https://github.com/fmtlib/fmt/pull/2336, + https://github.com/fmtlib/fmt/pull/2346). + Thanks @jgopel, @alexezeder and @DanielaE. + +The change log for versions 0.8.0 - 7.1.3 is available [here]( +doc/ChangeLog-old.md). diff --git a/Engine/lib/openal-soft/fmt-11.1.1/LICENSE b/Engine/lib/openal-soft/fmt-11.1.1/LICENSE new file mode 100644 index 000000000..1cd1ef926 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- Optional exception to the license --- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into a machine-executable object form of such +source code, you may redistribute such embedded portions in such object form +without including the above copyright and permission notices. diff --git a/Engine/lib/openal-soft/fmt-11.1.1/README.md b/Engine/lib/openal-soft/fmt-11.1.1/README.md new file mode 100644 index 000000000..fd845db20 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/README.md @@ -0,0 +1,485 @@ +{fmt} + +[![image](https://github.com/fmtlib/fmt/workflows/linux/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux) +[![image](https://github.com/fmtlib/fmt/workflows/macos/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos) +[![image](https://github.com/fmtlib/fmt/workflows/windows/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows) +[![fmt is continuously fuzzed at oss-fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?\%0Acolspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\%0ASummary&q=proj%3Dfmt&can=1) +[![Ask questions at StackOverflow with the tag fmt](https://img.shields.io/badge/stackoverflow-fmt-blue.svg)](https://stackoverflow.com/questions/tagged/fmt) +[![image](https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge)](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt) + +**{fmt}** is an open-source formatting library providing a fast and safe +alternative to C stdio and C++ iostreams. + +If you like this project, please consider donating to one of the funds +that help victims of the war in Ukraine: . + +[Documentation](https://fmt.dev) + +[Cheat Sheets](https://hackingcpp.com/cpp/libs/fmt.html) + +Q&A: ask questions on [StackOverflow with the tag +fmt](https://stackoverflow.com/questions/tagged/fmt). + +Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v). + +# Features + +- Simple [format API](https://fmt.dev/latest/api/) with positional + arguments for localization +- Implementation of [C++20 + std::format](https://en.cppreference.com/w/cpp/utility/format) and + [C++23 std::print](https://en.cppreference.com/w/cpp/io/print) +- [Format string syntax](https://fmt.dev/latest/syntax/) similar + to Python\'s + [format](https://docs.python.org/3/library/stdtypes.html#str.format) +- Fast IEEE 754 floating-point formatter with correct rounding, + shortness and round-trip guarantees using the + [Dragonbox](https://github.com/jk-jeon/dragonbox) algorithm +- Portable Unicode support +- Safe [printf + implementation](https://fmt.dev/latest/api/#printf-formatting) + including the POSIX extension for positional arguments +- Extensibility: [support for user-defined + types](https://fmt.dev/latest/api/#formatting-user-defined-types) +- High performance: faster than common standard library + implementations of `(s)printf`, iostreams, `to_string` and + `to_chars`, see [Speed tests](#speed-tests) and [Converting a + hundred million integers to strings per + second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html) +- Small code size both in terms of source code with the minimum + configuration consisting of just three files, `core.h`, `format.h` + and `format-inl.h`, and compiled code; see [Compile time and code + bloat](#compile-time-and-code-bloat) +- Reliability: the library has an extensive set of + [tests](https://github.com/fmtlib/fmt/tree/master/test) and is + [continuously fuzzed](https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1) +- Safety: the library is fully type-safe, errors in format strings can + be reported at compile time, automatic memory management prevents + buffer overflow errors +- Ease of use: small self-contained code base, no external + dependencies, permissive MIT + [license](https://github.com/fmtlib/fmt/blob/master/LICENSE) +- [Portability](https://fmt.dev/latest/#portability) with + consistent output across platforms and support for older compilers +- Clean warning-free codebase even on high warning levels such as + `-Wall -Wextra -pedantic` +- Locale independence by default +- Optional header-only configuration enabled with the + `FMT_HEADER_ONLY` macro + +See the [documentation](https://fmt.dev) for more details. + +# Examples + +**Print to stdout** ([run](https://godbolt.org/z/Tevcjh)) + +``` c++ +#include + +int main() { + fmt::print("Hello, world!\n"); +} +``` + +**Format a string** ([run](https://godbolt.org/z/oK8h33)) + +``` c++ +std::string s = fmt::format("The answer is {}.", 42); +// s == "The answer is 42." +``` + +**Format a string using positional arguments** +([run](https://godbolt.org/z/Yn7Txe)) + +``` c++ +std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy"); +// s == "I'd rather be happy than right." +``` + +**Print dates and times** ([run](https://godbolt.org/z/c31ExdY3W)) + +``` c++ +#include + +int main() { + auto now = std::chrono::system_clock::now(); + fmt::print("Date and time: {}\n", now); + fmt::print("Time: {:%H:%M}\n", now); +} +``` + +Output: + + Date and time: 2023-12-26 19:10:31.557195597 + Time: 19:10 + +**Print a container** ([run](https://godbolt.org/z/MxM1YqjE7)) + +``` c++ +#include +#include + +int main() { + std::vector v = {1, 2, 3}; + fmt::print("{}\n", v); +} +``` + +Output: + + [1, 2, 3] + +**Check a format string at compile time** + +``` c++ +std::string s = fmt::format("{:d}", "I am not a number"); +``` + +This gives a compile-time error in C++20 because `d` is an invalid +format specifier for a string. + +**Write a file from a single thread** + +``` c++ +#include + +int main() { + auto out = fmt::output_file("guide.txt"); + out.print("Don't {}", "Panic"); +} +``` + +This can be [5 to 9 times faster than +fprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html). + +**Print with colors and text styles** + +``` c++ +#include + +int main() { + fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, + "Hello, {}!\n", "world"); + fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) | + fmt::emphasis::underline, "Olá, {}!\n", "Mundo"); + fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, + "你好{}!\n", "世界"); +} +``` + +Output on a modern terminal with Unicode support: + +![image](https://github.com/fmtlib/fmt/assets/%0A576385/2a93c904-d6fa-4aa6-b453-2618e1c327d7) + +# Benchmarks + +## Speed tests + +| Library | Method | Run Time, s | +|-------------------|---------------|-------------| +| libc | printf | 0.91 | +| libc++ | std::ostream | 2.49 | +| {fmt} 9.1 | fmt::print | 0.74 | +| Boost Format 1.80 | boost::format | 6.26 | +| Folly Format | folly::format | 1.87 | + +{fmt} is the fastest of the benchmarked methods, \~20% faster than +`printf`. + +The above results were generated by building `tinyformat_test.cpp` on +macOS 12.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and +taking the best of three runs. In the test, the format string +`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000 +times with output sent to `/dev/null`; for further details refer to the +[source](https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc). + +{fmt} is up to 20-30x faster than `std::ostringstream` and `sprintf` on +IEEE754 `float` and `double` formatting +([dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark)) and faster +than [double-conversion](https://github.com/google/double-conversion) +and [ryu](https://github.com/ulfjack/ryu): + +[![image](https://user-images.githubusercontent.com/576385/95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png)](https://fmt.dev/unknown_mac64_clang12.0.html) + +## Compile time and code bloat + +The script [bloat-test.py][test] from [format-benchmark][bench] tests compile +time and code bloat for nontrivial projects. It generates 100 translation units +and uses `printf()` or its alternative five times in each to simulate a +medium-sized project. The resulting executable size and compile time (Apple +clang version 15.0.0 (clang-1500.1.0.2.5), macOS Sonoma, best of three) is shown +in the following tables. + +[test]: https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py +[bench]: https://github.com/fmtlib/format-benchmark + +**Optimized build (-O3)** + +| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB | +|---------------|-----------------|----------------------|--------------------| +| printf | 1.6 | 54 | 50 | +| IOStreams | 25.9 | 98 | 84 | +| fmt 83652df | 4.8 | 54 | 50 | +| tinyformat | 29.1 | 161 | 136 | +| Boost Format | 55.0 | 530 | 317 | + +{fmt} is fast to compile and is comparable to `printf` in terms of per-call +binary size (within a rounding error on this system). + +**Non-optimized build** + +| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB | +|---------------|-----------------|----------------------|--------------------| +| printf | 1.4 | 54 | 50 | +| IOStreams | 23.4 | 92 | 68 | +| {fmt} 83652df | 4.4 | 89 | 85 | +| tinyformat | 24.5 | 204 | 161 | +| Boost Format | 36.4 | 831 | 462 | + +`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries +to compare formatting function overhead only. Boost Format is a +header-only library so it doesn\'t provide any linkage options. + +## Running the tests + +Please refer to [Building the +library](https://fmt.dev/latest/get-started/#building-from-source) for +instructions on how to build the library and run the unit tests. + +Benchmarks reside in a separate repository, +[format-benchmarks](https://github.com/fmtlib/format-benchmark), so to +run the benchmarks you first need to clone this repository and generate +Makefiles with CMake: + + $ git clone --recursive https://github.com/fmtlib/format-benchmark.git + $ cd format-benchmark + $ cmake . + +Then you can run the speed test: + + $ make speed-test + +or the bloat test: + + $ make bloat-test + +# Migrating code + +[clang-tidy](https://clang.llvm.org/extra/clang-tidy/) v18 provides the +[modernize-use-std-print](https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html) +check that is capable of converting occurrences of `printf` and +`fprintf` to `fmt::print` if configured to do so. (By default it +converts to `std::print`.) + +# Notable projects using this library + +- [0 A.D.](https://play0ad.com/): a free, open-source, cross-platform + real-time strategy game +- [AMPL/MP](https://github.com/ampl/mp): an open-source library for + mathematical programming +- [Apple's FoundationDB](https://github.com/apple/foundationdb): an open-source, + distributed, transactional key-value store +- [Aseprite](https://github.com/aseprite/aseprite): animated sprite + editor & pixel art tool +- [AvioBook](https://www.aviobook.aero/en): a comprehensive aircraft + operations suite +- [Blizzard Battle.net](https://battle.net/): an online gaming + platform +- [Celestia](https://celestia.space/): real-time 3D visualization of + space +- [Ceph](https://ceph.com/): a scalable distributed storage system +- [ccache](https://ccache.dev/): a compiler cache +- [ClickHouse](https://github.com/ClickHouse/ClickHouse): an + analytical database management system +- [ContextVision](https://www.contextvision.com/): medical imaging software +- [Contour](https://github.com/contour-terminal/contour/): a modern + terminal emulator +- [CUAUV](https://cuauv.org/): Cornell University\'s autonomous + underwater vehicle +- [Drake](https://drake.mit.edu/): a planning, control, and analysis + toolbox for nonlinear dynamical systems (MIT) +- [Envoy](https://github.com/envoyproxy/envoy): C++ L7 proxy and + communication bus (Lyft) +- [FiveM](https://fivem.net/): a modification framework for GTA V +- [fmtlog](https://github.com/MengRao/fmtlog): a performant + fmtlib-style logging library with latency in nanoseconds +- [Folly](https://github.com/facebook/folly): Facebook open-source + library +- [GemRB](https://gemrb.org/): a portable open-source implementation + of Bioware's Infinity Engine +- [Grand Mountain + Adventure](https://store.steampowered.com/app/1247360/Grand_Mountain_Adventure/): + a beautiful open-world ski & snowboarding game +- [HarpyWar/pvpgn](https://github.com/pvpgn/pvpgn-server): Player vs + Player Gaming Network with tweaks +- [KBEngine](https://github.com/kbengine/kbengine): an open-source + MMOG server engine +- [Keypirinha](https://keypirinha.com/): a semantic launcher for + Windows +- [Kodi](https://kodi.tv/) (formerly xbmc): home theater software +- [Knuth](https://kth.cash/): high-performance Bitcoin full-node +- [libunicode](https://github.com/contour-terminal/libunicode/): a + modern C++17 Unicode library +- [MariaDB](https://mariadb.org/): relational database management + system +- [Microsoft Verona](https://github.com/microsoft/verona): research + programming language for concurrent ownership +- [MongoDB](https://mongodb.com/): distributed document database +- [MongoDB Smasher](https://github.com/duckie/mongo_smasher): a small + tool to generate randomized datasets +- [OpenSpace](https://openspaceproject.com/): an open-source + astrovisualization framework +- [PenUltima Online (POL)](https://www.polserver.com/): an MMO server, + compatible with most Ultima Online clients +- [PyTorch](https://github.com/pytorch/pytorch): an open-source + machine learning library +- [quasardb](https://www.quasardb.net/): a distributed, + high-performance, associative database +- [Quill](https://github.com/odygrd/quill): asynchronous low-latency + logging library +- [QKW](https://github.com/ravijanjam/qkw): generalizing aliasing to + simplify navigation, and execute complex multi-line terminal + command sequences +- [redis-cerberus](https://github.com/HunanTV/redis-cerberus): a Redis + cluster proxy +- [redpanda](https://vectorized.io/redpanda): a 10x faster Kafka® + replacement for mission-critical systems written in C++ +- [rpclib](http://rpclib.net/): a modern C++ msgpack-RPC server and + client library +- [Salesforce Analytics + Cloud](https://www.salesforce.com/analytics-cloud/overview/): + business intelligence software +- [Scylla](https://www.scylladb.com/): a Cassandra-compatible NoSQL + data store that can handle 1 million transactions per second on a + single server +- [Seastar](http://www.seastar-project.org/): an advanced, open-source + C++ framework for high-performance server applications on modern + hardware +- [spdlog](https://github.com/gabime/spdlog): super fast C++ logging + library +- [Stellar](https://www.stellar.org/): financial platform +- [Touch Surgery](https://www.touchsurgery.com/): surgery simulator +- [TrinityCore](https://github.com/TrinityCore/TrinityCore): + open-source MMORPG framework +- [🐙 userver framework](https://userver.tech/): open-source + asynchronous framework with a rich set of abstractions and database + drivers +- [Windows Terminal](https://github.com/microsoft/terminal): the new + Windows terminal + +[More\...](https://github.com/search?q=fmtlib&type=Code) + +If you are aware of other projects using this library, please let me +know by [email](mailto:victor.zverovich@gmail.com) or by submitting an +[issue](https://github.com/fmtlib/fmt/issues). + +# Motivation + +So why yet another formatting library? + +There are plenty of methods for doing this task, from standard ones like +the printf family of function and iostreams to Boost Format and +FastFormat libraries. The reason for creating a new library is that +every existing solution that I found either had serious issues or +didn\'t provide all the features I needed. + +## printf + +The good thing about `printf` is that it is pretty fast and readily +available being a part of the C standard library. The main drawback is +that it doesn\'t support user-defined types. `printf` also has safety +issues although they are somewhat mitigated with [\_\_attribute\_\_ +((format (printf, +\...))](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html) in +GCC. There is a POSIX extension that adds positional arguments required +for +[i18n](https://en.wikipedia.org/wiki/Internationalization_and_localization) +to `printf` but it is not a part of C99 and may not be available on some +platforms. + +## iostreams + +The main issue with iostreams is best illustrated with an example: + +``` c++ +std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n"; +``` + +which is a lot of typing compared to printf: + +``` c++ +printf("%.2f\n", 1.23456); +``` + +Matthew Wilson, the author of FastFormat, called this \"chevron hell\". +iostreams don\'t support positional arguments by design. + +The good part is that iostreams support user-defined types and are safe +although error handling is awkward. + +## Boost Format + +This is a very powerful library that supports both `printf`-like format +strings and positional arguments. Its main drawback is performance. +According to various benchmarks, it is much slower than other methods +considered here. Boost Format also has excessive build times and severe +code bloat issues (see [Benchmarks](#benchmarks)). + +## FastFormat + +This is an interesting library that is fast, safe and has positional +arguments. However, it has significant limitations, citing its author: + +> Three features that have no hope of being accommodated within the +> current design are: +> +> - Leading zeros (or any other non-space padding) +> - Octal/hexadecimal encoding +> - Runtime width/alignment specification + +It is also quite big and has a heavy dependency, on STLSoft, which might be +too restrictive for use in some projects. + +## Boost Spirit.Karma + +This is not a formatting library but I decided to include it here for +completeness. As iostreams, it suffers from the problem of mixing +verbatim text with arguments. The library is pretty fast, but slower on +integer formatting than `fmt::format_to` with format string compilation +on Karma\'s own benchmark, see [Converting a hundred million integers to +strings per +second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html). + +# License + +{fmt} is distributed under the MIT +[license](https://github.com/fmtlib/fmt/blob/master/LICENSE). + +# Documentation License + +The [Format String Syntax](https://fmt.dev/latest/syntax/) section +in the documentation is based on the one from Python [string module +documentation](https://docs.python.org/3/library/string.html#module-string). +For this reason, the documentation is distributed under the Python +Software Foundation license available in +[doc/python-license.txt](https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt). +It only applies if you distribute the documentation of {fmt}. + +# Maintainers + +The {fmt} library is maintained by Victor Zverovich +([vitaut](https://github.com/vitaut)) with contributions from many other +people. See +[Contributors](https://github.com/fmtlib/fmt/graphs/contributors) and +[Releases](https://github.com/fmtlib/fmt/releases) for some of the +names. Let us know if your contribution is not listed or mentioned +incorrectly and we\'ll make it right. + +# Security Policy + +To report a security issue, please disclose it at [security +advisory](https://github.com/fmtlib/fmt/security/advisories/new). + +This project is maintained by a team of volunteers on a +reasonable-effort basis. As such, please give us at least *90* days to +work on a fix before public exposure. diff --git a/Engine/lib/openal-soft/fmt-11.1.1/doc/ChangeLog-old.md b/Engine/lib/openal-soft/fmt-11.1.1/doc/ChangeLog-old.md new file mode 100644 index 000000000..3f31d1e94 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/doc/ChangeLog-old.md @@ -0,0 +1,3290 @@ +# 7.1.3 - 2020-11-24 + +- Fixed handling of buffer boundaries in `format_to_n` + (https://github.com/fmtlib/fmt/issues/1996, + https://github.com/fmtlib/fmt/issues/2029). +- Fixed linkage errors when linking with a shared library + (https://github.com/fmtlib/fmt/issues/2011). +- Reintroduced ostream support to range formatters + (https://github.com/fmtlib/fmt/issues/2014). +- Worked around an issue with mixing std versions in gcc + (https://github.com/fmtlib/fmt/issues/2017). + +# 7.1.2 - 2020-11-04 + +- Fixed floating point formatting with large precision + (https://github.com/fmtlib/fmt/issues/1976). + +# 7.1.1 - 2020-11-01 + +- Fixed ABI compatibility with 7.0.x + (https://github.com/fmtlib/fmt/issues/1961). +- Added the `FMT_ARM_ABI_COMPATIBILITY` macro to work around ABI + incompatibility between GCC and Clang on ARM + (https://github.com/fmtlib/fmt/issues/1919). +- Worked around a SFINAE bug in GCC 8 + (https://github.com/fmtlib/fmt/issues/1957). +- Fixed linkage errors when building with GCC\'s LTO + (https://github.com/fmtlib/fmt/issues/1955). +- Fixed a compilation error when building without `__builtin_clz` or + equivalent (https://github.com/fmtlib/fmt/pull/1968). + Thanks @tohammer. +- Fixed a sign conversion warning + (https://github.com/fmtlib/fmt/pull/1964). Thanks @OptoCloud. + +# 7.1.0 - 2020-10-25 + +- Switched from + [Grisu3](https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf) + to [Dragonbox](https://github.com/jk-jeon/dragonbox) for the default + floating-point formatting which gives the shortest decimal + representation with round-trip guarantee and correct rounding + (https://github.com/fmtlib/fmt/pull/1882, + https://github.com/fmtlib/fmt/pull/1887, + https://github.com/fmtlib/fmt/pull/1894). This makes {fmt} + up to 20-30x faster than common implementations of + `std::ostringstream` and `sprintf` on + [dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark) and + faster than double-conversion and Ryū: + + ![](https://user-images.githubusercontent.com/576385/95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png) + + It is possible to get even better performance at the cost of larger + binary size by compiling with the `FMT_USE_FULL_CACHE_DRAGONBOX` + macro set to 1. + + Thanks @jk-jeon. + +- Added an experimental unsynchronized file output API which, together + with [format string + compilation](https://fmt.dev/latest/api.html#compile-api), can give + [5-9 times speed up compared to + fprintf](https://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html) + on common platforms ([godbolt](https://godbolt.org/z/nsTcG8)): + + ```c++ + #include + + int main() { + auto f = fmt::output_file("guide"); + f.print("The answer is {}.", 42); + } + ``` + +- Added a formatter for `std::chrono::time_point` + (https://github.com/fmtlib/fmt/issues/1819, + https://github.com/fmtlib/fmt/pull/1837). For example + ([godbolt](https://godbolt.org/z/c4M6fh)): + + ```c++ + #include + + int main() { + auto now = std::chrono::system_clock::now(); + fmt::print("The time is {:%H:%M:%S}.\n", now); + } + ``` + + Thanks @adamburgess. + +- Added support for ranges with non-const `begin`/`end` to `fmt::join` + (https://github.com/fmtlib/fmt/issues/1784, + https://github.com/fmtlib/fmt/pull/1786). For example + ([godbolt](https://godbolt.org/z/jP63Tv)): + + ```c++ + #include + #include + + int main() { + using std::literals::string_literals::operator""s; + auto strs = std::array{"a"s, "bb"s, "ccc"s}; + auto range = strs | ranges::views::filter( + [] (const std::string &x) { return x.size() != 2; } + ); + fmt::print("{}\n", fmt::join(range, "")); + } + ``` + + prints \"accc\". + + Thanks @tonyelewis. + +- Added a `memory_buffer::append` overload that takes a range + (https://github.com/fmtlib/fmt/pull/1806). Thanks @BRevzin. + +- Improved handling of single code units in `FMT_COMPILE`. For + example: + + ```c++ + #include + + char* f(char* buf) { + return fmt::format_to(buf, FMT_COMPILE("x{}"), 42); + } + ``` + + compiles to just ([godbolt](https://godbolt.org/z/5vncz3)): + + ```asm + _Z1fPc: + movb $120, (%rdi) + xorl %edx, %edx + cmpl $42, _ZN3fmt2v76detail10basic_dataIvE23zero_or_powers_of_10_32E+8(%rip) + movl $3, %eax + seta %dl + subl %edx, %eax + movzwl _ZN3fmt2v76detail10basic_dataIvE6digitsE+84(%rip), %edx + cltq + addq %rdi, %rax + movw %dx, -2(%rax) + ret + ``` + + Here a single `mov` instruction writes `'x'` (`$120`) to the output + buffer. + +- Added dynamic width support to format string compilation + (https://github.com/fmtlib/fmt/issues/1809). + +- Improved error reporting for unformattable types: now you\'ll get + the type name directly in the error message instead of the note: + + ```c++ + #include + + struct how_about_no {}; + + int main() { + fmt::print("{}", how_about_no()); + } + ``` + + Error ([godbolt](https://godbolt.org/z/GoxM4e)): + + `fmt/core.h:1438:3: error: static_assert failed due to requirement 'fmt::v7::formattable()' "Cannot format an argument. To make type T formattable provide a formatter specialization: https://fmt.dev/latest/api.html#udt" ...` + +- Added the + [make_args_checked](https://fmt.dev/7.1.0/api.html#argument-lists) + function template that allows you to write formatting functions with + compile-time format string checks and avoid binary code bloat + ([godbolt](https://godbolt.org/z/PEf9qr)): + + ```c++ + void vlog(const char* file, int line, fmt::string_view format, + fmt::format_args args) { + fmt::print("{}: {}: ", file, line); + fmt::vprint(format, args); + } + + template + void log(const char* file, int line, const S& format, Args&&... args) { + vlog(file, line, format, + fmt::make_args_checked(format, args...)); + } + + #define MY_LOG(format, ...) \ + log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__) + + MY_LOG("invalid squishiness: {}", 42); + ``` + +- Replaced `snprintf` fallback with a faster internal IEEE 754 `float` + and `double` formatter for arbitrary precision. For example + ([godbolt](https://godbolt.org/z/dPhWvj)): + + ```c++ + #include + + int main() { + fmt::print("{:.500}\n", 4.9406564584124654E-324); + } + ``` + + prints + + `4.9406564584124654417656879286822137236505980261432476442558568250067550727020875186529983636163599237979656469544571773092665671035593979639877479601078187812630071319031140452784581716784898210368871863605699873072305000638740915356498438731247339727316961514003171538539807412623856559117102665855668676818703956031062493194527159149245532930545654440112748012970999954193198940908041656332452475714786901472678015935523861155013480352649347201937902681071074917033322268447533357208324319360923829e-324`. + +- Made `format_to_n` and `formatted_size` part of the [core + API](https://fmt.dev/latest/api.html#core-api) + ([godbolt](https://godbolt.org/z/sPjY1K)): + + ```c++ + #include + + int main() { + char buffer[10]; + auto result = fmt::format_to_n(buffer, sizeof(buffer), "{}", 42); + } + ``` + +- Added `fmt::format_to_n` overload with format string compilation + (https://github.com/fmtlib/fmt/issues/1764, + https://github.com/fmtlib/fmt/pull/1767, + https://github.com/fmtlib/fmt/pull/1869). For example + ([godbolt](https://godbolt.org/z/93h86q)): + + ```c++ + #include + + int main() { + char buffer[8]; + fmt::format_to_n(buffer, sizeof(buffer), FMT_COMPILE("{}"), 42); + } + ``` + + Thanks @Kurkin and @alexezeder. + +- Added `fmt::format_to` overload that take `text_style` + (https://github.com/fmtlib/fmt/issues/1593, + https://github.com/fmtlib/fmt/issues/1842, + https://github.com/fmtlib/fmt/pull/1843). For example + ([godbolt](https://godbolt.org/z/91153r)): + + ```c++ + #include + + int main() { + std::string out; + fmt::format_to(std::back_inserter(out), + fmt::emphasis::bold | fg(fmt::color::red), + "The answer is {}.", 42); + } + ``` + + Thanks @Naios. + +- Made the `'#'` specifier emit trailing zeros in addition to the + decimal point (https://github.com/fmtlib/fmt/issues/1797). + For example ([godbolt](https://godbolt.org/z/bhdcW9)): + + ```c++ + #include + + int main() { + fmt::print("{:#.2g}", 0.5); + } + ``` + + prints `0.50`. + +- Changed the default floating point format to not include `.0` for + consistency with `std::format` and `std::to_chars` + (https://github.com/fmtlib/fmt/issues/1893, + https://github.com/fmtlib/fmt/issues/1943). It is possible + to get the decimal point and trailing zero with the `#` specifier. + +- Fixed an issue with floating-point formatting that could result in + addition of a non-significant trailing zero in rare cases e.g. + `1.00e-34` instead of `1.0e-34` + (https://github.com/fmtlib/fmt/issues/1873, + https://github.com/fmtlib/fmt/issues/1917). + +- Made `fmt::to_string` fallback on `ostream` insertion operator if + the `formatter` specialization is not provided + (https://github.com/fmtlib/fmt/issues/1815, + https://github.com/fmtlib/fmt/pull/1829). Thanks @alexezeder. + +- Added support for the append mode to the experimental file API and + improved `fcntl.h` detection. + (https://github.com/fmtlib/fmt/pull/1847, + https://github.com/fmtlib/fmt/pull/1848). Thanks @t-wiser. + +- Fixed handling of types that have both an implicit conversion + operator and an overloaded `ostream` insertion operator + (https://github.com/fmtlib/fmt/issues/1766). + +- Fixed a slicing issue in an internal iterator type + (https://github.com/fmtlib/fmt/pull/1822). Thanks @BRevzin. + +- Fixed an issue in locale-specific integer formatting + (https://github.com/fmtlib/fmt/issues/1927). + +- Fixed handling of exotic code unit types + (https://github.com/fmtlib/fmt/issues/1870, + https://github.com/fmtlib/fmt/issues/1932). + +- Improved `FMT_ALWAYS_INLINE` + (https://github.com/fmtlib/fmt/pull/1878). Thanks @jk-jeon. + +- Removed dependency on `windows.h` + (https://github.com/fmtlib/fmt/pull/1900). Thanks @bernd5. + +- Optimized counting of decimal digits on MSVC + (https://github.com/fmtlib/fmt/pull/1890). Thanks @mwinterb. + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/1772, + https://github.com/fmtlib/fmt/pull/1775, + https://github.com/fmtlib/fmt/pull/1792, + https://github.com/fmtlib/fmt/pull/1838, + https://github.com/fmtlib/fmt/pull/1888, + https://github.com/fmtlib/fmt/pull/1918, + https://github.com/fmtlib/fmt/pull/1939). + Thanks @leolchat, @pepsiman, @Klaim, @ravijanjam, @francesco-st and @udnaan. + +- Added the `FMT_REDUCE_INT_INSTANTIATIONS` CMake option that reduces + the binary code size at the cost of some integer formatting + performance. This can be useful for extremely memory-constrained + embedded systems + (https://github.com/fmtlib/fmt/issues/1778, + https://github.com/fmtlib/fmt/pull/1781). Thanks @kammce. + +- Added the `FMT_USE_INLINE_NAMESPACES` macro to control usage of + inline namespaces + (https://github.com/fmtlib/fmt/pull/1945). Thanks @darklukee. + +- Improved build configuration + (https://github.com/fmtlib/fmt/pull/1760, + https://github.com/fmtlib/fmt/pull/1770, + https://github.com/fmtlib/fmt/issues/1779, + https://github.com/fmtlib/fmt/pull/1783, + https://github.com/fmtlib/fmt/pull/1823). + Thanks @dvetutnev, @xvitaly, @tambry, @medithe and @martinwuehrer. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/pull/1790, + https://github.com/fmtlib/fmt/pull/1802, + https://github.com/fmtlib/fmt/pull/1808, + https://github.com/fmtlib/fmt/issues/1810, + https://github.com/fmtlib/fmt/issues/1811, + https://github.com/fmtlib/fmt/pull/1812, + https://github.com/fmtlib/fmt/pull/1814, + https://github.com/fmtlib/fmt/pull/1816, + https://github.com/fmtlib/fmt/pull/1817, + https://github.com/fmtlib/fmt/pull/1818, + https://github.com/fmtlib/fmt/issues/1825, + https://github.com/fmtlib/fmt/pull/1836, + https://github.com/fmtlib/fmt/pull/1855, + https://github.com/fmtlib/fmt/pull/1856, + https://github.com/fmtlib/fmt/pull/1860, + https://github.com/fmtlib/fmt/pull/1877, + https://github.com/fmtlib/fmt/pull/1879, + https://github.com/fmtlib/fmt/pull/1880, + https://github.com/fmtlib/fmt/issues/1896, + https://github.com/fmtlib/fmt/pull/1897, + https://github.com/fmtlib/fmt/pull/1898, + https://github.com/fmtlib/fmt/issues/1904, + https://github.com/fmtlib/fmt/pull/1908, + https://github.com/fmtlib/fmt/issues/1911, + https://github.com/fmtlib/fmt/issues/1912, + https://github.com/fmtlib/fmt/issues/1928, + https://github.com/fmtlib/fmt/pull/1929, + https://github.com/fmtlib/fmt/issues/1935, + https://github.com/fmtlib/fmt/pull/1937, + https://github.com/fmtlib/fmt/pull/1942, + https://github.com/fmtlib/fmt/issues/1949). + Thanks @TheQwertiest, @medithe, @martinwuehrer, @n16h7hunt3r, @Othereum, + @gsjaardema, @AlexanderLanin, @gcerretani, @chronoxor, @noizefloor, + @akohlmey, @jk-jeon, @rimathia, @rglarix, @moiwi, @heckad, @MarcDirven. + @BartSiwek and @darklukee. + +# 7.0.3 - 2020-08-06 + +- Worked around broken `numeric_limits` for 128-bit integers + (https://github.com/fmtlib/fmt/issues/1787). +- Added error reporting on missing named arguments + (https://github.com/fmtlib/fmt/issues/1796). +- Stopped using 128-bit integers with clang-cl + (https://github.com/fmtlib/fmt/pull/1800). Thanks @Kingcom. +- Fixed issues in locale-specific integer formatting + (https://github.com/fmtlib/fmt/issues/1782, + https://github.com/fmtlib/fmt/issues/1801). + +# 7.0.2 - 2020-07-29 + +- Worked around broken `numeric_limits` for 128-bit integers + (https://github.com/fmtlib/fmt/issues/1725). +- Fixed compatibility with CMake 3.4 + (https://github.com/fmtlib/fmt/issues/1779). +- Fixed handling of digit separators in locale-specific formatting + (https://github.com/fmtlib/fmt/issues/1782). + +# 7.0.1 - 2020-07-07 + +- Updated the inline version namespace name. +- Worked around a gcc bug in mangling of alias templates + (https://github.com/fmtlib/fmt/issues/1753). +- Fixed a linkage error on Windows + (https://github.com/fmtlib/fmt/issues/1757). Thanks @Kurkin. +- Fixed minor issues with the documentation. + +# 7.0.0 - 2020-07-05 + +- Reduced the library size. For example, on macOS a stripped test + binary statically linked with {fmt} [shrank from \~368k to less than + 100k](http://www.zverovich.net/2020/05/21/reducing-library-size.html). + +- Added a simpler and more efficient [format string compilation + API](https://fmt.dev/7.0.0/api.html#compile-api): + + ```c++ + #include + + // Converts 42 into std::string using the most efficient method and no + // runtime format string processing. + std::string s = fmt::format(FMT_COMPILE("{}"), 42); + ``` + + The old `fmt::compile` API is now deprecated. + +- Optimized integer formatting: `format_to` with format string + compilation and a stack-allocated buffer is now [faster than + to_chars on both libc++ and + libstdc++](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html). + +- Optimized handling of small format strings. For example, + + ```c++ + fmt::format("Result: {}: ({},{},{},{})", str1, str2, str3, str4, str5) + ``` + + is now \~40% faster + (https://github.com/fmtlib/fmt/issues/1685). + +- Applied extern templates to improve compile times when using the + core API and `fmt/format.h` + (https://github.com/fmtlib/fmt/issues/1452). For example, + on macOS with clang the compile time of a test translation unit + dropped from 2.3s to 0.3s with `-O2` and from 0.6s to 0.3s with the + default settings (`-O0`). + + Before (`-O2`): + + % time c++ -c test.cc -I include -std=c++17 -O2 + c++ -c test.cc -I include -std=c++17 -O2 2.22s user 0.08s system 99% cpu 2.311 total + + After (`-O2`): + + % time c++ -c test.cc -I include -std=c++17 -O2 + c++ -c test.cc -I include -std=c++17 -O2 0.26s user 0.04s system 98% cpu 0.303 total + + Before (default): + + % time c++ -c test.cc -I include -std=c++17 + c++ -c test.cc -I include -std=c++17 0.53s user 0.06s system 98% cpu 0.601 total + + After (default): + + % time c++ -c test.cc -I include -std=c++17 + c++ -c test.cc -I include -std=c++17 0.24s user 0.06s system 98% cpu 0.301 total + + It is still recommended to use `fmt/core.h` instead of + `fmt/format.h` but the compile time difference is now smaller. + Thanks @alex3d for the suggestion. + +- Named arguments are now stored on stack (no dynamic memory + allocations) and the compiled code is more compact and efficient. + For example + + ```c++ + #include + + int main() { + fmt::print("The answer is {answer}\n", fmt::arg("answer", 42)); + } + ``` + + compiles to just ([godbolt](https://godbolt.org/z/NcfEp_)) + + ```asm + .LC0: + .string "answer" + .LC1: + .string "The answer is {answer}\n" + main: + sub rsp, 56 + mov edi, OFFSET FLAT:.LC1 + mov esi, 23 + movabs rdx, 4611686018427387905 + lea rax, [rsp+32] + lea rcx, [rsp+16] + mov QWORD PTR [rsp+8], 1 + mov QWORD PTR [rsp], rax + mov DWORD PTR [rsp+16], 42 + mov QWORD PTR [rsp+32], OFFSET FLAT:.LC0 + mov DWORD PTR [rsp+40], 0 + call fmt::v6::vprint(fmt::v6::basic_string_view, + fmt::v6::format_args) + xor eax, eax + add rsp, 56 + ret + + .L.str.1: + .asciz "answer" + ``` + +- Implemented compile-time checks for dynamic width and precision + (https://github.com/fmtlib/fmt/issues/1614): + + ```c++ + #include + + int main() { + fmt::print(FMT_STRING("{0:{1}}"), 42); + } + ``` + + now gives a compilation error because argument 1 doesn\'t exist: + + In file included from test.cc:1: + include/fmt/format.h:2726:27: error: constexpr variable 'invalid_format' must be + initialized by a constant expression + FMT_CONSTEXPR_DECL bool invalid_format = + ^ + ... + include/fmt/core.h:569:26: note: in call to + '&checker(s, {}).context_->on_error(&"argument not found"[0])' + if (id >= num_args_) on_error("argument not found"); + ^ + +- Added sentinel support to `fmt::join` + (https://github.com/fmtlib/fmt/pull/1689) + + ```c++ + struct zstring_sentinel {}; + bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; } + bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; } + + struct zstring { + const char* p; + const char* begin() const { return p; } + zstring_sentinel end() const { return {}; } + }; + + auto s = fmt::format("{}", fmt::join(zstring{"hello"}, "_")); + // s == "h_e_l_l_o" + ``` + + Thanks @BRevzin. + +- Added support for named arguments, `clear` and `reserve` to + `dynamic_format_arg_store` + (https://github.com/fmtlib/fmt/issues/1655, + https://github.com/fmtlib/fmt/pull/1663, + https://github.com/fmtlib/fmt/pull/1674, + https://github.com/fmtlib/fmt/pull/1677). Thanks @vsolontsov-ll. + +- Added support for the `'c'` format specifier to integral types for + compatibility with `std::format` + (https://github.com/fmtlib/fmt/issues/1652). + +- Replaced the `'n'` format specifier with `'L'` for compatibility + with `std::format` + (https://github.com/fmtlib/fmt/issues/1624). The `'n'` + specifier can be enabled via the `FMT_DEPRECATED_N_SPECIFIER` macro. + +- The `'='` format specifier is now disabled by default for + compatibility with `std::format`. It can be enabled via the + `FMT_DEPRECATED_NUMERIC_ALIGN` macro. + +- Removed the following deprecated APIs: + + - `FMT_STRING_ALIAS` and `fmt` macros - replaced by `FMT_STRING` + - `fmt::basic_string_view::char_type` - replaced by + `fmt::basic_string_view::value_type` + - `convert_to_int` + - `format_arg_store::types` + - `*parse_context` - replaced by `*format_parse_context` + - `FMT_DEPRECATED_INCLUDE_OS` + - `FMT_DEPRECATED_PERCENT` - incompatible with `std::format` + - `*writer` - replaced by compiled format API + +- Renamed the `internal` namespace to `detail` + (https://github.com/fmtlib/fmt/issues/1538). The former is + still provided as an alias if the `FMT_USE_INTERNAL` macro is + defined. + +- Improved compatibility between `fmt::printf` with the standard specs + (https://github.com/fmtlib/fmt/issues/1595, + https://github.com/fmtlib/fmt/pull/1682, + https://github.com/fmtlib/fmt/pull/1683, + https://github.com/fmtlib/fmt/pull/1687, + https://github.com/fmtlib/fmt/pull/1699). Thanks @rimathia. + +- Fixed handling of `operator<<` overloads that use `copyfmt` + (https://github.com/fmtlib/fmt/issues/1666). + +- Added the `FMT_OS` CMake option to control inclusion of OS-specific + APIs in the fmt target. This can be useful for embedded platforms + (https://github.com/fmtlib/fmt/issues/1654, + https://github.com/fmtlib/fmt/pull/1656). Thanks @kwesolowski. + +- Replaced `FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION` with the + `FMT_FUZZ` macro to prevent interfering with fuzzing of projects + using {fmt} (https://github.com/fmtlib/fmt/pull/1650). + Thanks @asraa. + +- Fixed compatibility with emscripten + (https://github.com/fmtlib/fmt/issues/1636, + https://github.com/fmtlib/fmt/pull/1637). Thanks @ArthurSonzogni. + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/704, + https://github.com/fmtlib/fmt/pull/1643, + https://github.com/fmtlib/fmt/pull/1660, + https://github.com/fmtlib/fmt/pull/1681, + https://github.com/fmtlib/fmt/pull/1691, + https://github.com/fmtlib/fmt/pull/1706, + https://github.com/fmtlib/fmt/pull/1714, + https://github.com/fmtlib/fmt/pull/1721, + https://github.com/fmtlib/fmt/pull/1739, + https://github.com/fmtlib/fmt/pull/1740, + https://github.com/fmtlib/fmt/pull/1741, + https://github.com/fmtlib/fmt/pull/1751). + Thanks @senior7515, @lsr0, @puetzk, @fpelliccioni, Alexey Kuzmenko, @jelly, + @claremacrae, @jiapengwen, @gsjaardema and @alexey-milovidov. + +- Implemented various build configuration fixes and improvements + (https://github.com/fmtlib/fmt/pull/1603, + https://github.com/fmtlib/fmt/pull/1657, + https://github.com/fmtlib/fmt/pull/1702, + https://github.com/fmtlib/fmt/pull/1728). + Thanks @scramsby, @jtojnar, @orivej and @flagarde. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/pull/1616, + https://github.com/fmtlib/fmt/issues/1620, + https://github.com/fmtlib/fmt/issues/1622, + https://github.com/fmtlib/fmt/issues/1625, + https://github.com/fmtlib/fmt/pull/1627, + https://github.com/fmtlib/fmt/issues/1628, + https://github.com/fmtlib/fmt/pull/1629, + https://github.com/fmtlib/fmt/issues/1631, + https://github.com/fmtlib/fmt/pull/1633, + https://github.com/fmtlib/fmt/pull/1649, + https://github.com/fmtlib/fmt/issues/1658, + https://github.com/fmtlib/fmt/pull/1661, + https://github.com/fmtlib/fmt/pull/1667, + https://github.com/fmtlib/fmt/issues/1668, + https://github.com/fmtlib/fmt/pull/1669, + https://github.com/fmtlib/fmt/issues/1692, + https://github.com/fmtlib/fmt/pull/1696, + https://github.com/fmtlib/fmt/pull/1697, + https://github.com/fmtlib/fmt/issues/1707, + https://github.com/fmtlib/fmt/pull/1712, + https://github.com/fmtlib/fmt/pull/1716, + https://github.com/fmtlib/fmt/pull/1722, + https://github.com/fmtlib/fmt/issues/1724, + https://github.com/fmtlib/fmt/pull/1729, + https://github.com/fmtlib/fmt/pull/1738, + https://github.com/fmtlib/fmt/issues/1742, + https://github.com/fmtlib/fmt/issues/1743, + https://github.com/fmtlib/fmt/pull/1744, + https://github.com/fmtlib/fmt/issues/1747, + https://github.com/fmtlib/fmt/pull/1750). + Thanks @gsjaardema, @gabime, @johnor, @Kurkin, @invexed, @peterbell10, + @daixtrose, @petrutlucian94, @Neargye, @ambitslix, @gabime, @erthink, + @tohammer and @0x8000-0000. + +# 6.2.1 - 2020-05-09 + +- Fixed ostream support in `sprintf` + (https://github.com/fmtlib/fmt/issues/1631). +- Fixed type detection when using implicit conversion to `string_view` + and ostream `operator<<` inconsistently + (https://github.com/fmtlib/fmt/issues/1662). + +# 6.2.0 - 2020-04-05 + +- Improved error reporting when trying to format an object of a + non-formattable type: + + ```c++ + fmt::format("{}", S()); + ``` + + now gives: + + include/fmt/core.h:1015:5: error: static_assert failed due to requirement + 'formattable' "Cannot format argument. To make type T formattable provide a + formatter specialization: + https://fmt.dev/latest/api.html#formatting-user-defined-types" + static_assert( + ^ + ... + note: in instantiation of function template specialization + 'fmt::v6::format' requested here + fmt::format("{}", S()); + ^ + + if `S` is not formattable. + +- Reduced the library size by \~10%. + +- Always print decimal point if `#` is specified + (https://github.com/fmtlib/fmt/issues/1476, + https://github.com/fmtlib/fmt/issues/1498): + + ```c++ + fmt::print("{:#.0f}", 42.0); + ``` + + now prints `42.` + +- Implemented the `'L'` specifier for locale-specific numeric + formatting to improve compatibility with `std::format`. The `'n'` + specifier is now deprecated and will be removed in the next major + release. + +- Moved OS-specific APIs such as `windows_error` from `fmt/format.h` + to `fmt/os.h`. You can define `FMT_DEPRECATED_INCLUDE_OS` to + automatically include `fmt/os.h` from `fmt/format.h` for + compatibility but this will be disabled in the next major release. + +- Added precision overflow detection in floating-point formatting. + +- Implemented detection of invalid use of `fmt::arg`. + +- Used `type_identity` to block unnecessary template argument + deduction. Thanks Tim Song. + +- Improved UTF-8 handling + (https://github.com/fmtlib/fmt/issues/1109): + + ```c++ + fmt::print("┌{0:─^{2}}┐\n" + "│{1: ^{2}}│\n" + "└{0:─^{2}}┘\n", "", "Прывітанне, свет!", 21); + ``` + + now prints: + + ┌─────────────────────┐ + │ Прывітанне, свет! │ + └─────────────────────┘ + + on systems that support Unicode. + +- Added experimental dynamic argument storage + (https://github.com/fmtlib/fmt/issues/1170, + https://github.com/fmtlib/fmt/pull/1584): + + ```c++ + fmt::dynamic_format_arg_store store; + store.push_back("answer"); + store.push_back(42); + fmt::vprint("The {} is {}.\n", store); + ``` + + prints: + + The answer is 42. + + Thanks @vsolontsov-ll. + +- Made `fmt::join` accept `initializer_list` + (https://github.com/fmtlib/fmt/pull/1591). Thanks @Rapotkinnik. + +- Fixed handling of empty tuples + (https://github.com/fmtlib/fmt/issues/1588). + +- Fixed handling of output iterators in `format_to_n` + (https://github.com/fmtlib/fmt/issues/1506). + +- Fixed formatting of `std::chrono::duration` types to wide output + (https://github.com/fmtlib/fmt/pull/1533). Thanks @zeffy. + +- Added const `begin` and `end` overload to buffers + (https://github.com/fmtlib/fmt/pull/1553). Thanks @dominicpoeschko. + +- Added the ability to disable floating-point formatting via + `FMT_USE_FLOAT`, `FMT_USE_DOUBLE` and `FMT_USE_LONG_DOUBLE` macros + for extremely memory-constrained embedded system + (https://github.com/fmtlib/fmt/pull/1590). Thanks @albaguirre. + +- Made `FMT_STRING` work with `constexpr` `string_view` + (https://github.com/fmtlib/fmt/pull/1589). Thanks @scramsby. + +- Implemented a minor optimization in the format string parser + (https://github.com/fmtlib/fmt/pull/1560). Thanks @IkarusDeveloper. + +- Improved attribute detection + (https://github.com/fmtlib/fmt/pull/1469, + https://github.com/fmtlib/fmt/pull/1475, + https://github.com/fmtlib/fmt/pull/1576). + Thanks @federico-busato, @chronoxor and @refnum. + +- Improved documentation + (https://github.com/fmtlib/fmt/pull/1481, + https://github.com/fmtlib/fmt/pull/1523). + Thanks @JackBoosY and @imba-tjd. + +- Fixed symbol visibility on Linux when compiling with + `-fvisibility=hidden` + (https://github.com/fmtlib/fmt/pull/1535). Thanks @milianw. + +- Implemented various build configuration fixes and improvements + (https://github.com/fmtlib/fmt/issues/1264, + https://github.com/fmtlib/fmt/issues/1460, + https://github.com/fmtlib/fmt/pull/1534, + https://github.com/fmtlib/fmt/issues/1536, + https://github.com/fmtlib/fmt/issues/1545, + https://github.com/fmtlib/fmt/pull/1546, + https://github.com/fmtlib/fmt/issues/1566, + https://github.com/fmtlib/fmt/pull/1582, + https://github.com/fmtlib/fmt/issues/1597, + https://github.com/fmtlib/fmt/pull/1598). + Thanks @ambitslix, @jwillikers and @stac47. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/pull/1433, + https://github.com/fmtlib/fmt/issues/1461, + https://github.com/fmtlib/fmt/pull/1470, + https://github.com/fmtlib/fmt/pull/1480, + https://github.com/fmtlib/fmt/pull/1485, + https://github.com/fmtlib/fmt/pull/1492, + https://github.com/fmtlib/fmt/issues/1493, + https://github.com/fmtlib/fmt/issues/1504, + https://github.com/fmtlib/fmt/pull/1505, + https://github.com/fmtlib/fmt/pull/1512, + https://github.com/fmtlib/fmt/issues/1515, + https://github.com/fmtlib/fmt/pull/1516, + https://github.com/fmtlib/fmt/pull/1518, + https://github.com/fmtlib/fmt/pull/1519, + https://github.com/fmtlib/fmt/pull/1520, + https://github.com/fmtlib/fmt/pull/1521, + https://github.com/fmtlib/fmt/pull/1522, + https://github.com/fmtlib/fmt/issues/1524, + https://github.com/fmtlib/fmt/pull/1530, + https://github.com/fmtlib/fmt/issues/1531, + https://github.com/fmtlib/fmt/pull/1532, + https://github.com/fmtlib/fmt/issues/1539, + https://github.com/fmtlib/fmt/issues/1547, + https://github.com/fmtlib/fmt/issues/1548, + https://github.com/fmtlib/fmt/pull/1554, + https://github.com/fmtlib/fmt/issues/1567, + https://github.com/fmtlib/fmt/pull/1568, + https://github.com/fmtlib/fmt/pull/1569, + https://github.com/fmtlib/fmt/pull/1571, + https://github.com/fmtlib/fmt/pull/1573, + https://github.com/fmtlib/fmt/pull/1575, + https://github.com/fmtlib/fmt/pull/1581, + https://github.com/fmtlib/fmt/issues/1583, + https://github.com/fmtlib/fmt/issues/1586, + https://github.com/fmtlib/fmt/issues/1587, + https://github.com/fmtlib/fmt/issues/1594, + https://github.com/fmtlib/fmt/pull/1596, + https://github.com/fmtlib/fmt/issues/1604, + https://github.com/fmtlib/fmt/pull/1606, + https://github.com/fmtlib/fmt/issues/1607, + https://github.com/fmtlib/fmt/issues/1609). + Thanks @marti4d, @iPherian, @parkertomatoes, @gsjaardema, @chronoxor, + @DanielaE, @torsten48, @tohammer, @lefticus, @ryusakki, @adnsv, @fghzxm, + @refnum, @pramodk, @Spirrwell and @scramsby. + +# 6.1.2 - 2019-12-11 + +- Fixed ABI compatibility with `libfmt.so.6.0.0` + (https://github.com/fmtlib/fmt/issues/1471). +- Fixed handling types convertible to `std::string_view` + (https://github.com/fmtlib/fmt/pull/1451). Thanks @denizevrenci. +- Made CUDA test an opt-in enabled via the `FMT_CUDA_TEST` CMake + option. +- Fixed sign conversion warnings + (https://github.com/fmtlib/fmt/pull/1440). Thanks @0x8000-0000. + +# 6.1.1 - 2019-12-04 + +- Fixed shared library build on Windows + (https://github.com/fmtlib/fmt/pull/1443, + https://github.com/fmtlib/fmt/issues/1445, + https://github.com/fmtlib/fmt/pull/1446, + https://github.com/fmtlib/fmt/issues/1450). + Thanks @egorpugin and @bbolli. +- Added a missing decimal point in exponent notation with trailing + zeros. +- Removed deprecated `format_arg_store::TYPES`. + +# 6.1.0 - 2019-12-01 + +- {fmt} now formats IEEE 754 `float` and `double` using the shortest + decimal representation with correct rounding by default: + + ```c++ + #include + #include + + int main() { + fmt::print("{}", M_PI); + } + ``` + + prints `3.141592653589793`. + +- Made the fast binary to decimal floating-point formatter the + default, simplified it and improved performance. {fmt} is now 15 + times faster than libc++\'s `std::ostringstream`, 11 times faster + than `printf` and 10% faster than double-conversion on + [dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark): + + | Function | Time (ns) | Speedup | + | ------------- | --------: | ------: | + | ostringstream | 1,346.30 | 1.00x | + | ostrstream | 1,195.74 | 1.13x | + | sprintf | 995.08 | 1.35x | + | doubleconv | 99.10 | 13.59x | + | fmt | 88.34 | 15.24x | + + ![](https://user-images.githubusercontent.com/576385/69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png) + +- {fmt} no longer converts `float` arguments to `double`. In + particular this improves the default (shortest) representation of + floats and makes `fmt::format` consistent with `std::format` specs + (https://github.com/fmtlib/fmt/issues/1336, + https://github.com/fmtlib/fmt/issues/1353, + https://github.com/fmtlib/fmt/pull/1360, + https://github.com/fmtlib/fmt/pull/1361): + + ```c++ + fmt::print("{}", 0.1f); + ``` + + prints `0.1` instead of `0.10000000149011612`. + + Thanks @orivej. + +- Made floating-point formatting output consistent with + `printf`/iostreams + (https://github.com/fmtlib/fmt/issues/1376, + https://github.com/fmtlib/fmt/issues/1417). + +- Added support for 128-bit integers + (https://github.com/fmtlib/fmt/pull/1287): + + ```c++ + fmt::print("{}", std::numeric_limits<__int128_t>::max()); + ``` + + prints `170141183460469231731687303715884105727`. + + Thanks @denizevrenci. + +- The overload of `print` that takes `text_style` is now atomic, i.e. + the output from different threads doesn\'t interleave + (https://github.com/fmtlib/fmt/pull/1351). Thanks @tankiJong. + +- Made compile time in the header-only mode \~20% faster by reducing + the number of template instantiations. `wchar_t` overload of + `vprint` was moved from `fmt/core.h` to `fmt/format.h`. + +- Added an overload of `fmt::join` that works with tuples + (https://github.com/fmtlib/fmt/issues/1322, + https://github.com/fmtlib/fmt/pull/1330): + + ```c++ + #include + #include + + int main() { + std::tuple t{'a', 1, 2.0f}; + fmt::print("{}", t); + } + ``` + + prints `('a', 1, 2.0)`. + + Thanks @jeremyong. + +- Changed formatting of octal zero with prefix from \"00\" to \"0\": + + ```c++ + fmt::print("{:#o}", 0); + ``` + + prints `0`. + +- The locale is now passed to ostream insertion (`<<`) operators + (https://github.com/fmtlib/fmt/pull/1406): + + ```c++ + #include + #include + + struct S { + double value; + }; + + std::ostream& operator<<(std::ostream& os, S s) { + return os << s.value; + } + + int main() { + auto s = fmt::format(std::locale("fr_FR.UTF-8"), "{}", S{0.42}); + // s == "0,42" + } + ``` + + Thanks @dlaugt. + +- Locale-specific number formatting now uses grouping + (https://github.com/fmtlib/fmt/issues/1393, + https://github.com/fmtlib/fmt/pull/1394). Thanks @skrdaniel. + +- Fixed handling of types with deleted implicit rvalue conversion to + `const char**` (https://github.com/fmtlib/fmt/issues/1421): + + ```c++ + struct mystring { + operator const char*() const&; + operator const char*() &; + operator const char*() const&& = delete; + operator const char*() && = delete; + }; + mystring str; + fmt::print("{}", str); // now compiles + ``` + +- Enums are now mapped to correct underlying types instead of `int` + (https://github.com/fmtlib/fmt/pull/1286). Thanks @agmt. + +- Enum classes are no longer implicitly converted to `int` + (https://github.com/fmtlib/fmt/issues/1424). + +- Added `basic_format_parse_context` for consistency with C++20 + `std::format` and deprecated `basic_parse_context`. + +- Fixed handling of UTF-8 in precision + (https://github.com/fmtlib/fmt/issues/1389, + https://github.com/fmtlib/fmt/pull/1390). Thanks @tajtiattila. + +- {fmt} can now be installed on Linux, macOS and Windows with + [Conda](https://docs.conda.io/en/latest/) using its + [conda-forge](https://conda-forge.org) + [package](https://github.com/conda-forge/fmt-feedstock) + (https://github.com/fmtlib/fmt/pull/1410): + + conda install -c conda-forge fmt + + Thanks @tdegeus. + +- Added a CUDA test (https://github.com/fmtlib/fmt/pull/1285, + https://github.com/fmtlib/fmt/pull/1317). + Thanks @luncliff and @risa2000. + +- Improved documentation + (https://github.com/fmtlib/fmt/pull/1276, + https://github.com/fmtlib/fmt/issues/1291, + https://github.com/fmtlib/fmt/issues/1296, + https://github.com/fmtlib/fmt/pull/1315, + https://github.com/fmtlib/fmt/pull/1332, + https://github.com/fmtlib/fmt/pull/1337, + https://github.com/fmtlib/fmt/issues/1395 + https://github.com/fmtlib/fmt/pull/1418). + Thanks @waywardmonkeys, @pauldreik and @jackoalan. + +- Various code improvements + (https://github.com/fmtlib/fmt/pull/1358, + https://github.com/fmtlib/fmt/pull/1407). + Thanks @orivej and @dpacbach. + +- Fixed compile-time format string checks for user-defined types + (https://github.com/fmtlib/fmt/issues/1292). + +- Worked around a false positive in `unsigned-integer-overflow` sanitizer + (https://github.com/fmtlib/fmt/issues/1377). + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/1273, + https://github.com/fmtlib/fmt/pull/1278, + https://github.com/fmtlib/fmt/pull/1280, + https://github.com/fmtlib/fmt/issues/1281, + https://github.com/fmtlib/fmt/issues/1288, + https://github.com/fmtlib/fmt/pull/1290, + https://github.com/fmtlib/fmt/pull/1301, + https://github.com/fmtlib/fmt/issues/1305, + https://github.com/fmtlib/fmt/issues/1306, + https://github.com/fmtlib/fmt/issues/1309, + https://github.com/fmtlib/fmt/pull/1312, + https://github.com/fmtlib/fmt/issues/1313, + https://github.com/fmtlib/fmt/issues/1316, + https://github.com/fmtlib/fmt/issues/1319, + https://github.com/fmtlib/fmt/pull/1320, + https://github.com/fmtlib/fmt/pull/1326, + https://github.com/fmtlib/fmt/pull/1328, + https://github.com/fmtlib/fmt/issues/1344, + https://github.com/fmtlib/fmt/pull/1345, + https://github.com/fmtlib/fmt/pull/1347, + https://github.com/fmtlib/fmt/pull/1349, + https://github.com/fmtlib/fmt/issues/1354, + https://github.com/fmtlib/fmt/issues/1362, + https://github.com/fmtlib/fmt/issues/1366, + https://github.com/fmtlib/fmt/pull/1364, + https://github.com/fmtlib/fmt/pull/1370, + https://github.com/fmtlib/fmt/pull/1371, + https://github.com/fmtlib/fmt/issues/1385, + https://github.com/fmtlib/fmt/issues/1388, + https://github.com/fmtlib/fmt/pull/1397, + https://github.com/fmtlib/fmt/pull/1414, + https://github.com/fmtlib/fmt/pull/1416, + https://github.com/fmtlib/fmt/issues/1422 + https://github.com/fmtlib/fmt/pull/1427, + https://github.com/fmtlib/fmt/issues/1431, + https://github.com/fmtlib/fmt/pull/1433). + Thanks @hhb, @gsjaardema, @gabime, @neheb, @vedranmiletic, @dkavolis, + @mwinterb, @orivej, @denizevrenci, @leonklingele, @chronoxor, @kent-tri, + @0x8000-0000 and @marti4d. + +# 6.0.0 - 2019-08-26 + +- Switched to the [MIT license]( + https://github.com/fmtlib/fmt/blob/5a4b24613ba16cc689977c3b5bd8274a3ba1dd1f/LICENSE.rst) + with an optional exception that allows distributing binary code + without attribution. + +- Floating-point formatting is now locale-independent by default: + + ```c++ + #include + #include + + int main() { + std::locale::global(std::locale("ru_RU.UTF-8")); + fmt::print("value = {}", 4.2); + } + ``` + + prints \"value = 4.2\" regardless of the locale. + + For locale-specific formatting use the `n` specifier: + + ```c++ + std::locale::global(std::locale("ru_RU.UTF-8")); + fmt::print("value = {:n}", 4.2); + ``` + + prints \"value = 4,2\". + +- Added an experimental Grisu floating-point formatting algorithm + implementation (disabled by default). To enable it compile with the + `FMT_USE_GRISU` macro defined to 1: + + ```c++ + #define FMT_USE_GRISU 1 + #include + + auto s = fmt::format("{}", 4.2); // formats 4.2 using Grisu + ``` + + With Grisu enabled, {fmt} is 13x faster than `std::ostringstream` + (libc++) and 10x faster than `sprintf` on + [dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark) ([full + results](https://fmt.dev/unknown_mac64_clang10.0.html)): + + ![](https://user-images.githubusercontent.com/576385/54883977-9fe8c000-4e28-11e9-8bde-272d122e7c52.jpg) + +- Separated formatting and parsing contexts for consistency with + [C++20 std::format](http://eel.is/c++draft/format), removing the + undocumented `basic_format_context::parse_context()` function. + +- Added [oss-fuzz](https://github.com/google/oss-fuzz) support + (https://github.com/fmtlib/fmt/pull/1199). Thanks @pauldreik. + +- `formatter` specializations now always take precedence over + `operator<<` (https://github.com/fmtlib/fmt/issues/952): + + ```c++ + #include + #include + + struct S {}; + + std::ostream& operator<<(std::ostream& os, S) { + return os << 1; + } + + template <> + struct fmt::formatter : fmt::formatter { + auto format(S, format_context& ctx) { + return formatter::format(2, ctx); + } + }; + + int main() { + std::cout << S() << "\n"; // prints 1 using operator<< + fmt::print("{}\n", S()); // prints 2 using formatter + } + ``` + +- Introduced the experimental `fmt::compile` function that does format + string compilation + (https://github.com/fmtlib/fmt/issues/618, + https://github.com/fmtlib/fmt/issues/1169, + https://github.com/fmtlib/fmt/pull/1171): + + ```c++ + #include + + auto f = fmt::compile("{}"); + std::string s = fmt::format(f, 42); // can be called multiple times to + // format different values + // s == "42" + ``` + + It moves the cost of parsing a format string outside of the format + function which can be beneficial when identically formatting many + objects of the same types. Thanks @stryku. + +- Added experimental `%` format specifier that formats floating-point + values as percentages + (https://github.com/fmtlib/fmt/pull/1060, + https://github.com/fmtlib/fmt/pull/1069, + https://github.com/fmtlib/fmt/pull/1071): + + ```c++ + auto s = fmt::format("{:.1%}", 0.42); // s == "42.0%" + ``` + + Thanks @gawain-bolton. + +- Implemented precision for floating-point durations + (https://github.com/fmtlib/fmt/issues/1004, + https://github.com/fmtlib/fmt/pull/1012): + + ```c++ + auto s = fmt::format("{:.1}", std::chrono::duration(1.234)); + // s == 1.2s + ``` + + Thanks @DanielaE. + +- Implemented `chrono` format specifiers `%Q` and `%q` that give the + value and the unit respectively + (https://github.com/fmtlib/fmt/pull/1019): + + ```c++ + auto value = fmt::format("{:%Q}", 42s); // value == "42" + auto unit = fmt::format("{:%q}", 42s); // unit == "s" + ``` + + Thanks @DanielaE. + +- Fixed handling of dynamic width in chrono formatter: + + ```c++ + auto s = fmt::format("{0:{1}%H:%M:%S}", std::chrono::seconds(12345), 12); + // ^ width argument index ^ width + // s == "03:25:45 " + ``` + + Thanks Howard Hinnant. + +- Removed deprecated `fmt/time.h`. Use `fmt/chrono.h` instead. + +- Added `fmt::format` and `fmt::vformat` overloads that take + `text_style` (https://github.com/fmtlib/fmt/issues/993, + https://github.com/fmtlib/fmt/pull/994): + + ```c++ + #include + + std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), + "The answer is {}.", 42); + ``` + + Thanks @Naios. + +- Removed the deprecated color API (`print_colored`). Use the new API, + namely `print` overloads that take `text_style` instead. + +- Made `std::unique_ptr` and `std::shared_ptr` formattable as pointers + via `fmt::ptr` (https://github.com/fmtlib/fmt/pull/1121): + + ```c++ + std::unique_ptr p = ...; + fmt::print("{}", fmt::ptr(p)); // prints p as a pointer + ``` + + Thanks @sighingnow. + +- Made `print` and `vprint` report I/O errors + (https://github.com/fmtlib/fmt/issues/1098, + https://github.com/fmtlib/fmt/pull/1099). Thanks @BillyDonahue. + +- Marked deprecated APIs with the `[[deprecated]]` attribute and + removed internal uses of deprecated APIs + (https://github.com/fmtlib/fmt/pull/1022). Thanks @eliaskosunen. + +- Modernized the codebase using more C++11 features and removing + workarounds. Most importantly, `buffer_context` is now an alias + template, so use `buffer_context` instead of + `buffer_context::type`. These features require GCC 4.8 or later. + +- `formatter` specializations now always take precedence over implicit + conversions to `int` and the undocumented `convert_to_int` trait is + now deprecated. + +- Moved the undocumented `basic_writer`, `writer`, and `wwriter` types + to the `internal` namespace. + +- Removed deprecated `basic_format_context::begin()`. Use `out()` + instead. + +- Disallowed passing the result of `join` as an lvalue to prevent + misuse. + +- Refactored the undocumented structs that represent parsed format + specifiers to simplify the API and allow multibyte fill. + +- Moved SFINAE to template parameters to reduce symbol sizes. + +- Switched to `fputws` for writing wide strings so that it\'s no + longer required to call `_setmode` on Windows + (https://github.com/fmtlib/fmt/issues/1229, + https://github.com/fmtlib/fmt/pull/1243). Thanks @jackoalan. + +- Improved literal-based API + (https://github.com/fmtlib/fmt/pull/1254). Thanks @sylveon. + +- Added support for exotic platforms without `uintptr_t` such as IBM i + (AS/400) which has 128-bit pointers and only 64-bit integers + (https://github.com/fmtlib/fmt/issues/1059). + +- Added [Sublime Text syntax highlighting config]( + https://github.com/fmtlib/fmt/blob/master/support/C%2B%2B.sublime-syntax) + (https://github.com/fmtlib/fmt/issues/1037). Thanks @Kronuz. + +- Added the `FMT_ENFORCE_COMPILE_STRING` macro to enforce the use of + compile-time format strings + (https://github.com/fmtlib/fmt/pull/1231). Thanks @jackoalan. + +- Stopped setting `CMAKE_BUILD_TYPE` if {fmt} is a subproject + (https://github.com/fmtlib/fmt/issues/1081). + +- Various build improvements + (https://github.com/fmtlib/fmt/pull/1039, + https://github.com/fmtlib/fmt/pull/1078, + https://github.com/fmtlib/fmt/pull/1091, + https://github.com/fmtlib/fmt/pull/1103, + https://github.com/fmtlib/fmt/pull/1177). + Thanks @luncliff, @jasonszang, @olafhering, @Lecetem and @pauldreik. + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/1049, + https://github.com/fmtlib/fmt/pull/1051, + https://github.com/fmtlib/fmt/pull/1083, + https://github.com/fmtlib/fmt/pull/1113, + https://github.com/fmtlib/fmt/pull/1114, + https://github.com/fmtlib/fmt/issues/1146, + https://github.com/fmtlib/fmt/issues/1180, + https://github.com/fmtlib/fmt/pull/1250, + https://github.com/fmtlib/fmt/pull/1252, + https://github.com/fmtlib/fmt/pull/1265). + Thanks @mikelui, @foonathan, @BillyDonahue, @jwakely, @kaisbe and + @sdebionne. + +- Fixed ambiguous formatter specialization in `fmt/ranges.h` + (https://github.com/fmtlib/fmt/issues/1123). + +- Fixed formatting of a non-empty `std::filesystem::path` which is an + infinitely deep range of its components + (https://github.com/fmtlib/fmt/issues/1268). + +- Fixed handling of general output iterators when formatting + characters (https://github.com/fmtlib/fmt/issues/1056, + https://github.com/fmtlib/fmt/pull/1058). Thanks @abolz. + +- Fixed handling of output iterators in `formatter` specialization for + ranges (https://github.com/fmtlib/fmt/issues/1064). + +- Fixed handling of exotic character types + (https://github.com/fmtlib/fmt/issues/1188). + +- Made chrono formatting work with exceptions disabled + (https://github.com/fmtlib/fmt/issues/1062). + +- Fixed DLL visibility issues + (https://github.com/fmtlib/fmt/pull/1134, + https://github.com/fmtlib/fmt/pull/1147). Thanks @denchat. + +- Disabled the use of UDL template extension on GCC 9 + (https://github.com/fmtlib/fmt/issues/1148). + +- Removed misplaced `format` compile-time checks from `printf` + (https://github.com/fmtlib/fmt/issues/1173). + +- Fixed issues in the experimental floating-point formatter + (https://github.com/fmtlib/fmt/issues/1072, + https://github.com/fmtlib/fmt/issues/1129, + https://github.com/fmtlib/fmt/issues/1153, + https://github.com/fmtlib/fmt/pull/1155, + https://github.com/fmtlib/fmt/issues/1210, + https://github.com/fmtlib/fmt/issues/1222). Thanks @alabuzhev. + +- Fixed bugs discovered by fuzzing or during fuzzing integration + (https://github.com/fmtlib/fmt/issues/1124, + https://github.com/fmtlib/fmt/issues/1127, + https://github.com/fmtlib/fmt/issues/1132, + https://github.com/fmtlib/fmt/pull/1135, + https://github.com/fmtlib/fmt/issues/1136, + https://github.com/fmtlib/fmt/issues/1141, + https://github.com/fmtlib/fmt/issues/1142, + https://github.com/fmtlib/fmt/issues/1178, + https://github.com/fmtlib/fmt/issues/1179, + https://github.com/fmtlib/fmt/issues/1194). Thanks @pauldreik. + +- Fixed building tests on FreeBSD and Hurd + (https://github.com/fmtlib/fmt/issues/1043). Thanks @jackyf. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/pull/998, + https://github.com/fmtlib/fmt/pull/1006, + https://github.com/fmtlib/fmt/issues/1008, + https://github.com/fmtlib/fmt/issues/1011, + https://github.com/fmtlib/fmt/issues/1025, + https://github.com/fmtlib/fmt/pull/1027, + https://github.com/fmtlib/fmt/pull/1028, + https://github.com/fmtlib/fmt/pull/1029, + https://github.com/fmtlib/fmt/pull/1030, + https://github.com/fmtlib/fmt/pull/1031, + https://github.com/fmtlib/fmt/pull/1054, + https://github.com/fmtlib/fmt/issues/1063, + https://github.com/fmtlib/fmt/pull/1068, + https://github.com/fmtlib/fmt/pull/1074, + https://github.com/fmtlib/fmt/pull/1075, + https://github.com/fmtlib/fmt/pull/1079, + https://github.com/fmtlib/fmt/pull/1086, + https://github.com/fmtlib/fmt/issues/1088, + https://github.com/fmtlib/fmt/pull/1089, + https://github.com/fmtlib/fmt/pull/1094, + https://github.com/fmtlib/fmt/issues/1101, + https://github.com/fmtlib/fmt/pull/1102, + https://github.com/fmtlib/fmt/issues/1105, + https://github.com/fmtlib/fmt/pull/1107, + https://github.com/fmtlib/fmt/issues/1115, + https://github.com/fmtlib/fmt/issues/1117, + https://github.com/fmtlib/fmt/issues/1118, + https://github.com/fmtlib/fmt/issues/1120, + https://github.com/fmtlib/fmt/issues/1123, + https://github.com/fmtlib/fmt/pull/1139, + https://github.com/fmtlib/fmt/issues/1140, + https://github.com/fmtlib/fmt/issues/1143, + https://github.com/fmtlib/fmt/pull/1144, + https://github.com/fmtlib/fmt/pull/1150, + https://github.com/fmtlib/fmt/pull/1151, + https://github.com/fmtlib/fmt/issues/1152, + https://github.com/fmtlib/fmt/issues/1154, + https://github.com/fmtlib/fmt/issues/1156, + https://github.com/fmtlib/fmt/pull/1159, + https://github.com/fmtlib/fmt/issues/1175, + https://github.com/fmtlib/fmt/issues/1181, + https://github.com/fmtlib/fmt/issues/1186, + https://github.com/fmtlib/fmt/pull/1187, + https://github.com/fmtlib/fmt/pull/1191, + https://github.com/fmtlib/fmt/issues/1197, + https://github.com/fmtlib/fmt/issues/1200, + https://github.com/fmtlib/fmt/issues/1203, + https://github.com/fmtlib/fmt/issues/1205, + https://github.com/fmtlib/fmt/pull/1206, + https://github.com/fmtlib/fmt/issues/1213, + https://github.com/fmtlib/fmt/issues/1214, + https://github.com/fmtlib/fmt/pull/1217, + https://github.com/fmtlib/fmt/issues/1228, + https://github.com/fmtlib/fmt/pull/1230, + https://github.com/fmtlib/fmt/issues/1232, + https://github.com/fmtlib/fmt/pull/1235, + https://github.com/fmtlib/fmt/pull/1236, + https://github.com/fmtlib/fmt/issues/1240). + Thanks @DanielaE, @mwinterb, @eliaskosunen, @morinmorin, @ricco19, + @waywardmonkeys, @chronoxor, @remyabel, @pauldreik, @gsjaardema, @rcane, + @mocabe, @denchat, @cjdb, @HazardyKnusperkeks, @vedranmiletic, @jackoalan, + @DaanDeMeyer and @starkmapper. + +# 5.3.0 - 2018-12-28 + +- Introduced experimental chrono formatting support: + + ```c++ + #include + + int main() { + using namespace std::literals::chrono_literals; + fmt::print("Default format: {} {}\n", 42s, 100ms); + fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s); + } + ``` + + prints: + + Default format: 42s 100ms + strftime-like format: 03:15:30 + +- Added experimental support for emphasis (bold, italic, underline, + strikethrough), colored output to a file stream, and improved + colored formatting API + (https://github.com/fmtlib/fmt/pull/961, + https://github.com/fmtlib/fmt/pull/967, + https://github.com/fmtlib/fmt/pull/973): + + ```c++ + #include + + int main() { + fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, + "Hello, {}!\n", "world"); + fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) | + fmt::emphasis::underline, "Olá, {}!\n", "Mundo"); + fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, + "你好{}!\n", "世界"); + } + ``` + + prints the following on modern terminals with RGB color support: + + ![](https://github.com/fmtlib/fmt/assets/%0A576385/2a93c904-d6fa-4aa6-b453-2618e1c327d7) + + Thanks @Rakete1111. + +- Added support for 4-bit terminal colors + (https://github.com/fmtlib/fmt/issues/968, + https://github.com/fmtlib/fmt/pull/974) + + ```c++ + #include + + int main() { + print(fg(fmt::terminal_color::red), "stop\n"); + } + ``` + + Note that these colors vary by terminal: + + ![](https://user-images.githubusercontent.com/576385/50405925-dbfc7e00-0770-11e9-9b85-333fab0af9ac.png) + + Thanks @Rakete1111. + +- Parameterized formatting functions on the type of the format string + (https://github.com/fmtlib/fmt/issues/880, + https://github.com/fmtlib/fmt/pull/881, + https://github.com/fmtlib/fmt/pull/883, + https://github.com/fmtlib/fmt/pull/885, + https://github.com/fmtlib/fmt/pull/897, + https://github.com/fmtlib/fmt/issues/920). Any object of + type `S` that has an overloaded `to_string_view(const S&)` returning + `fmt::string_view` can be used as a format string: + + ```c++ + namespace my_ns { + inline string_view to_string_view(const my_string& s) { + return {s.data(), s.length()}; + } + } + + std::string message = fmt::format(my_string("The answer is {}."), 42); + ``` + + Thanks @DanielaE. + +- Made `std::string_view` work as a format string + (https://github.com/fmtlib/fmt/pull/898): + + ```c++ + auto message = fmt::format(std::string_view("The answer is {}."), 42); + ``` + + Thanks @DanielaE. + +- Added wide string support to compile-time format string checks + (https://github.com/fmtlib/fmt/pull/924): + + ```c++ + print(fmt(L"{:f}"), 42); // compile-time error: invalid type specifier + ``` + + Thanks @XZiar. + +- Made colored print functions work with wide strings + (https://github.com/fmtlib/fmt/pull/867): + + ```c++ + #include + + int main() { + print(fg(fmt::color::red), L"{}\n", 42); + } + ``` + + Thanks @DanielaE. + +- Introduced experimental Unicode support + (https://github.com/fmtlib/fmt/issues/628, + https://github.com/fmtlib/fmt/pull/891): + + ```c++ + using namespace fmt::literals; + auto s = fmt::format("{:*^5}"_u, "🤡"_u); // s == "**🤡**"_u + ``` + +- Improved locale support: + + ```c++ + #include + + struct numpunct : std::numpunct { + protected: + char do_thousands_sep() const override { return '~'; } + }; + + std::locale loc; + auto s = fmt::format(std::locale(loc, new numpunct()), "{:n}", 1234567); + // s == "1~234~567" + ``` + +- Constrained formatting functions on proper iterator types + (https://github.com/fmtlib/fmt/pull/921). Thanks @DanielaE. + +- Added `make_printf_args` and `make_wprintf_args` functions + (https://github.com/fmtlib/fmt/pull/934). Thanks @tnovotny. + +- Deprecated `fmt::visit`, `parse_context`, and `wparse_context`. Use + `fmt::visit_format_arg`, `format_parse_context`, and + `wformat_parse_context` instead. + +- Removed undocumented `basic_fixed_buffer` which has been superseded + by the iterator-based API + (https://github.com/fmtlib/fmt/issues/873, + https://github.com/fmtlib/fmt/pull/902). Thanks @superfunc. + +- Disallowed repeated leading zeros in an argument ID: + + ```c++ + fmt::print("{000}", 42); // error + ``` + +- Reintroduced support for gcc 4.4. + +- Fixed compilation on platforms with exotic `double` + (https://github.com/fmtlib/fmt/issues/878). + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/164, + https://github.com/fmtlib/fmt/issues/877, + https://github.com/fmtlib/fmt/pull/901, + https://github.com/fmtlib/fmt/pull/906, + https://github.com/fmtlib/fmt/pull/979). + Thanks @kookjr, @DarkDimius and @HecticSerenity. + +- Added pkgconfig support which makes it easier to consume the library + from meson and other build systems + (https://github.com/fmtlib/fmt/pull/916). Thanks @colemickens. + +- Various build improvements + (https://github.com/fmtlib/fmt/pull/909, + https://github.com/fmtlib/fmt/pull/926, + https://github.com/fmtlib/fmt/pull/937, + https://github.com/fmtlib/fmt/pull/953, + https://github.com/fmtlib/fmt/pull/959). + Thanks @tchaikov, @luncliff, @AndreasSchoenle, @hotwatermorning and @Zefz. + +- Improved `string_view` construction performance + (https://github.com/fmtlib/fmt/pull/914). Thanks @gabime. + +- Fixed non-matching char types + (https://github.com/fmtlib/fmt/pull/895). Thanks @DanielaE. + +- Fixed `format_to_n` with `std::back_insert_iterator` + (https://github.com/fmtlib/fmt/pull/913). Thanks @DanielaE. + +- Fixed locale-dependent formatting + (https://github.com/fmtlib/fmt/issues/905). + +- Fixed various compiler warnings and errors + (https://github.com/fmtlib/fmt/pull/882, + https://github.com/fmtlib/fmt/pull/886, + https://github.com/fmtlib/fmt/pull/933, + https://github.com/fmtlib/fmt/pull/941, + https://github.com/fmtlib/fmt/issues/931, + https://github.com/fmtlib/fmt/pull/943, + https://github.com/fmtlib/fmt/pull/954, + https://github.com/fmtlib/fmt/pull/956, + https://github.com/fmtlib/fmt/pull/962, + https://github.com/fmtlib/fmt/issues/965, + https://github.com/fmtlib/fmt/issues/977, + https://github.com/fmtlib/fmt/pull/983, + https://github.com/fmtlib/fmt/pull/989). + Thanks @Luthaf, @stevenhoving, @christinaa, @lgritz, @DanielaE, + @0x8000-0000 and @liuping1997. + +# 5.2.1 - 2018-09-21 + +- Fixed `visit` lookup issues on gcc 7 & 8 + (https://github.com/fmtlib/fmt/pull/870). Thanks @medithe. +- Fixed linkage errors on older gcc. +- Prevented `fmt/range.h` from specializing `fmt::basic_string_view` + (https://github.com/fmtlib/fmt/issues/865, + https://github.com/fmtlib/fmt/pull/868). Thanks @hhggit. +- Improved error message when formatting unknown types + (https://github.com/fmtlib/fmt/pull/872). Thanks @foonathan. +- Disabled templated user-defined literals when compiled under nvcc + (https://github.com/fmtlib/fmt/pull/875). Thanks @CandyGumdrop. +- Fixed `format_to` formatting to `wmemory_buffer` + (https://github.com/fmtlib/fmt/issues/874). + +# 5.2.0 - 2018-09-13 + +- Optimized format string parsing and argument processing which + resulted in up to 5x speed up on long format strings and significant + performance boost on various benchmarks. For example, version 5.2 is + 2.22x faster than 5.1 on decimal integer formatting with `format_to` + (macOS, clang-902.0.39.2): + + | Method | Time, s | Speedup | + | -------------------------- | --------------: | ------: | + | fmt::format 5.1 | 0.58 | | + | fmt::format 5.2 | 0.35 | 1.66x | + | fmt::format_to 5.1 | 0.51 | | + | fmt::format_to 5.2 | 0.23 | 2.22x | + | sprintf | 0.71 | | + | std::to_string | 1.01 | | + | std::stringstream | 1.73 | | + +- Changed the `fmt` macro from opt-out to opt-in to prevent name + collisions. To enable it define the `FMT_STRING_ALIAS` macro to 1 + before including `fmt/format.h`: + + ```c++ + #define FMT_STRING_ALIAS 1 + #include + std::string answer = format(fmt("{}"), 42); + ``` + +- Added compile-time format string checks to `format_to` overload that + takes `fmt::memory_buffer` + (https://github.com/fmtlib/fmt/issues/783): + + ```c++ + fmt::memory_buffer buf; + // Compile-time error: invalid type specifier. + fmt::format_to(buf, fmt("{:d}"), "foo"); + ``` + +- Moved experimental color support to `fmt/color.h` and enabled the + new API by default. The old API can be enabled by defining the + `FMT_DEPRECATED_COLORS` macro. + +- Added formatting support for types explicitly convertible to + `fmt::string_view`: + + ```c++ + struct foo { + explicit operator fmt::string_view() const { return "foo"; } + }; + auto s = format("{}", foo()); + ``` + + In particular, this makes formatting function work with + `folly::StringPiece`. + +- Implemented preliminary support for `char*_t` by replacing the + `format` function overloads with a single function template + parameterized on the string type. + +- Added support for dynamic argument lists + (https://github.com/fmtlib/fmt/issues/814, + https://github.com/fmtlib/fmt/pull/819). Thanks @MikePopoloski. + +- Reduced executable size overhead for embedded targets using newlib + nano by making locale dependency optional + (https://github.com/fmtlib/fmt/pull/839). Thanks @teajay-fr. + +- Keep `noexcept` specifier when exceptions are disabled + (https://github.com/fmtlib/fmt/issues/801, + https://github.com/fmtlib/fmt/pull/810). Thanks @qis. + +- Fixed formatting of user-defined types providing `operator<<` with + `format_to_n` (https://github.com/fmtlib/fmt/pull/806). + Thanks @mkurdej. + +- Fixed dynamic linkage of new symbols + (https://github.com/fmtlib/fmt/issues/808). + +- Fixed global initialization issue + (https://github.com/fmtlib/fmt/issues/807): + + ```c++ + // This works on compilers with constexpr support. + static const std::string answer = fmt::format("{}", 42); + ``` + +- Fixed various compiler warnings and errors + (https://github.com/fmtlib/fmt/pull/804, + https://github.com/fmtlib/fmt/issues/809, + https://github.com/fmtlib/fmt/pull/811, + https://github.com/fmtlib/fmt/issues/822, + https://github.com/fmtlib/fmt/pull/827, + https://github.com/fmtlib/fmt/issues/830, + https://github.com/fmtlib/fmt/pull/838, + https://github.com/fmtlib/fmt/issues/843, + https://github.com/fmtlib/fmt/pull/844, + https://github.com/fmtlib/fmt/issues/851, + https://github.com/fmtlib/fmt/pull/852, + https://github.com/fmtlib/fmt/pull/854). + Thanks @henryiii, @medithe, and @eliasdaler. + +# 5.1.0 - 2018-07-05 + +- Added experimental support for RGB color output enabled with the + `FMT_EXTENDED_COLORS` macro: + + ```c++ + #define FMT_EXTENDED_COLORS + #define FMT_HEADER_ONLY // or compile fmt with FMT_EXTENDED_COLORS defined + #include + + fmt::print(fmt::color::steel_blue, "Some beautiful text"); + ``` + + The old API (the `print_colored` and `vprint_colored` functions and + the `color` enum) is now deprecated. + (https://github.com/fmtlib/fmt/issues/762 + https://github.com/fmtlib/fmt/pull/767). thanks @Remotion. + +- Added quotes to strings in ranges and tuples + (https://github.com/fmtlib/fmt/pull/766). Thanks @Remotion. + +- Made `format_to` work with `basic_memory_buffer` + (https://github.com/fmtlib/fmt/issues/776). + +- Added `vformat_to_n` and `wchar_t` overload of `format_to_n` + (https://github.com/fmtlib/fmt/issues/764, + https://github.com/fmtlib/fmt/issues/769). + +- Made `is_range` and `is_tuple_like` part of public (experimental) + API to allow specialization for user-defined types + (https://github.com/fmtlib/fmt/issues/751, + https://github.com/fmtlib/fmt/pull/759). Thanks @drrlvn. + +- Added more compilers to continuous integration and increased + `FMT_PEDANTIC` warning levels + (https://github.com/fmtlib/fmt/pull/736). Thanks @eliaskosunen. + +- Fixed compilation with MSVC 2013. + +- Fixed handling of user-defined types in `format_to` + (https://github.com/fmtlib/fmt/issues/793). + +- Forced linking of inline `vformat` functions into the library + (https://github.com/fmtlib/fmt/issues/795). + +- Fixed incorrect call to on_align in `'{:}='` + (https://github.com/fmtlib/fmt/issues/750). + +- Fixed floating-point formatting to a non-back_insert_iterator with + sign & numeric alignment specified + (https://github.com/fmtlib/fmt/issues/756). + +- Fixed formatting to an array with `format_to_n` + (https://github.com/fmtlib/fmt/issues/778). + +- Fixed formatting of more than 15 named arguments + (https://github.com/fmtlib/fmt/issues/754). + +- Fixed handling of compile-time strings when including + `fmt/ostream.h`. (https://github.com/fmtlib/fmt/issues/768). + +- Fixed various compiler warnings and errors + (https://github.com/fmtlib/fmt/issues/742, + https://github.com/fmtlib/fmt/issues/748, + https://github.com/fmtlib/fmt/issues/752, + https://github.com/fmtlib/fmt/issues/770, + https://github.com/fmtlib/fmt/pull/775, + https://github.com/fmtlib/fmt/issues/779, + https://github.com/fmtlib/fmt/pull/780, + https://github.com/fmtlib/fmt/pull/790, + https://github.com/fmtlib/fmt/pull/792, + https://github.com/fmtlib/fmt/pull/800). + Thanks @Remotion, @gabime, @foonathan, @Dark-Passenger and @0x8000-0000. + +# 5.0.0 - 2018-05-21 + +- Added a requirement for partial C++11 support, most importantly + variadic templates and type traits, and dropped `FMT_VARIADIC_*` + emulation macros. Variadic templates are available since GCC 4.4, + Clang 2.9 and MSVC 18.0 (2013). For older compilers use {fmt} + [version 4.x](https://github.com/fmtlib/fmt/releases/tag/4.1.0) + which continues to be maintained and works with C++98 compilers. + +- Renamed symbols to follow standard C++ naming conventions and + proposed a subset of the library for standardization in [P0645R2 + Text Formatting](https://wg21.link/P0645). + +- Implemented `constexpr` parsing of format strings and [compile-time + format string + checks](https://fmt.dev/latest/api.html#compile-time-format-string-checks). + For example + + ```c++ + #include + + std::string s = format(fmt("{:d}"), "foo"); + ``` + + gives a compile-time error because `d` is an invalid specifier for + strings ([godbolt](https://godbolt.org/g/rnCy9Q)): + + ... + :4:19: note: in instantiation of function template specialization 'fmt::v5::format' requested here + std::string s = format(fmt("{:d}"), "foo"); + ^ + format.h:1337:13: note: non-constexpr function 'on_error' cannot be used in a constant expression + handler.on_error("invalid type specifier"); + + Compile-time checks require relaxed `constexpr` (C++14 feature) + support. If the latter is not available, checks will be performed at + runtime. + +- Separated format string parsing and formatting in the extension API + to enable compile-time format string processing. For example + + ```c++ + struct Answer {}; + + namespace fmt { + template <> + struct formatter { + constexpr auto parse(parse_context& ctx) { + auto it = ctx.begin(); + spec = *it; + if (spec != 'd' && spec != 's') + throw format_error("invalid specifier"); + return ++it; + } + + template + auto format(Answer, FormatContext& ctx) { + return spec == 's' ? + format_to(ctx.begin(), "{}", "fourty-two") : + format_to(ctx.begin(), "{}", 42); + } + + char spec = 0; + }; + } + + std::string s = format(fmt("{:x}"), Answer()); + ``` + + gives a compile-time error due to invalid format specifier + ([godbolt](https://godbolt.org/g/2jQ1Dv)): + + ... + :12:45: error: expression '' is not a constant expression + throw format_error("invalid specifier"); + +- Added [iterator + support](https://fmt.dev/latest/api.html#output-iterator-support): + + ```c++ + #include + #include + + std::vector out; + fmt::format_to(std::back_inserter(out), "{}", 42); + ``` + +- Added the + [format_to_n](https://fmt.dev/latest/api.html#_CPPv2N3fmt11format_to_nE8OutputItNSt6size_tE11string_viewDpRK4Args) + function that restricts the output to the specified number of + characters (https://github.com/fmtlib/fmt/issues/298): + + ```c++ + char out[4]; + fmt::format_to_n(out, sizeof(out), "{}", 12345); + // out == "1234" (without terminating '\0') + ``` + +- Added the [formatted_size]( + https://fmt.dev/latest/api.html#_CPPv2N3fmt14formatted_sizeE11string_viewDpRK4Args) + function for computing the output size: + + ```c++ + #include + + auto size = fmt::formatted_size("{}", 12345); // size == 5 + ``` + +- Improved compile times by reducing dependencies on standard headers + and providing a lightweight [core + API](https://fmt.dev/latest/api.html#core-api): + + ```c++ + #include + + fmt::print("The answer is {}.", 42); + ``` + + See [Compile time and code + bloat](https://github.com/fmtlib/fmt#compile-time-and-code-bloat). + +- Added the [make_format_args]( + https://fmt.dev/latest/api.html#_CPPv2N3fmt16make_format_argsEDpRK4Args) + function for capturing formatting arguments: + + ```c++ + // Prints formatted error message. + void vreport_error(const char *format, fmt::format_args args) { + fmt::print("Error: "); + fmt::vprint(format, args); + } + template + void report_error(const char *format, const Args & ... args) { + vreport_error(format, fmt::make_format_args(args...)); + } + ``` + +- Added the `make_printf_args` function for capturing `printf` + arguments (https://github.com/fmtlib/fmt/issues/687, + https://github.com/fmtlib/fmt/pull/694). Thanks @Kronuz. + +- Added prefix `v` to non-variadic functions taking `format_args` to + distinguish them from variadic ones: + + ```c++ + std::string vformat(string_view format_str, format_args args); + + template + std::string format(string_view format_str, const Args & ... args); + ``` + +- Added experimental support for formatting ranges, containers and + tuple-like types in `fmt/ranges.h` + (https://github.com/fmtlib/fmt/pull/735): + + ```c++ + #include + + std::vector v = {1, 2, 3}; + fmt::print("{}", v); // prints {1, 2, 3} + ``` + + Thanks @Remotion. + +- Implemented `wchar_t` date and time formatting + (https://github.com/fmtlib/fmt/pull/712): + + ```c++ + #include + + std::time_t t = std::time(nullptr); + auto s = fmt::format(L"The date is {:%Y-%m-%d}.", *std::localtime(&t)); + ``` + + Thanks @DanielaE. + +- Provided more wide string overloads + (https://github.com/fmtlib/fmt/pull/724). Thanks @DanielaE. + +- Switched from a custom null-terminated string view class to + `string_view` in the format API and provided `fmt::string_view` + which implements a subset of `std::string_view` API for pre-C++17 + systems. + +- Added support for `std::experimental::string_view` + (https://github.com/fmtlib/fmt/pull/607): + + ```c++ + #include + #include + + fmt::print("{}", std::experimental::string_view("foo")); + ``` + + Thanks @virgiliofornazin. + +- Allowed mixing named and automatic arguments: + + ```c++ + fmt::format("{} {two}", 1, fmt::arg("two", 2)); + ``` + +- Removed the write API in favor of the [format + API](https://fmt.dev/latest/api.html#format-api) with compile-time + handling of format strings. + +- Disallowed formatting of multibyte strings into a wide character + target (https://github.com/fmtlib/fmt/pull/606). + +- Improved documentation + (https://github.com/fmtlib/fmt/pull/515, + https://github.com/fmtlib/fmt/issues/614, + https://github.com/fmtlib/fmt/pull/617, + https://github.com/fmtlib/fmt/pull/661, + https://github.com/fmtlib/fmt/pull/680). + Thanks @ibell, @mihaitodor and @johnthagen. + +- Implemented more efficient handling of large number of format + arguments. + +- Introduced an inline namespace for symbol versioning. + +- Added debug postfix `d` to the `fmt` library name + (https://github.com/fmtlib/fmt/issues/636). + +- Removed unnecessary `fmt/` prefix in includes + (https://github.com/fmtlib/fmt/pull/397). Thanks @chronoxor. + +- Moved `fmt/*.h` to `include/fmt/*.h` to prevent irrelevant files and + directories appearing on the include search paths when fmt is used + as a subproject and moved source files to the `src` directory. + +- Added qmake project file `support/fmt.pro` + (https://github.com/fmtlib/fmt/pull/641). Thanks @cowo78. + +- Added Gradle build file `support/build.gradle` + (https://github.com/fmtlib/fmt/pull/649). Thanks @luncliff. + +- Removed `FMT_CPPFORMAT` CMake option. + +- Fixed a name conflict with the macro `CHAR_WIDTH` in glibc + (https://github.com/fmtlib/fmt/pull/616). Thanks @aroig. + +- Fixed handling of nested braces in `fmt::join` + (https://github.com/fmtlib/fmt/issues/638). + +- Added `SOURCELINK_SUFFIX` for compatibility with Sphinx 1.5 + (https://github.com/fmtlib/fmt/pull/497). Thanks @ginggs. + +- Added a missing `inline` in the header-only mode + (https://github.com/fmtlib/fmt/pull/626). Thanks @aroig. + +- Fixed various compiler warnings + (https://github.com/fmtlib/fmt/pull/640, + https://github.com/fmtlib/fmt/pull/656, + https://github.com/fmtlib/fmt/pull/679, + https://github.com/fmtlib/fmt/pull/681, + https://github.com/fmtlib/fmt/pull/705, + https://github.com/fmtlib/fmt/issues/715, + https://github.com/fmtlib/fmt/pull/717, + https://github.com/fmtlib/fmt/pull/720, + https://github.com/fmtlib/fmt/pull/723, + https://github.com/fmtlib/fmt/pull/726, + https://github.com/fmtlib/fmt/pull/730, + https://github.com/fmtlib/fmt/pull/739). + Thanks @peterbell10, @LarsGullik, @foonathan, @eliaskosunen, + @christianparpart, @DanielaE and @mwinterb. + +- Worked around an MSVC bug and fixed several warnings + (https://github.com/fmtlib/fmt/pull/653). Thanks @alabuzhev. + +- Worked around GCC bug 67371 + (https://github.com/fmtlib/fmt/issues/682). + +- Fixed compilation with `-fno-exceptions` + (https://github.com/fmtlib/fmt/pull/655). Thanks @chenxiaolong. + +- Made `constexpr remove_prefix` gcc version check tighter + (https://github.com/fmtlib/fmt/issues/648). + +- Renamed internal type enum constants to prevent collision with + poorly written C libraries + (https://github.com/fmtlib/fmt/issues/644). + +- Added detection of `wostream operator<<` + (https://github.com/fmtlib/fmt/issues/650). + +- Fixed compilation on OpenBSD + (https://github.com/fmtlib/fmt/pull/660). Thanks @hubslave. + +- Fixed compilation on FreeBSD 12 + (https://github.com/fmtlib/fmt/pull/732). Thanks @dankm. + +- Fixed compilation when there is a mismatch between `-std` options + between the library and user code + (https://github.com/fmtlib/fmt/issues/664). + +- Fixed compilation with GCC 7 and `-std=c++11` + (https://github.com/fmtlib/fmt/issues/734). + +- Improved generated binary code on GCC 7 and older + (https://github.com/fmtlib/fmt/issues/668). + +- Fixed handling of numeric alignment with no width + (https://github.com/fmtlib/fmt/issues/675). + +- Fixed handling of empty strings in UTF8/16 converters + (https://github.com/fmtlib/fmt/pull/676). Thanks @vgalka-sl. + +- Fixed formatting of an empty `string_view` + (https://github.com/fmtlib/fmt/issues/689). + +- Fixed detection of `string_view` on libc++ + (https://github.com/fmtlib/fmt/issues/686). + +- Fixed DLL issues (https://github.com/fmtlib/fmt/pull/696). + Thanks @sebkoenig. + +- Fixed compile checks for mixing narrow and wide strings + (https://github.com/fmtlib/fmt/issues/690). + +- Disabled unsafe implicit conversion to `std::string` + (https://github.com/fmtlib/fmt/issues/729). + +- Fixed handling of reused format specs (as in `fmt::join`) for + pointers (https://github.com/fmtlib/fmt/pull/725). Thanks @mwinterb. + +- Fixed installation of `fmt/ranges.h` + (https://github.com/fmtlib/fmt/pull/738). Thanks @sv1990. + +# 4.1.0 - 2017-12-20 + +- Added `fmt::to_wstring()` in addition to `fmt::to_string()` + (https://github.com/fmtlib/fmt/pull/559). Thanks @alabuzhev. +- Added support for C++17 `std::string_view` + (https://github.com/fmtlib/fmt/pull/571 and + https://github.com/fmtlib/fmt/pull/578). + Thanks @thelostt and @mwinterb. +- Enabled stream exceptions to catch errors + (https://github.com/fmtlib/fmt/issues/581). Thanks @crusader-mike. +- Allowed formatting of class hierarchies with `fmt::format_arg()` + (https://github.com/fmtlib/fmt/pull/547). Thanks @rollbear. +- Removed limitations on character types + (https://github.com/fmtlib/fmt/pull/563). Thanks @Yelnats321. +- Conditionally enabled use of `std::allocator_traits` + (https://github.com/fmtlib/fmt/pull/583). Thanks @mwinterb. +- Added support for `const` variadic member function emulation with + `FMT_VARIADIC_CONST` + (https://github.com/fmtlib/fmt/pull/591). Thanks @ludekvodicka. +- Various bugfixes: bad overflow check, unsupported implicit type + conversion when determining formatting function, test segfaults + (https://github.com/fmtlib/fmt/issues/551), ill-formed + macros (https://github.com/fmtlib/fmt/pull/542) and + ambiguous overloads + (https://github.com/fmtlib/fmt/issues/580). Thanks @xylosper. +- Prevented warnings on MSVC + (https://github.com/fmtlib/fmt/pull/605, + https://github.com/fmtlib/fmt/pull/602, and + https://github.com/fmtlib/fmt/pull/545), clang + (https://github.com/fmtlib/fmt/pull/582), GCC + (https://github.com/fmtlib/fmt/issues/573), various + conversion warnings (https://github.com/fmtlib/fmt/pull/609, + https://github.com/fmtlib/fmt/pull/567, + https://github.com/fmtlib/fmt/pull/553 and + https://github.com/fmtlib/fmt/pull/553), and added + `override` and `[[noreturn]]` + (https://github.com/fmtlib/fmt/pull/549 and + https://github.com/fmtlib/fmt/issues/555). + Thanks @alabuzhev, @virgiliofornazin, @alexanderbock, @yumetodo, @VaderY, + @jpcima, @thelostt and @Manu343726. +- Improved CMake: Used `GNUInstallDirs` to set installation location + (https://github.com/fmtlib/fmt/pull/610) and fixed warnings + (https://github.com/fmtlib/fmt/pull/536 and + https://github.com/fmtlib/fmt/pull/556). + Thanks @mikecrowe, @evgen231 and @henryiii. + +# 4.0.0 - 2017-06-27 + +- Removed old compatibility headers `cppformat/*.h` and CMake options + (https://github.com/fmtlib/fmt/pull/527). Thanks @maddinat0r. + +- Added `string.h` containing `fmt::to_string()` as alternative to + `std::to_string()` as well as other string writer functionality + (https://github.com/fmtlib/fmt/issues/326 and + https://github.com/fmtlib/fmt/pull/441): + + ```c++ + #include "fmt/string.h" + + std::string answer = fmt::to_string(42); + ``` + + Thanks @glebov-andrey. + +- Moved `fmt::printf()` to new `printf.h` header and allowed `%s` as + generic specifier (https://github.com/fmtlib/fmt/pull/453), + made `%.f` more conformant to regular `printf()` + (https://github.com/fmtlib/fmt/pull/490), added custom + writer support (https://github.com/fmtlib/fmt/issues/476) + and implemented missing custom argument formatting + (https://github.com/fmtlib/fmt/pull/339 and + https://github.com/fmtlib/fmt/pull/340): + + ```c++ + #include "fmt/printf.h" + + // %s format specifier can be used with any argument type. + fmt::printf("%s", 42); + ``` + + Thanks @mojoBrendan, @manylegged and @spacemoose. + See also https://github.com/fmtlib/fmt/issues/360, + https://github.com/fmtlib/fmt/issues/335 and + https://github.com/fmtlib/fmt/issues/331. + +- Added `container.h` containing a `BasicContainerWriter` to write to + containers like `std::vector` + (https://github.com/fmtlib/fmt/pull/450). Thanks @polyvertex. + +- Added `fmt::join()` function that takes a range and formats its + elements separated by a given string + (https://github.com/fmtlib/fmt/pull/466): + + ```c++ + #include "fmt/format.h" + + std::vector v = {1.2, 3.4, 5.6}; + // Prints "(+01.20, +03.40, +05.60)". + fmt::print("({:+06.2f})", fmt::join(v.begin(), v.end(), ", ")); + ``` + + Thanks @olivier80. + +- Added support for custom formatting specifications to simplify + customization of built-in formatting + (https://github.com/fmtlib/fmt/pull/444). Thanks @polyvertex. + See also https://github.com/fmtlib/fmt/issues/439. + +- Added `fmt::format_system_error()` for error code formatting + (https://github.com/fmtlib/fmt/issues/323 and + https://github.com/fmtlib/fmt/pull/526). Thanks @maddinat0r. + +- Added thread-safe `fmt::localtime()` and `fmt::gmtime()` as + replacement for the standard version to `time.h` + (https://github.com/fmtlib/fmt/pull/396). Thanks @codicodi. + +- Internal improvements to `NamedArg` and `ArgLists` + (https://github.com/fmtlib/fmt/pull/389 and + https://github.com/fmtlib/fmt/pull/390). Thanks @chronoxor. + +- Fixed crash due to bug in `FormatBuf` + (https://github.com/fmtlib/fmt/pull/493). Thanks @effzeh. See also + https://github.com/fmtlib/fmt/issues/480 and + https://github.com/fmtlib/fmt/issues/491. + +- Fixed handling of wide strings in `fmt::StringWriter`. + +- Improved compiler error messages + (https://github.com/fmtlib/fmt/issues/357). + +- Fixed various warnings and issues with various compilers + (https://github.com/fmtlib/fmt/pull/494, + https://github.com/fmtlib/fmt/pull/499, + https://github.com/fmtlib/fmt/pull/483, + https://github.com/fmtlib/fmt/pull/485, + https://github.com/fmtlib/fmt/pull/482, + https://github.com/fmtlib/fmt/pull/475, + https://github.com/fmtlib/fmt/pull/473 and + https://github.com/fmtlib/fmt/pull/414). + Thanks @chronoxor, @zhaohuaxishi, @pkestene, @dschmidt and @0x414c. + +- Improved CMake: targets are now namespaced + (https://github.com/fmtlib/fmt/pull/511 and + https://github.com/fmtlib/fmt/pull/513), supported + header-only `printf.h` + (https://github.com/fmtlib/fmt/pull/354), fixed issue with + minimal supported library subset + (https://github.com/fmtlib/fmt/issues/418, + https://github.com/fmtlib/fmt/pull/419 and + https://github.com/fmtlib/fmt/pull/420). + Thanks @bjoernthiel, @niosHD, @LogicalKnight and @alabuzhev. + +- Improved documentation (https://github.com/fmtlib/fmt/pull/393). + Thanks @pwm1234. + +# 3.0.2 - 2017-06-14 + +- Added `FMT_VERSION` macro + (https://github.com/fmtlib/fmt/issues/411). +- Used `FMT_NULL` instead of literal `0` + (https://github.com/fmtlib/fmt/pull/409). Thanks @alabuzhev. +- Added extern templates for `format_float` + (https://github.com/fmtlib/fmt/issues/413). +- Fixed implicit conversion issue + (https://github.com/fmtlib/fmt/issues/507). +- Fixed signbit detection + (https://github.com/fmtlib/fmt/issues/423). +- Fixed naming collision + (https://github.com/fmtlib/fmt/issues/425). +- Fixed missing intrinsic for C++/CLI + (https://github.com/fmtlib/fmt/pull/457). Thanks @calumr. +- Fixed Android detection + (https://github.com/fmtlib/fmt/pull/458). Thanks @Gachapen. +- Use lean `windows.h` if not in header-only mode + (https://github.com/fmtlib/fmt/pull/503). Thanks @Quentin01. +- Fixed issue with CMake exporting C++11 flag + (https://github.com/fmtlib/fmt/pull/455). Thanks @EricWF. +- Fixed issue with nvcc and MSVC compiler bug and MinGW + (https://github.com/fmtlib/fmt/issues/505). +- Fixed DLL issues (https://github.com/fmtlib/fmt/pull/469 and + https://github.com/fmtlib/fmt/pull/502). + Thanks @richardeakin and @AndreasSchoenle. +- Fixed test compilation under FreeBSD + (https://github.com/fmtlib/fmt/issues/433). +- Fixed various warnings + (https://github.com/fmtlib/fmt/pull/403, + https://github.com/fmtlib/fmt/pull/410 and + https://github.com/fmtlib/fmt/pull/510). + Thanks @Lecetem, @chenhayat and @trozen. +- Worked around a broken `__builtin_clz` in clang with MS codegen + (https://github.com/fmtlib/fmt/issues/519). +- Removed redundant include + (https://github.com/fmtlib/fmt/issues/479). +- Fixed documentation issues. + +# 3.0.1 - 2016-11-01 + +- Fixed handling of thousands separator + (https://github.com/fmtlib/fmt/issues/353). +- Fixed handling of `unsigned char` strings + (https://github.com/fmtlib/fmt/issues/373). +- Corrected buffer growth when formatting time + (https://github.com/fmtlib/fmt/issues/367). +- Removed warnings under MSVC and clang + (https://github.com/fmtlib/fmt/issues/318, + https://github.com/fmtlib/fmt/issues/250, also merged + https://github.com/fmtlib/fmt/pull/385 and + https://github.com/fmtlib/fmt/pull/361). + Thanks @jcelerier and @nmoehrle. +- Fixed compilation issues under Android + (https://github.com/fmtlib/fmt/pull/327, + https://github.com/fmtlib/fmt/issues/345 and + https://github.com/fmtlib/fmt/pull/381), FreeBSD + (https://github.com/fmtlib/fmt/pull/358), Cygwin + (https://github.com/fmtlib/fmt/issues/388), MinGW + (https://github.com/fmtlib/fmt/issues/355) as well as other + issues (https://github.com/fmtlib/fmt/issues/350, + https://github.com/fmtlib/fmt/issues/355, + https://github.com/fmtlib/fmt/pull/348, + https://github.com/fmtlib/fmt/pull/402, + https://github.com/fmtlib/fmt/pull/405). + Thanks @dpantele, @hghwng, @arvedarved, @LogicalKnight and @JanHellwig. +- Fixed some documentation issues and extended specification + (https://github.com/fmtlib/fmt/issues/320, + https://github.com/fmtlib/fmt/pull/333, + https://github.com/fmtlib/fmt/issues/347, + https://github.com/fmtlib/fmt/pull/362). Thanks @smellman. + +# 3.0.0 - 2016-05-07 + +- The project has been renamed from C++ Format (cppformat) to fmt for + consistency with the used namespace and macro prefix + (https://github.com/fmtlib/fmt/issues/307). Library headers + are now located in the `fmt` directory: + + ```c++ + #include "fmt/format.h" + ``` + + Including `format.h` from the `cppformat` directory is deprecated + but works via a proxy header which will be removed in the next major + version. + + The documentation is now available at . + +- Added support for + [strftime](http://en.cppreference.com/w/cpp/chrono/c/strftime)-like + [date and time + formatting](https://fmt.dev/3.0.0/api.html#date-and-time-formatting) + (https://github.com/fmtlib/fmt/issues/283): + + ```c++ + #include "fmt/time.h" + + std::time_t t = std::time(nullptr); + // Prints "The date is 2016-04-29." (with the current date) + fmt::print("The date is {:%Y-%m-%d}.", *std::localtime(&t)); + ``` + +- `std::ostream` support including formatting of user-defined types + that provide overloaded `operator<<` has been moved to + `fmt/ostream.h`: + + ```c++ + #include "fmt/ostream.h" + + class Date { + int year_, month_, day_; + public: + Date(int year, int month, int day) : year_(year), month_(month), day_(day) {} + + friend std::ostream &operator<<(std::ostream &os, const Date &d) { + return os << d.year_ << '-' << d.month_ << '-' << d.day_; + } + }; + + std::string s = fmt::format("The date is {}", Date(2012, 12, 9)); + // s == "The date is 2012-12-9" + ``` + +- Added support for [custom argument + formatters](https://fmt.dev/3.0.0/api.html#argument-formatters) + (https://github.com/fmtlib/fmt/issues/235). + +- Added support for locale-specific integer formatting with the `n` + specifier (https://github.com/fmtlib/fmt/issues/305): + + ```c++ + std::setlocale(LC_ALL, "en_US.utf8"); + fmt::print("cppformat: {:n}\n", 1234567); // prints 1,234,567 + ``` + +- Sign is now preserved when formatting an integer with an incorrect + `printf` format specifier + (https://github.com/fmtlib/fmt/issues/265): + + ```c++ + fmt::printf("%lld", -42); // prints -42 + ``` + + Note that it would be an undefined behavior in `std::printf`. + +- Length modifiers such as `ll` are now optional in printf formatting + functions and the correct type is determined automatically + (https://github.com/fmtlib/fmt/issues/255): + + ```c++ + fmt::printf("%d", std::numeric_limits::max()); + ``` + + Note that it would be an undefined behavior in `std::printf`. + +- Added initial support for custom formatters + (https://github.com/fmtlib/fmt/issues/231). + +- Fixed detection of user-defined literal support on Intel C++ + compiler (https://github.com/fmtlib/fmt/issues/311, + https://github.com/fmtlib/fmt/pull/312). + Thanks @dean0x7d and @speth. + +- Reduced compile time + (https://github.com/fmtlib/fmt/pull/243, + https://github.com/fmtlib/fmt/pull/249, + https://github.com/fmtlib/fmt/issues/317): + + ![](https://cloud.githubusercontent.com/assets/4831417/11614060/b9e826d2-9c36-11e5-8666-d4131bf503ef.png) + + ![](https://cloud.githubusercontent.com/assets/4831417/11614080/6ac903cc-9c37-11e5-8165-26df6efae364.png) + + Thanks @dean0x7d. + +- Compile test fixes (https://github.com/fmtlib/fmt/pull/313). + Thanks @dean0x7d. + +- Documentation fixes (https://github.com/fmtlib/fmt/pull/239, + https://github.com/fmtlib/fmt/issues/248, + https://github.com/fmtlib/fmt/issues/252, + https://github.com/fmtlib/fmt/pull/258, + https://github.com/fmtlib/fmt/issues/260, + https://github.com/fmtlib/fmt/issues/301, + https://github.com/fmtlib/fmt/pull/309). + Thanks @ReadmeCritic @Gachapen and @jwilk. + +- Fixed compiler and sanitizer warnings + (https://github.com/fmtlib/fmt/issues/244, + https://github.com/fmtlib/fmt/pull/256, + https://github.com/fmtlib/fmt/pull/259, + https://github.com/fmtlib/fmt/issues/263, + https://github.com/fmtlib/fmt/issues/274, + https://github.com/fmtlib/fmt/pull/277, + https://github.com/fmtlib/fmt/pull/286, + https://github.com/fmtlib/fmt/issues/291, + https://github.com/fmtlib/fmt/issues/296, + https://github.com/fmtlib/fmt/issues/308). + Thanks @mwinterb, @pweiskircher and @Naios. + +- Improved compatibility with Windows Store apps + (https://github.com/fmtlib/fmt/issues/280, + https://github.com/fmtlib/fmt/pull/285) Thanks @mwinterb. + +- Added tests of compatibility with older C++ standards + (https://github.com/fmtlib/fmt/pull/273). Thanks @niosHD. + +- Fixed Android build + (https://github.com/fmtlib/fmt/pull/271). Thanks @newnon. + +- Changed `ArgMap` to be backed by a vector instead of a map. + (https://github.com/fmtlib/fmt/issues/261, + https://github.com/fmtlib/fmt/pull/262). Thanks @mwinterb. + +- Added `fprintf` overload that writes to a `std::ostream` + (https://github.com/fmtlib/fmt/pull/251). + Thanks @nickhutchinson. + +- Export symbols when building a Windows DLL + (https://github.com/fmtlib/fmt/pull/245). + Thanks @macdems. + +- Fixed compilation on Cygwin + (https://github.com/fmtlib/fmt/issues/304). + +- Implemented a workaround for a bug in Apple LLVM version 4.2 of + clang (https://github.com/fmtlib/fmt/issues/276). + +- Implemented a workaround for Google Test bug + https://github.com/google/googletest/issues/705 on gcc 6 + (https://github.com/fmtlib/fmt/issues/268). Thanks @octoploid. + +- Removed Biicode support because the latter has been discontinued. + +# 2.1.1 - 2016-04-11 + +- The install location for generated CMake files is now configurable + via the `FMT_CMAKE_DIR` CMake variable + (https://github.com/fmtlib/fmt/pull/299). Thanks @niosHD. +- Documentation fixes + (https://github.com/fmtlib/fmt/issues/252). + +# 2.1.0 - 2016-03-21 + +- Project layout and build system improvements + (https://github.com/fmtlib/fmt/pull/267): + + - The code have been moved to the `cppformat` directory. Including + `format.h` from the top-level directory is deprecated but works + via a proxy header which will be removed in the next major + version. + - C++ Format CMake targets now have proper interface definitions. + - Installed version of the library now supports the header-only + configuration. + - Targets `doc`, `install`, and `test` are now disabled if C++ + Format is included as a CMake subproject. They can be enabled by + setting `FMT_DOC`, `FMT_INSTALL`, and `FMT_TEST` in the parent + project. + + Thanks @niosHD. + +# 2.0.1 - 2016-03-13 + +- Improved CMake find and package support + (https://github.com/fmtlib/fmt/issues/264). Thanks @niosHD. +- Fix compile error with Android NDK and mingw32 + (https://github.com/fmtlib/fmt/issues/241). Thanks @Gachapen. +- Documentation fixes + (https://github.com/fmtlib/fmt/issues/248, + https://github.com/fmtlib/fmt/issues/260). + +# 2.0.0 - 2015-12-01 + +## General + +- \[Breaking\] Named arguments + (https://github.com/fmtlib/fmt/pull/169, + https://github.com/fmtlib/fmt/pull/173, + https://github.com/fmtlib/fmt/pull/174): + + ```c++ + fmt::print("The answer is {answer}.", fmt::arg("answer", 42)); + ``` + + Thanks @jamboree. + +- \[Experimental\] User-defined literals for format and named + arguments (https://github.com/fmtlib/fmt/pull/204, + https://github.com/fmtlib/fmt/pull/206, + https://github.com/fmtlib/fmt/pull/207): + + ```c++ + using namespace fmt::literals; + fmt::print("The answer is {answer}.", "answer"_a=42); + ``` + + Thanks @dean0x7d. + +- \[Breaking\] Formatting of more than 16 arguments is now supported + when using variadic templates + (https://github.com/fmtlib/fmt/issues/141). Thanks @Shauren. + +- Runtime width specification + (https://github.com/fmtlib/fmt/pull/168): + + ```c++ + fmt::format("{0:{1}}", 42, 5); // gives " 42" + ``` + + Thanks @jamboree. + +- \[Breaking\] Enums are now formatted with an overloaded + `std::ostream` insertion operator (`operator<<`) if available + (https://github.com/fmtlib/fmt/issues/232). + +- \[Breaking\] Changed default `bool` format to textual, \"true\" or + \"false\" (https://github.com/fmtlib/fmt/issues/170): + + ```c++ + fmt::print("{}", true); // prints "true" + ``` + + To print `bool` as a number use numeric format specifier such as + `d`: + + ```c++ + fmt::print("{:d}", true); // prints "1" + ``` + +- `fmt::printf` and `fmt::sprintf` now support formatting of `bool` + with the `%s` specifier giving textual output, \"true\" or \"false\" + (https://github.com/fmtlib/fmt/pull/223): + + ```c++ + fmt::printf("%s", true); // prints "true" + ``` + + Thanks @LarsGullik. + +- \[Breaking\] `signed char` and `unsigned char` are now formatted as + integers by default + (https://github.com/fmtlib/fmt/pull/217). + +- \[Breaking\] Pointers to C strings can now be formatted with the `p` + specifier (https://github.com/fmtlib/fmt/pull/223): + + ```c++ + fmt::print("{:p}", "test"); // prints pointer value + ``` + + Thanks @LarsGullik. + +- \[Breaking\] `fmt::printf` and `fmt::sprintf` now print null + pointers as `(nil)` and null strings as `(null)` for consistency + with glibc (https://github.com/fmtlib/fmt/pull/226). + Thanks @LarsGullik. + +- \[Breaking\] `fmt::(s)printf` now supports formatting of objects of + user-defined types that provide an overloaded `std::ostream` + insertion operator (`operator<<`) + (https://github.com/fmtlib/fmt/issues/201): + + ```c++ + fmt::printf("The date is %s", Date(2012, 12, 9)); + ``` + +- \[Breaking\] The `Buffer` template is now part of the public API and + can be used to implement custom memory buffers + (https://github.com/fmtlib/fmt/issues/140). Thanks @polyvertex. + +- \[Breaking\] Improved compatibility between `BasicStringRef` and + [std::experimental::basic_string_view]( + http://en.cppreference.com/w/cpp/experimental/basic_string_view) + (https://github.com/fmtlib/fmt/issues/100, + https://github.com/fmtlib/fmt/issues/159, + https://github.com/fmtlib/fmt/issues/183): + + - Comparison operators now compare string content, not pointers + - `BasicStringRef::c_str` replaced by `BasicStringRef::data` + - `BasicStringRef` is no longer assumed to be null-terminated + + References to null-terminated strings are now represented by a new + class, `BasicCStringRef`. + +- Dependency on pthreads introduced by Google Test is now optional + (https://github.com/fmtlib/fmt/issues/185). + +- New CMake options `FMT_DOC`, `FMT_INSTALL` and `FMT_TEST` to control + generation of `doc`, `install` and `test` targets respectively, on + by default (https://github.com/fmtlib/fmt/issues/197, + https://github.com/fmtlib/fmt/issues/198, + https://github.com/fmtlib/fmt/issues/200). Thanks @maddinat0r. + +- `noexcept` is now used when compiling with MSVC2015 + (https://github.com/fmtlib/fmt/pull/215). Thanks @dmkrepo. + +- Added an option to disable use of `windows.h` when + `FMT_USE_WINDOWS_H` is defined as 0 before including `format.h` + (https://github.com/fmtlib/fmt/issues/171). Thanks @alfps. + +- \[Breaking\] `windows.h` is now included with `NOMINMAX` unless + `FMT_WIN_MINMAX` is defined. This is done to prevent breaking code + using `std::min` and `std::max` and only affects the header-only + configuration (https://github.com/fmtlib/fmt/issues/152, + https://github.com/fmtlib/fmt/pull/153, + https://github.com/fmtlib/fmt/pull/154). Thanks @DevO2012. + +- Improved support for custom character types + (https://github.com/fmtlib/fmt/issues/171). Thanks @alfps. + +- Added an option to disable use of IOStreams when `FMT_USE_IOSTREAMS` + is defined as 0 before including `format.h` + (https://github.com/fmtlib/fmt/issues/205, + https://github.com/fmtlib/fmt/pull/208). Thanks @JodiTheTigger. + +- Improved detection of `isnan`, `isinf` and `signbit`. + +## Optimization + +- Made formatting of user-defined types more efficient with a custom + stream buffer (https://github.com/fmtlib/fmt/issues/92, + https://github.com/fmtlib/fmt/pull/230). Thanks @NotImplemented. +- Further improved performance of `fmt::Writer` on integer formatting + and fixed a minor regression. Now it is \~7% faster than + `karma::generate` on Karma\'s benchmark + (https://github.com/fmtlib/fmt/issues/186). +- \[Breaking\] Reduced [compiled code + size](https://github.com/fmtlib/fmt#compile-time-and-code-bloat) + (https://github.com/fmtlib/fmt/issues/143, + https://github.com/fmtlib/fmt/pull/149). + +## Distribution + +- \[Breaking\] Headers are now installed in + `${CMAKE_INSTALL_PREFIX}/include/cppformat` + (https://github.com/fmtlib/fmt/issues/178). Thanks @jackyf. + +- \[Breaking\] Changed the library name from `format` to `cppformat` + for consistency with the project name and to avoid potential + conflicts (https://github.com/fmtlib/fmt/issues/178). + Thanks @jackyf. + +- C++ Format is now available in [Debian](https://www.debian.org/) + GNU/Linux + ([stretch](https://packages.debian.org/source/stretch/cppformat), + [sid](https://packages.debian.org/source/sid/cppformat)) and derived + distributions such as + [Ubuntu](https://launchpad.net/ubuntu/+source/cppformat) 15.10 and + later (https://github.com/fmtlib/fmt/issues/155): + + $ sudo apt-get install libcppformat1-dev + + Thanks @jackyf. + +- [Packages for Fedora and + RHEL](https://admin.fedoraproject.org/pkgdb/package/cppformat/) are + now available. Thanks Dave Johansen. + +- C++ Format can now be installed via [Homebrew](http://brew.sh/) on + OS X (https://github.com/fmtlib/fmt/issues/157): + + $ brew install cppformat + + Thanks @ortho and Anatoliy Bulukin. + +## Documentation + +- Migrated from ReadTheDocs to GitHub Pages for better responsiveness + and reliability (https://github.com/fmtlib/fmt/issues/128). + New documentation address is . +- Added [Building thedocumentation]( + https://fmt.dev/2.0.0/usage.html#building-the-documentation) + section to the documentation. +- Documentation build script is now compatible with Python 3 and newer + pip versions. (https://github.com/fmtlib/fmt/pull/189, + https://github.com/fmtlib/fmt/issues/209). + Thanks @JodiTheTigger and @xentec. +- Documentation fixes and improvements + (https://github.com/fmtlib/fmt/issues/36, + https://github.com/fmtlib/fmt/issues/75, + https://github.com/fmtlib/fmt/issues/125, + https://github.com/fmtlib/fmt/pull/160, + https://github.com/fmtlib/fmt/pull/161, + https://github.com/fmtlib/fmt/issues/162, + https://github.com/fmtlib/fmt/issues/165, + https://github.com/fmtlib/fmt/issues/210). + Thanks @syohex. +- Fixed out-of-tree documentation build + (https://github.com/fmtlib/fmt/issues/177). Thanks @jackyf. + +## Fixes + +- Fixed `initializer_list` detection + (https://github.com/fmtlib/fmt/issues/136). Thanks @Gachapen. + +- \[Breaking\] Fixed formatting of enums with numeric format + specifiers in `fmt::(s)printf` + (https://github.com/fmtlib/fmt/issues/131, + https://github.com/fmtlib/fmt/issues/139): + + ```c++ + enum { ANSWER = 42 }; + fmt::printf("%d", ANSWER); + ``` + + Thanks @Naios. + +- Improved compatibility with old versions of MinGW + (https://github.com/fmtlib/fmt/issues/129, + https://github.com/fmtlib/fmt/pull/130, + https://github.com/fmtlib/fmt/issues/132). Thanks @cstamford. + +- Fixed a compile error on MSVC with disabled exceptions + (https://github.com/fmtlib/fmt/issues/144). + +- Added a workaround for broken implementation of variadic templates + in MSVC2012 (https://github.com/fmtlib/fmt/issues/148). + +- Placed the anonymous namespace within `fmt` namespace for the + header-only configuration (https://github.com/fmtlib/fmt/issues/171). + Thanks @alfps. + +- Fixed issues reported by Coverity Scan + (https://github.com/fmtlib/fmt/issues/187, + https://github.com/fmtlib/fmt/issues/192). + +- Implemented a workaround for a name lookup bug in MSVC2010 + (https://github.com/fmtlib/fmt/issues/188). + +- Fixed compiler warnings + (https://github.com/fmtlib/fmt/issues/95, + https://github.com/fmtlib/fmt/issues/96, + https://github.com/fmtlib/fmt/pull/114, + https://github.com/fmtlib/fmt/issues/135, + https://github.com/fmtlib/fmt/issues/142, + https://github.com/fmtlib/fmt/issues/145, + https://github.com/fmtlib/fmt/issues/146, + https://github.com/fmtlib/fmt/issues/158, + https://github.com/fmtlib/fmt/issues/163, + https://github.com/fmtlib/fmt/issues/175, + https://github.com/fmtlib/fmt/issues/190, + https://github.com/fmtlib/fmt/pull/191, + https://github.com/fmtlib/fmt/issues/194, + https://github.com/fmtlib/fmt/pull/196, + https://github.com/fmtlib/fmt/issues/216, + https://github.com/fmtlib/fmt/pull/218, + https://github.com/fmtlib/fmt/pull/220, + https://github.com/fmtlib/fmt/pull/229, + https://github.com/fmtlib/fmt/issues/233, + https://github.com/fmtlib/fmt/issues/234, + https://github.com/fmtlib/fmt/pull/236, + https://github.com/fmtlib/fmt/issues/281, + https://github.com/fmtlib/fmt/issues/289). + Thanks @seanmiddleditch, @dixlorenz, @CarterLi, @Naios, @fmatthew5876, + @LevskiWeng, @rpopescu, @gabime, @cubicool, @jkflying, @LogicalKnight, + @inguin and @Jopie64. + +- Fixed portability issues (mostly causing test failures) on ARM, + ppc64, ppc64le, s390x and SunOS 5.11 i386 + (https://github.com/fmtlib/fmt/issues/138, + https://github.com/fmtlib/fmt/issues/179, + https://github.com/fmtlib/fmt/issues/180, + https://github.com/fmtlib/fmt/issues/202, + https://github.com/fmtlib/fmt/issues/225, [Red Hat Bugzilla + Bug 1260297](https://bugzilla.redhat.com/show_bug.cgi?id=1260297)). + Thanks @Naios, @jackyf and Dave Johansen. + +- Fixed a name conflict with macro `free` defined in `crtdbg.h` when + `_CRTDBG_MAP_ALLOC` is set (https://github.com/fmtlib/fmt/issues/211). + +- Fixed shared library build on OS X + (https://github.com/fmtlib/fmt/pull/212). Thanks @dean0x7d. + +- Fixed an overload conflict on MSVC when `/Zc:wchar_t-` option is + specified (https://github.com/fmtlib/fmt/pull/214). + Thanks @slavanap. + +- Improved compatibility with MSVC 2008 + (https://github.com/fmtlib/fmt/pull/236). Thanks @Jopie64. + +- Improved compatibility with bcc32 + (https://github.com/fmtlib/fmt/issues/227). + +- Fixed `static_assert` detection on Clang + (https://github.com/fmtlib/fmt/pull/228). Thanks @dean0x7d. + +# 1.1.0 - 2015-03-06 + +- Added `BasicArrayWriter`, a class template that provides operations + for formatting and writing data into a fixed-size array + (https://github.com/fmtlib/fmt/issues/105 and + https://github.com/fmtlib/fmt/issues/122): + + ```c++ + char buffer[100]; + fmt::ArrayWriter w(buffer); + w.write("The answer is {}", 42); + ``` + +- Added [0 A.D.](http://play0ad.com/) and [PenUltima Online + (POL)](http://www.polserver.com/) to the list of notable projects + using C++ Format. + +- C++ Format now uses MSVC intrinsics for better formatting performance + (https://github.com/fmtlib/fmt/pull/115, + https://github.com/fmtlib/fmt/pull/116, + https://github.com/fmtlib/fmt/pull/118 and + https://github.com/fmtlib/fmt/pull/121). Previously these + optimizations where only used on GCC and Clang. + Thanks @CarterLi and @objectx. + +- CMake install target + (https://github.com/fmtlib/fmt/pull/119). Thanks @TrentHouliston. + + You can now install C++ Format with `make install` command. + +- Improved [Biicode](http://www.biicode.com/) support + (https://github.com/fmtlib/fmt/pull/98 and + https://github.com/fmtlib/fmt/pull/104). + Thanks @MariadeAnton and @franramirez688. + +- Improved support for building with [Android NDK]( + https://developer.android.com/tools/sdk/ndk/index.html) + (https://github.com/fmtlib/fmt/pull/107). Thanks @newnon. + + The [android-ndk-example](https://github.com/fmtlib/android-ndk-example) + repository provides and example of using C++ Format with Android NDK: + + ![](https://raw.githubusercontent.com/fmtlib/android-ndk-example/master/screenshot.png) + +- Improved documentation of `SystemError` and `WindowsError` + (https://github.com/fmtlib/fmt/issues/54). + +- Various code improvements + (https://github.com/fmtlib/fmt/pull/110, + https://github.com/fmtlib/fmt/pull/111 + https://github.com/fmtlib/fmt/pull/112). Thanks @CarterLi. + +- Improved compile-time errors when formatting wide into narrow + strings (https://github.com/fmtlib/fmt/issues/117). + +- Fixed `BasicWriter::write` without formatting arguments when C++11 + support is disabled + (https://github.com/fmtlib/fmt/issues/109). + +- Fixed header-only build on OS X with GCC 4.9 + (https://github.com/fmtlib/fmt/issues/124). + +- Fixed packaging issues (https://github.com/fmtlib/fmt/issues/94). + +- Added [changelog](https://github.com/fmtlib/fmt/blob/master/ChangeLog.md) + (https://github.com/fmtlib/fmt/issues/103). + +# 1.0.0 - 2015-02-05 + +- Add support for a header-only configuration when `FMT_HEADER_ONLY` + is defined before including `format.h`: + + ```c++ + #define FMT_HEADER_ONLY + #include "format.h" + ``` + +- Compute string length in the constructor of `BasicStringRef` instead + of the `size` method + (https://github.com/fmtlib/fmt/issues/79). This eliminates + size computation for string literals on reasonable optimizing + compilers. + +- Fix formatting of types with overloaded `operator <<` for + `std::wostream` (https://github.com/fmtlib/fmt/issues/86): + + ```c++ + fmt::format(L"The date is {0}", Date(2012, 12, 9)); + ``` + +- Fix linkage of tests on Arch Linux + (https://github.com/fmtlib/fmt/issues/89). + +- Allow precision specifier for non-float arguments + (https://github.com/fmtlib/fmt/issues/90): + + ```c++ + fmt::print("{:.3}\n", "Carpet"); // prints "Car" + ``` + +- Fix build on Android NDK (https://github.com/fmtlib/fmt/issues/93). + +- Improvements to documentation build procedure. + +- Remove `FMT_SHARED` CMake variable in favor of standard [BUILD_SHARED_LIBS]( + http://www.cmake.org/cmake/help/v3.0/variable/BUILD_SHARED_LIBS.html). + +- Fix error handling in `fmt::fprintf`. + +- Fix a number of warnings. + +# 0.12.0 - 2014-10-25 + +- \[Breaking\] Improved separation between formatting and buffer + management. `Writer` is now a base class that cannot be instantiated + directly. The new `MemoryWriter` class implements the default buffer + management with small allocations done on stack. So `fmt::Writer` + should be replaced with `fmt::MemoryWriter` in variable + declarations. + + Old code: + + ```c++ + fmt::Writer w; + ``` + + New code: + + ```c++ + fmt::MemoryWriter w; + ``` + + If you pass `fmt::Writer` by reference, you can continue to do so: + + ```c++ + void f(fmt::Writer &w); + ``` + + This doesn\'t affect the formatting API. + +- Support for custom memory allocators + (https://github.com/fmtlib/fmt/issues/69) + +- Formatting functions now accept [signed char]{.title-ref} and + [unsigned char]{.title-ref} strings as arguments + (https://github.com/fmtlib/fmt/issues/73): + + ```c++ + auto s = format("GLSL version: {}", glGetString(GL_VERSION)); + ``` + +- Reduced code bloat. According to the new [benchmark + results](https://github.com/fmtlib/fmt#compile-time-and-code-bloat), + cppformat is close to `printf` and by the order of magnitude better + than Boost Format in terms of compiled code size. + +- Improved appearance of the documentation on mobile by using the + [Sphinx Bootstrap + theme](http://ryan-roemer.github.io/sphinx-bootstrap-theme/): + + | Old | New | + | --- | --- | + | ![](https://cloud.githubusercontent.com/assets/576385/4792130/cd256436-5de3-11e4-9a62-c077d0c2b003.png) | ![](https://cloud.githubusercontent.com/assets/576385/4792131/cd29896c-5de3-11e4-8f59-cac952942bf0.png) | + +# 0.11.0 - 2014-08-21 + +- Safe printf implementation with a POSIX extension for positional + arguments: + + ```c++ + fmt::printf("Elapsed time: %.2f seconds", 1.23); + fmt::printf("%1$s, %3$d %2$s", weekday, month, day); + ``` + +- Arguments of `char` type can now be formatted as integers (Issue + https://github.com/fmtlib/fmt/issues/55): + + ```c++ + fmt::format("0x{0:02X}", 'a'); + ``` + +- Deprecated parts of the API removed. + +- The library is now built and tested on MinGW with Appveyor in + addition to existing test platforms Linux/GCC, OS X/Clang, + Windows/MSVC. + +# 0.10.0 - 2014-07-01 + +**Improved API** + +- All formatting methods are now implemented as variadic functions + instead of using `operator<<` for feeding arbitrary arguments into a + temporary formatter object. This works both with C++11 where + variadic templates are used and with older standards where variadic + functions are emulated by providing lightweight wrapper functions + defined with the `FMT_VARIADIC` macro. You can use this macro for + defining your own portable variadic functions: + + ```c++ + void report_error(const char *format, const fmt::ArgList &args) { + fmt::print("Error: {}"); + fmt::print(format, args); + } + FMT_VARIADIC(void, report_error, const char *) + + report_error("file not found: {}", path); + ``` + + Apart from a more natural syntax, this also improves performance as + there is no need to construct temporary formatter objects and + control arguments\' lifetimes. Because the wrapper functions are + very lightweight, this doesn\'t cause code bloat even in pre-C++11 + mode. + +- Simplified common case of formatting an `std::string`. Now it + requires a single function call: + + ```c++ + std::string s = format("The answer is {}.", 42); + ``` + + Previously it required 2 function calls: + + ```c++ + std::string s = str(Format("The answer is {}.") << 42); + ``` + + Instead of unsafe `c_str` function, `fmt::Writer` should be used + directly to bypass creation of `std::string`: + + ```c++ + fmt::Writer w; + w.write("The answer is {}.", 42); + w.c_str(); // returns a C string + ``` + + This doesn\'t do dynamic memory allocation for small strings and is + less error prone as the lifetime of the string is the same as for + `std::string::c_str` which is well understood (hopefully). + +- Improved consistency in naming functions that are a part of the + public API. Now all public functions are lowercase following the + standard library conventions. Previously it was a combination of + lowercase and CapitalizedWords. Issue + https://github.com/fmtlib/fmt/issues/50. + +- Old functions are marked as deprecated and will be removed in the + next release. + +**Other Changes** + +- Experimental support for printf format specifications (work in + progress): + + ```c++ + fmt::printf("The answer is %d.", 42); + std::string s = fmt::sprintf("Look, a %s!", "string"); + ``` + +- Support for hexadecimal floating point format specifiers `a` and + `A`: + + ```c++ + print("{:a}", -42.0); // Prints -0x1.5p+5 + print("{:A}", -42.0); // Prints -0X1.5P+5 + ``` + +- CMake option `FMT_SHARED` that specifies whether to build format as + a shared library (off by default). + +# 0.9.0 - 2014-05-13 + +- More efficient implementation of variadic formatting functions. + +- `Writer::Format` now has a variadic overload: + + ```c++ + Writer out; + out.Format("Look, I'm {}!", "variadic"); + ``` + +- For efficiency and consistency with other overloads, variadic + overload of the `Format` function now returns `Writer` instead of + `std::string`. Use the `str` function to convert it to + `std::string`: + + ```c++ + std::string s = str(Format("Look, I'm {}!", "variadic")); + ``` + +- Replaced formatter actions with output sinks: `NoAction` -\> + `NullSink`, `Write` -\> `FileSink`, `ColorWriter` -\> + `ANSITerminalSink`. This improves naming consistency and shouldn\'t + affect client code unless these classes are used directly which + should be rarely needed. + +- Added `ThrowSystemError` function that formats a message and throws + `SystemError` containing the formatted message and system-specific + error description. For example, the following code + + ```c++ + FILE *f = fopen(filename, "r"); + if (!f) + ThrowSystemError(errno, "Failed to open file '{}'") << filename; + ``` + + will throw `SystemError` exception with description \"Failed to open + file \'\\': No such file or directory\" if file doesn\'t + exist. + +- Support for AppVeyor continuous integration platform. + +- `Format` now throws `SystemError` in case of I/O errors. + +- Improve test infrastructure. Print functions are now tested by + redirecting the output to a pipe. + +# 0.8.0 - 2014-04-14 + +- Initial release diff --git a/Engine/lib/openal-soft/fmt-11.1.1/doc/api.md b/Engine/lib/openal-soft/fmt-11.1.1/doc/api.md new file mode 100644 index 000000000..e86f0b062 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/doc/api.md @@ -0,0 +1,673 @@ +# API Reference + +The {fmt} library API consists of the following components: + +- [`fmt/base.h`](#base-api): the base API providing main formatting functions + for `char`/UTF-8 with C++20 compile-time checks and minimal dependencies +- [`fmt/format.h`](#format-api): `fmt::format` and other formatting functions + as well as locale support +- [`fmt/ranges.h`](#ranges-api): formatting of ranges and tuples +- [`fmt/chrono.h`](#chrono-api): date and time formatting +- [`fmt/std.h`](#std-api): formatters for standard library types +- [`fmt/compile.h`](#compile-api): format string compilation +- [`fmt/color.h`](#color-api): terminal colors and text styles +- [`fmt/os.h`](#os-api): system APIs +- [`fmt/ostream.h`](#ostream-api): `std::ostream` support +- [`fmt/args.h`](#args-api): dynamic argument lists +- [`fmt/printf.h`](#printf-api): safe `printf` +- [`fmt/xchar.h`](#xchar-api): optional `wchar_t` support + +All functions and types provided by the library reside in namespace `fmt` +and macros have prefix `FMT_`. + +## Base API + +`fmt/base.h` defines the base API which provides main formatting functions +for `char`/UTF-8 with C++20 compile-time checks. It has minimal include +dependencies for better compile times. This header is only beneficial when +using {fmt} as a library (the default) and not in the header-only mode. +It also provides `formatter` specializations for the following types: + +- `int`, `long long`, +- `unsigned`, `unsigned long long` +- `float`, `double`, `long double` +- `bool` +- `char` +- `const char*`, [`fmt::string_view`](#basic_string_view) +- `const void*` + +The following functions use [format string syntax](syntax.md) similar to that +of [str.format](https://docs.python.org/3/library/stdtypes.html#str.format) +in Python. They take *fmt* and *args* as arguments. + +*fmt* is a format string that contains literal text and replacement fields +surrounded by braces `{}`. The fields are replaced with formatted arguments +in the resulting string. [`fmt::format_string`](#format_string) is a format +string which can be implicitly constructed from a string literal or a +`constexpr` string and is checked at compile time in C++20. To pass a runtime +format string wrap it in [`fmt::runtime`](#runtime). + +*args* is an argument list representing objects to be formatted. + +I/O errors are reported as [`std::system_error`]( +https://en.cppreference.com/w/cpp/error/system_error) exceptions unless +specified otherwise. + +::: print(format_string, T&&...) + +::: print(FILE*, format_string, T&&...) + +::: println(format_string, T&&...) + +::: println(FILE*, format_string, T&&...) + +::: format_to(OutputIt&&, format_string, T&&...) + +::: format_to_n(OutputIt, size_t, format_string, T&&...) + +::: format_to_n_result + +::: formatted_size(format_string, T&&...) + + +### Formatting User-Defined Types + +The {fmt} library provides formatters for many standard C++ types. +See [`fmt/ranges.h`](#ranges-api) for ranges and tuples including standard +containers such as `std::vector`, [`fmt/chrono.h`](#chrono-api) for date and +time formatting and [`fmt/std.h`](#std-api) for other standard library types. + +There are two ways to make a user-defined type formattable: providing a +`format_as` function or specializing the `formatter` struct template. + +Use `format_as` if you want to make your type formattable as some other +type with the same format specifiers. The `format_as` function should +take an object of your type and return an object of a formattable type. +It should be defined in the same namespace as your type. + +Example ([run](https://godbolt.org/z/nvME4arz8)): + + #include + + namespace kevin_namespacy { + + enum class film { + house_of_cards, american_beauty, se7en = 7 + }; + + auto format_as(film f) { return fmt::underlying(f); } + + } + + int main() { + fmt::print("{}\n", kevin_namespacy::film::se7en); // Output: 7 + } + +Using specialization is more complex but gives you full control over +parsing and formatting. To use this method specialize the `formatter` +struct template for your type and implement `parse` and `format` +methods. + +The recommended way of defining a formatter is by reusing an existing +one via inheritance or composition. This way you can support standard +format specifiers without implementing them yourself. For example: + +```c++ +// color.h: +#include + +enum class color {red, green, blue}; + +template <> struct fmt::formatter: formatter { + // parse is inherited from formatter. + + auto format(color c, format_context& ctx) const + -> format_context::iterator; +}; +``` + +```c++ +// color.cc: +#include "color.h" +#include + +auto fmt::formatter::format(color c, format_context& ctx) const + -> format_context::iterator { + string_view name = "unknown"; + switch (c) { + case color::red: name = "red"; break; + case color::green: name = "green"; break; + case color::blue: name = "blue"; break; + } + return formatter::format(name, ctx); +} +``` + +Note that `formatter::format` is defined in `fmt/format.h` +so it has to be included in the source file. Since `parse` is inherited +from `formatter` it will recognize all string format +specifications, for example + +```c++ +fmt::format("{:>10}", color::blue) +``` + +will return `" blue"`. + + + +In general the formatter has the following form: + + template <> struct fmt::formatter { + // Parses format specifiers and stores them in the formatter. + // + // [ctx.begin(), ctx.end()) is a, possibly empty, character range that + // contains a part of the format string starting from the format + // specifications to be parsed, e.g. in + // + // fmt::format("{:f} continued", ...); + // + // the range will contain "f} continued". The formatter should parse + // specifiers until '}' or the end of the range. In this example the + // formatter should parse the 'f' specifier and return an iterator + // pointing to '}'. + constexpr auto parse(format_parse_context& ctx) + -> format_parse_context::iterator; + + // Formats value using the parsed format specification stored in this + // formatter and writes the output to ctx.out(). + auto format(const T& value, format_context& ctx) const + -> format_context::iterator; + }; + +It is recommended to at least support fill, align and width that apply +to the whole object and have the same semantics as in standard +formatters. + +You can also write a formatter for a hierarchy of classes: + +```c++ +// demo.h: +#include +#include + +struct A { + virtual ~A() {} + virtual std::string name() const { return "A"; } +}; + +struct B : A { + virtual std::string name() const { return "B"; } +}; + +template +struct fmt::formatter, char>> : + fmt::formatter { + auto format(const A& a, format_context& ctx) const { + return formatter::format(a.name(), ctx); + } +}; +``` + +```c++ +// demo.cc: +#include "demo.h" +#include + +int main() { + B b; + A& a = b; + fmt::print("{}", a); // Output: B +} +``` + +Providing both a `formatter` specialization and a `format_as` overload is +disallowed. + +::: basic_format_parse_context + +::: context + +::: format_context + +### Compile-Time Checks + +Compile-time format string checks are enabled by default on compilers +that support C++20 `consteval`. On older compilers you can use the +[FMT_STRING](#legacy-checks) macro defined in `fmt/format.h` instead. + +Unused arguments are allowed as in Python's `str.format` and ordinary functions. + +See [Type Erasure](#type-erasure) for an example of how to enable compile-time +checks in your own functions with `fmt::format_string` while avoiding template +bloat. + +::: fstring + +::: format_string + +::: runtime(string_view) + +### Type Erasure + +You can create your own formatting function with compile-time checks and +small binary footprint, for example ([run](https://godbolt.org/z/b9Pbasvzc)): + +```c++ +#include + +void vlog(const char* file, int line, + fmt::string_view fmt, fmt::format_args args) { + fmt::print("{}: {}: {}", file, line, fmt::vformat(fmt, args)); +} + +template +void log(const char* file, int line, + fmt::format_string fmt, T&&... args) { + vlog(file, line, fmt, fmt::make_format_args(args...)); +} + +#define MY_LOG(fmt, ...) log(__FILE__, __LINE__, fmt, __VA_ARGS__) + +MY_LOG("invalid squishiness: {}", 42); +``` + +Note that `vlog` is not parameterized on argument types which improves +compile times and reduces binary code size compared to a fully +parameterized version. + +::: make_format_args(T&...) + +::: basic_format_args + +::: format_args + +::: basic_format_arg + +### Named Arguments + +::: arg(const Char*, const T&) + +Named arguments are not supported in compile-time checks at the moment. + +### Compatibility + +::: basic_string_view + +::: string_view + +## Format API + +`fmt/format.h` defines the full format API providing additional +formatting functions and locale support. + + +::: format(format_string, T&&...) + +::: vformat(string_view, format_args) + +::: operator""_a() + +### Utilities + +::: ptr(T) + +::: underlying(Enum) + +::: to_string(const T&) + +::: group_digits(T) + +::: detail::buffer + +::: basic_memory_buffer + +### System Errors + +{fmt} does not use `errno` to communicate errors to the user, but it may +call system functions which set `errno`. Users should not make any +assumptions about the value of `errno` being preserved by library +functions. + +::: system_error + +::: format_system_error + +### Custom Allocators + +The {fmt} library supports custom dynamic memory allocators. A custom +allocator class can be specified as a template argument to +[`fmt::basic_memory_buffer`](#basic_memory_buffer): + + using custom_memory_buffer = + fmt::basic_memory_buffer; + +It is also possible to write a formatting function that uses a custom +allocator: + + using custom_string = + std::basic_string, custom_allocator>; + + auto vformat(custom_allocator alloc, fmt::string_view fmt, + fmt::format_args args) -> custom_string { + auto buf = custom_memory_buffer(alloc); + fmt::vformat_to(std::back_inserter(buf), fmt, args); + return custom_string(buf.data(), buf.size(), alloc); + } + + template + auto format(custom_allocator alloc, fmt::string_view fmt, + const Args& ... args) -> custom_string { + return vformat(alloc, fmt, fmt::make_format_args(args...)); + } + +The allocator will be used for the output container only. Formatting +functions normally don't do any allocations for built-in and string +types except for non-default floating-point formatting that occasionally +falls back on `sprintf`. + +### Locale + +All formatting is locale-independent by default. Use the `'L'` format +specifier to insert the appropriate number separator characters from the +locale: + + #include + #include + + std::locale::global(std::locale("en_US.UTF-8")); + auto s = fmt::format("{:L}", 1000000); // s == "1,000,000" + +`fmt/format.h` provides the following overloads of formatting functions +that take `std::locale` as a parameter. The locale type is a template +parameter to avoid the expensive `` include. + +::: format(detail::locale_ref, format_string, T&&...) + +::: format_to(OutputIt, detail::locale_ref, format_string, T&&...) + +::: formatted_size(detail::locale_ref, format_string, T&&...) + + +### Legacy Compile-Time Checks + +`FMT_STRING` enables compile-time checks on older compilers. It requires +C++14 or later and is a no-op in C++11. + +::: FMT_STRING + +To force the use of legacy compile-time checks, define the preprocessor +variable `FMT_ENFORCE_COMPILE_STRING`. When set, functions accepting +`FMT_STRING` will fail to compile with regular strings. + + +## Range and Tuple Formatting + +`fmt/ranges.h` provides formatting support for ranges and tuples: + + #include + + fmt::print("{}", std::tuple{'a', 42}); + // Output: ('a', 42) + +Using `fmt::join`, you can separate tuple elements with a custom separator: + + #include + + auto t = std::tuple{1, 'a'}; + fmt::print("{}", fmt::join(t, ", ")); + // Output: 1, a + +::: join(Range&&, string_view) + +::: join(It, Sentinel, string_view) + +::: join(std::initializer_list, string_view) + + +## Date and Time Formatting + +`fmt/chrono.h` provides formatters for + +- [`std::chrono::duration`](https://en.cppreference.com/w/cpp/chrono/duration) +- [`std::chrono::time_point`]( + https://en.cppreference.com/w/cpp/chrono/time_point) +- [`std::tm`](https://en.cppreference.com/w/cpp/chrono/c/tm) + +The format syntax is described in [Chrono Format Specifications](syntax.md# +chrono-format-specifications). + +**Example**: + + #include + + int main() { + std::time_t t = std::time(nullptr); + + fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t)); + // Output: The date is 2020-11-07. + // (with 2020-11-07 replaced by the current date) + + using namespace std::literals::chrono_literals; + + fmt::print("Default format: {} {}\n", 42s, 100ms); + // Output: Default format: 42s 100ms + + fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s); + // Output: strftime-like format: 03:15:30 + } + +::: localtime(std::time_t) + +::: gmtime(std::time_t) + + +## Standard Library Types Formatting + +`fmt/std.h` provides formatters for: + +- [`std::atomic`](https://en.cppreference.com/w/cpp/atomic/atomic) +- [`std::atomic_flag`](https://en.cppreference.com/w/cpp/atomic/atomic_flag) +- [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset) +- [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code) +- [`std::exception`](https://en.cppreference.com/w/cpp/error/exception) +- [`std::filesystem::path`](https://en.cppreference.com/w/cpp/filesystem/path) +- [`std::monostate`]( + https://en.cppreference.com/w/cpp/utility/variant/monostate) +- [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional) +- [`std::source_location`]( + https://en.cppreference.com/w/cpp/utility/source_location) +- [`std::thread::id`](https://en.cppreference.com/w/cpp/thread/thread/id) +- [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant/variant) + +::: ptr(const std::unique_ptr&) + +::: ptr(const std::shared_ptr&) + +### Variants + +A `std::variant` is only formattable if every variant alternative is +formattable, and requires the `__cpp_lib_variant` [library +feature](https://en.cppreference.com/w/cpp/feature_test). + +**Example**: + + #include + + fmt::print("{}", std::variant('x')); + // Output: variant('x') + + fmt::print("{}", std::variant()); + // Output: variant(monostate) + +## Bit-Fields and Packed Structs + +To format a bit-field or a field of a struct with `__attribute__((packed))` +applied to it, you need to convert it to the underlying or compatible type via +a cast or a unary `+` ([godbolt](https://www.godbolt.org/z/3qKKs6T5Y)): + +```c++ +struct smol { + int bit : 1; +}; + +auto s = smol(); +fmt::print("{}", +s.bit); +``` + +This is a known limitation of "perfect" forwarding in C++. + + +## Format String Compilation + +`fmt/compile.h` provides format string compilation and compile-time +(`constexpr`) formatting enabled via the `FMT_COMPILE` macro or the `_cf` +user-defined literal defined in namespace `fmt::literals`. Format strings +marked with `FMT_COMPILE` or `_cf` are parsed, checked and converted into +efficient formatting code at compile-time. This supports arguments of built-in +and string types as well as user-defined types with `format` functions taking +the format context type as a template parameter in their `formatter` +specializations. For example: + + template <> struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx); + + template + auto format(const point& p, FormatContext& ctx) const; + }; + +Format string compilation can generate more binary code compared to the +default API and is only recommended in places where formatting is a +performance bottleneck. + +::: FMT_COMPILE + +::: operator""_cf + + +## Terminal Colors and Text Styles + +`fmt/color.h` provides support for terminal color and text style output. + +::: print(const text_style&, format_string, T&&...) + +::: fg(detail::color_type) + +::: bg(detail::color_type) + +::: styled(const T&, text_style) + + +## System APIs + +::: ostream + +::: windows_error + + +## `std::ostream` Support + +`fmt/ostream.h` provides `std::ostream` support including formatting of +user-defined types that have an overloaded insertion operator +(`operator<<`). In order to make a type formattable via `std::ostream` +you should provide a `formatter` specialization inherited from +`ostream_formatter`: + + #include + + struct date { + int year, month, day; + + friend std::ostream& operator<<(std::ostream& os, const date& d) { + return os << d.year << '-' << d.month << '-' << d.day; + } + }; + + template <> struct fmt::formatter : ostream_formatter {}; + + std::string s = fmt::format("The date is {}", date{2012, 12, 9}); + // s == "The date is 2012-12-9" + +::: streamed(const T&) + +::: print(std::ostream&, format_string, T&&...) + + +## Dynamic Argument Lists + +The header `fmt/args.h` provides `dynamic_format_arg_store`, a builder-like API +that can be used to construct format argument lists dynamically. + +::: dynamic_format_arg_store + + +## Safe `printf` + +The header `fmt/printf.h` provides `printf`-like formatting +functionality. The following functions use [printf format string +syntax](https://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html) +with the POSIX extension for positional arguments. Unlike their standard +counterparts, the `fmt` functions are type-safe and throw an exception +if an argument type doesn't match its format specification. + +::: printf(string_view, const T&...) + +::: fprintf(std::FILE*, const S&, const T&...) + +::: sprintf(const S&, const T&...) + + +## Wide Strings + +The optional header `fmt/xchar.h` provides support for `wchar_t` and +exotic character types. + +::: is_char + +::: wstring_view + +::: wformat_context + +::: to_wstring(const T&) + +## Compatibility with C++20 `std::format` + +{fmt} implements nearly all of the [C++20 formatting +library](https://en.cppreference.com/w/cpp/utility/format) with the +following differences: + +- Names are defined in the `fmt` namespace instead of `std` to avoid + collisions with standard library implementations. +- Width calculation doesn't use grapheme clusterization. The latter has + been implemented in a separate branch but hasn't been integrated yet. diff --git a/Engine/lib/openal-soft/fmt-11.1.1/doc/fmt.css b/Engine/lib/openal-soft/fmt-11.1.1/doc/fmt.css new file mode 100644 index 000000000..994d6e2e9 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/doc/fmt.css @@ -0,0 +1,61 @@ +:root { + --md-primary-fg-color: #0050D0; +} + +.md-grid { + max-width: 960px; +} + +@media (min-width: 400px) { + .md-tabs { + display: block; + } +} + +.docblock { + border-left: .05rem solid var(--md-primary-fg-color); +} + +.docblock-desc { + margin-left: 1em; +} + +pre > code.decl { + white-space: pre-wrap; +} + + +code.decl > div { + text-indent: -2ch; /* Negative indent to counteract the indent on the first line */ + padding-left: 2ch; /* Add padding to the left to create an indent */ +} + +.features-container { + display: flex; + flex-wrap: wrap; + gap: 20px; + justify-content: center; /* Center the items horizontally */ +} + +.feature { + flex: 1 1 calc(50% - 20px); /* Two columns with space between */ + max-width: 600px; /* Set the maximum width for the feature boxes */ + box-sizing: border-box; + padding: 10px; + overflow: hidden; /* Hide overflow content */ + text-overflow: ellipsis; /* Handle text overflow */ + white-space: normal; /* Allow text wrapping */ +} + +.feature h2 { + margin-top: 0px; + font-weight: bold; +} + +@media (max-width: 768px) { + .feature { + flex: 1 1 100%; /* Stack columns on smaller screens */ + max-width: 100%; /* Allow full width on smaller screens */ + white-space: normal; /* Allow text wrapping on smaller screens */ + } +} diff --git a/Engine/lib/openal-soft/fmt-11.1.1/doc/fmt.js b/Engine/lib/openal-soft/fmt-11.1.1/doc/fmt.js new file mode 100644 index 000000000..da7e95b77 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/doc/fmt.js @@ -0,0 +1,4 @@ +document$.subscribe(() => { + hljs.highlightAll(), + { language: 'c++' } +}) diff --git a/Engine/lib/openal-soft/fmt-11.1.1/doc/get-started.md b/Engine/lib/openal-soft/fmt-11.1.1/doc/get-started.md new file mode 100644 index 000000000..e61da8829 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/doc/get-started.md @@ -0,0 +1,222 @@ +# Get Started + +Compile and run {fmt} examples online with [Compiler Explorer]( +https://godbolt.org/z/P7h6cd6o3). + +{fmt} is compatible with any build system. The next section describes its usage +with CMake, while the [Build Systems](#build-systems) section covers the rest. + +## CMake + +{fmt} provides two CMake targets: `fmt::fmt` for the compiled library and +`fmt::fmt-header-only` for the header-only library. It is recommended to use +the compiled library for improved build times. + +There are three primary ways to use {fmt} with CMake: + +* **FetchContent**: Starting from CMake 3.11, you can use [`FetchContent`]( + https://cmake.org/cmake/help/v3.30/module/FetchContent.html) to automatically + download {fmt} as a dependency at configure time: + + include(FetchContent) + + FetchContent_Declare( + fmt + GIT_REPOSITORY https://github.com/fmtlib/fmt + GIT_TAG e69e5f977d458f2650bb346dadf2ad30c5320281) # 10.2.1 + FetchContent_MakeAvailable(fmt) + + target_link_libraries( fmt::fmt) + +* **Installed**: You can find and use an [installed](#installation) version of + {fmt} in your `CMakeLists.txt` file as follows: + + find_package(fmt) + target_link_libraries( fmt::fmt) + +* **Embedded**: You can add the {fmt} source tree to your project and include it + in your `CMakeLists.txt` file: + + add_subdirectory(fmt) + target_link_libraries( fmt::fmt) + +## Installation + +### Debian/Ubuntu + +To install {fmt} on Debian, Ubuntu, or any other Debian-based Linux +distribution, use the following command: + + apt install libfmt-dev + +### Homebrew + +Install {fmt} on macOS using [Homebrew](https://brew.sh/): + + brew install fmt + +### Conda + +Install {fmt} on Linux, macOS, and Windows with [Conda]( +https://docs.conda.io/en/latest/), using its [conda-forge package]( +https://github.com/conda-forge/fmt-feedstock): + + conda install -c conda-forge fmt + +### vcpkg + +Download and install {fmt} using the vcpkg package manager: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install fmt + + + +## Building from Source + +CMake works by generating native makefiles or project files that can be +used in the compiler environment of your choice. The typical workflow +starts with: + + mkdir build # Create a directory to hold the build output. + cd build + cmake .. # Generate native build scripts. + +run in the `fmt` repository. + +If you are on a Unix-like system, you should now see a Makefile in the +current directory. Now you can build the library by running `make`. + +Once the library has been built you can invoke `make test` to run the tests. + +You can control generation of the make `test` target with the `FMT_TEST` +CMake option. This can be useful if you include fmt as a subdirectory in +your project but don't want to add fmt's tests to your `test` target. + +To build a shared library set the `BUILD_SHARED_LIBS` CMake variable to `TRUE`: + + cmake -DBUILD_SHARED_LIBS=TRUE .. + +To build a static library with position-independent code (e.g. for +linking it into another shared library such as a Python extension), set the +`CMAKE_POSITION_INDEPENDENT_CODE` CMake variable to `TRUE`: + + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. + +After building the library you can install it on a Unix-like system by +running `sudo make install`. + +### Building the Docs + +To build the documentation you need the following software installed on +your system: + +- [Python](https://www.python.org/) +- [Doxygen](http://www.stack.nl/~dimitri/doxygen/) +- [MkDocs](https://www.mkdocs.org/) with `mkdocs-material`, `mkdocstrings`, + `pymdown-extensions` and `mike` + +First generate makefiles or project files using CMake as described in +the previous section. Then compile the `doc` target/project, for example: + + make doc + +This will generate the HTML documentation in `doc/html`. + +## Build Systems + +### build2 + +You can use [build2](https://build2.org), a dependency manager and a build +system, to use {fmt}. + +Currently this package is available in these package repositories: + +- for released and published versions. +- for unreleased or custom versions. + +**Usage:** + +- `build2` package name: `fmt` +- Library target name: `lib{fmt}` + +To make your `build2` project depend on `fmt`: + +- Add one of the repositories to your configurations, or in your + `repositories.manifest`, if not already there: + + : + role: prerequisite + location: https://pkg.cppget.org/1/stable + +- Add this package as a dependency to your `manifest` file (example + for version 10): + + depends: fmt ~10.0.0 + +- Import the target and use it as a prerequisite to your own target + using `fmt` in the appropriate `buildfile`: + + import fmt = fmt%lib{fmt} + lib{mylib} : cxx{**} ... $fmt + +Then build your project as usual with `b` or `bdep update`. + +### Meson + +[Meson WrapDB](https://mesonbuild.com/Wrapdb-projects.html) includes an `fmt` +package. + +**Usage:** + +- Install the `fmt` subproject from the WrapDB by running: + + meson wrap install fmt + + from the root of your project. + +- In your project's `meson.build` file, add an entry for the new subproject: + + fmt = subproject('fmt') + fmt_dep = fmt.get_variable('fmt_dep') + +- Include the new dependency object to link with fmt: + + my_build_target = executable( + 'name', 'src/main.cc', dependencies: [fmt_dep]) + +**Options:** + +If desired, {fmt} can be built as a static library, or as a header-only library. + +For a static build, use the following subproject definition: + + fmt = subproject('fmt', default_options: 'default_library=static') + fmt_dep = fmt.get_variable('fmt_dep') + +For the header-only version, use: + + fmt = subproject('fmt') + fmt_dep = fmt.get_variable('fmt_header_only_dep') + +### Android NDK + +{fmt} provides [Android.mk file]( +https://github.com/fmtlib/fmt/blob/master/support/Android.mk) that can be used +to build the library with [Android NDK]( +https://developer.android.com/tools/sdk/ndk/index.html). + +### Other + +To use the {fmt} library with any other build system, add +`include/fmt/base.h`, `include/fmt/format.h`, `include/fmt/format-inl.h`, +`src/format.cc` and optionally other headers from a [release archive]( +https://github.com/fmtlib/fmt/releases) or the [git repository]( +https://github.com/fmtlib/fmt) to your project, add `include` to include +directories and make sure `src/format.cc` is compiled and linked with your code. diff --git a/Engine/lib/openal-soft/fmt-11.1.1/doc/index.md b/Engine/lib/openal-soft/fmt-11.1.1/doc/index.md new file mode 100644 index 000000000..4f28e114f --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/doc/index.md @@ -0,0 +1,151 @@ +--- +hide: + - navigation + - toc +--- + +# A modern formatting library + +
+ +
+

Safety

+

+ Inspired by Python's formatting facility, {fmt} provides a safe replacement + for the printf family of functions. Errors in format strings, + which are a common source of vulnerabilities in C, are reported at + compile time. For example: + +

fmt::format("{:d}", "I am not a number");
+ + will give a compile-time error because d is not a valid + format specifier for strings. APIs like + fmt::format prevent buffer overflow errors via + automatic memory management. +

+→ Learn more +
+ +
+

Extensibility

+

+ Formatting of most standard types, including all containers, dates, + and times is supported out-of-the-box. For example: + +

fmt::print("{}", std::vector{1, 2, 3});
+ + prints the vector in a JSON-like format: + +
[1, 2, 3]
+ + You can make your own types formattable and even make compile-time + checks work for them. +

+→ Learn more +
+ +
+

Performance

+

+ {fmt} can be anywhere from tens of percent to 20-30 times faster than + iostreams and sprintf, especially for numeric formatting. + + + + + + The library minimizes dynamic memory allocations and can optionally + compile format strings to optimal code. +

+
+ +
+

Unicode support

+

+ {fmt} provides portable Unicode support on major operating systems + with UTF-8 and char strings. For example: + +

fmt::print("Слава Україні!");
+ + will be printed correctly on Linux, macOS, and even Windows console, + irrespective of the codepages. +

+

+ The default is locale-independent, but you can opt into localized + formatting and {fmt} makes it work with Unicode, addressing issues in the + standard libary. +

+
+ +
+

Fast compilation

+

+ The library makes extensive use of type erasure to achieve fast + compilation. fmt/base.h provides a subset of the API with + minimal include dependencies and enough functionality to replace + all uses of *printf. +

+

+ Code using {fmt} is usually several times faster to compile than the + equivalent iostreams code, and while printf compiles faster + still, the gap is narrowing. +

+ +→ Learn more +
+ +
+

Small binary footprint

+

+ Type erasure is also used to prevent template bloat, resulting in compact + per-call binary code. For example, a call to fmt::print with + a single argument is just a few + instructions, comparable to printf despite adding + runtime safety, and much smaller than the equivalent iostreams code. +

+

+ The library itself has small binary footprint and some components such as + floating-point formatting can be disabled to make it even smaller for + resource-constrained devices. +

+
+ +
+

Portability

+

+ {fmt} has a small self-contained codebase with the core consisting of + just three headers and no external dependencies. +

+

+ The library is highly portable and requires only a minimal subset of + C++11 features which are available in GCC 4.9, Clang 3.4, MSVC 19.10 + (2017) and later. Newer compiler and standard library features are used + if available, and enable additional functionality. +

+

+ Where possible, the output of formatting functions is consistent across + platforms. +

+

+
+ +
+

Open source

+

+ {fmt} is in the top hundred open-source C++ libraries on GitHub and has + hundreds of + all-time contributors. +

+

+ The library is distributed under a permissive MIT + license and is + relied upon by many open-source projects, including Blender, PyTorch, + Apple's FoundationDB, Windows Terminal, MongoDB, and others. +

+
+ +
diff --git a/Engine/lib/openal-soft/fmt-11.1.1/doc/perf.svg b/Engine/lib/openal-soft/fmt-11.1.1/doc/perf.svg new file mode 100644 index 000000000..7867a1544 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/doc/perf.svg @@ -0,0 +1 @@ +double to string02505007501,0001,2501,500ostringstreamostrstreamsprintfdoubleconvfmtTime (ns), smaller is better \ No newline at end of file diff --git a/Engine/lib/openal-soft/fmt-11.1.1/doc/python-license.txt b/Engine/lib/openal-soft/fmt-11.1.1/doc/python-license.txt new file mode 100644 index 000000000..88eed1f9c --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/doc/python-license.txt @@ -0,0 +1,290 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations (now Zope +Corporation, see http://www.zope.com). In 2001, the Python Software +Foundation (PSF, see http://www.python.org/psf/) was formed, a +non-profit organization created specifically to own Python-related +Intellectual Property. Zope Corporation is a sponsoring member of +the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.2 2.1.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2.1 2.2 2002 PSF yes + 2.2.2 2.2.1 2002 PSF yes + 2.2.3 2.2.2 2003 PSF yes + 2.3 2.2.2 2002-2003 PSF yes + 2.3.1 2.3 2002-2003 PSF yes + 2.3.2 2.3.1 2002-2003 PSF yes + 2.3.3 2.3.2 2002-2003 PSF yes + 2.3.4 2.3.3 2004 PSF yes + 2.3.5 2.3.4 2005 PSF yes + 2.4 2.3 2004 PSF yes + 2.4.1 2.4 2005 PSF yes + 2.4.2 2.4.1 2005 PSF yes + 2.4.3 2.4.2 2006 PSF yes + 2.4.4 2.4.3 2006 PSF yes + 2.5 2.4 2006 PSF yes + 2.5.1 2.5 2007 PSF yes + 2.5.2 2.5.1 2008 PSF yes + 2.5.3 2.5.2 2008 PSF yes + 2.6 2.5 2008 PSF yes + 2.6.1 2.6 2008 PSF yes + 2.6.2 2.6.1 2009 PSF yes + 2.6.3 2.6.2 2009 PSF yes + 2.6.4 2.6.3 2009 PSF yes + 2.6.5 2.6.4 2010 PSF yes + 3.0 2.6 2008 PSF yes + 3.0.1 3.0 2009 PSF yes + 3.1 3.0.1 2009 PSF yes + 3.1.1 3.1 2009 PSF yes + 3.1.2 3.1.1 2010 PSF yes + 3.1.3 3.1.2 2010 PSF yes + 3.1.4 3.1.3 2011 PSF yes + 3.2 3.1 2011 PSF yes + 3.2.1 3.2 2011 PSF yes + 3.2.2 3.2.1 2011 PSF yes + 3.2.3 3.2.2 2012 PSF yes + 3.3.0 3.2 2012 PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012 Python Software Foundation; All Rights Reserved" are retained in Python +alone or in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +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 Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Engine/lib/openal-soft/fmt-11.1.1/doc/syntax.md b/Engine/lib/openal-soft/fmt-11.1.1/doc/syntax.md new file mode 100644 index 000000000..46d7d2fd2 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/doc/syntax.md @@ -0,0 +1,886 @@ +# Format String Syntax + +Formatting functions such as [`fmt::format`](api.md#format) and [`fmt::print`]( +api.md#print) use the same format string syntax described in this section. + +Format strings contain "replacement fields" surrounded by curly braces `{}`. +Anything that is not contained in braces is considered literal text, which is +copied unchanged to the output. If you need to include a brace character in +the literal text, it can be escaped by doubling: `{{` and `}}`. + +The grammar for a replacement field is as follows: + + +
replacement_field ::= "{" [arg_id] [":" (format_spec | chrono_format_spec)] "}"
+arg_id            ::= integer | identifier
+integer           ::= digit+
+digit             ::= "0"..."9"
+identifier        ::= id_start id_continue*
+id_start          ::= "a"..."z" | "A"..."Z" | "_"
+id_continue       ::= id_start | digit
+
+ +In less formal terms, the replacement field can start with an *arg_id* that +specifies the argument whose value is to be formatted and inserted into the +output instead of the replacement field. The *arg_id* is optionally followed +by a *format_spec*, which is preceded by a colon `':'`. These specify a +non-default format for the replacement value. + +See also the [Format Specification +Mini-Language](#format-specification-mini-language) section. + +If the numerical arg_ids in a format string are 0, 1, 2, ... in sequence, +they can all be omitted (not just some) and the numbers 0, 1, 2, ... will be +automatically inserted in that order. + +Named arguments can be referred to by their names or indices. + +Some simple format string examples: + +```c++ +"First, thou shalt count to {0}" // References the first argument +"Bring me a {}" // Implicitly references the first argument +"From {} to {}" // Same as "From {0} to {1}" +``` + +The *format_spec* field contains a specification of how the value should +be presented, including such details as field width, alignment, padding, +decimal precision and so on. Each value type can define its own +"formatting mini-language" or interpretation of the *format_spec*. + +Most built-in types support a common formatting mini-language, which is +described in the next section. + +A *format_spec* field can also include nested replacement fields in +certain positions within it. These nested replacement fields can contain +only an argument id; format specifications are not allowed. This allows +the formatting of a value to be dynamically specified. + +See the [Format Examples](#format-examples) section for some examples. + +## Format Specification Mini-Language + +"Format specifications" are used within replacement fields contained within a +format string to define how individual values are presented. Each formattable +type may define how the format specification is to be interpreted. + +Most built-in types implement the following options for format +specifications, although some of the formatting options are only +supported by the numeric types. + +The general form of a *standard format specifier* is: + + +
format_spec ::= [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
+fill        ::= <a character other than '{' or '}'>
+align       ::= "<" | ">" | "^"
+sign        ::= "+" | "-" | " "
+width       ::= integer | "{" [arg_id] "}"
+precision   ::= integer | "{" [arg_id] "}"
+type        ::= "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" |
+                "g" | "G" | "o" | "p" | "s" | "x" | "X" | "?"
+
+ +The *fill* character can be any Unicode code point other than `'{'` or `'}'`. +The presence of a fill character is signaled by the character following it, +which must be one of the alignment options. If the second character of +*format_spec* is not a valid alignment option, then it is assumed that both +the fill character and the alignment option are absent. + +The meaning of the various alignment options is as follows: + + + + + + + + + + + + + + + + + + +
OptionMeaning
'<' + Forces the field to be left-aligned within the available space (this is the + default for most objects). +
'>' + Forces the field to be right-aligned within the available space (this is + the default for numbers). +
'^'Forces the field to be centered within the available space.
+ +Note that unless a minimum field width is defined, the field width will +always be the same size as the data to fill it, so that the alignment +option has no meaning in this case. + +The *sign* option is only valid for floating point and signed integer types, +and can be one of the following: + + + + + + + + + + + + + + + + + + +
OptionMeaning
'+' + Indicates that a sign should be used for both nonnegative as well as + negative numbers. +
'-' + Indicates that a sign should be used only for negative numbers (this is the + default behavior). +
space + Indicates that a leading space should be used on nonnegative numbers, and a + minus sign on negative numbers. +
+ +The `'#'` option causes the "alternate form" to be used for the +conversion. The alternate form is defined differently for different +types. This option is only valid for integer and floating-point types. +For integers, when binary, octal, or hexadecimal output is used, this +option adds the prefix respective `"0b"` (`"0B"`), `"0"`, or `"0x"` +(`"0X"`) to the output value. Whether the prefix is lower-case or +upper-case is determined by the case of the type specifier, for example, +the prefix `"0x"` is used for the type `'x'` and `"0X"` is used for +`'X'`. For floating-point numbers the alternate form causes the result +of the conversion to always contain a decimal-point character, even if +no digits follow it. Normally, a decimal-point character appears in the +result of these conversions only if a digit follows it. In addition, for +`'g'` and `'G'` conversions, trailing zeros are not removed from the +result. + +*width* is a decimal integer defining the minimum field width. If not +specified, then the field width will be determined by the content. + +Preceding the *width* field by a zero (`'0'`) character enables +sign-aware zero-padding for numeric types. It forces the padding to be +placed after the sign or base (if any) but before the digits. This is +used for printing fields in the form "+000000120". This option is only +valid for numeric types and it has no effect on formatting of infinity +and NaN. This option is ignored when any alignment specifier is present. + +The *precision* is a decimal number indicating how many digits should be +displayed after the decimal point for a floating-point value formatted +with `'f'` and `'F'`, or before and after the decimal point for a +floating-point value formatted with `'g'` or `'G'`. For non-number types +the field indicates the maximum field size - in other words, how many +characters will be used from the field content. The *precision* is not +allowed for integer, character, Boolean, and pointer values. Note that a +C string must be null-terminated even if precision is specified. + +The `'L'` option uses the current locale setting to insert the appropriate +number separator characters. This option is only valid for numeric types. + +Finally, the *type* determines how the data should be presented. + +The available string presentation types are: + + + + + + + + + + + + + + + + + + +
TypeMeaning
's' + String format. This is the default type for strings and may be omitted. +
'?'Debug format. The string is quoted and special characters escaped.
noneThe same as 's'.
+ +The available character presentation types are: + + + + + + + + + + + + + + + + + + +
TypeMeaning
'c' + Character format. This is the default type for characters and may be + omitted. +
'?'Debug format. The character is quoted and special characters escaped.
noneThe same as 'c'.
+ +The available integer presentation types are: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeMeaning
'b' + Binary format. Outputs the number in base 2. Using the '#' + option with this type adds the prefix "0b" to the output value. +
'B' + Binary format. Outputs the number in base 2. Using the '#' + option with this type adds the prefix "0B" to the output value. +
'c'Character format. Outputs the number as a character.
'd'Decimal integer. Outputs the number in base 10.
'o'Octal format. Outputs the number in base 8.
'x' + Hex format. Outputs the number in base 16, using lower-case letters for the + digits above 9. Using the '#' option with this type adds the + prefix "0x" to the output value. +
'X' + Hex format. Outputs the number in base 16, using upper-case letters for the + digits above 9. Using the '#' option with this type adds the + prefix "0X" to the output value. +
noneThe same as 'd'.
+ +Integer presentation types can also be used with character and Boolean values +with the only exception that `'c'` cannot be used with `bool`. Boolean values +are formatted using textual representation, either `true` or `false`, if the +presentation type is not specified. + +The available presentation types for floating-point values are: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeMeaning
'a' + Hexadecimal floating point format. Prints the number in base 16 with + prefix "0x" and lower-case letters for digits above 9. + Uses 'p' to indicate the exponent. +
'A' + Same as 'a' except it uses upper-case letters for the + prefix, digits above 9 and to indicate the exponent. +
'e' + Exponent notation. Prints the number in scientific notation using + the letter 'e' to indicate the exponent. +
'E' + Exponent notation. Same as 'e' except it uses an + upper-case 'E' as the separator character. +
'f'Fixed point. Displays the number as a fixed-point number.
'F' + Fixed point. Same as 'f', but converts nan + to NAN and inf to INF. +
'g' +

General format. For a given precision p >= 1, + this rounds the number to p significant digits and then + formats the result in either fixed-point format or in scientific + notation, depending on its magnitude.

+

A precision of 0 is treated as equivalent to a precision + of 1.

+
'G' + General format. Same as 'g' except switches to + 'E' if the number gets too large. The representations of + infinity and NaN are uppercased, too. +
none + Similar to 'g', except that the default precision is as + high as needed to represent the particular value. +
+ +The available presentation types for pointers are: + + + + + + + + + + + + + + +
TypeMeaning
'p' + Pointer format. This is the default type for pointers and may be omitted. +
noneThe same as 'p'.
+ +## Chrono Format Specifications + +Format specifications for chrono duration and time point types as well as +`std::tm` have the following syntax: + + +
chrono_format_spec ::= [[fill]align][width]["." precision][chrono_specs]
+chrono_specs       ::= conversion_spec |
+                       chrono_specs (conversion_spec | literal_char)
+conversion_spec    ::= "%" [padding_modifier] [locale_modifier] chrono_type
+literal_char       ::= <a character other than '{', '}' or '%'>
+padding_modifier   ::= "-" | "_"  | "0"
+locale_modifier    ::= "E" | "O"
+chrono_type        ::= "a" | "A" | "b" | "B" | "c" | "C" | "d" | "D" | "e" |
+                       "F" | "g" | "G" | "h" | "H" | "I" | "j" | "m" | "M" |
+                       "n" | "p" | "q" | "Q" | "r" | "R" | "S" | "t" | "T" |
+                       "u" | "U" | "V" | "w" | "W" | "x" | "X" | "y" | "Y" |
+                       "z" | "Z" | "%"
+
+ +Literal chars are copied unchanged to the output. Precision is valid only +for `std::chrono::duration` types with a floating-point representation type. + +The available presentation types (*chrono_type*) are: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeMeaning
'a' + The abbreviated weekday name, e.g. "Sat". If the value does not contain a + valid weekday, an exception of type format_error is thrown. +
'A' + The full weekday name, e.g. "Saturday". If the value does not contain a + valid weekday, an exception of type format_error is thrown. +
'b' + The abbreviated month name, e.g. "Nov". If the value does not contain a + valid month, an exception of type format_error is thrown. +
'B' + The full month name, e.g. "November". If the value does not contain a valid + month, an exception of type format_error is thrown. +
'c' + The date and time representation, e.g. "Sat Nov 12 22:04:00 1955". The + modified command %Ec produces the locale's alternate date and + time representation. +
'C' + The year divided by 100 using floored division, e.g. "19". If the result + is a single decimal digit, it is prefixed with 0. The modified command + %EC produces the locale's alternative representation of the + century. +
'd' + The day of month as a decimal number. If the result is a single decimal + digit, it is prefixed with 0. The modified command %Od + produces the locale's alternative representation. +
'D'Equivalent to %m/%d/%y, e.g. "11/12/55".
'e' + The day of month as a decimal number. If the result is a single decimal + digit, it is prefixed with a space. The modified command %Oe + produces the locale's alternative representation. +
'F'Equivalent to %Y-%m-%d, e.g. "1955-11-12".
'g' + The last two decimal digits of the ISO week-based year. If the result is a + single digit it is prefixed by 0. +
'G' + The ISO week-based year as a decimal number. If the result is less than + four digits it is left-padded with 0 to four digits. +
'h'Equivalent to %b, e.g. "Nov".
'H' + The hour (24-hour clock) as a decimal number. If the result is a single + digit, it is prefixed with 0. The modified command %OH + produces the locale's alternative representation. +
'I' + The hour (12-hour clock) as a decimal number. If the result is a single + digit, it is prefixed with 0. The modified command %OI + produces the locale's alternative representation. +
'j' + If the type being formatted is a specialization of duration, the decimal + number of days without padding. Otherwise, the day of the year as a decimal + number. Jan 1 is 001. If the result is less than three digits, it is + left-padded with 0 to three digits. +
'm' + The month as a decimal number. Jan is 01. If the result is a single digit, + it is prefixed with 0. The modified command %Om produces the + locale's alternative representation. +
'M' + The minute as a decimal number. If the result is a single digit, it + is prefixed with 0. The modified command %OM produces the + locale's alternative representation. +
'n'A new-line character.
'p'The AM/PM designations associated with a 12-hour clock.
'q'The duration's unit suffix.
'Q' + The duration's numeric value (as if extracted via .count()). +
'r'The 12-hour clock time, e.g. "10:04:00 PM".
'R'Equivalent to %H:%M, e.g. "22:04".
'S' + Seconds as a decimal number. If the number of seconds is less than 10, the + result is prefixed with 0. If the precision of the input cannot be exactly + represented with seconds, then the format is a decimal floating-point number + with a fixed format and a precision matching that of the precision of the + input (or to a microseconds precision if the conversion to floating-point + decimal seconds cannot be made within 18 fractional digits). The modified + command %OS produces the locale's alternative representation. +
't'A horizontal-tab character.
'T'Equivalent to %H:%M:%S.
'u' + The ISO weekday as a decimal number (1-7), where Monday is 1. The modified + command %Ou produces the locale's alternative representation. +
'U' + The week number of the year as a decimal number. The first Sunday of the + year is the first day of week 01. Days of the same year prior to that are + in week 00. If the result is a single digit, it is prefixed with 0. + The modified command %OU produces the locale's alternative + representation. +
'V' + The ISO week-based week number as a decimal number. If the result is a + single digit, it is prefixed with 0. The modified command %OV + produces the locale's alternative representation. +
'w' + The weekday as a decimal number (0-6), where Sunday is 0. The modified + command %Ow produces the locale's alternative representation. +
'W' + The week number of the year as a decimal number. The first Monday of the + year is the first day of week 01. Days of the same year prior to that are + in week 00. If the result is a single digit, it is prefixed with 0. + The modified command %OW produces the locale's alternative + representation. +
'x' + The date representation, e.g. "11/12/55". The modified command + %Ex produces the locale's alternate date representation. +
'X' + The time representation, e.g. "10:04:00". The modified command + %EX produces the locale's alternate time representation. +
'y' + The last two decimal digits of the year. If the result is a single digit + it is prefixed by 0. The modified command %Oy produces the + locale's alternative representation. The modified command %Ey + produces the locale's alternative representation of offset from + %EC (year only). +
'Y' + The year as a decimal number. If the result is less than four digits it is + left-padded with 0 to four digits. The modified command %EY + produces the locale's alternative full year representation. +
'z' + The offset from UTC in the ISO 8601:2004 format. For example -0430 refers + to 4 hours 30 minutes behind UTC. If the offset is zero, +0000 is used. + The modified commands %Ez and %Oz insert a + : between the hours and minutes: -04:30. If the offset + information is not available, an exception of type + format_error is thrown. +
'Z' + The time zone abbreviation. If the time zone abbreviation is not available, + an exception of type format_error is thrown. +
'%'A % character.
+ +Specifiers that have a calendaric component such as `'d'` (the day of month) +are valid only for `std::tm` and time points but not durations. + +The available padding modifiers (*padding_modifier*) are: + +| Type | Meaning | +|-------|-----------------------------------------| +| `'_'` | Pad a numeric result with spaces. | +| `'-'` | Do not pad a numeric result string. | +| `'0'` | Pad a numeric result string with zeros. | + +These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`, +`'V'`, `'W'`, `'Y'`, `'d'`, `'j'` and `'m'` presentation types. + +## Range Format Specifications + +Format specifications for range types have the following syntax: + +
range_format_spec ::= ["n"][range_type][range_underlying_spec]
+
+ +The `'n'` option formats the range without the opening and closing brackets. + +The available presentation types for `range_type` are: + +| Type | Meaning | +|--------|------------------------------------------------------------| +| none | Default format. | +| `'s'` | String format. The range is formatted as a string. | +| `'?⁠s'` | Debug format. The range is formatted as an escaped string. | + +If `range_type` is `'s'` or `'?s'`, the range element type must be a character +type. The `'n'` option and `range_underlying_spec` are mutually exclusive with +`'s'` and `'?s'`. + +The `range_underlying_spec` is parsed based on the formatter of the range's +element type. + +By default, a range of characters or strings is printed escaped and quoted. +But if any `range_underlying_spec` is provided (even if it is empty), then the +characters or strings are printed according to the provided specification. + +Examples: + +```c++ +fmt::print("{}", std::vector{10, 20, 30}); +// Output: [10, 20, 30] +fmt::print("{::#x}", std::vector{10, 20, 30}); +// Output: [0xa, 0x14, 0x1e] +fmt::print("{}", std::vector{'h', 'e', 'l', 'l', 'o'}); +// Output: ['h', 'e', 'l', 'l', 'o'] +fmt::print("{:n}", std::vector{'h', 'e', 'l', 'l', 'o'}); +// Output: 'h', 'e', 'l', 'l', 'o' +fmt::print("{:s}", std::vector{'h', 'e', 'l', 'l', 'o'}); +// Output: "hello" +fmt::print("{:?s}", std::vector{'h', 'e', 'l', 'l', 'o', '\n'}); +// Output: "hello\n" +fmt::print("{::}", std::vector{'h', 'e', 'l', 'l', 'o'}); +// Output: [h, e, l, l, o] +fmt::print("{::d}", std::vector{'h', 'e', 'l', 'l', 'o'}); +// Output: [104, 101, 108, 108, 111] +``` + +## Format Examples + +This section contains examples of the format syntax and comparison with +the printf formatting. + +In most of the cases the syntax is similar to the printf formatting, +with the addition of the `{}` and with `:` used instead of `%`. For +example, `"%03.2f"` can be translated to `"{:03.2f}"`. + +The new format syntax also supports new and different options, shown in +the following examples. + +Accessing arguments by position: + +```c++ +fmt::format("{0}, {1}, {2}", 'a', 'b', 'c'); +// Result: "a, b, c" +fmt::format("{}, {}, {}", 'a', 'b', 'c'); +// Result: "a, b, c" +fmt::format("{2}, {1}, {0}", 'a', 'b', 'c'); +// Result: "c, b, a" +fmt::format("{0}{1}{0}", "abra", "cad"); // arguments' indices can be repeated +// Result: "abracadabra" +``` + +Aligning the text and specifying a width: + +```c++ +fmt::format("{:<30}", "left aligned"); +// Result: "left aligned " +fmt::format("{:>30}", "right aligned"); +// Result: " right aligned" +fmt::format("{:^30}", "centered"); +// Result: " centered " +fmt::format("{:*^30}", "centered"); // use '*' as a fill char +// Result: "***********centered***********" +``` + +Dynamic width: + +```c++ +fmt::format("{:<{}}", "left aligned", 30); +// Result: "left aligned " +``` + +Dynamic precision: + +```c++ +fmt::format("{:.{}f}", 3.14, 1); +// Result: "3.1" +``` + +Replacing `%+f`, `%-f`, and `% f` and specifying a sign: + +```c++ +fmt::format("{:+f}; {:+f}", 3.14, -3.14); // show it always +// Result: "+3.140000; -3.140000" +fmt::format("{: f}; {: f}", 3.14, -3.14); // show a space for positive numbers +// Result: " 3.140000; -3.140000" +fmt::format("{:-f}; {:-f}", 3.14, -3.14); // show only the minus -- same as '{:f}; {:f}' +// Result: "3.140000; -3.140000" +``` + +Replacing `%x` and `%o` and converting the value to different bases: + +```c++ +fmt::format("int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); +// Result: "int: 42; hex: 2a; oct: 52; bin: 101010" +// with 0x or 0 or 0b as prefix: +fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}", 42); +// Result: "int: 42; hex: 0x2a; oct: 052; bin: 0b101010" +``` + +Padded hex byte with prefix and always prints both hex characters: + +```c++ +fmt::format("{:#04x}", 0); +// Result: "0x00" +``` + +Box drawing using Unicode fill: + +```c++ +fmt::print( + "┌{0:─^{2}}┐\n" + "│{1: ^{2}}│\n" + "└{0:─^{2}}┘\n", "", "Hello, world!", 20); +``` + +prints: + +``` +┌────────────────────┐ +│ Hello, world! │ +└────────────────────┘ +``` + +Using type-specific formatting: + +```c++ +#include + +auto t = tm(); +t.tm_year = 2010 - 1900; +t.tm_mon = 7; +t.tm_mday = 4; +t.tm_hour = 12; +t.tm_min = 15; +t.tm_sec = 58; +fmt::print("{:%Y-%m-%d %H:%M:%S}", t); +// Prints: 2010-08-04 12:15:58 +``` + +Using the comma as a thousands separator: + +```c++ +#include + +auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890); +// s == "1,234,567,890" +``` diff --git a/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/args.h b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/args.h new file mode 100644 index 000000000..3ff478807 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/args.h @@ -0,0 +1,220 @@ +// Formatting library for C++ - dynamic argument lists +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_ARGS_H_ +#define FMT_ARGS_H_ + +#ifndef FMT_MODULE +# include // std::reference_wrapper +# include // std::unique_ptr +# include +#endif + +#include "format.h" // std_string_view + +FMT_BEGIN_NAMESPACE +namespace detail { + +template struct is_reference_wrapper : std::false_type {}; +template +struct is_reference_wrapper> : std::true_type {}; + +template auto unwrap(const T& v) -> const T& { return v; } +template +auto unwrap(const std::reference_wrapper& v) -> const T& { + return static_cast(v); +} + +// node is defined outside dynamic_arg_list to workaround a C2504 bug in MSVC +// 2022 (v17.10.0). +// +// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for +// templates it doesn't complain about inability to deduce single translation +// unit for placing vtable. So node is made a fake template. +template struct node { + virtual ~node() = default; + std::unique_ptr> next; +}; + +class dynamic_arg_list { + template struct typed_node : node<> { + T value; + + template + FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} + + template + FMT_CONSTEXPR typed_node(const basic_string_view& arg) + : value(arg.data(), arg.size()) {} + }; + + std::unique_ptr> head_; + + public: + template auto push(const Arg& arg) -> const T& { + auto new_node = std::unique_ptr>(new typed_node(arg)); + auto& value = new_node->value; + new_node->next = std::move(head_); + head_ = std::move(new_node); + return value; + } +}; +} // namespace detail + +/** + * A dynamic list of formatting arguments with storage. + * + * It can be implicitly converted into `fmt::basic_format_args` for passing + * into type-erased formatting functions such as `fmt::vformat`. + */ +template class dynamic_format_arg_store { + private: + using char_type = typename Context::char_type; + + template struct need_copy { + static constexpr detail::type mapped_type = + detail::mapped_type_constant::value; + + enum { + value = !(detail::is_reference_wrapper::value || + std::is_same>::value || + std::is_same>::value || + (mapped_type != detail::type::cstring_type && + mapped_type != detail::type::string_type && + mapped_type != detail::type::custom_type)) + }; + }; + + template + using stored_t = conditional_t< + std::is_convertible>::value && + !detail::is_reference_wrapper::value, + std::basic_string, T>; + + // Storage of basic_format_arg must be contiguous. + std::vector> data_; + std::vector> named_info_; + + // Storage of arguments not fitting into basic_format_arg must grow + // without relocation because items in data_ refer to it. + detail::dynamic_arg_list dynamic_args_; + + friend class basic_format_args; + + auto data() const -> const basic_format_arg* { + return named_info_.empty() ? data_.data() : data_.data() + 1; + } + + template void emplace_arg(const T& arg) { + data_.emplace_back(arg); + } + + template + void emplace_arg(const detail::named_arg& arg) { + if (named_info_.empty()) + data_.insert(data_.begin(), basic_format_arg(nullptr, 0)); + data_.emplace_back(detail::unwrap(arg.value)); + auto pop_one = [](std::vector>* data) { + data->pop_back(); + }; + std::unique_ptr>, decltype(pop_one)> + guard{&data_, pop_one}; + named_info_.push_back({arg.name, static_cast(data_.size() - 2u)}); + data_[0] = {named_info_.data(), named_info_.size()}; + guard.release(); + } + + public: + constexpr dynamic_format_arg_store() = default; + + operator basic_format_args() const { + return basic_format_args(data(), static_cast(data_.size()), + !named_info_.empty()); + } + + /** + * Adds an argument into the dynamic store for later passing to a formatting + * function. + * + * Note that custom types and string types (but not string views) are copied + * into the store dynamically allocating memory if necessary. + * + * **Example**: + * + * fmt::dynamic_format_arg_store store; + * store.push_back(42); + * store.push_back("abc"); + * store.push_back(1.5f); + * std::string result = fmt::vformat("{} and {} and {}", store); + */ + template void push_back(const T& arg) { + if (detail::const_check(need_copy::value)) + emplace_arg(dynamic_args_.push>(arg)); + else + emplace_arg(detail::unwrap(arg)); + } + + /** + * Adds a reference to the argument into the dynamic store for later passing + * to a formatting function. + * + * **Example**: + * + * fmt::dynamic_format_arg_store store; + * char band[] = "Rolling Stones"; + * store.push_back(std::cref(band)); + * band[9] = 'c'; // Changing str affects the output. + * std::string result = fmt::vformat("{}", store); + * // result == "Rolling Scones" + */ + template void push_back(std::reference_wrapper arg) { + static_assert( + need_copy::value, + "objects of built-in types and string views are always copied"); + emplace_arg(arg.get()); + } + + /** + * Adds named argument into the dynamic store for later passing to a + * formatting function. `std::reference_wrapper` is supported to avoid + * copying of the argument. The name is always copied into the store. + */ + template + void push_back(const detail::named_arg& arg) { + const char_type* arg_name = + dynamic_args_.push>(arg.name).c_str(); + if (detail::const_check(need_copy::value)) { + emplace_arg( + fmt::arg(arg_name, dynamic_args_.push>(arg.value))); + } else { + emplace_arg(fmt::arg(arg_name, arg.value)); + } + } + + /// Erase all elements from the store. + void clear() { + data_.clear(); + named_info_.clear(); + dynamic_args_ = {}; + } + + /// Reserves space to store at least `new_cap` arguments including + /// `new_cap_named` named arguments. + void reserve(size_t new_cap, size_t new_cap_named) { + FMT_ASSERT(new_cap >= new_cap_named, + "set of arguments includes set of named arguments"); + data_.reserve(new_cap); + named_info_.reserve(new_cap_named); + } + + /// Returns the number of elements in the store. + size_t size() const noexcept { return data_.size(); } +}; + +FMT_END_NAMESPACE + +#endif // FMT_ARGS_H_ diff --git a/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/base.h b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/base.h new file mode 100644 index 000000000..42b7a5559 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/base.h @@ -0,0 +1,2954 @@ +// Formatting library for C++ - the base API for char/UTF-8 +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_BASE_H_ +#define FMT_BASE_H_ + +#if defined(FMT_IMPORT_STD) && !defined(FMT_MODULE) +# define FMT_MODULE +#endif + +#ifndef FMT_MODULE +# include // CHAR_BIT +# include // FILE +# include // memcmp + +# include // std::enable_if +#endif + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 110101 + +// Detect compiler versions. +#if defined(__clang__) && !defined(__ibmxl__) +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +#else +# define FMT_CLANG_VERSION 0 +#endif +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#else +# define FMT_GCC_VERSION 0 +#endif +#if defined(__ICL) +# define FMT_ICC_VERSION __ICL +#elif defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER +#else +# define FMT_ICC_VERSION 0 +#endif +#if defined(_MSC_VER) +# define FMT_MSC_VERSION _MSC_VER +#else +# define FMT_MSC_VERSION 0 +#endif + +// Detect standard library versions. +#ifdef _GLIBCXX_RELEASE +# define FMT_GLIBCXX_RELEASE _GLIBCXX_RELEASE +#else +# define FMT_GLIBCXX_RELEASE 0 +#endif +#ifdef _LIBCPP_VERSION +# define FMT_LIBCPP_VERSION _LIBCPP_VERSION +#else +# define FMT_LIBCPP_VERSION 0 +#endif + +#ifdef _MSVC_LANG +# define FMT_CPLUSPLUS _MSVC_LANG +#else +# define FMT_CPLUSPLUS __cplusplus +#endif + +// Detect __has_*. +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 +#endif +#ifdef __has_include +# define FMT_HAS_INCLUDE(x) __has_include(x) +#else +# define FMT_HAS_INCLUDE(x) 0 +#endif +#ifdef __has_builtin +# define FMT_HAS_BUILTIN(x) __has_builtin(x) +#else +# define FMT_HAS_BUILTIN(x) 0 +#endif +#ifdef __has_cpp_attribute +# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ + (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ + (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +// Detect C++14 relaxed constexpr. +#ifdef FMT_USE_CONSTEXPR +// Use the provided definition. +#elif FMT_GCC_VERSION >= 600 && FMT_CPLUSPLUS >= 201402L +// GCC only allows throw in constexpr since version 6: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67371. +# define FMT_USE_CONSTEXPR 1 +#elif FMT_ICC_VERSION +# define FMT_USE_CONSTEXPR 0 // https://github.com/fmtlib/fmt/issues/1628 +#elif FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912 +# define FMT_USE_CONSTEXPR 1 +#else +# define FMT_USE_CONSTEXPR 0 +#endif +#if FMT_USE_CONSTEXPR +# define FMT_CONSTEXPR constexpr +#else +# define FMT_CONSTEXPR +#endif + +// Detect consteval, C++20 constexpr extensions and std::is_constant_evaluated. +#if !defined(__cpp_lib_is_constant_evaluated) +# define FMT_USE_CONSTEVAL 0 +#elif FMT_CPLUSPLUS < 201709L +# define FMT_USE_CONSTEVAL 0 +#elif FMT_GLIBCXX_RELEASE && FMT_GLIBCXX_RELEASE < 10 +# define FMT_USE_CONSTEVAL 0 +#elif FMT_LIBCPP_VERSION && FMT_LIBCPP_VERSION < 10000 +# define FMT_USE_CONSTEVAL 0 +#elif defined(__apple_build_version__) && __apple_build_version__ < 14000029L +# define FMT_USE_CONSTEVAL 0 // consteval is broken in Apple clang < 14. +#elif FMT_MSC_VERSION && FMT_MSC_VERSION < 1929 +# define FMT_USE_CONSTEVAL 0 // consteval is broken in MSVC VS2019 < 16.10. +#elif defined(__cpp_consteval) +# define FMT_USE_CONSTEVAL 1 +#elif FMT_GCC_VERSION >= 1002 || FMT_CLANG_VERSION >= 1101 +# define FMT_USE_CONSTEVAL 1 +#else +# define FMT_USE_CONSTEVAL 0 +#endif +#if FMT_USE_CONSTEVAL +# define FMT_CONSTEVAL consteval +# define FMT_CONSTEXPR20 constexpr +#else +# define FMT_CONSTEVAL +# define FMT_CONSTEXPR20 +#endif + +// Check if exceptions are disabled. +#ifdef FMT_USE_EXCEPTIONS +// Use the provided definition. +#elif defined(__GNUC__) && !defined(__EXCEPTIONS) +# define FMT_USE_EXCEPTIONS 0 +#elif defined(__clang__) && !defined(__cpp_exceptions) +# define FMT_USE_EXCEPTIONS 0 +#elif FMT_MSC_VERSION && !_HAS_EXCEPTIONS +# define FMT_USE_EXCEPTIONS 0 +#else +# define FMT_USE_EXCEPTIONS 1 +#endif +#if FMT_USE_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#ifdef FMT_NO_UNIQUE_ADDRESS +// Use the provided definition. +#elif FMT_CPLUSPLUS < 202002L +// Not supported. +#elif FMT_HAS_CPP_ATTRIBUTE(no_unique_address) +# define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]] +// VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485). +#elif FMT_MSC_VERSION >= 1929 && !FMT_CLANG_VERSION +# define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +#endif +#ifndef FMT_NO_UNIQUE_ADDRESS +# define FMT_NO_UNIQUE_ADDRESS +#endif + +#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) +# define FMT_FALLTHROUGH [[fallthrough]] +#elif defined(__clang__) +# define FMT_FALLTHROUGH [[clang::fallthrough]] +#elif FMT_GCC_VERSION >= 700 && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) +# define FMT_FALLTHROUGH [[gnu::fallthrough]] +#else +# define FMT_FALLTHROUGH +#endif + +// Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings. +#if FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && !defined(__NVCC__) +# define FMT_NORETURN [[noreturn]] +#else +# define FMT_NORETURN +#endif + +#ifdef FMT_NODISCARD +// Use the provided definition. +#elif FMT_HAS_CPP17_ATTRIBUTE(nodiscard) +# define FMT_NODISCARD [[nodiscard]] +#else +# define FMT_NODISCARD +#endif + +#ifdef FMT_DEPRECATED +// Use the provided definition. +#elif FMT_HAS_CPP14_ATTRIBUTE(deprecated) +# define FMT_DEPRECATED [[deprecated]] +#else +# define FMT_DEPRECATED /* deprecated */ +#endif + +#ifdef FMT_ALWAYS_INLINE +// Use the provided definition. +#elif FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) +#else +# define FMT_ALWAYS_INLINE inline +#endif +// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode. +#ifdef NDEBUG +# define FMT_INLINE FMT_ALWAYS_INLINE +#else +# define FMT_INLINE inline +#endif + +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_VISIBILITY(value) __attribute__((visibility(value))) +#else +# define FMT_VISIBILITY(value) +#endif + +// Detect pragmas. +#define FMT_PRAGMA_IMPL(x) _Pragma(#x) +#if FMT_GCC_VERSION >= 504 && !defined(__NVCOMPILER) +// Workaround a _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884 +// and an nvhpc warning: https://github.com/fmtlib/fmt/pull/2582. +# define FMT_PRAGMA_GCC(x) FMT_PRAGMA_IMPL(GCC x) +#else +# define FMT_PRAGMA_GCC(x) +#endif +#if FMT_CLANG_VERSION +# define FMT_PRAGMA_CLANG(x) FMT_PRAGMA_IMPL(clang x) +#else +# define FMT_PRAGMA_CLANG(x) +#endif +#if FMT_MSC_VERSION +# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) +#else +# define FMT_MSC_WARNING(...) +#endif + +#ifndef FMT_BEGIN_NAMESPACE +# define FMT_BEGIN_NAMESPACE \ + namespace fmt { \ + inline namespace v11_alsoft { +# define FMT_END_NAMESPACE \ + } \ + } +#endif + +#ifndef FMT_EXPORT +# define FMT_EXPORT +# define FMT_BEGIN_EXPORT +# define FMT_END_EXPORT +#endif + +#ifdef _WIN32 +# define FMT_WIN32 1 +#else +# define FMT_WIN32 0 +#endif + +#if !defined(FMT_HEADER_ONLY) && FMT_WIN32 +# if defined(FMT_LIB_EXPORT) +# define FMT_API __declspec(dllexport) +# elif defined(FMT_SHARED) +# define FMT_API __declspec(dllimport) +# endif +#elif defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_API FMT_VISIBILITY("default") +#endif +#ifndef FMT_API +# define FMT_API +#endif + +#ifndef FMT_OPTIMIZE_SIZE +# define FMT_OPTIMIZE_SIZE 0 +#endif + +// FMT_BUILTIN_TYPE=0 may result in smaller library size at the cost of higher +// per-call binary size by passing built-in types through the extension API. +#ifndef FMT_BUILTIN_TYPES +# define FMT_BUILTIN_TYPES 1 +#endif + +#define FMT_APPLY_VARIADIC(expr) \ + using ignore = int[]; \ + (void)ignore { 0, (expr, 0)... } + +// Enable minimal optimizations for more compact code in debug mode. +FMT_PRAGMA_GCC(push_options) +#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) +FMT_PRAGMA_GCC(optimize("Og")) +#endif +FMT_PRAGMA_CLANG(diagnostic push) + +FMT_BEGIN_NAMESPACE + +// Implementations of enable_if_t and other metafunctions for older systems. +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; +template using bool_constant = std::integral_constant; +template +using remove_reference_t = typename std::remove_reference::type; +template +using remove_const_t = typename std::remove_const::type; +template +using remove_cvref_t = typename std::remove_cv>::type; +template +using make_unsigned_t = typename std::make_unsigned::type; +template +using underlying_t = typename std::underlying_type::type; +template using decay_t = typename std::decay::type; +using nullptr_t = decltype(nullptr); + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 +// A workaround for gcc 4.9 to make void_t work in a SFINAE context. +template struct void_t_impl { + using type = void; +}; +template using void_t = typename void_t_impl::type; +#else +template using void_t = void; +#endif + +struct monostate { + constexpr monostate() {} +}; + +// An enable_if helper to be used in template parameters which results in much +// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed +// to workaround a bug in MSVC 2019 (see #1140 and #1186). +#ifdef FMT_DOC +# define FMT_ENABLE_IF(...) +#else +# define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 +#endif + +template constexpr auto min_of(T a, T b) -> T { + return a < b ? a : b; +} +template constexpr auto max_of(T a, T b) -> T { + return a > b ? a : b; +} + +namespace detail { +// Suppresses "unused variable" warnings with the method described in +// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. +// (void)var does not work on many Intel compilers. +template FMT_CONSTEXPR void ignore_unused(const T&...) {} + +constexpr auto is_constant_evaluated(bool default_value = false) noexcept + -> bool { +// Workaround for incompatibility between clang 14 and libstdc++ consteval-based +// std::is_constant_evaluated: https://github.com/fmtlib/fmt/issues/3247. +#if FMT_CPLUSPLUS >= 202002L && FMT_GLIBCXX_RELEASE >= 12 && \ + (FMT_CLANG_VERSION >= 1400 && FMT_CLANG_VERSION < 1500) + ignore_unused(default_value); + return __builtin_is_constant_evaluated(); +#elif defined(__cpp_lib_is_constant_evaluated) + ignore_unused(default_value); + return std::is_constant_evaluated(); +#else + return default_value; +#endif +} + +// Suppresses "conditional expression is constant" warnings. +template FMT_ALWAYS_INLINE constexpr auto const_check(T val) -> T { + return val; +} + +FMT_NORETURN FMT_API void assert_fail(const char* file, int line, + const char* message); + +#if defined(FMT_ASSERT) +// Use the provided definition. +#elif defined(NDEBUG) +// FMT_ASSERT is not empty to avoid -Wempty-body. +# define FMT_ASSERT(condition, message) \ + fmt::detail::ignore_unused((condition), (message)) +#else +# define FMT_ASSERT(condition, message) \ + ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ + ? (void)0 \ + : fmt::detail::assert_fail(__FILE__, __LINE__, (message))) +#endif + +#ifdef FMT_USE_INT128 +// Use the provided definition. +#elif defined(__SIZEOF_INT128__) && !defined(__NVCC__) && \ + !(FMT_CLANG_VERSION && FMT_MSC_VERSION) +# define FMT_USE_INT128 1 +using int128_opt = __int128_t; // An optional native 128-bit integer. +using uint128_opt = __uint128_t; +inline auto map(int128_opt x) -> int128_opt { return x; } +inline auto map(uint128_opt x) -> uint128_opt { return x; } +#else +# define FMT_USE_INT128 0 +#endif +#if !FMT_USE_INT128 +enum class int128_opt {}; +enum class uint128_opt {}; +// Reduce template instantiations. +inline auto map(int128_opt) -> monostate { return {}; } +inline auto map(uint128_opt) -> monostate { return {}; } +#endif + +#ifndef FMT_USE_BITINT +# define FMT_USE_BITINT (FMT_CLANG_VERSION >= 1500) +#endif + +#if FMT_USE_BITINT +FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension") +template using bitint = _BitInt(N); +template using ubitint = unsigned _BitInt(N); +#else +template struct bitint {}; +template struct ubitint {}; +#endif // FMT_USE_BITINT + +// Casts a nonnegative integer to unsigned. +template +FMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t { + FMT_ASSERT(std::is_unsigned::value || value >= 0, "negative value"); + return static_cast>(value); +} + +template +using unsigned_char = conditional_t; + +// A heuristic to detect std::string and std::[experimental::]string_view. +// It is mainly used to avoid dependency on <[experimental/]string_view>. +template +struct is_std_string_like : std::false_type {}; +template +struct is_std_string_like().find_first_of( + typename T::value_type(), 0))>> + : std::is_convertible().data()), + const typename T::value_type*> {}; + +// Check if the literal encoding is UTF-8. +enum { is_utf8_enabled = "\u00A7"[1] == '\xA7' }; +enum { use_utf8 = !FMT_WIN32 || is_utf8_enabled }; + +#ifndef FMT_UNICODE +# define FMT_UNICODE 1 +#endif + +static_assert(!FMT_UNICODE || use_utf8, + "Unicode support requires compiling with /utf-8"); + +template constexpr const char* narrow(const T*) { return nullptr; } +constexpr FMT_ALWAYS_INLINE const char* narrow(const char* s) { return s; } + +template +FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n) + -> int { + if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n); + for (; n != 0; ++s1, ++s2, --n) { + if (*s1 < *s2) return -1; + if (*s1 > *s2) return 1; + } + return 0; +} + +namespace adl { +using namespace std; + +template +auto invoke_back_inserter() + -> decltype(back_inserter(std::declval())); +} // namespace adl + +template +struct is_back_insert_iterator : std::false_type {}; + +template +struct is_back_insert_iterator< + It, bool_constant()), + It>::value>> : std::true_type {}; + +// Extracts a reference to the container from *insert_iterator. +template +inline FMT_CONSTEXPR20 auto get_container(OutputIt it) -> + typename OutputIt::container_type& { + struct accessor : OutputIt { + FMT_CONSTEXPR20 accessor(OutputIt base) : OutputIt(base) {} + using OutputIt::container; + }; + return *accessor(it).container; +} +} // namespace detail + +// Parsing-related public API and forward declarations. +FMT_BEGIN_EXPORT + +/** + * An implementation of `std::basic_string_view` for pre-C++17. It provides a + * subset of the API. `fmt::basic_string_view` is used for format strings even + * if `std::basic_string_view` is available to prevent issues when a library is + * compiled with a different `-std` option than the client code (which is not + * recommended). + */ +template class basic_string_view { + private: + const Char* data_; + size_t size_; + + public: + using value_type = Char; + using iterator = const Char*; + + constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} + + /// Constructs a string reference object from a C string and a size. + constexpr basic_string_view(const Char* s, size_t count) noexcept + : data_(s), size_(count) {} + + constexpr basic_string_view(nullptr_t) = delete; + + /// Constructs a string reference object from a C string. +#if FMT_GCC_VERSION + FMT_ALWAYS_INLINE +#endif + FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) { +#if FMT_HAS_BUILTIN(__buitin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION + if (std::is_same::value) { + size_ = __builtin_strlen(detail::narrow(s)); + return; + } +#endif + size_t len = 0; + while (*s++) ++len; + size_ = len; + } + + /// Constructs a string reference from a `std::basic_string` or a + /// `std::basic_string_view` object. + template ::value&& std::is_same< + typename S::value_type, Char>::value)> + FMT_CONSTEXPR basic_string_view(const S& s) noexcept + : data_(s.data()), size_(s.size()) {} + + /// Returns a pointer to the string data. + constexpr auto data() const noexcept -> const Char* { return data_; } + + /// Returns the string size. + constexpr auto size() const noexcept -> size_t { return size_; } + + constexpr auto begin() const noexcept -> iterator { return data_; } + constexpr auto end() const noexcept -> iterator { return data_ + size_; } + + constexpr auto operator[](size_t pos) const noexcept -> const Char& { + return data_[pos]; + } + + FMT_CONSTEXPR void remove_prefix(size_t n) noexcept { + data_ += n; + size_ -= n; + } + + FMT_CONSTEXPR auto starts_with(basic_string_view sv) const noexcept + -> bool { + return size_ >= sv.size_ && detail::compare(data_, sv.data_, sv.size_) == 0; + } + FMT_CONSTEXPR auto starts_with(Char c) const noexcept -> bool { + return size_ >= 1 && *data_ == c; + } + FMT_CONSTEXPR auto starts_with(const Char* s) const -> bool { + return starts_with(basic_string_view(s)); + } + + // Lexicographically compare this string reference to other. + FMT_CONSTEXPR auto compare(basic_string_view other) const -> int { + int result = + detail::compare(data_, other.data_, min_of(size_, other.size_)); + if (result != 0) return result; + return size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + } + + FMT_CONSTEXPR friend auto operator==(basic_string_view lhs, + basic_string_view rhs) -> bool { + return lhs.compare(rhs) == 0; + } + friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) != 0; + } + friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) < 0; + } + friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) <= 0; + } + friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) > 0; + } + friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) >= 0; + } +}; + +using string_view = basic_string_view; + +/// Specifies if `T` is an extended character type. Can be specialized by users. +template struct is_xchar : std::false_type {}; +template <> struct is_xchar : std::true_type {}; +template <> struct is_xchar : std::true_type {}; +template <> struct is_xchar : std::true_type {}; +#ifdef __cpp_char8_t +template <> struct is_xchar : std::true_type {}; +#endif + +// DEPRECATED! Will be replaced with an alias to prevent specializations. +template struct is_char : is_xchar {}; +template <> struct is_char : std::true_type {}; + +template class basic_appender; +using appender = basic_appender; + +// Checks whether T is a container with contiguous storage. +template struct is_contiguous : std::false_type {}; + +class context; +template class generic_context; +template class parse_context; + +// Longer aliases for C++20 compatibility. +template using basic_format_parse_context = parse_context; +using format_parse_context = parse_context; +template +using basic_format_context = + conditional_t::value, context, + generic_context>; +using format_context = context; + +template +using buffered_context = + conditional_t::value, context, + generic_context, Char>>; + +template class basic_format_arg; +template class basic_format_args; + +// A separate type would result in shorter symbols but break ABI compatibility +// between clang and gcc on ARM (#1919). +using format_args = basic_format_args; + +// A formatter for objects of type T. +template +struct formatter { + // A deleted default constructor indicates a disabled formatter. + formatter() = delete; +}; + +/// Reports a format error at compile time or, via a `format_error` exception, +/// at runtime. +// This function is intentionally not constexpr to give a compile-time error. +FMT_NORETURN FMT_API void report_error(const char* message); + +enum class presentation_type : unsigned char { + // Common specifiers: + none = 0, + debug = 1, // '?' + string = 2, // 's' (string, bool) + + // Integral, bool and character specifiers: + dec = 3, // 'd' + hex, // 'x' or 'X' + oct, // 'o' + bin, // 'b' or 'B' + chr, // 'c' + + // String and pointer specifiers: + pointer = 3, // 'p' + + // Floating-point specifiers: + exp = 1, // 'e' or 'E' (1 since there is no FP debug presentation) + fixed, // 'f' or 'F' + general, // 'g' or 'G' + hexfloat // 'a' or 'A' +}; + +enum class align { none, left, right, center, numeric }; +enum class sign { none, minus, plus, space }; +enum class arg_id_kind { none, index, name }; + +// Basic format specifiers for built-in and string types. +class basic_specs { + private: + // Data is arranged as follows: + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |type |align| w | p | s |u|#|L| f | unused | + // +-----+-----+---+---+---+-+-+-+-----+---------------------------+ + // + // w - dynamic width info + // p - dynamic precision info + // s - sign + // u - uppercase (e.g. 'X' for 'x') + // # - alternate form ('#') + // L - localized + // f - fill size + // + // Bitfields are not used because of compiler bugs such as gcc bug 61414. + enum : unsigned { + type_mask = 0x00007, + align_mask = 0x00038, + width_mask = 0x000C0, + precision_mask = 0x00300, + sign_mask = 0x00C00, + uppercase_mask = 0x01000, + alternate_mask = 0x02000, + localized_mask = 0x04000, + fill_size_mask = 0x38000, + + align_shift = 3, + width_shift = 6, + precision_shift = 8, + sign_shift = 10, + fill_size_shift = 15, + + max_fill_size = 4 + }; + + size_t data_ = 1 << fill_size_shift; + + // Character (code unit) type is erased to prevent template bloat. + char fill_data_[max_fill_size] = {' '}; + + FMT_CONSTEXPR void set_fill_size(size_t size) { + data_ = (data_ & ~fill_size_mask) | (size << fill_size_shift); + } + + public: + constexpr auto type() const -> presentation_type { + return static_cast(data_ & type_mask); + } + FMT_CONSTEXPR void set_type(presentation_type t) { + data_ = (data_ & ~type_mask) | static_cast(t); + } + + constexpr auto align() const -> align { + return static_cast((data_ & align_mask) >> align_shift); + } + FMT_CONSTEXPR void set_align(fmt::align a) { + data_ = (data_ & ~align_mask) | (static_cast(a) << align_shift); + } + + constexpr auto dynamic_width() const -> arg_id_kind { + return static_cast((data_ & width_mask) >> width_shift); + } + FMT_CONSTEXPR void set_dynamic_width(arg_id_kind w) { + data_ = (data_ & ~width_mask) | (static_cast(w) << width_shift); + } + + FMT_CONSTEXPR auto dynamic_precision() const -> arg_id_kind { + return static_cast((data_ & precision_mask) >> + precision_shift); + } + FMT_CONSTEXPR void set_dynamic_precision(arg_id_kind p) { + data_ = (data_ & ~precision_mask) | + (static_cast(p) << precision_shift); + } + + constexpr bool dynamic() const { + return (data_ & (width_mask | precision_mask)) != 0; + } + + constexpr auto sign() const -> sign { + return static_cast((data_ & sign_mask) >> sign_shift); + } + FMT_CONSTEXPR void set_sign(fmt::sign s) { + data_ = (data_ & ~sign_mask) | (static_cast(s) << sign_shift); + } + + constexpr auto upper() const -> bool { return (data_ & uppercase_mask) != 0; } + FMT_CONSTEXPR void set_upper() { data_ |= uppercase_mask; } + + constexpr auto alt() const -> bool { return (data_ & alternate_mask) != 0; } + FMT_CONSTEXPR void set_alt() { data_ |= alternate_mask; } + FMT_CONSTEXPR void clear_alt() { data_ &= ~alternate_mask; } + + constexpr auto localized() const -> bool { + return (data_ & localized_mask) != 0; + } + FMT_CONSTEXPR void set_localized() { data_ |= localized_mask; } + + constexpr auto fill_size() const -> size_t { + return (data_ & fill_size_mask) >> fill_size_shift; + } + + template ::value)> + constexpr auto fill() const -> const Char* { + return fill_data_; + } + template ::value)> + constexpr auto fill() const -> const Char* { + return nullptr; + } + + template constexpr auto fill_unit() const -> Char { + using uchar = unsigned char; + return static_cast(static_cast(fill_data_[0]) | + (static_cast(fill_data_[1]) << 8) | + (static_cast(fill_data_[2]) << 16)); + } + + FMT_CONSTEXPR void set_fill(char c) { + fill_data_[0] = c; + set_fill_size(1); + } + + template + FMT_CONSTEXPR void set_fill(basic_string_view s) { + auto size = s.size(); + set_fill_size(size); + if (size == 1) { + unsigned uchar = static_cast>(s[0]); + fill_data_[0] = static_cast(uchar); + fill_data_[1] = static_cast(uchar >> 8); + fill_data_[2] = static_cast(uchar >> 16); + return; + } + FMT_ASSERT(size <= max_fill_size, "invalid fill"); + for (size_t i = 0; i < size; ++i) + fill_data_[i & 3] = static_cast(s[i]); + } +}; + +// Format specifiers for built-in and string types. +struct format_specs : basic_specs { + int width; + int precision; + + constexpr format_specs() : width(0), precision(-1) {} +}; + +/** + * Parsing context consisting of a format string range being parsed and an + * argument counter for automatic indexing. + */ +template class parse_context { + private: + basic_string_view fmt_; + int next_arg_id_; + + enum { use_constexpr_cast = !FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200 }; + + FMT_CONSTEXPR void do_check_arg_id(int arg_id); + + public: + using char_type = Char; + using iterator = const Char*; + + constexpr explicit parse_context(basic_string_view fmt, + int next_arg_id = 0) + : fmt_(fmt), next_arg_id_(next_arg_id) {} + + /// Returns an iterator to the beginning of the format string range being + /// parsed. + constexpr auto begin() const noexcept -> iterator { return fmt_.begin(); } + + /// Returns an iterator past the end of the format string range being parsed. + constexpr auto end() const noexcept -> iterator { return fmt_.end(); } + + /// Advances the begin iterator to `it`. + FMT_CONSTEXPR void advance_to(iterator it) { + fmt_.remove_prefix(detail::to_unsigned(it - begin())); + } + + /// Reports an error if using the manual argument indexing; otherwise returns + /// the next argument index and switches to the automatic indexing. + FMT_CONSTEXPR auto next_arg_id() -> int { + if (next_arg_id_ < 0) { + report_error("cannot switch from manual to automatic argument indexing"); + return 0; + } + int id = next_arg_id_++; + do_check_arg_id(id); + return id; + } + + /// Reports an error if using the automatic argument indexing; otherwise + /// switches to the manual indexing. + FMT_CONSTEXPR void check_arg_id(int id) { + if (next_arg_id_ > 0) { + report_error("cannot switch from automatic to manual argument indexing"); + return; + } + next_arg_id_ = -1; + do_check_arg_id(id); + } + FMT_CONSTEXPR void check_arg_id(basic_string_view) { + next_arg_id_ = -1; + } + FMT_CONSTEXPR void check_dynamic_spec(int arg_id); +}; + +FMT_END_EXPORT + +namespace detail { + +// Constructs fmt::basic_string_view from types implicitly convertible +// to it, deducing Char. Explicitly convertible types such as the ones returned +// from FMT_STRING are intentionally excluded. +template ::value)> +constexpr auto to_string_view(const Char* s) -> basic_string_view { + return s; +} +template ::value)> +constexpr auto to_string_view(const T& s) + -> basic_string_view { + return s; +} +template +constexpr auto to_string_view(basic_string_view s) + -> basic_string_view { + return s; +} + +template +struct has_to_string_view : std::false_type {}; +// detail:: is intentional since to_string_view is not an extension point. +template +struct has_to_string_view< + T, void_t()))>> + : std::true_type {}; + +/// String's character (code unit) type. detail:: is intentional to prevent ADL. +template ()))> +using char_t = typename V::value_type; + +enum class type { + none_type, + // Integer types should go first, + int_type, + uint_type, + long_long_type, + ulong_long_type, + int128_type, + uint128_type, + bool_type, + char_type, + last_integer_type = char_type, + // followed by floating-point types. + float_type, + double_type, + long_double_type, + last_numeric_type = long_double_type, + cstring_type, + string_type, + pointer_type, + custom_type +}; + +// Maps core type T to the corresponding type enum constant. +template +struct type_constant : std::integral_constant {}; + +#define FMT_TYPE_CONSTANT(Type, constant) \ + template \ + struct type_constant \ + : std::integral_constant {} + +FMT_TYPE_CONSTANT(int, int_type); +FMT_TYPE_CONSTANT(unsigned, uint_type); +FMT_TYPE_CONSTANT(long long, long_long_type); +FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); +FMT_TYPE_CONSTANT(int128_opt, int128_type); +FMT_TYPE_CONSTANT(uint128_opt, uint128_type); +FMT_TYPE_CONSTANT(bool, bool_type); +FMT_TYPE_CONSTANT(Char, char_type); +FMT_TYPE_CONSTANT(float, float_type); +FMT_TYPE_CONSTANT(double, double_type); +FMT_TYPE_CONSTANT(long double, long_double_type); +FMT_TYPE_CONSTANT(const Char*, cstring_type); +FMT_TYPE_CONSTANT(basic_string_view, string_type); +FMT_TYPE_CONSTANT(const void*, pointer_type); + +constexpr auto is_integral_type(type t) -> bool { + return t > type::none_type && t <= type::last_integer_type; +} +constexpr auto is_arithmetic_type(type t) -> bool { + return t > type::none_type && t <= type::last_numeric_type; +} + +constexpr auto set(type rhs) -> int { return 1 << static_cast(rhs); } +constexpr auto in(type t, int set) -> bool { + return ((set >> static_cast(t)) & 1) != 0; +} + +// Bitsets of types. +enum { + sint_set = + set(type::int_type) | set(type::long_long_type) | set(type::int128_type), + uint_set = set(type::uint_type) | set(type::ulong_long_type) | + set(type::uint128_type), + bool_set = set(type::bool_type), + char_set = set(type::char_type), + float_set = set(type::float_type) | set(type::double_type) | + set(type::long_double_type), + string_set = set(type::string_type), + cstring_set = set(type::cstring_type), + pointer_set = set(type::pointer_type) +}; + +struct view {}; + +template struct named_arg; +template struct is_named_arg : std::false_type {}; +template struct is_static_named_arg : std::false_type {}; + +template +struct is_named_arg> : std::true_type {}; + +template struct named_arg : view { + const Char* name; + const T& value; + + named_arg(const Char* n, const T& v) : name(n), value(v) {} + static_assert(!is_named_arg::value, "nested named arguments"); +}; + +template constexpr auto count() -> int { return B ? 1 : 0; } +template constexpr auto count() -> int { + return (B1 ? 1 : 0) + count(); +} + +template constexpr auto count_named_args() -> int { + return count::value...>(); +} +template constexpr auto count_static_named_args() -> int { + return count::value...>(); +} + +template struct named_arg_info { + const Char* name; + int id; +}; + +template ::value)> +void init_named_arg(named_arg_info*, int& arg_index, int&, const T&) { + ++arg_index; +} +template ::value)> +void init_named_arg(named_arg_info* named_args, int& arg_index, + int& named_arg_index, const T& arg) { + named_args[named_arg_index++] = {arg.name, arg_index++}; +} + +template ::value)> +FMT_CONSTEXPR void init_static_named_arg(named_arg_info*, int& arg_index, + int&) { + ++arg_index; +} +template ::value)> +FMT_CONSTEXPR void init_static_named_arg(named_arg_info* named_args, + int& arg_index, int& named_arg_index) { + named_args[named_arg_index++] = {T::name, arg_index++}; +} + +// To minimize the number of types we need to deal with, long is translated +// either to int or to long long depending on its size. +enum { long_short = sizeof(long) == sizeof(int) }; +using long_type = conditional_t; +using ulong_type = conditional_t; + +template +using format_as_result = + remove_cvref_t()))>; +template +using format_as_member_result = + remove_cvref_t::format_as(std::declval()))>; + +template +struct use_format_as : std::false_type {}; +// format_as member is only used to avoid injection into the std namespace. +template +struct use_format_as_member : std::false_type {}; + +// Only map owning types because mapping views can be unsafe. +template +struct use_format_as< + T, bool_constant>::value>> + : std::true_type {}; +template +struct use_format_as_member< + T, bool_constant>::value>> + : std::true_type {}; + +template > +using use_formatter = + bool_constant<(std::is_class::value || std::is_enum::value || + std::is_union::value || std::is_array::value) && + !has_to_string_view::value && !is_named_arg::value && + !use_format_as::value && !use_format_as_member::value>; + +template > +auto has_formatter_impl(T* p, buffered_context* ctx = nullptr) + -> decltype(formatter().format(*p, *ctx), std::true_type()); +template auto has_formatter_impl(...) -> std::false_type; + +// T can be const-qualified to check if it is const-formattable. +template constexpr auto has_formatter() -> bool { + return decltype(has_formatter_impl(static_cast(nullptr)))::value; +} + +// Maps formatting argument types to natively supported types or user-defined +// types with formatters. Returns void on errors to be SFINAE-friendly. +template struct type_mapper { + static auto map(signed char) -> int; + static auto map(unsigned char) -> unsigned; + static auto map(short) -> int; + static auto map(unsigned short) -> unsigned; + static auto map(int) -> int; + static auto map(unsigned) -> unsigned; + static auto map(long) -> long_type; + static auto map(unsigned long) -> ulong_type; + static auto map(long long) -> long long; + static auto map(unsigned long long) -> unsigned long long; + static auto map(int128_opt) -> int128_opt; + static auto map(uint128_opt) -> uint128_opt; + static auto map(bool) -> bool; + + template + static auto map(bitint) -> conditional_t; + template + static auto map(ubitint) + -> conditional_t; + + template ::value)> + static auto map(T) -> conditional_t< + std::is_same::value || std::is_same::value, Char, void>; + + static auto map(float) -> float; + static auto map(double) -> double; + static auto map(long double) -> long double; + + static auto map(Char*) -> const Char*; + static auto map(const Char*) -> const Char*; + template , + FMT_ENABLE_IF(!std::is_pointer::value)> + static auto map(const T&) -> conditional_t::value, + basic_string_view, void>; + + static auto map(void*) -> const void*; + static auto map(const void*) -> const void*; + static auto map(volatile void*) -> const void*; + static auto map(const volatile void*) -> const void*; + static auto map(nullptr_t) -> const void*; + template ::value || + std::is_member_pointer::value)> + static auto map(const T&) -> void; + + template ::value)> + static auto map(const T& x) -> decltype(map(format_as(x))); + template ::value)> + static auto map(const T& x) -> decltype(map(formatter::format_as(x))); + + template ::value)> + static auto map(T&) -> conditional_t(), T&, void>; + + template ::value)> + static auto map(const T& named_arg) -> decltype(map(named_arg.value)); +}; + +// detail:: is used to workaround a bug in MSVC 2017. +template +using mapped_t = decltype(detail::type_mapper::map(std::declval())); + +// A type constant after applying type_mapper. +template +using mapped_type_constant = type_constant, Char>; + +template ::value> +using stored_type_constant = std::integral_constant< + type, Context::builtin_types || TYPE == type::int_type ? TYPE + : type::custom_type>; +// A parse context with extra data used only in compile-time checks. +template +class compile_parse_context : public parse_context { + private: + int num_args_; + const type* types_; + using base = parse_context; + + public: + FMT_CONSTEXPR explicit compile_parse_context(basic_string_view fmt, + int num_args, const type* types, + int next_arg_id = 0) + : base(fmt, next_arg_id), num_args_(num_args), types_(types) {} + + constexpr auto num_args() const -> int { return num_args_; } + constexpr auto arg_type(int id) const -> type { return types_[id]; } + + FMT_CONSTEXPR auto next_arg_id() -> int { + int id = base::next_arg_id(); + if (id >= num_args_) report_error("argument not found"); + return id; + } + + FMT_CONSTEXPR void check_arg_id(int id) { + base::check_arg_id(id); + if (id >= num_args_) report_error("argument not found"); + } + using base::check_arg_id; + + FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { + ignore_unused(arg_id); + if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) + report_error("width/precision is not integer"); + } +}; + +// An argument reference. +template union arg_ref { + FMT_CONSTEXPR arg_ref(int idx = 0) : index(idx) {} + FMT_CONSTEXPR arg_ref(basic_string_view n) : name(n) {} + + int index; + basic_string_view name; +}; + +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow reusing the same parsed specifiers with +// different sets of arguments (precompilation of format strings). +template struct dynamic_format_specs : format_specs { + arg_ref width_ref; + arg_ref precision_ref; +}; + +// Converts a character to ASCII. Returns '\0' on conversion failure. +template ::value)> +constexpr auto to_ascii(Char c) -> char { + return c <= 0xff ? static_cast(c) : '\0'; +} + +// Returns the number of code units in a code point or 1 on error. +template +FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { + if (const_check(sizeof(Char) != 1)) return 1; + auto c = static_cast(*begin); + return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 3) + 1; +} + +// Parses the range [begin, end) as an unsigned integer. This function assumes +// that the range is non-empty and the first character is a digit. +template +FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, + int error_value) noexcept -> int { + FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); + unsigned value = 0, prev = 0; + auto p = begin; + do { + prev = value; + value = value * 10 + unsigned(*p - '0'); + ++p; + } while (p != end && '0' <= *p && *p <= '9'); + auto num_digits = p - begin; + begin = p; + int digits10 = static_cast(sizeof(int) * CHAR_BIT * 3 / 10); + if (num_digits <= digits10) return static_cast(value); + // Check for overflow. + unsigned max = INT_MAX; + return num_digits == digits10 + 1 && + prev * 10ull + unsigned(p[-1] - '0') <= max + ? static_cast(value) + : error_value; +} + +FMT_CONSTEXPR inline auto parse_align(char c) -> align { + switch (c) { + case '<': return align::left; + case '>': return align::right; + case '^': return align::center; + } + return align::none; +} + +template constexpr auto is_name_start(Char c) -> bool { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; +} + +template +FMT_CONSTEXPR auto parse_arg_id(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + Char c = *begin; + if (c >= '0' && c <= '9') { + int index = 0; + if (c != '0') + index = parse_nonnegative_int(begin, end, INT_MAX); + else + ++begin; + if (begin == end || (*begin != '}' && *begin != ':')) + report_error("invalid format string"); + else + handler.on_index(index); + return begin; + } + if (FMT_OPTIMIZE_SIZE > 1 || !is_name_start(c)) { + report_error("invalid format string"); + return begin; + } + auto it = begin; + do { + ++it; + } while (it != end && (is_name_start(*it) || ('0' <= *it && *it <= '9'))); + handler.on_name({begin, to_unsigned(it - begin)}); + return it; +} + +template struct dynamic_spec_handler { + parse_context& ctx; + arg_ref& ref; + arg_id_kind& kind; + + FMT_CONSTEXPR void on_index(int id) { + ref = id; + kind = arg_id_kind::index; + ctx.check_arg_id(id); + ctx.check_dynamic_spec(id); + } + FMT_CONSTEXPR void on_name(basic_string_view id) { + ref = id; + kind = arg_id_kind::name; + ctx.check_arg_id(id); + } +}; + +template struct parse_dynamic_spec_result { + const Char* end; + arg_id_kind kind; +}; + +// Parses integer | "{" [arg_id] "}". +template +FMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end, + int& value, arg_ref& ref, + parse_context& ctx) + -> parse_dynamic_spec_result { + FMT_ASSERT(begin != end, ""); + auto kind = arg_id_kind::none; + if ('0' <= *begin && *begin <= '9') { + int val = parse_nonnegative_int(begin, end, -1); + if (val == -1) report_error("number is too big"); + value = val; + } else { + if (*begin == '{') { + ++begin; + if (begin != end) { + Char c = *begin; + if (c == '}' || c == ':') { + int id = ctx.next_arg_id(); + ref = id; + kind = arg_id_kind::index; + ctx.check_dynamic_spec(id); + } else { + begin = parse_arg_id(begin, end, + dynamic_spec_handler{ctx, ref, kind}); + } + } + if (begin != end && *begin == '}') return {++begin, kind}; + } + report_error("invalid format string"); + } + return {begin, kind}; +} + +template +FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, + format_specs& specs, arg_ref& width_ref, + parse_context& ctx) -> const Char* { + auto result = parse_dynamic_spec(begin, end, specs.width, width_ref, ctx); + specs.set_dynamic_width(result.kind); + return result.end; +} + +template +FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, + format_specs& specs, + arg_ref& precision_ref, + parse_context& ctx) -> const Char* { + ++begin; + if (begin == end) { + report_error("invalid precision"); + return begin; + } + auto result = + parse_dynamic_spec(begin, end, specs.precision, precision_ref, ctx); + specs.set_dynamic_precision(result.kind); + return result.end; +} + +enum class state { start, align, sign, hash, zero, width, precision, locale }; + +// Parses standard format specifiers. +template +FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, + dynamic_format_specs& specs, + parse_context& ctx, type arg_type) + -> const Char* { + auto c = '\0'; + if (end - begin > 1) { + auto next = to_ascii(begin[1]); + c = parse_align(next) == align::none ? to_ascii(*begin) : '\0'; + } else { + if (begin == end) return begin; + c = to_ascii(*begin); + } + + struct { + state current_state = state::start; + FMT_CONSTEXPR void operator()(state s, bool valid = true) { + if (current_state >= s || !valid) + report_error("invalid format specifier"); + current_state = s; + } + } enter_state; + + using pres = presentation_type; + constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; + struct { + const Char*& begin; + format_specs& specs; + type arg_type; + + FMT_CONSTEXPR auto operator()(pres pres_type, int set) -> const Char* { + if (!in(arg_type, set)) report_error("invalid format specifier"); + specs.set_type(pres_type); + return begin + 1; + } + } parse_presentation_type{begin, specs, arg_type}; + + for (;;) { + switch (c) { + case '<': + case '>': + case '^': + enter_state(state::align); + specs.set_align(parse_align(c)); + ++begin; + break; + case '+': + case ' ': + specs.set_sign(c == ' ' ? sign::space : sign::plus); + FMT_FALLTHROUGH; + case '-': + enter_state(state::sign, in(arg_type, sint_set | float_set)); + ++begin; + break; + case '#': + enter_state(state::hash, is_arithmetic_type(arg_type)); + specs.set_alt(); + ++begin; + break; + case '0': + enter_state(state::zero); + if (!is_arithmetic_type(arg_type)) + report_error("format specifier requires numeric argument"); + if (specs.align() == align::none) { + // Ignore 0 if align is specified for compatibility with std::format. + specs.set_align(align::numeric); + specs.set_fill('0'); + } + ++begin; + break; + // clang-format off + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': case '{': + // clang-format on + enter_state(state::width); + begin = parse_width(begin, end, specs, specs.width_ref, ctx); + break; + case '.': + enter_state(state::precision, + in(arg_type, float_set | string_set | cstring_set)); + begin = parse_precision(begin, end, specs, specs.precision_ref, ctx); + break; + case 'L': + enter_state(state::locale, is_arithmetic_type(arg_type)); + specs.set_localized(); + ++begin; + break; + case 'd': return parse_presentation_type(pres::dec, integral_set); + case 'X': specs.set_upper(); FMT_FALLTHROUGH; + case 'x': return parse_presentation_type(pres::hex, integral_set); + case 'o': return parse_presentation_type(pres::oct, integral_set); + case 'B': specs.set_upper(); FMT_FALLTHROUGH; + case 'b': return parse_presentation_type(pres::bin, integral_set); + case 'E': specs.set_upper(); FMT_FALLTHROUGH; + case 'e': return parse_presentation_type(pres::exp, float_set); + case 'F': specs.set_upper(); FMT_FALLTHROUGH; + case 'f': return parse_presentation_type(pres::fixed, float_set); + case 'G': specs.set_upper(); FMT_FALLTHROUGH; + case 'g': return parse_presentation_type(pres::general, float_set); + case 'A': specs.set_upper(); FMT_FALLTHROUGH; + case 'a': return parse_presentation_type(pres::hexfloat, float_set); + case 'c': + if (arg_type == type::bool_type) report_error("invalid format specifier"); + return parse_presentation_type(pres::chr, integral_set); + case 's': + return parse_presentation_type(pres::string, + bool_set | string_set | cstring_set); + case 'p': + return parse_presentation_type(pres::pointer, pointer_set | cstring_set); + case '?': + return parse_presentation_type(pres::debug, + char_set | string_set | cstring_set); + case '}': return begin; + default: { + if (*begin == '}') return begin; + // Parse fill and alignment. + auto fill_end = begin + code_point_length(begin); + if (end - fill_end <= 0) { + report_error("invalid format specifier"); + return begin; + } + if (*begin == '{') { + report_error("invalid fill character '{'"); + return begin; + } + auto alignment = parse_align(to_ascii(*fill_end)); + enter_state(state::align, alignment != align::none); + specs.set_fill( + basic_string_view(begin, to_unsigned(fill_end - begin))); + specs.set_align(alignment); + begin = fill_end + 1; + } + } + if (begin == end) return begin; + c = to_ascii(*begin); + } +} + +template +FMT_CONSTEXPR FMT_INLINE auto parse_replacement_field(const Char* begin, + const Char* end, + Handler&& handler) + -> const Char* { + ++begin; + if (begin == end) { + handler.on_error("invalid format string"); + return end; + } + int arg_id = 0; + switch (*begin) { + case '}': + handler.on_replacement_field(handler.on_arg_id(), begin); + return begin + 1; + case '{': handler.on_text(begin, begin + 1); return begin + 1; + case ':': arg_id = handler.on_arg_id(); break; + default: { + struct id_adapter { + Handler& handler; + int arg_id; + + FMT_CONSTEXPR void on_index(int id) { arg_id = handler.on_arg_id(id); } + FMT_CONSTEXPR void on_name(basic_string_view id) { + arg_id = handler.on_arg_id(id); + } + } adapter = {handler, 0}; + begin = parse_arg_id(begin, end, adapter); + arg_id = adapter.arg_id; + Char c = begin != end ? *begin : Char(); + if (c == '}') { + handler.on_replacement_field(arg_id, begin); + return begin + 1; + } + if (c != ':') { + handler.on_error("missing '}' in format string"); + return end; + } + break; + } + } + begin = handler.on_format_specs(arg_id, begin + 1, end); + if (begin == end || *begin != '}') + return handler.on_error("unknown format specifier"), end; + return begin + 1; +} + +template +FMT_CONSTEXPR void parse_format_string(basic_string_view fmt, + Handler&& handler) { + auto begin = fmt.data(), end = begin + fmt.size(); + auto p = begin; + while (p != end) { + auto c = *p++; + if (c == '{') { + handler.on_text(begin, p - 1); + begin = p = parse_replacement_field(p - 1, end, handler); + } else if (c == '}') { + if (p == end || *p != '}') + return handler.on_error("unmatched '}' in format string"); + handler.on_text(begin, p); + begin = ++p; + } + } + handler.on_text(begin, end); +} + +// Checks char specs and returns true iff the presentation type is char-like. +FMT_CONSTEXPR inline auto check_char_specs(const format_specs& specs) -> bool { + auto type = specs.type(); + if (type != presentation_type::none && type != presentation_type::chr && + type != presentation_type::debug) { + return false; + } + if (specs.align() == align::numeric || specs.sign() != sign::none || + specs.alt()) { + report_error("invalid format specifier for char"); + } + return true; +} + +// A base class for compile-time strings. +struct compile_string {}; + +template +FMT_VISIBILITY("hidden") // Suppress an ld warning on macOS (#3769). +FMT_CONSTEXPR auto invoke_parse(parse_context& ctx) -> const Char* { + using mapped_type = remove_cvref_t>; + constexpr bool formattable = + std::is_constructible>::value; + if (!formattable) return ctx.begin(); // Error is reported in the value ctor. + using formatted_type = conditional_t; + return formatter().parse(ctx); +} + +template struct arg_pack {}; + +template +class format_string_checker { + private: + type types_[max_of(1, NUM_ARGS)]; + named_arg_info named_args_[max_of(1, NUM_NAMED_ARGS)]; + compile_parse_context context_; + + using parse_func = auto (*)(parse_context&) -> const Char*; + parse_func parse_funcs_[max_of(1, NUM_ARGS)]; + + public: + template + FMT_CONSTEXPR explicit format_string_checker(basic_string_view fmt, + arg_pack) + : types_{mapped_type_constant::value...}, + named_args_{}, + context_(fmt, NUM_ARGS, types_), + parse_funcs_{&invoke_parse...} { + int arg_index = 0, named_arg_index = 0; + FMT_APPLY_VARIADIC( + init_static_named_arg(named_args_, arg_index, named_arg_index)); + ignore_unused(arg_index, named_arg_index); + } + + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + + FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); } + FMT_CONSTEXPR auto on_arg_id(int id) -> int { + context_.check_arg_id(id); + return id; + } + FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { + for (int i = 0; i < NUM_NAMED_ARGS; ++i) { + if (named_args_[i].name == id) return named_args_[i].id; + } + if (!DYNAMIC_NAMES) on_error("argument not found"); + return -1; + } + + FMT_CONSTEXPR void on_replacement_field(int id, const Char* begin) { + on_format_specs(id, begin, begin); // Call parse() on empty specs. + } + + FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char* end) + -> const Char* { + context_.advance_to(begin); + if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_); + while (begin != end && *begin != '}') ++begin; + return begin; + } + + FMT_NORETURN FMT_CONSTEXPR void on_error(const char* message) { + report_error(message); + } +}; + +/// A contiguous memory buffer with an optional growing ability. It is an +/// internal class and shouldn't be used directly, only via `memory_buffer`. +template class buffer { + private: + T* ptr_; + size_t size_; + size_t capacity_; + + using grow_fun = void (*)(buffer& buf, size_t capacity); + grow_fun grow_; + + protected: + // Don't initialize ptr_ since it is not accessed to save a few cycles. + FMT_MSC_WARNING(suppress : 26495) + FMT_CONSTEXPR buffer(grow_fun grow, size_t sz) noexcept + : size_(sz), capacity_(sz), grow_(grow) {} + + constexpr buffer(grow_fun grow, T* p = nullptr, size_t sz = 0, + size_t cap = 0) noexcept + : ptr_(p), size_(sz), capacity_(cap), grow_(grow) {} + + FMT_CONSTEXPR20 ~buffer() = default; + buffer(buffer&&) = default; + + /// Sets the buffer data and capacity. + FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) noexcept { + ptr_ = buf_data; + capacity_ = buf_capacity; + } + + public: + using value_type = T; + using const_reference = const T&; + + buffer(const buffer&) = delete; + void operator=(const buffer&) = delete; + + auto begin() noexcept -> T* { return ptr_; } + auto end() noexcept -> T* { return ptr_ + size_; } + + auto begin() const noexcept -> const T* { return ptr_; } + auto end() const noexcept -> const T* { return ptr_ + size_; } + + /// Returns the size of this buffer. + constexpr auto size() const noexcept -> size_t { return size_; } + + /// Returns the capacity of this buffer. + constexpr auto capacity() const noexcept -> size_t { return capacity_; } + + /// Returns a pointer to the buffer data (not null-terminated). + FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; } + FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; } + + /// Clears this buffer. + FMT_CONSTEXPR void clear() { size_ = 0; } + + // Tries resizing the buffer to contain `count` elements. If T is a POD type + // the new elements may not be initialized. + FMT_CONSTEXPR void try_resize(size_t count) { + try_reserve(count); + size_ = min_of(count, capacity_); + } + + // Tries increasing the buffer capacity to `new_capacity`. It can increase the + // capacity by a smaller amount than requested but guarantees there is space + // for at least one additional element either by increasing the capacity or by + // flushing the buffer if it is full. + FMT_CONSTEXPR void try_reserve(size_t new_capacity) { + if (new_capacity > capacity_) grow_(*this, new_capacity); + } + + FMT_CONSTEXPR void push_back(const T& value) { + try_reserve(size_ + 1); + ptr_[size_++] = value; + } + + /// Appends data to the end of the buffer. + template +// Workaround for MSVC2019 to fix error C2893: Failed to specialize function +// template 'void fmt::v11::detail::buffer::append(const U *,const U *)'. +#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940 + FMT_CONSTEXPR20 +#endif + void + append(const U* begin, const U* end) { + while (begin != end) { + auto count = to_unsigned(end - begin); + try_reserve(size_ + count); + auto free_cap = capacity_ - size_; + if (free_cap < count) count = free_cap; + // A loop is faster than memcpy on small sizes. + T* out = ptr_ + size_; + for (size_t i = 0; i < count; ++i) out[i] = begin[i]; + size_ += count; + begin += count; + } + } + + template FMT_CONSTEXPR auto operator[](Idx index) -> T& { + return ptr_[index]; + } + template + FMT_CONSTEXPR auto operator[](Idx index) const -> const T& { + return ptr_[index]; + } +}; + +struct buffer_traits { + constexpr explicit buffer_traits(size_t) {} + constexpr auto count() const -> size_t { return 0; } + constexpr auto limit(size_t size) const -> size_t { return size; } +}; + +class fixed_buffer_traits { + private: + size_t count_ = 0; + size_t limit_; + + public: + constexpr explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} + constexpr auto count() const -> size_t { return count_; } + FMT_CONSTEXPR auto limit(size_t size) -> size_t { + size_t n = limit_ > count_ ? limit_ - count_ : 0; + count_ += size; + return min_of(size, n); + } +}; + +// A buffer that writes to an output iterator when flushed. +template +class iterator_buffer : public Traits, public buffer { + private: + OutputIt out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() == buffer_size) static_cast(buf).flush(); + } + + void flush() { + auto size = this->size(); + this->clear(); + const T* begin = data_; + const T* end = begin + this->limit(size); + while (begin != end) *out_++ = *begin++; + } + + public: + explicit iterator_buffer(OutputIt out, size_t n = buffer_size) + : Traits(n), buffer(grow, data_, 0, buffer_size), out_(out) {} + iterator_buffer(iterator_buffer&& other) noexcept + : Traits(other), + buffer(grow, data_, 0, buffer_size), + out_(other.out_) {} + ~iterator_buffer() { + // Don't crash if flush fails during unwinding. + FMT_TRY { flush(); } + FMT_CATCH(...) {} + } + + auto out() -> OutputIt { + flush(); + return out_; + } + auto count() const -> size_t { return Traits::count() + this->size(); } +}; + +template +class iterator_buffer : public fixed_buffer_traits, + public buffer { + private: + T* out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() == buf.capacity()) + static_cast(buf).flush(); + } + + void flush() { + size_t n = this->limit(this->size()); + if (this->data() == out_) { + out_ += n; + this->set(data_, buffer_size); + } + this->clear(); + } + + public: + explicit iterator_buffer(T* out, size_t n = buffer_size) + : fixed_buffer_traits(n), buffer(grow, out, 0, n), out_(out) {} + iterator_buffer(iterator_buffer&& other) noexcept + : fixed_buffer_traits(other), + buffer(static_cast(other)), + out_(other.out_) { + if (this->data() != out_) { + this->set(data_, buffer_size); + this->clear(); + } + } + ~iterator_buffer() { flush(); } + + auto out() -> T* { + flush(); + return out_; + } + auto count() const -> size_t { + return fixed_buffer_traits::count() + this->size(); + } +}; + +template class iterator_buffer : public buffer { + public: + explicit iterator_buffer(T* out, size_t = 0) + : buffer([](buffer&, size_t) {}, out, 0, ~size_t()) {} + + auto out() -> T* { return &*this->end(); } +}; + +template +class container_buffer : public buffer { + private: + using value_type = typename Container::value_type; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t capacity) { + auto& self = static_cast(buf); + self.container.resize(capacity); + self.set(&self.container[0], capacity); + } + + public: + Container& container; + + explicit container_buffer(Container& c) + : buffer(grow, c.size()), container(c) {} +}; + +// A buffer that writes to a container with the contiguous storage. +template +class iterator_buffer< + OutputIt, + enable_if_t::value && + is_contiguous::value, + typename OutputIt::container_type::value_type>> + : public container_buffer { + private: + using base = container_buffer; + + public: + explicit iterator_buffer(typename OutputIt::container_type& c) : base(c) {} + explicit iterator_buffer(OutputIt out, size_t = 0) + : base(get_container(out)) {} + + auto out() -> OutputIt { return OutputIt(this->container); } +}; + +// A buffer that counts the number of code units written discarding the output. +template class counting_buffer : public buffer { + private: + enum { buffer_size = 256 }; + T data_[buffer_size]; + size_t count_ = 0; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() != buffer_size) return; + static_cast(buf).count_ += buf.size(); + buf.clear(); + } + + public: + FMT_CONSTEXPR counting_buffer() : buffer(grow, data_, 0, buffer_size) {} + + constexpr auto count() const noexcept -> size_t { + return count_ + this->size(); + } +}; + +template +struct is_back_insert_iterator> : std::true_type {}; + +template +struct has_back_insert_iterator_container_append : std::false_type {}; +template +struct has_back_insert_iterator_container_append< + OutputIt, InputIt, + void_t()) + .append(std::declval(), + std::declval()))>> : std::true_type {}; + +// An optimized version of std::copy with the output value type (T). +template ::value&& + has_back_insert_iterator_container_append< + OutputIt, InputIt>::value)> +FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) + -> OutputIt { + get_container(out).append(begin, end); + return out; +} + +template ::value && + !has_back_insert_iterator_container_append< + OutputIt, InputIt>::value)> +FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) + -> OutputIt { + auto& c = get_container(out); + c.insert(c.end(), begin, end); + return out; +} + +template ::value)> +FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { + while (begin != end) *out++ = static_cast(*begin++); + return out; +} + +template +FMT_CONSTEXPR auto copy(basic_string_view s, OutputIt out) -> OutputIt { + return copy(s.begin(), s.end(), out); +} + +template +struct is_buffer_appender : std::false_type {}; +template +struct is_buffer_appender< + It, bool_constant< + is_back_insert_iterator::value && + std::is_base_of, + typename It::container_type>::value>> + : std::true_type {}; + +// Maps an output iterator to a buffer. +template ::value)> +auto get_buffer(OutputIt out) -> iterator_buffer { + return iterator_buffer(out); +} +template ::value)> +auto get_buffer(OutputIt out) -> buffer& { + return get_container(out); +} + +template +auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) { + return buf.out(); +} +template +auto get_iterator(buffer&, OutputIt out) -> OutputIt { + return out; +} + +// This type is intentionally undefined, only used for errors. +template struct type_is_unformattable_for; + +template struct string_value { + const Char* data; + size_t size; + auto str() const -> basic_string_view { return {data, size}; } +}; + +template struct custom_value { + using char_type = typename Context::char_type; + void* value; + void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); +}; + +template struct named_arg_value { + const named_arg_info* data; + size_t size; +}; + +struct custom_tag {}; + +#if !FMT_BUILTIN_TYPES +# define FMT_BUILTIN , monostate +#else +# define FMT_BUILTIN +#endif + +// A formatting argument value. +template class value { + public: + using char_type = typename Context::char_type; + + union { + monostate no_value; + int int_value; + unsigned uint_value; + long long long_long_value; + unsigned long long ulong_long_value; + int128_opt int128_value; + uint128_opt uint128_value; + bool bool_value; + char_type char_value; + float float_value; + double double_value; + long double long_double_value; + const void* pointer; + string_value string; + custom_value custom; + named_arg_value named_args; + }; + + constexpr FMT_INLINE value() : no_value() {} + constexpr FMT_INLINE value(signed char x) : int_value(x) {} + constexpr FMT_INLINE value(unsigned char x FMT_BUILTIN) : uint_value(x) {} + constexpr FMT_INLINE value(signed short x) : int_value(x) {} + constexpr FMT_INLINE value(unsigned short x FMT_BUILTIN) : uint_value(x) {} + constexpr FMT_INLINE value(int x) : int_value(x) {} + constexpr FMT_INLINE value(unsigned x FMT_BUILTIN) : uint_value(x) {} + FMT_CONSTEXPR FMT_INLINE value(long x FMT_BUILTIN) : value(long_type(x)) {} + FMT_CONSTEXPR FMT_INLINE value(unsigned long x FMT_BUILTIN) + : value(ulong_type(x)) {} + constexpr FMT_INLINE value(long long x FMT_BUILTIN) : long_long_value(x) {} + constexpr FMT_INLINE value(unsigned long long x FMT_BUILTIN) + : ulong_long_value(x) {} + FMT_INLINE value(int128_opt x FMT_BUILTIN) : int128_value(x) {} + FMT_INLINE value(uint128_opt x FMT_BUILTIN) : uint128_value(x) {} + constexpr FMT_INLINE value(bool x FMT_BUILTIN) : bool_value(x) {} + + template + constexpr FMT_INLINE value(bitint x FMT_BUILTIN) : long_long_value(x) { + static_assert(N <= 64, "unsupported _BitInt"); + } + template + constexpr FMT_INLINE value(ubitint x FMT_BUILTIN) : ulong_long_value(x) { + static_assert(N <= 64, "unsupported _BitInt"); + } + + template ::value)> + constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) { + static_assert( + std::is_same::value || std::is_same::value, + "mixing character types is disallowed"); + } + + constexpr FMT_INLINE value(float x FMT_BUILTIN) : float_value(x) {} + constexpr FMT_INLINE value(double x FMT_BUILTIN) : double_value(x) {} + FMT_INLINE value(long double x FMT_BUILTIN) : long_double_value(x) {} + + FMT_CONSTEXPR FMT_INLINE value(char_type* x FMT_BUILTIN) { + string.data = x; + if (is_constant_evaluated()) string.size = 0; + } + FMT_CONSTEXPR FMT_INLINE value(const char_type* x FMT_BUILTIN) { + string.data = x; + if (is_constant_evaluated()) string.size = 0; + } + template , + FMT_ENABLE_IF(!std::is_pointer::value)> + FMT_CONSTEXPR value(const T& x FMT_BUILTIN) { + static_assert(std::is_same::value, + "mixing character types is disallowed"); + auto sv = to_string_view(x); + string.data = sv.data(); + string.size = sv.size(); + } + FMT_INLINE value(void* x FMT_BUILTIN) : pointer(x) {} + FMT_INLINE value(const void* x FMT_BUILTIN) : pointer(x) {} + FMT_INLINE value(volatile void* x FMT_BUILTIN) + : pointer(const_cast(x)) {} + FMT_INLINE value(const volatile void* x FMT_BUILTIN) + : pointer(const_cast(x)) {} + FMT_INLINE value(nullptr_t) : pointer(nullptr) {} + + template ::value || + std::is_member_pointer::value)> + value(const T&) { + // Formatting of arbitrary pointers is disallowed. If you want to format a + // pointer cast it to `void*` or `const void*`. In particular, this forbids + // formatting of `[const] volatile char*` printed as bool by iostreams. + static_assert(sizeof(T) == 0, + "formatting of non-void pointers is disallowed"); + } + + template ::value)> + value(const T& x) : value(format_as(x)) {} + template ::value)> + value(const T& x) : value(formatter::format_as(x)) {} + + template ::value)> + value(const T& named_arg) : value(named_arg.value) {} + + template ::value || !FMT_BUILTIN_TYPES)> + FMT_CONSTEXPR20 FMT_INLINE value(T& x) : value(x, custom_tag()) {} + + FMT_ALWAYS_INLINE value(const named_arg_info* args, size_t size) + : named_args{args, size} {} + + private: + template ())> + FMT_CONSTEXPR value(T& x, custom_tag) { + using value_type = remove_const_t; + // T may overload operator& e.g. std::vector::reference in libc++. + if (!is_constant_evaluated()) { + custom.value = + const_cast(&reinterpret_cast(x)); + } else { + custom.value = nullptr; +#if defined(__cpp_if_constexpr) + if constexpr (std::is_same*>::value) + custom.value = const_cast(&x); +#endif + } + custom.format = format_custom>; + } + + template ())> + FMT_CONSTEXPR value(const T&, custom_tag) { + // Cannot format an argument; to make type T formattable provide a + // formatter specialization: https://fmt.dev/latest/api.html#udt. + type_is_unformattable_for _; + } + + // Formats an argument of a custom type, such as a user-defined class. + template + static void format_custom(void* arg, parse_context& parse_ctx, + Context& ctx) { + auto f = Formatter(); + parse_ctx.advance_to(f.parse(parse_ctx)); + using qualified_type = + conditional_t(), const T, T>; + // format must be const for compatibility with std::format and compilation. + const auto& cf = f; + ctx.advance_to(cf.format(*static_cast(arg), ctx)); + } +}; + +enum { packed_arg_bits = 4 }; +// Maximum number of arguments with packed types. +enum { max_packed_args = 62 / packed_arg_bits }; +enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; +enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; + +template +struct is_output_iterator : std::false_type {}; + +template <> struct is_output_iterator : std::true_type {}; + +template +struct is_output_iterator< + It, T, + void_t&>()++ = std::declval())>> + : std::true_type {}; + +#ifndef FMT_USE_LOCALE +# define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1) +#endif + +// A type-erased reference to an std::locale to avoid a heavy include. +struct locale_ref { +#if FMT_USE_LOCALE + private: + const void* locale_; // A type-erased pointer to std::locale. + + public: + constexpr locale_ref() : locale_(nullptr) {} + + template + locale_ref(const Locale& loc); + + inline explicit operator bool() const noexcept { return locale_ != nullptr; } +#endif // FMT_USE_LOCALE + + template auto get() const -> Locale; +}; + +template constexpr auto encode_types() -> unsigned long long { + return 0; +} + +template +constexpr auto encode_types() -> unsigned long long { + return static_cast(stored_type_constant::value) | + (encode_types() << packed_arg_bits); +} + +template +constexpr auto make_descriptor() -> unsigned long long { + return NUM_ARGS <= max_packed_args ? encode_types() + : is_unpacked_bit | NUM_ARGS; +} + +template +using arg_t = conditional_t, + basic_format_arg>; + +template +struct named_arg_store { + // args_[0].named_args points to named_args to avoid bloating format_args. + arg_t args[1 + NUM_ARGS]; + named_arg_info named_args[NUM_NAMED_ARGS]; + + template + FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values) + : args{{named_args, NUM_NAMED_ARGS}, values...} { + int arg_index = 0, named_arg_index = 0; + FMT_APPLY_VARIADIC( + init_named_arg(named_args, arg_index, named_arg_index, values)); + } + + named_arg_store(named_arg_store&& rhs) { + args[0] = {named_args, NUM_NAMED_ARGS}; + for (size_t i = 1; i < sizeof(args) / sizeof(*args); ++i) + args[i] = rhs.args[i]; + for (size_t i = 0; i < NUM_NAMED_ARGS; ++i) + named_args[i] = rhs.named_args[i]; + } + + named_arg_store(const named_arg_store& rhs) = delete; + named_arg_store& operator=(const named_arg_store& rhs) = delete; + named_arg_store& operator=(named_arg_store&& rhs) = delete; + operator const arg_t*() const { return args + 1; } +}; + +// An array of references to arguments. It can be implicitly converted to +// `basic_format_args` for passing into type-erased formatting functions +// such as `vformat`. It is a plain struct to reduce binary size in debug mode. +template +struct format_arg_store { + // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. + using type = + conditional_t[max_of(1, NUM_ARGS)], + named_arg_store>; + type args; +}; + +// TYPE can be different from type_constant, e.g. for __float128. +template struct native_formatter { + private: + dynamic_format_specs specs_; + + public: + using nonlocking = void; + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); + auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, TYPE); + if (const_check(TYPE == type::char_type)) check_char_specs(specs_); + return end; + } + + template + FMT_CONSTEXPR void set_debug_format(bool set = true) { + specs_.set_type(set ? presentation_type::debug : presentation_type::none); + } + + FMT_PRAGMA_CLANG(diagnostic ignored "-Wundefined-inline") + template + FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const + -> decltype(ctx.out()); +}; + +template +struct locking + : bool_constant::value == type::custom_type> {}; +template +struct locking>::nonlocking>> + : std::false_type {}; + +template FMT_CONSTEXPR inline auto is_locking() -> bool { + return locking::value; +} +template +FMT_CONSTEXPR inline auto is_locking() -> bool { + return locking::value || is_locking(); +} + +FMT_API void vformat_to(buffer& buf, string_view fmt, format_args args, + locale_ref loc = {}); + +#if FMT_WIN32 +FMT_API void vprint_mojibake(FILE*, string_view, format_args, bool); +#else // format_args is passed by reference since it is defined later. +inline void vprint_mojibake(FILE*, string_view, const format_args&, bool) {} +#endif +} // namespace detail + +// The main public API. + +template +FMT_CONSTEXPR void parse_context::do_check_arg_id(int arg_id) { + // Argument id is only checked at compile time during parsing because + // formatting has its own validation. + if (detail::is_constant_evaluated() && use_constexpr_cast) { + auto ctx = static_cast*>(this); + if (arg_id >= ctx->num_args()) report_error("argument not found"); + } +} + +template +FMT_CONSTEXPR void parse_context::check_dynamic_spec(int arg_id) { + using detail::compile_parse_context; + if (detail::is_constant_evaluated() && use_constexpr_cast) + static_cast*>(this)->check_dynamic_spec(arg_id); +} + +FMT_BEGIN_EXPORT + +// An output iterator that appends to a buffer. It is used instead of +// back_insert_iterator to reduce symbol sizes and avoid dependency. +template class basic_appender { + protected: + detail::buffer* container; + + public: + using container_type = detail::buffer; + + FMT_CONSTEXPR basic_appender(detail::buffer& buf) : container(&buf) {} + + FMT_CONSTEXPR20 auto operator=(T c) -> basic_appender& { + container->push_back(c); + return *this; + } + FMT_CONSTEXPR20 auto operator*() -> basic_appender& { return *this; } + FMT_CONSTEXPR20 auto operator++() -> basic_appender& { return *this; } + FMT_CONSTEXPR20 auto operator++(int) -> basic_appender { return *this; } +}; + +// A formatting argument. Context is a template parameter for the compiled API +// where output can be unbuffered. +template class basic_format_arg { + private: + detail::value value_; + detail::type type_; + + friend class basic_format_args; + + using char_type = typename Context::char_type; + + public: + class handle { + private: + detail::custom_value custom_; + + public: + explicit handle(detail::custom_value custom) : custom_(custom) {} + + void format(parse_context& parse_ctx, Context& ctx) const { + custom_.format(custom_.value, parse_ctx, ctx); + } + }; + + constexpr basic_format_arg() : type_(detail::type::none_type) {} + basic_format_arg(const detail::named_arg_info* args, size_t size) + : value_(args, size) {} + template + basic_format_arg(T&& val) + : value_(val), type_(detail::stored_type_constant::value) {} + + constexpr explicit operator bool() const noexcept { + return type_ != detail::type::none_type; + } + auto type() const -> detail::type { return type_; } + + /** + * Visits an argument dispatching to the appropriate visit method based on + * the argument type. For example, if the argument type is `double` then + * `vis(value)` will be called with the value of type `double`. + */ + template + FMT_CONSTEXPR FMT_INLINE auto visit(Visitor&& vis) const -> decltype(vis(0)) { + using detail::map; + switch (type_) { + case detail::type::none_type: break; + case detail::type::int_type: return vis(value_.int_value); + case detail::type::uint_type: return vis(value_.uint_value); + case detail::type::long_long_type: return vis(value_.long_long_value); + case detail::type::ulong_long_type: return vis(value_.ulong_long_value); + case detail::type::int128_type: return vis(map(value_.int128_value)); + case detail::type::uint128_type: return vis(map(value_.uint128_value)); + case detail::type::bool_type: return vis(value_.bool_value); + case detail::type::char_type: return vis(value_.char_value); + case detail::type::float_type: return vis(value_.float_value); + case detail::type::double_type: return vis(value_.double_value); + case detail::type::long_double_type: return vis(value_.long_double_value); + case detail::type::cstring_type: return vis(value_.string.data); + case detail::type::string_type: return vis(value_.string.str()); + case detail::type::pointer_type: return vis(value_.pointer); + case detail::type::custom_type: return vis(handle(value_.custom)); + } + return vis(monostate()); + } + + auto format_custom(const char_type* parse_begin, + parse_context& parse_ctx, Context& ctx) + -> bool { + if (type_ != detail::type::custom_type) return false; + parse_ctx.advance_to(parse_begin); + value_.custom.format(value_.custom.value, parse_ctx, ctx); + return true; + } +}; + +/** + * A view of a collection of formatting arguments. To avoid lifetime issues it + * should only be used as a parameter type in type-erased functions such as + * `vformat`: + * + * void vlog(fmt::string_view fmt, fmt::format_args args); // OK + * fmt::format_args args = fmt::make_format_args(); // Dangling reference + */ +template class basic_format_args { + private: + // A descriptor that contains information about formatting arguments. + // If the number of arguments is less or equal to max_packed_args then + // argument types are passed in the descriptor. This reduces binary code size + // per formatting function call. + unsigned long long desc_; + union { + // If is_packed() returns true then argument values are stored in values_; + // otherwise they are stored in args_. This is done to improve cache + // locality and reduce compiled code size since storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const detail::value* values_; + const basic_format_arg* args_; + }; + + constexpr auto is_packed() const -> bool { + return (desc_ & detail::is_unpacked_bit) == 0; + } + constexpr auto has_named_args() const -> bool { + return (desc_ & detail::has_named_args_bit) != 0; + } + + FMT_CONSTEXPR auto type(int index) const -> detail::type { + int shift = index * detail::packed_arg_bits; + unsigned mask = (1 << detail::packed_arg_bits) - 1; + return static_cast((desc_ >> shift) & mask); + } + + template + using store = + detail::format_arg_store; + + public: + using format_arg = basic_format_arg; + + constexpr basic_format_args() : desc_(0), args_(nullptr) {} + + /// Constructs a `basic_format_args` object from `format_arg_store`. + template + constexpr FMT_ALWAYS_INLINE basic_format_args( + const store& s) + : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), + values_(s.args) {} + + template detail::max_packed_args)> + constexpr basic_format_args(const store& s) + : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), + args_(s.args) {} + + /// Constructs a `basic_format_args` object from a dynamic list of arguments. + constexpr basic_format_args(const format_arg* args, int count, + bool has_named = false) + : desc_(detail::is_unpacked_bit | detail::to_unsigned(count) | + (has_named ? +detail::has_named_args_bit : 0)), + args_(args) {} + + /// Returns the argument with the specified id. + FMT_CONSTEXPR auto get(int id) const -> format_arg { + auto arg = format_arg(); + if (!is_packed()) { + if (id < max_size()) arg = args_[id]; + return arg; + } + if (static_cast(id) >= detail::max_packed_args) return arg; + arg.type_ = type(id); + if (arg.type_ != detail::type::none_type) arg.value_ = values_[id]; + return arg; + } + + template + auto get(basic_string_view name) const -> format_arg { + int id = get_id(name); + return id >= 0 ? get(id) : format_arg(); + } + + template + FMT_CONSTEXPR auto get_id(basic_string_view name) const -> int { + if (!has_named_args()) return -1; + const auto& named_args = + (is_packed() ? values_[-1] : args_[-1].value_).named_args; + for (size_t i = 0; i < named_args.size; ++i) { + if (named_args.data[i].name == name) return named_args.data[i].id; + } + return -1; + } + + auto max_size() const -> int { + unsigned long long max_packed = detail::max_packed_args; + return static_cast(is_packed() ? max_packed + : desc_ & ~detail::is_unpacked_bit); + } +}; + +// A formatting context. +class context { + private: + appender out_; + format_args args_; + FMT_NO_UNIQUE_ADDRESS detail::locale_ref loc_; + + public: + /// The character type for the output. + using char_type = char; + + using iterator = appender; + using format_arg = basic_format_arg; + using parse_context_type FMT_DEPRECATED = parse_context<>; + template using formatter_type FMT_DEPRECATED = formatter; + enum { builtin_types = FMT_BUILTIN_TYPES }; + + /// Constructs a `context` object. References to the arguments are stored + /// in the object so make sure they have appropriate lifetimes. + FMT_CONSTEXPR context(iterator out, format_args args, + detail::locale_ref loc = {}) + : out_(out), args_(args), loc_(loc) {} + context(context&&) = default; + context(const context&) = delete; + void operator=(const context&) = delete; + + FMT_CONSTEXPR auto arg(int id) const -> format_arg { return args_.get(id); } + inline auto arg(string_view name) const -> format_arg { + return args_.get(name); + } + FMT_CONSTEXPR auto arg_id(string_view name) const -> int { + return args_.get_id(name); + } + + // Returns an iterator to the beginning of the output range. + FMT_CONSTEXPR auto out() const -> iterator { return out_; } + + // Advances the begin iterator to `it`. + FMT_CONSTEXPR void advance_to(iterator) {} + + FMT_CONSTEXPR auto locale() const -> detail::locale_ref { return loc_; } +}; + +template struct runtime_format_string { + basic_string_view str; +}; + +/** + * Creates a runtime format string. + * + * **Example**: + * + * // Check format string at runtime instead of compile-time. + * fmt::print(fmt::runtime("{:d}"), "I am not a number"); + */ +inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } + +/// A compile-time format string. Use `format_string` in the public API to +/// prevent type deduction. +template struct fstring { + private: + static constexpr int num_static_named_args = + detail::count_static_named_args(); + + using checker = detail::format_string_checker< + char, static_cast(sizeof...(T)), num_static_named_args, + num_static_named_args != detail::count_named_args()>; + + using arg_pack = detail::arg_pack; + + public: + string_view str; + using t = fstring; + + // Reports a compile-time error if S is not a valid format string for T. + template + FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) { + using namespace detail; + static_assert(count<(std::is_base_of>::value && + std::is_reference::value)...>() == 0, + "passing views as lvalues is disallowed"); + if (FMT_USE_CONSTEVAL) parse_format_string(s, checker(s, arg_pack())); +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert( + FMT_USE_CONSTEVAL && sizeof(s) != 0, + "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); +#endif + } + template ::value)> + FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const S& s) : str(s) { + auto sv = string_view(str); + if (FMT_USE_CONSTEVAL) + detail::parse_format_string(sv, checker(sv, arg_pack())); +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert( + FMT_USE_CONSTEVAL && sizeof(s) != 0, + "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); +#endif + } + template ::value&& + std::is_same::value)> + FMT_ALWAYS_INLINE fstring(const S&) : str(S()) { + FMT_CONSTEXPR auto sv = string_view(S()); + FMT_CONSTEXPR int ignore = + (parse_format_string(sv, checker(sv, arg_pack())), 0); + detail::ignore_unused(ignore); + } + fstring(runtime_format_string<> fmt) : str(fmt.str) {} + + // Returning by reference generates better code in debug mode. + FMT_ALWAYS_INLINE operator const string_view&() const { return str; } + auto get() const -> string_view { return str; } +}; + +template using format_string = typename fstring::t; + +template +using is_formattable = bool_constant::value, int*, T>, Char>, + void>::value>; +#ifdef __cpp_concepts +template +concept formattable = is_formattable, Char>::value; +#endif + +template +using has_formatter FMT_DEPRECATED = std::is_constructible>; + +// A formatter specialization for natively supported types. +template +struct formatter::value != + detail::type::custom_type>> + : detail::native_formatter::value> { +}; + +/** + * Constructs an object that stores references to arguments and can be + * implicitly converted to `format_args`. `Context` can be omitted in which case + * it defaults to `context`. See `arg` for lifetime considerations. + */ +// Take arguments by lvalue references to avoid some lifetime issues, e.g. +// auto args = make_format_args(std::string()); +template (), + unsigned long long DESC = detail::make_descriptor()> +constexpr FMT_ALWAYS_INLINE auto make_format_args(T&... args) + -> detail::format_arg_store { + // Suppress warnings for pathological types convertible to detail::value. + FMT_PRAGMA_GCC(diagnostic ignored "-Wconversion") + return {{args...}}; +} + +template +using vargs = + detail::format_arg_store(), + detail::make_descriptor()>; + +/** + * Returns a named argument to be used in a formatting function. + * It should only be used in a call to a formatting function. + * + * **Example**: + * + * fmt::print("The answer is {answer}.", fmt::arg("answer", 42)); + */ +template +inline auto arg(const Char* name, const T& arg) -> detail::named_arg { + return {name, arg}; +} + +/// Formats a string and writes the output to `out`. +template , + char>::value)> +auto vformat_to(OutputIt&& out, string_view fmt, format_args args) + -> remove_cvref_t { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, fmt, args, {}); + return detail::get_iterator(buf, out); +} + +/** + * Formats `args` according to specifications in `fmt`, writes the result to + * the output iterator `out` and returns the iterator past the end of the output + * range. `format_to` does not append a terminating null character. + * + * **Example**: + * + * auto out = std::vector(); + * fmt::format_to(std::back_inserter(out), "{}", 42); + */ +template , + char>::value)> +FMT_INLINE auto format_to(OutputIt&& out, format_string fmt, T&&... args) + -> remove_cvref_t { + return vformat_to(out, fmt.str, vargs{{args...}}); +} + +template struct format_to_n_result { + /// Iterator past the end of the output range. + OutputIt out; + /// Total (not truncated) output size. + size_t size; +}; + +template ::value)> +auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + detail::vformat_to(buf, fmt, args, {}); + return {buf.out(), buf.count()}; +} + +/** + * Formats `args` according to specifications in `fmt`, writes up to `n` + * characters of the result to the output iterator `out` and returns the total + * (not truncated) output size and the iterator past the end of the output + * range. `format_to_n` does not append a terminating null character. + */ +template ::value)> +FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, + T&&... args) -> format_to_n_result { + return vformat_to_n(out, n, fmt.str, vargs{{args...}}); +} + +struct format_to_result { + /// Pointer to just after the last successful write in the array. + char* out; + /// Specifies if the output was truncated. + bool truncated; + + FMT_CONSTEXPR operator char*() const { + // Report truncation to prevent silent data loss. + if (truncated) report_error("output is truncated"); + return out; + } +}; + +template +auto vformat_to(char (&out)[N], string_view fmt, format_args args) + -> format_to_result { + auto result = vformat_to_n(out, N, fmt, args); + return {result.out, result.size > N}; +} + +template +FMT_INLINE auto format_to(char (&out)[N], format_string fmt, T&&... args) + -> format_to_result { + auto result = vformat_to_n(out, N, fmt.str, vargs{{args...}}); + return {result.out, result.size > N}; +} + +/// Returns the number of chars in the output of `format(fmt, args...)`. +template +FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, + T&&... args) -> size_t { + auto buf = detail::counting_buffer<>(); + detail::vformat_to(buf, fmt.str, vargs{{args...}}, {}); + return buf.count(); +} + +FMT_API void vprint(string_view fmt, format_args args); +FMT_API void vprint(FILE* f, string_view fmt, format_args args); +FMT_API void vprintln(FILE* f, string_view fmt, format_args args); +FMT_API void vprint_buffered(FILE* f, string_view fmt, format_args args); + +/** + * Formats `args` according to specifications in `fmt` and writes the output + * to `stdout`. + * + * **Example**: + * + * fmt::print("The answer is {}.", 42); + */ +template +FMT_INLINE void print(format_string fmt, T&&... args) { + vargs va = {{args...}}; + if (detail::const_check(!detail::use_utf8)) + return detail::vprint_mojibake(stdout, fmt.str, va, false); + return detail::is_locking() ? vprint_buffered(stdout, fmt.str, va) + : vprint(fmt.str, va); +} + +/** + * Formats `args` according to specifications in `fmt` and writes the + * output to the file `f`. + * + * **Example**: + * + * fmt::print(stderr, "Don't {}!", "panic"); + */ +template +FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { + vargs va = {{args...}}; + if (detail::const_check(!detail::use_utf8)) + return detail::vprint_mojibake(f, fmt.str, va, false); + return detail::is_locking() ? vprint_buffered(f, fmt.str, va) + : vprint(f, fmt.str, va); +} + +/// Formats `args` according to specifications in `fmt` and writes the output +/// to the file `f` followed by a newline. +template +FMT_INLINE void println(FILE* f, format_string fmt, T&&... args) { + vargs va = {{args...}}; + return detail::const_check(detail::use_utf8) + ? vprintln(f, fmt.str, va) + : detail::vprint_mojibake(f, fmt.str, va, true); +} + +/// Formats `args` according to specifications in `fmt` and writes the output +/// to `stdout` followed by a newline. +template +FMT_INLINE void println(format_string fmt, T&&... args) { + return fmt::println(stdout, fmt, static_cast(args)...); +} + +FMT_END_EXPORT +FMT_PRAGMA_CLANG(diagnostic pop) +FMT_PRAGMA_GCC(pop_options) +FMT_END_NAMESPACE + +#ifdef FMT_HEADER_ONLY +# include "format.h" +#endif +#endif // FMT_BASE_H_ diff --git a/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/chrono.h b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/chrono.h new file mode 100644 index 000000000..abf3671e5 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/chrono.h @@ -0,0 +1,2338 @@ +// Formatting library for C++ - chrono support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_CHRONO_H_ +#define FMT_CHRONO_H_ + +#ifndef FMT_MODULE +# include +# include +# include // std::isfinite +# include // std::memcpy +# include +# include +# include +# include +# include +#endif + +#include "format.h" + +namespace fmt_detail { +struct time_zone { + template + auto to_sys(T) + -> std::chrono::time_point { + return {}; + } +}; +template inline auto current_zone(T...) -> time_zone* { + return nullptr; +} + +template inline void _tzset(T...) {} +} // namespace fmt_detail + +FMT_BEGIN_NAMESPACE + +// Enable safe chrono durations, unless explicitly disabled. +#ifndef FMT_SAFE_DURATION_CAST +# define FMT_SAFE_DURATION_CAST 1 +#endif +#if FMT_SAFE_DURATION_CAST + +// For conversion between std::chrono::durations without undefined +// behaviour or erroneous results. +// This is a stripped down version of duration_cast, for inclusion in fmt. +// See https://github.com/pauldreik/safe_duration_cast +// +// Copyright Paul Dreik 2019 +namespace safe_duration_cast { + +template ::value && + std::numeric_limits::is_signed == + std::numeric_limits::is_signed)> +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { + ec = 0; + using F = std::numeric_limits; + using T = std::numeric_limits; + static_assert(F::is_integer, "From must be integral"); + static_assert(T::is_integer, "To must be integral"); + + // A and B are both signed, or both unsigned. + if (detail::const_check(F::digits <= T::digits)) { + // From fits in To without any problem. + } else { + // From does not always fit in To, resort to a dynamic check. + if (from < (T::min)() || from > (T::max)()) { + // outside range. + ec = 1; + return {}; + } + } + return static_cast(from); +} + +/// Converts From to To, without loss. If the dynamic value of from +/// can't be converted to To without loss, ec is set. +template ::value && + std::numeric_limits::is_signed != + std::numeric_limits::is_signed)> +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { + ec = 0; + using F = std::numeric_limits; + using T = std::numeric_limits; + static_assert(F::is_integer, "From must be integral"); + static_assert(T::is_integer, "To must be integral"); + + if (detail::const_check(F::is_signed && !T::is_signed)) { + // From may be negative, not allowed! + if (fmt::detail::is_negative(from)) { + ec = 1; + return {}; + } + // From is positive. Can it always fit in To? + if (detail::const_check(F::digits > T::digits) && + from > static_cast(detail::max_value())) { + ec = 1; + return {}; + } + } + + if (detail::const_check(!F::is_signed && T::is_signed && + F::digits >= T::digits) && + from > static_cast(detail::max_value())) { + ec = 1; + return {}; + } + return static_cast(from); // Lossless conversion. +} + +template ::value)> +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { + ec = 0; + return from; +} // function + +// clang-format off +/** + * converts From to To if possible, otherwise ec is set. + * + * input | output + * ---------------------------------|--------------- + * NaN | NaN + * Inf | Inf + * normal, fits in output | converted (possibly lossy) + * normal, does not fit in output | ec is set + * subnormal | best effort + * -Inf | -Inf + */ +// clang-format on +template ::value)> +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { + ec = 0; + using T = std::numeric_limits; + static_assert(std::is_floating_point::value, "From must be floating"); + static_assert(std::is_floating_point::value, "To must be floating"); + + // catch the only happy case + if (std::isfinite(from)) { + if (from >= T::lowest() && from <= (T::max)()) { + return static_cast(from); + } + // not within range. + ec = 1; + return {}; + } + + // nan and inf will be preserved + return static_cast(from); +} // function + +template ::value)> +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { + ec = 0; + static_assert(std::is_floating_point::value, "From must be floating"); + return from; +} + +/// Safe duration_cast between floating point durations +template ::value), + FMT_ENABLE_IF(std::is_floating_point::value)> +auto safe_duration_cast(std::chrono::duration from, + int& ec) -> To { + using From = std::chrono::duration; + ec = 0; + if (std::isnan(from.count())) { + // nan in, gives nan out. easy. + return To{std::numeric_limits::quiet_NaN()}; + } + // maybe we should also check if from is denormal, and decide what to do about + // it. + + // +-inf should be preserved. + if (std::isinf(from.count())) { + return To{from.count()}; + } + + // the basic idea is that we need to convert from count() in the from type + // to count() in the To type, by multiplying it with this: + struct Factor + : std::ratio_divide {}; + + static_assert(Factor::num > 0, "num must be positive"); + static_assert(Factor::den > 0, "den must be positive"); + + // the conversion is like this: multiply from.count() with Factor::num + // /Factor::den and convert it to To::rep, all this without + // overflow/underflow. let's start by finding a suitable type that can hold + // both To, From and Factor::num + using IntermediateRep = + typename std::common_type::type; + + // force conversion of From::rep -> IntermediateRep to be safe, + // even if it will never happen be narrowing in this context. + IntermediateRep count = + safe_float_conversion(from.count(), ec); + if (ec) { + return {}; + } + + // multiply with Factor::num without overflow or underflow + if (detail::const_check(Factor::num != 1)) { + constexpr auto max1 = detail::max_value() / + static_cast(Factor::num); + if (count > max1) { + ec = 1; + return {}; + } + constexpr auto min1 = std::numeric_limits::lowest() / + static_cast(Factor::num); + if (count < min1) { + ec = 1; + return {}; + } + count *= static_cast(Factor::num); + } + + // this can't go wrong, right? den>0 is checked earlier. + if (detail::const_check(Factor::den != 1)) { + using common_t = typename std::common_type::type; + count /= static_cast(Factor::den); + } + + // convert to the to type, safely + using ToRep = typename To::rep; + + const ToRep tocount = safe_float_conversion(count, ec); + if (ec) { + return {}; + } + return To{tocount}; +} +} // namespace safe_duration_cast +#endif + +namespace detail { + +// Check if std::chrono::utc_time is available. +#ifdef FMT_USE_UTC_TIME +// Use the provided definition. +#elif defined(__cpp_lib_chrono) +# define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L) +#else +# define FMT_USE_UTC_TIME 0 +#endif +#if FMT_USE_UTC_TIME +using utc_clock = std::chrono::utc_clock; +#else +struct utc_clock { + void to_sys(); +}; +#endif + +// Check if std::chrono::local_time is available. +#ifdef FMT_USE_LOCAL_TIME +// Use the provided definition. +#elif defined(__cpp_lib_chrono) +# define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L) +#else +# define FMT_USE_LOCAL_TIME 0 +#endif +#if FMT_USE_LOCAL_TIME +using local_t = std::chrono::local_t; +#else +struct local_t {}; +#endif + +} // namespace detail + +template +using sys_time = std::chrono::time_point; + +template +using utc_time = std::chrono::time_point; + +template +using local_time = std::chrono::time_point; + +namespace detail { + +// Prevents expansion of a preceding token as a function-style macro. +// Usage: f FMT_NOMACRO() +#define FMT_NOMACRO + +template struct null {}; +inline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); } +inline auto localtime_s(...) -> null<> { return null<>(); } +inline auto gmtime_r(...) -> null<> { return null<>(); } +inline auto gmtime_s(...) -> null<> { return null<>(); } + +// It is defined here and not in ostream.h because the latter has expensive +// includes. +template class formatbuf : public StreamBuf { + private: + using char_type = typename StreamBuf::char_type; + using streamsize = decltype(std::declval().sputn(nullptr, 0)); + using int_type = typename StreamBuf::int_type; + using traits_type = typename StreamBuf::traits_type; + + buffer& buffer_; + + public: + explicit formatbuf(buffer& buf) : buffer_(buf) {} + + protected: + // The put area is always empty. This makes the implementation simpler and has + // the advantage that the streambuf and the buffer are always in sync and + // sputc never writes into uninitialized memory. A disadvantage is that each + // call to sputc always results in a (virtual) call to overflow. There is no + // disadvantage here for sputn since this always results in a call to xsputn. + + auto overflow(int_type ch) -> int_type override { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast(ch)); + return ch; + } + + auto xsputn(const char_type* s, streamsize count) -> streamsize override { + buffer_.append(s, s + count); + return count; + } +}; + +inline auto get_classic_locale() -> const std::locale& { + static const auto& locale = std::locale::classic(); + return locale; +} + +template struct codecvt_result { + static constexpr const size_t max_size = 32; + CodeUnit buf[max_size]; + CodeUnit* end; +}; + +template +void write_codecvt(codecvt_result& out, string_view in, + const std::locale& loc) { + FMT_PRAGMA_CLANG(diagnostic push) + FMT_PRAGMA_CLANG(diagnostic ignored "-Wdeprecated") + auto& f = std::use_facet>(loc); + FMT_PRAGMA_CLANG(diagnostic pop) + auto mb = std::mbstate_t(); + const char* from_next = nullptr; + auto result = f.in(mb, in.begin(), in.end(), from_next, std::begin(out.buf), + std::end(out.buf), out.end); + if (result != std::codecvt_base::ok) + FMT_THROW(format_error("failed to format time")); +} + +template +auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) + -> OutputIt { + if (detail::use_utf8 && loc != get_classic_locale()) { + // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and + // gcc-4. +#if FMT_MSC_VERSION != 0 || \ + (defined(__GLIBCXX__) && \ + (!defined(_GLIBCXX_USE_DUAL_ABI) || _GLIBCXX_USE_DUAL_ABI == 0)) + // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5 + // and newer. + using code_unit = wchar_t; +#else + using code_unit = char32_t; +#endif + + using unit_t = codecvt_result; + unit_t unit; + write_codecvt(unit, in, loc); + // In UTF-8 is used one to four one-byte code units. + auto u = + to_utf8>(); + if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)})) + FMT_THROW(format_error("failed to format time")); + return copy(u.c_str(), u.c_str() + u.size(), out); + } + return copy(in.data(), in.data() + in.size(), out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + codecvt_result unit; + write_codecvt(unit, sv, loc); + return copy(unit.buf, unit.end, out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + return write_encoded_tm_str(out, sv, loc); +} + +template +inline void do_write(buffer& buf, const std::tm& time, + const std::locale& loc, char format, char modifier) { + auto&& format_buf = formatbuf>(buf); + auto&& os = std::basic_ostream(&format_buf); + os.imbue(loc); + const auto& facet = std::use_facet>(loc); + auto end = facet.put(os, os, Char(' '), &time, format, modifier); + if (end.failed()) FMT_THROW(format_error("failed to format time")); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = get_buffer(out); + do_write(buf, time, loc, format, modifier); + return get_iterator(buf, out); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = basic_memory_buffer(); + do_write(buf, time, loc, format, modifier); + return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); +} + +template +struct is_same_arithmetic_type + : public std::integral_constant::value && + std::is_integral::value) || + (std::is_floating_point::value && + std::is_floating_point::value)> { +}; + +FMT_NORETURN inline void throw_duration_error() { + FMT_THROW(format_error("cannot format duration")); +} + +// Cast one integral duration to another with an overflow check. +template ::value&& + std::is_integral::value)> +auto duration_cast(std::chrono::duration from) -> To { +#if !FMT_SAFE_DURATION_CAST + return std::chrono::duration_cast(from); +#else + // The conversion factor: to.count() == factor * from.count(). + using factor = std::ratio_divide; + + using common_rep = typename std::common_type::type; + + int ec = 0; + auto count = safe_duration_cast::lossless_integral_conversion( + from.count(), ec); + if (ec) throw_duration_error(); + + // Multiply from.count() by factor and check for overflow. + if (const_check(factor::num != 1)) { + if (count > max_value() / factor::num) throw_duration_error(); + const auto min = (std::numeric_limits::min)() / factor::num; + if (const_check(!std::is_unsigned::value) && count < min) + throw_duration_error(); + count *= factor::num; + } + if (const_check(factor::den != 1)) count /= factor::den; + auto to = + To(safe_duration_cast::lossless_integral_conversion( + count, ec)); + if (ec) throw_duration_error(); + return to; +#endif +} + +template ::value&& + std::is_floating_point::value)> +auto duration_cast(std::chrono::duration from) -> To { +#if FMT_SAFE_DURATION_CAST + // Throwing version of safe_duration_cast is only available for + // integer to integer or float to float casts. + int ec; + To to = safe_duration_cast::safe_duration_cast(from, ec); + if (ec) throw_duration_error(); + return to; +#else + // Standard duration cast, may overflow. + return std::chrono::duration_cast(from); +#endif +} + +template < + typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(!is_same_arithmetic_type::value)> +auto duration_cast(std::chrono::duration from) -> To { + // Mixed integer <-> float cast is not supported by safe_duration_cast. + return std::chrono::duration_cast(from); +} + +template +auto to_time_t(sys_time time_point) -> std::time_t { + // Cannot use std::chrono::system_clock::to_time_t since this would first + // require a cast to std::chrono::system_clock::time_point, which could + // overflow. + return detail::duration_cast>( + time_point.time_since_epoch()) + .count(); +} + +// Workaround a bug in libstdc++ which sets __cpp_lib_chrono to 201907 without +// providing current_zone(): https://github.com/fmtlib/fmt/issues/4160. +template FMT_CONSTEXPR auto has_current_zone() -> bool { + using namespace std::chrono; + using namespace fmt_detail; + return !std::is_same::value; +} +} // namespace detail + +FMT_BEGIN_EXPORT + +/** + * Converts given time since epoch as `std::time_t` value into calendar time, + * expressed in local time. Unlike `std::localtime`, this function is + * thread-safe on most platforms. + */ +inline auto localtime(std::time_t time) -> std::tm { + struct dispatcher { + std::time_t time_; + std::tm tm_; + + inline dispatcher(std::time_t t) : time_(t) {} + + inline auto run() -> bool { + using namespace fmt::detail; + return handle(localtime_r(&time_, &tm_)); + } + + inline auto handle(std::tm* tm) -> bool { return tm != nullptr; } + + inline auto handle(detail::null<>) -> bool { + using namespace fmt::detail; + return fallback(localtime_s(&tm_, &time_)); + } + + inline auto fallback(int res) -> bool { return res == 0; } + +#if !FMT_MSC_VERSION + inline auto fallback(detail::null<>) -> bool { + using namespace fmt::detail; + std::tm* tm = std::localtime(&time_); + if (tm) tm_ = *tm; + return tm != nullptr; + } +#endif + }; + dispatcher lt(time); + // Too big time values may be unsupported. + if (!lt.run()) FMT_THROW(format_error("time_t value out of range")); + return lt.tm_; +} + +#if FMT_USE_LOCAL_TIME +template ())> +inline auto localtime(std::chrono::local_time time) -> std::tm { + using namespace std::chrono; + using namespace fmt_detail; + return localtime(detail::to_time_t(current_zone()->to_sys(time))); +} +#endif + +/** + * Converts given time since epoch as `std::time_t` value into calendar time, + * expressed in Coordinated Universal Time (UTC). Unlike `std::gmtime`, this + * function is thread-safe on most platforms. + */ +inline auto gmtime(std::time_t time) -> std::tm { + struct dispatcher { + std::time_t time_; + std::tm tm_; + + inline dispatcher(std::time_t t) : time_(t) {} + + inline auto run() -> bool { + using namespace fmt::detail; + return handle(gmtime_r(&time_, &tm_)); + } + + inline auto handle(std::tm* tm) -> bool { return tm != nullptr; } + + inline auto handle(detail::null<>) -> bool { + using namespace fmt::detail; + return fallback(gmtime_s(&tm_, &time_)); + } + + inline auto fallback(int res) -> bool { return res == 0; } + +#if !FMT_MSC_VERSION + inline auto fallback(detail::null<>) -> bool { + std::tm* tm = std::gmtime(&time_); + if (tm) tm_ = *tm; + return tm != nullptr; + } +#endif + }; + auto gt = dispatcher(time); + // Too big time values may be unsupported. + if (!gt.run()) FMT_THROW(format_error("time_t value out of range")); + return gt.tm_; +} + +template +inline auto gmtime(sys_time time_point) -> std::tm { + return gmtime(detail::to_time_t(time_point)); +} + +namespace detail { + +// Writes two-digit numbers a, b and c separated by sep to buf. +// The method by Pavel Novikov based on +// https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/. +inline void write_digit2_separated(char* buf, unsigned a, unsigned b, + unsigned c, char sep) { + unsigned long long digits = + a | (b << 24) | (static_cast(c) << 48); + // Convert each value to BCD. + // We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b. + // The difference is + // y - x = a * 6 + // a can be found from x: + // a = floor(x / 10) + // then + // y = x + a * 6 = x + floor(x / 10) * 6 + // floor(x / 10) is (x * 205) >> 11 (needs 16 bits). + digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6; + // Put low nibbles to high bytes and high nibbles to low bytes. + digits = ((digits & 0x00f00000f00000f0) >> 4) | + ((digits & 0x000f00000f00000f) << 8); + auto usep = static_cast(sep); + // Add ASCII '0' to each digit byte and insert separators. + digits |= 0x3030003030003030 | (usep << 16) | (usep << 40); + + constexpr const size_t len = 8; + if (const_check(is_big_endian())) { + char tmp[len]; + std::memcpy(tmp, &digits, len); + std::reverse_copy(tmp, tmp + len, buf); + } else { + std::memcpy(buf, &digits, len); + } +} + +template +FMT_CONSTEXPR inline auto get_units() -> const char* { + if (std::is_same::value) return "as"; + if (std::is_same::value) return "fs"; + if (std::is_same::value) return "ps"; + if (std::is_same::value) return "ns"; + if (std::is_same::value) + return detail::use_utf8 ? "µs" : "us"; + if (std::is_same::value) return "ms"; + if (std::is_same::value) return "cs"; + if (std::is_same::value) return "ds"; + if (std::is_same>::value) return "s"; + if (std::is_same::value) return "das"; + if (std::is_same::value) return "hs"; + if (std::is_same::value) return "ks"; + if (std::is_same::value) return "Ms"; + if (std::is_same::value) return "Gs"; + if (std::is_same::value) return "Ts"; + if (std::is_same::value) return "Ps"; + if (std::is_same::value) return "Es"; + if (std::is_same>::value) return "min"; + if (std::is_same>::value) return "h"; + if (std::is_same>::value) return "d"; + return nullptr; +} + +enum class numeric_system { + standard, + // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale. + alternative +}; + +// Glibc extensions for formatting numeric values. +enum class pad_type { + // Pad a numeric result string with zeros (the default). + zero, + // Do not pad a numeric result string. + none, + // Pad a numeric result string with spaces. + space, +}; + +template +auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt { + if (pad == pad_type::none) return out; + return detail::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); +} + +template +auto write_padding(OutputIt out, pad_type pad) -> OutputIt { + if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0'; + return out; +} + +// Parses a put_time-like format string and invokes handler actions. +template +FMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + if (begin == end || *begin == '}') return begin; + if (*begin != '%') FMT_THROW(format_error("invalid format")); + auto ptr = begin; + while (ptr != end) { + pad_type pad = pad_type::zero; + auto c = *ptr; + if (c == '}') break; + if (c != '%') { + ++ptr; + continue; + } + if (begin != ptr) handler.on_text(begin, ptr); + ++ptr; // consume '%' + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr; + switch (c) { + case '_': + pad = pad_type::space; + ++ptr; + break; + case '-': + pad = pad_type::none; + ++ptr; + break; + } + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case '%': handler.on_text(ptr - 1, ptr); break; + case 'n': { + const Char newline[] = {'\n'}; + handler.on_text(newline, newline + 1); + break; + } + case 't': { + const Char tab[] = {'\t'}; + handler.on_text(tab, tab + 1); + break; + } + // Year: + case 'Y': handler.on_year(numeric_system::standard, pad); break; + case 'y': handler.on_short_year(numeric_system::standard); break; + case 'C': handler.on_century(numeric_system::standard); break; + case 'G': handler.on_iso_week_based_year(); break; + case 'g': handler.on_iso_week_based_short_year(); break; + // Day of the week: + case 'a': handler.on_abbr_weekday(); break; + case 'A': handler.on_full_weekday(); break; + case 'w': handler.on_dec0_weekday(numeric_system::standard); break; + case 'u': handler.on_dec1_weekday(numeric_system::standard); break; + // Month: + case 'b': + case 'h': handler.on_abbr_month(); break; + case 'B': handler.on_full_month(); break; + case 'm': handler.on_dec_month(numeric_system::standard, pad); break; + // Day of the year/month: + case 'U': + handler.on_dec0_week_of_year(numeric_system::standard, pad); + break; + case 'W': + handler.on_dec1_week_of_year(numeric_system::standard, pad); + break; + case 'V': handler.on_iso_week_of_year(numeric_system::standard, pad); break; + case 'j': handler.on_day_of_year(pad); break; + case 'd': handler.on_day_of_month(numeric_system::standard, pad); break; + case 'e': + handler.on_day_of_month(numeric_system::standard, pad_type::space); + break; + // Hour, minute, second: + case 'H': handler.on_24_hour(numeric_system::standard, pad); break; + case 'I': handler.on_12_hour(numeric_system::standard, pad); break; + case 'M': handler.on_minute(numeric_system::standard, pad); break; + case 'S': handler.on_second(numeric_system::standard, pad); break; + // Other: + case 'c': handler.on_datetime(numeric_system::standard); break; + case 'x': handler.on_loc_date(numeric_system::standard); break; + case 'X': handler.on_loc_time(numeric_system::standard); break; + case 'D': handler.on_us_date(); break; + case 'F': handler.on_iso_date(); break; + case 'r': handler.on_12_hour_time(); break; + case 'R': handler.on_24_hour_time(); break; + case 'T': handler.on_iso_time(); break; + case 'p': handler.on_am_pm(); break; + case 'Q': handler.on_duration_value(); break; + case 'q': handler.on_duration_unit(); break; + case 'z': handler.on_utc_offset(numeric_system::standard); break; + case 'Z': handler.on_tz_name(); break; + // Alternative representation: + case 'E': { + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case 'Y': handler.on_year(numeric_system::alternative, pad); break; + case 'y': handler.on_offset_year(); break; + case 'C': handler.on_century(numeric_system::alternative); break; + case 'c': handler.on_datetime(numeric_system::alternative); break; + case 'x': handler.on_loc_date(numeric_system::alternative); break; + case 'X': handler.on_loc_time(numeric_system::alternative); break; + case 'z': handler.on_utc_offset(numeric_system::alternative); break; + default: FMT_THROW(format_error("invalid format")); + } + break; + } + case 'O': + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case 'y': handler.on_short_year(numeric_system::alternative); break; + case 'm': handler.on_dec_month(numeric_system::alternative, pad); break; + case 'U': + handler.on_dec0_week_of_year(numeric_system::alternative, pad); + break; + case 'W': + handler.on_dec1_week_of_year(numeric_system::alternative, pad); + break; + case 'V': + handler.on_iso_week_of_year(numeric_system::alternative, pad); + break; + case 'd': + handler.on_day_of_month(numeric_system::alternative, pad); + break; + case 'e': + handler.on_day_of_month(numeric_system::alternative, pad_type::space); + break; + case 'w': handler.on_dec0_weekday(numeric_system::alternative); break; + case 'u': handler.on_dec1_weekday(numeric_system::alternative); break; + case 'H': handler.on_24_hour(numeric_system::alternative, pad); break; + case 'I': handler.on_12_hour(numeric_system::alternative, pad); break; + case 'M': handler.on_minute(numeric_system::alternative, pad); break; + case 'S': handler.on_second(numeric_system::alternative, pad); break; + case 'z': handler.on_utc_offset(numeric_system::alternative); break; + default: FMT_THROW(format_error("invalid format")); + } + break; + default: FMT_THROW(format_error("invalid format")); + } + begin = ptr; + } + if (begin != ptr) handler.on_text(begin, ptr); + return ptr; +} + +template struct null_chrono_spec_handler { + FMT_CONSTEXPR void unsupported() { + static_cast(this)->unsupported(); + } + FMT_CONSTEXPR void on_year(numeric_system, pad_type) { unsupported(); } + FMT_CONSTEXPR void on_short_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_offset_year() { unsupported(); } + FMT_CONSTEXPR void on_century(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_iso_week_based_year() { unsupported(); } + FMT_CONSTEXPR void on_iso_week_based_short_year() { unsupported(); } + FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); } + FMT_CONSTEXPR void on_full_weekday() { unsupported(); } + FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_abbr_month() { unsupported(); } + FMT_CONSTEXPR void on_full_month() { unsupported(); } + FMT_CONSTEXPR void on_dec_month(numeric_system, pad_type) { unsupported(); } + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_day_of_year(pad_type) { unsupported(); } + FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_us_date() { unsupported(); } + FMT_CONSTEXPR void on_iso_date() { unsupported(); } + FMT_CONSTEXPR void on_12_hour_time() { unsupported(); } + FMT_CONSTEXPR void on_24_hour_time() { unsupported(); } + FMT_CONSTEXPR void on_iso_time() { unsupported(); } + FMT_CONSTEXPR void on_am_pm() { unsupported(); } + FMT_CONSTEXPR void on_duration_value() { unsupported(); } + FMT_CONSTEXPR void on_duration_unit() { unsupported(); } + FMT_CONSTEXPR void on_utc_offset(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_tz_name() { unsupported(); } +}; + +struct tm_format_checker : null_chrono_spec_handler { + FMT_NORETURN inline void unsupported() { + FMT_THROW(format_error("no format")); + } + + template + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_CONSTEXPR void on_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_short_year(numeric_system) {} + FMT_CONSTEXPR void on_offset_year() {} + FMT_CONSTEXPR void on_century(numeric_system) {} + FMT_CONSTEXPR void on_iso_week_based_year() {} + FMT_CONSTEXPR void on_iso_week_based_short_year() {} + FMT_CONSTEXPR void on_abbr_weekday() {} + FMT_CONSTEXPR void on_full_weekday() {} + FMT_CONSTEXPR void on_dec0_weekday(numeric_system) {} + FMT_CONSTEXPR void on_dec1_weekday(numeric_system) {} + FMT_CONSTEXPR void on_abbr_month() {} + FMT_CONSTEXPR void on_full_month() {} + FMT_CONSTEXPR void on_dec_month(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_day_of_year(pad_type) {} + FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_datetime(numeric_system) {} + FMT_CONSTEXPR void on_loc_date(numeric_system) {} + FMT_CONSTEXPR void on_loc_time(numeric_system) {} + FMT_CONSTEXPR void on_us_date() {} + FMT_CONSTEXPR void on_iso_date() {} + FMT_CONSTEXPR void on_12_hour_time() {} + FMT_CONSTEXPR void on_24_hour_time() {} + FMT_CONSTEXPR void on_iso_time() {} + FMT_CONSTEXPR void on_am_pm() {} + FMT_CONSTEXPR void on_utc_offset(numeric_system) {} + FMT_CONSTEXPR void on_tz_name() {} +}; + +inline auto tm_wday_full_name(int wday) -> const char* { + static constexpr const char* full_name_list[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"}; + return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?"; +} +inline auto tm_wday_short_name(int wday) -> const char* { + static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat"}; + return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???"; +} + +inline auto tm_mon_full_name(int mon) -> const char* { + static constexpr const char* full_name_list[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"}; + return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?"; +} +inline auto tm_mon_short_name(int mon) -> const char* { + static constexpr const char* short_name_list[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + return mon >= 0 && mon <= 11 ? short_name_list[mon] : "???"; +} + +template +struct has_member_data_tm_gmtoff : std::false_type {}; +template +struct has_member_data_tm_gmtoff> + : std::true_type {}; + +template +struct has_member_data_tm_zone : std::false_type {}; +template +struct has_member_data_tm_zone> + : std::true_type {}; + +inline void tzset_once() { + static bool init = []() { + using namespace fmt_detail; + _tzset(); + return false; + }(); + ignore_unused(init); +} + +// Converts value to Int and checks that it's in the range [0, upper). +template ::value)> +inline auto to_nonnegative_int(T value, Int upper) -> Int { + if (!std::is_unsigned::value && + (value < 0 || to_unsigned(value) > to_unsigned(upper))) { + FMT_THROW(fmt::format_error("chrono value is out of range")); + } + return static_cast(value); +} +template ::value)> +inline auto to_nonnegative_int(T value, Int upper) -> Int { + auto int_value = static_cast(value); + if (int_value < 0 || value > static_cast(upper)) + FMT_THROW(format_error("invalid value")); + return int_value; +} + +constexpr auto pow10(std::uint32_t n) -> long long { + return n == 0 ? 1 : 10 * pow10(n - 1); +} + +// Counts the number of fractional digits in the range [0, 18] according to the +// C++20 spec. If more than 18 fractional digits are required then returns 6 for +// microseconds precision. +template () / 10)> +struct count_fractional_digits { + static constexpr int value = + Num % Den == 0 ? N : count_fractional_digits::value; +}; + +// Base case that doesn't instantiate any more templates +// in order to avoid overflow. +template +struct count_fractional_digits { + static constexpr int value = (Num % Den == 0) ? N : 6; +}; + +// Format subseconds which are given as an integer type with an appropriate +// number of digits. +template +void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { + constexpr auto num_fractional_digits = + count_fractional_digits::value; + + using subsecond_precision = std::chrono::duration< + typename std::common_type::type, + std::ratio<1, pow10(num_fractional_digits)>>; + + const auto fractional = d - detail::duration_cast(d); + const auto subseconds = + std::chrono::treat_as_floating_point< + typename subsecond_precision::rep>::value + ? fractional.count() + : detail::duration_cast(fractional).count(); + auto n = static_cast>(subseconds); + const int num_digits = count_digits(n); + + int leading_zeroes = (std::max)(0, num_fractional_digits - num_digits); + if (precision < 0) { + FMT_ASSERT(!std::is_floating_point::value, ""); + if (std::ratio_less::value) { + *out++ = '.'; + out = detail::fill_n(out, leading_zeroes, '0'); + out = format_decimal(out, n, num_digits); + } + } else if (precision > 0) { + *out++ = '.'; + leading_zeroes = min_of(leading_zeroes, precision); + int remaining = precision - leading_zeroes; + out = detail::fill_n(out, leading_zeroes, '0'); + if (remaining < num_digits) { + int num_truncated_digits = num_digits - remaining; + n /= to_unsigned(pow10(to_unsigned(num_truncated_digits))); + if (n != 0) out = format_decimal(out, n, remaining); + return; + } + if (n != 0) { + out = format_decimal(out, n, num_digits); + remaining -= num_digits; + } + out = detail::fill_n(out, remaining, '0'); + } +} + +// Format subseconds which are given as a floating point type with an +// appropriate number of digits. We cannot pass the Duration here, as we +// explicitly need to pass the Rep value in the chrono_formatter. +template +void write_floating_seconds(memory_buffer& buf, Duration duration, + int num_fractional_digits = -1) { + using rep = typename Duration::rep; + FMT_ASSERT(std::is_floating_point::value, ""); + + auto val = duration.count(); + + if (num_fractional_digits < 0) { + // For `std::round` with fallback to `round`: + // On some toolchains `std::round` is not available (e.g. GCC 6). + using namespace std; + num_fractional_digits = + count_fractional_digits::value; + if (num_fractional_digits < 6 && static_cast(round(val)) != val) + num_fractional_digits = 6; + } + + fmt::format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"), + std::fmod(val * static_cast(Duration::period::num) / + static_cast(Duration::period::den), + static_cast(60)), + num_fractional_digits); +} + +template +class tm_writer { + private: + static constexpr int days_per_week = 7; + + const std::locale& loc_; + const bool is_classic_; + OutputIt out_; + const Duration* subsecs_; + const std::tm& tm_; + + auto tm_sec() const noexcept -> int { + FMT_ASSERT(tm_.tm_sec >= 0 && tm_.tm_sec <= 61, ""); + return tm_.tm_sec; + } + auto tm_min() const noexcept -> int { + FMT_ASSERT(tm_.tm_min >= 0 && tm_.tm_min <= 59, ""); + return tm_.tm_min; + } + auto tm_hour() const noexcept -> int { + FMT_ASSERT(tm_.tm_hour >= 0 && tm_.tm_hour <= 23, ""); + return tm_.tm_hour; + } + auto tm_mday() const noexcept -> int { + FMT_ASSERT(tm_.tm_mday >= 1 && tm_.tm_mday <= 31, ""); + return tm_.tm_mday; + } + auto tm_mon() const noexcept -> int { + FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon <= 11, ""); + return tm_.tm_mon; + } + auto tm_year() const noexcept -> long long { return 1900ll + tm_.tm_year; } + auto tm_wday() const noexcept -> int { + FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday <= 6, ""); + return tm_.tm_wday; + } + auto tm_yday() const noexcept -> int { + FMT_ASSERT(tm_.tm_yday >= 0 && tm_.tm_yday <= 365, ""); + return tm_.tm_yday; + } + + auto tm_hour12() const noexcept -> int { + const auto h = tm_hour(); + const auto z = h < 12 ? h : h - 12; + return z == 0 ? 12 : z; + } + + // POSIX and the C Standard are unclear or inconsistent about what %C and %y + // do if the year is negative or exceeds 9999. Use the convention that %C + // concatenated with %y yields the same output as %Y, and that %Y contains at + // least 4 characters, with more only if necessary. + auto split_year_lower(long long year) const noexcept -> int { + auto l = year % 100; + if (l < 0) l = -l; // l in [0, 99] + return static_cast(l); + } + + // Algorithm: https://en.wikipedia.org/wiki/ISO_week_date. + auto iso_year_weeks(long long curr_year) const noexcept -> int { + const auto prev_year = curr_year - 1; + const auto curr_p = + (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % + days_per_week; + const auto prev_p = + (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) % + days_per_week; + return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0); + } + auto iso_week_num(int tm_yday, int tm_wday) const noexcept -> int { + return (tm_yday + 11 - (tm_wday == 0 ? days_per_week : tm_wday)) / + days_per_week; + } + auto tm_iso_week_year() const noexcept -> long long { + const auto year = tm_year(); + const auto w = iso_week_num(tm_yday(), tm_wday()); + if (w < 1) return year - 1; + if (w > iso_year_weeks(year)) return year + 1; + return year; + } + auto tm_iso_week_of_year() const noexcept -> int { + const auto year = tm_year(); + const auto w = iso_week_num(tm_yday(), tm_wday()); + if (w < 1) return iso_year_weeks(year - 1); + if (w > iso_year_weeks(year)) return 1; + return w; + } + + void write1(int value) { + *out_++ = static_cast('0' + to_unsigned(value) % 10); + } + void write2(int value) { + const char* d = digits2(to_unsigned(value) % 100); + *out_++ = *d++; + *out_++ = *d; + } + void write2(int value, pad_type pad) { + unsigned int v = to_unsigned(value) % 100; + if (v >= 10) { + const char* d = digits2(v); + *out_++ = *d++; + *out_++ = *d; + } else { + out_ = detail::write_padding(out_, pad); + *out_++ = static_cast('0' + v); + } + } + + void write_year_extended(long long year, pad_type pad) { + // At least 4 characters. + int width = 4; + bool negative = year < 0; + if (negative) { + year = 0 - year; + --width; + } + uint32_or_64_or_128_t n = to_unsigned(year); + const int num_digits = count_digits(n); + if (negative && pad == pad_type::zero) *out_++ = '-'; + if (width > num_digits) { + out_ = detail::write_padding(out_, pad, width - num_digits); + } + if (negative && pad != pad_type::zero) *out_++ = '-'; + out_ = format_decimal(out_, n, num_digits); + } + void write_year(long long year, pad_type pad) { + write_year_extended(year, pad); + } + + void write_utc_offset(long long offset, numeric_system ns) { + if (offset < 0) { + *out_++ = '-'; + offset = -offset; + } else { + *out_++ = '+'; + } + offset /= 60; + write2(static_cast(offset / 60)); + if (ns != numeric_system::standard) *out_++ = ':'; + write2(static_cast(offset % 60)); + } + + template ::value)> + void format_utc_offset_impl(const T& tm, numeric_system ns) { + write_utc_offset(tm.tm_gmtoff, ns); + } + template ::value)> + void format_utc_offset_impl(const T& tm, numeric_system ns) { +#if defined(_WIN32) && defined(_UCRT) + tzset_once(); + long offset = 0; + _get_timezone(&offset); + if (tm.tm_isdst) { + long dstbias = 0; + _get_dstbias(&dstbias); + offset += dstbias; + } + write_utc_offset(-offset, ns); +#else + if (ns == numeric_system::standard) return format_localized('z'); + + // Extract timezone offset from timezone conversion functions. + std::tm gtm = tm; + std::time_t gt = std::mktime(>m); + std::tm ltm = gmtime(gt); + std::time_t lt = std::mktime(<m); + long long offset = gt - lt; + write_utc_offset(offset, ns); +#endif + } + + template ::value)> + void format_tz_name_impl(const T& tm) { + if (is_classic_) + out_ = write_tm_str(out_, tm.tm_zone, loc_); + else + format_localized('Z'); + } + template ::value)> + void format_tz_name_impl(const T&) { + format_localized('Z'); + } + + void format_localized(char format, char modifier = 0) { + out_ = write(out_, tm_, loc_, format, modifier); + } + + public: + tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm, + const Duration* subsecs = nullptr) + : loc_(loc), + is_classic_(loc_ == get_classic_locale()), + out_(out), + subsecs_(subsecs), + tm_(tm) {} + + auto out() const -> OutputIt { return out_; } + + FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { + out_ = copy(begin, end, out_); + } + + void on_abbr_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_short_name(tm_wday())); + else + format_localized('a'); + } + void on_full_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_full_name(tm_wday())); + else + format_localized('A'); + } + void on_dec0_weekday(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write1(tm_wday()); + format_localized('w', 'O'); + } + void on_dec1_weekday(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto wday = tm_wday(); + write1(wday == 0 ? days_per_week : wday); + } else { + format_localized('u', 'O'); + } + } + + void on_abbr_month() { + if (is_classic_) + out_ = write(out_, tm_mon_short_name(tm_mon())); + else + format_localized('b'); + } + void on_full_month() { + if (is_classic_) + out_ = write(out_, tm_mon_full_name(tm_mon())); + else + format_localized('B'); + } + + void on_datetime(numeric_system ns) { + if (is_classic_) { + on_abbr_weekday(); + *out_++ = ' '; + on_abbr_month(); + *out_++ = ' '; + on_day_of_month(numeric_system::standard, pad_type::space); + *out_++ = ' '; + on_iso_time(); + *out_++ = ' '; + on_year(numeric_system::standard, pad_type::space); + } else { + format_localized('c', ns == numeric_system::standard ? '\0' : 'E'); + } + } + void on_loc_date(numeric_system ns) { + if (is_classic_) + on_us_date(); + else + format_localized('x', ns == numeric_system::standard ? '\0' : 'E'); + } + void on_loc_time(numeric_system ns) { + if (is_classic_) + on_iso_time(); + else + format_localized('X', ns == numeric_system::standard ? '\0' : 'E'); + } + void on_us_date() { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_mon() + 1), + to_unsigned(tm_mday()), + to_unsigned(split_year_lower(tm_year())), '/'); + out_ = copy(std::begin(buf), std::end(buf), out_); + } + void on_iso_date() { + auto year = tm_year(); + char buf[10]; + size_t offset = 0; + if (year >= 0 && year < 10000) { + write2digits(buf, static_cast(year / 100)); + } else { + offset = 4; + write_year_extended(year, pad_type::zero); + year = 0; + } + write_digit2_separated(buf + 2, static_cast(year % 100), + to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()), + '-'); + out_ = copy(std::begin(buf) + offset, std::end(buf), out_); + } + + void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); } + void on_tz_name() { format_tz_name_impl(tm_); } + + void on_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write_year(tm_year(), pad); + format_localized('Y', 'E'); + } + void on_short_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2(split_year_lower(tm_year())); + format_localized('y', 'O'); + } + void on_offset_year() { + if (is_classic_) return write2(split_year_lower(tm_year())); + format_localized('y', 'E'); + } + + void on_century(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto year = tm_year(); + auto upper = year / 100; + if (year >= -99 && year < 0) { + // Zero upper on negative year. + *out_++ = '-'; + *out_++ = '0'; + } else if (upper >= 0 && upper < 100) { + write2(static_cast(upper)); + } else { + out_ = write(out_, upper); + } + } else { + format_localized('C', 'E'); + } + } + + void on_dec_month(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_mon() + 1, pad); + format_localized('m', 'O'); + } + + void on_dec0_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week, + pad); + format_localized('U', 'O'); + } + void on_dec1_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) { + auto wday = tm_wday(); + write2((tm_yday() + days_per_week - + (wday == 0 ? (days_per_week - 1) : (wday - 1))) / + days_per_week, + pad); + } else { + format_localized('W', 'O'); + } + } + void on_iso_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_iso_week_of_year(), pad); + format_localized('V', 'O'); + } + + void on_iso_week_based_year() { + write_year(tm_iso_week_year(), pad_type::zero); + } + void on_iso_week_based_short_year() { + write2(split_year_lower(tm_iso_week_year())); + } + + void on_day_of_year(pad_type pad) { + auto yday = tm_yday() + 1; + auto digit1 = yday / 100; + if (digit1 != 0) { + write1(digit1); + } else { + out_ = detail::write_padding(out_, pad); + } + write2(yday % 100, pad); + } + + void on_day_of_month(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_mday(), pad); + format_localized('d', 'O'); + } + + void on_24_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour(), pad); + format_localized('H', 'O'); + } + void on_12_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour12(), pad); + format_localized('I', 'O'); + } + void on_minute(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_min(), pad); + format_localized('M', 'O'); + } + + void on_second(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) { + write2(tm_sec(), pad); + if (subsecs_) { + if (std::is_floating_point::value) { + auto buf = memory_buffer(); + write_floating_seconds(buf, *subsecs_); + if (buf.size() > 1) { + // Remove the leading "0", write something like ".123". + out_ = copy(buf.begin() + 1, buf.end(), out_); + } + } else { + write_fractional_seconds(out_, *subsecs_); + } + } + } else { + // Currently no formatting of subseconds when a locale is set. + format_localized('S', 'O'); + } + } + + void on_12_hour_time() { + if (is_classic_) { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_hour12()), + to_unsigned(tm_min()), to_unsigned(tm_sec()), ':'); + out_ = copy(std::begin(buf), std::end(buf), out_); + *out_++ = ' '; + on_am_pm(); + } else { + format_localized('r'); + } + } + void on_24_hour_time() { + write2(tm_hour()); + *out_++ = ':'; + write2(tm_min()); + } + void on_iso_time() { + on_24_hour_time(); + *out_++ = ':'; + on_second(numeric_system::standard, pad_type::zero); + } + + void on_am_pm() { + if (is_classic_) { + *out_++ = tm_hour() < 12 ? 'A' : 'P'; + *out_++ = 'M'; + } else { + format_localized('p'); + } + } + + // These apply to chrono durations but not tm. + void on_duration_value() {} + void on_duration_unit() {} +}; + +struct chrono_format_checker : null_chrono_spec_handler { + bool has_precision_integral = false; + + FMT_NORETURN inline void unsupported() { FMT_THROW(format_error("no date")); } + + template + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_CONSTEXPR void on_day_of_year(pad_type) {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour_time() {} + FMT_CONSTEXPR void on_24_hour_time() {} + FMT_CONSTEXPR void on_iso_time() {} + FMT_CONSTEXPR void on_am_pm() {} + FMT_CONSTEXPR void on_duration_value() const { + if (has_precision_integral) + FMT_THROW(format_error("precision not allowed for this argument type")); + } + FMT_CONSTEXPR void on_duration_unit() {} +}; + +template ::value&& has_isfinite::value)> +inline auto isfinite(T) -> bool { + return true; +} + +template ::value)> +inline auto mod(T x, int y) -> T { + return x % static_cast(y); +} +template ::value)> +inline auto mod(T x, int y) -> T { + return std::fmod(x, static_cast(y)); +} + +// If T is an integral type, maps T to its unsigned counterpart, otherwise +// leaves it unchanged (unlike std::make_unsigned). +template ::value> +struct make_unsigned_or_unchanged { + using type = T; +}; + +template struct make_unsigned_or_unchanged { + using type = typename std::make_unsigned::type; +}; + +template ::value)> +inline auto get_milliseconds(std::chrono::duration d) + -> std::chrono::duration { + // this may overflow and/or the result may not fit in the + // target type. +#if FMT_SAFE_DURATION_CAST + using CommonSecondsType = + typename std::common_type::type; + const auto d_as_common = detail::duration_cast(d); + const auto d_as_whole_seconds = + detail::duration_cast(d_as_common); + // this conversion should be nonproblematic + const auto diff = d_as_common - d_as_whole_seconds; + const auto ms = + detail::duration_cast>(diff); + return ms; +#else + auto s = detail::duration_cast(d); + return detail::duration_cast(d - s); +#endif +} + +template ::value)> +auto format_duration_value(OutputIt out, Rep val, int) -> OutputIt { + return write(out, val); +} + +template ::value)> +auto format_duration_value(OutputIt out, Rep val, int precision) -> OutputIt { + auto specs = format_specs(); + specs.precision = precision; + specs.set_type(precision >= 0 ? presentation_type::fixed + : presentation_type::general); + return write(out, val, specs); +} + +template +auto copy_unit(string_view unit, OutputIt out, Char) -> OutputIt { + return copy(unit.begin(), unit.end(), out); +} + +template +auto copy_unit(string_view unit, OutputIt out, wchar_t) -> OutputIt { + // This works when wchar_t is UTF-32 because units only contain characters + // that have the same representation in UTF-16 and UTF-32. + utf8_to_utf16 u(unit); + return copy(u.c_str(), u.c_str() + u.size(), out); +} + +template +auto format_duration_unit(OutputIt out) -> OutputIt { + if (const char* unit = get_units()) + return copy_unit(string_view(unit), out, Char()); + *out++ = '['; + out = write(out, Period::num); + if (const_check(Period::den != 1)) { + *out++ = '/'; + out = write(out, Period::den); + } + *out++ = ']'; + *out++ = 's'; + return out; +} + +class get_locale { + private: + union { + std::locale locale_; + }; + bool has_locale_ = false; + + public: + inline get_locale(bool localized, locale_ref loc) : has_locale_(localized) { + if (localized) + ::new (&locale_) std::locale(loc.template get()); + } + inline ~get_locale() { + if (has_locale_) locale_.~locale(); + } + inline operator const std::locale&() const { + return has_locale_ ? locale_ : get_classic_locale(); + } +}; + +template +struct chrono_formatter { + FormatContext& context; + OutputIt out; + int precision; + bool localized = false; + // rep is unsigned to avoid overflow. + using rep = + conditional_t::value && sizeof(Rep) < sizeof(int), + unsigned, typename make_unsigned_or_unchanged::type>; + rep val; + using seconds = std::chrono::duration; + seconds s; + using milliseconds = std::chrono::duration; + bool negative; + + using char_type = typename FormatContext::char_type; + using tm_writer_type = tm_writer; + + chrono_formatter(FormatContext& ctx, OutputIt o, + std::chrono::duration d) + : context(ctx), + out(o), + val(static_cast(d.count())), + negative(false) { + if (d.count() < 0) { + val = 0 - val; + negative = true; + } + + // this may overflow and/or the result may not fit in the + // target type. + // might need checked conversion (rep!=Rep) + s = detail::duration_cast(std::chrono::duration(val)); + } + + // returns true if nan or inf, writes to out. + auto handle_nan_inf() -> bool { + if (isfinite(val)) { + return false; + } + if (isnan(val)) { + write_nan(); + return true; + } + // must be +-inf + if (val > 0) { + write_pinf(); + } else { + write_ninf(); + } + return true; + } + + auto days() const -> Rep { return static_cast(s.count() / 86400); } + auto hour() const -> Rep { + return static_cast(mod((s.count() / 3600), 24)); + } + + auto hour12() const -> Rep { + Rep hour = static_cast(mod((s.count() / 3600), 12)); + return hour <= 0 ? 12 : hour; + } + + auto minute() const -> Rep { + return static_cast(mod((s.count() / 60), 60)); + } + auto second() const -> Rep { return static_cast(mod(s.count(), 60)); } + + auto time() const -> std::tm { + auto time = std::tm(); + time.tm_hour = to_nonnegative_int(hour(), 24); + time.tm_min = to_nonnegative_int(minute(), 60); + time.tm_sec = to_nonnegative_int(second(), 60); + return time; + } + + void write_sign() { + if (negative) { + *out++ = '-'; + negative = false; + } + } + + void write(Rep value, int width, pad_type pad = pad_type::zero) { + write_sign(); + if (isnan(value)) return write_nan(); + uint32_or_64_or_128_t n = + to_unsigned(to_nonnegative_int(value, max_value())); + int num_digits = detail::count_digits(n); + if (width > num_digits) { + out = detail::write_padding(out, pad, width - num_digits); + } + out = format_decimal(out, n, num_digits); + } + + void write_nan() { std::copy_n("nan", 3, out); } + void write_pinf() { std::copy_n("inf", 3, out); } + void write_ninf() { std::copy_n("-inf", 4, out); } + + template + void format_tm(const tm& time, Callback cb, Args... args) { + if (isnan(val)) return write_nan(); + get_locale loc(localized, context.locale()); + auto w = tm_writer_type(loc, out, time); + (w.*cb)(args...); + out = w.out(); + } + + void on_text(const char_type* begin, const char_type* end) { + copy(begin, end, out); + } + + // These are not implemented because durations don't have date information. + void on_abbr_weekday() {} + void on_full_weekday() {} + void on_dec0_weekday(numeric_system) {} + void on_dec1_weekday(numeric_system) {} + void on_abbr_month() {} + void on_full_month() {} + void on_datetime(numeric_system) {} + void on_loc_date(numeric_system) {} + void on_loc_time(numeric_system) {} + void on_us_date() {} + void on_iso_date() {} + void on_utc_offset(numeric_system) {} + void on_tz_name() {} + void on_year(numeric_system, pad_type) {} + void on_short_year(numeric_system) {} + void on_offset_year() {} + void on_century(numeric_system) {} + void on_iso_week_based_year() {} + void on_iso_week_based_short_year() {} + void on_dec_month(numeric_system, pad_type) {} + void on_dec0_week_of_year(numeric_system, pad_type) {} + void on_dec1_week_of_year(numeric_system, pad_type) {} + void on_iso_week_of_year(numeric_system, pad_type) {} + void on_day_of_month(numeric_system, pad_type) {} + + void on_day_of_year(pad_type) { + if (handle_nan_inf()) return; + write(days(), 0); + } + + void on_24_hour(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(hour(), 2, pad); + auto time = tm(); + time.tm_hour = to_nonnegative_int(hour(), 24); + format_tm(time, &tm_writer_type::on_24_hour, ns, pad); + } + + void on_12_hour(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(hour12(), 2, pad); + auto time = tm(); + time.tm_hour = to_nonnegative_int(hour12(), 12); + format_tm(time, &tm_writer_type::on_12_hour, ns, pad); + } + + void on_minute(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(minute(), 2, pad); + auto time = tm(); + time.tm_min = to_nonnegative_int(minute(), 60); + format_tm(time, &tm_writer_type::on_minute, ns, pad); + } + + void on_second(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) { + if (std::is_floating_point::value) { + auto buf = memory_buffer(); + write_floating_seconds(buf, std::chrono::duration(val), + precision); + if (negative) *out++ = '-'; + if (buf.size() < 2 || buf[1] == '.') { + out = detail::write_padding(out, pad); + } + out = copy(buf.begin(), buf.end(), out); + } else { + write(second(), 2, pad); + write_fractional_seconds( + out, std::chrono::duration(val), precision); + } + return; + } + auto time = tm(); + time.tm_sec = to_nonnegative_int(second(), 60); + format_tm(time, &tm_writer_type::on_second, ns, pad); + } + + void on_12_hour_time() { + if (handle_nan_inf()) return; + format_tm(time(), &tm_writer_type::on_12_hour_time); + } + + void on_24_hour_time() { + if (handle_nan_inf()) { + *out++ = ':'; + handle_nan_inf(); + return; + } + + write(hour(), 2); + *out++ = ':'; + write(minute(), 2); + } + + void on_iso_time() { + on_24_hour_time(); + *out++ = ':'; + if (handle_nan_inf()) return; + on_second(numeric_system::standard, pad_type::zero); + } + + void on_am_pm() { + if (handle_nan_inf()) return; + format_tm(time(), &tm_writer_type::on_am_pm); + } + + void on_duration_value() { + if (handle_nan_inf()) return; + write_sign(); + out = format_duration_value(out, val, precision); + } + + void on_duration_unit() { + out = format_duration_unit(out); + } +}; + +} // namespace detail + +#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907 +using weekday = std::chrono::weekday; +using day = std::chrono::day; +using month = std::chrono::month; +using year = std::chrono::year; +using year_month_day = std::chrono::year_month_day; +#else +// A fallback version of weekday. +class weekday { + private: + unsigned char value_; + + public: + weekday() = default; + constexpr explicit weekday(unsigned wd) noexcept + : value_(static_cast(wd != 7 ? wd : 0)) {} + constexpr auto c_encoding() const noexcept -> unsigned { return value_; } +}; + +class day { + private: + unsigned char value_; + + public: + day() = default; + constexpr explicit day(unsigned d) noexcept + : value_(static_cast(d)) {} + constexpr explicit operator unsigned() const noexcept { return value_; } +}; + +class month { + private: + unsigned char value_; + + public: + month() = default; + constexpr explicit month(unsigned m) noexcept + : value_(static_cast(m)) {} + constexpr explicit operator unsigned() const noexcept { return value_; } +}; + +class year { + private: + int value_; + + public: + year() = default; + constexpr explicit year(int y) noexcept : value_(y) {} + constexpr explicit operator int() const noexcept { return value_; } +}; + +class year_month_day { + private: + fmt::year year_; + fmt::month month_; + fmt::day day_; + + public: + year_month_day() = default; + constexpr year_month_day(const year& y, const month& m, const day& d) noexcept + : year_(y), month_(m), day_(d) {} + constexpr auto year() const noexcept -> fmt::year { return year_; } + constexpr auto month() const noexcept -> fmt::month { return month_; } + constexpr auto day() const noexcept -> fmt::day { return day_; } +}; +#endif + +template +struct formatter : private formatter { + private: + bool localized_ = false; + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'L') { + ++it; + localized_ = true; + return it; + } + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_wday = static_cast(wd.c_encoding()); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(localized_, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_abbr_weekday(); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(day d, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_mday = static_cast(static_cast(d)); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(false, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_day_of_month(detail::numeric_system::standard, detail::pad_type::zero); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool localized_ = false; + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'L') { + ++it; + localized_ = true; + return it; + } + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(month m, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_mon = static_cast(static_cast(m)) - 1; + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(localized_, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_abbr_month(); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(year y, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_year = static_cast(y) - 1900; + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(false, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_year(detail::numeric_system::standard, detail::pad_type::zero); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(year_month_day val, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_year = static_cast(val.year()) - 1900; + time.tm_mon = static_cast(static_cast(val.month())) - 1; + time.tm_mday = static_cast(static_cast(val.day())); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(true, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_iso_date(); + return w.out(); + } +}; + +template +struct formatter, Char> { + private: + format_specs specs_; + detail::arg_ref width_ref_; + detail::arg_ref precision_ref_; + bool localized_ = false; + basic_string_view fmt_; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *it == '}') return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + Char c = *it; + if ((c >= '0' && c <= '9') || c == '{') { + it = detail::parse_width(it, end, specs_, width_ref_, ctx); + if (it == end) return it; + } + + auto checker = detail::chrono_format_checker(); + if (*it == '.') { + checker.has_precision_integral = !std::is_floating_point::value; + it = detail::parse_precision(it, end, specs_, precision_ref_, ctx); + } + if (it != end && *it == 'L') { + localized_ = true; + ++it; + } + end = detail::parse_chrono_format(it, end, checker); + fmt_ = {it, detail::to_unsigned(end - it)}; + return end; + } + + template + auto format(std::chrono::duration d, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto specs = specs_; + auto precision = specs.precision; + specs.precision = -1; + auto begin = fmt_.begin(), end = fmt_.end(); + // As a possible future optimization, we could avoid extra copying if width + // is not specified. + auto buf = basic_memory_buffer(); + auto out = basic_appender(buf); + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), precision, + precision_ref_, ctx); + if (begin == end || *begin == '}') { + out = detail::format_duration_value(out, d.count(), precision); + detail::format_duration_unit(out); + } else { + using chrono_formatter = + detail::chrono_formatter; + auto f = chrono_formatter(ctx, out, d); + f.precision = precision; + f.localized = localized_; + detail::parse_chrono_format(begin, end, f); + } + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } +}; + +template struct formatter { + private: + format_specs specs_; + detail::arg_ref width_ref_; + + protected: + basic_string_view fmt_; + + template + auto do_format(const std::tm& tm, FormatContext& ctx, + const Duration* subsecs) const -> decltype(ctx.out()) { + auto specs = specs_; + auto buf = basic_memory_buffer(); + auto out = basic_appender(buf); + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + + auto loc_ref = ctx.locale(); + detail::get_locale loc(static_cast(loc_ref), loc_ref); + auto w = + detail::tm_writer(loc, out, tm, subsecs); + detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w); + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *it == '}') return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + Char c = *it; + if ((c >= '0' && c <= '9') || c == '{') { + it = detail::parse_width(it, end, specs_, width_ref_, ctx); + if (it == end) return it; + } + + end = detail::parse_chrono_format(it, end, detail::tm_format_checker()); + // Replace the default format string only if the new spec is not empty. + if (end != it) fmt_ = {it, detail::to_unsigned(end - it)}; + return end; + } + + template + auto format(const std::tm& tm, FormatContext& ctx) const + -> decltype(ctx.out()) { + return do_format(tm, ctx, nullptr); + } +}; + +template +struct formatter, Char> : formatter { + FMT_CONSTEXPR formatter() { + this->fmt_ = detail::string_literal(); + } + + template + auto format(sys_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + std::tm tm = gmtime(val); + using period = typename Duration::period; + if (detail::const_check( + period::num == 1 && period::den == 1 && + !std::is_floating_point::value)) { + return formatter::format(tm, ctx); + } + Duration epoch = val.time_since_epoch(); + Duration subsecs = detail::duration_cast( + epoch - detail::duration_cast(epoch)); + if (subsecs.count() < 0) { + auto second = detail::duration_cast(std::chrono::seconds(1)); + if (tm.tm_sec != 0) + --tm.tm_sec; + else + tm = gmtime(val - second); + subsecs += detail::duration_cast(std::chrono::seconds(1)); + } + return formatter::do_format(tm, ctx, &subsecs); + } +}; + +template +struct formatter, Char> + : formatter, Char> { + template + auto format(utc_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter, Char>::format( + detail::utc_clock::to_sys(val), ctx); + } +}; + +template +struct formatter, Char> : formatter { + FMT_CONSTEXPR formatter() { + this->fmt_ = detail::string_literal(); + } + + template + auto format(local_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + using period = typename Duration::period; + if (period::num == 1 && period::den == 1 && + !std::is_floating_point::value) { + return formatter::format(localtime(val), ctx); + } + auto epoch = val.time_since_epoch(); + auto subsecs = detail::duration_cast( + epoch - detail::duration_cast(epoch)); + return formatter::do_format(localtime(val), ctx, &subsecs); + } +}; + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_CHRONO_H_ diff --git a/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/color.h b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/color.h new file mode 100644 index 000000000..2faaf3a06 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/color.h @@ -0,0 +1,610 @@ +// Formatting library for C++ - color support +// +// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_COLOR_H_ +#define FMT_COLOR_H_ + +#include "format.h" + +FMT_BEGIN_NAMESPACE +FMT_BEGIN_EXPORT + +enum class color : uint32_t { + alice_blue = 0xF0F8FF, // rgb(240,248,255) + antique_white = 0xFAEBD7, // rgb(250,235,215) + aqua = 0x00FFFF, // rgb(0,255,255) + aquamarine = 0x7FFFD4, // rgb(127,255,212) + azure = 0xF0FFFF, // rgb(240,255,255) + beige = 0xF5F5DC, // rgb(245,245,220) + bisque = 0xFFE4C4, // rgb(255,228,196) + black = 0x000000, // rgb(0,0,0) + blanched_almond = 0xFFEBCD, // rgb(255,235,205) + blue = 0x0000FF, // rgb(0,0,255) + blue_violet = 0x8A2BE2, // rgb(138,43,226) + brown = 0xA52A2A, // rgb(165,42,42) + burly_wood = 0xDEB887, // rgb(222,184,135) + cadet_blue = 0x5F9EA0, // rgb(95,158,160) + chartreuse = 0x7FFF00, // rgb(127,255,0) + chocolate = 0xD2691E, // rgb(210,105,30) + coral = 0xFF7F50, // rgb(255,127,80) + cornflower_blue = 0x6495ED, // rgb(100,149,237) + cornsilk = 0xFFF8DC, // rgb(255,248,220) + crimson = 0xDC143C, // rgb(220,20,60) + cyan = 0x00FFFF, // rgb(0,255,255) + dark_blue = 0x00008B, // rgb(0,0,139) + dark_cyan = 0x008B8B, // rgb(0,139,139) + dark_golden_rod = 0xB8860B, // rgb(184,134,11) + dark_gray = 0xA9A9A9, // rgb(169,169,169) + dark_green = 0x006400, // rgb(0,100,0) + dark_khaki = 0xBDB76B, // rgb(189,183,107) + dark_magenta = 0x8B008B, // rgb(139,0,139) + dark_olive_green = 0x556B2F, // rgb(85,107,47) + dark_orange = 0xFF8C00, // rgb(255,140,0) + dark_orchid = 0x9932CC, // rgb(153,50,204) + dark_red = 0x8B0000, // rgb(139,0,0) + dark_salmon = 0xE9967A, // rgb(233,150,122) + dark_sea_green = 0x8FBC8F, // rgb(143,188,143) + dark_slate_blue = 0x483D8B, // rgb(72,61,139) + dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) + dark_turquoise = 0x00CED1, // rgb(0,206,209) + dark_violet = 0x9400D3, // rgb(148,0,211) + deep_pink = 0xFF1493, // rgb(255,20,147) + deep_sky_blue = 0x00BFFF, // rgb(0,191,255) + dim_gray = 0x696969, // rgb(105,105,105) + dodger_blue = 0x1E90FF, // rgb(30,144,255) + fire_brick = 0xB22222, // rgb(178,34,34) + floral_white = 0xFFFAF0, // rgb(255,250,240) + forest_green = 0x228B22, // rgb(34,139,34) + fuchsia = 0xFF00FF, // rgb(255,0,255) + gainsboro = 0xDCDCDC, // rgb(220,220,220) + ghost_white = 0xF8F8FF, // rgb(248,248,255) + gold = 0xFFD700, // rgb(255,215,0) + golden_rod = 0xDAA520, // rgb(218,165,32) + gray = 0x808080, // rgb(128,128,128) + green = 0x008000, // rgb(0,128,0) + green_yellow = 0xADFF2F, // rgb(173,255,47) + honey_dew = 0xF0FFF0, // rgb(240,255,240) + hot_pink = 0xFF69B4, // rgb(255,105,180) + indian_red = 0xCD5C5C, // rgb(205,92,92) + indigo = 0x4B0082, // rgb(75,0,130) + ivory = 0xFFFFF0, // rgb(255,255,240) + khaki = 0xF0E68C, // rgb(240,230,140) + lavender = 0xE6E6FA, // rgb(230,230,250) + lavender_blush = 0xFFF0F5, // rgb(255,240,245) + lawn_green = 0x7CFC00, // rgb(124,252,0) + lemon_chiffon = 0xFFFACD, // rgb(255,250,205) + light_blue = 0xADD8E6, // rgb(173,216,230) + light_coral = 0xF08080, // rgb(240,128,128) + light_cyan = 0xE0FFFF, // rgb(224,255,255) + light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) + light_gray = 0xD3D3D3, // rgb(211,211,211) + light_green = 0x90EE90, // rgb(144,238,144) + light_pink = 0xFFB6C1, // rgb(255,182,193) + light_salmon = 0xFFA07A, // rgb(255,160,122) + light_sea_green = 0x20B2AA, // rgb(32,178,170) + light_sky_blue = 0x87CEFA, // rgb(135,206,250) + light_slate_gray = 0x778899, // rgb(119,136,153) + light_steel_blue = 0xB0C4DE, // rgb(176,196,222) + light_yellow = 0xFFFFE0, // rgb(255,255,224) + lime = 0x00FF00, // rgb(0,255,0) + lime_green = 0x32CD32, // rgb(50,205,50) + linen = 0xFAF0E6, // rgb(250,240,230) + magenta = 0xFF00FF, // rgb(255,0,255) + maroon = 0x800000, // rgb(128,0,0) + medium_aquamarine = 0x66CDAA, // rgb(102,205,170) + medium_blue = 0x0000CD, // rgb(0,0,205) + medium_orchid = 0xBA55D3, // rgb(186,85,211) + medium_purple = 0x9370DB, // rgb(147,112,219) + medium_sea_green = 0x3CB371, // rgb(60,179,113) + medium_slate_blue = 0x7B68EE, // rgb(123,104,238) + medium_spring_green = 0x00FA9A, // rgb(0,250,154) + medium_turquoise = 0x48D1CC, // rgb(72,209,204) + medium_violet_red = 0xC71585, // rgb(199,21,133) + midnight_blue = 0x191970, // rgb(25,25,112) + mint_cream = 0xF5FFFA, // rgb(245,255,250) + misty_rose = 0xFFE4E1, // rgb(255,228,225) + moccasin = 0xFFE4B5, // rgb(255,228,181) + navajo_white = 0xFFDEAD, // rgb(255,222,173) + navy = 0x000080, // rgb(0,0,128) + old_lace = 0xFDF5E6, // rgb(253,245,230) + olive = 0x808000, // rgb(128,128,0) + olive_drab = 0x6B8E23, // rgb(107,142,35) + orange = 0xFFA500, // rgb(255,165,0) + orange_red = 0xFF4500, // rgb(255,69,0) + orchid = 0xDA70D6, // rgb(218,112,214) + pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) + pale_green = 0x98FB98, // rgb(152,251,152) + pale_turquoise = 0xAFEEEE, // rgb(175,238,238) + pale_violet_red = 0xDB7093, // rgb(219,112,147) + papaya_whip = 0xFFEFD5, // rgb(255,239,213) + peach_puff = 0xFFDAB9, // rgb(255,218,185) + peru = 0xCD853F, // rgb(205,133,63) + pink = 0xFFC0CB, // rgb(255,192,203) + plum = 0xDDA0DD, // rgb(221,160,221) + powder_blue = 0xB0E0E6, // rgb(176,224,230) + purple = 0x800080, // rgb(128,0,128) + rebecca_purple = 0x663399, // rgb(102,51,153) + red = 0xFF0000, // rgb(255,0,0) + rosy_brown = 0xBC8F8F, // rgb(188,143,143) + royal_blue = 0x4169E1, // rgb(65,105,225) + saddle_brown = 0x8B4513, // rgb(139,69,19) + salmon = 0xFA8072, // rgb(250,128,114) + sandy_brown = 0xF4A460, // rgb(244,164,96) + sea_green = 0x2E8B57, // rgb(46,139,87) + sea_shell = 0xFFF5EE, // rgb(255,245,238) + sienna = 0xA0522D, // rgb(160,82,45) + silver = 0xC0C0C0, // rgb(192,192,192) + sky_blue = 0x87CEEB, // rgb(135,206,235) + slate_blue = 0x6A5ACD, // rgb(106,90,205) + slate_gray = 0x708090, // rgb(112,128,144) + snow = 0xFFFAFA, // rgb(255,250,250) + spring_green = 0x00FF7F, // rgb(0,255,127) + steel_blue = 0x4682B4, // rgb(70,130,180) + tan = 0xD2B48C, // rgb(210,180,140) + teal = 0x008080, // rgb(0,128,128) + thistle = 0xD8BFD8, // rgb(216,191,216) + tomato = 0xFF6347, // rgb(255,99,71) + turquoise = 0x40E0D0, // rgb(64,224,208) + violet = 0xEE82EE, // rgb(238,130,238) + wheat = 0xF5DEB3, // rgb(245,222,179) + white = 0xFFFFFF, // rgb(255,255,255) + white_smoke = 0xF5F5F5, // rgb(245,245,245) + yellow = 0xFFFF00, // rgb(255,255,0) + yellow_green = 0x9ACD32 // rgb(154,205,50) +}; // enum class color + +enum class terminal_color : uint8_t { + black = 30, + red, + green, + yellow, + blue, + magenta, + cyan, + white, + bright_black = 90, + bright_red, + bright_green, + bright_yellow, + bright_blue, + bright_magenta, + bright_cyan, + bright_white +}; + +enum class emphasis : uint8_t { + bold = 1, + faint = 1 << 1, + italic = 1 << 2, + underline = 1 << 3, + blink = 1 << 4, + reverse = 1 << 5, + conceal = 1 << 6, + strikethrough = 1 << 7, +}; + +// rgb is a struct for red, green and blue colors. +// Using the name "rgb" makes some editors show the color in a tooltip. +struct rgb { + FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {} + FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} + FMT_CONSTEXPR rgb(uint32_t hex) + : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} + FMT_CONSTEXPR rgb(color hex) + : r((uint32_t(hex) >> 16) & 0xFF), + g((uint32_t(hex) >> 8) & 0xFF), + b(uint32_t(hex) & 0xFF) {} + uint8_t r; + uint8_t g; + uint8_t b; +}; + +namespace detail { + +// color is a struct of either a rgb color or a terminal color. +struct color_type { + FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {} + FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} { + value.rgb_color = static_cast(rgb_color); + } + FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} { + value.rgb_color = (static_cast(rgb_color.r) << 16) | + (static_cast(rgb_color.g) << 8) | rgb_color.b; + } + FMT_CONSTEXPR color_type(terminal_color term_color) noexcept + : is_rgb(), value{} { + value.term_color = static_cast(term_color); + } + bool is_rgb; + union color_union { + uint8_t term_color; + uint32_t rgb_color; + } value; +}; +} // namespace detail + +/// A text style consisting of foreground and background colors and emphasis. +class text_style { + public: + FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept + : set_foreground_color(), set_background_color(), ems(em) {} + + FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& { + if (!set_foreground_color) { + set_foreground_color = rhs.set_foreground_color; + foreground_color = rhs.foreground_color; + } else if (rhs.set_foreground_color) { + if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) + report_error("can't OR a terminal color"); + foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; + } + + if (!set_background_color) { + set_background_color = rhs.set_background_color; + background_color = rhs.background_color; + } else if (rhs.set_background_color) { + if (!background_color.is_rgb || !rhs.background_color.is_rgb) + report_error("can't OR a terminal color"); + background_color.value.rgb_color |= rhs.background_color.value.rgb_color; + } + + ems = static_cast(static_cast(ems) | + static_cast(rhs.ems)); + return *this; + } + + friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs) + -> text_style { + return lhs |= rhs; + } + + FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { + return set_foreground_color; + } + FMT_CONSTEXPR auto has_background() const noexcept -> bool { + return set_background_color; + } + FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { + return static_cast(ems) != 0; + } + FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { + FMT_ASSERT(has_foreground(), "no foreground specified for this style"); + return foreground_color; + } + FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { + FMT_ASSERT(has_background(), "no background specified for this style"); + return background_color; + } + FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { + FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); + return ems; + } + + private: + FMT_CONSTEXPR text_style(bool is_foreground, + detail::color_type text_color) noexcept + : set_foreground_color(), set_background_color(), ems() { + if (is_foreground) { + foreground_color = text_color; + set_foreground_color = true; + } else { + background_color = text_color; + set_background_color = true; + } + } + + friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept + -> text_style; + + friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept + -> text_style; + + detail::color_type foreground_color; + detail::color_type background_color; + bool set_foreground_color; + bool set_background_color; + emphasis ems; +}; + +/// Creates a text style from the foreground (text) color. +FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept + -> text_style { + return text_style(true, foreground); +} + +/// Creates a text style from the background color. +FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept + -> text_style { + return text_style(false, background); +} + +FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept + -> text_style { + return text_style(lhs) | rhs; +} + +namespace detail { + +template struct ansi_color_escape { + FMT_CONSTEXPR ansi_color_escape(color_type text_color, + const char* esc) noexcept { + // If we have a terminal color, we need to output another escape code + // sequence. + if (!text_color.is_rgb) { + bool is_background = esc == string_view("\x1b[48;2;"); + uint32_t value = text_color.value.term_color; + // Background ASCII codes are the same as the foreground ones but with + // 10 more. + if (is_background) value += 10u; + + size_t index = 0; + buffer[index++] = static_cast('\x1b'); + buffer[index++] = static_cast('['); + + if (value >= 100u) { + buffer[index++] = static_cast('1'); + value %= 100u; + } + buffer[index++] = static_cast('0' + value / 10u); + buffer[index++] = static_cast('0' + value % 10u); + + buffer[index++] = static_cast('m'); + buffer[index++] = static_cast('\0'); + return; + } + + for (int i = 0; i < 7; i++) { + buffer[i] = static_cast(esc[i]); + } + rgb color(text_color.value.rgb_color); + to_esc(color.r, buffer + 7, ';'); + to_esc(color.g, buffer + 11, ';'); + to_esc(color.b, buffer + 15, 'm'); + buffer[19] = static_cast(0); + } + FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept { + uint8_t em_codes[num_emphases] = {}; + if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1; + if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2; + if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3; + if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4; + if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5; + if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7; + if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8; + if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9; + + size_t index = 0; + for (size_t i = 0; i < num_emphases; ++i) { + if (!em_codes[i]) continue; + buffer[index++] = static_cast('\x1b'); + buffer[index++] = static_cast('['); + buffer[index++] = static_cast('0' + em_codes[i]); + buffer[index++] = static_cast('m'); + } + buffer[index++] = static_cast(0); + } + FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } + + FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; } + FMT_CONSTEXPR20 auto end() const noexcept -> const Char* { + return buffer + basic_string_view(buffer).size(); + } + + private: + static constexpr size_t num_emphases = 8; + Char buffer[7u + 3u * num_emphases + 1u]; + + static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, + char delimiter) noexcept { + out[0] = static_cast('0' + c / 100); + out[1] = static_cast('0' + c / 10 % 10); + out[2] = static_cast('0' + c % 10); + out[3] = static_cast(delimiter); + } + static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept + -> bool { + return static_cast(em) & static_cast(mask); + } +}; + +template +FMT_CONSTEXPR auto make_foreground_color(color_type foreground) noexcept + -> ansi_color_escape { + return ansi_color_escape(foreground, "\x1b[38;2;"); +} + +template +FMT_CONSTEXPR auto make_background_color(color_type background) noexcept + -> ansi_color_escape { + return ansi_color_escape(background, "\x1b[48;2;"); +} + +template +FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept + -> ansi_color_escape { + return ansi_color_escape(em); +} + +template inline void reset_color(buffer& buffer) { + auto reset_color = string_view("\x1b[0m"); + buffer.append(reset_color.begin(), reset_color.end()); +} + +template struct styled_arg : view { + const T& value; + text_style style; + styled_arg(const T& v, text_style s) : value(v), style(s) {} +}; + +template +void vformat_to(buffer& buf, const text_style& ts, + basic_string_view fmt, + basic_format_args> args) { + bool has_style = false; + if (ts.has_emphasis()) { + has_style = true; + auto emphasis = make_emphasis(ts.get_emphasis()); + buf.append(emphasis.begin(), emphasis.end()); + } + if (ts.has_foreground()) { + has_style = true; + auto foreground = make_foreground_color(ts.get_foreground()); + buf.append(foreground.begin(), foreground.end()); + } + if (ts.has_background()) { + has_style = true; + auto background = make_background_color(ts.get_background()); + buf.append(background.begin(), background.end()); + } + vformat_to(buf, fmt, args); + if (has_style) reset_color(buf); +} +} // namespace detail + +inline void vprint(FILE* f, const text_style& ts, string_view fmt, + format_args args) { + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size())); +} + +/** + * Formats a string and prints it to the specified file stream using ANSI + * escape sequences to specify text formatting. + * + * **Example**: + * + * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); + */ +template +void print(FILE* f, const text_style& ts, format_string fmt, + T&&... args) { + vprint(f, ts, fmt.str, vargs{{args...}}); +} + +/** + * Formats a string and prints it to stdout using ANSI escape sequences to + * specify text formatting. + * + * **Example**: + * + * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); + */ +template +void print(const text_style& ts, format_string fmt, T&&... args) { + return print(stdout, ts, fmt, std::forward(args)...); +} + +inline auto vformat(const text_style& ts, string_view fmt, format_args args) + -> std::string { + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + return fmt::to_string(buf); +} + +/** + * Formats arguments and returns the result as a string using ANSI escape + * sequences to specify text formatting. + * + * **Example**: + * + * ``` + * #include + * std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), + * "The answer is {}", 42); + * ``` + */ +template +inline auto format(const text_style& ts, format_string fmt, T&&... args) + -> std::string { + return fmt::vformat(ts, fmt.str, vargs{{args...}}); +} + +/// Formats a string with the given text_style and writes the output to `out`. +template ::value)> +auto vformat_to(OutputIt out, const text_style& ts, string_view fmt, + format_args args) -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, ts, fmt, args); + return detail::get_iterator(buf, out); +} + +/** + * Formats arguments with the given text style, writes the result to the output + * iterator `out` and returns the iterator past the end of the output range. + * + * **Example**: + * + * std::vector out; + * fmt::format_to(std::back_inserter(out), + * fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); + */ +template ::value)> +inline auto format_to(OutputIt out, const text_style& ts, + format_string fmt, T&&... args) -> OutputIt { + return vformat_to(out, ts, fmt.str, vargs{{args...}}); +} + +template +struct formatter, Char> : formatter { + template + auto format(const detail::styled_arg& arg, FormatContext& ctx) const + -> decltype(ctx.out()) { + const auto& ts = arg.style; + auto out = ctx.out(); + + bool has_style = false; + if (ts.has_emphasis()) { + has_style = true; + auto emphasis = detail::make_emphasis(ts.get_emphasis()); + out = detail::copy(emphasis.begin(), emphasis.end(), out); + } + if (ts.has_foreground()) { + has_style = true; + auto foreground = + detail::make_foreground_color(ts.get_foreground()); + out = detail::copy(foreground.begin(), foreground.end(), out); + } + if (ts.has_background()) { + has_style = true; + auto background = + detail::make_background_color(ts.get_background()); + out = detail::copy(background.begin(), background.end(), out); + } + out = formatter::format(arg.value, ctx); + if (has_style) { + auto reset_color = string_view("\x1b[0m"); + out = detail::copy(reset_color.begin(), reset_color.end(), out); + } + return out; + } +}; + +/** + * Returns an argument that will be formatted using ANSI escape sequences, + * to be used in a formatting function. + * + * **Example**: + * + * fmt::print("Elapsed time: {0:.2f} seconds", + * fmt::styled(1.23, fmt::fg(fmt::color::green) | + * fmt::bg(fmt::color::blue))); + */ +template +FMT_CONSTEXPR auto styled(const T& value, text_style ts) + -> detail::styled_arg> { + return detail::styled_arg>{value, ts}; +} + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_COLOR_H_ diff --git a/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/compile.h b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/compile.h new file mode 100644 index 000000000..68b451c71 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/compile.h @@ -0,0 +1,551 @@ +// Formatting library for C++ - experimental format string compilation +// +// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_COMPILE_H_ +#define FMT_COMPILE_H_ + +#ifndef FMT_MODULE +# include // std::back_inserter +#endif + +#include "format.h" + +FMT_BEGIN_NAMESPACE + +// A compile-time string which is compiled into fast formatting code. +FMT_EXPORT class compiled_string {}; + +namespace detail { + +template +struct is_compiled_string : std::is_base_of {}; + +/** + * Converts a string literal `s` into a format string that will be parsed at + * compile time and converted into efficient formatting code. Requires C++17 + * `constexpr if` compiler support. + * + * **Example**: + * + * // Converts 42 into std::string using the most efficient method and no + * // runtime format string processing. + * std::string s = fmt::format(FMT_COMPILE("{}"), 42); + */ +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) +# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string) +#else +# define FMT_COMPILE(s) FMT_STRING(s) +#endif + +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template Str> +struct udl_compiled_string : compiled_string { + using char_type = Char; + constexpr explicit operator basic_string_view() const { + return {Str.data, N - 1}; + } +}; +#endif + +template +auto first(const T& value, const Tail&...) -> const T& { + return value; +} + +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) +template struct type_list {}; + +// Returns a reference to the argument at index N from [first, rest...]. +template +constexpr const auto& get([[maybe_unused]] const T& first, + [[maybe_unused]] const Args&... rest) { + static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); + if constexpr (N == 0) + return first; + else + return detail::get(rest...); +} + +# if FMT_USE_NONTYPE_TEMPLATE_ARGS +template +constexpr auto get_arg_index_by_name(basic_string_view name) -> int { + if constexpr (is_static_named_arg()) { + if (name == T::name) return N; + } + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name(name); + (void)name; // Workaround an MSVC bug about "unused" parameter. + return -1; +} +# endif + +template +FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { +# if FMT_USE_NONTYPE_TEMPLATE_ARGS + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name<0, Args...>(name); +# endif + (void)name; + return -1; +} + +template +constexpr int get_arg_index_by_name(basic_string_view name, + type_list) { + return get_arg_index_by_name(name); +} + +template struct get_type_impl; + +template struct get_type_impl> { + using type = + remove_cvref_t(std::declval()...))>; +}; + +template +using get_type = typename get_type_impl::type; + +template struct is_compiled_format : std::false_type {}; + +template struct text { + basic_string_view data; + using char_type = Char; + + template + constexpr OutputIt format(OutputIt out, const Args&...) const { + return write(out, data); + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +template +constexpr text make_text(basic_string_view s, size_t pos, + size_t size) { + return {{&s[pos], size}}; +} + +template struct code_unit { + Char value; + using char_type = Char; + + template + constexpr OutputIt format(OutputIt out, const Args&...) const { + *out++ = value; + return out; + } +}; + +// This ensures that the argument type is convertible to `const T&`. +template +constexpr const T& get_arg_checked(const Args&... args) { + const auto& arg = detail::get(args...); + if constexpr (detail::is_named_arg>()) { + return arg.value; + } else { + return arg; + } +} + +template +struct is_compiled_format> : std::true_type {}; + +// A replacement field that refers to argument N. +template struct field { + using char_type = Char; + + template + constexpr OutputIt format(OutputIt out, const Args&... args) const { + const T& arg = get_arg_checked(args...); + if constexpr (std::is_convertible>::value) { + auto s = basic_string_view(arg); + return copy(s.begin(), s.end(), out); + } else { + return write(out, arg); + } + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +// A replacement field that refers to argument with name. +template struct runtime_named_field { + using char_type = Char; + basic_string_view name; + + template + constexpr static bool try_format_argument( + OutputIt& out, + // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9 + [[maybe_unused]] basic_string_view arg_name, const T& arg) { + if constexpr (is_named_arg::type>::value) { + if (arg_name == arg.name) { + out = write(out, arg.value); + return true; + } + } + return false; + } + + template + constexpr OutputIt format(OutputIt out, const Args&... args) const { + bool found = (try_format_argument(out, name, args) || ...); + if (!found) { + FMT_THROW(format_error("argument with specified name is not found")); + } + return out; + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +// A replacement field that refers to argument N and has format specifiers. +template struct spec_field { + using char_type = Char; + formatter fmt; + + template + constexpr FMT_INLINE OutputIt format(OutputIt out, + const Args&... args) const { + const auto& vargs = + fmt::make_format_args>(args...); + basic_format_context ctx(out, vargs); + return fmt.format(get_arg_checked(args...), ctx); + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +template struct concat { + L lhs; + R rhs; + using char_type = typename L::char_type; + + template + constexpr OutputIt format(OutputIt out, const Args&... args) const { + out = lhs.format(out, args...); + return rhs.format(out, args...); + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +template +constexpr concat make_concat(L lhs, R rhs) { + return {lhs, rhs}; +} + +struct unknown_format {}; + +template +constexpr size_t parse_text(basic_string_view str, size_t pos) { + for (size_t size = str.size(); pos != size; ++pos) { + if (str[pos] == '{' || str[pos] == '}') break; + } + return pos; +} + +template +constexpr auto compile_format_string(S fmt); + +template +constexpr auto parse_tail(T head, S fmt) { + if constexpr (POS != basic_string_view(fmt).size()) { + constexpr auto tail = compile_format_string(fmt); + if constexpr (std::is_same, + unknown_format>()) + return tail; + else + return make_concat(head, tail); + } else { + return head; + } +} + +template struct parse_specs_result { + formatter fmt; + size_t end; + int next_arg_id; +}; + +enum { manual_indexing_id = -1 }; + +template +constexpr parse_specs_result parse_specs(basic_string_view str, + size_t pos, int next_arg_id) { + str.remove_prefix(pos); + auto ctx = + compile_parse_context(str, max_value(), nullptr, next_arg_id); + auto f = formatter(); + auto end = f.parse(ctx); + return {f, pos + fmt::detail::to_unsigned(end - str.data()), + next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; +} + +template struct arg_id_handler { + arg_id_kind kind; + arg_ref arg_id; + + constexpr int on_auto() { + FMT_ASSERT(false, "handler cannot be used with automatic indexing"); + return 0; + } + constexpr int on_index(int id) { + kind = arg_id_kind::index; + arg_id = arg_ref(id); + return 0; + } + constexpr int on_name(basic_string_view id) { + kind = arg_id_kind::name; + arg_id = arg_ref(id); + return 0; + } +}; + +template struct parse_arg_id_result { + arg_id_kind kind; + arg_ref arg_id; + const Char* arg_id_end; +}; + +template +constexpr auto parse_arg_id(const Char* begin, const Char* end) { + auto handler = arg_id_handler{arg_id_kind::none, arg_ref{}}; + auto arg_id_end = parse_arg_id(begin, end, handler); + return parse_arg_id_result{handler.kind, handler.arg_id, arg_id_end}; +} + +template struct field_type { + using type = remove_cvref_t; +}; + +template +struct field_type::value>> { + using type = remove_cvref_t; +}; + +template +constexpr auto parse_replacement_field_then_tail(S fmt) { + using char_type = typename S::char_type; + constexpr auto str = basic_string_view(fmt); + constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); + if constexpr (c == '}') { + return parse_tail( + field::type, ARG_INDEX>(), fmt); + } else if constexpr (c != ':') { + FMT_THROW(format_error("expected ':'")); + } else { + constexpr auto result = parse_specs::type>( + str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); + if constexpr (result.end >= str.size() || str[result.end] != '}') { + FMT_THROW(format_error("expected '}'")); + return 0; + } else { + return parse_tail( + spec_field::type, ARG_INDEX>{ + result.fmt}, + fmt); + } + } +} + +// Compiles a non-empty format string and returns the compiled representation +// or unknown_format() on unrecognized input. +template +constexpr auto compile_format_string(S fmt) { + using char_type = typename S::char_type; + constexpr auto str = basic_string_view(fmt); + if constexpr (str[POS] == '{') { + if constexpr (POS + 1 == str.size()) + FMT_THROW(format_error("unmatched '{' in format string")); + if constexpr (str[POS + 1] == '{') { + return parse_tail(make_text(str, POS, 1), fmt); + } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { + static_assert(ID != manual_indexing_id, + "cannot switch from manual to automatic argument indexing"); + constexpr auto next_id = + ID != manual_indexing_id ? ID + 1 : manual_indexing_id; + return parse_replacement_field_then_tail, Args, + POS + 1, ID, next_id>(fmt); + } else { + constexpr auto arg_id_result = + parse_arg_id(str.data() + POS + 1, str.data() + str.size()); + constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data(); + constexpr char_type c = + arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); + static_assert(c == '}' || c == ':', "missing '}' in format string"); + if constexpr (arg_id_result.kind == arg_id_kind::index) { + static_assert( + ID == manual_indexing_id || ID == 0, + "cannot switch from automatic to manual argument indexing"); + constexpr auto arg_index = arg_id_result.arg_id.index; + return parse_replacement_field_then_tail, + Args, arg_id_end_pos, + arg_index, manual_indexing_id>( + fmt); + } else if constexpr (arg_id_result.kind == arg_id_kind::name) { + constexpr auto arg_index = + get_arg_index_by_name(arg_id_result.arg_id.name, Args{}); + if constexpr (arg_index >= 0) { + constexpr auto next_id = + ID != manual_indexing_id ? ID + 1 : manual_indexing_id; + return parse_replacement_field_then_tail< + decltype(get_type::value), Args, arg_id_end_pos, + arg_index, next_id>(fmt); + } else if constexpr (c == '}') { + return parse_tail( + runtime_named_field{arg_id_result.arg_id.name}, fmt); + } else if constexpr (c == ':') { + return unknown_format(); // no type info for specs parsing + } + } + } + } else if constexpr (str[POS] == '}') { + if constexpr (POS + 1 == str.size()) + FMT_THROW(format_error("unmatched '}' in format string")); + return parse_tail(make_text(str, POS, 1), fmt); + } else { + constexpr auto end = parse_text(str, POS + 1); + if constexpr (end - POS > 1) { + return parse_tail(make_text(str, POS, end - POS), fmt); + } else { + return parse_tail(code_unit{str[POS]}, fmt); + } + } +} + +template ::value)> +constexpr auto compile(S fmt) { + constexpr auto str = basic_string_view(fmt); + if constexpr (str.size() == 0) { + return detail::make_text(str, 0, 0); + } else { + constexpr auto result = + detail::compile_format_string, 0, 0>(fmt); + return result; + } +} +#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) +} // namespace detail + +FMT_BEGIN_EXPORT + +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) + +template ::value)> +FMT_INLINE std::basic_string format(const CompiledFormat& cf, + const Args&... args) { + auto s = std::basic_string(); + cf.format(std::back_inserter(s), args...); + return s; +} + +template ::value)> +constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf, + const Args&... args) { + return cf.format(out, args...); +} + +template ::value)> +FMT_INLINE std::basic_string format(const S&, + Args&&... args) { + if constexpr (std::is_same::value) { + constexpr auto str = basic_string_view(S()); + if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { + const auto& first = detail::first(args...); + if constexpr (detail::is_named_arg< + remove_cvref_t>::value) { + return fmt::to_string(first.value); + } else { + return fmt::to_string(first); + } + } + } + constexpr auto compiled = detail::compile(S()); + if constexpr (std::is_same, + detail::unknown_format>()) { + return fmt::format( + static_cast>(S()), + std::forward(args)...); + } else { + return fmt::format(compiled, std::forward(args)...); + } +} + +template ::value)> +FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { + constexpr auto compiled = detail::compile(S()); + if constexpr (std::is_same, + detail::unknown_format>()) { + return fmt::format_to( + out, static_cast>(S()), + std::forward(args)...); + } else { + return fmt::format_to(out, compiled, std::forward(args)...); + } +} +#endif + +template ::value)> +auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + fmt::format_to(std::back_inserter(buf), fmt, std::forward(args)...); + return {buf.out(), buf.count()}; +} + +template ::value)> +FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args) + -> size_t { + auto buf = detail::counting_buffer<>(); + fmt::format_to(appender(buf), fmt, args...); + return buf.count(); +} + +template ::value)> +void print(std::FILE* f, const S& fmt, const Args&... args) { + auto buf = memory_buffer(); + fmt::format_to(appender(buf), fmt, args...); + detail::print(f, {buf.data(), buf.size()}); +} + +template ::value)> +void print(const S& fmt, const Args&... args) { + print(stdout, fmt, args...); +} + +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +inline namespace literals { +template constexpr auto operator""_cf() { + using char_t = remove_cvref_t; + return detail::udl_compiled_string(); +} +} // namespace literals +#endif + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_COMPILE_H_ diff --git a/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/core.h b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/core.h new file mode 100644 index 000000000..8ca735f0c --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/core.h @@ -0,0 +1,5 @@ +// This file is only provided for compatibility and may be removed in future +// versions. Use fmt/base.h if you don't need fmt::format and fmt/format.h +// otherwise. + +#include "format.h" diff --git a/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/format-inl.h b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/format-inl.h new file mode 100644 index 000000000..38308bf2a --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/format-inl.h @@ -0,0 +1,1947 @@ +// Formatting library for C++ - implementation +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_FORMAT_INL_H_ +#define FMT_FORMAT_INL_H_ + +#ifndef FMT_MODULE +# include +# include // errno +# include +# include +# include +#endif + +#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) +# include // _isatty +#endif + +#include "format.h" + +#if FMT_USE_LOCALE +# include +#endif + +#ifndef FMT_FUNC +# define FMT_FUNC +#endif + +FMT_BEGIN_NAMESPACE +namespace detail { + +FMT_FUNC void assert_fail(const char* file, int line, const char* message) { + // Use unchecked std::fprintf to avoid triggering another assertion when + // writing to stderr fails. + fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message); + abort(); +} + +FMT_FUNC void format_error_code(detail::buffer& out, int error_code, + string_view message) noexcept { + // Report error code making sure that the output fits into + // inline_buffer_size to avoid dynamic memory allocation and potential + // bad_alloc. + out.try_resize(0); + static const char SEP[] = ": "; + static const char ERROR_STR[] = "error "; + // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. + size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; + auto abs_value = static_cast>(error_code); + if (detail::is_negative(error_code)) { + abs_value = 0 - abs_value; + ++error_code_size; + } + error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); + auto it = appender(out); + if (message.size() <= inline_buffer_size - error_code_size) + fmt::format_to(it, FMT_STRING("{}{}"), message, SEP); + fmt::format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); + FMT_ASSERT(out.size() <= inline_buffer_size, ""); +} + +FMT_FUNC void do_report_error(format_func func, int error_code, + const char* message) noexcept { + memory_buffer full_message; + func(full_message, error_code, message); + // Don't use fwrite_all because the latter may throw. + if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0) + std::fputc('\n', stderr); +} + +// A wrapper around fwrite that throws on error. +inline void fwrite_all(const void* ptr, size_t count, FILE* stream) { + size_t written = std::fwrite(ptr, 1, count, stream); + if (written < count) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); +} + +#if FMT_USE_LOCALE +using std::locale; +using std::numpunct; +using std::use_facet; + +template > +locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { + static_assert(std::is_same::value, ""); +} +#else +struct locale {}; +template struct numpunct { + auto grouping() const -> std::string { return "\03"; } + auto thousands_sep() const -> Char { return ','; } + auto decimal_point() const -> Char { return '.'; } +}; +template Facet use_facet(locale) { return {}; } +#endif // FMT_USE_LOCALE + +template auto locale_ref::get() const -> Locale { + static_assert(std::is_same::value, ""); +#if FMT_USE_LOCALE + if (locale_) return *static_cast(locale_); +#endif + return locale(); +} + +template +FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result { + auto&& facet = use_facet>(loc.get()); + auto grouping = facet.grouping(); + auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep(); + return {std::move(grouping), thousands_sep}; +} +template +FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char { + return use_facet>(loc.get()).decimal_point(); +} + +#if FMT_USE_LOCALE +FMT_FUNC auto write_loc(appender out, loc_value value, + const format_specs& specs, locale_ref loc) -> bool { + auto locale = loc.get(); + // We cannot use the num_put facet because it may produce output in + // a wrong encoding. + using facet = format_facet; + if (std::has_facet(locale)) + return use_facet(locale).put(out, value, specs); + return facet(locale).put(out, value, specs); +} +#endif +} // namespace detail + +FMT_FUNC void report_error(const char* message) { +#if FMT_USE_EXCEPTIONS + throw format_error(message); +#else + fputs(message, stderr); + abort(); +#endif +} + +template typename Locale::id format_facet::id; + +template format_facet::format_facet(Locale& loc) { + auto& np = detail::use_facet>(loc); + grouping_ = np.grouping(); + if (!grouping_.empty()) separator_ = std::string(1, np.thousands_sep()); +} + +#if FMT_USE_LOCALE +template <> +FMT_API FMT_FUNC auto format_facet::do_put( + appender out, loc_value val, const format_specs& specs) const -> bool { + return val.visit( + detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_}); +} +#endif + +FMT_FUNC auto vsystem_error(int error_code, string_view fmt, format_args args) + -> std::system_error { + auto ec = std::error_code(error_code, std::generic_category()); + return std::system_error(ec, vformat(fmt, args)); +} + +namespace detail { + +template +inline auto operator==(basic_fp x, basic_fp y) -> bool { + return x.f == y.f && x.e == y.e; +} + +// Compilers should be able to optimize this into the ror instruction. +FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t { + r &= 31; + return (n >> r) | (n << (32 - r)); +} +FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t { + r &= 63; + return (n >> r) | (n << (64 - r)); +} + +// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. +namespace dragonbox { +// Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a +// 64-bit unsigned integer. +inline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t { + return umul128_upper64(static_cast(x) << 32, y); +} + +// Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { + uint64_t high = x * y.high(); + uint128_fallback high_low = umul128(x, y.low()); + return {high + high_low.high(), high_low.low()}; +} + +// Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a +// 64-bit unsigned integer. +inline auto umul96_lower64(uint32_t x, uint64_t y) noexcept -> uint64_t { + return x * y; +} + +// Various fast log computations. +inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int { + FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); + return (e * 631305 - 261663) >> 21; +} + +FMT_INLINE_VARIABLE constexpr struct { + uint32_t divisor; + int shift_amount; +} div_small_pow10_infos[] = {{10, 16}, {100, 16}}; + +// Replaces n by floor(n / pow(10, N)) returning true if and only if n is +// divisible by pow(10, N). +// Precondition: n <= pow(10, N + 1). +template +auto check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept -> bool { + // The numbers below are chosen such that: + // 1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100, + // 2. nm mod 2^k < m if and only if n is divisible by d, + // where m is magic_number, k is shift_amount + // and d is divisor. + // + // Item 1 is a common technique of replacing division by a constant with + // multiplication, see e.g. "Division by Invariant Integers Using + // Multiplication" by Granlund and Montgomery (1994). magic_number (m) is set + // to ceil(2^k/d) for large enough k. + // The idea for item 2 originates from Schubfach. + constexpr auto info = div_small_pow10_infos[N - 1]; + FMT_ASSERT(n <= info.divisor * 10, "n is too large"); + constexpr uint32_t magic_number = + (1u << info.shift_amount) / info.divisor + 1; + n *= magic_number; + const uint32_t comparison_mask = (1u << info.shift_amount) - 1; + bool result = (n & comparison_mask) < magic_number; + n >>= info.shift_amount; + return result; +} + +// Computes floor(n / pow(10, N)) for small n and N. +// Precondition: n <= pow(10, N + 1). +template auto small_division_by_pow10(uint32_t n) noexcept -> uint32_t { + constexpr auto info = div_small_pow10_infos[N - 1]; + FMT_ASSERT(n <= info.divisor * 10, "n is too large"); + constexpr uint32_t magic_number = + (1u << info.shift_amount) / info.divisor + 1; + return (n * magic_number) >> info.shift_amount; +} + +// Computes floor(n / 10^(kappa + 1)) (float) +inline auto divide_by_10_to_kappa_plus_1(uint32_t n) noexcept -> uint32_t { + // 1374389535 = ceil(2^37/100) + return static_cast((static_cast(n) * 1374389535) >> 37); +} +// Computes floor(n / 10^(kappa + 1)) (double) +inline auto divide_by_10_to_kappa_plus_1(uint64_t n) noexcept -> uint64_t { + // 2361183241434822607 = ceil(2^(64+7)/1000) + return umul128_upper64(n, 2361183241434822607ull) >> 7; +} + +// Various subroutines using pow10 cache +template struct cache_accessor; + +template <> struct cache_accessor { + using carrier_uint = float_info::carrier_uint; + using cache_entry_type = uint64_t; + + static auto get_cached_power(int k) noexcept -> uint64_t { + FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, + "k is out of range"); + static constexpr const uint64_t pow10_significands[] = { + 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, + 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, + 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, + 0xf1c90080baf72cb2, 0x971da05074da7bef, 0xbce5086492111aeb, + 0xec1e4a7db69561a6, 0x9392ee8e921d5d08, 0xb877aa3236a4b44a, + 0xe69594bec44de15c, 0x901d7cf73ab0acda, 0xb424dc35095cd810, + 0xe12e13424bb40e14, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff, + 0xdbe6fecebdedd5bf, 0x89705f4136b4a598, 0xabcc77118461cefd, + 0xd6bf94d5e57a42bd, 0x8637bd05af6c69b6, 0xa7c5ac471b478424, + 0xd1b71758e219652c, 0x83126e978d4fdf3c, 0xa3d70a3d70a3d70b, + 0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000, + 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000, + 0xc350000000000000, 0xf424000000000000, 0x9896800000000000, + 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000, + 0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000, + 0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000, + 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000, + 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000, + 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0, + 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940985, + 0xa18f07d736b90be6, 0xc9f2c9cd04674edf, 0xfc6f7c4045812297, + 0x9dc5ada82b70b59e, 0xc5371912364ce306, 0xf684df56c3e01bc7, + 0x9a130b963a6c115d, 0xc097ce7bc90715b4, 0xf0bdc21abb48db21, + 0x96769950b50d88f5, 0xbc143fa4e250eb32, 0xeb194f8e1ae525fe, + 0x92efd1b8d0cf37bf, 0xb7abc627050305ae, 0xe596b7b0c643c71a, + 0x8f7e32ce7bea5c70, 0xb35dbf821ae4f38c, 0xe0352f62a19e306f}; + return pow10_significands[k - float_info::min_k]; + } + + struct compute_mul_result { + carrier_uint result; + bool is_integer; + }; + struct compute_mul_parity_result { + bool parity; + bool is_integer; + }; + + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { + auto r = umul96_upper64(u, cache); + return {static_cast(r >> 32), + static_cast(r) == 0}; + } + + static auto compute_delta(const cache_entry_type& cache, int beta) noexcept + -> uint32_t { + return static_cast(cache >> (64 - 1 - beta)); + } + + static auto compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta) noexcept + -> compute_mul_parity_result { + FMT_ASSERT(beta >= 1, ""); + FMT_ASSERT(beta < 64, ""); + + auto r = umul96_lower64(two_f, cache); + return {((r >> (64 - beta)) & 1) != 0, + static_cast(r >> (32 - beta)) == 0}; + } + + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return static_cast( + (cache - (cache >> (num_significand_bits() + 2))) >> + (64 - num_significand_bits() - 1 - beta)); + } + + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return static_cast( + (cache + (cache >> (num_significand_bits() + 1))) >> + (64 - num_significand_bits() - 1 - beta)); + } + + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return (static_cast( + cache >> (64 - num_significand_bits() - 2 - beta)) + + 1) / + 2; + } +}; + +template <> struct cache_accessor { + using carrier_uint = float_info::carrier_uint; + using cache_entry_type = uint128_fallback; + + static auto get_cached_power(int k) noexcept -> uint128_fallback { + FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, + "k is out of range"); + + static constexpr const uint128_fallback pow10_significands[] = { +#if FMT_USE_FULL_CACHE_DRAGONBOX + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, + {0x9faacf3df73609b1, 0x77b191618c54e9ad}, + {0xc795830d75038c1d, 0xd59df5b9ef6a2418}, + {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e}, + {0x9becce62836ac577, 0x4ee367f9430aec33}, + {0xc2e801fb244576d5, 0x229c41f793cda740}, + {0xf3a20279ed56d48a, 0x6b43527578c11110}, + {0x9845418c345644d6, 0x830a13896b78aaaa}, + {0xbe5691ef416bd60c, 0x23cc986bc656d554}, + {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9}, + {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa}, + {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54}, + {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69}, + {0x91376c36d99995be, 0x23100809b9c21fa2}, + {0xb58547448ffffb2d, 0xabd40a0c2832a78b}, + {0xe2e69915b3fff9f9, 0x16c90c8f323f516d}, + {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4}, + {0xb1442798f49ffb4a, 0x99cd11cfdf41779d}, + {0xdd95317f31c7fa1d, 0x40405643d711d584}, + {0x8a7d3eef7f1cfc52, 0x482835ea666b2573}, + {0xad1c8eab5ee43b66, 0xda3243650005eed0}, + {0xd863b256369d4a40, 0x90bed43e40076a83}, + {0x873e4f75e2224e68, 0x5a7744a6e804a292}, + {0xa90de3535aaae202, 0x711515d0a205cb37}, + {0xd3515c2831559a83, 0x0d5a5b44ca873e04}, + {0x8412d9991ed58091, 0xe858790afe9486c3}, + {0xa5178fff668ae0b6, 0x626e974dbe39a873}, + {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, + {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a}, + {0xa139029f6a239f72, 0x1c1fffc1ebc44e81}, + {0xc987434744ac874e, 0xa327ffb266b56221}, + {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9}, + {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa}, + {0xc4ce17b399107c22, 0xcb550fb4384d21d4}, + {0xf6019da07f549b2b, 0x7e2a53a146606a49}, + {0x99c102844f94e0fb, 0x2eda7444cbfc426e}, + {0xc0314325637a1939, 0xfa911155fefb5309}, + {0xf03d93eebc589f88, 0x793555ab7eba27cb}, + {0x96267c7535b763b5, 0x4bc1558b2f3458df}, + {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17}, + {0xea9c227723ee8bcb, 0x465e15a979c1cadd}, + {0x92a1958a7675175f, 0x0bfacd89ec191eca}, + {0xb749faed14125d36, 0xcef980ec671f667c}, + {0xe51c79a85916f484, 0x82b7e12780e7401b}, + {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811}, + {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16}, + {0xdfbdcece67006ac9, 0x67a791e093e1d49b}, + {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1}, + {0xaecc49914078536d, 0x58fae9f773886e19}, + {0xda7f5bf590966848, 0xaf39a475506a899f}, + {0x888f99797a5e012d, 0x6d8406c952429604}, + {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84}, + {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65}, + {0x855c3be0a17fcd26, 0x5cf2eea09a550680}, + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, + {0xd0601d8efc57b08b, 0xf13b94daf124da27}, + {0x823c12795db6ce57, 0x76c53d08d6b70859}, + {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f}, + {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a}, + {0xfe5d54150b090b02, 0xd3f93b35435d7c4d}, + {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0}, + {0xc6b8e9b0709f109a, 0x359ab6419ca1091c}, + {0xf867241c8cc6d4c0, 0xc30163d203c94b63}, + {0x9b407691d7fc44f8, 0x79e0de63425dcf1e}, + {0xc21094364dfb5636, 0x985915fc12f542e5}, + {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e}, + {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43}, + {0xbd8430bd08277231, 0x50c6ff782a838354}, + {0xece53cec4a314ebd, 0xa4f8bf5635246429}, + {0x940f4613ae5ed136, 0x871b7795e136be9a}, + {0xb913179899f68584, 0x28e2557b59846e40}, + {0xe757dd7ec07426e5, 0x331aeada2fe589d0}, + {0x9096ea6f3848984f, 0x3ff0d2c85def7622}, + {0xb4bca50b065abe63, 0x0fed077a756b53aa}, + {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895}, + {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d}, + {0xb080392cc4349dec, 0xbd8d794d96aacfb4}, + {0xdca04777f541c567, 0xecf0d7a0fc5583a1}, + {0x89e42caaf9491b60, 0xf41686c49db57245}, + {0xac5d37d5b79b6239, 0x311c2875c522ced6}, + {0xd77485cb25823ac7, 0x7d633293366b828c}, + {0x86a8d39ef77164bc, 0xae5dff9c02033198}, + {0xa8530886b54dbdeb, 0xd9f57f830283fdfd}, + {0xd267caa862a12d66, 0xd072df63c324fd7c}, + {0x8380dea93da4bc60, 0x4247cb9e59f71e6e}, + {0xa46116538d0deb78, 0x52d9be85f074e609}, + {0xcd795be870516656, 0x67902e276c921f8c}, + {0x806bd9714632dff6, 0x00ba1cd8a3db53b7}, + {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5}, + {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce}, + {0xfad2a4b13d1b5d6c, 0x796b805720085f82}, + {0x9cc3a6eec6311a63, 0xcbe3303674053bb1}, + {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d}, + {0xf4f1b4d515acb93b, 0xee92fb5515482d45}, + {0x991711052d8bf3c5, 0x751bdd152d4d1c4b}, + {0xbf5cd54678eef0b6, 0xd262d45a78a0635e}, + {0xef340a98172aace4, 0x86fb897116c87c35}, + {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1}, + {0xbae0a846d2195712, 0x8974836059cca10a}, + {0xe998d258869facd7, 0x2bd1a438703fc94c}, + {0x91ff83775423cc06, 0x7b6306a34627ddd0}, + {0xb67f6455292cbf08, 0x1a3bc84c17b1d543}, + {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94}, + {0x8e938662882af53e, 0x547eb47b7282ee9d}, + {0xb23867fb2a35b28d, 0xe99e619a4f23aa44}, + {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5}, + {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05}, + {0xae0b158b4738705e, 0x9624ab50b148d446}, + {0xd98ddaee19068c76, 0x3badd624dd9b0958}, + {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7}, + {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d}, + {0xd47487cc8470652b, 0x7647c32000696720}, + {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074}, + {0xa5fb0a17c777cf09, 0xf468107100525891}, + {0xcf79cc9db955c2cc, 0x7182148d4066eeb5}, + {0x81ac1fe293d599bf, 0xc6f14cd848405531}, + {0xa21727db38cb002f, 0xb8ada00e5a506a7d}, + {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d}, + {0xfd442e4688bd304a, 0x908f4a166d1da664}, + {0x9e4a9cec15763e2e, 0x9a598e4e043287ff}, + {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe}, + {0xf7549530e188c128, 0xd12bee59e68ef47d}, + {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf}, + {0xc13a148e3032d6e7, 0xe36a52363c1faf02}, + {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2}, + {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba}, + {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8}, + {0xebdf661791d60f56, 0x111b495b3464ad22}, + {0x936b9fcebb25c995, 0xcab10dd900beec35}, + {0xb84687c269ef3bfb, 0x3d5d514f40eea743}, + {0xe65829b3046b0afa, 0x0cb4a5a3112a5113}, + {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac}, + {0xb3f4e093db73a093, 0x59ed216765690f57}, + {0xe0f218b8d25088b8, 0x306869c13ec3532d}, + {0x8c974f7383725573, 0x1e414218c73a13fc}, + {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, + {0xdbac6c247d62a583, 0xdf45f746b74abf3a}, + {0x894bc396ce5da772, 0x6b8bba8c328eb784}, + {0xab9eb47c81f5114f, 0x066ea92f3f326565}, + {0xd686619ba27255a2, 0xc80a537b0efefebe}, + {0x8613fd0145877585, 0xbd06742ce95f5f37}, + {0xa798fc4196e952e7, 0x2c48113823b73705}, + {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6}, + {0x82ef85133de648c4, 0x9a984d73dbe722fc}, + {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb}, + {0xcc963fee10b7d1b3, 0x318df905079926a9}, + {0xffbbcfe994e5c61f, 0xfdf17746497f7053}, + {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634}, + {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1}, + {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1}, + {0x9c1661a651213e2d, 0x06bea10ca65c084f}, + {0xc31bfa0fe5698db8, 0x486e494fcff30a63}, + {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb}, + {0x986ddb5c6b3a76b7, 0xf89629465a75e01d}, + {0xbe89523386091465, 0xf6bbb397f1135824}, + {0xee2ba6c0678b597f, 0x746aa07ded582e2d}, + {0x94db483840b717ef, 0xa8c2a44eb4571cdd}, + {0xba121a4650e4ddeb, 0x92f34d62616ce414}, + {0xe896a0d7e51e1566, 0x77b020baf9c81d18}, + {0x915e2486ef32cd60, 0x0ace1474dc1d122f}, + {0xb5b5ada8aaff80b8, 0x0d819992132456bb}, + {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a}, + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, + {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3}, + {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf}, + {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c}, + {0xad4ab7112eb3929d, 0x86c16c98d2c953c7}, + {0xd89d64d57a607744, 0xe871c7bf077ba8b8}, + {0x87625f056c7c4a8b, 0x11471cd764ad4973}, + {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0}, + {0xd389b47879823479, 0x4aff1d108d4ec2c4}, + {0x843610cb4bf160cb, 0xcedf722a585139bb}, + {0xa54394fe1eedb8fe, 0xc2974eb4ee658829}, + {0xce947a3da6a9273e, 0x733d226229feea33}, + {0x811ccc668829b887, 0x0806357d5a3f5260}, + {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8}, + {0xc9bcff6034c13052, 0xfc89b393dd02f0b6}, + {0xfc2c3f3841f17c67, 0xbbac2078d443ace3}, + {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e}, + {0xc5029163f384a931, 0x0a9e795e65d4df12}, + {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6}, + {0x99ea0196163fa42e, 0x504bced1bf8e4e46}, + {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7}, + {0xf07da27a82c37088, 0x5d767327bb4e5a4d}, + {0x964e858c91ba2655, 0x3a6a07f8d510f870}, + {0xbbe226efb628afea, 0x890489f70a55368c}, + {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f}, + {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e}, + {0xb77ada0617e3bbcb, 0x09ce6ebb40173745}, + {0xe55990879ddcaabd, 0xcc420a6a101d0516}, + {0x8f57fa54c2a9eab6, 0x9fa946824a12232e}, + {0xb32df8e9f3546564, 0x47939822dc96abfa}, + {0xdff9772470297ebd, 0x59787e2b93bc56f8}, + {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b}, + {0xaefae51477a06b03, 0xede622920b6b23f2}, + {0xdab99e59958885c4, 0xe95fab368e45ecee}, + {0x88b402f7fd75539b, 0x11dbcb0218ebb415}, + {0xaae103b5fcd2a881, 0xd652bdc29f26a11a}, + {0xd59944a37c0752a2, 0x4be76d3346f04960}, + {0x857fcae62d8493a5, 0x6f70a4400c562ddc}, + {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953}, + {0xd097ad07a71f26b2, 0x7e2000a41346a7a8}, + {0x825ecc24c873782f, 0x8ed400668c0c28c9}, + {0xa2f67f2dfa90563b, 0x728900802f0f32fb}, + {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba}, + {0xfea126b7d78186bc, 0xe2f610c84987bfa9}, + {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca}, + {0xc6ede63fa05d3143, 0x91503d1c79720dbc}, + {0xf8a95fcf88747d94, 0x75a44c6397ce912b}, + {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb}, + {0xc24452da229b021b, 0xfbe85badce996169}, + {0xf2d56790ab41c2a2, 0xfae27299423fb9c4}, + {0x97c560ba6b0919a5, 0xdccd879fc967d41b}, + {0xbdb6b8e905cb600f, 0x5400e987bbc1c921}, + {0xed246723473e3813, 0x290123e9aab23b69}, + {0x9436c0760c86e30b, 0xf9a0b6720aaf6522}, + {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, + {0xe7958cb87392c2c2, 0xb60b1d1230b20e05}, + {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3}, + {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4}, + {0xe2280b6c20dd5232, 0x25c6da63c38de1b1}, + {0x8d590723948a535f, 0x579c487e5a38ad0f}, + {0xb0af48ec79ace837, 0x2d835a9df0c6d852}, + {0xdcdb1b2798182244, 0xf8e431456cf88e66}, + {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900}, + {0xac8b2d36eed2dac5, 0xe272467e3d222f40}, + {0xd7adf884aa879177, 0x5b0ed81dcc6abb10}, + {0x86ccbb52ea94baea, 0x98e947129fc2b4ea}, + {0xa87fea27a539e9a5, 0x3f2398d747b36225}, + {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae}, + {0x83a3eeeef9153e89, 0x1953cf68300424ad}, + {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8}, + {0xcdb02555653131b6, 0x3792f412cb06794e}, + {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1}, + {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5}, + {0xc8de047564d20a8b, 0xf245825a5a445276}, + {0xfb158592be068d2e, 0xeed6e2f0f0d56713}, + {0x9ced737bb6c4183d, 0x55464dd69685606c}, + {0xc428d05aa4751e4c, 0xaa97e14c3c26b887}, + {0xf53304714d9265df, 0xd53dd99f4b3066a9}, + {0x993fe2c6d07b7fab, 0xe546a8038efe402a}, + {0xbf8fdb78849a5f96, 0xde98520472bdd034}, + {0xef73d256a5c0f77c, 0x963e66858f6d4441}, + {0x95a8637627989aad, 0xdde7001379a44aa9}, + {0xbb127c53b17ec159, 0x5560c018580d5d53}, + {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7}, + {0x9226712162ab070d, 0xcab3961304ca70e9}, + {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23}, + {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b}, + {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243}, + {0xb267ed1940f1c61c, 0x55f038b237591ed4}, + {0xdf01e85f912e37a3, 0x6b6c46dec52f6689}, + {0x8b61313bbabce2c6, 0x2323ac4b3b3da016}, + {0xae397d8aa96c1b77, 0xabec975e0a0d081b}, + {0xd9c7dced53c72255, 0x96e7bd358c904a22}, + {0x881cea14545c7575, 0x7e50d64177da2e55}, + {0xaa242499697392d2, 0xdde50bd1d5d0b9ea}, + {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865}, + {0x84ec3c97da624ab4, 0xbd5af13bef0b113f}, + {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f}, + {0xcfb11ead453994ba, 0x67de18eda5814af3}, + {0x81ceb32c4b43fcf4, 0x80eacf948770ced8}, + {0xa2425ff75e14fc31, 0xa1258379a94d028e}, + {0xcad2f7f5359a3b3e, 0x096ee45813a04331}, + {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd}, + {0x9e74d1b791e07e48, 0x775ea264cf55347e}, + {0xc612062576589dda, 0x95364afe032a819e}, + {0xf79687aed3eec551, 0x3a83ddbd83f52205}, + {0x9abe14cd44753b52, 0xc4926a9672793543}, + {0xc16d9a0095928a27, 0x75b7053c0f178294}, + {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, + {0x971da05074da7bee, 0xd3f6fc16ebca5e04}, + {0xbce5086492111aea, 0x88f4bb1ca6bcf585}, + {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6}, + {0x9392ee8e921d5d07, 0x3aff322e62439fd0}, + {0xb877aa3236a4b449, 0x09befeb9fad487c3}, + {0xe69594bec44de15b, 0x4c2ebe687989a9b4}, + {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11}, + {0xb424dc35095cd80f, 0x538484c19ef38c95}, + {0xe12e13424bb40e13, 0x2865a5f206b06fba}, + {0x8cbccc096f5088cb, 0xf93f87b7442e45d4}, + {0xafebff0bcb24aafe, 0xf78f69a51539d749}, + {0xdbe6fecebdedd5be, 0xb573440e5a884d1c}, + {0x89705f4136b4a597, 0x31680a88f8953031}, + {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e}, + {0xd6bf94d5e57a42bc, 0x3d32907604691b4d}, + {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110}, + {0xa7c5ac471b478423, 0x0fcf80dc33721d54}, + {0xd1b71758e219652b, 0xd3c36113404ea4a9}, + {0x83126e978d4fdf3b, 0x645a1cac083126ea}, + {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4}, + {0xcccccccccccccccc, 0xcccccccccccccccd}, + {0x8000000000000000, 0x0000000000000000}, + {0xa000000000000000, 0x0000000000000000}, + {0xc800000000000000, 0x0000000000000000}, + {0xfa00000000000000, 0x0000000000000000}, + {0x9c40000000000000, 0x0000000000000000}, + {0xc350000000000000, 0x0000000000000000}, + {0xf424000000000000, 0x0000000000000000}, + {0x9896800000000000, 0x0000000000000000}, + {0xbebc200000000000, 0x0000000000000000}, + {0xee6b280000000000, 0x0000000000000000}, + {0x9502f90000000000, 0x0000000000000000}, + {0xba43b74000000000, 0x0000000000000000}, + {0xe8d4a51000000000, 0x0000000000000000}, + {0x9184e72a00000000, 0x0000000000000000}, + {0xb5e620f480000000, 0x0000000000000000}, + {0xe35fa931a0000000, 0x0000000000000000}, + {0x8e1bc9bf04000000, 0x0000000000000000}, + {0xb1a2bc2ec5000000, 0x0000000000000000}, + {0xde0b6b3a76400000, 0x0000000000000000}, + {0x8ac7230489e80000, 0x0000000000000000}, + {0xad78ebc5ac620000, 0x0000000000000000}, + {0xd8d726b7177a8000, 0x0000000000000000}, + {0x878678326eac9000, 0x0000000000000000}, + {0xa968163f0a57b400, 0x0000000000000000}, + {0xd3c21bcecceda100, 0x0000000000000000}, + {0x84595161401484a0, 0x0000000000000000}, + {0xa56fa5b99019a5c8, 0x0000000000000000}, + {0xcecb8f27f4200f3a, 0x0000000000000000}, + {0x813f3978f8940984, 0x4000000000000000}, + {0xa18f07d736b90be5, 0x5000000000000000}, + {0xc9f2c9cd04674ede, 0xa400000000000000}, + {0xfc6f7c4045812296, 0x4d00000000000000}, + {0x9dc5ada82b70b59d, 0xf020000000000000}, + {0xc5371912364ce305, 0x6c28000000000000}, + {0xf684df56c3e01bc6, 0xc732000000000000}, + {0x9a130b963a6c115c, 0x3c7f400000000000}, + {0xc097ce7bc90715b3, 0x4b9f100000000000}, + {0xf0bdc21abb48db20, 0x1e86d40000000000}, + {0x96769950b50d88f4, 0x1314448000000000}, + {0xbc143fa4e250eb31, 0x17d955a000000000}, + {0xeb194f8e1ae525fd, 0x5dcfab0800000000}, + {0x92efd1b8d0cf37be, 0x5aa1cae500000000}, + {0xb7abc627050305ad, 0xf14a3d9e40000000}, + {0xe596b7b0c643c719, 0x6d9ccd05d0000000}, + {0x8f7e32ce7bea5c6f, 0xe4820023a2000000}, + {0xb35dbf821ae4f38b, 0xdda2802c8a800000}, + {0xe0352f62a19e306e, 0xd50b2037ad200000}, + {0x8c213d9da502de45, 0x4526f422cc340000}, + {0xaf298d050e4395d6, 0x9670b12b7f410000}, + {0xdaf3f04651d47b4c, 0x3c0cdd765f114000}, + {0x88d8762bf324cd0f, 0xa5880a69fb6ac800}, + {0xab0e93b6efee0053, 0x8eea0d047a457a00}, + {0xd5d238a4abe98068, 0x72a4904598d6d880}, + {0x85a36366eb71f041, 0x47a6da2b7f864750}, + {0xa70c3c40a64e6c51, 0x999090b65f67d924}, + {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d}, + {0x82818f1281ed449f, 0xbff8f10e7a8921a5}, + {0xa321f2d7226895c7, 0xaff72d52192b6a0e}, + {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764491}, + {0xfee50b7025c36a08, 0x02f236d04753d5b5}, + {0x9f4f2726179a2245, 0x01d762422c946591}, + {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef6}, + {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb3}, + {0x9b934c3b330c8577, 0x63cc55f49f88eb30}, + {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fc}, + {0xf316271c7fc3908a, 0x8bef464e3945ef7b}, + {0x97edd871cfda3a56, 0x97758bf0e3cbb5ad}, + {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea318}, + {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bde}, + {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6b}, + {0xb975d6b6ee39e436, 0xb3e2fd538e122b45}, + {0xe7d34c64a9c85d44, 0x60dbbca87196b617}, + {0x90e40fbeea1d3a4a, 0xbc8955e946fe31ce}, + {0xb51d13aea4a488dd, 0x6babab6398bdbe42}, + {0xe264589a4dcdab14, 0xc696963c7eed2dd2}, + {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca3}, + {0xb0de65388cc8ada8, 0x3b25a55f43294bcc}, + {0xdd15fe86affad912, 0x49ef0eb713f39ebf}, + {0x8a2dbf142dfcc7ab, 0x6e3569326c784338}, + {0xacb92ed9397bf996, 0x49c2c37f07965405}, + {0xd7e77a8f87daf7fb, 0xdc33745ec97be907}, + {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a4}, + {0xa8acd7c0222311bc, 0xc40832ea0d68ce0d}, + {0xd2d80db02aabd62b, 0xf50a3fa490c30191}, + {0x83c7088e1aab65db, 0x792667c6da79e0fb}, + {0xa4b8cab1a1563f52, 0x577001b891185939}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, + {0x80b05e5ac60b6178, 0x544f8158315b05b5}, + {0xa0dc75f1778e39d6, 0x696361ae3db1c722}, + {0xc913936dd571c84c, 0x03bc3a19cd1e38ea}, + {0xfb5878494ace3a5f, 0x04ab48a04065c724}, + {0x9d174b2dcec0e47b, 0x62eb0d64283f9c77}, + {0xc45d1df942711d9a, 0x3ba5d0bd324f8395}, + {0xf5746577930d6500, 0xca8f44ec7ee3647a}, + {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecc}, + {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67f}, + {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101f}, + {0x95d04aee3b80ece5, 0xbba1f1d158724a13}, + {0xbb445da9ca61281f, 0x2a8a6e45ae8edc98}, + {0xea1575143cf97226, 0xf52d09d71a3293be}, + {0x924d692ca61be758, 0x593c2626705f9c57}, + {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836d}, + {0xe498f455c38b997a, 0x0b6dfb9c0f956448}, + {0x8edf98b59a373fec, 0x4724bd4189bd5ead}, + {0xb2977ee300c50fe7, 0x58edec91ec2cb658}, + {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ee}, + {0x8b865b215899f46c, 0xbd79e0d20082ee75}, + {0xae67f1e9aec07187, 0xecd8590680a3aa12}, + {0xda01ee641a708de9, 0xe80e6f4820cc9496}, + {0x884134fe908658b2, 0x3109058d147fdcde}, + {0xaa51823e34a7eede, 0xbd4b46f0599fd416}, + {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91b}, + {0x850fadc09923329e, 0x03e2cf6bc604ddb1}, + {0xa6539930bf6bff45, 0x84db8346b786151d}, + {0xcfe87f7cef46ff16, 0xe612641865679a64}, + {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07f}, + {0xa26da3999aef7749, 0xe3be5e330f38f09e}, + {0xcb090c8001ab551c, 0x5cadf5bfd3072cc6}, + {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f7}, + {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afb}, + {0xc646d63501a1511d, 0xb281e1fd541501b9}, + {0xf7d88bc24209a565, 0x1f225a7ca91a4227}, + {0x9ae757596946075f, 0x3375788de9b06959}, + {0xc1a12d2fc3978937, 0x0052d6b1641c83af}, + {0xf209787bb47d6b84, 0xc0678c5dbd23a49b}, + {0x9745eb4d50ce6332, 0xf840b7ba963646e1}, + {0xbd176620a501fbff, 0xb650e5a93bc3d899}, + {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebf}, + {0x93ba47c980e98cdf, 0xc66f336c36b10138}, + {0xb8a8d9bbe123f017, 0xb80b0047445d4185}, + {0xe6d3102ad96cec1d, 0xa60dc059157491e6}, + {0x9043ea1ac7e41392, 0x87c89837ad68db30}, + {0xb454e4a179dd1877, 0x29babe4598c311fc}, + {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67b}, + {0x8ce2529e2734bb1d, 0x1899e4a65f58660d}, + {0xb01ae745b101e9e4, 0x5ec05dcff72e7f90}, + {0xdc21a1171d42645d, 0x76707543f4fa1f74}, + {0x899504ae72497eba, 0x6a06494a791c53a9}, + {0xabfa45da0edbde69, 0x0487db9d17636893}, + {0xd6f8d7509292d603, 0x45a9d2845d3c42b7}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, + {0xa7f26836f282b732, 0x8e6cac7768d7141f}, + {0xd1ef0244af2364ff, 0x3207d795430cd927}, + {0x8335616aed761f1f, 0x7f44e6bd49e807b9}, + {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a7}, + {0xcd036837130890a1, 0x36dba887c37a8c10}, + {0x802221226be55a64, 0xc2494954da2c978a}, + {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6d}, + {0xc83553c5c8965d3d, 0x6f92829494e5acc8}, + {0xfa42a8b73abbf48c, 0xcb772339ba1f17fa}, + {0x9c69a97284b578d7, 0xff2a760414536efc}, + {0xc38413cf25e2d70d, 0xfef5138519684abb}, + {0xf46518c2ef5b8cd1, 0x7eb258665fc25d6a}, + {0x98bf2f79d5993802, 0xef2f773ffbd97a62}, + {0xbeeefb584aff8603, 0xaafb550ffacfd8fb}, + {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf39}, + {0x952ab45cfa97a0b2, 0xdd945a747bf26184}, + {0xba756174393d88df, 0x94f971119aeef9e5}, + {0xe912b9d1478ceb17, 0x7a37cd5601aab85e}, + {0x91abb422ccb812ee, 0xac62e055c10ab33b}, + {0xb616a12b7fe617aa, 0x577b986b314d600a}, + {0xe39c49765fdf9d94, 0xed5a7e85fda0b80c}, + {0x8e41ade9fbebc27d, 0x14588f13be847308}, + {0xb1d219647ae6b31c, 0x596eb2d8ae258fc9}, + {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bc}, + {0x8aec23d680043bee, 0x25de7bb9480d5855}, + {0xada72ccc20054ae9, 0xaf561aa79a10ae6b}, + {0xd910f7ff28069da4, 0x1b2ba1518094da05}, + {0x87aa9aff79042286, 0x90fb44d2f05d0843}, + {0xa99541bf57452b28, 0x353a1607ac744a54}, + {0xd3fa922f2d1675f2, 0x42889b8997915ce9}, + {0x847c9b5d7c2e09b7, 0x69956135febada12}, + {0xa59bc234db398c25, 0x43fab9837e699096}, + {0xcf02b2c21207ef2e, 0x94f967e45e03f4bc}, + {0x8161afb94b44f57d, 0x1d1be0eebac278f6}, + {0xa1ba1ba79e1632dc, 0x6462d92a69731733}, + {0xca28a291859bbf93, 0x7d7b8f7503cfdcff}, + {0xfcb2cb35e702af78, 0x5cda735244c3d43f}, + {0x9defbf01b061adab, 0x3a0888136afa64a8}, + {0xc56baec21c7a1916, 0x088aaa1845b8fdd1}, + {0xf6c69a72a3989f5b, 0x8aad549e57273d46}, + {0x9a3c2087a63f6399, 0x36ac54e2f678864c}, + {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7de}, + {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d6}, + {0x969eb7c47859e743, 0x9f644ae5a4b1b326}, + {0xbc4665b596706114, 0x873d5d9f0dde1fef}, + {0xeb57ff22fc0c7959, 0xa90cb506d155a7eb}, + {0x9316ff75dd87cbd8, 0x09a7f12442d588f3}, + {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb30}, + {0xe5d3ef282a242e81, 0x8f1668c8a86da5fb}, + {0x8fa475791a569d10, 0xf96e017d694487bd}, + {0xb38d92d760ec4455, 0x37c981dcc395a9ad}, + {0xe070f78d3927556a, 0x85bbe253f47b1418}, + {0x8c469ab843b89562, 0x93956d7478ccec8f}, + {0xaf58416654a6babb, 0x387ac8d1970027b3}, + {0xdb2e51bfe9d0696a, 0x06997b05fcc0319f}, + {0x88fcf317f22241e2, 0x441fece3bdf81f04}, + {0xab3c2fddeeaad25a, 0xd527e81cad7626c4}, + {0xd60b3bd56a5586f1, 0x8a71e223d8d3b075}, + {0x85c7056562757456, 0xf6872d5667844e4a}, + {0xa738c6bebb12d16c, 0xb428f8ac016561dc}, + {0xd106f86e69d785c7, 0xe13336d701beba53}, + {0x82a45b450226b39c, 0xecc0024661173474}, + {0xa34d721642b06084, 0x27f002d7f95d0191}, + {0xcc20ce9bd35c78a5, 0x31ec038df7b441f5}, + {0xff290242c83396ce, 0x7e67047175a15272}, + {0x9f79a169bd203e41, 0x0f0062c6e984d387}, + {0xc75809c42c684dd1, 0x52c07b78a3e60869}, + {0xf92e0c3537826145, 0xa7709a56ccdf8a83}, + {0x9bbcc7a142b17ccb, 0x88a66076400bb692}, + {0xc2abf989935ddbfe, 0x6acff893d00ea436}, + {0xf356f7ebf83552fe, 0x0583f6b8c4124d44}, + {0x98165af37b2153de, 0xc3727a337a8b704b}, + {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5d}, + {0xeda2ee1c7064130c, 0x1162def06f79df74}, + {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba9}, + {0xb9a74a0637ce2ee1, 0x6d953e2bd7173693}, + {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0438}, + {0x910ab1d4db9914a0, 0x1d9c9892400a22a3}, + {0xb54d5e4a127f59c8, 0x2503beb6d00cab4c}, + {0xe2a0b5dc971f303a, 0x2e44ae64840fd61e}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, + {0xb10d8e1456105dad, 0x7425a83e872c5f48}, + {0xdd50f1996b947518, 0xd12f124e28f7771a}, + {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa70}, + {0xace73cbfdc0bfb7b, 0x636cc64d1001550c}, + {0xd8210befd30efa5a, 0x3c47f7e05401aa4f}, + {0x8714a775e3e95c78, 0x65acfaec34810a72}, + {0xa8d9d1535ce3b396, 0x7f1839a741a14d0e}, + {0xd31045a8341ca07c, 0x1ede48111209a051}, + {0x83ea2b892091e44d, 0x934aed0aab460433}, + {0xa4e4b66b68b65d60, 0xf81da84d56178540}, + {0xce1de40642e3f4b9, 0x36251260ab9d668f}, + {0x80d2ae83e9ce78f3, 0xc1d72b7c6b42601a}, + {0xa1075a24e4421730, 0xb24cf65b8612f820}, + {0xc94930ae1d529cfc, 0xdee033f26797b628}, + {0xfb9b7cd9a4a7443c, 0x169840ef017da3b2}, + {0x9d412e0806e88aa5, 0x8e1f289560ee864f}, + {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e3}, + {0xf5b5d7ec8acb58a2, 0xae10af696774b1dc}, + {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef2a}, + {0xbff610b0cc6edd3f, 0x17fd090a58d32af4}, + {0xeff394dcff8a948e, 0xddfc4b4cef07f5b1}, + {0x95f83d0a1fb69cd9, 0x4abdaf101564f98f}, + {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f2}, + {0xea53df5fd18d5513, 0x84c86189216dc5ee}, + {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb5}, + {0xb7118682dbb66a77, 0x3fbc8c33221dc2a2}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, + {0x8f05b1163ba6832d, 0x29cb4d87f2a7400f}, + {0xb2c71d5bca9023f8, 0x743e20e9ef511013}, + {0xdf78e4b2bd342cf6, 0x914da9246b255417}, + {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548f}, + {0xae9672aba3d0c320, 0xa184ac2473b529b2}, + {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741f}, + {0x8865899617fb1871, 0x7e2fa67c7a658893}, + {0xaa7eebfb9df9de8d, 0xddbb901b98feeab8}, + {0xd51ea6fa85785631, 0x552a74227f3ea566}, + {0x8533285c936b35de, 0xd53a88958f872760}, + {0xa67ff273b8460356, 0x8a892abaf368f138}, + {0xd01fef10a657842c, 0x2d2b7569b0432d86}, + {0x8213f56a67f6b29b, 0x9c3b29620e29fc74}, + {0xa298f2c501f45f42, 0x8349f3ba91b47b90}, + {0xcb3f2f7642717713, 0x241c70a936219a74}, + {0xfe0efb53d30dd4d7, 0xed238cd383aa0111}, + {0x9ec95d1463e8a506, 0xf4363804324a40ab}, + {0xc67bb4597ce2ce48, 0xb143c6053edcd0d6}, + {0xf81aa16fdc1b81da, 0xdd94b7868e94050b}, + {0x9b10a4e5e9913128, 0xca7cf2b4191c8327}, + {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f1}, + {0xf24a01a73cf2dccf, 0xbc633b39673c8ced}, + {0x976e41088617ca01, 0xd5be0503e085d814}, + {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e19}, + {0xec9c459d51852ba2, 0xddf8e7d60ed1219f}, + {0x93e1ab8252f33b45, 0xcabb90e5c942b504}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, + {0xe7109bfba19c0c9d, 0x0cc512670a783ad5}, + {0x906a617d450187e2, 0x27fb2b80668b24c6}, + {0xb484f9dc9641e9da, 0xb1f9f660802dedf7}, + {0xe1a63853bbd26451, 0x5e7873f8a0396974}, + {0x8d07e33455637eb2, 0xdb0b487b6423e1e9}, + {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda63}, + {0xdc5c5301c56b75f7, 0x7641a140cc7810fc}, + {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9e}, + {0xac2820d9623bf429, 0x546345fa9fbdcd45}, + {0xd732290fbacaf133, 0xa97c177947ad4096}, + {0x867f59a9d4bed6c0, 0x49ed8eabcccc485e}, + {0xa81f301449ee8c70, 0x5c68f256bfff5a75}, + {0xd226fc195c6a2f8c, 0x73832eec6fff3112}, + {0x83585d8fd9c25db7, 0xc831fd53c5ff7eac}, + {0xa42e74f3d032f525, 0xba3e7ca8b77f5e56}, + {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35ec}, + {0x80444b5e7aa7cf85, 0x7980d163cf5b81b4}, + {0xa0555e361951c366, 0xd7e105bcc3326220}, + {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa8}, + {0xfa856334878fc150, 0xb14f98f6f0feb952}, + {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d4}, + {0xc3b8358109e84f07, 0x0a862f80ec4700c9}, + {0xf4a642e14c6262c8, 0xcd27bb612758c0fb}, + {0x98e7e9cccfbd7dbd, 0x8038d51cb897789d}, + {0xbf21e44003acdd2c, 0xe0470a63e6bd56c4}, + {0xeeea5d5004981478, 0x1858ccfce06cac75}, + {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, + {0xbaa718e68396cffd, 0xd30560258f54e6bb}, + {0xe950df20247c83fd, 0x47c6b82ef32a206a}, + {0x91d28b7416cdd27e, 0x4cdc331d57fa5442}, + {0xb6472e511c81471d, 0xe0133fe4adf8e953}, + {0xe3d8f9e563a198e5, 0x58180fddd97723a7}, + {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7649}, + {0xb201833b35d63f73, 0x2cd2cc6551e513db}, + {0xde81e40a034bcf4f, 0xf8077f7ea65e58d2}, + {0x8b112e86420f6191, 0xfb04afaf27faf783}, + {0xadd57a27d29339f6, 0x79c5db9af1f9b564}, + {0xd94ad8b1c7380874, 0x18375281ae7822bd}, + {0x87cec76f1c830548, 0x8f2293910d0b15b6}, + {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb23}, + {0xd433179d9c8cb841, 0x5fa60692a46151ec}, + {0x849feec281d7f328, 0xdbc7c41ba6bcd334}, + {0xa5c7ea73224deff3, 0x12b9b522906c0801}, + {0xcf39e50feae16bef, 0xd768226b34870a01}, + {0x81842f29f2cce375, 0xe6a1158300d46641}, + {0xa1e53af46f801c53, 0x60495ae3c1097fd1}, + {0xca5e89b18b602368, 0x385bb19cb14bdfc5}, + {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, + {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, + {0xc5a05277621be293, 0xc7098b7305241886}, + {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8}, + {0x9a65406d44a5c903, 0x737f74f1dc043329}, + {0xc0fe908895cf3b44, 0x505f522e53053ff3}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0}, + {0x96c6e0eab509e64d, 0x5eca783430dc19f6}, + {0xbc789925624c5fe0, 0xb67d16413d132073}, + {0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890}, + {0x933e37a534cbaae7, 0x8e91b962f7b6f15a}, + {0xb80dc58e81fe95a1, 0x723627bbb5a4adb1}, + {0xe61136f2227e3b09, 0xcec3b1aaa30dd91d}, + {0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2}, + {0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e}, + {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, + {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, + {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, + {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2}, +#else + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, + {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, + {0x86a8d39ef77164bc, 0xae5dff9c02033198}, + {0xd98ddaee19068c76, 0x3badd624dd9b0958}, + {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, + {0xe55990879ddcaabd, 0xcc420a6a101d0516}, + {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, + {0x95a8637627989aad, 0xdde7001379a44aa9}, + {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, + {0xc350000000000000, 0x0000000000000000}, + {0x9dc5ada82b70b59d, 0xf020000000000000}, + {0xfee50b7025c36a08, 0x02f236d04753d5b5}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, + {0xa6539930bf6bff45, 0x84db8346b786151d}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, + {0xd910f7ff28069da4, 0x1b2ba1518094da05}, + {0xaf58416654a6babb, 0x387ac8d1970027b3}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, + {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0} +#endif + }; + +#if FMT_USE_FULL_CACHE_DRAGONBOX + return pow10_significands[k - float_info::min_k]; +#else + static constexpr const uint64_t powers_of_5_64[] = { + 0x0000000000000001, 0x0000000000000005, 0x0000000000000019, + 0x000000000000007d, 0x0000000000000271, 0x0000000000000c35, + 0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1, + 0x00000000001dcd65, 0x00000000009502f9, 0x0000000002e90edd, + 0x000000000e8d4a51, 0x0000000048c27395, 0x000000016bcc41e9, + 0x000000071afd498d, 0x0000002386f26fc1, 0x000000b1a2bc2ec5, + 0x000003782dace9d9, 0x00001158e460913d, 0x000056bc75e2d631, + 0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed, + 0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9}; + + static const int compression_ratio = 27; + + // Compute base index. + int cache_index = (k - float_info::min_k) / compression_ratio; + int kb = cache_index * compression_ratio + float_info::min_k; + int offset = k - kb; + + // Get base cache. + uint128_fallback base_cache = pow10_significands[cache_index]; + if (offset == 0) return base_cache; + + // Compute the required amount of bit-shift. + int alpha = floor_log2_pow10(kb + offset) - floor_log2_pow10(kb) - offset; + FMT_ASSERT(alpha > 0 && alpha < 64, "shifting error detected"); + + // Try to recover the real cache. + uint64_t pow5 = powers_of_5_64[offset]; + uint128_fallback recovered_cache = umul128(base_cache.high(), pow5); + uint128_fallback middle_low = umul128(base_cache.low(), pow5); + + recovered_cache += middle_low.high(); + + uint64_t high_to_middle = recovered_cache.high() << (64 - alpha); + uint64_t middle_to_low = recovered_cache.low() << (64 - alpha); + + recovered_cache = + uint128_fallback{(recovered_cache.low() >> alpha) | high_to_middle, + ((middle_low.low() >> alpha) | middle_to_low)}; + FMT_ASSERT(recovered_cache.low() + 1 != 0, ""); + return {recovered_cache.high(), recovered_cache.low() + 1}; +#endif + } + + struct compute_mul_result { + carrier_uint result; + bool is_integer; + }; + struct compute_mul_parity_result { + bool parity; + bool is_integer; + }; + + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { + auto r = umul192_upper128(u, cache); + return {r.high(), r.low() == 0}; + } + + static auto compute_delta(cache_entry_type const& cache, int beta) noexcept + -> uint32_t { + return static_cast(cache.high() >> (64 - 1 - beta)); + } + + static auto compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta) noexcept + -> compute_mul_parity_result { + FMT_ASSERT(beta >= 1, ""); + FMT_ASSERT(beta < 64, ""); + + auto r = umul192_lower128(two_f, cache); + return {((r.high() >> (64 - beta)) & 1) != 0, + ((r.high() << beta) | (r.low() >> (64 - beta))) == 0}; + } + + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return (cache.high() - + (cache.high() >> (num_significand_bits() + 2))) >> + (64 - num_significand_bits() - 1 - beta); + } + + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return (cache.high() + + (cache.high() >> (num_significand_bits() + 1))) >> + (64 - num_significand_bits() - 1 - beta); + } + + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return ((cache.high() >> (64 - num_significand_bits() - 2 - beta)) + + 1) / + 2; + } +}; + +FMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback { + return cache_accessor::get_cached_power(k); +} + +// Various integer checks +template +auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool { + const int case_shorter_interval_left_endpoint_lower_threshold = 2; + const int case_shorter_interval_left_endpoint_upper_threshold = 3; + return exponent >= case_shorter_interval_left_endpoint_lower_threshold && + exponent <= case_shorter_interval_left_endpoint_upper_threshold; +} + +// Remove trailing zeros from n and return the number of zeros removed (float) +FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept { + FMT_ASSERT(n != 0, ""); + // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1. + constexpr uint32_t mod_inv_5 = 0xcccccccd; + constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5 + + while (true) { + auto q = rotr(n * mod_inv_25, 2); + if (q > max_value() / 100) break; + n = q; + s += 2; + } + auto q = rotr(n * mod_inv_5, 1); + if (q <= max_value() / 10) { + n = q; + s |= 1; + } + return s; +} + +// Removes trailing zeros and returns the number of zeros removed (double) +FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept { + FMT_ASSERT(n != 0, ""); + + // This magic number is ceil(2^90 / 10^8). + constexpr uint64_t magic_number = 12379400392853802749ull; + auto nm = umul128(n, magic_number); + + // Is n is divisible by 10^8? + if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) { + // If yes, work with the quotient... + auto n32 = static_cast(nm.high() >> (90 - 64)); + // ... and use the 32 bit variant of the function + int s = remove_trailing_zeros(n32, 8); + n = n32; + return s; + } + + // If n is not divisible by 10^8, work with n itself. + constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd; + constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // mod_inv_5 * mod_inv_5 + + int s = 0; + while (true) { + auto q = rotr(n * mod_inv_25, 2); + if (q > max_value() / 100) break; + n = q; + s += 2; + } + auto q = rotr(n * mod_inv_5, 1); + if (q <= max_value() / 10) { + n = q; + s |= 1; + } + + return s; +} + +// The main algorithm for shorter interval case +template +FMT_INLINE decimal_fp shorter_interval_case(int exponent) noexcept { + decimal_fp ret_value; + // Compute k and beta + const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent); + const int beta = exponent + floor_log2_pow10(-minus_k); + + // Compute xi and zi + using cache_entry_type = typename cache_accessor::cache_entry_type; + const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); + + auto xi = cache_accessor::compute_left_endpoint_for_shorter_interval_case( + cache, beta); + auto zi = cache_accessor::compute_right_endpoint_for_shorter_interval_case( + cache, beta); + + // If the left endpoint is not an integer, increase it + if (!is_left_endpoint_integer_shorter_interval(exponent)) ++xi; + + // Try bigger divisor + ret_value.significand = zi / 10; + + // If succeed, remove trailing zeros if necessary and return + if (ret_value.significand * 10 >= xi) { + ret_value.exponent = minus_k + 1; + ret_value.exponent += remove_trailing_zeros(ret_value.significand); + return ret_value; + } + + // Otherwise, compute the round-up of y + ret_value.significand = + cache_accessor::compute_round_up_for_shorter_interval_case(cache, + beta); + ret_value.exponent = minus_k; + + // When tie occurs, choose one of them according to the rule + if (exponent >= float_info::shorter_interval_tie_lower_threshold && + exponent <= float_info::shorter_interval_tie_upper_threshold) { + ret_value.significand = ret_value.significand % 2 == 0 + ? ret_value.significand + : ret_value.significand - 1; + } else if (ret_value.significand < xi) { + ++ret_value.significand; + } + return ret_value; +} + +template auto to_decimal(T x) noexcept -> decimal_fp { + // Step 1: integer promotion & Schubfach multiplier calculation. + + using carrier_uint = typename float_info::carrier_uint; + using cache_entry_type = typename cache_accessor::cache_entry_type; + auto br = bit_cast(x); + + // Extract significand bits and exponent bits. + const carrier_uint significand_mask = + (static_cast(1) << num_significand_bits()) - 1; + carrier_uint significand = (br & significand_mask); + int exponent = + static_cast((br & exponent_mask()) >> num_significand_bits()); + + if (exponent != 0) { // Check if normal. + exponent -= exponent_bias() + num_significand_bits(); + + // Shorter interval case; proceed like Schubfach. + // In fact, when exponent == 1 and significand == 0, the interval is + // regular. However, it can be shown that the end-results are anyway same. + if (significand == 0) return shorter_interval_case(exponent); + + significand |= (static_cast(1) << num_significand_bits()); + } else { + // Subnormal case; the interval is always regular. + if (significand == 0) return {0, 0}; + exponent = + std::numeric_limits::min_exponent - num_significand_bits() - 1; + } + + const bool include_left_endpoint = (significand % 2 == 0); + const bool include_right_endpoint = include_left_endpoint; + + // Compute k and beta. + const int minus_k = floor_log10_pow2(exponent) - float_info::kappa; + const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); + const int beta = exponent + floor_log2_pow10(-minus_k); + + // Compute zi and deltai. + // 10^kappa <= deltai < 10^(kappa + 1) + const uint32_t deltai = cache_accessor::compute_delta(cache, beta); + const carrier_uint two_fc = significand << 1; + + // For the case of binary32, the result of integer check is not correct for + // 29711844 * 2^-82 + // = 6.1442653300000000008655037797566933477355632930994033813476... * 10^-18 + // and 29711844 * 2^-81 + // = 1.2288530660000000001731007559513386695471126586198806762695... * 10^-17, + // and they are the unique counterexamples. However, since 29711844 is even, + // this does not cause any problem for the endpoints calculations; it can only + // cause a problem when we need to perform integer check for the center. + // Fortunately, with these inputs, that branch is never executed, so we are + // fine. + const typename cache_accessor::compute_mul_result z_mul = + cache_accessor::compute_mul((two_fc | 1) << beta, cache); + + // Step 2: Try larger divisor; remove trailing zeros if necessary. + + // Using an upper bound on zi, we might be able to optimize the division + // better than the compiler; we are computing zi / big_divisor here. + decimal_fp ret_value; + ret_value.significand = divide_by_10_to_kappa_plus_1(z_mul.result); + uint32_t r = static_cast(z_mul.result - float_info::big_divisor * + ret_value.significand); + + if (r < deltai) { + // Exclude the right endpoint if necessary. + if (r == 0 && (z_mul.is_integer & !include_right_endpoint)) { + --ret_value.significand; + r = float_info::big_divisor; + goto small_divisor_case_label; + } + } else if (r > deltai) { + goto small_divisor_case_label; + } else { + // r == deltai; compare fractional parts. + const typename cache_accessor::compute_mul_parity_result x_mul = + cache_accessor::compute_mul_parity(two_fc - 1, cache, beta); + + if (!(x_mul.parity | (x_mul.is_integer & include_left_endpoint))) + goto small_divisor_case_label; + } + ret_value.exponent = minus_k + float_info::kappa + 1; + + // We may need to remove trailing zeros. + ret_value.exponent += remove_trailing_zeros(ret_value.significand); + return ret_value; + + // Step 3: Find the significand with the smaller divisor. + +small_divisor_case_label: + ret_value.significand *= 10; + ret_value.exponent = minus_k + float_info::kappa; + + uint32_t dist = r - (deltai / 2) + (float_info::small_divisor / 2); + const bool approx_y_parity = + ((dist ^ (float_info::small_divisor / 2)) & 1) != 0; + + // Is dist divisible by 10^kappa? + const bool divisible_by_small_divisor = + check_divisibility_and_divide_by_pow10::kappa>(dist); + + // Add dist / 10^kappa to the significand. + ret_value.significand += dist; + + if (!divisible_by_small_divisor) return ret_value; + + // Check z^(f) >= epsilon^(f). + // We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1, + // where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f). + // Since there are only 2 possibilities, we only need to care about the + // parity. Also, zi and r should have the same parity since the divisor + // is an even number. + const auto y_mul = cache_accessor::compute_mul_parity(two_fc, cache, beta); + + // If z^(f) >= epsilon^(f), we might have a tie when z^(f) == epsilon^(f), + // or equivalently, when y is an integer. + if (y_mul.parity != approx_y_parity) + --ret_value.significand; + else if (y_mul.is_integer & (ret_value.significand % 2 != 0)) + --ret_value.significand; + return ret_value; +} +} // namespace dragonbox +} // namespace detail + +template <> struct formatter { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) + -> format_parse_context::iterator { + return ctx.begin(); + } + + auto format(const detail::bigint& n, format_context& ctx) const + -> format_context::iterator { + auto out = ctx.out(); + bool first = true; + for (auto i = n.bigits_.size(); i > 0; --i) { + auto value = n.bigits_[i - 1u]; + if (first) { + out = fmt::format_to(out, FMT_STRING("{:x}"), value); + first = false; + continue; + } + out = fmt::format_to(out, FMT_STRING("{:08x}"), value); + } + if (n.exp_ > 0) + out = fmt::format_to(out, FMT_STRING("p{}"), + n.exp_ * detail::bigint::bigit_bits); + return out; + } +}; + +FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { + for_each_codepoint(s, [this](uint32_t cp, string_view) { + if (cp == invalid_code_point) FMT_THROW(std::runtime_error("invalid utf8")); + if (cp <= 0xFFFF) { + buffer_.push_back(static_cast(cp)); + } else { + cp -= 0x10000; + buffer_.push_back(static_cast(0xD800 + (cp >> 10))); + buffer_.push_back(static_cast(0xDC00 + (cp & 0x3FF))); + } + return true; + }); + buffer_.push_back(0); +} + +FMT_FUNC void format_system_error(detail::buffer& out, int error_code, + const char* message) noexcept { + FMT_TRY { + auto ec = std::error_code(error_code, std::generic_category()); + detail::write(appender(out), std::system_error(ec, message).what()); + return; + } + FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +FMT_FUNC void report_system_error(int error_code, + const char* message) noexcept { + do_report_error(format_system_error, error_code, message); +} + +FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string { + // Don't optimize the "{}" case to keep the binary size small and because it + // can be better optimized in fmt::format anyway. + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + return to_string(buffer); +} + +namespace detail { + +FMT_FUNC void vformat_to(buffer& buf, string_view fmt, format_args args, + locale_ref loc) { + auto out = appender(buf); + if (fmt.size() == 2 && equal2(fmt.data(), "{}")) + return args.get(0).visit(default_arg_formatter{out}); + parse_format_string( + fmt, format_handler{parse_context(fmt), {out, args, loc}}); +} + +template struct span { + T* data; + size_t size; +}; + +template auto flockfile(F* f) -> decltype(_lock_file(f)) { + _lock_file(f); +} +template auto funlockfile(F* f) -> decltype(_unlock_file(f)) { + _unlock_file(f); +} + +#ifndef getc_unlocked +template auto getc_unlocked(F* f) -> decltype(_fgetc_nolock(f)) { + return _fgetc_nolock(f); +} +#endif + +template +struct has_flockfile : std::false_type {}; + +template +struct has_flockfile()))>> + : std::true_type {}; + +// A FILE wrapper. F is FILE defined as a template parameter to make system API +// detection work. +template class file_base { + public: + F* file_; + + public: + file_base(F* file) : file_(file) {} + operator F*() const { return file_; } + + // Reads a code unit from the stream. + auto get() -> int { + int result = getc_unlocked(file_); + if (result == EOF && ferror(file_) != 0) + FMT_THROW(system_error(errno, FMT_STRING("getc failed"))); + return result; + } + + // Puts the code unit back into the stream buffer. + void unget(char c) { + if (ungetc(c, file_) == EOF) + FMT_THROW(system_error(errno, FMT_STRING("ungetc failed"))); + } + + void flush() { fflush(this->file_); } +}; + +// A FILE wrapper for glibc. +template class glibc_file : public file_base { + private: + enum { + line_buffered = 0x200, // _IO_LINE_BUF + unbuffered = 2 // _IO_UNBUFFERED + }; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { + return (this->file_->_flags & unbuffered) == 0; + } + + void init_buffer() { + if (this->file_->_IO_write_ptr) return; + // Force buffer initialization by placing and removing a char in a buffer. + assume(this->file_->_IO_write_ptr >= this->file_->_IO_write_end); + putc_unlocked(0, this->file_); + --this->file_->_IO_write_ptr; + } + + // Returns the file's read buffer. + auto get_read_buffer() const -> span { + auto ptr = this->file_->_IO_read_ptr; + return {ptr, to_unsigned(this->file_->_IO_read_end - ptr)}; + } + + // Returns the file's write buffer. + auto get_write_buffer() const -> span { + auto ptr = this->file_->_IO_write_ptr; + return {ptr, to_unsigned(this->file_->_IO_buf_end - ptr)}; + } + + void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; } + + bool needs_flush() const { + if ((this->file_->_flags & line_buffered) == 0) return false; + char* end = this->file_->_IO_write_end; + return memchr(end, '\n', to_unsigned(this->file_->_IO_write_ptr - end)); + } + + void flush() { fflush_unlocked(this->file_); } +}; + +// A FILE wrapper for Apple's libc. +template class apple_file : public file_base { + private: + enum { + line_buffered = 1, // __SNBF + unbuffered = 2 // __SLBF + }; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { + return (this->file_->_flags & unbuffered) == 0; + } + + void init_buffer() { + if (this->file_->_p) return; + // Force buffer initialization by placing and removing a char in a buffer. + putc_unlocked(0, this->file_); + --this->file_->_p; + ++this->file_->_w; + } + + auto get_read_buffer() const -> span { + return {reinterpret_cast(this->file_->_p), + to_unsigned(this->file_->_r)}; + } + + auto get_write_buffer() const -> span { + return {reinterpret_cast(this->file_->_p), + to_unsigned(this->file_->_bf._base + this->file_->_bf._size - + this->file_->_p)}; + } + + void advance_write_buffer(size_t size) { + this->file_->_p += size; + this->file_->_w -= size; + } + + bool needs_flush() const { + if ((this->file_->_flags & line_buffered) == 0) return false; + return memchr(this->file_->_p + this->file_->_w, '\n', + to_unsigned(-this->file_->_w)); + } +}; + +// A fallback FILE wrapper. +template class fallback_file : public file_base { + private: + char next_; // The next unconsumed character in the buffer. + bool has_next_ = false; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { return false; } + auto needs_flush() const -> bool { return false; } + void init_buffer() {} + + auto get_read_buffer() const -> span { + return {&next_, has_next_ ? 1u : 0u}; + } + + auto get_write_buffer() const -> span { return {nullptr, 0}; } + + void advance_write_buffer(size_t) {} + + auto get() -> int { + has_next_ = false; + return file_base::get(); + } + + void unget(char c) { + file_base::unget(c); + next_ = c; + has_next_ = true; + } +}; + +#ifndef FMT_USE_FALLBACK_FILE +# define FMT_USE_FALLBACK_FILE 0 +#endif + +template +auto get_file(F* f, int) -> apple_file { + return f; +} +template +inline auto get_file(F* f, int) -> glibc_file { + return f; +} + +inline auto get_file(FILE* f, ...) -> fallback_file { return f; } + +using file_ref = decltype(get_file(static_cast(nullptr), 0)); + +template +class file_print_buffer : public buffer { + public: + explicit file_print_buffer(F*) : buffer(nullptr, size_t()) {} +}; + +template +class file_print_buffer::value>> + : public buffer { + private: + file_ref file_; + + static void grow(buffer& base, size_t) { + auto& self = static_cast(base); + self.file_.advance_write_buffer(self.size()); + if (self.file_.get_write_buffer().size == 0) self.file_.flush(); + auto buf = self.file_.get_write_buffer(); + FMT_ASSERT(buf.size > 0, ""); + self.set(buf.data, buf.size); + self.clear(); + } + + public: + explicit file_print_buffer(F* f) : buffer(grow, size_t()), file_(f) { + flockfile(f); + file_.init_buffer(); + auto buf = file_.get_write_buffer(); + set(buf.data, buf.size); + } + ~file_print_buffer() { + file_.advance_write_buffer(size()); + bool flush = file_.needs_flush(); + F* f = file_; // Make funlockfile depend on the template parameter F + funlockfile(f); // for the system API detection to work. + if (flush) fflush(file_); + } +}; + +#if !defined(_WIN32) || defined(FMT_USE_WRITE_CONSOLE) +FMT_FUNC auto write_console(int, string_view) -> bool { return false; } +#else +using dword = conditional_t; +extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // + void*, const void*, dword, dword*, void*); + +FMT_FUNC bool write_console(int fd, string_view text) { + auto u16 = utf8_to_utf16(text); + return WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), + static_cast(u16.size()), nullptr, nullptr) != 0; +} +#endif + +#ifdef _WIN32 +// Print assuming legacy (non-Unicode) encoding. +FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args, + bool newline) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + if (newline) buffer.push_back('\n'); + fwrite_all(buffer.data(), buffer.size(), f); +} +#endif + +FMT_FUNC void print(std::FILE* f, string_view text) { +#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) + int fd = _fileno(f); + if (_isatty(fd)) { + std::fflush(f); + if (write_console(fd, text)) return; + } +#endif + fwrite_all(text.data(), text.size(), f); +} +} // namespace detail + +FMT_FUNC void vprint_buffered(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + detail::print(f, {buffer.data(), buffer.size()}); +} + +FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) { + if (!detail::file_ref(f).is_buffered() || !detail::has_flockfile<>()) + return vprint_buffered(f, fmt, args); + auto&& buffer = detail::file_print_buffer<>(f); + return detail::vformat_to(buffer, fmt, args); +} + +FMT_FUNC void vprintln(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + buffer.push_back('\n'); + detail::print(f, {buffer.data(), buffer.size()}); +} + +FMT_FUNC void vprint(string_view fmt, format_args args) { + vprint(stdout, fmt, args); +} + +namespace detail { + +struct singleton { + unsigned char upper; + unsigned char lower_count; +}; + +inline auto is_printable(uint16_t x, const singleton* singletons, + size_t singletons_size, + const unsigned char* singleton_lowers, + const unsigned char* normal, size_t normal_size) + -> bool { + auto upper = x >> 8; + auto lower_start = 0; + for (size_t i = 0; i < singletons_size; ++i) { + auto s = singletons[i]; + auto lower_end = lower_start + s.lower_count; + if (upper < s.upper) break; + if (upper == s.upper) { + for (auto j = lower_start; j < lower_end; ++j) { + if (singleton_lowers[j] == (x & 0xff)) return false; + } + } + lower_start = lower_end; + } + + auto xsigned = static_cast(x); + auto current = true; + for (size_t i = 0; i < normal_size; ++i) { + auto v = static_cast(normal[i]); + auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v; + xsigned -= len; + if (xsigned < 0) break; + current = !current; + } + return current; +} + +// This code is generated by support/printable.py. +FMT_FUNC auto is_printable(uint32_t cp) -> bool { + static constexpr singleton singletons0[] = { + {0x00, 1}, {0x03, 5}, {0x05, 6}, {0x06, 3}, {0x07, 6}, {0x08, 8}, + {0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13}, + {0x0f, 4}, {0x10, 3}, {0x12, 18}, {0x13, 9}, {0x16, 1}, {0x17, 5}, + {0x18, 2}, {0x19, 3}, {0x1a, 7}, {0x1c, 2}, {0x1d, 1}, {0x1f, 22}, + {0x20, 3}, {0x2b, 3}, {0x2c, 2}, {0x2d, 11}, {0x2e, 1}, {0x30, 3}, + {0x31, 2}, {0x32, 1}, {0xa7, 2}, {0xa9, 2}, {0xaa, 4}, {0xab, 8}, + {0xfa, 2}, {0xfb, 5}, {0xfd, 4}, {0xfe, 3}, {0xff, 9}, + }; + static constexpr unsigned char singletons0_lower[] = { + 0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90, + 0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f, + 0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1, + 0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04, + 0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d, + 0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf, + 0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, + 0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d, + 0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d, + 0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d, + 0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5, + 0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7, + 0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49, + 0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7, + 0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7, + 0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e, + 0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16, + 0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e, + 0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f, + 0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf, + 0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0, + 0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27, + 0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91, + 0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7, + 0xfe, 0xff, + }; + static constexpr singleton singletons1[] = { + {0x00, 6}, {0x01, 1}, {0x03, 1}, {0x04, 2}, {0x08, 8}, {0x09, 2}, + {0x0a, 5}, {0x0b, 2}, {0x0e, 4}, {0x10, 1}, {0x11, 2}, {0x12, 5}, + {0x13, 17}, {0x14, 1}, {0x15, 2}, {0x17, 2}, {0x19, 13}, {0x1c, 5}, + {0x1d, 8}, {0x24, 1}, {0x6a, 3}, {0x6b, 2}, {0xbc, 2}, {0xd1, 2}, + {0xd4, 12}, {0xd5, 9}, {0xd6, 2}, {0xd7, 2}, {0xda, 1}, {0xe0, 5}, + {0xe1, 2}, {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2}, {0xf9, 2}, + {0xfa, 2}, {0xfb, 1}, + }; + static constexpr unsigned char singletons1_lower[] = { + 0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07, + 0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36, + 0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87, + 0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, + 0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b, + 0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9, + 0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66, + 0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27, + 0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc, + 0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7, + 0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6, + 0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c, + 0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66, + 0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0, + 0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93, + }; + static constexpr unsigned char normal0[] = { + 0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04, + 0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0, + 0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01, + 0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03, + 0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03, + 0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a, + 0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15, + 0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f, + 0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80, + 0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07, + 0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06, + 0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04, + 0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac, + 0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c, + 0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11, + 0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c, + 0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b, + 0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6, + 0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03, + 0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80, + 0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06, + 0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c, + 0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17, + 0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80, + 0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80, + 0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d, + }; + static constexpr unsigned char normal1[] = { + 0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f, + 0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e, + 0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04, + 0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09, + 0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16, + 0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f, + 0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36, + 0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33, + 0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08, + 0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e, + 0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41, + 0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03, + 0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22, + 0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04, + 0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45, + 0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03, + 0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81, + 0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75, + 0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1, + 0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a, + 0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11, + 0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09, + 0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89, + 0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6, + 0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09, + 0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50, + 0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05, + 0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83, + 0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05, + 0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80, + 0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80, + 0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07, + 0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e, + 0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07, + 0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06, + }; + auto lower = static_cast(cp); + if (cp < 0x10000) { + return is_printable(lower, singletons0, + sizeof(singletons0) / sizeof(*singletons0), + singletons0_lower, normal0, sizeof(normal0)); + } + if (cp < 0x20000) { + return is_printable(lower, singletons1, + sizeof(singletons1) / sizeof(*singletons1), + singletons1_lower, normal1, sizeof(normal1)); + } + if (0x2a6de <= cp && cp < 0x2a700) return false; + if (0x2b735 <= cp && cp < 0x2b740) return false; + if (0x2b81e <= cp && cp < 0x2b820) return false; + if (0x2cea2 <= cp && cp < 0x2ceb0) return false; + if (0x2ebe1 <= cp && cp < 0x2f800) return false; + if (0x2fa1e <= cp && cp < 0x30000) return false; + if (0x3134b <= cp && cp < 0xe0100) return false; + if (0xe01f0 <= cp && cp < 0x110000) return false; + return cp < 0x110000; +} + +} // namespace detail + +FMT_END_NAMESPACE + +#endif // FMT_FORMAT_INL_H_ diff --git a/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/format.h b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/format.h new file mode 100644 index 000000000..d1b83d186 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/format.h @@ -0,0 +1,4220 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - present, Victor Zverovich + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + --- Optional exception to the license --- + + As an exception, if, as a result of your compiling your source code, portions + of this Software are embedded into a machine-executable object form of such + source code, you may redistribute such embedded portions in such object form + without including the above copyright and permission notices. + */ + +#ifndef FMT_FORMAT_H_ +#define FMT_FORMAT_H_ + +#ifndef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +# define _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +# define FMT_REMOVE_TRANSITIVE_INCLUDES +#endif + +#include "base.h" + +#ifndef FMT_MODULE +# include // std::signbit +# include // std::byte +# include // uint32_t +# include // std::memcpy +# include // std::numeric_limits +# include // std::bad_alloc +# if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI) +// Workaround for pre gcc 5 libstdc++. +# include // std::allocator_traits +# endif +# include // std::runtime_error +# include // std::string +# include // std::system_error + +// Check FMT_CPLUSPLUS to avoid a warning in MSVC. +# if FMT_HAS_INCLUDE() && FMT_CPLUSPLUS > 201703L +# include // std::bit_cast +# endif + +// libc++ supports string_view in pre-c++17. +# if FMT_HAS_INCLUDE() && \ + (FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION)) +# include +# define FMT_USE_STRING_VIEW +# endif + +# if FMT_MSC_VERSION +# include // _BitScanReverse[64], _umul128 +# endif +#endif // FMT_MODULE + +#if defined(FMT_USE_NONTYPE_TEMPLATE_ARGS) +// Use the provided definition. +#elif defined(__NVCOMPILER) +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +#elif FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#elif defined(__cpp_nontype_template_args) && \ + __cpp_nontype_template_args >= 201911L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#elif FMT_CLANG_VERSION >= 1200 && FMT_CPLUSPLUS >= 202002L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#else +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +#endif + +#if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L +# define FMT_INLINE_VARIABLE inline +#else +# define FMT_INLINE_VARIABLE +#endif + +// Check if RTTI is disabled. +#ifdef FMT_USE_RTTI +// Use the provided definition. +#elif defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || defined(_CPPRTTI) || \ + defined(__INTEL_RTTI__) || defined(__RTTI) +// __RTTI is for EDG compilers. _CPPRTTI is for MSVC. +# define FMT_USE_RTTI 1 +#else +# define FMT_USE_RTTI 0 +#endif + +// Visibility when compiled as a shared library/object. +#if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_SO_VISIBILITY(value) FMT_VISIBILITY(value) +#else +# define FMT_SO_VISIBILITY(value) +#endif + +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_NOINLINE __attribute__((noinline)) +#else +# define FMT_NOINLINE +#endif + +namespace std { +template struct iterator_traits> { + using iterator_category = output_iterator_tag; + using value_type = T; + using difference_type = + decltype(static_cast(nullptr) - static_cast(nullptr)); + using pointer = void; + using reference = void; +}; +} // namespace std + +#ifndef FMT_THROW +# if FMT_USE_EXCEPTIONS +# if FMT_MSC_VERSION || defined(__NVCC__) +FMT_BEGIN_NAMESPACE +namespace detail { +template inline void do_throw(const Exception& x) { + // Silence unreachable code warnings in MSVC and NVCC because these + // are nearly impossible to fix in a generic code. + volatile bool b = true; + if (b) throw x; +} +} // namespace detail +FMT_END_NAMESPACE +# define FMT_THROW(x) detail::do_throw(x) +# else +# define FMT_THROW(x) throw x +# endif +# else +# define FMT_THROW(x) \ + ::fmt::detail::assert_fail(__FILE__, __LINE__, (x).what()) +# endif // FMT_USE_EXCEPTIONS +#endif // FMT_THROW + +// Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of +// integer formatter template instantiations to just one by only using the +// largest integer type. This results in a reduction in binary size but will +// cause a decrease in integer formatting performance. +#if !defined(FMT_REDUCE_INT_INSTANTIATIONS) +# define FMT_REDUCE_INT_INSTANTIATIONS 0 +#endif + +FMT_BEGIN_NAMESPACE + +template +struct is_contiguous> + : std::true_type {}; + +namespace detail { + +// __builtin_clz is broken in clang with Microsoft codegen: +// https://github.com/fmtlib/fmt/issues/519. +#if !FMT_MSC_VERSION +# if FMT_HAS_BUILTIN(__builtin_clz) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +# endif +# if FMT_HAS_BUILTIN(__builtin_clzll) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +# endif +#endif + +// Some compilers masquerade as both MSVC and GCC but otherwise support +// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the +// MSVC intrinsics if the clz and clzll builtins are not available. +#if FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) +// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. +# ifndef __clang__ +# pragma intrinsic(_BitScanReverse) +# ifdef _WIN64 +# pragma intrinsic(_BitScanReverse64) +# endif +# endif + +inline auto clz(uint32_t x) -> int { + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. + unsigned long r = 0; + _BitScanReverse(&r, x); + return 31 ^ static_cast(r); +} +# define FMT_BUILTIN_CLZ(n) detail::clz(n) + +inline auto clzll(uint64_t x) -> int { + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. + unsigned long r = 0; +# ifdef _WIN64 + _BitScanReverse64(&r, x); +# else + // Scan the high 32 bits. + if (_BitScanReverse(&r, static_cast(x >> 32))) + return 63 ^ static_cast(r + 32); + // Scan the low 32 bits. + _BitScanReverse(&r, static_cast(x)); +# endif + return 63 ^ static_cast(r); +} +# define FMT_BUILTIN_CLZLL(n) detail::clzll(n) +#endif // FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) + +FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { + ignore_unused(condition); +#ifdef FMT_FUZZ + if (condition) throw std::runtime_error("fuzzing limit reached"); +#endif +} + +#if defined(FMT_USE_STRING_VIEW) +template using std_string_view = std::basic_string_view; +#else +template struct std_string_view {}; +#endif + +template struct string_literal { + static constexpr Char value[sizeof...(C)] = {C...}; + constexpr operator basic_string_view() const { + return {value, sizeof...(C)}; + } +}; +#if FMT_CPLUSPLUS < 201703L +template +constexpr Char string_literal::value[sizeof...(C)]; +#endif + +// Implementation of std::bit_cast for pre-C++20. +template +FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { +#ifdef __cpp_lib_bit_cast + if (is_constant_evaluated()) return std::bit_cast(from); +#endif + auto to = To(); + // The cast suppresses a bogus -Wclass-memaccess on GCC. + std::memcpy(static_cast(&to), &from, sizeof(to)); + return to; +} + +inline auto is_big_endian() -> bool { +#ifdef _WIN32 + return false; +#elif defined(__BIG_ENDIAN__) + return true; +#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) + return __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; +#else + struct bytes { + char data[sizeof(int)]; + }; + return bit_cast(1).data[0] == 0; +#endif +} + +class uint128_fallback { + private: + uint64_t lo_, hi_; + + public: + constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} + constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {} + + constexpr auto high() const noexcept -> uint64_t { return hi_; } + constexpr auto low() const noexcept -> uint64_t { return lo_; } + + template ::value)> + constexpr explicit operator T() const { + return static_cast(lo_); + } + + friend constexpr auto operator==(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return lhs.hi_ == rhs.hi_ && lhs.lo_ == rhs.lo_; + } + friend constexpr auto operator!=(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return !(lhs == rhs); + } + friend constexpr auto operator>(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return lhs.hi_ != rhs.hi_ ? lhs.hi_ > rhs.hi_ : lhs.lo_ > rhs.lo_; + } + friend constexpr auto operator|(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + return {lhs.hi_ | rhs.hi_, lhs.lo_ | rhs.lo_}; + } + friend constexpr auto operator&(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_}; + } + friend constexpr auto operator~(const uint128_fallback& n) + -> uint128_fallback { + return {~n.hi_, ~n.lo_}; + } + friend FMT_CONSTEXPR auto operator+(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + auto result = uint128_fallback(lhs); + result += rhs; + return result; + } + friend FMT_CONSTEXPR auto operator*(const uint128_fallback& lhs, uint32_t rhs) + -> uint128_fallback { + FMT_ASSERT(lhs.hi_ == 0, ""); + uint64_t hi = (lhs.lo_ >> 32) * rhs; + uint64_t lo = (lhs.lo_ & ~uint32_t()) * rhs; + uint64_t new_lo = (hi << 32) + lo; + return {(hi >> 32) + (new_lo < lo ? 1 : 0), new_lo}; + } + friend constexpr auto operator-(const uint128_fallback& lhs, uint64_t rhs) + -> uint128_fallback { + return {lhs.hi_ - (lhs.lo_ < rhs ? 1 : 0), lhs.lo_ - rhs}; + } + FMT_CONSTEXPR auto operator>>(int shift) const -> uint128_fallback { + if (shift == 64) return {0, hi_}; + if (shift > 64) return uint128_fallback(0, hi_) >> (shift - 64); + return {hi_ >> shift, (hi_ << (64 - shift)) | (lo_ >> shift)}; + } + FMT_CONSTEXPR auto operator<<(int shift) const -> uint128_fallback { + if (shift == 64) return {lo_, 0}; + if (shift > 64) return uint128_fallback(lo_, 0) << (shift - 64); + return {hi_ << shift | (lo_ >> (64 - shift)), (lo_ << shift)}; + } + FMT_CONSTEXPR auto operator>>=(int shift) -> uint128_fallback& { + return *this = *this >> shift; + } + FMT_CONSTEXPR void operator+=(uint128_fallback n) { + uint64_t new_lo = lo_ + n.lo_; + uint64_t new_hi = hi_ + n.hi_ + (new_lo < lo_ ? 1 : 0); + FMT_ASSERT(new_hi >= hi_, ""); + lo_ = new_lo; + hi_ = new_hi; + } + FMT_CONSTEXPR void operator&=(uint128_fallback n) { + lo_ &= n.lo_; + hi_ &= n.hi_; + } + + FMT_CONSTEXPR20 auto operator+=(uint64_t n) noexcept -> uint128_fallback& { + if (is_constant_evaluated()) { + lo_ += n; + hi_ += (lo_ < n ? 1 : 0); + return *this; + } +#if FMT_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__) + unsigned long long carry; + lo_ = __builtin_addcll(lo_, n, 0, &carry); + hi_ += carry; +#elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64) && !defined(__ibmxl__) + unsigned long long result; + auto carry = __builtin_ia32_addcarryx_u64(0, lo_, n, &result); + lo_ = result; + hi_ += carry; +#elif defined(_MSC_VER) && defined(_M_X64) + auto carry = _addcarry_u64(0, lo_, n, &lo_); + _addcarry_u64(carry, hi_, 0, &hi_); +#else + lo_ += n; + hi_ += (lo_ < n ? 1 : 0); +#endif + return *this; + } +}; + +using uint128_t = conditional_t; + +#ifdef UINTPTR_MAX +using uintptr_t = ::uintptr_t; +#else +using uintptr_t = uint128_t; +#endif + +// Returns the largest possible value for type T. Same as +// std::numeric_limits::max() but shorter and not affected by the max macro. +template constexpr auto max_value() -> T { + return (std::numeric_limits::max)(); +} +template constexpr auto num_bits() -> int { + return std::numeric_limits::digits; +} +// std::numeric_limits::digits may return 0 for 128-bit ints. +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } + +// A heterogeneous bit_cast used for converting 96-bit long double to uint128_t +// and 128-bit pointers to uint128_fallback. +template sizeof(From))> +inline auto bit_cast(const From& from) -> To { + constexpr auto size = static_cast(sizeof(From) / sizeof(unsigned short)); + struct data_t { + unsigned short value[static_cast(size)]; + } data = bit_cast(from); + auto result = To(); + if (const_check(is_big_endian())) { + for (int i = 0; i < size; ++i) + result = (result << num_bits()) | data.value[i]; + } else { + for (int i = size - 1; i >= 0; --i) + result = (result << num_bits()) | data.value[i]; + } + return result; +} + +template +FMT_CONSTEXPR20 inline auto countl_zero_fallback(UInt n) -> int { + int lz = 0; + constexpr UInt msb_mask = static_cast(1) << (num_bits() - 1); + for (; (n & msb_mask) == 0; n <<= 1) lz++; + return lz; +} + +FMT_CONSTEXPR20 inline auto countl_zero(uint32_t n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated()) return FMT_BUILTIN_CLZ(n); +#endif + return countl_zero_fallback(n); +} + +FMT_CONSTEXPR20 inline auto countl_zero(uint64_t n) -> int { +#ifdef FMT_BUILTIN_CLZLL + if (!is_constant_evaluated()) return FMT_BUILTIN_CLZLL(n); +#endif + return countl_zero_fallback(n); +} + +FMT_INLINE void assume(bool condition) { + (void)condition; +#if FMT_HAS_BUILTIN(__builtin_assume) && !FMT_ICC_VERSION + __builtin_assume(condition); +#elif FMT_GCC_VERSION + if (!condition) __builtin_unreachable(); +#endif +} + +// Attempts to reserve space for n extra characters in the output range. +// Returns a pointer to the reserved range or a reference to it. +template ::value&& + is_contiguous::value)> +#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION +__attribute__((no_sanitize("undefined"))) +#endif +FMT_CONSTEXPR20 inline auto +reserve(OutputIt it, size_t n) -> typename OutputIt::value_type* { + auto& c = get_container(it); + size_t size = c.size(); + c.resize(size + n); + return &c[size]; +} + +template +FMT_CONSTEXPR20 inline auto reserve(basic_appender it, size_t n) + -> basic_appender { + buffer& buf = get_container(it); + buf.try_reserve(buf.size() + n); + return it; +} + +template +constexpr auto reserve(Iterator& it, size_t) -> Iterator& { + return it; +} + +template +using reserve_iterator = + remove_reference_t(), 0))>; + +template +constexpr auto to_pointer(OutputIt, size_t) -> T* { + return nullptr; +} +template +FMT_CONSTEXPR20 auto to_pointer(basic_appender it, size_t n) -> T* { + buffer& buf = get_container(it); + buf.try_reserve(buf.size() + n); + auto size = buf.size(); + if (buf.capacity() < size + n) return nullptr; + buf.try_resize(size + n); + return buf.data() + size; +} + +template ::value&& + is_contiguous::value)> +inline auto base_iterator(OutputIt it, + typename OutputIt::container_type::value_type*) + -> OutputIt { + return it; +} + +template +constexpr auto base_iterator(Iterator, Iterator it) -> Iterator { + return it; +} + +// is spectacularly slow to compile in C++20 so use a simple fill_n +// instead (#1998). +template +FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value) + -> OutputIt { + for (Size i = 0; i < count; ++i) *out++ = value; + return out; +} +template +FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { + if (is_constant_evaluated()) return fill_n(out, count, value); + std::memset(out, value, to_unsigned(count)); + return out + count; +} + +template +FMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end, + OutputIt out) -> OutputIt { + return copy(begin, end, out); +} + +// A public domain branchless UTF-8 decoder by Christopher Wellons: +// https://github.com/skeeto/branchless-utf8 +/* Decode the next character, c, from s, reporting errors in e. + * + * Since this is a branchless decoder, four bytes will be read from the + * buffer regardless of the actual length of the next character. This + * means the buffer _must_ have at least three bytes of zero padding + * following the end of the data stream. + * + * Errors are reported in e, which will be non-zero if the parsed + * character was somehow invalid: invalid byte sequence, non-canonical + * encoding, or a surrogate half. + * + * The function returns a pointer to the next character. When an error + * occurs, this pointer will be a guess that depends on the particular + * error, but it will always advance at least one byte. + */ +FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) + -> const char* { + constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; + constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; + constexpr const int shiftc[] = {0, 18, 12, 6, 0}; + constexpr const int shifte[] = {0, 6, 4, 2, 0}; + + int len = "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4" + [static_cast(*s) >> 3]; + // Compute the pointer to the next character early so that the next + // iteration can start working on the next character. Neither Clang + // nor GCC figure out this reordering on their own. + const char* next = s + len + !len; + + using uchar = unsigned char; + + // Assume a four-byte character and load four bytes. Unused bits are + // shifted out. + *c = uint32_t(uchar(s[0]) & masks[len]) << 18; + *c |= uint32_t(uchar(s[1]) & 0x3f) << 12; + *c |= uint32_t(uchar(s[2]) & 0x3f) << 6; + *c |= uint32_t(uchar(s[3]) & 0x3f) << 0; + *c >>= shiftc[len]; + + // Accumulate the various error conditions. + *e = (*c < mins[len]) << 6; // non-canonical encoding + *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? + *e |= (*c > 0x10FFFF) << 8; // out of range? + *e |= (uchar(s[1]) & 0xc0) >> 2; + *e |= (uchar(s[2]) & 0xc0) >> 4; + *e |= uchar(s[3]) >> 6; + *e ^= 0x2a; // top two bits of each tail byte correct? + *e >>= shifte[len]; + + return next; +} + +constexpr FMT_INLINE_VARIABLE uint32_t invalid_code_point = ~uint32_t(); + +// Invokes f(cp, sv) for every code point cp in s with sv being the string view +// corresponding to the code point. cp is invalid_code_point on error. +template +FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { + auto decode = [f](const char* buf_ptr, const char* ptr) { + auto cp = uint32_t(); + auto error = 0; + auto end = utf8_decode(buf_ptr, &cp, &error); + bool result = f(error ? invalid_code_point : cp, + string_view(ptr, error ? 1 : to_unsigned(end - buf_ptr))); + return result ? (error ? buf_ptr + 1 : end) : nullptr; + }; + + auto p = s.data(); + const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. + if (s.size() >= block_size) { + for (auto end = p + s.size() - block_size + 1; p < end;) { + p = decode(p, p); + if (!p) return; + } + } + auto num_chars_left = to_unsigned(s.data() + s.size() - p); + if (num_chars_left == 0) return; + + // Suppress bogus -Wstringop-overflow. + if (FMT_GCC_VERSION) num_chars_left &= 3; + char buf[2 * block_size - 1] = {}; + copy(p, p + num_chars_left, buf); + const char* buf_ptr = buf; + do { + auto end = decode(buf_ptr, p); + if (!end) return; + p += end - buf_ptr; + buf_ptr = end; + } while (buf_ptr < buf + num_chars_left); +} + +template +inline auto compute_width(basic_string_view s) -> size_t { + return s.size(); +} + +// Computes approximate display width of a UTF-8 string. +FMT_CONSTEXPR inline auto compute_width(string_view s) -> size_t { + size_t num_code_points = 0; + // It is not a lambda for compatibility with C++14. + struct count_code_points { + size_t* count; + FMT_CONSTEXPR auto operator()(uint32_t cp, string_view) const -> bool { + *count += to_unsigned( + 1 + + (cp >= 0x1100 && + (cp <= 0x115f || // Hangul Jamo init. consonants + cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET + cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET + // CJK ... Yi except IDEOGRAPHIC HALF FILL SPACE: + (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) || + (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables + (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs + (cp >= 0xfe10 && cp <= 0xfe19) || // Vertical Forms + (cp >= 0xfe30 && cp <= 0xfe6f) || // CJK Compatibility Forms + (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms + (cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth Forms + (cp >= 0x20000 && cp <= 0x2fffd) || // CJK + (cp >= 0x30000 && cp <= 0x3fffd) || + // Miscellaneous Symbols and Pictographs + Emoticons: + (cp >= 0x1f300 && cp <= 0x1f64f) || + // Supplemental Symbols and Pictographs: + (cp >= 0x1f900 && cp <= 0x1f9ff)))); + return true; + } + }; + // We could avoid branches by using utf8_decode directly. + for_each_codepoint(s, count_code_points{&num_code_points}); + return num_code_points; +} + +template +inline auto code_point_index(basic_string_view s, size_t n) -> size_t { + return min_of(n, s.size()); +} + +// Calculates the index of the nth code point in a UTF-8 string. +inline auto code_point_index(string_view s, size_t n) -> size_t { + size_t result = s.size(); + const char* begin = s.begin(); + for_each_codepoint(s, [begin, &n, &result](uint32_t, string_view sv) { + if (n != 0) { + --n; + return true; + } + result = to_unsigned(sv.begin() - begin); + return false; + }); + return result; +} + +template struct is_integral : std::is_integral {}; +template <> struct is_integral : std::true_type {}; +template <> struct is_integral : std::true_type {}; + +template +using is_signed = + std::integral_constant::is_signed || + std::is_same::value>; + +template +using is_integer = + bool_constant::value && !std::is_same::value && + !std::is_same::value && + !std::is_same::value>; + +#if defined(FMT_USE_FLOAT128) +// Use the provided definition. +#elif FMT_CLANG_VERSION && FMT_HAS_INCLUDE() +# define FMT_USE_FLOAT128 1 +#elif FMT_GCC_VERSION && defined(_GLIBCXX_USE_FLOAT128) && \ + !defined(__STRICT_ANSI__) +# define FMT_USE_FLOAT128 1 +#else +# define FMT_USE_FLOAT128 0 +#endif +#if FMT_USE_FLOAT128 +using float128 = __float128; +#else +struct float128 {}; +#endif + +template using is_float128 = std::is_same; + +template +using is_floating_point = + bool_constant::value || is_float128::value>; + +template ::value> +struct is_fast_float : bool_constant::is_iec559 && + sizeof(T) <= sizeof(double)> {}; +template struct is_fast_float : std::false_type {}; + +template +using is_double_double = bool_constant::digits == 106>; + +#ifndef FMT_USE_FULL_CACHE_DRAGONBOX +# define FMT_USE_FULL_CACHE_DRAGONBOX 0 +#endif + +// An allocator that uses malloc/free to allow removing dependency on the C++ +// standard libary runtime. +template struct allocator { + using value_type = T; + + T* allocate(size_t n) { + FMT_ASSERT(n <= max_value() / sizeof(T), ""); + T* p = static_cast(malloc(n * sizeof(T))); + if (!p) FMT_THROW(std::bad_alloc()); + return p; + } + + void deallocate(T* p, size_t) { free(p); } +}; + +} // namespace detail + +FMT_BEGIN_EXPORT + +// The number of characters to store in the basic_memory_buffer object itself +// to avoid dynamic memory allocation. +enum { inline_buffer_size = 500 }; + +/** + * A dynamically growing memory buffer for trivially copyable/constructible + * types with the first `SIZE` elements stored in the object itself. Most + * commonly used via the `memory_buffer` alias for `char`. + * + * **Example**: + * + * auto out = fmt::memory_buffer(); + * fmt::format_to(std::back_inserter(out), "The answer is {}.", 42); + * + * This will append "The answer is 42." to `out`. The buffer content can be + * converted to `std::string` with `to_string(out)`. + */ +template > +class basic_memory_buffer : public detail::buffer { + private: + T store_[SIZE]; + + // Don't inherit from Allocator to avoid generating type_info for it. + FMT_NO_UNIQUE_ADDRESS Allocator alloc_; + + // Deallocate memory allocated by the buffer. + FMT_CONSTEXPR20 void deallocate() { + T* data = this->data(); + if (data != store_) alloc_.deallocate(data, this->capacity()); + } + + static FMT_CONSTEXPR20 void grow(detail::buffer& buf, size_t size) { + detail::abort_fuzzing_if(size > 5000); + auto& self = static_cast(buf); + const size_t max_size = + std::allocator_traits::max_size(self.alloc_); + size_t old_capacity = buf.capacity(); + size_t new_capacity = old_capacity + old_capacity / 2; + if (size > new_capacity) + new_capacity = size; + else if (new_capacity > max_size) + new_capacity = max_of(size, max_size); + T* old_data = buf.data(); + T* new_data = self.alloc_.allocate(new_capacity); + // Suppress a bogus -Wstringop-overflow in gcc 13.1 (#3481). + detail::assume(buf.size() <= new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + memcpy(new_data, old_data, buf.size() * sizeof(T)); + self.set(new_data, new_capacity); + // deallocate must not throw according to the standard, but even if it does, + // the buffer already uses the new storage and will deallocate it in + // destructor. + if (old_data != self.store_) self.alloc_.deallocate(old_data, old_capacity); + } + + public: + using value_type = T; + using const_reference = const T&; + + FMT_CONSTEXPR explicit basic_memory_buffer( + const Allocator& alloc = Allocator()) + : detail::buffer(grow), alloc_(alloc) { + this->set(store_, SIZE); + if (detail::is_constant_evaluated()) detail::fill_n(store_, SIZE, T()); + } + FMT_CONSTEXPR20 ~basic_memory_buffer() { deallocate(); } + + private: + // Move data from other to this buffer. + FMT_CONSTEXPR20 void move(basic_memory_buffer& other) { + alloc_ = std::move(other.alloc_); + T* data = other.data(); + size_t size = other.size(), capacity = other.capacity(); + if (data == other.store_) { + this->set(store_, capacity); + detail::copy(other.store_, other.store_ + size, store_); + } else { + this->set(data, capacity); + // Set pointer to the inline array so that delete is not called + // when deallocating. + other.set(other.store_, 0); + other.clear(); + } + this->resize(size); + } + + public: + /// Constructs a `basic_memory_buffer` object moving the content of the other + /// object to it. + FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other) noexcept + : detail::buffer(grow) { + move(other); + } + + /// Moves the content of the other `basic_memory_buffer` object to this one. + auto operator=(basic_memory_buffer&& other) noexcept -> basic_memory_buffer& { + FMT_ASSERT(this != &other, ""); + deallocate(); + move(other); + return *this; + } + + // Returns a copy of the allocator associated with this buffer. + auto get_allocator() const -> Allocator { return alloc_; } + + /// Resizes the buffer to contain `count` elements. If T is a POD type new + /// elements may not be initialized. + FMT_CONSTEXPR void resize(size_t count) { this->try_resize(count); } + + /// Increases the buffer capacity to `new_capacity`. + void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } + + using detail::buffer::append; + template + FMT_CONSTEXPR20 void append(const ContiguousRange& range) { + append(range.data(), range.data() + range.size()); + } +}; + +using memory_buffer = basic_memory_buffer; + +template +FMT_NODISCARD auto to_string(const basic_memory_buffer& buf) + -> std::string { + auto size = buf.size(); + detail::assume(size < std::string().max_size()); + return {buf.data(), size}; +} + +// A writer to a buffered stream. It doesn't own the underlying stream. +class writer { + private: + detail::buffer* buf_; + + // We cannot create a file buffer in advance because any write to a FILE may + // invalidate it. + FILE* file_; + + public: + inline writer(FILE* f) : buf_(nullptr), file_(f) {} + inline writer(detail::buffer& buf) : buf_(&buf) {} + + /// Formats `args` according to specifications in `fmt` and writes the + /// output to the file. + template void print(format_string fmt, T&&... args) { + if (buf_) + fmt::format_to(appender(*buf_), fmt, std::forward(args)...); + else + fmt::print(file_, fmt, std::forward(args)...); + } +}; + +class string_buffer { + private: + std::string str_; + detail::container_buffer buf_; + + public: + inline string_buffer() : buf_(str_) {} + + inline operator writer() { return buf_; } + inline std::string& str() { return str_; } +}; + +template +struct is_contiguous> : std::true_type { +}; + +// Suppress a misleading warning in older versions of clang. +FMT_PRAGMA_CLANG(diagnostic ignored "-Wweak-vtables") + +/// An error reported from a formatting function. +class FMT_SO_VISIBILITY("default") format_error : public std::runtime_error { + public: + using std::runtime_error::runtime_error; +}; + +class loc_value; + +FMT_END_EXPORT +namespace detail { +FMT_API auto write_console(int fd, string_view text) -> bool; +FMT_API void print(FILE*, string_view); +} // namespace detail + +namespace detail { +template struct fixed_string { + FMT_CONSTEXPR20 fixed_string(const Char (&s)[N]) { + detail::copy(static_cast(s), s + N, + data); + } + Char data[N] = {}; +}; + +// Converts a compile-time string to basic_string_view. +FMT_EXPORT template +constexpr auto compile_string_to_view(const Char (&s)[N]) + -> basic_string_view { + // Remove trailing NUL character if needed. Won't be present if this is used + // with a raw character array (i.e. not defined as a string). + return {s, N - (std::char_traits::to_int_type(s[N - 1]) == 0 ? 1 : 0)}; +} +FMT_EXPORT template +constexpr auto compile_string_to_view(basic_string_view s) + -> basic_string_view { + return s; +} + +// Returns true if value is negative, false otherwise. +// Same as `value < 0` but doesn't produce warnings if T is an unsigned type. +template ::value)> +constexpr auto is_negative(T value) -> bool { + return value < 0; +} +template ::value)> +constexpr auto is_negative(T) -> bool { + return false; +} + +// Smallest of uint32_t, uint64_t, uint128_t that is large enough to +// represent all values of an integral type T. +template +using uint32_or_64_or_128_t = + conditional_t() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS, + uint32_t, + conditional_t() <= 64, uint64_t, uint128_t>>; +template +using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, (factor) * 100, (factor) * 1000, (factor) * 10000, \ + (factor) * 100000, (factor) * 1000000, (factor) * 10000000, \ + (factor) * 100000000, (factor) * 1000000000 + +// Converts value in the range [0, 100) to a string. +// GCC generates slightly better code when value is pointer-size. +inline auto digits2(size_t value) -> const char* { + // Align data since unaligned access may be slower when crossing a + // hardware-specific boundary. + alignas(2) static const char data[] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + return &data[value * 2]; +} + +template constexpr auto getsign(sign s) -> Char { + return static_cast(((' ' << 24) | ('+' << 16) | ('-' << 8)) >> + (static_cast(s) * 8)); +} + +template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { + int count = 1; + for (;;) { + // Integer division is slow so do it for a group of four digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + if (n < 10) return count; + if (n < 100) return count + 1; + if (n < 1000) return count + 2; + if (n < 10000) return count + 3; + n /= 10000u; + count += 4; + } +} +#if FMT_USE_INT128 +FMT_CONSTEXPR inline auto count_digits(uint128_opt n) -> int { + return count_digits_fallback(n); +} +#endif + +#ifdef FMT_BUILTIN_CLZLL +// It is a separate function rather than a part of count_digits to workaround +// the lack of static constexpr in constexpr functions. +inline auto do_count_digits(uint64_t n) -> int { + // This has comparable performance to the version by Kendall Willets + // (https://github.com/fmtlib/format-benchmark/blob/master/digits10) + // but uses smaller tables. + // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)). + static constexpr uint8_t bsr2log10[] = { + 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, + 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, + 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20}; + auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63]; + static constexpr const uint64_t zero_or_powers_of_10[] = { + 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL), + 10000000000000000000ULL}; + return t - (n < zero_or_powers_of_10[t]); +} +#endif + +// Returns the number of decimal digits in n. Leading zeros are not counted +// except for n == 0 in which case count_digits returns 1. +FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int { +#ifdef FMT_BUILTIN_CLZLL + if (!is_constant_evaluated() && !FMT_OPTIMIZE_SIZE) return do_count_digits(n); +#endif + return count_digits_fallback(n); +} + +// Counts the number of digits in n. BITS = log2(radix). +template +FMT_CONSTEXPR auto count_digits(UInt n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated() && num_bits() == 32) + return (FMT_BUILTIN_CLZ(static_cast(n) | 1) ^ 31) / BITS + 1; +#endif + // Lambda avoids unreachable code warnings from NVHPC. + return [](UInt m) { + int num_digits = 0; + do { + ++num_digits; + } while ((m >>= BITS) != 0); + return num_digits; + }(n); +} + +#ifdef FMT_BUILTIN_CLZ +// It is a separate function rather than a part of count_digits to workaround +// the lack of static constexpr in constexpr functions. +FMT_INLINE auto do_count_digits(uint32_t n) -> int { +// An optimization by Kendall Willets from https://bit.ly/3uOIQrB. +// This increments the upper 32 bits (log10(T) - 1) when >= T is added. +# define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T) + static constexpr uint64_t table[] = { + FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 + FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 + FMT_INC(100), FMT_INC(100), FMT_INC(100), // 512 + FMT_INC(1000), FMT_INC(1000), FMT_INC(1000), // 4096 + FMT_INC(10000), FMT_INC(10000), FMT_INC(10000), // 32k + FMT_INC(100000), FMT_INC(100000), FMT_INC(100000), // 256k + FMT_INC(1000000), FMT_INC(1000000), FMT_INC(1000000), // 2048k + FMT_INC(10000000), FMT_INC(10000000), FMT_INC(10000000), // 16M + FMT_INC(100000000), FMT_INC(100000000), FMT_INC(100000000), // 128M + FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000), // 1024M + FMT_INC(1000000000), FMT_INC(1000000000) // 4B + }; + auto inc = table[FMT_BUILTIN_CLZ(n | 1) ^ 31]; + return static_cast((n + inc) >> 32); +} +#endif + +// Optional version of count_digits for better performance on 32-bit platforms. +FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated() && !FMT_OPTIMIZE_SIZE) return do_count_digits(n); +#endif + return count_digits_fallback(n); +} + +template constexpr auto digits10() noexcept -> int { + return std::numeric_limits::digits10; +} +template <> constexpr auto digits10() noexcept -> int { return 38; } +template <> constexpr auto digits10() noexcept -> int { return 38; } + +template struct thousands_sep_result { + std::string grouping; + Char thousands_sep; +}; + +template +FMT_API auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result; +template +inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { + auto result = thousands_sep_impl(loc); + return {result.grouping, Char(result.thousands_sep)}; +} +template <> +inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { + return thousands_sep_impl(loc); +} + +template +FMT_API auto decimal_point_impl(locale_ref loc) -> Char; +template inline auto decimal_point(locale_ref loc) -> Char { + return Char(decimal_point_impl(loc)); +} +template <> inline auto decimal_point(locale_ref loc) -> wchar_t { + return decimal_point_impl(loc); +} + +#ifndef FMT_HEADER_ONLY +FMT_BEGIN_EXPORT +extern template FMT_API auto thousands_sep_impl(locale_ref) + -> thousands_sep_result; +extern template FMT_API auto thousands_sep_impl(locale_ref) + -> thousands_sep_result; +extern template FMT_API auto decimal_point_impl(locale_ref) -> char; +extern template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; +FMT_END_EXPORT +#endif // FMT_HEADER_ONLY + +// Compares two characters for equality. +template auto equal2(const Char* lhs, const char* rhs) -> bool { + return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]); +} +inline auto equal2(const char* lhs, const char* rhs) -> bool { + return memcmp(lhs, rhs, 2) == 0; +} + +// Writes a two-digit value to out. +template +FMT_CONSTEXPR20 FMT_INLINE void write2digits(Char* out, size_t value) { + if (!is_constant_evaluated() && std::is_same::value && + !FMT_OPTIMIZE_SIZE) { + memcpy(out, digits2(value), 2); + return; + } + *out++ = static_cast('0' + value / 10); + *out = static_cast('0' + value % 10); +} + +// Formats a decimal unsigned integer value writing to out pointing to a buffer +// of specified size. The caller must ensure that the buffer is large enough. +template +FMT_CONSTEXPR20 auto do_format_decimal(Char* out, UInt value, int size) + -> Char* { + FMT_ASSERT(size >= count_digits(value), "invalid digit count"); + unsigned n = to_unsigned(size); + while (value >= 100) { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + n -= 2; + write2digits(out + n, static_cast(value % 100)); + value /= 100; + } + if (value >= 10) { + n -= 2; + write2digits(out + n, static_cast(value)); + } else { + out[--n] = static_cast('0' + value); + } + return out + n; +} + +template +FMT_CONSTEXPR FMT_INLINE auto format_decimal(Char* out, UInt value, + int num_digits) -> Char* { + do_format_decimal(out, value, num_digits); + return out + num_digits; +} + +template ::value)> +FMT_CONSTEXPR auto format_decimal(OutputIt out, UInt value, int num_digits) + -> OutputIt { + if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { + do_format_decimal(ptr, value, num_digits); + return out; + } + // Buffer is large enough to hold all digits (digits10 + 1). + char buffer[digits10() + 1]; + if (is_constant_evaluated()) fill_n(buffer, sizeof(buffer), '\0'); + do_format_decimal(buffer, value, num_digits); + return copy_noinline(buffer, buffer + num_digits, out); +} + +template +FMT_CONSTEXPR auto do_format_base2e(int base_bits, Char* out, UInt value, + int size, bool upper = false) -> Char* { + out += size; + do { + const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + unsigned digit = static_cast(value & ((1 << base_bits) - 1)); + *--out = static_cast(base_bits < 4 ? static_cast('0' + digit) + : digits[digit]); + } while ((value >>= base_bits) != 0); + return out; +} + +// Formats an unsigned integer in the power of two base (binary, octal, hex). +template +FMT_CONSTEXPR auto format_base2e(int base_bits, Char* out, UInt value, + int num_digits, bool upper = false) -> Char* { + do_format_base2e(base_bits, out, value, num_digits, upper); + return out + num_digits; +} + +template ::value)> +FMT_CONSTEXPR inline auto format_base2e(int base_bits, OutputIt out, UInt value, + int num_digits, bool upper = false) + -> OutputIt { + if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { + format_base2e(base_bits, ptr, value, num_digits, upper); + return out; + } + // Make buffer large enough for any base. + char buffer[num_bits()]; + if (is_constant_evaluated()) fill_n(buffer, sizeof(buffer), '\0'); + format_base2e(base_bits, buffer, value, num_digits, upper); + return detail::copy_noinline(buffer, buffer + num_digits, out); +} + +// A converter from UTF-8 to UTF-16. +class utf8_to_utf16 { + private: + basic_memory_buffer buffer_; + + public: + FMT_API explicit utf8_to_utf16(string_view s); + inline operator basic_string_view() const { + return {&buffer_[0], size()}; + } + inline auto size() const -> size_t { return buffer_.size() - 1; } + inline auto c_str() const -> const wchar_t* { return &buffer_[0]; } + inline auto str() const -> std::wstring { return {&buffer_[0], size()}; } +}; + +enum class to_utf8_error_policy { abort, replace }; + +// A converter from UTF-16/UTF-32 (host endian) to UTF-8. +template class to_utf8 { + private: + Buffer buffer_; + + public: + to_utf8() {} + explicit to_utf8(basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) { + static_assert(sizeof(WChar) == 2 || sizeof(WChar) == 4, + "Expect utf16 or utf32"); + if (!convert(s, policy)) + FMT_THROW(std::runtime_error(sizeof(WChar) == 2 ? "invalid utf16" + : "invalid utf32")); + } + operator string_view() const { return string_view(&buffer_[0], size()); } + auto size() const -> size_t { return buffer_.size() - 1; } + auto c_str() const -> const char* { return &buffer_[0]; } + auto str() const -> std::string { return std::string(&buffer_[0], size()); } + + // Performs conversion returning a bool instead of throwing exception on + // conversion error. This method may still throw in case of memory allocation + // error. + auto convert(basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { + if (!convert(buffer_, s, policy)) return false; + buffer_.push_back(0); + return true; + } + static auto convert(Buffer& buf, basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { + for (auto p = s.begin(); p != s.end(); ++p) { + uint32_t c = static_cast(*p); + if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) { + // Handle a surrogate pair. + ++p; + if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { + if (policy == to_utf8_error_policy::abort) return false; + buf.append(string_view("\xEF\xBF\xBD")); + --p; + continue; + } else { + c = (c << 10) + static_cast(*p) - 0x35fdc00; + } + } + if (c < 0x80) { + buf.push_back(static_cast(c)); + } else if (c < 0x800) { + buf.push_back(static_cast(0xc0 | (c >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { + buf.push_back(static_cast(0xe0 | (c >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if (c >= 0x10000 && c <= 0x10ffff) { + buf.push_back(static_cast(0xf0 | (c >> 18))); + buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else { + return false; + } + } + return true; + } +}; + +// Computes 128-bit result of multiplication of two 64-bit unsigned integers. +inline auto umul128(uint64_t x, uint64_t y) noexcept -> uint128_fallback { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return {static_cast(p >> 64), static_cast(p)}; +#elif defined(_MSC_VER) && defined(_M_X64) + auto hi = uint64_t(); + auto lo = _umul128(x, y, &hi); + return {hi, lo}; +#else + const uint64_t mask = static_cast(max_value()); + + uint64_t a = x >> 32; + uint64_t b = x & mask; + uint64_t c = y >> 32; + uint64_t d = y & mask; + + uint64_t ac = a * c; + uint64_t bc = b * c; + uint64_t ad = a * d; + uint64_t bd = b * d; + + uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); + + return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), + (intermediate << 32) + (bd & mask)}; +#endif +} + +namespace dragonbox { +// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from +// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. +inline auto floor_log10_pow2(int e) noexcept -> int { + FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); + static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); + return (e * 315653) >> 20; +} + +inline auto floor_log2_pow10(int e) noexcept -> int { + FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); + return (e * 1741647) >> 19; +} + +// Computes upper 64 bits of multiplication of two 64-bit unsigned integers. +inline auto umul128_upper64(uint64_t x, uint64_t y) noexcept -> uint64_t { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return static_cast(p >> 64); +#elif defined(_MSC_VER) && defined(_M_X64) + return __umulh(x, y); +#else + return umul128(x, y).high(); +#endif +} + +// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline auto umul192_upper128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { + uint128_fallback r = umul128(x, y.high()); + r += umul128_upper64(x, y.low()); + return r; +} + +FMT_API auto get_cached_power(int k) noexcept -> uint128_fallback; + +// Type-specific information that Dragonbox uses. +template struct float_info; + +template <> struct float_info { + using carrier_uint = uint32_t; + static const int exponent_bits = 8; + static const int kappa = 1; + static const int big_divisor = 100; + static const int small_divisor = 10; + static const int min_k = -31; + static const int max_k = 46; + static const int shorter_interval_tie_lower_threshold = -35; + static const int shorter_interval_tie_upper_threshold = -35; +}; + +template <> struct float_info { + using carrier_uint = uint64_t; + static const int exponent_bits = 11; + static const int kappa = 2; + static const int big_divisor = 1000; + static const int small_divisor = 100; + static const int min_k = -292; + static const int max_k = 341; + static const int shorter_interval_tie_lower_threshold = -77; + static const int shorter_interval_tie_upper_threshold = -77; +}; + +// An 80- or 128-bit floating point number. +template +struct float_info::digits == 64 || + std::numeric_limits::digits == 113 || + is_float128::value>> { + using carrier_uint = detail::uint128_t; + static const int exponent_bits = 15; +}; + +// A double-double floating point number. +template +struct float_info::value>> { + using carrier_uint = detail::uint128_t; +}; + +template struct decimal_fp { + using significand_type = typename float_info::carrier_uint; + significand_type significand; + int exponent; +}; + +template FMT_API auto to_decimal(T x) noexcept -> decimal_fp; +} // namespace dragonbox + +// Returns true iff Float has the implicit bit which is not stored. +template constexpr auto has_implicit_bit() -> bool { + // An 80-bit FP number has a 64-bit significand an no implicit bit. + return std::numeric_limits::digits != 64; +} + +// Returns the number of significand bits stored in Float. The implicit bit is +// not counted since it is not stored. +template constexpr auto num_significand_bits() -> int { + // std::numeric_limits may not support __float128. + return is_float128() ? 112 + : (std::numeric_limits::digits - + (has_implicit_bit() ? 1 : 0)); +} + +template +constexpr auto exponent_mask() -> + typename dragonbox::float_info::carrier_uint { + using float_uint = typename dragonbox::float_info::carrier_uint; + return ((float_uint(1) << dragonbox::float_info::exponent_bits) - 1) + << num_significand_bits(); +} +template constexpr auto exponent_bias() -> int { + // std::numeric_limits may not support __float128. + return is_float128() ? 16383 + : std::numeric_limits::max_exponent - 1; +} + +// Writes the exponent exp in the form "[+-]d{2,3}" to buffer. +template +FMT_CONSTEXPR auto write_exponent(int exp, OutputIt out) -> OutputIt { + FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); + if (exp < 0) { + *out++ = static_cast('-'); + exp = -exp; + } else { + *out++ = static_cast('+'); + } + auto uexp = static_cast(exp); + if (is_constant_evaluated()) { + if (uexp < 10) *out++ = '0'; + return format_decimal(out, uexp, count_digits(uexp)); + } + if (uexp >= 100u) { + const char* top = digits2(uexp / 100); + if (uexp >= 1000u) *out++ = static_cast(top[0]); + *out++ = static_cast(top[1]); + uexp %= 100; + } + const char* d = digits2(uexp); + *out++ = static_cast(d[0]); + *out++ = static_cast(d[1]); + return out; +} + +// A floating-point number f * pow(2, e) where F is an unsigned type. +template struct basic_fp { + F f; + int e; + + static constexpr const int num_significand_bits = + static_cast(sizeof(F) * num_bits()); + + constexpr basic_fp() : f(0), e(0) {} + constexpr basic_fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} + + // Constructs fp from an IEEE754 floating-point number. + template FMT_CONSTEXPR basic_fp(Float n) { assign(n); } + + // Assigns n to this and return true iff predecessor is closer than successor. + template ::value)> + FMT_CONSTEXPR auto assign(Float n) -> bool { + static_assert(std::numeric_limits::digits <= 113, "unsupported FP"); + // Assume Float is in the format [sign][exponent][significand]. + using carrier_uint = typename dragonbox::float_info::carrier_uint; + const auto num_float_significand_bits = + detail::num_significand_bits(); + const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; + const auto significand_mask = implicit_bit - 1; + auto u = bit_cast(n); + f = static_cast(u & significand_mask); + auto biased_e = static_cast((u & exponent_mask()) >> + num_float_significand_bits); + // The predecessor is closer if n is a normalized power of 2 (f == 0) + // other than the smallest normalized number (biased_e > 1). + auto is_predecessor_closer = f == 0 && biased_e > 1; + if (biased_e == 0) + biased_e = 1; // Subnormals use biased exponent 1 (min exponent). + else if (has_implicit_bit()) + f += static_cast(implicit_bit); + e = biased_e - exponent_bias() - num_float_significand_bits; + if (!has_implicit_bit()) ++e; + return is_predecessor_closer; + } + + template ::value)> + FMT_CONSTEXPR auto assign(Float n) -> bool { + static_assert(std::numeric_limits::is_iec559, "unsupported FP"); + return assign(static_cast(n)); + } +}; + +using fp = basic_fp; + +// Normalizes the value converted from double and multiplied by (1 << SHIFT). +template +FMT_CONSTEXPR auto normalize(basic_fp value) -> basic_fp { + // Handle subnormals. + const auto implicit_bit = F(1) << num_significand_bits(); + const auto shifted_implicit_bit = implicit_bit << SHIFT; + while ((value.f & shifted_implicit_bit) == 0) { + value.f <<= 1; + --value.e; + } + // Subtract 1 to account for hidden bit. + const auto offset = basic_fp::num_significand_bits - + num_significand_bits() - SHIFT - 1; + value.f <<= offset; + value.e -= offset; + return value; +} + +// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. +FMT_CONSTEXPR inline auto multiply(uint64_t lhs, uint64_t rhs) -> uint64_t { +#if FMT_USE_INT128 + auto product = static_cast<__uint128_t>(lhs) * rhs; + auto f = static_cast(product >> 64); + return (static_cast(product) & (1ULL << 63)) != 0 ? f + 1 : f; +#else + // Multiply 32-bit parts of significands. + uint64_t mask = (1ULL << 32) - 1; + uint64_t a = lhs >> 32, b = lhs & mask; + uint64_t c = rhs >> 32, d = rhs & mask; + uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; + // Compute mid 64-bit of result and round. + uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); + return ac + (ad >> 32) + (bc >> 32) + (mid >> 32); +#endif +} + +FMT_CONSTEXPR inline auto operator*(fp x, fp y) -> fp { + return {multiply(x.f, y.f), x.e + y.e + 64}; +} + +template () == num_bits()> +using convert_float_result = + conditional_t::value || doublish, double, T>; + +template +constexpr auto convert_float(T value) -> convert_float_result { + return static_cast>(value); +} + +template +FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, + const basic_specs& specs) -> OutputIt { + auto fill_size = specs.fill_size(); + if (fill_size == 1) return detail::fill_n(it, n, specs.fill_unit()); + if (const Char* data = specs.fill()) { + for (size_t i = 0; i < n; ++i) it = copy(data, data + fill_size, it); + } + return it; +} + +// Writes the output of f, padded according to format specifications in specs. +// size: output size in code units. +// width: output display width in (terminal) column positions. +template +FMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs& specs, + size_t size, size_t width, F&& f) -> OutputIt { + static_assert(default_align == align::left || default_align == align::right, + ""); + unsigned spec_width = to_unsigned(specs.width); + size_t padding = spec_width > width ? spec_width - width : 0; + // Shifts are encoded as string literals because static constexpr is not + // supported in constexpr functions. + auto* shifts = + default_align == align::left ? "\x1f\x1f\x00\x01" : "\x00\x1f\x00\x01"; + size_t left_padding = padding >> shifts[static_cast(specs.align())]; + size_t right_padding = padding - left_padding; + auto it = reserve(out, size + padding * specs.fill_size()); + if (left_padding != 0) it = fill(it, left_padding, specs); + it = f(it); + if (right_padding != 0) it = fill(it, right_padding, specs); + return base_iterator(out, it); +} + +template +constexpr auto write_padded(OutputIt out, const format_specs& specs, + size_t size, F&& f) -> OutputIt { + return write_padded(out, specs, size, size, f); +} + +template +FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, + const format_specs& specs = {}) -> OutputIt { + return write_padded( + out, specs, bytes.size(), [bytes](reserve_iterator it) { + const char* data = bytes.data(); + return copy(data, data + bytes.size(), it); + }); +} + +template +auto write_ptr(OutputIt out, UIntPtr value, const format_specs* specs) + -> OutputIt { + int num_digits = count_digits<4>(value); + auto size = to_unsigned(num_digits) + size_t(2); + auto write = [=](reserve_iterator it) { + *it++ = static_cast('0'); + *it++ = static_cast('x'); + return format_base2e(4, it, value, num_digits); + }; + return specs ? write_padded(out, *specs, size, write) + : base_iterator(out, write(reserve(out, size))); +} + +// Returns true iff the code point cp is printable. +FMT_API auto is_printable(uint32_t cp) -> bool; + +inline auto needs_escape(uint32_t cp) -> bool { + if (cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\') return true; + if (const_check(FMT_OPTIMIZE_SIZE > 1)) return false; + return !is_printable(cp); +} + +template struct find_escape_result { + const Char* begin; + const Char* end; + uint32_t cp; +}; + +template +auto find_escape(const Char* begin, const Char* end) + -> find_escape_result { + for (; begin != end; ++begin) { + uint32_t cp = static_cast>(*begin); + if (const_check(sizeof(Char) == 1) && cp >= 0x80) continue; + if (needs_escape(cp)) return {begin, begin + 1, cp}; + } + return {begin, nullptr, 0}; +} + +inline auto find_escape(const char* begin, const char* end) + -> find_escape_result { + if (const_check(!use_utf8)) return find_escape(begin, end); + auto result = find_escape_result{end, nullptr, 0}; + for_each_codepoint(string_view(begin, to_unsigned(end - begin)), + [&](uint32_t cp, string_view sv) { + if (needs_escape(cp)) { + result = {sv.begin(), sv.end(), cp}; + return false; + } + return true; + }); + return result; +} + +template +auto write_codepoint(OutputIt out, char prefix, uint32_t cp) -> OutputIt { + *out++ = static_cast('\\'); + *out++ = static_cast(prefix); + Char buf[width]; + fill_n(buf, width, static_cast('0')); + format_base2e(4, buf, cp, width); + return copy(buf, buf + width, out); +} + +template +auto write_escaped_cp(OutputIt out, const find_escape_result& escape) + -> OutputIt { + auto c = static_cast(escape.cp); + switch (escape.cp) { + case '\n': + *out++ = static_cast('\\'); + c = static_cast('n'); + break; + case '\r': + *out++ = static_cast('\\'); + c = static_cast('r'); + break; + case '\t': + *out++ = static_cast('\\'); + c = static_cast('t'); + break; + case '"': FMT_FALLTHROUGH; + case '\'': FMT_FALLTHROUGH; + case '\\': *out++ = static_cast('\\'); break; + default: + if (escape.cp < 0x100) return write_codepoint<2, Char>(out, 'x', escape.cp); + if (escape.cp < 0x10000) + return write_codepoint<4, Char>(out, 'u', escape.cp); + if (escape.cp < 0x110000) + return write_codepoint<8, Char>(out, 'U', escape.cp); + for (Char escape_char : basic_string_view( + escape.begin, to_unsigned(escape.end - escape.begin))) { + out = write_codepoint<2, Char>(out, 'x', + static_cast(escape_char) & 0xFF); + } + return out; + } + *out++ = c; + return out; +} + +template +auto write_escaped_string(OutputIt out, basic_string_view str) + -> OutputIt { + *out++ = static_cast('"'); + auto begin = str.begin(), end = str.end(); + do { + auto escape = find_escape(begin, end); + out = copy(begin, escape.begin, out); + begin = escape.end; + if (!begin) break; + out = write_escaped_cp(out, escape); + } while (begin != end); + *out++ = static_cast('"'); + return out; +} + +template +auto write_escaped_char(OutputIt out, Char v) -> OutputIt { + Char v_array[1] = {v}; + *out++ = static_cast('\''); + if ((needs_escape(static_cast(v)) && v != static_cast('"')) || + v == static_cast('\'')) { + out = write_escaped_cp(out, + find_escape_result{v_array, v_array + 1, + static_cast(v)}); + } else { + *out++ = v; + } + *out++ = static_cast('\''); + return out; +} + +template +FMT_CONSTEXPR auto write_char(OutputIt out, Char value, + const format_specs& specs) -> OutputIt { + bool is_debug = specs.type() == presentation_type::debug; + return write_padded(out, specs, 1, [=](reserve_iterator it) { + if (is_debug) return write_escaped_char(it, value); + *it++ = value; + return it; + }); +} +template +FMT_CONSTEXPR auto write(OutputIt out, Char value, const format_specs& specs, + locale_ref loc = {}) -> OutputIt { + // char is formatted as unsigned char for consistency across platforms. + using unsigned_type = + conditional_t::value, unsigned char, unsigned>; + return check_char_specs(specs) + ? write_char(out, value, specs) + : write(out, static_cast(value), specs, loc); +} + +template class digit_grouping { + private: + std::string grouping_; + std::basic_string thousands_sep_; + + struct next_state { + std::string::const_iterator group; + int pos; + }; + auto initial_state() const -> next_state { return {grouping_.begin(), 0}; } + + // Returns the next digit group separator position. + auto next(next_state& state) const -> int { + if (thousands_sep_.empty()) return max_value(); + if (state.group == grouping_.end()) return state.pos += grouping_.back(); + if (*state.group <= 0 || *state.group == max_value()) + return max_value(); + state.pos += *state.group++; + return state.pos; + } + + public: + explicit digit_grouping(locale_ref loc, bool localized = true) { + if (!localized) return; + auto sep = thousands_sep(loc); + grouping_ = sep.grouping; + if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); + } + digit_grouping(std::string grouping, std::basic_string sep) + : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} + + auto has_separator() const -> bool { return !thousands_sep_.empty(); } + + auto count_separators(int num_digits) const -> int { + int count = 0; + auto state = initial_state(); + while (num_digits > next(state)) ++count; + return count; + } + + // Applies grouping to digits and write the output to out. + template + auto apply(Out out, basic_string_view digits) const -> Out { + auto num_digits = static_cast(digits.size()); + auto separators = basic_memory_buffer(); + separators.push_back(0); + auto state = initial_state(); + while (int i = next(state)) { + if (i >= num_digits) break; + separators.push_back(i); + } + for (int i = 0, sep_index = static_cast(separators.size() - 1); + i < num_digits; ++i) { + if (num_digits - i == separators[sep_index]) { + out = copy(thousands_sep_.data(), + thousands_sep_.data() + thousands_sep_.size(), out); + --sep_index; + } + *out++ = static_cast(digits[to_unsigned(i)]); + } + return out; + } +}; + +FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { + prefix |= prefix != 0 ? value << 8 : value; + prefix += (1u + (value > 0xff ? 1 : 0)) << 24; +} + +// Writes a decimal integer with digit grouping. +template +auto write_int(OutputIt out, UInt value, unsigned prefix, + const format_specs& specs, const digit_grouping& grouping) + -> OutputIt { + static_assert(std::is_same, UInt>::value, ""); + int num_digits = 0; + auto buffer = memory_buffer(); + switch (specs.type()) { + default: FMT_ASSERT(false, ""); FMT_FALLTHROUGH; + case presentation_type::none: + case presentation_type::dec: + num_digits = count_digits(value); + format_decimal(appender(buffer), value, num_digits); + break; + case presentation_type::hex: + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0'); + num_digits = count_digits<4>(value); + format_base2e(4, appender(buffer), value, num_digits, specs.upper()); + break; + case presentation_type::oct: + num_digits = count_digits<3>(value); + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + if (specs.alt() && specs.precision <= num_digits && value != 0) + prefix_append(prefix, '0'); + format_base2e(3, appender(buffer), value, num_digits); + break; + case presentation_type::bin: + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0'); + num_digits = count_digits<1>(value); + format_base2e(1, appender(buffer), value, num_digits); + break; + case presentation_type::chr: + return write_char(out, static_cast(value), specs); + } + + unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) + + to_unsigned(grouping.count_separators(num_digits)); + return write_padded( + out, specs, size, size, [&](reserve_iterator it) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + return grouping.apply(it, string_view(buffer.data(), buffer.size())); + }); +} + +#if FMT_USE_LOCALE +// Writes a localized value. +FMT_API auto write_loc(appender out, loc_value value, const format_specs& specs, + locale_ref loc) -> bool; +#endif +template +inline auto write_loc(OutputIt, const loc_value&, const format_specs&, + locale_ref) -> bool { + return false; +} + +template struct write_int_arg { + UInt abs_value; + unsigned prefix; +}; + +template +FMT_CONSTEXPR auto make_write_int_arg(T value, sign s) + -> write_int_arg> { + auto prefix = 0u; + auto abs_value = static_cast>(value); + if (is_negative(value)) { + prefix = 0x01000000 | '-'; + abs_value = 0 - abs_value; + } else { + constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', + 0x1000000u | ' '}; + prefix = prefixes[static_cast(s)]; + } + return {abs_value, prefix}; +} + +template struct loc_writer { + basic_appender out; + const format_specs& specs; + std::basic_string sep; + std::string grouping; + std::basic_string decimal_point; + + template ::value)> + auto operator()(T value) -> bool { + auto arg = make_write_int_arg(value, specs.sign()); + write_int(out, static_cast>(arg.abs_value), arg.prefix, + specs, digit_grouping(grouping, sep)); + return true; + } + + template ::value)> + auto operator()(T) -> bool { + return false; + } +}; + +// Size and padding computation separate from write_int to avoid template bloat. +struct size_padding { + unsigned size; + unsigned padding; + + FMT_CONSTEXPR size_padding(int num_digits, unsigned prefix, + const format_specs& specs) + : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { + if (specs.align() == align::numeric) { + auto width = to_unsigned(specs.width); + if (width > size) { + padding = width - size; + size = width; + } + } else if (specs.precision > num_digits) { + size = (prefix >> 24) + to_unsigned(specs.precision); + padding = to_unsigned(specs.precision - num_digits); + } + } +}; + +template +FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, + const format_specs& specs) -> OutputIt { + static_assert(std::is_same>::value, ""); + + constexpr int buffer_size = num_bits(); + char buffer[buffer_size]; + if (is_constant_evaluated()) fill_n(buffer, buffer_size, '\0'); + const char* begin = nullptr; + const char* end = buffer + buffer_size; + + auto abs_value = arg.abs_value; + auto prefix = arg.prefix; + switch (specs.type()) { + default: FMT_ASSERT(false, ""); FMT_FALLTHROUGH; + case presentation_type::none: + case presentation_type::dec: + begin = do_format_decimal(buffer, abs_value, buffer_size); + break; + case presentation_type::hex: + begin = do_format_base2e(4, buffer, abs_value, buffer_size, specs.upper()); + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0'); + break; + case presentation_type::oct: { + begin = do_format_base2e(3, buffer, abs_value, buffer_size); + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + auto num_digits = end - begin; + if (specs.alt() && specs.precision <= num_digits && abs_value != 0) + prefix_append(prefix, '0'); + break; + } + case presentation_type::bin: + begin = do_format_base2e(1, buffer, abs_value, buffer_size); + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0'); + break; + case presentation_type::chr: + return write_char(out, static_cast(abs_value), specs); + } + + // Write an integer in the format + // + // prefix contains chars in three lower bytes and the size in the fourth byte. + int num_digits = static_cast(end - begin); + // Slightly faster check for specs.width == 0 && specs.precision == -1. + if ((specs.width | (specs.precision + 1)) == 0) { + auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24)); + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + return base_iterator(out, copy(begin, end, it)); + } + auto sp = size_padding(num_digits, prefix, specs); + unsigned padding = sp.padding; + return write_padded( + out, specs, sp.size, [=](reserve_iterator it) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + it = detail::fill_n(it, padding, static_cast('0')); + return copy(begin, end, it); + }); +} + +template +FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline(OutputIt out, + write_int_arg arg, + const format_specs& specs) + -> OutputIt { + return write_int(out, arg, specs); +} + +template ::value && + !std::is_same::value && + !std::is_same::value)> +FMT_CONSTEXPR FMT_INLINE auto write(basic_appender out, T value, + const format_specs& specs, locale_ref loc) + -> basic_appender { + if (specs.localized() && write_loc(out, value, specs, loc)) return out; + return write_int_noinline(out, make_write_int_arg(value, specs.sign()), + specs); +} + +// An inlined version of write used in format string compilation. +template ::value && + !std::is_same::value && + !std::is_same::value && + !std::is_same>::value)> +FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, + const format_specs& specs, locale_ref loc) + -> OutputIt { + if (specs.localized() && write_loc(out, value, specs, loc)) return out; + return write_int(out, make_write_int_arg(value, specs.sign()), specs); +} + +template +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, + const format_specs& specs) -> OutputIt { + auto data = s.data(); + auto size = s.size(); + if (specs.precision >= 0 && to_unsigned(specs.precision) < size) + size = code_point_index(s, to_unsigned(specs.precision)); + + bool is_debug = specs.type() == presentation_type::debug; + if (is_debug) { + auto buf = counting_buffer(); + write_escaped_string(basic_appender(buf), s); + size = buf.count(); + } + + size_t width = 0; + if (specs.width != 0) { + width = + is_debug ? size : compute_width(basic_string_view(data, size)); + } + return write_padded( + out, specs, size, width, [=](reserve_iterator it) { + return is_debug ? write_escaped_string(it, s) + : copy(data, data + size, it); + }); +} +template +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, + const format_specs& specs, locale_ref) -> OutputIt { + return write(out, s, specs); +} +template +FMT_CONSTEXPR auto write(OutputIt out, const Char* s, const format_specs& specs, + locale_ref) -> OutputIt { + if (specs.type() == presentation_type::pointer) + return write_ptr(out, bit_cast(s), &specs); + if (!s) report_error("string pointer is null"); + return write(out, basic_string_view(s), specs, {}); +} + +template ::value && + !std::is_same::value && + !std::is_same::value)> +FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { + auto abs_value = static_cast>(value); + bool negative = is_negative(value); + // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. + if (negative) abs_value = ~abs_value + 1; + int num_digits = count_digits(abs_value); + auto size = (negative ? 1 : 0) + static_cast(num_digits); + if (auto ptr = to_pointer(out, size)) { + if (negative) *ptr++ = static_cast('-'); + format_decimal(ptr, abs_value, num_digits); + return out; + } + if (negative) *out++ = static_cast('-'); + return format_decimal(out, abs_value, num_digits); +} + +template +FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, + format_specs& specs) -> const Char* { + FMT_ASSERT(begin != end, ""); + auto alignment = align::none; + auto p = begin + code_point_length(begin); + if (end - p <= 0) p = begin; + for (;;) { + switch (to_ascii(*p)) { + case '<': alignment = align::left; break; + case '>': alignment = align::right; break; + case '^': alignment = align::center; break; + } + if (alignment != align::none) { + if (p != begin) { + auto c = *begin; + if (c == '}') return begin; + if (c == '{') { + report_error("invalid fill character '{'"); + return begin; + } + specs.set_fill(basic_string_view(begin, to_unsigned(p - begin))); + begin = p + 1; + } else { + ++begin; + } + break; + } else if (p == begin) { + break; + } + p = begin; + } + specs.set_align(alignment); + return begin; +} + +template +FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, + format_specs specs, sign s) -> OutputIt { + auto str = + isnan ? (specs.upper() ? "NAN" : "nan") : (specs.upper() ? "INF" : "inf"); + constexpr size_t str_size = 3; + auto size = str_size + (s != sign::none ? 1 : 0); + // Replace '0'-padding with space for non-finite values. + const bool is_zero_fill = + specs.fill_size() == 1 && specs.fill_unit() == '0'; + if (is_zero_fill) specs.set_fill(' '); + return write_padded(out, specs, size, + [=](reserve_iterator it) { + if (s != sign::none) + *it++ = detail::getsign(s); + return copy(str, str + str_size, it); + }); +} + +// A decimal floating-point number significand * pow(10, exp). +struct big_decimal_fp { + const char* significand; + int significand_size; + int exponent; +}; + +constexpr auto get_significand_size(const big_decimal_fp& f) -> int { + return f.significand_size; +} +template +inline auto get_significand_size(const dragonbox::decimal_fp& f) -> int { + return count_digits(f.significand); +} + +template +constexpr auto write_significand(OutputIt out, const char* significand, + int significand_size) -> OutputIt { + return copy(significand, significand + significand_size, out); +} +template +inline auto write_significand(OutputIt out, UInt significand, + int significand_size) -> OutputIt { + return format_decimal(out, significand, significand_size); +} +template +FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, + int significand_size, int exponent, + const Grouping& grouping) -> OutputIt { + if (!grouping.has_separator()) { + out = write_significand(out, significand, significand_size); + return detail::fill_n(out, exponent, static_cast('0')); + } + auto buffer = memory_buffer(); + write_significand(appender(buffer), significand, significand_size); + detail::fill_n(appender(buffer), exponent, '0'); + return grouping.apply(out, string_view(buffer.data(), buffer.size())); +} + +template ::value)> +inline auto write_significand(Char* out, UInt significand, int significand_size, + int integral_size, Char decimal_point) -> Char* { + if (!decimal_point) return format_decimal(out, significand, significand_size); + out += significand_size + 1; + Char* end = out; + int floating_size = significand_size - integral_size; + for (int i = floating_size / 2; i > 0; --i) { + out -= 2; + write2digits(out, static_cast(significand % 100)); + significand /= 100; + } + if (floating_size % 2 != 0) { + *--out = static_cast('0' + significand % 10); + significand /= 10; + } + *--out = decimal_point; + format_decimal(out - integral_size, significand, integral_size); + return end; +} + +template >::value)> +inline auto write_significand(OutputIt out, UInt significand, + int significand_size, int integral_size, + Char decimal_point) -> OutputIt { + // Buffer is large enough to hold digits (digits10 + 1) and a decimal point. + Char buffer[digits10() + 2]; + auto end = write_significand(buffer, significand, significand_size, + integral_size, decimal_point); + return detail::copy_noinline(buffer, end, out); +} + +template +FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand, + int significand_size, int integral_size, + Char decimal_point) -> OutputIt { + out = detail::copy_noinline(significand, significand + integral_size, + out); + if (!decimal_point) return out; + *out++ = decimal_point; + return detail::copy_noinline(significand + integral_size, + significand + significand_size, out); +} + +template +FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, + int significand_size, int integral_size, + Char decimal_point, + const Grouping& grouping) -> OutputIt { + if (!grouping.has_separator()) { + return write_significand(out, significand, significand_size, integral_size, + decimal_point); + } + auto buffer = basic_memory_buffer(); + write_significand(basic_appender(buffer), significand, significand_size, + integral_size, decimal_point); + grouping.apply( + out, basic_string_view(buffer.data(), to_unsigned(integral_size))); + return detail::copy_noinline(buffer.data() + integral_size, + buffer.end(), out); +} + +template > +FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, + const format_specs& specs, sign s, + locale_ref loc) -> OutputIt { + auto significand = f.significand; + int significand_size = get_significand_size(f); + const Char zero = static_cast('0'); + size_t size = to_unsigned(significand_size) + (s != sign::none ? 1 : 0); + using iterator = reserve_iterator; + + Char decimal_point = specs.localized() ? detail::decimal_point(loc) + : static_cast('.'); + + int output_exp = f.exponent + significand_size - 1; + auto use_exp_format = [=]() { + if (specs.type() == presentation_type::exp) return true; + if (specs.type() == presentation_type::fixed) return false; + // Use the fixed notation if the exponent is in [exp_lower, exp_upper), + // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. + const int exp_lower = -4, exp_upper = 16; + return output_exp < exp_lower || + output_exp >= (specs.precision > 0 ? specs.precision : exp_upper); + }; + if (use_exp_format()) { + int num_zeros = 0; + if (specs.alt()) { + num_zeros = specs.precision - significand_size; + if (num_zeros < 0) num_zeros = 0; + size += to_unsigned(num_zeros); + } else if (significand_size == 1) { + decimal_point = Char(); + } + auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp; + int exp_digits = 2; + if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3; + + size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits); + char exp_char = specs.upper() ? 'E' : 'e'; + auto write = [=](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + // Insert a decimal point after the first digit and add an exponent. + it = write_significand(it, significand, significand_size, 1, + decimal_point); + if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero); + *it++ = static_cast(exp_char); + return write_exponent(output_exp, it); + }; + return specs.width > 0 + ? write_padded(out, specs, size, write) + : base_iterator(out, write(reserve(out, size))); + } + + int exp = f.exponent + significand_size; + if (f.exponent >= 0) { + // 1234e5 -> 123400000[.0+] + size += to_unsigned(f.exponent); + int num_zeros = specs.precision - exp; + abort_fuzzing_if(num_zeros > 5000); + if (specs.alt()) { + ++size; + if (num_zeros <= 0 && specs.type() != presentation_type::fixed) + num_zeros = 0; + if (num_zeros > 0) size += to_unsigned(num_zeros); + } + auto grouping = Grouping(loc, specs.localized()); + size += to_unsigned(grouping.count_separators(exp)); + return write_padded(out, specs, size, [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + it = write_significand(it, significand, significand_size, + f.exponent, grouping); + if (!specs.alt()) return it; + *it++ = decimal_point; + return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; + }); + } else if (exp > 0) { + // 1234e-2 -> 12.34[0+] + int num_zeros = specs.alt() ? specs.precision - significand_size : 0; + size += 1 + static_cast(max_of(num_zeros, 0)); + auto grouping = Grouping(loc, specs.localized()); + size += to_unsigned(grouping.count_separators(exp)); + return write_padded(out, specs, size, [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + it = write_significand(it, significand, significand_size, exp, + decimal_point, grouping); + return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; + }); + } + // 1234e-6 -> 0.001234 + int num_zeros = -exp; + if (significand_size == 0 && specs.precision >= 0 && + specs.precision < num_zeros) { + num_zeros = specs.precision; + } + bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt(); + size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros); + return write_padded(out, specs, size, [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + *it++ = zero; + if (!pointy) return it; + *it++ = decimal_point; + it = detail::fill_n(it, num_zeros, zero); + return write_significand(it, significand, significand_size); + }); +} + +template class fallback_digit_grouping { + public: + constexpr fallback_digit_grouping(locale_ref, bool) {} + + constexpr auto has_separator() const -> bool { return false; } + + constexpr auto count_separators(int) const -> int { return 0; } + + template + constexpr auto apply(Out out, basic_string_view) const -> Out { + return out; + } +}; + +template +FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, + const format_specs& specs, sign s, + locale_ref loc) -> OutputIt { + if (is_constant_evaluated()) { + return do_write_float>(out, f, specs, s, loc); + } else { + return do_write_float(out, f, specs, s, loc); + } +} + +template constexpr auto isnan(T value) -> bool { + return value != value; // std::isnan doesn't support __float128. +} + +template +struct has_isfinite : std::false_type {}; + +template +struct has_isfinite> + : std::true_type {}; + +template ::value&& + has_isfinite::value)> +FMT_CONSTEXPR20 auto isfinite(T value) -> bool { + constexpr T inf = T(std::numeric_limits::infinity()); + if (is_constant_evaluated()) + return !detail::isnan(value) && value < inf && value > -inf; + return std::isfinite(value); +} +template ::value)> +FMT_CONSTEXPR auto isfinite(T value) -> bool { + T inf = T(std::numeric_limits::infinity()); + // std::isfinite doesn't support __float128. + return !detail::isnan(value) && value < inf && value > -inf; +} + +template ::value)> +FMT_INLINE FMT_CONSTEXPR bool signbit(T value) { + if (is_constant_evaluated()) { +#ifdef __cpp_if_constexpr + if constexpr (std::numeric_limits::is_iec559) { + auto bits = detail::bit_cast(static_cast(value)); + return (bits >> (num_bits() - 1)) != 0; + } +#endif + } + return std::signbit(static_cast(value)); +} + +inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) { + // Adjust fixed precision by exponent because it is relative to decimal + // point. + if (exp10 > 0 && precision > max_value() - exp10) + FMT_THROW(format_error("number is too big")); + precision += exp10; +} + +class bigint { + private: + // A bigint is a number in the form bigit_[N - 1] ... bigit_[0] * 32^exp_. + using bigit = uint32_t; // A big digit. + using double_bigit = uint64_t; + enum { bigit_bits = num_bits() }; + enum { bigits_capacity = 32 }; + basic_memory_buffer bigits_; + int exp_; + + friend struct formatter; + + FMT_CONSTEXPR auto get_bigit(int i) const -> bigit { + return i >= exp_ && i < num_bigits() ? bigits_[i - exp_] : 0; + } + + FMT_CONSTEXPR void subtract_bigits(int index, bigit other, bigit& borrow) { + auto result = double_bigit(bigits_[index]) - other - borrow; + bigits_[index] = static_cast(result); + borrow = static_cast(result >> (bigit_bits * 2 - 1)); + } + + FMT_CONSTEXPR void remove_leading_zeros() { + int num_bigits = static_cast(bigits_.size()) - 1; + while (num_bigits > 0 && bigits_[num_bigits] == 0) --num_bigits; + bigits_.resize(to_unsigned(num_bigits + 1)); + } + + // Computes *this -= other assuming aligned bigints and *this >= other. + FMT_CONSTEXPR void subtract_aligned(const bigint& other) { + FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); + FMT_ASSERT(compare(*this, other) >= 0, ""); + bigit borrow = 0; + int i = other.exp_ - exp_; + for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) + subtract_bigits(i, other.bigits_[j], borrow); + if (borrow != 0) subtract_bigits(i, 0, borrow); + FMT_ASSERT(borrow == 0, ""); + remove_leading_zeros(); + } + + FMT_CONSTEXPR void multiply(uint32_t value) { + bigit carry = 0; + const double_bigit wide_value = value; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + double_bigit result = bigits_[i] * wide_value + carry; + bigits_[i] = static_cast(result); + carry = static_cast(result >> bigit_bits); + } + if (carry != 0) bigits_.push_back(carry); + } + + template ::value || + std::is_same::value)> + FMT_CONSTEXPR void multiply(UInt value) { + using half_uint = + conditional_t::value, uint64_t, uint32_t>; + const int shift = num_bits() - bigit_bits; + const UInt lower = static_cast(value); + const UInt upper = value >> num_bits(); + UInt carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + UInt result = lower * bigits_[i] + static_cast(carry); + carry = (upper * bigits_[i] << shift) + (result >> bigit_bits) + + (carry >> bigit_bits); + bigits_[i] = static_cast(result); + } + while (carry != 0) { + bigits_.push_back(static_cast(carry)); + carry >>= bigit_bits; + } + } + + template ::value || + std::is_same::value)> + FMT_CONSTEXPR void assign(UInt n) { + size_t num_bigits = 0; + do { + bigits_[num_bigits++] = static_cast(n); + n >>= bigit_bits; + } while (n != 0); + bigits_.resize(num_bigits); + exp_ = 0; + } + + public: + FMT_CONSTEXPR bigint() : exp_(0) {} + explicit bigint(uint64_t n) { assign(n); } + + bigint(const bigint&) = delete; + void operator=(const bigint&) = delete; + + FMT_CONSTEXPR void assign(const bigint& other) { + auto size = other.bigits_.size(); + bigits_.resize(size); + auto data = other.bigits_.data(); + copy(data, data + size, bigits_.data()); + exp_ = other.exp_; + } + + template FMT_CONSTEXPR void operator=(Int n) { + FMT_ASSERT(n > 0, ""); + assign(uint64_or_128_t(n)); + } + + FMT_CONSTEXPR auto num_bigits() const -> int { + return static_cast(bigits_.size()) + exp_; + } + + FMT_CONSTEXPR auto operator<<=(int shift) -> bigint& { + FMT_ASSERT(shift >= 0, ""); + exp_ += shift / bigit_bits; + shift %= bigit_bits; + if (shift == 0) return *this; + bigit carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + bigit c = bigits_[i] >> (bigit_bits - shift); + bigits_[i] = (bigits_[i] << shift) + carry; + carry = c; + } + if (carry != 0) bigits_.push_back(carry); + return *this; + } + + template FMT_CONSTEXPR auto operator*=(Int value) -> bigint& { + FMT_ASSERT(value > 0, ""); + multiply(uint32_or_64_or_128_t(value)); + return *this; + } + + friend FMT_CONSTEXPR auto compare(const bigint& b1, const bigint& b2) -> int { + int num_bigits1 = b1.num_bigits(), num_bigits2 = b2.num_bigits(); + if (num_bigits1 != num_bigits2) return num_bigits1 > num_bigits2 ? 1 : -1; + int i = static_cast(b1.bigits_.size()) - 1; + int j = static_cast(b2.bigits_.size()) - 1; + int end = i - j; + if (end < 0) end = 0; + for (; i >= end; --i, --j) { + bigit b1_bigit = b1.bigits_[i], b2_bigit = b2.bigits_[j]; + if (b1_bigit != b2_bigit) return b1_bigit > b2_bigit ? 1 : -1; + } + if (i != j) return i > j ? 1 : -1; + return 0; + } + + // Returns compare(lhs1 + lhs2, rhs). + friend FMT_CONSTEXPR auto add_compare(const bigint& lhs1, const bigint& lhs2, + const bigint& rhs) -> int { + int max_lhs_bigits = max_of(lhs1.num_bigits(), lhs2.num_bigits()); + int num_rhs_bigits = rhs.num_bigits(); + if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; + if (max_lhs_bigits > num_rhs_bigits) return 1; + double_bigit borrow = 0; + int min_exp = min_of(min_of(lhs1.exp_, lhs2.exp_), rhs.exp_); + for (int i = num_rhs_bigits - 1; i >= min_exp; --i) { + double_bigit sum = double_bigit(lhs1.get_bigit(i)) + lhs2.get_bigit(i); + bigit rhs_bigit = rhs.get_bigit(i); + if (sum > rhs_bigit + borrow) return 1; + borrow = rhs_bigit + borrow - sum; + if (borrow > 1) return -1; + borrow <<= bigit_bits; + } + return borrow != 0 ? -1 : 0; + } + + // Assigns pow(10, exp) to this bigint. + FMT_CONSTEXPR20 void assign_pow10(int exp) { + FMT_ASSERT(exp >= 0, ""); + if (exp == 0) return *this = 1; + int bitmask = 1 << (num_bits() - + countl_zero(static_cast(exp)) - 1); + // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by + // repeated squaring and multiplication. + *this = 5; + bitmask >>= 1; + while (bitmask != 0) { + square(); + if ((exp & bitmask) != 0) *this *= 5; + bitmask >>= 1; + } + *this <<= exp; // Multiply by pow(2, exp) by shifting. + } + + FMT_CONSTEXPR20 void square() { + int num_bigits = static_cast(bigits_.size()); + int num_result_bigits = 2 * num_bigits; + basic_memory_buffer n(std::move(bigits_)); + bigits_.resize(to_unsigned(num_result_bigits)); + auto sum = uint128_t(); + for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) { + // Compute bigit at position bigit_index of the result by adding + // cross-product terms n[i] * n[j] such that i + j == bigit_index. + for (int i = 0, j = bigit_index; j >= 0; ++i, --j) { + // Most terms are multiplied twice which can be optimized in the future. + sum += double_bigit(n[i]) * n[j]; + } + bigits_[bigit_index] = static_cast(sum); + sum >>= num_bits(); // Compute the carry. + } + // Do the same for the top half. + for (int bigit_index = num_bigits; bigit_index < num_result_bigits; + ++bigit_index) { + for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) + sum += double_bigit(n[i++]) * n[j--]; + bigits_[bigit_index] = static_cast(sum); + sum >>= num_bits(); + } + remove_leading_zeros(); + exp_ *= 2; + } + + // If this bigint has a bigger exponent than other, adds trailing zero to make + // exponents equal. This simplifies some operations such as subtraction. + FMT_CONSTEXPR void align(const bigint& other) { + int exp_difference = exp_ - other.exp_; + if (exp_difference <= 0) return; + int num_bigits = static_cast(bigits_.size()); + bigits_.resize(to_unsigned(num_bigits + exp_difference)); + for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) + bigits_[j] = bigits_[i]; + memset(bigits_.data(), 0, to_unsigned(exp_difference) * sizeof(bigit)); + exp_ -= exp_difference; + } + + // Divides this bignum by divisor, assigning the remainder to this and + // returning the quotient. + FMT_CONSTEXPR auto divmod_assign(const bigint& divisor) -> int { + FMT_ASSERT(this != &divisor, ""); + if (compare(*this, divisor) < 0) return 0; + FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); + align(divisor); + int quotient = 0; + do { + subtract_aligned(divisor); + ++quotient; + } while (compare(*this, divisor) >= 0); + return quotient; + } +}; + +// format_dragon flags. +enum dragon { + predecessor_closer = 1, + fixup = 2, // Run fixup to correct exp10 which can be off by one. + fixed = 4, +}; + +// Formats a floating-point number using a variation of the Fixed-Precision +// Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White: +// https://fmt.dev/papers/p372-steele.pdf. +FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, + unsigned flags, int num_digits, + buffer& buf, int& exp10) { + bigint numerator; // 2 * R in (FPP)^2. + bigint denominator; // 2 * S in (FPP)^2. + // lower and upper are differences between value and corresponding boundaries. + bigint lower; // (M^- in (FPP)^2). + bigint upper_store; // upper's value if different from lower. + bigint* upper = nullptr; // (M^+ in (FPP)^2). + // Shift numerator and denominator by an extra bit or two (if lower boundary + // is closer) to make lower and upper integers. This eliminates multiplication + // by 2 during later computations. + bool is_predecessor_closer = (flags & dragon::predecessor_closer) != 0; + int shift = is_predecessor_closer ? 2 : 1; + if (value.e >= 0) { + numerator = value.f; + numerator <<= value.e + shift; + lower = 1; + lower <<= value.e; + if (is_predecessor_closer) { + upper_store = 1; + upper_store <<= value.e + 1; + upper = &upper_store; + } + denominator.assign_pow10(exp10); + denominator <<= shift; + } else if (exp10 < 0) { + numerator.assign_pow10(-exp10); + lower.assign(numerator); + if (is_predecessor_closer) { + upper_store.assign(numerator); + upper_store <<= 1; + upper = &upper_store; + } + numerator *= value.f; + numerator <<= shift; + denominator = 1; + denominator <<= shift - value.e; + } else { + numerator = value.f; + numerator <<= shift; + denominator.assign_pow10(exp10); + denominator <<= shift - value.e; + lower = 1; + if (is_predecessor_closer) { + upper_store = 1ULL << 1; + upper = &upper_store; + } + } + int even = static_cast((value.f & 1) == 0); + if (!upper) upper = &lower; + bool shortest = num_digits < 0; + if ((flags & dragon::fixup) != 0) { + if (add_compare(numerator, *upper, denominator) + even <= 0) { + --exp10; + numerator *= 10; + if (num_digits < 0) { + lower *= 10; + if (upper != &lower) *upper *= 10; + } + } + if ((flags & dragon::fixed) != 0) adjust_precision(num_digits, exp10 + 1); + } + // Invariant: value == (numerator / denominator) * pow(10, exp10). + if (shortest) { + // Generate the shortest representation. + num_digits = 0; + char* data = buf.data(); + for (;;) { + int digit = numerator.divmod_assign(denominator); + bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. + // numerator + upper >[=] pow10: + bool high = add_compare(numerator, *upper, denominator) + even > 0; + data[num_digits++] = static_cast('0' + digit); + if (low || high) { + if (!low) { + ++data[num_digits - 1]; + } else if (high) { + int result = add_compare(numerator, numerator, denominator); + // Round half to even. + if (result > 0 || (result == 0 && (digit % 2) != 0)) + ++data[num_digits - 1]; + } + buf.try_resize(to_unsigned(num_digits)); + exp10 -= num_digits - 1; + return; + } + numerator *= 10; + lower *= 10; + if (upper != &lower) *upper *= 10; + } + } + // Generate the given number of digits. + exp10 -= num_digits - 1; + if (num_digits <= 0) { + auto digit = '0'; + if (num_digits == 0) { + denominator *= 10; + digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; + } + buf.push_back(digit); + return; + } + buf.try_resize(to_unsigned(num_digits)); + for (int i = 0; i < num_digits - 1; ++i) { + int digit = numerator.divmod_assign(denominator); + buf[i] = static_cast('0' + digit); + numerator *= 10; + } + int digit = numerator.divmod_assign(denominator); + auto result = add_compare(numerator, numerator, denominator); + if (result > 0 || (result == 0 && (digit % 2) != 0)) { + if (digit == 9) { + const auto overflow = '0' + 10; + buf[num_digits - 1] = overflow; + // Propagate the carry. + for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] == overflow) { + buf[0] = '1'; + if ((flags & dragon::fixed) != 0) + buf.push_back('0'); + else + ++exp10; + } + return; + } + ++digit; + } + buf[num_digits - 1] = static_cast('0' + digit); +} + +// Formats a floating-point number using the hexfloat format. +template ::value)> +FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, + buffer& buf) { + // float is passed as double to reduce the number of instantiations and to + // simplify implementation. + static_assert(!std::is_same::value, ""); + + using info = dragonbox::float_info; + + // Assume Float is in the format [sign][exponent][significand]. + using carrier_uint = typename info::carrier_uint; + + const auto num_float_significand_bits = detail::num_significand_bits(); + + basic_fp f(value); + f.e += num_float_significand_bits; + if (!has_implicit_bit()) --f.e; + + const auto num_fraction_bits = + num_float_significand_bits + (has_implicit_bit() ? 1 : 0); + const auto num_xdigits = (num_fraction_bits + 3) / 4; + + const auto leading_shift = ((num_xdigits - 1) * 4); + const auto leading_mask = carrier_uint(0xF) << leading_shift; + const auto leading_xdigit = + static_cast((f.f & leading_mask) >> leading_shift); + if (leading_xdigit > 1) f.e -= (32 - countl_zero(leading_xdigit) - 1); + + int print_xdigits = num_xdigits - 1; + if (specs.precision >= 0 && print_xdigits > specs.precision) { + const int shift = ((print_xdigits - specs.precision - 1) * 4); + const auto mask = carrier_uint(0xF) << shift; + const auto v = static_cast((f.f & mask) >> shift); + + if (v >= 8) { + const auto inc = carrier_uint(1) << (shift + 4); + f.f += inc; + f.f &= ~(inc - 1); + } + + // Check long double overflow + if (!has_implicit_bit()) { + const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; + if ((f.f & implicit_bit) == implicit_bit) { + f.f >>= 4; + f.e += 4; + } + } + + print_xdigits = specs.precision; + } + + char xdigits[num_bits() / 4]; + detail::fill_n(xdigits, sizeof(xdigits), '0'); + format_base2e(4, xdigits, f.f, num_xdigits, specs.upper()); + + // Remove zero tail + while (print_xdigits > 0 && xdigits[print_xdigits] == '0') --print_xdigits; + + buf.push_back('0'); + buf.push_back(specs.upper() ? 'X' : 'x'); + buf.push_back(xdigits[0]); + if (specs.alt() || print_xdigits > 0 || print_xdigits < specs.precision) + buf.push_back('.'); + buf.append(xdigits + 1, xdigits + 1 + print_xdigits); + for (; print_xdigits < specs.precision; ++print_xdigits) buf.push_back('0'); + + buf.push_back(specs.upper() ? 'P' : 'p'); + + uint32_t abs_e; + if (f.e < 0) { + buf.push_back('-'); + abs_e = static_cast(-f.e); + } else { + buf.push_back('+'); + abs_e = static_cast(f.e); + } + format_decimal(appender(buf), abs_e, detail::count_digits(abs_e)); +} + +template ::value)> +FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, + buffer& buf) { + format_hexfloat(static_cast(value), specs, buf); +} + +constexpr auto fractional_part_rounding_thresholds(int index) -> uint32_t { + // For checking rounding thresholds. + // The kth entry is chosen to be the smallest integer such that the + // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k. + // It is equal to ceil(2^31 + 2^32/10^(k + 1)). + // These are stored in a string literal because we cannot have static arrays + // in constexpr functions and non-static ones are poorly optimized. + return U"\x9999999a\x828f5c29\x80418938\x80068db9\x8000a7c6\x800010c7" + U"\x800001ae\x8000002b"[index]; +} + +template +FMT_CONSTEXPR20 auto format_float(Float value, int precision, + const format_specs& specs, bool binary32, + buffer& buf) -> int { + // float is passed as double to reduce the number of instantiations. + static_assert(!std::is_same::value, ""); + auto converted_value = convert_float(value); + + const bool fixed = specs.type() == presentation_type::fixed; + if (value == 0) { + if (precision <= 0 || !fixed) { + buf.push_back('0'); + return 0; + } + buf.try_resize(to_unsigned(precision)); + fill_n(buf.data(), precision, '0'); + return -precision; + } + + int exp = 0; + bool use_dragon = true; + unsigned dragon_flags = 0; + if (!is_fast_float() || is_constant_evaluated()) { + const auto inv_log2_10 = 0.3010299956639812; // 1 / log2(10) + using info = dragonbox::float_info; + const auto f = basic_fp(converted_value); + // Compute exp, an approximate power of 10, such that + // 10^(exp - 1) <= value < 10^exp or 10^exp <= value < 10^(exp + 1). + // This is based on log10(value) == log2(value) / log2(10) and approximation + // of log2(value) by e + num_fraction_bits idea from double-conversion. + auto e = (f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10; + exp = static_cast(e); + if (e > exp) ++exp; // Compute ceil. + dragon_flags = dragon::fixup; + } else { + // Extract significand bits and exponent bits. + using info = dragonbox::float_info; + auto br = bit_cast(static_cast(value)); + + const uint64_t significand_mask = + (static_cast(1) << num_significand_bits()) - 1; + uint64_t significand = (br & significand_mask); + int exponent = static_cast((br & exponent_mask()) >> + num_significand_bits()); + + if (exponent != 0) { // Check if normal. + exponent -= exponent_bias() + num_significand_bits(); + significand |= + (static_cast(1) << num_significand_bits()); + significand <<= 1; + } else { + // Normalize subnormal inputs. + FMT_ASSERT(significand != 0, "zeros should not appear here"); + int shift = countl_zero(significand); + FMT_ASSERT(shift >= num_bits() - num_significand_bits(), + ""); + shift -= (num_bits() - num_significand_bits() - 2); + exponent = (std::numeric_limits::min_exponent - + num_significand_bits()) - + shift; + significand <<= shift; + } + + // Compute the first several nonzero decimal significand digits. + // We call the number we get the first segment. + const int k = info::kappa - dragonbox::floor_log10_pow2(exponent); + exp = -k; + const int beta = exponent + dragonbox::floor_log2_pow10(k); + uint64_t first_segment; + bool has_more_segments; + int digits_in_the_first_segment; + { + const auto r = dragonbox::umul192_upper128( + significand << beta, dragonbox::get_cached_power(k)); + first_segment = r.high(); + has_more_segments = r.low() != 0; + + // The first segment can have 18 ~ 19 digits. + if (first_segment >= 1000000000000000000ULL) { + digits_in_the_first_segment = 19; + } else { + // When it is of 18-digits, we align it to 19-digits by adding a bogus + // zero at the end. + digits_in_the_first_segment = 18; + first_segment *= 10; + } + } + + // Compute the actual number of decimal digits to print. + if (fixed) adjust_precision(precision, exp + digits_in_the_first_segment); + + // Use Dragon4 only when there might be not enough digits in the first + // segment. + if (digits_in_the_first_segment > precision) { + use_dragon = false; + + if (precision <= 0) { + exp += digits_in_the_first_segment; + + if (precision < 0) { + // Nothing to do, since all we have are just leading zeros. + buf.try_resize(0); + } else { + // We may need to round-up. + buf.try_resize(1); + if ((first_segment | static_cast(has_more_segments)) > + 5000000000000000000ULL) { + buf[0] = '1'; + } else { + buf[0] = '0'; + } + } + } // precision <= 0 + else { + exp += digits_in_the_first_segment - precision; + + // When precision > 0, we divide the first segment into three + // subsegments, each with 9, 9, and 0 ~ 1 digits so that each fits + // in 32-bits which usually allows faster calculation than in + // 64-bits. Since some compiler (e.g. MSVC) doesn't know how to optimize + // division-by-constant for large 64-bit divisors, we do it here + // manually. The magic number 7922816251426433760 below is equal to + // ceil(2^(64+32) / 10^10). + const uint32_t first_subsegment = static_cast( + dragonbox::umul128_upper64(first_segment, 7922816251426433760ULL) >> + 32); + const uint64_t second_third_subsegments = + first_segment - first_subsegment * 10000000000ULL; + + uint64_t prod; + uint32_t digits; + bool should_round_up; + int number_of_digits_to_print = min_of(precision, 9); + + // Print a 9-digits subsegment, either the first or the second. + auto print_subsegment = [&](uint32_t subsegment, char* buffer) { + int number_of_digits_printed = 0; + + // If we want to print an odd number of digits from the subsegment, + if ((number_of_digits_to_print & 1) != 0) { + // Convert to 64-bit fixed-point fractional form with 1-digit + // integer part. The magic number 720575941 is a good enough + // approximation of 2^(32 + 24) / 10^8; see + // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case + // for details. + prod = ((subsegment * static_cast(720575941)) >> 24) + 1; + digits = static_cast(prod >> 32); + *buffer = static_cast('0' + digits); + number_of_digits_printed++; + } + // If we want to print an even number of digits from the + // first_subsegment, + else { + // Convert to 64-bit fixed-point fractional form with 2-digits + // integer part. The magic number 450359963 is a good enough + // approximation of 2^(32 + 20) / 10^7; see + // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case + // for details. + prod = ((subsegment * static_cast(450359963)) >> 20) + 1; + digits = static_cast(prod >> 32); + write2digits(buffer, digits); + number_of_digits_printed += 2; + } + + // Print all digit pairs. + while (number_of_digits_printed < number_of_digits_to_print) { + prod = static_cast(prod) * static_cast(100); + digits = static_cast(prod >> 32); + write2digits(buffer + number_of_digits_printed, digits); + number_of_digits_printed += 2; + } + }; + + // Print first subsegment. + print_subsegment(first_subsegment, buf.data()); + + // Perform rounding if the first subsegment is the last subsegment to + // print. + if (precision <= 9) { + // Rounding inside the subsegment. + // We round-up if: + // - either the fractional part is strictly larger than 1/2, or + // - the fractional part is exactly 1/2 and the last digit is odd. + // We rely on the following observations: + // - If fractional_part >= threshold, then the fractional part is + // strictly larger than 1/2. + // - If the MSB of fractional_part is set, then the fractional part + // must be at least 1/2. + // - When the MSB of fractional_part is set, either + // second_third_subsegments being nonzero or has_more_segments + // being true means there are further digits not printed, so the + // fractional part is strictly larger than 1/2. + if (precision < 9) { + uint32_t fractional_part = static_cast(prod); + should_round_up = + fractional_part >= fractional_part_rounding_thresholds( + 8 - number_of_digits_to_print) || + ((fractional_part >> 31) & + ((digits & 1) | (second_third_subsegments != 0) | + has_more_segments)) != 0; + } + // Rounding at the subsegment boundary. + // In this case, the fractional part is at least 1/2 if and only if + // second_third_subsegments >= 5000000000ULL, and is strictly larger + // than 1/2 if we further have either second_third_subsegments > + // 5000000000ULL or has_more_segments == true. + else { + should_round_up = second_third_subsegments > 5000000000ULL || + (second_third_subsegments == 5000000000ULL && + ((digits & 1) != 0 || has_more_segments)); + } + } + // Otherwise, print the second subsegment. + else { + // Compilers are not aware of how to leverage the maximum value of + // second_third_subsegments to find out a better magic number which + // allows us to eliminate an additional shift. 1844674407370955162 = + // ceil(2^64/10) < ceil(2^64*(10^9/(10^10 - 1))). + const uint32_t second_subsegment = + static_cast(dragonbox::umul128_upper64( + second_third_subsegments, 1844674407370955162ULL)); + const uint32_t third_subsegment = + static_cast(second_third_subsegments) - + second_subsegment * 10; + + number_of_digits_to_print = precision - 9; + print_subsegment(second_subsegment, buf.data() + 9); + + // Rounding inside the subsegment. + if (precision < 18) { + // The condition third_subsegment != 0 implies that the segment was + // of 19 digits, so in this case the third segment should be + // consisting of a genuine digit from the input. + uint32_t fractional_part = static_cast(prod); + should_round_up = + fractional_part >= fractional_part_rounding_thresholds( + 8 - number_of_digits_to_print) || + ((fractional_part >> 31) & + ((digits & 1) | (third_subsegment != 0) | + has_more_segments)) != 0; + } + // Rounding at the subsegment boundary. + else { + // In this case, the segment must be of 19 digits, thus + // the third subsegment should be consisting of a genuine digit from + // the input. + should_round_up = third_subsegment > 5 || + (third_subsegment == 5 && + ((digits & 1) != 0 || has_more_segments)); + } + } + + // Round-up if necessary. + if (should_round_up) { + ++buf[precision - 1]; + for (int i = precision - 1; i > 0 && buf[i] > '9'; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] > '9') { + buf[0] = '1'; + if (fixed) + buf[precision++] = '0'; + else + ++exp; + } + } + buf.try_resize(to_unsigned(precision)); + } + } // if (digits_in_the_first_segment > precision) + else { + // Adjust the exponent for its use in Dragon4. + exp += digits_in_the_first_segment - 1; + } + } + if (use_dragon) { + auto f = basic_fp(); + bool is_predecessor_closer = binary32 ? f.assign(static_cast(value)) + : f.assign(converted_value); + if (is_predecessor_closer) dragon_flags |= dragon::predecessor_closer; + if (fixed) dragon_flags |= dragon::fixed; + // Limit precision to the maximum possible number of significant digits in + // an IEEE754 double because we don't need to generate zeros. + const int max_double_digits = 767; + if (precision > max_double_digits) precision = max_double_digits; + format_dragon(f, dragon_flags, precision, buf, exp); + } + if (!fixed && !specs.alt()) { + // Remove trailing zeros. + auto num_digits = buf.size(); + while (num_digits > 0 && buf[num_digits - 1] == '0') { + --num_digits; + ++exp; + } + buf.try_resize(num_digits); + } + return exp; +} + +template +FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs, + locale_ref loc) -> OutputIt { + // Use signbit because value < 0 is false for NaN. + sign s = detail::signbit(value) ? sign::minus : specs.sign(); + + if (!detail::isfinite(value)) + return write_nonfinite(out, detail::isnan(value), specs, s); + + if (specs.align() == align::numeric && s != sign::none) { + *out++ = detail::getsign(s); + s = sign::none; + if (specs.width != 0) --specs.width; + } + + int precision = specs.precision; + if (precision < 0) { + if (specs.type() != presentation_type::none) { + precision = 6; + } else if (is_fast_float::value && !is_constant_evaluated()) { + // Use Dragonbox for the shortest format. + using floaty = conditional_t= sizeof(double), double, float>; + auto dec = dragonbox::to_decimal(static_cast(value)); + return write_float(out, dec, specs, s, loc); + } + } + + memory_buffer buffer; + if (specs.type() == presentation_type::hexfloat) { + if (s != sign::none) buffer.push_back(detail::getsign(s)); + format_hexfloat(convert_float(value), specs, buffer); + return write_bytes(out, {buffer.data(), buffer.size()}, + specs); + } + + if (specs.type() == presentation_type::exp) { + if (precision == max_value()) + report_error("number is too big"); + else + ++precision; + if (specs.precision != 0) specs.set_alt(); + } else if (specs.type() == presentation_type::fixed) { + if (specs.precision != 0) specs.set_alt(); + } else if (precision == 0) { + precision = 1; + } + int exp = format_float(convert_float(value), precision, specs, + std::is_same(), buffer); + + specs.precision = precision; + auto f = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; + return write_float(out, f, specs, s, loc); +} + +template ::value)> +FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs, + locale_ref loc = {}) -> OutputIt { + return specs.localized() && write_loc(out, value, specs, loc) + ? out + : write_float(out, value, specs, loc); +} + +template ::value)> +FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { + if (is_constant_evaluated()) return write(out, value, format_specs()); + + auto s = detail::signbit(value) ? sign::minus : sign::none; + + constexpr auto specs = format_specs(); + using floaty = conditional_t= sizeof(double), double, float>; + using floaty_uint = typename dragonbox::float_info::carrier_uint; + floaty_uint mask = exponent_mask(); + if ((bit_cast(value) & mask) == mask) + return write_nonfinite(out, std::isnan(value), specs, s); + + auto dec = dragonbox::to_decimal(static_cast(value)); + return write_float(out, dec, specs, s, {}); +} + +template ::value && + !is_fast_float::value)> +inline auto write(OutputIt out, T value) -> OutputIt { + return write(out, value, format_specs()); +} + +template +auto write(OutputIt out, monostate, format_specs = {}, locale_ref = {}) + -> OutputIt { + FMT_ASSERT(false, ""); + return out; +} + +template +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view value) + -> OutputIt { + return copy_noinline(value.begin(), value.end(), out); +} + +template ::value)> +constexpr auto write(OutputIt out, const T& value) -> OutputIt { + return write(out, to_string_view(value)); +} + +// FMT_ENABLE_IF() condition separated to workaround an MSVC bug. +template < + typename Char, typename OutputIt, typename T, + bool check = std::is_enum::value && !std::is_same::value && + mapped_type_constant::value != type::custom_type, + FMT_ENABLE_IF(check)> +FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { + return write(out, static_cast>(value)); +} + +template ::value)> +FMT_CONSTEXPR auto write(OutputIt out, T value, const format_specs& specs = {}, + locale_ref = {}) -> OutputIt { + return specs.type() != presentation_type::none && + specs.type() != presentation_type::string + ? write(out, value ? 1 : 0, specs, {}) + : write_bytes(out, value ? "true" : "false", specs); +} + +template +FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt { + auto it = reserve(out, 1); + *it++ = value; + return base_iterator(out, it); +} + +template +FMT_CONSTEXPR20 auto write(OutputIt out, const Char* value) -> OutputIt { + if (value) return write(out, basic_string_view(value)); + report_error("string pointer is null"); + return out; +} + +template ::value)> +auto write(OutputIt out, const T* value, const format_specs& specs = {}, + locale_ref = {}) -> OutputIt { + return write_ptr(out, bit_cast(value), &specs); +} + +template ::value == + type::custom_type && + !std::is_fundamental::value)> +FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> OutputIt { + auto f = formatter(); + auto parse_ctx = parse_context({}); + f.parse(parse_ctx); + auto ctx = basic_format_context(out, {}, {}); + return f.format(value, ctx); +} + +template +using is_builtin = + bool_constant::value || FMT_BUILTIN_TYPES>; + +// An argument visitor that formats the argument and writes it via the output +// iterator. It's a class and not a generic lambda for compatibility with C++11. +template struct default_arg_formatter { + using context = buffered_context; + + basic_appender out; + + void operator()(monostate) { report_error("argument not found"); } + + template ::value)> + void operator()(T value) { + write(out, value); + } + + template ::value)> + void operator()(T) { + FMT_ASSERT(false, ""); + } + + void operator()(typename basic_format_arg::handle h) { + // Use a null locale since the default format must be unlocalized. + auto parse_ctx = parse_context({}); + auto format_ctx = context(out, {}, {}); + h.format(parse_ctx, format_ctx); + } +}; + +template struct arg_formatter { + basic_appender out; + const format_specs& specs; + FMT_NO_UNIQUE_ADDRESS locale_ref locale; + + template ::value)> + FMT_CONSTEXPR FMT_INLINE void operator()(T value) { + detail::write(out, value, specs, locale); + } + + template ::value)> + void operator()(T) { + FMT_ASSERT(false, ""); + } + + void operator()(typename basic_format_arg>::handle) { + // User-defined types are handled separately because they require access + // to the parse context. + } +}; + +struct dynamic_spec_getter { + template ::value)> + FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { + return is_negative(value) ? ~0ull : static_cast(value); + } + + template ::value)> + FMT_CONSTEXPR auto operator()(T) -> unsigned long long { + report_error("width/precision is not integer"); + return 0; + } +}; + +template +FMT_CONSTEXPR auto get_arg(Context& ctx, ID id) -> basic_format_arg { + auto arg = ctx.arg(id); + if (!arg) report_error("argument not found"); + return arg; +} + +template +FMT_CONSTEXPR int get_dynamic_spec( + arg_id_kind kind, const arg_ref& ref, + Context& ctx) { + FMT_ASSERT(kind != arg_id_kind::none, ""); + auto arg = + kind == arg_id_kind::index ? ctx.arg(ref.index) : ctx.arg(ref.name); + if (!arg) report_error("argument not found"); + unsigned long long value = arg.visit(dynamic_spec_getter()); + if (value > to_unsigned(max_value())) + report_error("width/precision is out of range"); + return static_cast(value); +} + +template +FMT_CONSTEXPR void handle_dynamic_spec( + arg_id_kind kind, int& value, + const arg_ref& ref, Context& ctx) { + if (kind != arg_id_kind::none) value = get_dynamic_spec(kind, ref, ctx); +} + +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template Str> +struct static_named_arg : view { + static constexpr auto name = Str.data; + + const T& value; + static_named_arg(const T& v) : value(v) {} +}; + +template Str> +struct is_named_arg> : std::true_type {}; + +template Str> +struct is_static_named_arg> : std::true_type { +}; + +template Str> +struct udl_arg { + template auto operator=(T&& value) const { + return static_named_arg(std::forward(value)); + } +}; +#else +template struct udl_arg { + const Char* str; + + template auto operator=(T&& value) const -> named_arg { + return {str, std::forward(value)}; + } +}; +#endif // FMT_USE_NONTYPE_TEMPLATE_ARGS + +template struct format_handler { + parse_context parse_ctx; + buffered_context ctx; + + void on_text(const Char* begin, const Char* end) { + copy_noinline(begin, end, ctx.out()); + } + + FMT_CONSTEXPR auto on_arg_id() -> int { return parse_ctx.next_arg_id(); } + FMT_CONSTEXPR auto on_arg_id(int id) -> int { + parse_ctx.check_arg_id(id); + return id; + } + FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { + parse_ctx.check_arg_id(id); + int arg_id = ctx.arg_id(id); + if (arg_id < 0) report_error("argument not found"); + return arg_id; + } + + FMT_INLINE void on_replacement_field(int id, const Char*) { + ctx.arg(id).visit(default_arg_formatter{ctx.out()}); + } + + auto on_format_specs(int id, const Char* begin, const Char* end) + -> const Char* { + auto arg = get_arg(ctx, id); + // Not using a visitor for custom types gives better codegen. + if (arg.format_custom(begin, parse_ctx, ctx)) return parse_ctx.begin(); + + auto specs = dynamic_format_specs(); + begin = parse_format_specs(begin, end, specs, parse_ctx, arg.type()); + if (specs.dynamic()) { + handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref, + ctx); + handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, ctx); + } + + arg.visit(arg_formatter{ctx.out(), specs, ctx.locale()}); + return begin; + } + + FMT_NORETURN void on_error(const char* message) { report_error(message); } +}; + +using format_func = void (*)(detail::buffer&, int, const char*); +FMT_API void do_report_error(format_func func, int error_code, + const char* message) noexcept; + +FMT_API void format_error_code(buffer& out, int error_code, + string_view message) noexcept; + +template +template +FMT_CONSTEXPR auto native_formatter::format( + const T& val, FormatContext& ctx) const -> decltype(ctx.out()) { + if (!specs_.dynamic()) + return write(ctx.out(), val, specs_, ctx.locale()); + auto specs = format_specs(specs_); + handle_dynamic_spec(specs.dynamic_width(), specs.width, specs_.width_ref, + ctx); + handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs_.precision_ref, ctx); + return write(ctx.out(), val, specs, ctx.locale()); +} + +// DEPRECATED! +template struct vformat_args { + using type = basic_format_args>; +}; +template <> struct vformat_args { + using type = format_args; +}; + +template +void vformat_to(buffer& buf, basic_string_view fmt, + typename vformat_args::type args, locale_ref loc = {}) { + auto out = basic_appender(buf); + parse_format_string( + fmt, format_handler{parse_context(fmt), {out, args, loc}}); +} +} // namespace detail + +FMT_BEGIN_EXPORT + +// A generic formatting context with custom output iterator and character +// (code unit) support. Char is the format string code unit type which can be +// different from OutputIt::value_type. +template class generic_context { + private: + OutputIt out_; + basic_format_args args_; + detail::locale_ref loc_; + + public: + using char_type = Char; + using iterator = OutputIt; + using parse_context_type FMT_DEPRECATED = parse_context; + template + using formatter_type FMT_DEPRECATED = formatter; + enum { builtin_types = FMT_BUILTIN_TYPES }; + + constexpr generic_context(OutputIt out, + basic_format_args args, + detail::locale_ref loc = {}) + : out_(out), args_(args), loc_(loc) {} + generic_context(generic_context&&) = default; + generic_context(const generic_context&) = delete; + void operator=(const generic_context&) = delete; + + constexpr auto arg(int id) const -> basic_format_arg { + return args_.get(id); + } + auto arg(basic_string_view name) const + -> basic_format_arg { + return args_.get(name); + } + constexpr auto arg_id(basic_string_view name) const -> int { + return args_.get_id(name); + } + + constexpr auto out() const -> iterator { return out_; } + + void advance_to(iterator it) { + if (!detail::is_back_insert_iterator()) out_ = it; + } + + constexpr auto locale() const -> detail::locale_ref { return loc_; } +}; + +class loc_value { + private: + basic_format_arg value_; + + public: + template ::value)> + loc_value(T value) : value_(value) {} + + template ::value)> + loc_value(T) {} + + template auto visit(Visitor&& vis) -> decltype(vis(0)) { + return value_.visit(vis); + } +}; + +// A locale facet that formats values in UTF-8. +// It is parameterized on the locale to avoid the heavy include. +template class format_facet : public Locale::facet { + private: + std::string separator_; + std::string grouping_; + std::string decimal_point_; + + protected: + virtual auto do_put(appender out, loc_value val, + const format_specs& specs) const -> bool; + + public: + static FMT_API typename Locale::id id; + + explicit format_facet(Locale& loc); + explicit format_facet(string_view sep = "", std::string grouping = "\3", + std::string decimal_point = ".") + : separator_(sep.data(), sep.size()), + grouping_(grouping), + decimal_point_(decimal_point) {} + + auto put(appender out, loc_value val, const format_specs& specs) const + -> bool { + return do_put(out, val, specs); + } +}; + +#define FMT_FORMAT_AS(Type, Base) \ + template \ + struct formatter : formatter { \ + template \ + FMT_CONSTEXPR auto format(Type value, FormatContext& ctx) const \ + -> decltype(ctx.out()) { \ + return formatter::format(value, ctx); \ + } \ + } + +FMT_FORMAT_AS(signed char, int); +FMT_FORMAT_AS(unsigned char, unsigned); +FMT_FORMAT_AS(short, int); +FMT_FORMAT_AS(unsigned short, unsigned); +FMT_FORMAT_AS(long, detail::long_type); +FMT_FORMAT_AS(unsigned long, detail::ulong_type); +FMT_FORMAT_AS(Char*, const Char*); +FMT_FORMAT_AS(detail::std_string_view, basic_string_view); +FMT_FORMAT_AS(std::nullptr_t, const void*); +FMT_FORMAT_AS(void*, const void*); + +template +struct formatter : formatter, Char> {}; + +template +class formatter, Char> + : public formatter, Char> {}; + +template +struct formatter, Char> : formatter {}; +template +struct formatter, Char> + : formatter {}; + +template +struct formatter + : detail::native_formatter {}; + +template +struct formatter>> + : formatter, Char> { + template + FMT_CONSTEXPR auto format(const T& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto&& val = format_as(value); // Make an lvalue reference for format. + return formatter, Char>::format(val, ctx); + } +}; + +/** + * Converts `p` to `const void*` for pointer formatting. + * + * **Example**: + * + * auto s = fmt::format("{}", fmt::ptr(p)); + */ +template auto ptr(T p) -> const void* { + static_assert(std::is_pointer::value, ""); + return detail::bit_cast(p); +} + +/** + * Converts `e` to the underlying type. + * + * **Example**: + * + * enum class color { red, green, blue }; + * auto s = fmt::format("{}", fmt::underlying(color::red)); // s == "0" + */ +template +constexpr auto underlying(Enum e) noexcept -> underlying_t { + return static_cast>(e); +} + +namespace enums { +template ::value)> +constexpr auto format_as(Enum e) noexcept -> underlying_t { + return static_cast>(e); +} +} // namespace enums + +#ifdef __cpp_lib_byte +template <> struct formatter : formatter { + static auto format_as(std::byte b) -> unsigned char { + return static_cast(b); + } + template + auto format(std::byte b, Context& ctx) const -> decltype(ctx.out()) { + return formatter::format(format_as(b), ctx); + } +}; +#endif + +struct bytes { + string_view data; + + inline explicit bytes(string_view s) : data(s) {} +}; + +template <> struct formatter { + private: + detail::dynamic_format_specs<> specs_; + + public: + FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { + return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, + detail::type::string_type); + } + + template + auto format(bytes b, FormatContext& ctx) const -> decltype(ctx.out()) { + auto specs = specs_; + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, + specs.width_ref, ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, ctx); + return detail::write_bytes(ctx.out(), b.data, specs); + } +}; + +// group_digits_view is not derived from view because it copies the argument. +template struct group_digits_view { + T value; +}; + +/** + * Returns a view that formats an integer value using ',' as a + * locale-independent thousands separator. + * + * **Example**: + * + * fmt::print("{}", fmt::group_digits(12345)); + * // Output: "12,345" + */ +template auto group_digits(T value) -> group_digits_view { + return {value}; +} + +template struct formatter> : formatter { + private: + detail::dynamic_format_specs<> specs_; + + public: + FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { + return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, + detail::type::int_type); + } + + template + auto format(group_digits_view view, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto specs = specs_; + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, + specs.width_ref, ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, ctx); + auto arg = detail::make_write_int_arg(view.value, specs.sign()); + return detail::write_int( + ctx.out(), static_cast>(arg.abs_value), + arg.prefix, specs, detail::digit_grouping("\3", ",")); + } +}; + +template struct nested_view { + const formatter* fmt; + const T* value; +}; + +template +struct formatter, Char> { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + template + auto format(nested_view view, FormatContext& ctx) const + -> decltype(ctx.out()) { + return view.fmt->format(*view.value, ctx); + } +}; + +template struct nested_formatter { + private: + basic_specs specs_; + int width_; + formatter formatter_; + + public: + constexpr nested_formatter() : width_(0) {} + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it == end) return it; + auto specs = format_specs(); + it = detail::parse_align(it, end, specs); + specs_ = specs; + Char c = *it; + auto width_ref = detail::arg_ref(); + if ((c >= '0' && c <= '9') || c == '{') { + it = detail::parse_width(it, end, specs, width_ref, ctx); + width_ = specs.width; + } + ctx.advance_to(it); + return formatter_.parse(ctx); + } + + template + auto write_padded(FormatContext& ctx, F write) const -> decltype(ctx.out()) { + if (width_ == 0) return write(ctx.out()); + auto buf = basic_memory_buffer(); + write(basic_appender(buf)); + auto specs = format_specs(); + specs.width = width_; + specs.set_fill( + basic_string_view(specs_.fill(), specs_.fill_size())); + specs.set_align(specs_.align()); + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } + + auto nested(const T& value) const -> nested_view { + return nested_view{&formatter_, &value}; + } +}; + +inline namespace literals { +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template constexpr auto operator""_a() { + using char_t = remove_cvref_t; + return detail::udl_arg(); +} +#else +/** + * User-defined literal equivalent of `fmt::arg`. + * + * **Example**: + * + * using namespace fmt::literals; + * fmt::print("The answer is {answer}.", "answer"_a=42); + */ +constexpr auto operator""_a(const char* s, size_t) -> detail::udl_arg { + return {s}; +} +#endif // FMT_USE_NONTYPE_TEMPLATE_ARGS +} // namespace literals + +/// A fast integer formatter. +class format_int { + private: + // Buffer should be large enough to hold all digits (digits10 + 1), + // a sign and a null character. + enum { buffer_size = std::numeric_limits::digits10 + 3 }; + mutable char buffer_[buffer_size]; + char* str_; + + template + FMT_CONSTEXPR20 auto format_unsigned(UInt value) -> char* { + auto n = static_cast>(value); + return detail::do_format_decimal(buffer_, n, buffer_size - 1); + } + + template + FMT_CONSTEXPR20 auto format_signed(Int value) -> char* { + auto abs_value = static_cast>(value); + bool negative = value < 0; + if (negative) abs_value = 0 - abs_value; + auto begin = format_unsigned(abs_value); + if (negative) *--begin = '-'; + return begin; + } + + public: + FMT_CONSTEXPR20 explicit format_int(int value) : str_(format_signed(value)) {} + FMT_CONSTEXPR20 explicit format_int(long value) + : str_(format_signed(value)) {} + FMT_CONSTEXPR20 explicit format_int(long long value) + : str_(format_signed(value)) {} + FMT_CONSTEXPR20 explicit format_int(unsigned value) + : str_(format_unsigned(value)) {} + FMT_CONSTEXPR20 explicit format_int(unsigned long value) + : str_(format_unsigned(value)) {} + FMT_CONSTEXPR20 explicit format_int(unsigned long long value) + : str_(format_unsigned(value)) {} + + /// Returns the number of characters written to the output buffer. + FMT_CONSTEXPR20 auto size() const -> size_t { + return detail::to_unsigned(buffer_ - str_ + buffer_size - 1); + } + + /// Returns a pointer to the output buffer content. No terminating null + /// character is appended. + FMT_CONSTEXPR20 auto data() const -> const char* { return str_; } + + /// Returns a pointer to the output buffer content with terminating null + /// character appended. + FMT_CONSTEXPR20 auto c_str() const -> const char* { + buffer_[buffer_size - 1] = '\0'; + return str_; + } + + /// Returns the content of the output buffer as an `std::string`. + inline auto str() const -> std::string { return {str_, size()}; } +}; + +#define FMT_STRING_IMPL(s, base) \ + [] { \ + /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ + /* Use a macro-like name to avoid shadowing warnings. */ \ + struct FMT_VISIBILITY("hidden") FMT_COMPILE_STRING : base { \ + using char_type = fmt::remove_cvref_t; \ + constexpr explicit operator fmt::basic_string_view() const { \ + return fmt::detail::compile_string_to_view(s); \ + } \ + }; \ + using FMT_STRING_VIEW = \ + fmt::basic_string_view; \ + fmt::detail::ignore_unused(FMT_STRING_VIEW(FMT_COMPILE_STRING())); \ + return FMT_COMPILE_STRING(); \ + }() + +/** + * Constructs a legacy compile-time format string from a string literal `s`. + * + * **Example**: + * + * // A compile-time error because 'd' is an invalid specifier for strings. + * std::string s = fmt::format(FMT_STRING("{:d}"), "foo"); + */ +#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string) + +FMT_API auto vsystem_error(int error_code, string_view fmt, format_args args) + -> std::system_error; + +/** + * Constructs `std::system_error` with a message formatted with + * `fmt::format(fmt, args...)`. + * `error_code` is a system error code as given by `errno`. + * + * **Example**: + * + * // This throws std::system_error with the description + * // cannot open file 'madeup': No such file or directory + * // or similar (system message may vary). + * const char* filename = "madeup"; + * FILE* file = fopen(filename, "r"); + * if (!file) + * throw fmt::system_error(errno, "cannot open file '{}'", filename); + */ +template +auto system_error(int error_code, format_string fmt, T&&... args) + -> std::system_error { + return vsystem_error(error_code, fmt.str, vargs{{args...}}); +} + +/** + * Formats an error message for an error returned by an operating system or a + * language runtime, for example a file opening error, and writes it to `out`. + * The format is the same as the one used by `std::system_error(ec, message)` + * where `ec` is `std::error_code(error_code, std::generic_category())`. + * It is implementation-defined but normally looks like: + * + * : + * + * where `` is the passed message and `` is the system + * message corresponding to the error code. + * `error_code` is a system error code as given by `errno`. + */ +FMT_API void format_system_error(detail::buffer& out, int error_code, + const char* message) noexcept; + +// Reports a system error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_system_error(int error_code, const char* message) noexcept; + +inline auto vformat(detail::locale_ref loc, string_view fmt, format_args args) + -> std::string { + auto buf = memory_buffer(); + detail::vformat_to(buf, fmt, args, loc); + return {buf.data(), buf.size()}; +} + +template +FMT_INLINE auto format(detail::locale_ref loc, format_string fmt, + T&&... args) -> std::string { + return vformat(loc, fmt.str, vargs{{args...}}); +} + +template ::value)> +auto vformat_to(OutputIt out, detail::locale_ref loc, string_view fmt, + format_args args) -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, fmt, args, loc); + return detail::get_iterator(buf, out); +} + +template ::value)> +FMT_INLINE auto format_to(OutputIt out, detail::locale_ref loc, + format_string fmt, T&&... args) -> OutputIt { + return fmt::vformat_to(out, loc, fmt.str, vargs{{args...}}); +} + +template +FMT_NODISCARD FMT_INLINE auto formatted_size(detail::locale_ref loc, + format_string fmt, + T&&... args) -> size_t { + auto buf = detail::counting_buffer<>(); + detail::vformat_to(buf, fmt.str, vargs{{args...}}, loc); + return buf.count(); +} + +FMT_API auto vformat(string_view fmt, format_args args) -> std::string; + +/** + * Formats `args` according to specifications in `fmt` and returns the result + * as a string. + * + * **Example**: + * + * #include + * std::string message = fmt::format("The answer is {}.", 42); + */ +template +FMT_NODISCARD FMT_INLINE auto format(format_string fmt, T&&... args) + -> std::string { + return vformat(fmt.str, vargs{{args...}}); +} + +/** + * Converts `value` to `std::string` using the default format for type `T`. + * + * **Example**: + * + * std::string answer = fmt::to_string(42); + */ +template ::value)> +FMT_NODISCARD auto to_string(T value) -> std::string { + // The buffer should be large enough to store the number including the sign + // or "false" for bool. + char buffer[max_of(detail::digits10() + 2, 5)]; + return {buffer, detail::write(buffer, value)}; +} + +template ::value)> +FMT_NODISCARD auto to_string(const T& value) -> std::string { + return to_string(format_as(value)); +} + +template ::value && + !detail::use_format_as::value)> +FMT_NODISCARD auto to_string(const T& value) -> std::string { + auto buffer = memory_buffer(); + detail::write(appender(buffer), value); + return {buffer.data(), buffer.size()}; +} + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#ifdef FMT_HEADER_ONLY +# define FMT_FUNC inline +# include "format-inl.h" +#endif + +// Restore _LIBCPP_REMOVE_TRANSITIVE_INCLUDES. +#ifdef FMT_REMOVE_TRANSITIVE_INCLUDES +# undef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +#endif + +#endif // FMT_FORMAT_H_ diff --git a/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/os.h b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/os.h new file mode 100644 index 000000000..b2cc5e4b8 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/os.h @@ -0,0 +1,427 @@ +// Formatting library for C++ - optional OS-specific functionality +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_OS_H_ +#define FMT_OS_H_ + +#include "format.h" + +#ifndef FMT_MODULE +# include +# include +# include +# include // std::system_error + +# if FMT_HAS_INCLUDE() +# include // LC_NUMERIC_MASK on macOS +# endif +#endif // FMT_MODULE + +#ifndef FMT_USE_FCNTL +// UWP doesn't provide _pipe. +# if FMT_HAS_INCLUDE("winapifamily.h") +# include +# endif +# if (FMT_HAS_INCLUDE() || defined(__APPLE__) || \ + defined(__linux__)) && \ + (!defined(WINAPI_FAMILY) || \ + (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) +# include // for O_RDONLY +# define FMT_USE_FCNTL 1 +# else +# define FMT_USE_FCNTL 0 +# endif +#endif + +#ifndef FMT_POSIX +# if defined(_WIN32) && !defined(__MINGW32__) +// Fix warnings about deprecated symbols. +# define FMT_POSIX(call) _##call +# else +# define FMT_POSIX(call) call +# endif +#endif + +// Calls to system functions are wrapped in FMT_SYSTEM for testability. +#ifdef FMT_SYSTEM +# define FMT_HAS_SYSTEM +# define FMT_POSIX_CALL(call) FMT_SYSTEM(call) +#else +# define FMT_SYSTEM(call) ::call +# ifdef _WIN32 +// Fix warnings about deprecated symbols. +# define FMT_POSIX_CALL(call) ::_##call +# else +# define FMT_POSIX_CALL(call) ::call +# endif +#endif + +// Retries the expression while it evaluates to error_result and errno +// equals to EINTR. +#ifndef _WIN32 +# define FMT_RETRY_VAL(result, expression, error_result) \ + do { \ + (result) = (expression); \ + } while ((result) == (error_result) && errno == EINTR) +#else +# define FMT_RETRY_VAL(result, expression, error_result) result = (expression) +#endif + +#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) + +FMT_BEGIN_NAMESPACE +FMT_BEGIN_EXPORT + +/** + * A reference to a null-terminated string. It can be constructed from a C + * string or `std::string`. + * + * You can use one of the following type aliases for common character types: + * + * +---------------+-----------------------------+ + * | Type | Definition | + * +===============+=============================+ + * | cstring_view | basic_cstring_view | + * +---------------+-----------------------------+ + * | wcstring_view | basic_cstring_view | + * +---------------+-----------------------------+ + * + * This class is most useful as a parameter type for functions that wrap C APIs. + */ +template class basic_cstring_view { + private: + const Char* data_; + + public: + /// Constructs a string reference object from a C string. + basic_cstring_view(const Char* s) : data_(s) {} + + /// Constructs a string reference from an `std::string` object. + basic_cstring_view(const std::basic_string& s) : data_(s.c_str()) {} + + /// Returns the pointer to a C string. + auto c_str() const -> const Char* { return data_; } +}; + +using cstring_view = basic_cstring_view; +using wcstring_view = basic_cstring_view; + +#ifdef _WIN32 +FMT_API const std::error_category& system_category() noexcept; + +namespace detail { +FMT_API void format_windows_error(buffer& out, int error_code, + const char* message) noexcept; +} + +FMT_API std::system_error vwindows_error(int error_code, string_view fmt, + format_args args); + +/** + * Constructs a `std::system_error` object with the description of the form + * + * : + * + * where `` is the formatted message and `` is the + * system message corresponding to the error code. + * `error_code` is a Windows error code as given by `GetLastError`. + * If `error_code` is not a valid error code such as -1, the system message + * will look like "error -1". + * + * **Example**: + * + * // This throws a system_error with the description + * // cannot open file 'madeup': The system cannot find the file + * specified. + * // or similar (system message may vary). + * const char *filename = "madeup"; + * LPOFSTRUCT of = LPOFSTRUCT(); + * HFILE file = OpenFile(filename, &of, OF_READ); + * if (file == HFILE_ERROR) { + * throw fmt::windows_error(GetLastError(), + * "cannot open file '{}'", filename); + * } + */ +template +auto windows_error(int error_code, string_view message, const T&... args) + -> std::system_error { + return vwindows_error(error_code, message, vargs{{args...}}); +} + +// Reports a Windows error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_windows_error(int error_code, const char* message) noexcept; +#else +inline auto system_category() noexcept -> const std::error_category& { + return std::system_category(); +} +#endif // _WIN32 + +// std::system is not available on some platforms such as iOS (#2248). +#ifdef __OSX__ +template > +void say(const S& fmt, Args&&... args) { + std::system(format("say \"{}\"", format(fmt, args...)).c_str()); +} +#endif + +// A buffered file. +class buffered_file { + private: + FILE* file_; + + friend class file; + + inline explicit buffered_file(FILE* f) : file_(f) {} + + public: + buffered_file(const buffered_file&) = delete; + void operator=(const buffered_file&) = delete; + + // Constructs a buffered_file object which doesn't represent any file. + inline buffered_file() noexcept : file_(nullptr) {} + + // Destroys the object closing the file it represents if any. + FMT_API ~buffered_file() noexcept; + + public: + inline buffered_file(buffered_file&& other) noexcept : file_(other.file_) { + other.file_ = nullptr; + } + + inline auto operator=(buffered_file&& other) -> buffered_file& { + close(); + file_ = other.file_; + other.file_ = nullptr; + return *this; + } + + // Opens a file. + FMT_API buffered_file(cstring_view filename, cstring_view mode); + + // Closes the file. + FMT_API void close(); + + // Returns the pointer to a FILE object representing this file. + inline auto get() const noexcept -> FILE* { return file_; } + + FMT_API auto descriptor() const -> int; + + template + inline void print(string_view fmt, const T&... args) { + fmt::vargs vargs = {{args...}}; + detail::is_locking() ? fmt::vprint_buffered(file_, fmt, vargs) + : fmt::vprint(file_, fmt, vargs); + } +}; + +#if FMT_USE_FCNTL + +// A file. Closed file is represented by a file object with descriptor -1. +// Methods that are not declared with noexcept may throw +// fmt::system_error in case of failure. Note that some errors such as +// closing the file multiple times will cause a crash on Windows rather +// than an exception. You can get standard behavior by overriding the +// invalid parameter handler with _set_invalid_parameter_handler. +class FMT_API file { + private: + int fd_; // File descriptor. + + // Constructs a file object with a given descriptor. + explicit file(int fd) : fd_(fd) {} + + friend struct pipe; + + public: + // Possible values for the oflag argument to the constructor. + enum { + RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. + WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. + RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing. + CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist. + APPEND = FMT_POSIX(O_APPEND), // Open in append mode. + TRUNC = FMT_POSIX(O_TRUNC) // Truncate the content of the file. + }; + + // Constructs a file object which doesn't represent any file. + inline file() noexcept : fd_(-1) {} + + // Opens a file and constructs a file object representing this file. + file(cstring_view path, int oflag); + + public: + file(const file&) = delete; + void operator=(const file&) = delete; + + inline file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; } + + // Move assignment is not noexcept because close may throw. + inline auto operator=(file&& other) -> file& { + close(); + fd_ = other.fd_; + other.fd_ = -1; + return *this; + } + + // Destroys the object closing the file it represents if any. + ~file() noexcept; + + // Returns the file descriptor. + inline auto descriptor() const noexcept -> int { return fd_; } + + // Closes the file. + void close(); + + // Returns the file size. The size has signed type for consistency with + // stat::st_size. + auto size() const -> long long; + + // Attempts to read count bytes from the file into the specified buffer. + auto read(void* buffer, size_t count) -> size_t; + + // Attempts to write count bytes from the specified buffer to the file. + auto write(const void* buffer, size_t count) -> size_t; + + // Duplicates a file descriptor with the dup function and returns + // the duplicate as a file object. + static auto dup(int fd) -> file; + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + void dup2(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + void dup2(int fd, std::error_code& ec) noexcept; + + // Creates a buffered_file object associated with this file and detaches + // this file object from the file. + auto fdopen(const char* mode) -> buffered_file; + +# if defined(_WIN32) && !defined(__MINGW32__) + // Opens a file and constructs a file object representing this file by + // wcstring_view filename. Windows only. + static file open_windows_file(wcstring_view path, int oflag); +# endif +}; + +struct FMT_API pipe { + file read_end; + file write_end; + + // Creates a pipe setting up read_end and write_end file objects for reading + // and writing respectively. + pipe(); +}; + +// Returns the memory page size. +auto getpagesize() -> long; + +namespace detail { + +struct buffer_size { + constexpr buffer_size() = default; + size_t value = 0; + FMT_CONSTEXPR auto operator=(size_t val) const -> buffer_size { + auto bs = buffer_size(); + bs.value = val; + return bs; + } +}; + +struct ostream_params { + int oflag = file::WRONLY | file::CREATE | file::TRUNC; + size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768; + + constexpr ostream_params() {} + + template + ostream_params(T... params, int new_oflag) : ostream_params(params...) { + oflag = new_oflag; + } + + template + ostream_params(T... params, detail::buffer_size bs) + : ostream_params(params...) { + this->buffer_size = bs.value; + } + +// Intel has a bug that results in failure to deduce a constructor +// for empty parameter packs. +# if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000 + ostream_params(int new_oflag) : oflag(new_oflag) {} + ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {} +# endif +}; + +} // namespace detail + +FMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size(); + +/// A fast buffered output stream for writing from a single thread. Writing from +/// multiple threads without external synchronization may result in a data race. +class FMT_API ostream : private detail::buffer { + private: + file file_; + + ostream(cstring_view path, const detail::ostream_params& params); + + static void grow(buffer& buf, size_t); + + public: + ostream(ostream&& other) noexcept; + ~ostream(); + + operator writer() { + detail::buffer& buf = *this; + return buf; + } + + inline void flush() { + if (size() == 0) return; + file_.write(data(), size() * sizeof(data()[0])); + clear(); + } + + template + friend auto output_file(cstring_view path, T... params) -> ostream; + + inline void close() { + flush(); + file_.close(); + } + + /// Formats `args` according to specifications in `fmt` and writes the + /// output to the file. + template void print(format_string fmt, T&&... args) { + vformat_to(appender(*this), fmt.str, vargs{{args...}}); + } +}; + +/** + * Opens a file for writing. Supported parameters passed in `params`: + * + * - ``: Flags passed to [open]( + * https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html) + * (`file::WRONLY | file::CREATE | file::TRUNC` by default) + * - `buffer_size=`: Output buffer size + * + * **Example**: + * + * auto out = fmt::output_file("guide.txt"); + * out.print("Don't {}", "Panic"); + */ +template +inline auto output_file(cstring_view path, T... params) -> ostream { + return {path, detail::ostream_params(params...)}; +} +#endif // FMT_USE_FCNTL + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_OS_H_ diff --git a/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/ostream.h b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/ostream.h new file mode 100644 index 000000000..5d893c921 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/ostream.h @@ -0,0 +1,166 @@ +// Formatting library for C++ - std::ostream support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_OSTREAM_H_ +#define FMT_OSTREAM_H_ + +#ifndef FMT_MODULE +# include // std::filebuf +#endif + +#ifdef _WIN32 +# ifdef __GLIBCXX__ +# include +# include +# endif +# include +#endif + +#include "chrono.h" // formatbuf + +#ifdef _MSVC_STL_UPDATE +# define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE +#elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5 +# define FMT_MSVC_STL_UPDATE _MSVC_LANG +#else +# define FMT_MSVC_STL_UPDATE 0 +#endif + +FMT_BEGIN_NAMESPACE +namespace detail { + +// Generate a unique explicit instantion in every translation unit using a tag +// type in an anonymous namespace. +namespace { +struct file_access_tag {}; +} // namespace +template +class file_access { + friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; } +}; + +#if FMT_MSVC_STL_UPDATE +template class file_access; +auto get_file(std::filebuf&) -> FILE*; +#endif + +// Write the content of buf to os. +// It is a separate function rather than a part of vprint to simplify testing. +template +void write_buffer(std::basic_ostream& os, buffer& buf) { + const Char* buf_data = buf.data(); + using unsigned_streamsize = make_unsigned_t; + unsigned_streamsize size = buf.size(); + unsigned_streamsize max_size = to_unsigned(max_value()); + do { + unsigned_streamsize n = size <= max_size ? size : max_size; + os.write(buf_data, static_cast(n)); + buf_data += n; + size -= n; + } while (size != 0); +} + +template struct streamed_view { + const T& value; +}; +} // namespace detail + +// Formats an object of type T that has an overloaded ostream operator<<. +template +struct basic_ostream_formatter : formatter, Char> { + void set_debug_format() = delete; + + template + auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) { + auto buffer = basic_memory_buffer(); + auto&& formatbuf = detail::formatbuf>(buffer); + auto&& output = std::basic_ostream(&formatbuf); + output.imbue(std::locale::classic()); // The default is always unlocalized. + output << value; + output.exceptions(std::ios_base::failbit | std::ios_base::badbit); + return formatter, Char>::format( + {buffer.data(), buffer.size()}, ctx); + } +}; + +using ostream_formatter = basic_ostream_formatter; + +template +struct formatter, Char> + : basic_ostream_formatter { + template + auto format(detail::streamed_view view, Context& ctx) const + -> decltype(ctx.out()) { + return basic_ostream_formatter::format(view.value, ctx); + } +}; + +/** + * Returns a view that formats `value` via an ostream `operator<<`. + * + * **Example**: + * + * fmt::print("Current thread id: {}\n", + * fmt::streamed(std::this_thread::get_id())); + */ +template +constexpr auto streamed(const T& value) -> detail::streamed_view { + return {value}; +} + +inline void vprint(std::ostream& os, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + FILE* f = nullptr; +#if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI + if (auto* buf = dynamic_cast(os.rdbuf())) + f = detail::get_file(*buf); +#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI + auto* rdbuf = os.rdbuf(); + if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf*>(rdbuf)) + f = sfbuf->file(); + else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf*>(rdbuf)) + f = fbuf->file(); +#endif +#ifdef _WIN32 + if (f) { + int fd = _fileno(f); + if (_isatty(fd)) { + os.flush(); + if (detail::write_console(fd, {buffer.data(), buffer.size()})) return; + } + } +#endif + detail::ignore_unused(f); + detail::write_buffer(os, buffer); +} + +/** + * Prints formatted data to the stream `os`. + * + * **Example**: + * + * fmt::print(cerr, "Don't {}!", "panic"); + */ +FMT_EXPORT template +void print(std::ostream& os, format_string fmt, T&&... args) { + fmt::vargs vargs = {{args...}}; + if (detail::use_utf8) return vprint(os, fmt.str, vargs); + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt.str, vargs); + detail::write_buffer(os, buffer); +} + +FMT_EXPORT template +void println(std::ostream& os, format_string fmt, T&&... args) { + fmt::print(os, "{}\n", fmt::format(fmt, std::forward(args)...)); +} + +FMT_END_NAMESPACE + +#endif // FMT_OSTREAM_H_ diff --git a/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/printf.h b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/printf.h new file mode 100644 index 000000000..e72684018 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/printf.h @@ -0,0 +1,633 @@ +// Formatting library for C++ - legacy printf implementation +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_PRINTF_H_ +#define FMT_PRINTF_H_ + +#ifndef FMT_MODULE +# include // std::max +# include // std::numeric_limits +#endif + +#include "format.h" + +FMT_BEGIN_NAMESPACE +FMT_BEGIN_EXPORT + +template struct printf_formatter { + printf_formatter() = delete; +}; + +template class basic_printf_context { + private: + basic_appender out_; + basic_format_args args_; + + static_assert(std::is_same::value || + std::is_same::value, + "Unsupported code unit type."); + + public: + using char_type = Char; + using parse_context_type = parse_context; + template using formatter_type = printf_formatter; + enum { builtin_types = 1 }; + + /// Constructs a `printf_context` object. References to the arguments are + /// stored in the context object so make sure they have appropriate lifetimes. + basic_printf_context(basic_appender out, + basic_format_args args) + : out_(out), args_(args) {} + + auto out() -> basic_appender { return out_; } + void advance_to(basic_appender) {} + + auto locale() -> detail::locale_ref { return {}; } + + auto arg(int id) const -> basic_format_arg { + return args_.get(id); + } +}; + +namespace detail { + +// Return the result via the out param to workaround gcc bug 77539. +template +FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool { + for (out = first; out != last; ++out) { + if (*out == value) return true; + } + return false; +} + +template <> +inline auto find(const char* first, const char* last, char value, + const char*& out) -> bool { + out = + static_cast(memchr(first, value, to_unsigned(last - first))); + return out != nullptr; +} + +// Checks if a value fits in int - used to avoid warnings about comparing +// signed and unsigned integers. +template struct int_checker { + template static auto fits_in_int(T value) -> bool { + unsigned max = to_unsigned(max_value()); + return value <= max; + } + inline static auto fits_in_int(bool) -> bool { return true; } +}; + +template <> struct int_checker { + template static auto fits_in_int(T value) -> bool { + return value >= (std::numeric_limits::min)() && + value <= max_value(); + } + inline static auto fits_in_int(int) -> bool { return true; } +}; + +struct printf_precision_handler { + template ::value)> + auto operator()(T value) -> int { + if (!int_checker::is_signed>::fits_in_int(value)) + report_error("number is too big"); + return (std::max)(static_cast(value), 0); + } + + template ::value)> + auto operator()(T) -> int { + report_error("precision is not integer"); + return 0; + } +}; + +// An argument visitor that returns true iff arg is a zero integer. +struct is_zero_int { + template ::value)> + auto operator()(T value) -> bool { + return value == 0; + } + + template ::value)> + auto operator()(T) -> bool { + return false; + } +}; + +template struct make_unsigned_or_bool : std::make_unsigned {}; + +template <> struct make_unsigned_or_bool { + using type = bool; +}; + +template class arg_converter { + private: + using char_type = typename Context::char_type; + + basic_format_arg& arg_; + char_type type_; + + public: + arg_converter(basic_format_arg& arg, char_type type) + : arg_(arg), type_(type) {} + + void operator()(bool value) { + if (type_ != 's') operator()(value); + } + + template ::value)> + void operator()(U value) { + bool is_signed = type_ == 'd' || type_ == 'i'; + using target_type = conditional_t::value, U, T>; + if (const_check(sizeof(target_type) <= sizeof(int))) { + // Extra casts are used to silence warnings. + using unsigned_type = typename make_unsigned_or_bool::type; + if (is_signed) + arg_ = static_cast(static_cast(value)); + else + arg_ = static_cast(static_cast(value)); + } else { + // glibc's printf doesn't sign extend arguments of smaller types: + // std::printf("%lld", -42); // prints "4294967254" + // but we don't have to do the same because it's a UB. + if (is_signed) + arg_ = static_cast(value); + else + arg_ = static_cast::type>(value); + } + } + + template ::value)> + void operator()(U) {} // No conversion needed for non-integral types. +}; + +// Converts an integer argument to T for printf, if T is an integral type. +// If T is void, the argument is converted to corresponding signed or unsigned +// type depending on the type specifier: 'd' and 'i' - signed, other - +// unsigned). +template +void convert_arg(basic_format_arg& arg, Char type) { + arg.visit(arg_converter(arg, type)); +} + +// Converts an integer argument to char for printf. +template class char_converter { + private: + basic_format_arg& arg_; + + public: + explicit char_converter(basic_format_arg& arg) : arg_(arg) {} + + template ::value)> + void operator()(T value) { + arg_ = static_cast(value); + } + + template ::value)> + void operator()(T) {} // No conversion needed for non-integral types. +}; + +// An argument visitor that return a pointer to a C string if argument is a +// string or null otherwise. +template struct get_cstring { + template auto operator()(T) -> const Char* { return nullptr; } + auto operator()(const Char* s) -> const Char* { return s; } +}; + +// Checks if an argument is a valid printf width specifier and sets +// left alignment if it is negative. +class printf_width_handler { + private: + format_specs& specs_; + + public: + inline explicit printf_width_handler(format_specs& specs) : specs_(specs) {} + + template ::value)> + auto operator()(T value) -> unsigned { + auto width = static_cast>(value); + if (detail::is_negative(value)) { + specs_.set_align(align::left); + width = 0 - width; + } + unsigned int_max = to_unsigned(max_value()); + if (width > int_max) report_error("number is too big"); + return static_cast(width); + } + + template ::value)> + auto operator()(T) -> unsigned { + report_error("width is not integer"); + return 0; + } +}; + +// Workaround for a bug with the XL compiler when initializing +// printf_arg_formatter's base class. +template +auto make_arg_formatter(basic_appender iter, format_specs& s) + -> arg_formatter { + return {iter, s, locale_ref()}; +} + +// The `printf` argument formatter. +template +class printf_arg_formatter : public arg_formatter { + private: + using base = arg_formatter; + using context_type = basic_printf_context; + + context_type& context_; + + void write_null_pointer(bool is_string = false) { + auto s = this->specs; + s.set_type(presentation_type::none); + write_bytes(this->out, is_string ? "(null)" : "(nil)", s); + } + + template void write(T value) { + detail::write(this->out, value, this->specs, this->locale); + } + + public: + printf_arg_formatter(basic_appender iter, format_specs& s, + context_type& ctx) + : base(make_arg_formatter(iter, s)), context_(ctx) {} + + void operator()(monostate value) { write(value); } + + template ::value)> + void operator()(T value) { + // MSVC2013 fails to compile separate overloads for bool and Char so use + // std::is_same instead. + if (!std::is_same::value) { + write(value); + return; + } + format_specs s = this->specs; + if (s.type() != presentation_type::none && + s.type() != presentation_type::chr) { + return (*this)(static_cast(value)); + } + s.set_sign(sign::none); + s.clear_alt(); + s.set_fill(' '); // Ignore '0' flag for char types. + // align::numeric needs to be overwritten here since the '0' flag is + // ignored for non-numeric types + if (s.align() == align::none || s.align() == align::numeric) + s.set_align(align::right); + detail::write(this->out, static_cast(value), s); + } + + template ::value)> + void operator()(T value) { + write(value); + } + + void operator()(const char* value) { + if (value) + write(value); + else + write_null_pointer(this->specs.type() != presentation_type::pointer); + } + + void operator()(const wchar_t* value) { + if (value) + write(value); + else + write_null_pointer(this->specs.type() != presentation_type::pointer); + } + + void operator()(basic_string_view value) { write(value); } + + void operator()(const void* value) { + if (value) + write(value); + else + write_null_pointer(); + } + + void operator()(typename basic_format_arg::handle handle) { + auto parse_ctx = parse_context({}); + handle.format(parse_ctx, context_); + } +}; + +template +void parse_flags(format_specs& specs, const Char*& it, const Char* end) { + for (; it != end; ++it) { + switch (*it) { + case '-': specs.set_align(align::left); break; + case '+': specs.set_sign(sign::plus); break; + case '0': specs.set_fill('0'); break; + case ' ': + if (specs.sign() != sign::plus) specs.set_sign(sign::space); + break; + case '#': specs.set_alt(); break; + default: return; + } + } +} + +template +auto parse_header(const Char*& it, const Char* end, format_specs& specs, + GetArg get_arg) -> int { + int arg_index = -1; + Char c = *it; + if (c >= '0' && c <= '9') { + // Parse an argument index (if followed by '$') or a width possibly + // preceded with '0' flag(s). + int value = parse_nonnegative_int(it, end, -1); + if (it != end && *it == '$') { // value is an argument index + ++it; + arg_index = value != -1 ? value : max_value(); + } else { + if (c == '0') specs.set_fill('0'); + if (value != 0) { + // Nonzero value means that we parsed width and don't need to + // parse it or flags again, so return now. + if (value == -1) report_error("number is too big"); + specs.width = value; + return arg_index; + } + } + } + parse_flags(specs, it, end); + // Parse width. + if (it != end) { + if (*it >= '0' && *it <= '9') { + specs.width = parse_nonnegative_int(it, end, -1); + if (specs.width == -1) report_error("number is too big"); + } else if (*it == '*') { + ++it; + specs.width = static_cast( + get_arg(-1).visit(detail::printf_width_handler(specs))); + } + } + return arg_index; +} + +inline auto parse_printf_presentation_type(char c, type t, bool& upper) + -> presentation_type { + using pt = presentation_type; + constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; + switch (c) { + case 'd': return in(t, integral_set) ? pt::dec : pt::none; + case 'o': return in(t, integral_set) ? pt::oct : pt::none; + case 'X': upper = true; FMT_FALLTHROUGH; + case 'x': return in(t, integral_set) ? pt::hex : pt::none; + case 'E': upper = true; FMT_FALLTHROUGH; + case 'e': return in(t, float_set) ? pt::exp : pt::none; + case 'F': upper = true; FMT_FALLTHROUGH; + case 'f': return in(t, float_set) ? pt::fixed : pt::none; + case 'G': upper = true; FMT_FALLTHROUGH; + case 'g': return in(t, float_set) ? pt::general : pt::none; + case 'A': upper = true; FMT_FALLTHROUGH; + case 'a': return in(t, float_set) ? pt::hexfloat : pt::none; + case 'c': return in(t, integral_set) ? pt::chr : pt::none; + case 's': return in(t, string_set | cstring_set) ? pt::string : pt::none; + case 'p': return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none; + default: return pt::none; + } +} + +template +void vprintf(buffer& buf, basic_string_view format, + basic_format_args args) { + using iterator = basic_appender; + auto out = iterator(buf); + auto context = basic_printf_context(out, args); + auto parse_ctx = parse_context(format); + + // Returns the argument with specified index or, if arg_index is -1, the next + // argument. + auto get_arg = [&](int arg_index) { + if (arg_index < 0) + arg_index = parse_ctx.next_arg_id(); + else + parse_ctx.check_arg_id(--arg_index); + return detail::get_arg(context, arg_index); + }; + + const Char* start = parse_ctx.begin(); + const Char* end = parse_ctx.end(); + auto it = start; + while (it != end) { + if (!find(it, end, '%', it)) { + it = end; // find leaves it == nullptr if it doesn't find '%'. + break; + } + Char c = *it++; + if (it != end && *it == c) { + write(out, basic_string_view(start, to_unsigned(it - start))); + start = ++it; + continue; + } + write(out, basic_string_view(start, to_unsigned(it - 1 - start))); + + auto specs = format_specs(); + specs.set_align(align::right); + + // Parse argument index, flags and width. + int arg_index = parse_header(it, end, specs, get_arg); + if (arg_index == 0) report_error("argument not found"); + + // Parse precision. + if (it != end && *it == '.') { + ++it; + c = it != end ? *it : 0; + if ('0' <= c && c <= '9') { + specs.precision = parse_nonnegative_int(it, end, 0); + } else if (c == '*') { + ++it; + specs.precision = + static_cast(get_arg(-1).visit(printf_precision_handler())); + } else { + specs.precision = 0; + } + } + + auto arg = get_arg(arg_index); + // For d, i, o, u, x, and X conversion specifiers, if a precision is + // specified, the '0' flag is ignored + if (specs.precision >= 0 && is_integral_type(arg.type())) { + // Ignore '0' for non-numeric types or if '-' present. + specs.set_fill(' '); + } + if (specs.precision >= 0 && arg.type() == type::cstring_type) { + auto str = arg.visit(get_cstring()); + auto str_end = str + specs.precision; + auto nul = std::find(str, str_end, Char()); + auto sv = basic_string_view( + str, to_unsigned(nul != str_end ? nul - str : specs.precision)); + arg = sv; + } + if (specs.alt() && arg.visit(is_zero_int())) specs.clear_alt(); + if (specs.fill_unit() == '0') { + if (is_arithmetic_type(arg.type()) && specs.align() != align::left) { + specs.set_align(align::numeric); + } else { + // Ignore '0' flag for non-numeric types or if '-' flag is also present. + specs.set_fill(' '); + } + } + + // Parse length and convert the argument to the required type. + c = it != end ? *it++ : 0; + Char t = it != end ? *it : 0; + switch (c) { + case 'h': + if (t == 'h') { + ++it; + t = it != end ? *it : 0; + convert_arg(arg, t); + } else { + convert_arg(arg, t); + } + break; + case 'l': + if (t == 'l') { + ++it; + t = it != end ? *it : 0; + convert_arg(arg, t); + } else { + convert_arg(arg, t); + } + break; + case 'j': convert_arg(arg, t); break; + case 'z': convert_arg(arg, t); break; + case 't': convert_arg(arg, t); break; + case 'L': + // printf produces garbage when 'L' is omitted for long double, no + // need to do the same. + break; + default: --it; convert_arg(arg, c); + } + + // Parse type. + if (it == end) report_error("invalid format string"); + char type = static_cast(*it++); + if (is_integral_type(arg.type())) { + // Normalize type. + switch (type) { + case 'i': + case 'u': type = 'd'; break; + case 'c': + arg.visit(char_converter>(arg)); + break; + } + } + bool upper = false; + specs.set_type(parse_printf_presentation_type(type, arg.type(), upper)); + if (specs.type() == presentation_type::none) + report_error("invalid format specifier"); + if (upper) specs.set_upper(); + + start = it; + + // Format argument. + arg.visit(printf_arg_formatter(out, specs, context)); + } + write(out, basic_string_view(start, to_unsigned(it - start))); +} +} // namespace detail + +using printf_context = basic_printf_context; +using wprintf_context = basic_printf_context; + +using printf_args = basic_format_args; +using wprintf_args = basic_format_args; + +/// Constructs an `format_arg_store` object that contains references to +/// arguments and can be implicitly converted to `printf_args`. +template +inline auto make_printf_args(T&... args) + -> decltype(fmt::make_format_args>(args...)) { + return fmt::make_format_args>(args...); +} + +template struct vprintf_args { + using type = basic_format_args>; +}; + +template +inline auto vsprintf(basic_string_view fmt, + typename vprintf_args::type args) + -> std::basic_string { + auto buf = basic_memory_buffer(); + detail::vprintf(buf, fmt, args); + return {buf.data(), buf.size()}; +} + +/** + * Formats `args` according to specifications in `fmt` and returns the result + * as as string. + * + * **Example**: + * + * std::string message = fmt::sprintf("The answer is %d", 42); + */ +template > +inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string { + return vsprintf(detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template +inline auto vfprintf(std::FILE* f, basic_string_view fmt, + typename vprintf_args::type args) -> int { + auto buf = basic_memory_buffer(); + detail::vprintf(buf, fmt, args); + size_t size = buf.size(); + return std::fwrite(buf.data(), sizeof(Char), size, f) < size + ? -1 + : static_cast(size); +} + +/** + * Formats `args` according to specifications in `fmt` and writes the output + * to `f`. + * + * **Example**: + * + * fmt::fprintf(stderr, "Don't %s!", "panic"); + */ +template > +inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int { + return vfprintf(f, detail::to_string_view(fmt), + make_printf_args(args...)); +} + +template +FMT_DEPRECATED inline auto vprintf(basic_string_view fmt, + typename vprintf_args::type args) + -> int { + return vfprintf(stdout, fmt, args); +} + +/** + * Formats `args` according to specifications in `fmt` and writes the output + * to `stdout`. + * + * **Example**: + * + * fmt::printf("Elapsed time: %.2f seconds", 1.23); + */ +template +inline auto printf(string_view fmt, const T&... args) -> int { + return vfprintf(stdout, fmt, make_printf_args(args...)); +} +template +FMT_DEPRECATED inline auto printf(basic_string_view fmt, + const T&... args) -> int { + return vfprintf(stdout, fmt, make_printf_args(args...)); +} + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_PRINTF_H_ diff --git a/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/ranges.h b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/ranges.h new file mode 100644 index 000000000..118d24fe8 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/ranges.h @@ -0,0 +1,848 @@ +// Formatting library for C++ - range and tuple support +// +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_RANGES_H_ +#define FMT_RANGES_H_ + +#ifndef FMT_MODULE +# include +# include +# include +# include +# include +# include +#endif + +#include "format.h" + +FMT_BEGIN_NAMESPACE + +FMT_EXPORT +enum class range_format { disabled, map, set, sequence, string, debug_string }; + +namespace detail { + +template class is_map { + template static auto check(U*) -> typename U::mapped_type; + template static void check(...); + + public: + static constexpr const bool value = + !std::is_void(nullptr))>::value; +}; + +template class is_set { + template static auto check(U*) -> typename U::key_type; + template static void check(...); + + public: + static constexpr const bool value = + !std::is_void(nullptr))>::value && !is_map::value; +}; + +// C array overload +template +auto range_begin(const T (&arr)[N]) -> const T* { + return arr; +} +template +auto range_end(const T (&arr)[N]) -> const T* { + return arr + N; +} + +template +struct has_member_fn_begin_end_t : std::false_type {}; + +template +struct has_member_fn_begin_end_t().begin()), + decltype(std::declval().end())>> + : std::true_type {}; + +// Member function overloads. +template +auto range_begin(T&& rng) -> decltype(static_cast(rng).begin()) { + return static_cast(rng).begin(); +} +template +auto range_end(T&& rng) -> decltype(static_cast(rng).end()) { + return static_cast(rng).end(); +} + +// ADL overloads. Only participate in overload resolution if member functions +// are not found. +template +auto range_begin(T&& rng) + -> enable_if_t::value, + decltype(begin(static_cast(rng)))> { + return begin(static_cast(rng)); +} +template +auto range_end(T&& rng) -> enable_if_t::value, + decltype(end(static_cast(rng)))> { + return end(static_cast(rng)); +} + +template +struct has_const_begin_end : std::false_type {}; +template +struct has_mutable_begin_end : std::false_type {}; + +template +struct has_const_begin_end< + T, void_t&>())), + decltype(detail::range_end( + std::declval&>()))>> + : std::true_type {}; + +template +struct has_mutable_begin_end< + T, void_t())), + decltype(detail::range_end(std::declval())), + // the extra int here is because older versions of MSVC don't + // SFINAE properly unless there are distinct types + int>> : std::true_type {}; + +template struct is_range_ : std::false_type {}; +template +struct is_range_ + : std::integral_constant::value || + has_mutable_begin_end::value)> {}; + +// tuple_size and tuple_element check. +template class is_tuple_like_ { + template ::type> + static auto check(U* p) -> decltype(std::tuple_size::value, 0); + template static void check(...); + + public: + static constexpr const bool value = + !std::is_void(nullptr))>::value; +}; + +// Check for integer_sequence +#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900 +template +using integer_sequence = std::integer_sequence; +template using index_sequence = std::index_sequence; +template using make_index_sequence = std::make_index_sequence; +#else +template struct integer_sequence { + using value_type = T; + + static FMT_CONSTEXPR auto size() -> size_t { return sizeof...(N); } +}; + +template using index_sequence = integer_sequence; + +template +struct make_integer_sequence : make_integer_sequence {}; +template +struct make_integer_sequence : integer_sequence {}; + +template +using make_index_sequence = make_integer_sequence; +#endif + +template +using tuple_index_sequence = make_index_sequence::value>; + +template ::value> +class is_tuple_formattable_ { + public: + static constexpr const bool value = false; +}; +template class is_tuple_formattable_ { + template + static auto all_true(index_sequence, + integer_sequence= 0)...>) -> std::true_type; + static auto all_true(...) -> std::false_type; + + template + static auto check(index_sequence) -> decltype(all_true( + index_sequence{}, + integer_sequence::type, + C>::value)...>{})); + + public: + static constexpr const bool value = + decltype(check(tuple_index_sequence{}))::value; +}; + +template +FMT_CONSTEXPR void for_each(index_sequence, Tuple&& t, F&& f) { + using std::get; + // Using a free function get(Tuple) now. + const int unused[] = {0, ((void)f(get(t)), 0)...}; + ignore_unused(unused); +} + +template +FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) { + for_each(tuple_index_sequence>(), + std::forward(t), std::forward(f)); +} + +template +void for_each2(index_sequence, Tuple1&& t1, Tuple2&& t2, F&& f) { + using std::get; + const int unused[] = {0, ((void)f(get(t1), get(t2)), 0)...}; + ignore_unused(unused); +} + +template +void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) { + for_each2(tuple_index_sequence>(), + std::forward(t1), std::forward(t2), + std::forward(f)); +} + +namespace tuple { +// Workaround a bug in MSVC 2019 (v140). +template +using result_t = std::tuple, Char>...>; + +using std::get; +template +auto get_formatters(index_sequence) + -> result_t(std::declval()))...>; +} // namespace tuple + +#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920 +// Older MSVC doesn't get the reference type correctly for arrays. +template struct range_reference_type_impl { + using type = decltype(*detail::range_begin(std::declval())); +}; + +template struct range_reference_type_impl { + using type = T&; +}; + +template +using range_reference_type = typename range_reference_type_impl::type; +#else +template +using range_reference_type = + decltype(*detail::range_begin(std::declval())); +#endif + +// We don't use the Range's value_type for anything, but we do need the Range's +// reference type, with cv-ref stripped. +template +using uncvref_type = remove_cvref_t>; + +template +FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set) + -> decltype(f.set_debug_format(set)) { + f.set_debug_format(set); +} +template +FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {} + +template +struct range_format_kind_ + : std::integral_constant, T>::value + ? range_format::disabled + : is_map::value ? range_format::map + : is_set::value ? range_format::set + : range_format::sequence> {}; + +template +using range_format_constant = std::integral_constant; + +// These are not generic lambdas for compatibility with C++11. +template struct parse_empty_specs { + template FMT_CONSTEXPR void operator()(Formatter& f) { + f.parse(ctx); + detail::maybe_set_debug_format(f, true); + } + parse_context& ctx; +}; +template struct format_tuple_element { + using char_type = typename FormatContext::char_type; + + template + void operator()(const formatter& f, const T& v) { + if (i > 0) ctx.advance_to(detail::copy(separator, ctx.out())); + ctx.advance_to(f.format(v, ctx)); + ++i; + } + + int i; + FormatContext& ctx; + basic_string_view separator; +}; + +} // namespace detail + +template struct is_tuple_like { + static constexpr const bool value = + detail::is_tuple_like_::value && !detail::is_range_::value; +}; + +template struct is_tuple_formattable { + static constexpr const bool value = + detail::is_tuple_formattable_::value; +}; + +template +struct formatter::value && + fmt::is_tuple_formattable::value>> { + private: + decltype(detail::tuple::get_formatters( + detail::tuple_index_sequence())) formatters_; + + basic_string_view separator_ = detail::string_literal{}; + basic_string_view opening_bracket_ = + detail::string_literal{}; + basic_string_view closing_bracket_ = + detail::string_literal{}; + + public: + FMT_CONSTEXPR formatter() {} + + FMT_CONSTEXPR void set_separator(basic_string_view sep) { + separator_ = sep; + } + + FMT_CONSTEXPR void set_brackets(basic_string_view open, + basic_string_view close) { + opening_bracket_ = open; + closing_bracket_ = close; + } + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(); + auto end = ctx.end(); + if (it != end && detail::to_ascii(*it) == 'n') { + ++it; + set_brackets({}, {}); + set_separator({}); + } + if (it != end && *it != '}') report_error("invalid format specifier"); + ctx.advance_to(it); + detail::for_each(formatters_, detail::parse_empty_specs{ctx}); + return it; + } + + template + auto format(const Tuple& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + ctx.advance_to(detail::copy(opening_bracket_, ctx.out())); + detail::for_each2( + formatters_, value, + detail::format_tuple_element{0, ctx, separator_}); + return detail::copy(closing_bracket_, ctx.out()); + } +}; + +template struct is_range { + static constexpr const bool value = + detail::is_range_::value && !detail::has_to_string_view::value; +}; + +namespace detail { + +template +using range_formatter_type = formatter, Char>; + +template +using maybe_const_range = + conditional_t::value, const R, R>; + +template +struct is_formattable_delayed + : is_formattable>, Char> {}; +} // namespace detail + +template struct conjunction : std::true_type {}; +template struct conjunction

: P {}; +template +struct conjunction + : conditional_t, P1> {}; + +template +struct range_formatter; + +template +struct range_formatter< + T, Char, + enable_if_t>, + is_formattable>::value>> { + private: + detail::range_formatter_type underlying_; + basic_string_view separator_ = detail::string_literal{}; + basic_string_view opening_bracket_ = + detail::string_literal{}; + basic_string_view closing_bracket_ = + detail::string_literal{}; + bool is_debug = false; + + template ::value)> + auto write_debug_string(Output& out, It it, Sentinel end) const -> Output { + auto buf = basic_memory_buffer(); + for (; it != end; ++it) buf.push_back(*it); + auto specs = format_specs(); + specs.set_type(presentation_type::debug); + return detail::write( + out, basic_string_view(buf.data(), buf.size()), specs); + } + + template ::value)> + auto write_debug_string(Output& out, It, Sentinel) const -> Output { + return out; + } + + public: + FMT_CONSTEXPR range_formatter() {} + + FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type& { + return underlying_; + } + + FMT_CONSTEXPR void set_separator(basic_string_view sep) { + separator_ = sep; + } + + FMT_CONSTEXPR void set_brackets(basic_string_view open, + basic_string_view close) { + opening_bracket_ = open; + closing_bracket_ = close; + } + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(); + auto end = ctx.end(); + detail::maybe_set_debug_format(underlying_, true); + if (it == end) return underlying_.parse(ctx); + + switch (detail::to_ascii(*it)) { + case 'n': + set_brackets({}, {}); + ++it; + break; + case '?': + is_debug = true; + set_brackets({}, {}); + ++it; + if (it == end || *it != 's') report_error("invalid format specifier"); + FMT_FALLTHROUGH; + case 's': + if (!std::is_same::value) + report_error("invalid format specifier"); + if (!is_debug) { + set_brackets(detail::string_literal{}, + detail::string_literal{}); + set_separator({}); + detail::maybe_set_debug_format(underlying_, false); + } + ++it; + return it; + } + + if (it != end && *it != '}') { + if (*it != ':') report_error("invalid format specifier"); + detail::maybe_set_debug_format(underlying_, false); + ++it; + } + + ctx.advance_to(it); + return underlying_.parse(ctx); + } + + template + auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) { + auto out = ctx.out(); + auto it = detail::range_begin(range); + auto end = detail::range_end(range); + if (is_debug) return write_debug_string(out, std::move(it), end); + + out = detail::copy(opening_bracket_, out); + int i = 0; + for (; it != end; ++it) { + if (i > 0) out = detail::copy(separator_, out); + ctx.advance_to(out); + auto&& item = *it; // Need an lvalue + out = underlying_.format(item, ctx); + ++i; + } + out = detail::copy(closing_bracket_, out); + return out; + } +}; + +FMT_EXPORT +template +struct range_format_kind + : conditional_t< + is_range::value, detail::range_format_kind_, + std::integral_constant> {}; + +template +struct formatter< + R, Char, + enable_if_t::value != range_format::disabled && + range_format_kind::value != range_format::map && + range_format_kind::value != range_format::string && + range_format_kind::value != range_format::debug_string>, + detail::is_formattable_delayed>::value>> { + private: + using range_type = detail::maybe_const_range; + range_formatter, Char> range_formatter_; + + public: + using nonlocking = void; + + FMT_CONSTEXPR formatter() { + if (detail::const_check(range_format_kind::value != + range_format::set)) + return; + range_formatter_.set_brackets(detail::string_literal{}, + detail::string_literal{}); + } + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return range_formatter_.parse(ctx); + } + + template + auto format(range_type& range, FormatContext& ctx) const + -> decltype(ctx.out()) { + return range_formatter_.format(range, ctx); + } +}; + +// A map formatter. +template +struct formatter< + R, Char, + enable_if_t::value == range_format::map>> { + private: + using map_type = detail::maybe_const_range; + using element_type = detail::uncvref_type; + + decltype(detail::tuple::get_formatters( + detail::tuple_index_sequence())) formatters_; + bool no_delimiters_ = false; + + public: + FMT_CONSTEXPR formatter() {} + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(); + auto end = ctx.end(); + if (it != end) { + if (detail::to_ascii(*it) == 'n') { + no_delimiters_ = true; + ++it; + } + if (it != end && *it != '}') { + if (*it != ':') report_error("invalid format specifier"); + ++it; + } + ctx.advance_to(it); + } + detail::for_each(formatters_, detail::parse_empty_specs{ctx}); + return it; + } + + template + auto format(map_type& map, FormatContext& ctx) const -> decltype(ctx.out()) { + auto out = ctx.out(); + basic_string_view open = detail::string_literal{}; + if (!no_delimiters_) out = detail::copy(open, out); + int i = 0; + basic_string_view sep = detail::string_literal{}; + for (auto&& value : map) { + if (i > 0) out = detail::copy(sep, out); + ctx.advance_to(out); + detail::for_each2(formatters_, value, + detail::format_tuple_element{ + 0, ctx, detail::string_literal{}}); + ++i; + } + basic_string_view close = detail::string_literal{}; + if (!no_delimiters_) out = detail::copy(close, out); + return out; + } +}; + +// A (debug_)string formatter. +template +struct formatter< + R, Char, + enable_if_t::value == range_format::string || + range_format_kind::value == + range_format::debug_string>> { + private: + using range_type = detail::maybe_const_range; + using string_type = + conditional_t, + decltype(detail::range_begin(std::declval())), + decltype(detail::range_end(std::declval()))>::value, + detail::std_string_view, std::basic_string>; + + formatter underlying_; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return underlying_.parse(ctx); + } + + template + auto format(range_type& range, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + if (detail::const_check(range_format_kind::value == + range_format::debug_string)) + *out++ = '"'; + out = underlying_.format( + string_type{detail::range_begin(range), detail::range_end(range)}, ctx); + if (detail::const_check(range_format_kind::value == + range_format::debug_string)) + *out++ = '"'; + return out; + } +}; + +template +struct join_view : detail::view { + It begin; + Sentinel end; + basic_string_view sep; + + join_view(It b, Sentinel e, basic_string_view s) + : begin(std::move(b)), end(e), sep(s) {} +}; + +template +struct formatter, Char> { + private: + using value_type = +#ifdef __cpp_lib_ranges + std::iter_value_t; +#else + typename std::iterator_traits::value_type; +#endif + formatter, Char> value_formatter_; + + using view = conditional_t::value, + const join_view, + join_view>; + + public: + using nonlocking = void; + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return value_formatter_.parse(ctx); + } + + template + auto format(view& value, FormatContext& ctx) const -> decltype(ctx.out()) { + using iter = + conditional_t::value, It, It&>; + iter it = value.begin; + auto out = ctx.out(); + if (it == value.end) return out; + out = value_formatter_.format(*it, ctx); + ++it; + while (it != value.end) { + out = detail::copy(value.sep.begin(), value.sep.end(), out); + ctx.advance_to(out); + out = value_formatter_.format(*it, ctx); + ++it; + } + return out; + } +}; + +template struct tuple_join_view : detail::view { + const Tuple& tuple; + basic_string_view sep; + + tuple_join_view(const Tuple& t, basic_string_view s) + : tuple(t), sep{s} {} +}; + +// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers +// support in tuple_join. It is disabled by default because of issues with +// the dynamic width and precision. +#ifndef FMT_TUPLE_JOIN_SPECIFIERS +# define FMT_TUPLE_JOIN_SPECIFIERS 0 +#endif + +template +struct formatter, Char, + enable_if_t::value>> { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return do_parse(ctx, std::tuple_size()); + } + + template + auto format(const tuple_join_view& value, + FormatContext& ctx) const -> typename FormatContext::iterator { + return do_format(value, ctx, std::tuple_size()); + } + + private: + decltype(detail::tuple::get_formatters( + detail::tuple_index_sequence())) formatters_; + + FMT_CONSTEXPR auto do_parse(parse_context& ctx, + std::integral_constant) + -> const Char* { + return ctx.begin(); + } + + template + FMT_CONSTEXPR auto do_parse(parse_context& ctx, + std::integral_constant) + -> const Char* { + auto end = ctx.begin(); +#if FMT_TUPLE_JOIN_SPECIFIERS + end = std::get::value - N>(formatters_).parse(ctx); + if (N > 1) { + auto end1 = do_parse(ctx, std::integral_constant()); + if (end != end1) + report_error("incompatible format specs for tuple elements"); + } +#endif + return end; + } + + template + auto do_format(const tuple_join_view&, FormatContext& ctx, + std::integral_constant) const -> + typename FormatContext::iterator { + return ctx.out(); + } + + template + auto do_format(const tuple_join_view& value, FormatContext& ctx, + std::integral_constant) const -> + typename FormatContext::iterator { + using std::get; + auto out = + std::get::value - N>(formatters_) + .format(get::value - N>(value.tuple), ctx); + if (N <= 1) return out; + out = detail::copy(value.sep, out); + ctx.advance_to(out); + return do_format(value, ctx, std::integral_constant()); + } +}; + +namespace detail { +// Check if T has an interface like a container adaptor (e.g. std::stack, +// std::queue, std::priority_queue). +template class is_container_adaptor_like { + template static auto check(U* p) -> typename U::container_type; + template static void check(...); + + public: + static constexpr const bool value = + !std::is_void(nullptr))>::value; +}; + +template struct all { + const Container& c; + auto begin() const -> typename Container::const_iterator { return c.begin(); } + auto end() const -> typename Container::const_iterator { return c.end(); } +}; +} // namespace detail + +template +struct formatter< + T, Char, + enable_if_t, + bool_constant::value == + range_format::disabled>>::value>> + : formatter, Char> { + using all = detail::all; + template + auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) { + struct getter : T { + static auto get(const T& t) -> all { + return {t.*(&getter::c)}; // Access c through the derived class. + } + }; + return formatter::format(getter::get(t), ctx); + } +}; + +FMT_BEGIN_EXPORT + +/// Returns a view that formats the iterator range `[begin, end)` with elements +/// separated by `sep`. +template +auto join(It begin, Sentinel end, string_view sep) -> join_view { + return {std::move(begin), end, sep}; +} + +/** + * Returns a view that formats `range` with elements separated by `sep`. + * + * **Example**: + * + * auto v = std::vector{1, 2, 3}; + * fmt::print("{}", fmt::join(v, ", ")); + * // Output: 1, 2, 3 + * + * `fmt::join` applies passed format specifiers to the range elements: + * + * fmt::print("{:02}", fmt::join(v, ", ")); + * // Output: 01, 02, 03 + */ +template ::value)> +auto join(Range&& r, string_view sep) + -> join_view { + return {detail::range_begin(r), detail::range_end(r), sep}; +} + +/** + * Returns an object that formats `std::tuple` with elements separated by `sep`. + * + * **Example**: + * + * auto t = std::tuple{1, 'a'}; + * fmt::print("{}", fmt::join(t, ", ")); + * // Output: 1, a + */ +template ::value)> +FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep) + -> tuple_join_view { + return {tuple, sep}; +} + +/** + * Returns an object that formats `std::initializer_list` with elements + * separated by `sep`. + * + * **Example**: + * + * fmt::print("{}", fmt::join({1, 2, 3}, ", ")); + * // Output: "1, 2, 3" + */ +template +auto join(std::initializer_list list, string_view sep) + -> join_view { + return join(std::begin(list), std::end(list), sep); +} + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_RANGES_H_ diff --git a/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/std.h b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/std.h new file mode 100644 index 000000000..b00e40225 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/std.h @@ -0,0 +1,727 @@ +// Formatting library for C++ - formatters for standard library types +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_STD_H_ +#define FMT_STD_H_ + +#include "format.h" +#include "ostream.h" + +#ifndef FMT_MODULE +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC. +# if FMT_CPLUSPLUS >= 201703L +# if FMT_HAS_INCLUDE() && \ + (!defined(FMT_CPP_LIB_FILESYSTEM) || FMT_CPP_LIB_FILESYSTEM != 0) +# include +# endif +# if FMT_HAS_INCLUDE() +# include +# endif +# if FMT_HAS_INCLUDE() +# include +# endif +# endif +// Use > instead of >= in the version check because may be +// available after C++17 but before C++20 is marked as implemented. +# if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE() +# include +# endif +# if FMT_CPLUSPLUS > 202002L && FMT_HAS_INCLUDE() +# include +# endif +#endif // FMT_MODULE + +#if FMT_HAS_INCLUDE() +# include +#endif + +// GCC 4 does not support FMT_HAS_INCLUDE. +#if FMT_HAS_INCLUDE() || defined(__GLIBCXX__) +# include +// Android NDK with gabi++ library on some architectures does not implement +// abi::__cxa_demangle(). +# ifndef __GABIXX_CXXABI_H__ +# define FMT_HAS_ABI_CXA_DEMANGLE +# endif +#endif + +// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined. +#ifndef FMT_CPP_LIB_FILESYSTEM +# ifdef __cpp_lib_filesystem +# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem +# else +# define FMT_CPP_LIB_FILESYSTEM 0 +# endif +#endif + +#ifndef FMT_CPP_LIB_VARIANT +# ifdef __cpp_lib_variant +# define FMT_CPP_LIB_VARIANT __cpp_lib_variant +# else +# define FMT_CPP_LIB_VARIANT 0 +# endif +#endif + +#if FMT_CPP_LIB_FILESYSTEM +FMT_BEGIN_NAMESPACE + +namespace detail { + +template +auto get_path_string(const std::filesystem::path& p, + const std::basic_string& native) { + if constexpr (std::is_same_v && std::is_same_v) + return to_utf8(native, to_utf8_error_policy::replace); + else + return p.string(); +} + +template +void write_escaped_path(basic_memory_buffer& quoted, + const std::filesystem::path& p, + const std::basic_string& native) { + if constexpr (std::is_same_v && + std::is_same_v) { + auto buf = basic_memory_buffer(); + write_escaped_string(std::back_inserter(buf), native); + bool valid = to_utf8::convert(quoted, {buf.data(), buf.size()}); + FMT_ASSERT(valid, "invalid utf16"); + } else if constexpr (std::is_same_v) { + write_escaped_string( + std::back_inserter(quoted), native); + } else { + write_escaped_string(std::back_inserter(quoted), p.string()); + } +} + +} // namespace detail + +FMT_EXPORT +template struct formatter { + private: + format_specs specs_; + detail::arg_ref width_ref_; + bool debug_ = false; + char path_type_ = 0; + + public: + FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; } + + FMT_CONSTEXPR auto parse(parse_context& ctx) { + auto it = ctx.begin(), end = ctx.end(); + if (it == end) return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + Char c = *it; + if ((c >= '0' && c <= '9') || c == '{') + it = detail::parse_width(it, end, specs_, width_ref_, ctx); + if (it != end && *it == '?') { + debug_ = true; + ++it; + } + if (it != end && (*it == 'g')) path_type_ = detail::to_ascii(*it++); + return it; + } + + template + auto format(const std::filesystem::path& p, FormatContext& ctx) const { + auto specs = specs_; + auto path_string = + !path_type_ ? p.native() + : p.generic_string(); + + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + if (!debug_) { + auto s = detail::get_path_string(p, path_string); + return detail::write(ctx.out(), basic_string_view(s), specs); + } + auto quoted = basic_memory_buffer(); + detail::write_escaped_path(quoted, p, path_string); + return detail::write(ctx.out(), + basic_string_view(quoted.data(), quoted.size()), + specs); + } +}; + +class path : public std::filesystem::path { + public: + auto display_string() const -> std::string { + const std::filesystem::path& base = *this; + return fmt::format(FMT_STRING("{}"), base); + } + auto system_string() const -> std::string { return string(); } + + auto generic_display_string() const -> std::string { + const std::filesystem::path& base = *this; + return fmt::format(FMT_STRING("{:g}"), base); + } + auto generic_system_string() const -> std::string { return generic_string(); } +}; + +FMT_END_NAMESPACE +#endif // FMT_CPP_LIB_FILESYSTEM + +FMT_BEGIN_NAMESPACE +FMT_EXPORT +template +struct formatter, Char> : nested_formatter { + private: + // Functor because C++11 doesn't support generic lambdas. + struct writer { + const std::bitset& bs; + + template + FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt { + for (auto pos = N; pos > 0; --pos) { + out = detail::write(out, bs[pos - 1] ? Char('1') : Char('0')); + } + + return out; + } + }; + + public: + template + auto format(const std::bitset& bs, FormatContext& ctx) const + -> decltype(ctx.out()) { + return write_padded(ctx, writer{bs}); + } +}; + +FMT_EXPORT +template +struct formatter : basic_ostream_formatter {}; +FMT_END_NAMESPACE + +#ifdef __cpp_lib_optional +FMT_BEGIN_NAMESPACE +FMT_EXPORT +template +struct formatter, Char, + std::enable_if_t::value>> { + private: + formatter underlying_; + static constexpr basic_string_view optional = + detail::string_literal{}; + static constexpr basic_string_view none = + detail::string_literal{}; + + template + FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set) + -> decltype(u.set_debug_format(set)) { + u.set_debug_format(set); + } + + template + FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {} + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) { + maybe_set_debug_format(underlying_, true); + return underlying_.parse(ctx); + } + + template + auto format(const std::optional& opt, FormatContext& ctx) const + -> decltype(ctx.out()) { + if (!opt) return detail::write(ctx.out(), none); + + auto out = ctx.out(); + out = detail::write(out, optional); + ctx.advance_to(out); + out = underlying_.format(*opt, ctx); + return detail::write(out, ')'); + } +}; +FMT_END_NAMESPACE +#endif // __cpp_lib_optional + +#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT + +FMT_BEGIN_NAMESPACE +namespace detail { + +template +auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt { + if constexpr (has_to_string_view::value) + return write_escaped_string(out, detail::to_string_view(v)); + if constexpr (std::is_same_v) return write_escaped_char(out, v); + return write(out, v); +} + +} // namespace detail + +FMT_END_NAMESPACE +#endif + +#ifdef __cpp_lib_expected +FMT_BEGIN_NAMESPACE + +FMT_EXPORT +template +struct formatter, Char, + std::enable_if_t<(std::is_void::value || + is_formattable::value) && + is_formattable::value>> { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + + template + auto format(const std::expected& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + + if (value.has_value()) { + out = detail::write(out, "expected("); + if constexpr (!std::is_void::value) + out = detail::write_escaped_alternative(out, *value); + } else { + out = detail::write(out, "unexpected("); + out = detail::write_escaped_alternative(out, value.error()); + } + *out++ = ')'; + return out; + } +}; +FMT_END_NAMESPACE +#endif // __cpp_lib_expected + +#ifdef __cpp_lib_source_location +FMT_BEGIN_NAMESPACE +FMT_EXPORT +template <> struct formatter { + FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); } + + template + auto format(const std::source_location& loc, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + out = detail::write(out, loc.file_name()); + out = detail::write(out, ':'); + out = detail::write(out, loc.line()); + out = detail::write(out, ':'); + out = detail::write(out, loc.column()); + out = detail::write(out, ": "); + out = detail::write(out, loc.function_name()); + return out; + } +}; +FMT_END_NAMESPACE +#endif + +#if FMT_CPP_LIB_VARIANT +FMT_BEGIN_NAMESPACE +namespace detail { + +template +using variant_index_sequence = + std::make_index_sequence::value>; + +template struct is_variant_like_ : std::false_type {}; +template +struct is_variant_like_> : std::true_type {}; + +// formattable element check. +template class is_variant_formattable_ { + template + static std::conjunction< + is_formattable, C>...> + check(std::index_sequence); + + public: + static constexpr const bool value = + decltype(check(variant_index_sequence{}))::value; +}; + +} // namespace detail + +template struct is_variant_like { + static constexpr const bool value = detail::is_variant_like_::value; +}; + +template struct is_variant_formattable { + static constexpr const bool value = + detail::is_variant_formattable_::value; +}; + +FMT_EXPORT +template struct formatter { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + + template + auto format(const std::monostate&, FormatContext& ctx) const + -> decltype(ctx.out()) { + return detail::write(ctx.out(), "monostate"); + } +}; + +FMT_EXPORT +template +struct formatter< + Variant, Char, + std::enable_if_t, is_variant_formattable>>> { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + + template + auto format(const Variant& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + + out = detail::write(out, "variant("); + FMT_TRY { + std::visit( + [&](const auto& v) { + out = detail::write_escaped_alternative(out, v); + }, + value); + } + FMT_CATCH(const std::bad_variant_access&) { + detail::write(out, "valueless by exception"); + } + *out++ = ')'; + return out; + } +}; +FMT_END_NAMESPACE +#endif // FMT_CPP_LIB_VARIANT + +FMT_BEGIN_NAMESPACE +FMT_EXPORT +template <> struct formatter { + private: + format_specs specs_; + detail::arg_ref width_ref_; + + public: + FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { + auto it = ctx.begin(), end = ctx.end(); + if (it == end) return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + char c = *it; + if ((c >= '0' && c <= '9') || c == '{') + it = detail::parse_width(it, end, specs_, width_ref_, ctx); + return it; + } + + template + FMT_CONSTEXPR20 auto format(const std::error_code& ec, + FormatContext& ctx) const -> decltype(ctx.out()) { + auto specs = specs_; + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + memory_buffer buf; + buf.append(string_view(ec.category().name())); + buf.push_back(':'); + detail::write(appender(buf), ec.value()); + return detail::write(ctx.out(), string_view(buf.data(), buf.size()), + specs); + } +}; + +#if FMT_USE_RTTI +namespace detail { + +template +auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt { +# ifdef FMT_HAS_ABI_CXA_DEMANGLE + int status = 0; + std::size_t size = 0; + std::unique_ptr demangled_name_ptr( + abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free); + + string_view demangled_name_view; + if (demangled_name_ptr) { + demangled_name_view = demangled_name_ptr.get(); + + // Normalization of stdlib inline namespace names. + // libc++ inline namespaces. + // std::__1::* -> std::* + // std::__1::__fs::* -> std::* + // libstdc++ inline namespaces. + // std::__cxx11::* -> std::* + // std::filesystem::__cxx11::* -> std::filesystem::* + if (demangled_name_view.starts_with("std::")) { + char* begin = demangled_name_ptr.get(); + char* to = begin + 5; // std:: + for (char *from = to, *end = begin + demangled_name_view.size(); + from < end;) { + // This is safe, because demangled_name is NUL-terminated. + if (from[0] == '_' && from[1] == '_') { + char* next = from + 1; + while (next < end && *next != ':') next++; + if (next[0] == ':' && next[1] == ':') { + from = next + 2; + continue; + } + } + *to++ = *from++; + } + demangled_name_view = {begin, detail::to_unsigned(to - begin)}; + } + } else { + demangled_name_view = string_view(ti.name()); + } + return detail::write_bytes(out, demangled_name_view); +# elif FMT_MSC_VERSION + const string_view demangled_name(ti.name()); + for (std::size_t i = 0; i < demangled_name.size(); ++i) { + auto sub = demangled_name; + sub.remove_prefix(i); + if (sub.starts_with("enum ")) { + i += 4; + continue; + } + if (sub.starts_with("class ") || sub.starts_with("union ")) { + i += 5; + continue; + } + if (sub.starts_with("struct ")) { + i += 6; + continue; + } + if (*sub.begin() != ' ') *out++ = *sub.begin(); + } + return out; +# else + return detail::write_bytes(out, string_view(ti.name())); +# endif +} + +} // namespace detail + +FMT_EXPORT +template +struct formatter { + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + + template + auto format(const std::type_info& ti, Context& ctx) const + -> decltype(ctx.out()) { + return detail::write_demangled_name(ctx.out(), ti); + } +}; +#endif + +FMT_EXPORT +template +struct formatter< + T, Char, // DEPRECATED! Mixing code unit types. + typename std::enable_if::value>::type> { + private: + bool with_typename_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(); + auto end = ctx.end(); + if (it == end || *it == '}') return it; + if (*it == 't') { + ++it; + with_typename_ = FMT_USE_RTTI != 0; + } + return it; + } + + template + auto format(const std::exception& ex, Context& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); +#if FMT_USE_RTTI + if (with_typename_) { + out = detail::write_demangled_name(out, typeid(ex)); + *out++ = ':'; + *out++ = ' '; + } +#endif + return detail::write_bytes(out, string_view(ex.what())); + } +}; + +namespace detail { + +template +struct has_flip : std::false_type {}; + +template +struct has_flip().flip())>> + : std::true_type {}; + +template struct is_bit_reference_like { + static constexpr const bool value = + std::is_convertible::value && + std::is_nothrow_assignable::value && has_flip::value; +}; + +#ifdef _LIBCPP_VERSION + +// Workaround for libc++ incompatibility with C++ standard. +// According to the Standard, `bitset::operator[] const` returns bool. +template +struct is_bit_reference_like> { + static constexpr const bool value = true; +}; + +#endif + +} // namespace detail + +// We can't use std::vector::reference and +// std::bitset::reference because the compiler can't deduce Allocator and N +// in partial specialization. +FMT_EXPORT +template +struct formatter::value>> + : formatter { + template + FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter::format(v, ctx); + } +}; + +template +auto ptr(const std::unique_ptr& p) -> const void* { + return p.get(); +} +template auto ptr(const std::shared_ptr& p) -> const void* { + return p.get(); +} + +FMT_EXPORT +template +struct formatter, Char, + enable_if_t::value>> + : formatter { + template + auto format(const std::atomic& v, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter::format(v.load(), ctx); + } +}; + +#ifdef __cpp_lib_atomic_flag_test +FMT_EXPORT +template +struct formatter : formatter { + template + auto format(const std::atomic_flag& v, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter::format(v.test(), ctx); + } +}; +#endif // __cpp_lib_atomic_flag_test + +FMT_EXPORT +template struct formatter, Char> { + private: + detail::dynamic_format_specs specs_; + + template + FMT_CONSTEXPR auto do_format(const std::complex& c, + detail::dynamic_format_specs& specs, + FormatContext& ctx, OutputIt out) const + -> OutputIt { + if (c.real() != 0) { + *out++ = Char('('); + out = detail::write(out, c.real(), specs, ctx.locale()); + specs.set_sign(sign::plus); + out = detail::write(out, c.imag(), specs, ctx.locale()); + if (!detail::isfinite(c.imag())) *out++ = Char(' '); + *out++ = Char('i'); + *out++ = Char(')'); + return out; + } + out = detail::write(out, c.imag(), specs, ctx.locale()); + if (!detail::isfinite(c.imag())) *out++ = Char(' '); + *out++ = Char('i'); + return out; + } + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); + return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, + detail::type_constant::value); + } + + template + auto format(const std::complex& c, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto specs = specs_; + if (specs.dynamic()) { + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, + specs.width_ref, ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, ctx); + } + + if (specs.width == 0) return do_format(c, specs, ctx, ctx.out()); + auto buf = basic_memory_buffer(); + + auto outer_specs = format_specs(); + outer_specs.width = specs.width; + auto fill = specs.template fill(); + if (fill) + outer_specs.set_fill(basic_string_view(fill, specs.fill_size())); + outer_specs.set_align(specs.align()); + + specs.width = 0; + specs.set_fill({}); + specs.set_align(align::none); + + do_format(c, specs, ctx, basic_appender(buf)); + return detail::write(ctx.out(), + basic_string_view(buf.data(), buf.size()), + outer_specs); + } +}; + +FMT_EXPORT +template +struct formatter, Char, + enable_if_t, Char>::value>> + : formatter, Char> { + template + auto format(std::reference_wrapper ref, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter, Char>::format(ref.get(), ctx); + } +}; + +FMT_END_NAMESPACE +#endif // FMT_STD_H_ diff --git a/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/xchar.h b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/xchar.h new file mode 100644 index 000000000..4cbda5421 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/include/fmt/xchar.h @@ -0,0 +1,368 @@ +// Formatting library for C++ - optional wchar_t and exotic character support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_XCHAR_H_ +#define FMT_XCHAR_H_ + +#include "color.h" +#include "format.h" +#include "ostream.h" +#include "ranges.h" + +#ifndef FMT_MODULE +# include +# if FMT_USE_LOCALE +# include +# endif +#endif + +FMT_BEGIN_NAMESPACE +namespace detail { + +template +using is_exotic_char = bool_constant::value>; + +template struct format_string_char {}; + +template +struct format_string_char< + S, void_t())))>> { + using type = char_t; +}; + +template +struct format_string_char< + S, enable_if_t::value>> { + using type = typename S::char_type; +}; + +template +using format_string_char_t = typename format_string_char::type; + +inline auto write_loc(basic_appender out, loc_value value, + const format_specs& specs, locale_ref loc) -> bool { +#if FMT_USE_LOCALE + auto& numpunct = + std::use_facet>(loc.get()); + auto separator = std::wstring(); + auto grouping = numpunct.grouping(); + if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep()); + return value.visit(loc_writer{out, specs, separator, grouping, {}}); +#endif + return false; +} +} // namespace detail + +FMT_BEGIN_EXPORT + +using wstring_view = basic_string_view; +using wformat_parse_context = parse_context; +using wformat_context = buffered_context; +using wformat_args = basic_format_args; +using wmemory_buffer = basic_memory_buffer; + +template struct basic_fstring { + private: + basic_string_view str_; + + static constexpr int num_static_named_args = + detail::count_static_named_args(); + + using checker = detail::format_string_checker< + Char, static_cast(sizeof...(T)), num_static_named_args, + num_static_named_args != detail::count_named_args()>; + + using arg_pack = detail::arg_pack; + + public: + using t = basic_fstring; + + template >::value)> + FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_fstring(const S& s) : str_(s) { + if (FMT_USE_CONSTEVAL) + detail::parse_format_string(s, checker(s, arg_pack())); + } + template ::value&& + std::is_same::value)> + FMT_ALWAYS_INLINE basic_fstring(const S&) : str_(S()) { + FMT_CONSTEXPR auto sv = basic_string_view(S()); + FMT_CONSTEXPR int ignore = + (parse_format_string(sv, checker(sv, arg_pack())), 0); + detail::ignore_unused(ignore); + } + basic_fstring(runtime_format_string fmt) : str_(fmt.str) {} + + operator basic_string_view() const { return str_; } + auto get() const -> basic_string_view { return str_; } +}; + +template +using basic_format_string = basic_fstring; + +template +using wformat_string = typename basic_format_string::t; +inline auto runtime(wstring_view s) -> runtime_format_string { + return {{s}}; +} + +template <> struct is_char : std::true_type {}; +template <> struct is_char : std::true_type {}; +template <> struct is_char : std::true_type {}; + +#ifdef __cpp_char8_t +template <> struct is_char : bool_constant {}; +#endif + +template +constexpr auto make_wformat_args(T&... args) + -> decltype(fmt::make_format_args(args...)) { + return fmt::make_format_args(args...); +} + +#if !FMT_USE_NONTYPE_TEMPLATE_ARGS +inline namespace literals { +inline auto operator""_a(const wchar_t* s, size_t) -> detail::udl_arg { + return {s}; +} +} // namespace literals +#endif + +template +auto join(It begin, Sentinel end, wstring_view sep) + -> join_view { + return {begin, end, sep}; +} + +template ::value)> +auto join(Range&& range, wstring_view sep) + -> join_view { + return join(std::begin(range), std::end(range), sep); +} + +template +auto join(std::initializer_list list, wstring_view sep) + -> join_view { + return join(std::begin(list), std::end(list), sep); +} + +template ::value)> +auto join(const Tuple& tuple, basic_string_view sep) + -> tuple_join_view { + return {tuple, sep}; +} + +template ::value)> +auto vformat(basic_string_view fmt, + typename detail::vformat_args::type args) + -> std::basic_string { + auto buf = basic_memory_buffer(); + detail::vformat_to(buf, fmt, args); + return {buf.data(), buf.size()}; +} + +template +auto format(wformat_string fmt, T&&... args) -> std::wstring { + return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...)); +} + +template +auto format_to(OutputIt out, wformat_string fmt, T&&... args) + -> OutputIt { + return vformat_to(out, fmt::wstring_view(fmt), + fmt::make_wformat_args(args...)); +} + +// Pass char_t as a default template parameter instead of using +// std::basic_string> to reduce the symbol size. +template , + FMT_ENABLE_IF(!std::is_same::value && + !std::is_same::value)> +auto format(const S& fmt, T&&... args) -> std::basic_string { + return vformat(detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_exotic_char::value)> +inline auto vformat(detail::locale_ref loc, const S& fmt, + typename detail::vformat_args::type args) + -> std::basic_string { + auto buf = basic_memory_buffer(); + detail::vformat_to(buf, detail::to_string_view(fmt), args, + detail::locale_ref(loc)); + return {buf.data(), buf.size()}; +} + +template , + FMT_ENABLE_IF(detail::is_exotic_char::value)> +inline auto format(detail::locale_ref loc, const S& fmt, T&&... args) + -> std::basic_string { + return vformat(loc, detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value&& + detail::is_exotic_char::value)> +auto vformat_to(OutputIt out, const S& fmt, + typename detail::vformat_args::type args) -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, detail::to_string_view(fmt), args); + return detail::get_iterator(buf, out); +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value && + !std::is_same::value && + !std::is_same::value)> +inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt { + return vformat_to(out, detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value&& + detail::is_exotic_char::value)> +inline auto vformat_to(OutputIt out, detail::locale_ref loc, const S& fmt, + typename detail::vformat_args::type args) + -> OutputIt { + auto&& buf = detail::get_buffer(out); + vformat_to(buf, detail::to_string_view(fmt), args, detail::locale_ref(loc)); + return detail::get_iterator(buf, out); +} + +template , + bool enable = detail::is_output_iterator::value && + detail::is_exotic_char::value> +inline auto format_to(OutputIt out, detail::locale_ref loc, const S& fmt, + T&&... args) -> + typename std::enable_if::type { + return vformat_to(out, loc, detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template ::value&& + detail::is_exotic_char::value)> +inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view fmt, + typename detail::vformat_args::type args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + detail::vformat_to(buf, fmt, args); + return {buf.out(), buf.count()}; +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value&& + detail::is_exotic_char::value)> +inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args) + -> format_to_n_result { + return vformat_to_n(out, n, fmt::basic_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_exotic_char::value)> +inline auto formatted_size(const S& fmt, T&&... args) -> size_t { + auto buf = detail::counting_buffer(); + detail::vformat_to(buf, detail::to_string_view(fmt), + fmt::make_format_args>(args...)); + return buf.count(); +} + +inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) { + auto buf = wmemory_buffer(); + detail::vformat_to(buf, fmt, args); + buf.push_back(L'\0'); + if (std::fputws(buf.data(), f) == -1) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); +} + +inline void vprint(wstring_view fmt, wformat_args args) { + vprint(stdout, fmt, args); +} + +template +void print(std::FILE* f, wformat_string fmt, T&&... args) { + return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...)); +} + +template void print(wformat_string fmt, T&&... args) { + return vprint(wstring_view(fmt), fmt::make_wformat_args(args...)); +} + +template +void println(std::FILE* f, wformat_string fmt, T&&... args) { + return print(f, L"{}\n", fmt::format(fmt, std::forward(args)...)); +} + +template void println(wformat_string fmt, T&&... args) { + return print(L"{}\n", fmt::format(fmt, std::forward(args)...)); +} + +inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args) + -> std::wstring { + auto buf = wmemory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + return {buf.data(), buf.size()}; +} + +template +inline auto format(const text_style& ts, wformat_string fmt, T&&... args) + -> std::wstring { + return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...)); +} + +template +FMT_DEPRECATED void print(std::FILE* f, const text_style& ts, + wformat_string fmt, const T&... args) { + vprint(f, ts, fmt, fmt::make_wformat_args(args...)); +} + +template +FMT_DEPRECATED void print(const text_style& ts, wformat_string fmt, + const T&... args) { + return print(stdout, ts, fmt, args...); +} + +inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) { + auto buffer = basic_memory_buffer(); + detail::vformat_to(buffer, fmt, args); + detail::write_buffer(os, buffer); +} + +template +void print(std::wostream& os, wformat_string fmt, T&&... args) { + vprint(os, fmt, fmt::make_format_args>(args...)); +} + +template +void println(std::wostream& os, wformat_string fmt, T&&... args) { + print(os, L"{}\n", fmt::format(fmt, std::forward(args)...)); +} + +/// Converts `value` to `std::wstring` using the default format for type `T`. +template inline auto to_wstring(const T& value) -> std::wstring { + return format(FMT_STRING(L"{}"), value); +} +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_XCHAR_H_ diff --git a/Engine/lib/openal-soft/fmt-11.1.1/src/fmt.cc b/Engine/lib/openal-soft/fmt-11.1.1/src/fmt.cc new file mode 100644 index 000000000..2dc4ef2f6 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/src/fmt.cc @@ -0,0 +1,151 @@ +module; + +#ifdef _MSVC_LANG +# define FMT_CPLUSPLUS _MSVC_LANG +#else +# define FMT_CPLUSPLUS __cplusplus +#endif + +// Put all implementation-provided headers into the global module fragment +// to prevent attachment to this module. +#ifndef FMT_IMPORT_STD +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# if FMT_CPLUSPLUS > 202002L +# include +# endif +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#else +# include +# include +# include +# include +#endif +#include +#include +#include + +#if __has_include() +# include +#endif +#if defined(_MSC_VER) || defined(__MINGW32__) +# include +#endif +#if defined __APPLE__ || defined(__FreeBSD__) +# include +#endif +#if __has_include() +# include +#endif +#if (__has_include() || defined(__APPLE__) || \ + defined(__linux__)) && \ + (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) +# include +# include +# include +# ifndef _WIN32 +# include +# else +# include +# endif +#endif +#ifdef _WIN32 +# if defined(__GLIBCXX__) +# include +# include +# endif +# define WIN32_LEAN_AND_MEAN +# include +#endif + +export module fmt; + +#ifdef FMT_IMPORT_STD +import std; +#endif + +#define FMT_EXPORT export +#define FMT_BEGIN_EXPORT export { +#define FMT_END_EXPORT } + +// If you define FMT_ATTACH_TO_GLOBAL_MODULE +// - all declarations are detached from module 'fmt' +// - the module behaves like a traditional static library, too +// - all library symbols are mangled traditionally +// - you can mix TUs with either importing or #including the {fmt} API +#ifdef FMT_ATTACH_TO_GLOBAL_MODULE +extern "C++" { +#endif + +#ifndef FMT_OS +# define FMT_OS 1 +#endif + +// All library-provided declarations and definitions must be in the module +// purview to be exported. +#include "fmt/args.h" +#include "fmt/chrono.h" +#include "fmt/color.h" +#include "fmt/compile.h" +#include "fmt/format.h" +#if FMT_OS +# include "fmt/os.h" +#endif +#include "fmt/ostream.h" +#include "fmt/printf.h" +#include "fmt/ranges.h" +#include "fmt/std.h" +#include "fmt/xchar.h" + +#ifdef FMT_ATTACH_TO_GLOBAL_MODULE +} +#endif + +// gcc doesn't yet implement private module fragments +#if !FMT_GCC_VERSION +module :private; +#endif + +#ifdef FMT_ATTACH_TO_GLOBAL_MODULE +extern "C++" { +#endif + +#if FMT_HAS_INCLUDE("format.cc") +# include "format.cc" +#endif +#if FMT_OS && FMT_HAS_INCLUDE("os.cc") +# include "os.cc" +#endif + +#ifdef FMT_ATTACH_TO_GLOBAL_MODULE +} +#endif diff --git a/Engine/lib/openal-soft/fmt-11.1.1/src/format.cc b/Engine/lib/openal-soft/fmt-11.1.1/src/format.cc new file mode 100644 index 000000000..3ccd80684 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/src/format.cc @@ -0,0 +1,46 @@ +// Formatting library for C++ +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/format-inl.h" + +FMT_BEGIN_NAMESPACE +namespace detail { + +template FMT_API auto dragonbox::to_decimal(float x) noexcept + -> dragonbox::decimal_fp; +template FMT_API auto dragonbox::to_decimal(double x) noexcept + -> dragonbox::decimal_fp; + +#if FMT_USE_LOCALE +// DEPRECATED! locale_ref in the detail namespace +template FMT_API locale_ref::locale_ref(const std::locale& loc); +template FMT_API auto locale_ref::get() const -> std::locale; +#endif + +// Explicit instantiations for char. + +template FMT_API auto thousands_sep_impl(locale_ref) + -> thousands_sep_result; +template FMT_API auto decimal_point_impl(locale_ref) -> char; + +// DEPRECATED! +template FMT_API void buffer::append(const char*, const char*); + +// DEPRECATED! +template FMT_API void vformat_to(buffer&, string_view, + typename vformat_args<>::type, locale_ref); + +// Explicit instantiations for wchar_t. + +template FMT_API auto thousands_sep_impl(locale_ref) + -> thousands_sep_result; +template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; + +template FMT_API void buffer::append(const wchar_t*, const wchar_t*); + +} // namespace detail +FMT_END_NAMESPACE diff --git a/Engine/lib/openal-soft/fmt-11.1.1/src/os.cc b/Engine/lib/openal-soft/fmt-11.1.1/src/os.cc new file mode 100644 index 000000000..c833a0514 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/src/os.cc @@ -0,0 +1,398 @@ +// Formatting library for C++ - optional OS-specific functionality +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +// Disable bogus MSVC warnings. +#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER) +# define _CRT_SECURE_NO_WARNINGS +#endif + +#include "fmt/os.h" + +#ifndef FMT_MODULE +# include + +# if FMT_USE_FCNTL +# include +# include + +# ifdef _WRS_KERNEL // VxWorks7 kernel +# include // getpagesize +# endif + +# ifndef _WIN32 +# include +# else +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# endif // _WIN32 +# endif // FMT_USE_FCNTL + +# ifdef _WIN32 +# include +# endif +#endif + +#ifdef _WIN32 +# ifndef S_IRUSR +# define S_IRUSR _S_IREAD +# endif +# ifndef S_IWUSR +# define S_IWUSR _S_IWRITE +# endif +# ifndef S_IRGRP +# define S_IRGRP 0 +# endif +# ifndef S_IWGRP +# define S_IWGRP 0 +# endif +# ifndef S_IROTH +# define S_IROTH 0 +# endif +# ifndef S_IWOTH +# define S_IWOTH 0 +# endif +#endif + +namespace { +#ifdef _WIN32 +// Return type of read and write functions. +using rwresult = int; + +// On Windows the count argument to read and write is unsigned, so convert +// it from size_t preventing integer overflow. +inline unsigned convert_rwcount(std::size_t count) { + return count <= UINT_MAX ? static_cast(count) : UINT_MAX; +} +#elif FMT_USE_FCNTL +// Return type of read and write functions. +using rwresult = ssize_t; + +inline std::size_t convert_rwcount(std::size_t count) { return count; } +#endif +} // namespace + +FMT_BEGIN_NAMESPACE + +#ifdef _WIN32 +namespace detail { + +class system_message { + system_message(const system_message&) = delete; + void operator=(const system_message&) = delete; + + unsigned long result_; + wchar_t* message_; + + static bool is_whitespace(wchar_t c) noexcept { + return c == L' ' || c == L'\n' || c == L'\r' || c == L'\t' || c == L'\0'; + } + + public: + explicit system_message(unsigned long error_code) + : result_(0), message_(nullptr) { + result_ = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message_), 0, nullptr); + if (result_ != 0) { + while (result_ != 0 && is_whitespace(message_[result_ - 1])) { + --result_; + } + } + } + ~system_message() { LocalFree(message_); } + explicit operator bool() const noexcept { return result_ != 0; } + operator basic_string_view() const noexcept { + return basic_string_view(message_, result_); + } +}; + +class utf8_system_category final : public std::error_category { + public: + const char* name() const noexcept override { return "system"; } + std::string message(int error_code) const override { + auto&& msg = system_message(error_code); + if (msg) { + auto utf8_message = to_utf8(); + if (utf8_message.convert(msg)) { + return utf8_message.str(); + } + } + return "unknown error"; + } +}; + +} // namespace detail + +FMT_API const std::error_category& system_category() noexcept { + static const detail::utf8_system_category category; + return category; +} + +std::system_error vwindows_error(int err_code, string_view format_str, + format_args args) { + auto ec = std::error_code(err_code, system_category()); + return std::system_error(ec, vformat(format_str, args)); +} + +void detail::format_windows_error(detail::buffer& out, int error_code, + const char* message) noexcept { + FMT_TRY { + auto&& msg = system_message(error_code); + if (msg) { + auto utf8_message = to_utf8(); + if (utf8_message.convert(msg)) { + fmt::format_to(appender(out), FMT_STRING("{}: {}"), message, + string_view(utf8_message)); + return; + } + } + } + FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +void report_windows_error(int error_code, const char* message) noexcept { + do_report_error(detail::format_windows_error, error_code, message); +} +#endif // _WIN32 + +buffered_file::~buffered_file() noexcept { + if (file_ && FMT_SYSTEM(fclose(file_)) != 0) + report_system_error(errno, "cannot close file"); +} + +buffered_file::buffered_file(cstring_view filename, cstring_view mode) { + FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), + nullptr); + if (!file_) + FMT_THROW(system_error(errno, FMT_STRING("cannot open file {}"), + filename.c_str())); +} + +void buffered_file::close() { + if (!file_) return; + int result = FMT_SYSTEM(fclose(file_)); + file_ = nullptr; + if (result != 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot close file"))); +} + +int buffered_file::descriptor() const { +#ifdef FMT_HAS_SYSTEM + // fileno is a macro on OpenBSD. +# ifdef fileno +# undef fileno +# endif + int fd = FMT_POSIX_CALL(fileno(file_)); +#elif defined(_WIN32) + int fd = _fileno(file_); +#else + int fd = fileno(file_); +#endif + if (fd == -1) + FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor"))); + return fd; +} + +#if FMT_USE_FCNTL +# ifdef _WIN32 +using mode_t = int; +# endif + +constexpr mode_t default_open_mode = + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + +file::file(cstring_view path, int oflag) { +# if defined(_WIN32) && !defined(__MINGW32__) + fd_ = -1; + auto converted = detail::utf8_to_utf16(string_view(path.c_str())); + *this = file::open_windows_file(converted.c_str(), oflag); +# else + FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, default_open_mode))); + if (fd_ == -1) + FMT_THROW( + system_error(errno, FMT_STRING("cannot open file {}"), path.c_str())); +# endif +} + +file::~file() noexcept { + // Don't retry close in case of EINTR! + // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html + if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0) + report_system_error(errno, "cannot close file"); +} + +void file::close() { + if (fd_ == -1) return; + // Don't retry close in case of EINTR! + // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html + int result = FMT_POSIX_CALL(close(fd_)); + fd_ = -1; + if (result != 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot close file"))); +} + +long long file::size() const { +# ifdef _WIN32 + // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT + // is less than 0x0500 as is the case with some default MinGW builds. + // Both functions support large file sizes. + DWORD size_upper = 0; + HANDLE handle = reinterpret_cast(_get_osfhandle(fd_)); + DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper)); + if (size_lower == INVALID_FILE_SIZE) { + DWORD error = GetLastError(); + if (error != NO_ERROR) + FMT_THROW(windows_error(GetLastError(), "cannot get file size")); + } + unsigned long long long_size = size_upper; + return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower; +# else + using Stat = struct stat; + Stat file_stat = Stat(); + if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1) + FMT_THROW(system_error(errno, FMT_STRING("cannot get file attributes"))); + static_assert(sizeof(long long) >= sizeof(file_stat.st_size), + "return type of file::size is not large enough"); + return file_stat.st_size; +# endif +} + +std::size_t file::read(void* buffer, std::size_t count) { + rwresult result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count)))); + if (result < 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot read from file"))); + return detail::to_unsigned(result); +} + +std::size_t file::write(const void* buffer, std::size_t count) { + rwresult result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count)))); + if (result < 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); + return detail::to_unsigned(result); +} + +file file::dup(int fd) { + // Don't retry as dup doesn't return EINTR. + // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html + int new_fd = FMT_POSIX_CALL(dup(fd)); + if (new_fd == -1) + FMT_THROW(system_error( + errno, FMT_STRING("cannot duplicate file descriptor {}"), fd)); + return file(new_fd); +} + +void file::dup2(int fd) { + int result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); + if (result == -1) { + FMT_THROW(system_error( + errno, FMT_STRING("cannot duplicate file descriptor {} to {}"), fd_, + fd)); + } +} + +void file::dup2(int fd, std::error_code& ec) noexcept { + int result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); + if (result == -1) ec = std::error_code(errno, std::generic_category()); +} + +buffered_file file::fdopen(const char* mode) { +// Don't retry as fdopen doesn't return EINTR. +# if defined(__MINGW32__) && defined(_POSIX_) + FILE* f = ::fdopen(fd_, mode); +# else + FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode)); +# endif + if (!f) { + FMT_THROW(system_error( + errno, FMT_STRING("cannot associate stream with file descriptor"))); + } + buffered_file bf(f); + fd_ = -1; + return bf; +} + +# if defined(_WIN32) && !defined(__MINGW32__) +file file::open_windows_file(wcstring_view path, int oflag) { + int fd = -1; + auto err = _wsopen_s(&fd, path.c_str(), oflag, _SH_DENYNO, default_open_mode); + if (fd == -1) { + FMT_THROW(system_error(err, FMT_STRING("cannot open file {}"), + detail::to_utf8(path.c_str()).c_str())); + } + return file(fd); +} +# endif + +pipe::pipe() { + int fds[2] = {}; +# ifdef _WIN32 + // Make the default pipe capacity same as on Linux 2.6.11+. + enum { DEFAULT_CAPACITY = 65536 }; + int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY)); +# else + // Don't retry as the pipe function doesn't return EINTR. + // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html + int result = FMT_POSIX_CALL(pipe(fds)); +# endif + if (result != 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot create pipe"))); + // The following assignments don't throw. + read_end = file(fds[0]); + write_end = file(fds[1]); +} + +# if !defined(__MSDOS__) +long getpagesize() { +# ifdef _WIN32 + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwPageSize; +# else +# ifdef _WRS_KERNEL + long size = FMT_POSIX_CALL(getpagesize()); +# else + long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE)); +# endif + + if (size < 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot get memory page size"))); + return size; +# endif +} +# endif + +void ostream::grow(buffer& buf, size_t) { + if (buf.size() == buf.capacity()) static_cast(buf).flush(); +} + +ostream::ostream(cstring_view path, const detail::ostream_params& params) + : buffer(grow), file_(path, params.oflag) { + set(new char[params.buffer_size], params.buffer_size); +} + +ostream::ostream(ostream&& other) noexcept + : buffer(grow, other.data(), other.size(), other.capacity()), + file_(std::move(other.file_)) { + other.clear(); + other.set(nullptr, 0); +} + +ostream::~ostream() { + flush(); + delete[] data(); +} +#endif // FMT_USE_FCNTL +FMT_END_NAMESPACE diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/Android.mk b/Engine/lib/openal-soft/fmt-11.1.1/support/Android.mk new file mode 100644 index 000000000..84a3e32f0 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/Android.mk @@ -0,0 +1,15 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := fmt_static +LOCAL_MODULE_FILENAME := libfmt + +LOCAL_SRC_FILES := ../src/format.cc + +LOCAL_C_INCLUDES := $(LOCAL_PATH) +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) + +LOCAL_CFLAGS += -std=c++11 -fexceptions + +include $(BUILD_STATIC_LIBRARY) + diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/AndroidManifest.xml b/Engine/lib/openal-soft/fmt-11.1.1/support/AndroidManifest.xml new file mode 100644 index 000000000..c282ef5a5 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/C++.sublime-syntax b/Engine/lib/openal-soft/fmt-11.1.1/support/C++.sublime-syntax new file mode 100644 index 000000000..9dfb5cbe4 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/C++.sublime-syntax @@ -0,0 +1,2061 @@ +%YAML 1.2 +--- +# http://www.sublimetext.com/docs/3/syntax.html +name: C++ (fmt) +comment: I don't think anyone uses .hp. .cp tends to be paired with .h. (I could be wrong. :) -- chris +file_extensions: + - cpp + - cc + - cp + - cxx + - c++ + - C + - h + - hh + - hpp + - hxx + - h++ + - inl + - ipp +first_line_match: '-\*- C\+\+ -\*-' +scope: source.c++ +variables: + identifier: \b[[:alpha:]_][[:alnum:]_]*\b # upper and lowercase + macro_identifier: \b[[:upper:]_][[:upper:][:digit:]_]{2,}\b # only uppercase, at least 3 chars + path_lookahead: '(?:::\s*)?(?:{{identifier}}\s*::\s*)*(?:template\s+)?{{identifier}}' + operator_method_name: '\boperator\s*(?:[-+*/%^&|~!=<>]|[-+*/%^&|=!<>]=|<<=?|>>=?|&&|\|\||\+\+|--|,|->\*?|\(\)|\[\]|""\s*{{identifier}})' + casts: 'const_cast|dynamic_cast|reinterpret_cast|static_cast' + operator_keywords: 'and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|xor|xor_eq|noexcept' + control_keywords: 'break|case|catch|continue|default|do|else|for|goto|if|_Pragma|return|switch|throw|try|while' + memory_operators: 'new|delete' + basic_types: 'asm|__asm__|auto|bool|_Bool|char|_Complex|double|float|_Imaginary|int|long|short|signed|unsigned|void' + before_tag: 'struct|union|enum\s+class|enum\s+struct|enum|class' + declspec: '__declspec\(\s*\w+(?:\([^)]+\))?\s*\)' + storage_classes: 'static|export|extern|friend|explicit|virtual|register|thread_local' + type_qualifier: 'const|constexpr|mutable|typename|volatile' + compiler_directive: 'inline|restrict|__restrict__|__restrict' + visibility_modifiers: 'private|protected|public' + other_keywords: 'typedef|nullptr|{{visibility_modifiers}}|static_assert|sizeof|using|typeid|alignof|alignas|namespace|template' + modifiers: '{{storage_classes}}|{{type_qualifier}}|{{compiler_directive}}' + non_angle_brackets: '(?=<<|<=)' + + regular: '[^(){}&;*^%=<>-]*' + paren_open: (?:\( + paren_close: '\))?' + generic_open: (?:< + generic_close: '>)?' + balance_parentheses: '{{regular}}{{paren_open}}{{regular}}{{paren_close}}{{regular}}' + generic_lookahead: <{{regular}}{{generic_open}}{{regular}}{{generic_open}}{{regular}}{{generic_close}}\s*{{generic_close}}{{balance_parentheses}}> + + data_structures_forward_decl_lookahead: '(\s+{{macro_identifier}})*\s*(:\s*({{path_lookahead}}|{{visibility_modifiers}}|,|\s|<[^;]*>)+)?;' + non_func_keywords: 'if|for|switch|while|decltype|sizeof|__declspec|__attribute__|typeid|alignof|alignas|static_assert' + + format_spec: |- + (?x: + (?:.? [<>=^])? # fill align + [ +-]? # sign + \#? # alternate form + # technically, octal and hexadecimal integers are also supported as 'width', but rarely used + \d* # width + ,? # thousands separator + (?:\.\d+)? # precision + [bcdeEfFgGnosxX%]? # type + ) + +contexts: + main: + - include: preprocessor-global + - include: global + + ############################################################################# + # Reusable contexts + # + # The follow contexts are currently constructed to be reused in the + # Objetive-C++ syntax. They are specifically constructed to not push into + # sub-contexts, which ensures that Objective-C++ code isn't accidentally + # lexed as plain C++. + # + # The "unique-*" contexts are additions that C++ makes over C, and thus can + # be directly reused in Objective-C++ along with contexts from Objective-C + # and C. + ############################################################################# + + unique-late-expressions: + # This is highlighted after all of the other control keywords + # to allow operator overloading to be lexed properly + - match: \boperator\b + scope: keyword.control.c++ + + unique-modifiers: + - match: \b({{modifiers}})\b + scope: storage.modifier.c++ + + unique-variables: + - match: \bthis\b + scope: variable.language.c++ + # common C++ instance var naming idiom -- fMemberName + - match: '\b(f|m)[[:upper:]]\w*\b' + scope: variable.other.readwrite.member.c++ + # common C++ instance var naming idiom -- m_member_name + - match: '\bm_[[:alnum:]_]+\b' + scope: variable.other.readwrite.member.c++ + + unique-constants: + - match: \bnullptr\b + scope: constant.language.c++ + + unique-keywords: + - match: \busing\b + scope: keyword.control.c++ + - match: \bbreak\b + scope: keyword.control.flow.break.c++ + - match: \bcontinue\b + scope: keyword.control.flow.continue.c++ + - match: \bgoto\b + scope: keyword.control.flow.goto.c++ + - match: \breturn\b + scope: keyword.control.flow.return.c++ + - match: \bthrow\b + scope: keyword.control.flow.throw.c++ + - match: \b({{control_keywords}})\b + scope: keyword.control.c++ + - match: '\bdelete\b(\s*\[\])?|\bnew\b(?!])' + scope: keyword.control.c++ + - match: \b({{operator_keywords}})\b + scope: keyword.operator.word.c++ + + unique-types: + - match: \b(char16_t|char32_t|wchar_t|nullptr_t)\b + scope: storage.type.c++ + - match: \bclass\b + scope: storage.type.c++ + + unique-strings: + - match: '((?:L|u8|u|U)?R)("([^\(\)\\ ]{0,16})\()' + captures: + 1: storage.type.string.c++ + 2: punctuation.definition.string.begin.c++ + push: + - meta_scope: string.quoted.double.c++ + - match: '\)\3"' + scope: punctuation.definition.string.end.c++ + pop: true + - match: '\{\{|\}\}' + scope: constant.character.escape.c++ + - include: formatting-syntax + + unique-numbers: + - match: |- + (?x) + (?: + # floats + (?: + (?:\b\d(?:[\d']*\d)?\.\d(?:[\d']*\d)?|\B\.\d(?:[\d']*\d)?)(?:[Ee][+-]?\d(?:[\d']*\d)?)?(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))?\b + | + (?:\b\d(?:[\d']*\d)?\.)(?:\B|(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))\b|(?:[Ee][+-]?\d(?:[\d']*\d)?)(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))?\b) + | + \b\d(?:[\d']*\d)?(?:[Ee][+-]?\d(?:[\d']*\d)?)(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))?\b + ) + | + # ints + \b(?: + (?: + # dec + [1-9](?:[\d']*\d)? + | + # oct + 0(?:[0-7']*[0-7])? + | + # hex + 0[Xx][\da-fA-F](?:[\da-fA-F']*[\da-fA-F])? + | + # bin + 0[Bb][01](?:[01']*[01])? + ) + # int suffixes + (?:(?:l{1,2}|L{1,2})[uU]?|[uU](?:l{0,2}|L{0,2})|(?:i[fl]?|h|min|[mun]?s|_\w*))?)\b + ) + (?!\.) # Number must not be followed by a decimal point + scope: constant.numeric.c++ + + identifiers: + - match: '{{identifier}}\s*(::)\s*' + captures: + 1: punctuation.accessor.c++ + - match: '(?:(::)\s*)?{{identifier}}' + captures: + 1: punctuation.accessor.c++ + + function-specifiers: + - match: \b(const|final|noexcept|override)\b + scope: storage.modifier.c++ + + ############################################################################# + # The following are C++-specific contexts that should not be reused. This is + # because they push into subcontexts and use variables that are C++-specific. + ############################################################################# + + ## Common context layout + + global: + - match: '(?=\btemplate\b)' + push: + - include: template + - match: (?=\S) + set: global-modifier + - include: namespace + - include: keywords-angle-brackets + - match: '(?={{path_lookahead}}\s*<)' + push: global-modifier + # Take care of comments just before a function definition. + - match: /\* + scope: punctuation.definition.comment.c + push: + - - match: \s*(?=\w) + set: global-modifier + - match: "" + pop: true + - - meta_scope: comment.block.c + - match: \*/ + scope: punctuation.definition.comment.c + pop: true + - include: early-expressions + - match: ^\s*\b(extern)(?=\s+"C(\+\+)?") + scope: storage.modifier.c++ + push: + - include: comments + - include: strings + - match: '\{' + scope: punctuation.section.block.begin.c++ + set: + - meta_scope: meta.extern-c.c++ + - match: '^\s*(#\s*ifdef)\s*__cplusplus\s*' + scope: meta.preprocessor.c++ + captures: + 1: keyword.control.import.c++ + set: + - match: '\}' + scope: punctuation.section.block.end.c++ + pop: true + - include: preprocessor-global + - include: global + - match: '\}' + scope: punctuation.section.block.end.c++ + pop: true + - include: preprocessor-global + - include: global + - match: (?=\S) + set: global-modifier + - match: ^\s*(?=\w) + push: global-modifier + - include: late-expressions + + statements: + - include: preprocessor-statements + - include: scope:source.c#label + - include: expressions + + expressions: + - include: early-expressions + - include: late-expressions + + early-expressions: + - include: early-expressions-before-generic-type + - include: generic-type + - include: early-expressions-after-generic-type + + early-expressions-before-generic-type: + - include: preprocessor-expressions + - include: comments + - include: case-default + - include: typedef + - include: keywords-angle-brackets + - include: keywords-parens + - include: keywords + - include: numbers + # Prevent a '<' from getting scoped as the start of another template + # parameter list, if in reality a less-than-or-equals sign is meant. + - match: <= + scope: keyword.operator.comparison.c + + early-expressions-after-generic-type: + - include: members-arrow + - include: operators + - include: members-dot + - include: strings + - include: parens + - include: brackets + - include: block + - include: variables + - include: constants + - match: ',' + scope: punctuation.separator.c++ + - match: '\)|\}' + scope: invalid.illegal.stray-bracket-end.c++ + + expressions-minus-generic-type: + - include: early-expressions-before-generic-type + - include: angle-brackets + - include: early-expressions-after-generic-type + - include: late-expressions + + expressions-minus-generic-type-function-call: + - include: early-expressions-before-generic-type + - include: angle-brackets + - include: early-expressions-after-generic-type + - include: late-expressions-before-function-call + - include: identifiers + - match: ';' + scope: punctuation.terminator.c++ + + late-expressions: + - include: late-expressions-before-function-call + - include: function-call + - include: identifiers + - match: ';' + scope: punctuation.terminator.c++ + + late-expressions-before-function-call: + - include: unique-late-expressions + - include: modifiers-parens + - include: modifiers + - include: types + + expressions-minus-function-call: + - include: early-expressions + - include: late-expressions-before-function-call + - include: identifiers + - match: ';' + scope: punctuation.terminator.c++ + + comments: + - include: scope:source.c#comments + + operators: + - include: scope:source.c#operators + + modifiers: + - include: unique-modifiers + - include: scope:source.c#modifiers + + variables: + - include: unique-variables + - include: scope:source.c#variables + + constants: + - include: unique-constants + - include: scope:source.c#constants + + keywords: + - include: unique-keywords + - include: scope:source.c#keywords + + types: + - include: unique-types + - include: types-parens + - include: scope:source.c#types + + strings: + - include: unique-strings + - match: '(L|u8|u|U)?(")' + captures: + 1: storage.type.string.c++ + 2: punctuation.definition.string.begin.c++ + push: + - meta_scope: string.quoted.double.c++ + - match: '"' + scope: punctuation.definition.string.end.c++ + pop: true + - include: scope:source.c#string_escaped_char + - match: |- + (?x)% + (\d+\$)? # field (argument #) + [#0\- +']* # flags + [,;:_]? # separator character (AltiVec) + ((-?\d+)|\*(-?\d+\$)?)? # minimum field width + (\.((-?\d+)|\*(-?\d+\$)?)?)? # precision + (hh|h|ll|l|j|t|z|q|L|vh|vl|v|hv|hl)? # length modifier + (\[[^\]]+\]|[am]s|[diouxXDOUeEfFgGaACcSspn%]) # conversion type + scope: constant.other.placeholder.c++ + - match: '\{\{|\}\}' + scope: constant.character.escape.c++ + - include: formatting-syntax + - include: scope:source.c#strings + + formatting-syntax: + # https://docs.python.org/3.6/library/string.html#formatstrings + - match: |- # simple form + (?x) + (\{) + (?: [\w.\[\]]+)? # field_name + ( ! [ars])? # conversion + ( : (?:{{format_spec}}| # format_spec OR + [^}%]*%.[^}]*) # any format-like string + )? + (\}) + scope: constant.other.placeholder.c++ + captures: + 1: punctuation.definition.placeholder.begin.c++ + 2: storage.modifier.c++onversion.c++ + 3: constant.other.format-spec.c++ + 4: punctuation.definition.placeholder.end.c++ + - match: \{(?=[^\}"']+\{[^"']*\}) # complex (nested) form + scope: punctuation.definition.placeholder.begin.c++ + push: + - meta_scope: constant.other.placeholder.c++ + - match: \} + scope: punctuation.definition.placeholder.end.c++ + pop: true + - match: '[\w.\[\]]+' + - match: '![ars]' + scope: storage.modifier.conversion.c++ + - match: ':' + push: + - meta_scope: meta.format-spec.c++ constant.other.format-spec.c++ + - match: (?=\}) + pop: true + - include: formatting-syntax + + numbers: + - include: unique-numbers + - include: scope:source.c#numbers + + ## C++-specific contexts + + case-default: + - match: '\b(default|case)\b' + scope: keyword.control.c++ + push: + - match: (?=[);,]) + pop: true + - match: ':' + scope: punctuation.separator.c++ + pop: true + - include: expressions + + modifiers-parens: + - match: '\b(alignas)\b\s*(\()' + captures: + 1: storage.modifier.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push: + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + - match: \b(__attribute__)\s*(\(\() + captures: + 1: storage.modifier.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push : + - meta_scope: meta.attribute.c++ + - meta_content_scope: meta.group.c++ + - include: parens + - include: strings + - match: \)\) + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - match: \b(__declspec)(\() + captures: + 1: storage.modifier.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push: + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - match: '\b(align|allocate|code_seg|deprecated|property|uuid)\b\s*(\()' + captures: + 1: storage.modifier.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push: + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: numbers + - include: strings + - match: \b(get|put)\b + scope: variable.parameter.c++ + - match: ',' + scope: punctuation.separator.c++ + - match: '=' + scope: keyword.operator.assignment.c++ + - match: '\b(appdomain|deprecated|dllimport|dllexport|jintrinsic|naked|noalias|noinline|noreturn|nothrow|novtable|process|restrict|safebuffers|selectany|thread)\b' + scope: constant.other.c++ + + types-parens: + - match: '\b(decltype)\b\s*(\()' + captures: + 1: storage.type.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push: + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + + keywords-angle-brackets: + - match: \b({{casts}})\b\s* + scope: keyword.operator.word.cast.c++ + push: + - match: '>' + scope: punctuation.section.generic.end.c++ + pop: true + - match: '<' + scope: punctuation.section.generic.begin.c++ + push: + - match: '(?=>)' + pop: true + - include: expressions-minus-generic-type-function-call + + keywords-parens: + - match: '\b(alignof|typeid|static_assert|sizeof)\b\s*(\()' + captures: + 1: keyword.operator.word.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push: + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + + namespace: + - match: '\b(using)\s+(namespace)\s+(?={{path_lookahead}})' + captures: + 1: keyword.control.c++ + 2: keyword.control.c++ + push: + - include: identifiers + - match: '' + pop: true + - match: '\b(namespace)\s+(?=({{path_lookahead}})?(?!\s*[;,]))' + scope: meta.namespace.c++ + captures: + 1: keyword.control.c++ + push: + - meta_content_scope: meta.namespace.c++ entity.name.namespace.c++ + - include: identifiers + - match: '' + set: + - meta_scope: meta.namespace.c++ + - include: comments + - match: '=' + scope: keyword.operator.alias.c++ + - match: '(?=;)' + pop: true + - match: '\}' + scope: meta.block.c++ punctuation.section.block.end.c++ + pop: true + - match: '\{' + scope: punctuation.section.block.begin.c++ + push: + - meta_scope: meta.block.c++ + - match: '(?=\})' + pop: true + - include: preprocessor-global + - include: global + - include: expressions + + template-common: + # Exit the template scope if we hit some basic invalid characters. This + # helps when a user is in the middle of typing their template types and + # prevents re-highlighting the whole file until the next > is found. + - match: (?=[{};]) + pop: true + - include: expressions + + template: + - match: \btemplate\b + scope: storage.type.template.c++ + push: + - meta_scope: meta.template.c++ + # Explicitly include comments here at the top, in order to NOT match the + # \S lookahead in the case of comments. + - include: comments + - match: < + scope: punctuation.section.generic.begin.c++ + set: + - meta_content_scope: meta.template.c++ + - match: '>' + scope: meta.template.c++ punctuation.section.generic.end.c++ + pop: true + - match: \.{3} + scope: keyword.operator.variadic.c++ + - match: \b(typename|{{before_tag}})\b + scope: storage.type.c++ + - include: template # include template here for nested templates + - include: template-common + - match: (?=\S) + set: + - meta_content_scope: meta.template.c++ + - match: \b({{before_tag}})\b + scope: storage.type.c++ + - include: template-common + + generic-type: + - match: '(?=(?!template){{path_lookahead}}\s*{{generic_lookahead}}\s*\()' + push: + - meta_scope: meta.function-call.c++ + - match: \btemplate\b + scope: storage.type.template.c++ + - match: '(?:(::)\s*)?{{identifier}}\s*(::)\s*' + captures: + 1: punctuation.accessor.double-colon.c++ + 2: punctuation.accessor.double-colon.c++ + - match: (?:(::)\s*)?({{identifier}})\s*(<) + captures: + 1: punctuation.accessor.double-colon.c++ + 2: variable.function.c++ + 3: punctuation.section.generic.begin.c++ + push: + - match: '>' + scope: punctuation.section.generic.end.c++ + pop: true + - include: expressions-minus-generic-type-function-call + - match: (?:(::)\s*)?({{identifier}})\s*(\() + captures: + 1: punctuation.accessor.double-colon.c++ + 2: variable.function.c++ + 3: punctuation.section.group.begin.c++ + set: + - meta_scope: meta.function-call.c++ + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + - include: angle-brackets + - match: '\(' + scope: meta.group.c++ punctuation.section.group.begin.c++ + set: + - meta_scope: meta.function-call.c++ + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + - match: '(?=(?!template){{path_lookahead}}\s*{{generic_lookahead}})' + push: + - include: identifiers + - match: '<' + scope: punctuation.section.generic.begin.c++ + set: + - match: '>' + scope: punctuation.section.generic.end.c++ + pop: true + - include: expressions-minus-generic-type-function-call + + angle-brackets: + - match: '<(?!<)' + scope: punctuation.section.generic.begin.c++ + push: + - match: '>' + scope: punctuation.section.generic.end.c++ + pop: true + - include: expressions-minus-generic-type-function-call + + block: + - match: '\{' + scope: punctuation.section.block.begin.c++ + push: + - meta_scope: meta.block.c++ + - match: (?=^\s*#\s*(elif|else|endif)\b) + pop: true + - match: '\}' + scope: punctuation.section.block.end.c++ + pop: true + - include: statements + + function-call: + - match: (?={{path_lookahead}}\s*\() + push: + - meta_scope: meta.function-call.c++ + - include: scope:source.c#c99 + - match: '(?:(::)\s*)?{{identifier}}\s*(::)\s*' + scope: variable.function.c++ + captures: + 1: punctuation.accessor.c++ + 2: punctuation.accessor.c++ + - match: '(?:(::)\s*)?{{identifier}}' + scope: variable.function.c++ + captures: + 1: punctuation.accessor.c++ + - match: '\(' + scope: meta.group.c++ punctuation.section.group.begin.c++ + set: + - meta_content_scope: meta.function-call.c++ meta.group.c++ + - match: '\)' + scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + + members-inside-function-call: + - meta_content_scope: meta.method-call.c++ meta.group.c++ + - match: \) + scope: meta.method-call.c++ meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + + members-after-accessor-junction: + # After we've seen an accessor (dot or arrow), this context decides what + # kind of entity we're accessing. + - include: comments + - match: \btemplate\b + scope: meta.method-call.c++ storage.type.template.c++ + # Guaranteed to be a template member function call after we match this + set: + - meta_content_scope: meta.method-call.c++ + - include: comments + - match: '{{identifier}}' + scope: variable.function.member.c++ + set: + - meta_content_scope: meta.method-call.c++ + - match: \( + scope: meta.group.c++ punctuation.section.group.begin.c++ + set: members-inside-function-call + - include: comments + - include: angle-brackets + - match: (?=\S) # safety pop + pop: true + - match: (?=\S) # safety pop + pop: true + # Operator overloading + - match: '({{operator_method_name}})\s*(\()' + captures: + 0: meta.method-call.c++ + 1: variable.function.member.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + set: members-inside-function-call + # Non-templated member function call + - match: (~?{{identifier}})\s*(\() + captures: + 0: meta.method-call.c++ + 1: variable.function.member.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + set: members-inside-function-call + # Templated member function call + - match: (~?{{identifier}})\s*(?={{generic_lookahead}}) + captures: + 1: variable.function.member.c++ + set: + - meta_scope: meta.method-call.c++ + - match: < + scope: punctuation.section.generic.begin.c++ + set: + - meta_content_scope: meta.method-call.c++ + - match: '>' + scope: punctuation.section.generic.end.c++ + set: + - meta_content_scope: meta.method-call.c++ + - include: comments + - match: \( + scope: punctuation.section.group.begin.c++ + set: members-inside-function-call + - match: (?=\S) # safety pop + pop: true + - include: expressions + # Explicit base-class access + - match: ({{identifier}})\s*(::) + captures: + 1: variable.other.base-class.c++ + 2: punctuation.accessor.double-colon.c++ + set: members-after-accessor-junction # reset + # Just a regular member variable + - match: '{{identifier}}' + scope: variable.other.readwrite.member.c++ + pop: true + + members-dot: + - include: scope:source.c#access-illegal + # No lookahead required because members-dot goes after operators in the + # early-expressions-after-generic-type context. This means triple dots + # (i.e. "..." or "variadic") is attempted first. + - match: \. + scope: punctuation.accessor.dot.c++ + push: members-after-accessor-junction + + members-arrow: + # This needs to be before operators in the + # early-expressions-after-generic-type context because otherwise the "->" + # from the C language will match. + - match: -> + scope: punctuation.accessor.arrow.c++ + push: members-after-accessor-junction + + typedef: + - match: \btypedef\b + scope: storage.type.c++ + push: + - match: ({{identifier}})?\s*(?=;) + captures: + 1: entity.name.type.typedef.c++ + pop: true + - match: \b(struct)\s+({{identifier}})\b + captures: + 1: storage.type.c++ + - include: expressions-minus-generic-type + + parens: + - match: \( + scope: punctuation.section.group.begin.c++ + push: + - meta_scope: meta.group.c++ + - match: \) + scope: punctuation.section.group.end.c++ + pop: true + - include: expressions + + brackets: + - match: \[ + scope: punctuation.section.brackets.begin.c++ + push: + - meta_scope: meta.brackets.c++ + - match: \] + scope: punctuation.section.brackets.end.c++ + pop: true + - include: expressions + + function-trailing-return-type: + - match: '{{non_angle_brackets}}' + pop: true + - include: angle-brackets + - include: types + - include: modifiers-parens + - include: modifiers + - include: identifiers + - match: \*|& + scope: keyword.operator.c++ + - include: function-trailing-return-type-parens + - match: '(?=\S)' + pop: true + + function-trailing-return-type-parens: + - match: \( + scope: punctuation.section.group.begin.c++ + push: + - meta_scope: meta.group.c++ + - match: \) + scope: punctuation.section.group.end.c++ + pop: true + - include: function-trailing-return-type + + ## Detection of function and data structure definitions at the global level + + global-modifier: + - include: comments + - include: modifiers-parens + - include: modifiers + # Constructors and destructors don't have a type + - match: '(?={{path_lookahead}}\s*::\s*{{identifier}}\s*(\(|$))' + set: + - meta_content_scope: meta.function.c++ entity.name.function.constructor.c++ + - include: identifiers + - match: '(?=[^\w\s])' + set: function-definition-params + - match: '(?={{path_lookahead}}\s*::\s*~{{identifier}}\s*(\(|$))' + set: + - meta_content_scope: meta.function.c++ entity.name.function.destructor.c++ + - include: identifiers + - match: '~{{identifier}}' + - match: '(?=[^\w\s])' + set: function-definition-params + # If we see a path ending in :: before a newline, we don't know if it is + # a constructor or destructor, or a long return type, so we are just going + # to treat it like a regular function. Most likely it is a constructor, + # since it doesn't seem most developers would create such a long typename. + - match: '(?={{path_lookahead}}\s*::\s*$)' + set: + - meta_content_scope: meta.function.c++ entity.name.function.c++ + - include: identifiers + - match: '~{{identifier}}' + - match: '(?=[^\w\s])' + set: function-definition-params + - include: unique-strings + - match: '(?=\S)' + set: global-type + + global-type: + - include: comments + - match: \*|& + scope: keyword.operator.c++ + - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}}|operator)\b)' + pop: true + - match: '(?=\s)' + set: global-maybe-function + # If a class/struct/enum followed by a name that is not a macro or declspec + # then this is likely a return type of a function. This is uncommon. + - match: |- + (?x: + ({{before_tag}}) + \s+ + (?= + (?![[:upper:][:digit:]_]+\b|__declspec|{{before_tag}}) + {{path_lookahead}} + (\s+{{identifier}}\s*\(|\s*[*&]) + ) + ) + captures: + 1: storage.type.c++ + set: + - include: identifiers + - match: '' + set: global-maybe-function + # The previous match handles return types of struct/enum/etc from a func, + # there this one exits the context to allow matching an actual struct/class + - match: '(?=\b({{before_tag}})\b)' + set: data-structures + - match: '(?=\b({{casts}})\b\s*<)' + pop: true + - match: '{{non_angle_brackets}}' + pop: true + - include: angle-brackets + - include: types + # Allow a macro call + - match: '({{identifier}})\s*(\()(?=[^\)]+\))' + captures: + 1: variable.function.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push: + - meta_scope: meta.function-call.c++ + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + - match: '(?={{path_lookahead}}\s*\()' + set: + - include: function-call + - match: '' + pop: true + - include: variables + - include: constants + - include: identifiers + - match: (?=\W) + pop: true + + global-maybe-function: + - include: comments + # Consume pointer info, macros and any type info that was offset by macros + - match: \*|& + scope: keyword.operator.c++ + - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}})\b)' + pop: true + - match: '\b({{type_qualifier}})\b' + scope: storage.modifier.c++ + - match: '{{non_angle_brackets}}' + pop: true + - include: angle-brackets + - include: types + - include: modifiers-parens + - include: modifiers + # All uppercase identifier just before a newline is most likely a macro + - match: '[[:upper:][:digit:]_]+\s*$' + # Operator overloading + - match: '(?=({{path_lookahead}}\s*(?:{{generic_lookahead}})?::\s*)?{{operator_method_name}}\s*(\(|$))' + set: + - meta_content_scope: meta.function.c++ entity.name.function.c++ + - include: identifiers + - match: '(?=\s*(\(|$))' + set: function-definition-params + # Identifier that is not the function name - likely a macro or type + - match: '(?={{path_lookahead}}([ \t]+|[*&])(?!\s*(<|::|\(|$)))' + push: + - include: identifiers + - match: '' + pop: true + # Real function definition + - match: '(?={{path_lookahead}}({{generic_lookahead}}({{path_lookahead}})?)\s*(\(|$))' + set: [function-definition-params, global-function-identifier-generic] + - match: '(?={{path_lookahead}}\s*(\(|$))' + set: [function-definition-params, global-function-identifier] + - match: '(?={{path_lookahead}}\s*::\s*$)' + set: [function-definition-params, global-function-identifier] + - match: '(?=\S)' + pop: true + + global-function-identifier-generic: + - include: angle-brackets + - match: '::' + scope: punctuation.accessor.c++ + - match: '(?={{identifier}}<.*>\s*\()' + push: + - meta_content_scope: entity.name.function.c++ + - include: identifiers + - match: '(?=<)' + pop: true + - match: '(?={{identifier}}\s*\()' + push: + - meta_content_scope: entity.name.function.c++ + - include: identifiers + - match: '' + pop: true + - match: '(?=\()' + pop: true + + global-function-identifier: + - meta_content_scope: entity.name.function.c++ + - include: identifiers + - match: '(?=\S)' + pop: true + + function-definition-params: + - meta_content_scope: meta.function.c++ + - include: comments + - match: '(?=\()' + set: + - match: \( + scope: meta.function.parameters.c++ meta.group.c++ punctuation.section.group.begin.c++ + set: + - meta_content_scope: meta.function.parameters.c++ meta.group.c++ + - match : \) + scope: punctuation.section.group.end.c++ + set: function-definition-continue + - match: '\bvoid\b' + scope: storage.type.c++ + - match: '{{identifier}}(?=\s*(\[|,|\)|=))' + scope: variable.parameter.c++ + - match: '=' + scope: keyword.operator.assignment.c++ + push: + - match: '(?=,|\))' + pop: true + - include: expressions-minus-generic-type + - include: scope:source.c#preprocessor-line-continuation + - include: expressions-minus-generic-type + - include: scope:source.c#preprocessor-line-continuation + - match: (?=\S) + pop: true + + function-definition-continue: + - meta_content_scope: meta.function.c++ + - include: comments + - match: '(?=;)' + pop: true + - match: '->' + scope: punctuation.separator.c++ + set: function-definition-trailing-return + - include: function-specifiers + - match: '=' + scope: keyword.operator.assignment.c++ + - match: '&' + scope: keyword.operator.c++ + - match: \b0\b + scope: constant.numeric.c++ + - match: \b(default|delete)\b + scope: storage.modifier.c++ + - match: '(?=\{)' + set: function-definition-body + - match: '(?=\S)' + pop: true + + function-definition-trailing-return: + - include: comments + - match: '(?=;)' + pop: true + - match: '(?=\{)' + set: function-definition-body + - include: function-specifiers + - include: function-trailing-return-type + + function-definition-body: + - meta_content_scope: meta.function.c++ meta.block.c++ + - match: '\{' + scope: punctuation.section.block.begin.c++ + set: + - meta_content_scope: meta.function.c++ meta.block.c++ + - match: '\}' + scope: meta.function.c++ meta.block.c++ punctuation.section.block.end.c++ + pop: true + - match: (?=^\s*#\s*(elif|else|endif)\b) + pop: true + - match: '(?=({{before_tag}})([^(;]+$|.*\{))' + push: data-structures + - include: statements + + ## Data structures including classes, structs, unions and enums + + data-structures: + - match: '\bclass\b' + scope: storage.type.c++ + set: data-structures-class-definition + # Detect variable type definitions using struct/enum/union followed by a tag + - match: '\b({{before_tag}})(?=\s+{{path_lookahead}}\s+{{path_lookahead}}\s*[=;\[])' + scope: storage.type.c++ + - match: '\bstruct\b' + scope: storage.type.c++ + set: data-structures-struct-definition + - match: '\benum(\s+(class|struct))?\b' + scope: storage.type.c++ + set: data-structures-enum-definition + - match: '\bunion\b' + scope: storage.type.c++ + set: data-structures-union-definition + - match: '(?=\S)' + pop: true + + preprocessor-workaround-eat-macro-before-identifier: + # Handle macros so they aren't matched as the class name + - match: ({{macro_identifier}})(?=\s+~?{{identifier}}) + captures: + 1: meta.assumed-macro.c + + data-structures-class-definition: + - meta_scope: meta.class.c++ + - include: data-structures-definition-common-begin + - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})' + scope: entity.name.class.forward-decl.c++ + set: data-structures-class-definition-after-identifier + - match: '{{identifier}}' + scope: entity.name.class.c++ + set: data-structures-class-definition-after-identifier + - match: '(?=[:{])' + set: data-structures-class-definition-after-identifier + - match: '(?=;)' + pop: true + + data-structures-class-definition-after-identifier: + - meta_content_scope: meta.class.c++ + - include: data-structures-definition-common-begin + # No matching of identifiers since they should all be macros at this point + - include: data-structures-definition-common-end + - match: '\{' + scope: meta.block.c++ punctuation.section.block.begin.c++ + set: + - meta_content_scope: meta.class.c++ meta.block.c++ + - match: '\}' + scope: meta.class.c++ meta.block.c++ punctuation.section.block.end.c++ + pop: true + - include: data-structures-body + + data-structures-struct-definition: + - meta_scope: meta.struct.c++ + - include: data-structures-definition-common-begin + - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})' + scope: entity.name.struct.forward-decl.c++ + set: data-structures-struct-definition-after-identifier + - match: '{{identifier}}' + scope: entity.name.struct.c++ + set: data-structures-struct-definition-after-identifier + - match: '(?=[:{])' + set: data-structures-struct-definition-after-identifier + - match: '(?=;)' + pop: true + + data-structures-struct-definition-after-identifier: + - meta_content_scope: meta.struct.c++ + - include: data-structures-definition-common-begin + # No matching of identifiers since they should all be macros at this point + - include: data-structures-definition-common-end + - match: '\{' + scope: meta.block.c++ punctuation.section.block.begin.c++ + set: + - meta_content_scope: meta.struct.c++ meta.block.c++ + - match: '\}' + scope: meta.struct.c++ meta.block.c++ punctuation.section.block.end.c++ + pop: true + - include: data-structures-body + + data-structures-enum-definition: + - meta_scope: meta.enum.c++ + - include: data-structures-definition-common-begin + - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})' + scope: entity.name.enum.forward-decl.c++ + set: data-structures-enum-definition-after-identifier + - match: '{{identifier}}' + scope: entity.name.enum.c++ + set: data-structures-enum-definition-after-identifier + - match: '(?=[:{])' + set: data-structures-enum-definition-after-identifier + - match: '(?=;)' + pop: true + + data-structures-enum-definition-after-identifier: + - meta_content_scope: meta.enum.c++ + - include: data-structures-definition-common-begin + # No matching of identifiers since they should all be macros at this point + - include: data-structures-definition-common-end + - match: '\{' + scope: meta.block.c++ punctuation.section.block.begin.c++ + set: + - meta_content_scope: meta.enum.c++ meta.block.c++ + # Enums don't support methods so we have a simplified body + - match: '\}' + scope: meta.enum.c++ meta.block.c++ punctuation.section.block.end.c++ + pop: true + - include: statements + + data-structures-union-definition: + - meta_scope: meta.union.c++ + - include: data-structures-definition-common-begin + - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})' + scope: entity.name.union.forward-decl.c++ + set: data-structures-union-definition-after-identifier + - match: '{{identifier}}' + scope: entity.name.union.c++ + set: data-structures-union-definition-after-identifier + - match: '(?=[{])' + set: data-structures-union-definition-after-identifier + - match: '(?=;)' + pop: true + + data-structures-union-definition-after-identifier: + - meta_content_scope: meta.union.c++ + - include: data-structures-definition-common-begin + # No matching of identifiers since they should all be macros at this point + # Unions don't support base classes + - include: angle-brackets + - match: '\{' + scope: meta.block.c++ punctuation.section.block.begin.c++ + set: + - meta_content_scope: meta.union.c++ meta.block.c++ + - match: '\}' + scope: meta.union.c++ meta.block.c++ punctuation.section.block.end.c++ + pop: true + - include: data-structures-body + - match: '(?=;)' + pop: true + + data-structures-definition-common-begin: + - include: comments + - match: '(?=\b(?:{{before_tag}}|{{control_keywords}})\b)' + pop: true + - include: preprocessor-other + - include: modifiers-parens + - include: modifiers + - include: preprocessor-workaround-eat-macro-before-identifier + + data-structures-definition-common-end: + - include: angle-brackets + - match: \bfinal\b + scope: storage.modifier.c++ + - match: ':' + scope: punctuation.separator.c++ + push: + - include: comments + - include: preprocessor-other + - include: modifiers-parens + - include: modifiers + - match: '\b(virtual|{{visibility_modifiers}})\b' + scope: storage.modifier.c++ + - match: (?={{path_lookahead}}) + push: + - meta_scope: entity.other.inherited-class.c++ + - include: identifiers + - match: '' + pop: true + - include: angle-brackets + - match: ',' + scope: punctuation.separator.c++ + - match: (?=\{|;) + pop: true + - match: '(?=;)' + pop: true + + data-structures-body: + - include: preprocessor-data-structures + - match: '(?=\btemplate\b)' + push: + - include: template + - match: (?=\S) + set: data-structures-modifier + - include: typedef + - match: \b({{visibility_modifiers}})\s*(:)(?!:) + captures: + 1: storage.modifier.c++ + 2: punctuation.section.class.c++ + - match: '^\s*(?=(?:~?\w+|::))' + push: data-structures-modifier + - include: expressions-minus-generic-type + + data-structures-modifier: + - match: '\bfriend\b' + scope: storage.modifier.c++ + push: + - match: (?=;) + pop: true + - match: '\{' + scope: punctuation.section.block.begin.c++ + set: + - meta_scope: meta.block.c++ + - match: '\}' + scope: punctuation.section.block.end.c++ + pop: true + - include: statements + - match: '\b({{before_tag}})\b' + scope: storage.type.c++ + - include: expressions-minus-function-call + - include: comments + - include: modifiers-parens + - include: modifiers + - match: '\bstatic_assert(?=\s*\()' + scope: meta.static-assert.c++ keyword.operator.word.c++ + push: + - match: '\(' + scope: meta.group.c++ punctuation.section.group.begin.c++ + set: + - meta_content_scope: meta.function-call.c++ meta.group.c++ + - match: '\)' + scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + # Destructor + - match: '(?:{{identifier}}\s*(::)\s*)?~{{identifier}}(?=\s*(\(|$))' + scope: meta.method.destructor.c++ entity.name.function.destructor.c++ + captures: + 1: punctuation.accessor.c++ + set: method-definition-params + # It's a macro, not a constructor if there is no type in the first param + - match: '({{identifier}})\s*(\()(?=\s*(?!void){{identifier}}\s*[),])' + captures: + 1: variable.function.c++ + 2: meta.group.c++ punctuation.section.group.begin.c++ + push: + - meta_scope: meta.function-call.c++ + - meta_content_scope: meta.group.c++ + - match: '\)' + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + # Constructor + - include: preprocessor-workaround-eat-macro-before-identifier + - match: '((?!{{before_tag}}|template){{identifier}})(?=\s*\()' + scope: meta.method.constructor.c++ entity.name.function.constructor.c++ + set: method-definition-params + # Long form constructor + - match: '({{identifier}}\s*(::)\s*{{identifier}})(?=\s*\()' + captures: + 1: meta.method.constructor.c++ entity.name.function.constructor.c++ + 2: punctuation.accessor.c++ + push: method-definition-params + - match: '(?=\S)' + set: data-structures-type + + data-structures-type: + - include: comments + - match: \*|& + scope: keyword.operator.c++ + # Cast methods + - match: '(operator)\s+({{identifier}})(?=\s*(\(|$))' + captures: + 1: keyword.control.c++ + 2: meta.method.c++ entity.name.function.c++ + set: method-definition-params + - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}}|operator)\b)' + pop: true + - match: '(?=\s)' + set: data-structures-maybe-method + # If a class/struct/enum followed by a name that is not a macro or declspec + # then this is likely a return type of a function. This is uncommon. + - match: |- + (?x: + ({{before_tag}}) + \s+ + (?= + (?![[:upper:][:digit:]_]+\b|__declspec|{{before_tag}}) + {{path_lookahead}} + (\s+{{identifier}}\s*\(|\s*[*&]) + ) + ) + captures: + 1: storage.type.c++ + set: + - include: identifiers + - match: '' + set: data-structures-maybe-method + # The previous match handles return types of struct/enum/etc from a func, + # there this one exits the context to allow matching an actual struct/class + - match: '(?=\b({{before_tag}})\b)' + set: data-structures + - match: '(?=\b({{casts}})\b\s*<)' + pop: true + - match: '{{non_angle_brackets}}' + pop: true + - include: angle-brackets + - include: types + - include: variables + - include: constants + - include: identifiers + - match: (?=[&*]) + set: data-structures-maybe-method + - match: (?=\W) + pop: true + + data-structures-maybe-method: + - include: comments + # Consume pointer info, macros and any type info that was offset by macros + - match: \*|& + scope: keyword.operator.c++ + - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}})\b)' + pop: true + - match: '\b({{type_qualifier}})\b' + scope: storage.modifier.c++ + - match: '{{non_angle_brackets}}' + pop: true + - include: angle-brackets + - include: types + - include: modifiers-parens + - include: modifiers + # Operator overloading + - match: '{{operator_method_name}}(?=\s*(\(|$))' + scope: meta.method.c++ entity.name.function.c++ + set: method-definition-params + # Identifier that is not the function name - likely a macro or type + - match: '(?={{path_lookahead}}([ \t]+|[*&])(?!\s*(<|::|\()))' + push: + - include: identifiers + - match: '' + pop: true + # Real function definition + - match: '(?={{path_lookahead}}({{generic_lookahead}})\s*(\())' + set: [method-definition-params, data-structures-function-identifier-generic] + - match: '(?={{path_lookahead}}\s*(\())' + set: [method-definition-params, data-structures-function-identifier] + - match: '(?={{path_lookahead}}\s*::\s*$)' + set: [method-definition-params, data-structures-function-identifier] + - match: '(?=\S)' + pop: true + + data-structures-function-identifier-generic: + - include: angle-brackets + - match: '(?={{identifier}})' + push: + - meta_content_scope: entity.name.function.c++ + - include: identifiers + - match: '(?=<)' + pop: true + - match: '(?=\()' + pop: true + + data-structures-function-identifier: + - meta_content_scope: entity.name.function.c++ + - include: identifiers + - match: '(?=\S)' + pop: true + + method-definition-params: + - meta_content_scope: meta.method.c++ + - include: comments + - match: '(?=\()' + set: + - match: \( + scope: meta.method.parameters.c++ meta.group.c++ punctuation.section.group.begin.c++ + set: + - meta_content_scope: meta.method.parameters.c++ meta.group.c++ + - match : \) + scope: punctuation.section.group.end.c++ + set: method-definition-continue + - match: '\bvoid\b' + scope: storage.type.c++ + - match: '{{identifier}}(?=\s*(\[|,|\)|=))' + scope: variable.parameter.c++ + - match: '=' + scope: keyword.operator.assignment.c++ + push: + - match: '(?=,|\))' + pop: true + - include: expressions-minus-generic-type + - include: expressions-minus-generic-type + - match: '(?=\S)' + pop: true + + method-definition-continue: + - meta_content_scope: meta.method.c++ + - include: comments + - match: '(?=;)' + pop: true + - match: '->' + scope: punctuation.separator.c++ + set: method-definition-trailing-return + - include: function-specifiers + - match: '=' + scope: keyword.operator.assignment.c++ + - match: '&' + scope: keyword.operator.c++ + - match: \b0\b + scope: constant.numeric.c++ + - match: \b(default|delete)\b + scope: storage.modifier.c++ + - match: '(?=:)' + set: + - match: ':' + scope: punctuation.separator.initializer-list.c++ + set: + - meta_scope: meta.method.constructor.initializer-list.c++ + - match: '{{identifier}}' + scope: variable.other.readwrite.member.c++ + push: + - match: \( + scope: meta.group.c++ punctuation.section.group.begin.c++ + set: + - meta_content_scope: meta.group.c++ + - match: \) + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + - match: \{ + scope: meta.group.c++ punctuation.section.group.begin.c++ + set: + - meta_content_scope: meta.group.c++ + - match: \} + scope: meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + - include: comments + - match: (?=\{|;) + set: method-definition-continue + - include: expressions + - match: '(?=\{)' + set: method-definition-body + - match: '(?=\S)' + pop: true + + method-definition-trailing-return: + - include: comments + - match: '(?=;)' + pop: true + - match: '(?=\{)' + set: method-definition-body + - include: function-specifiers + - include: function-trailing-return-type + + method-definition-body: + - meta_content_scope: meta.method.c++ meta.block.c++ + - match: '\{' + scope: punctuation.section.block.begin.c++ + set: + - meta_content_scope: meta.method.c++ meta.block.c++ + - match: '\}' + scope: meta.method.c++ meta.block.c++ punctuation.section.block.end.c++ + pop: true + - match: (?=^\s*#\s*(elif|else|endif)\b) + pop: true + - match: '(?=({{before_tag}})([^(;]+$|.*\{))' + push: data-structures + - include: statements + + ## Preprocessor for data-structures + + preprocessor-data-structures: + - include: preprocessor-rule-enabled-data-structures + - include: preprocessor-rule-disabled-data-structures + - include: preprocessor-practical-workarounds + + preprocessor-rule-disabled-data-structures: + - match: ^\s*((#if)\s+(0))\b + captures: + 1: meta.preprocessor.c++ + 2: keyword.control.import.c++ + 3: constant.numeric.preprocessor.c++ + push: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: ^\s*(#\s*else)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.else.c++ + push: + - match: (?=^\s*#\s*endif\b) + pop: true + - include: negated-block + - include: data-structures-body + - match: "" + push: + - meta_scope: comment.block.preprocessor.if-branch.c++ + - match: (?=^\s*#\s*(else|endif)\b) + pop: true + - include: scope:source.c#preprocessor-disabled + + preprocessor-rule-enabled-data-structures: + - match: ^\s*((#if)\s+(0*1))\b + captures: + 1: meta.preprocessor.c++ + 2: keyword.control.import.c++ + 3: constant.numeric.preprocessor.c++ + push: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: ^\s*(#\s*else)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.else.c++ + push: + - meta_content_scope: comment.block.preprocessor.else-branch.c++ + - match: (?=^\s*#\s*endif\b) + pop: true + - include: scope:source.c#preprocessor-disabled + - match: "" + push: + - match: (?=^\s*#\s*(else|endif)\b) + pop: true + - include: negated-block + - include: data-structures-body + + ## Preprocessor for global + + preprocessor-global: + - include: preprocessor-rule-enabled-global + - include: preprocessor-rule-disabled-global + - include: preprocessor-rule-other-global + + preprocessor-statements: + - include: preprocessor-rule-enabled-statements + - include: preprocessor-rule-disabled-statements + - include: preprocessor-rule-other-statements + + preprocessor-expressions: + - include: scope:source.c#incomplete-inc + - include: preprocessor-macro-define + - include: scope:source.c#pragma-mark + - include: preprocessor-other + + preprocessor-rule-disabled-global: + - match: ^\s*((#if)\s+(0))\b + captures: + 1: meta.preprocessor.c++ + 2: keyword.control.import.c++ + 3: constant.numeric.preprocessor.c++ + push: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: ^\s*(#\s*else)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.else.c++ + push: + - match: (?=^\s*#\s*endif\b) + pop: true + - include: preprocessor-global + - include: negated-block + - include: global + - match: "" + push: + - meta_scope: comment.block.preprocessor.if-branch.c++ + - match: (?=^\s*#\s*(else|endif)\b) + pop: true + - include: scope:source.c#preprocessor-disabled + + preprocessor-rule-enabled-global: + - match: ^\s*((#if)\s+(0*1))\b + captures: + 1: meta.preprocessor.c++ + 2: keyword.control.import.c++ + 3: constant.numeric.preprocessor.c++ + push: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: ^\s*(#\s*else)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.else.c++ + push: + - meta_content_scope: comment.block.preprocessor.else-branch.c++ + - match: (?=^\s*#\s*endif\b) + pop: true + - include: scope:source.c#preprocessor-disabled + - match: "" + push: + - match: (?=^\s*#\s*(else|endif)\b) + pop: true + - include: preprocessor-global + - include: negated-block + - include: global + + preprocessor-rule-other-global: + - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b + captures: + 1: keyword.control.import.c++ + push: + - meta_scope: meta.preprocessor.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-comments + - match: \bdefined\b + scope: keyword.control.c++ + # Enter a new scope where all elif/else branches have their + # contexts popped by a subsequent elif/else/endif. This ensures that + # preprocessor branches don't push multiple meta.block scopes on + # the stack, thus messing up the "global" context's detection of + # functions. + - match: $\n + set: preprocessor-if-branch-global + + # These gymnastics here ensure that we are properly handling scope even + # when the preprocessor is used to create different scope beginnings, such + # as a different if/while condition + preprocessor-if-branch-global: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: (?=^\s*#\s*(elif|else)\b) + push: preprocessor-elif-else-branch-global + - match: \{ + scope: punctuation.section.block.begin.c++ + set: preprocessor-block-if-branch-global + - include: preprocessor-global + - include: negated-block + - include: global + + preprocessor-block-if-branch-global: + - meta_scope: meta.block.c++ + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + set: preprocessor-block-finish-global + - match: (?=^\s*#\s*(elif|else)\b) + push: preprocessor-elif-else-branch-global + - match: \} + scope: punctuation.section.block.end.c++ + set: preprocessor-if-branch-global + - include: statements + + preprocessor-block-finish-global: + - meta_scope: meta.block.c++ + - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + set: preprocessor-block-finish-if-branch-global + - match: \} + scope: punctuation.section.block.end.c++ + pop: true + - include: statements + + preprocessor-block-finish-if-branch-global: + - match: ^\s*(#\s*endif)\b + captures: + 1: keyword.control.import.c++ + pop: true + - match: \} + scope: punctuation.section.block.end.c++ + set: preprocessor-if-branch-global + - include: statements + + preprocessor-elif-else-branch-global: + - match: (?=^\s*#\s*(endif)\b) + pop: true + - include: preprocessor-global + - include: negated-block + - include: global + + ## Preprocessor for statements + + preprocessor-rule-disabled-statements: + - match: ^\s*((#if)\s+(0))\b + captures: + 1: meta.preprocessor.c++ + 2: keyword.control.import.c++ + 3: constant.numeric.preprocessor.c++ + push: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: ^\s*(#\s*else)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.else.c++ + push: + - match: (?=^\s*#\s*endif\b) + pop: true + - include: negated-block + - include: statements + - match: "" + push: + - meta_scope: comment.block.preprocessor.if-branch.c++ + - match: (?=^\s*#\s*(else|endif)\b) + pop: true + - include: scope:source.c#preprocessor-disabled + + preprocessor-rule-enabled-statements: + - match: ^\s*((#if)\s+(0*1))\b + captures: + 1: meta.preprocessor.c++ + 2: keyword.control.import.c++ + 3: constant.numeric.preprocessor.c++ + push: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: ^\s*(#\s*else)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.else.c++ + push: + - meta_content_scope: comment.block.preprocessor.else-branch.c++ + - match: (?=^\s*#\s*endif\b) + pop: true + - include: scope:source.c#preprocessor-disabled + - match: "" + push: + - match: (?=^\s*#\s*(else|endif)\b) + pop: true + - include: negated-block + - include: statements + + preprocessor-rule-other-statements: + - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b + captures: + 1: keyword.control.import.c++ + push: + - meta_scope: meta.preprocessor.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-comments + - match: \bdefined\b + scope: keyword.control.c++ + # Enter a new scope where all elif/else branches have their + # contexts popped by a subsequent elif/else/endif. This ensures that + # preprocessor branches don't push multiple meta.block scopes on + # the stack, thus messing up the "global" context's detection of + # functions. + - match: $\n + set: preprocessor-if-branch-statements + + # These gymnastics here ensure that we are properly handling scope even + # when the preprocessor is used to create different scope beginnings, such + # as a different if/while condition + preprocessor-if-branch-statements: + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + pop: true + - match: (?=^\s*#\s*(elif|else)\b) + push: preprocessor-elif-else-branch-statements + - match: \{ + scope: punctuation.section.block.begin.c++ + set: preprocessor-block-if-branch-statements + - match: (?=(?!{{non_func_keywords}}){{path_lookahead}}\s*\() + set: preprocessor-if-branch-function-call + - include: negated-block + - include: statements + + preprocessor-if-branch-function-call: + - meta_content_scope: meta.function-call.c++ + - include: scope:source.c#c99 + - match: '(?:(::)\s*)?{{identifier}}\s*(::)\s*' + scope: variable.function.c++ + captures: + 1: punctuation.accessor.c++ + 2: punctuation.accessor.c++ + - match: '(?:(::)\s*)?{{identifier}}' + scope: variable.function.c++ + captures: + 1: punctuation.accessor.c++ + - match: '\(' + scope: meta.group.c++ punctuation.section.group.begin.c++ + set: preprocessor-if-branch-function-call-arguments + + preprocessor-if-branch-function-call-arguments: + - meta_content_scope: meta.function-call.c++ meta.group.c++ + - match : \) + scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++ + set: preprocessor-if-branch-statements + - match: ^\s*(#\s*(?:elif|else))\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + set: preprocessor-if-branch-statements + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + set: preprocessor-if-branch-function-call-arguments-finish + - include: expressions + + preprocessor-if-branch-function-call-arguments-finish: + - meta_content_scope: meta.function-call.c++ meta.group.c++ + - match: \) + scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++ + pop: true + - include: expressions + + preprocessor-block-if-branch-statements: + - meta_scope: meta.block.c++ + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + set: preprocessor-block-finish-statements + - match: (?=^\s*#\s*(elif|else)\b) + push: preprocessor-elif-else-branch-statements + - match: \} + scope: punctuation.section.block.end.c++ + set: preprocessor-if-branch-statements + - include: statements + + preprocessor-block-finish-statements: + - meta_scope: meta.block.c++ + - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + set: preprocessor-block-finish-if-branch-statements + - match: \} + scope: punctuation.section.block.end.c++ + pop: true + - include: statements + + preprocessor-block-finish-if-branch-statements: + - match: ^\s*(#\s*endif)\b + captures: + 1: keyword.control.import.c++ + pop: true + - match: \} + scope: meta.block.c++ punctuation.section.block.end.c++ + set: preprocessor-if-branch-statements + - include: statements + + preprocessor-elif-else-branch-statements: + - match: (?=^\s*#\s*endif\b) + pop: true + - include: negated-block + - include: statements + + ## Preprocessor other + + negated-block: + - match: '\}' + scope: punctuation.section.block.end.c++ + push: + - match: '\{' + scope: punctuation.section.block.begin.c++ + pop: true + - match: (?=^\s*#\s*(elif|else|endif)\b) + pop: true + - include: statements + + preprocessor-macro-define: + - match: ^\s*(\#\s*define)\b + captures: + 1: meta.preprocessor.macro.c++ keyword.control.import.define.c++ + push: + - meta_content_scope: meta.preprocessor.macro.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-line-ending + - include: scope:source.c#preprocessor-comments + - match: '({{identifier}})(?=\()' + scope: entity.name.function.preprocessor.c++ + set: + - match: '\(' + scope: punctuation.section.group.begin.c++ + set: preprocessor-macro-params + - match: '{{identifier}}' + scope: entity.name.constant.preprocessor.c++ + set: preprocessor-macro-definition + + preprocessor-macro-params: + - meta_scope: meta.preprocessor.macro.parameters.c++ meta.group.c++ + - match: '{{identifier}}' + scope: variable.parameter.c++ + - match: \) + scope: punctuation.section.group.end.c++ + set: preprocessor-macro-definition + - match: ',' + scope: punctuation.separator.c++ + push: + - match: '{{identifier}}' + scope: variable.parameter.c++ + pop: true + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-comments + - match: '\.\.\.' + scope: keyword.operator.variadic.c++ + - match: '(?=\))' + pop: true + - match: (/\*).*(\*/) + scope: comment.block.c++ + captures: + 1: punctuation.definition.comment.c++ + 2: punctuation.definition.comment.c++ + - match: '\S+' + scope: invalid.illegal.unexpected-character.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-comments + - match: '\.\.\.' + scope: keyword.operator.variadic.c++ + - match: (/\*).*(\*/) + scope: comment.block.c++ + captures: + 1: punctuation.definition.comment.c++ + 2: punctuation.definition.comment.c++ + - match: $\n + scope: invalid.illegal.unexpected-end-of-line.c++ + + preprocessor-macro-definition: + - meta_content_scope: meta.preprocessor.macro.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-line-ending + - include: scope:source.c#preprocessor-comments + # Don't define blocks in define statements + - match: '\{' + scope: punctuation.section.block.begin.c++ + - match: '\}' + scope: punctuation.section.block.end.c++ + - include: expressions + + preprocessor-practical-workarounds: + - include: preprocessor-convention-ignore-uppercase-ident-lines + - include: scope:source.c#preprocessor-convention-ignore-uppercase-calls-without-semicolon + + preprocessor-convention-ignore-uppercase-ident-lines: + - match: ^(\s*{{macro_identifier}})+\s*$ + scope: meta.assumed-macro.c++ + push: + # It's possible that we are dealing with a function return type on its own line, and the + # name of the function is on the subsequent line. + - match: '(?={{path_lookahead}}({{generic_lookahead}}({{path_lookahead}})?)\s*\()' + set: [function-definition-params, global-function-identifier-generic] + - match: '(?={{path_lookahead}}\s*\()' + set: [function-definition-params, global-function-identifier] + - match: ^ + pop: true + + preprocessor-other: + - match: ^\s*(#\s*(?:if|ifdef|ifndef|elif|else|line|pragma|undef))\b + captures: + 1: keyword.control.import.c++ + push: + - meta_scope: meta.preprocessor.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-line-ending + - include: scope:source.c#preprocessor-comments + - match: \bdefined\b + scope: keyword.control.c++ + - match: ^\s*(#\s*endif)\b + captures: + 1: meta.preprocessor.c++ keyword.control.import.c++ + - match: ^\s*(#\s*(?:error|warning))\b + captures: + 1: keyword.control.import.error.c++ + push: + - meta_scope: meta.preprocessor.diagnostic.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-line-ending + - include: scope:source.c#preprocessor-comments + - include: strings + - match: '\S+' + scope: string.unquoted.c++ + - match: ^\s*(#\s*(?:include|include_next|import))\b + captures: + 1: keyword.control.import.include.c++ + push: + - meta_scope: meta.preprocessor.include.c++ + - include: scope:source.c#preprocessor-line-continuation + - include: scope:source.c#preprocessor-line-ending + - include: scope:source.c#preprocessor-comments + - match: '"' + scope: punctuation.definition.string.begin.c++ + push: + - meta_scope: string.quoted.double.include.c++ + - match: '"' + scope: punctuation.definition.string.end.c++ + pop: true + - match: < + scope: punctuation.definition.string.begin.c++ + push: + - meta_scope: string.quoted.other.lt-gt.include.c++ + - match: '>' + scope: punctuation.definition.string.end.c++ + pop: true + - include: preprocessor-practical-workarounds diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/README b/Engine/lib/openal-soft/fmt-11.1.1/support/README new file mode 100644 index 000000000..468f5485d --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/README @@ -0,0 +1,4 @@ +This directory contains build support files such as + +* CMake modules +* Build scripts diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/Vagrantfile b/Engine/lib/openal-soft/fmt-11.1.1/support/Vagrantfile new file mode 100644 index 000000000..9d0ff6a53 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/Vagrantfile @@ -0,0 +1,19 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# A vagrant config for testing against gcc-4.8. +Vagrant.configure("2") do |config| + config.vm.box = "bento/ubuntu-22.04-arm64" + + config.vm.provider "vmware_desktop" do |vb| + vb.memory = "4096" + end + + config.vm.provision "shell", inline: <<-SHELL + apt-get update + apt-get install -y g++ make wget git + wget -q https://github.com/Kitware/CMake/releases/download/v3.26.0/cmake-3.26.0-Linux-x86_64.tar.gz + tar xzf cmake-3.26.0-Linux-x86_64.tar.gz + ln -s `pwd`/cmake-3.26.0-Linux-x86_64/bin/cmake /usr/local/bin + SHELL +end diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/.bazelversion b/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/.bazelversion new file mode 100644 index 000000000..a8a188756 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/.bazelversion @@ -0,0 +1 @@ +7.1.2 diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/BUILD.bazel b/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/BUILD.bazel new file mode 100644 index 000000000..1a06ed52b --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/BUILD.bazel @@ -0,0 +1,20 @@ +cc_library( + name = "fmt", + srcs = [ + #"src/fmt.cc", # No C++ module support, yet in Bazel (https://github.com/bazelbuild/bazel/pull/19940) + "src/format.cc", + "src/os.cc", + ], + hdrs = glob([ + "include/fmt/*.h", + ]), + copts = select({ + "@platforms//os:windows": ["-utf-8"], + "//conditions:default": [], + }), + includes = [ + "include", + ], + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/MODULE.bazel b/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/MODULE.bazel new file mode 100644 index 000000000..6023f362c --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/MODULE.bazel @@ -0,0 +1,6 @@ +module( + name = "fmt", + compatibility_level = 10, +) + +bazel_dep(name = "platforms", version = "0.0.10") diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/README.md b/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/README.md new file mode 100644 index 000000000..44508f1c2 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/README.md @@ -0,0 +1,28 @@ +# Bazel support + +To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, +`MODULE.bazel`, `WORKSPACE.bazel`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project. +This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}). + +## Using {fmt} as a dependency + +### Using Bzlmod + +The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registry/tree/main/modules/fmt) provides support for {fmt}. + +For instance, to use {fmt} add to your `MODULE.bazel` file: + +``` +bazel_dep(name = "fmt", version = "10.2.1") +``` + +### Live at head + +For a live-at-head approach, you can copy the contents of this repository and move the Bazel-related build files to the root folder of this project as described above and make use of `local_path_override`, e.g.: + +``` +local_path_override( + module_name = "fmt", + path = "../third_party/fmt", +) +``` diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/WORKSPACE.bazel b/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/WORKSPACE.bazel new file mode 100644 index 000000000..b1be18f38 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/bazel/WORKSPACE.bazel @@ -0,0 +1,2 @@ +# WORKSPACE marker file needed by Bazel + diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/build.gradle b/Engine/lib/openal-soft/fmt-11.1.1/support/build.gradle new file mode 100644 index 000000000..c5126d058 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/build.gradle @@ -0,0 +1,132 @@ +import java.nio.file.Paths + +// General gradle arguments for root project +buildscript { + repositories { + google() + jcenter() + } + dependencies { + // + // https://developer.android.com/studio/releases/gradle-plugin#updating-gradle + // + // Notice that 4.0.0 here is the version of [Android Gradle Plugin] + // According to URL above you will need Gradle 6.1 or higher + // + classpath "com.android.tools.build:gradle:4.1.1" + } +} +repositories { + google() + jcenter() +} + +// Project's root where CMakeLists.txt exists: rootDir/support/.cxx -> rootDir +def rootDir = Paths.get(project.buildDir.getParent()).getParent() +println("rootDir: ${rootDir}") + +// Output: Shared library (.so) for Android +apply plugin: "com.android.library" +android { + compileSdkVersion 25 // Android 7.0 + + // Target ABI + // - This option controls target platform of module + // - The platform might be limited by compiler's support + // some can work with Clang(default), but some can work only with GCC... + // if bad, both toolchains might not support it + splits { + abi { + enable true + // Specify platforms for Application + reset() + include "arm64-v8a", "armeabi-v7a", "x86_64" + } + } + ndkVersion "21.3.6528147" // ANDROID_NDK_HOME is deprecated. Be explicit + + defaultConfig { + minSdkVersion 21 // Android 5.0+ + targetSdkVersion 25 // Follow Compile SDK + versionCode 34 // Follow release count + versionName "7.1.2" // Follow Official version + + externalNativeBuild { + cmake { + arguments "-DANDROID_STL=c++_shared" // Specify Android STL + arguments "-DBUILD_SHARED_LIBS=true" // Build shared object + arguments "-DFMT_TEST=false" // Skip test + arguments "-DFMT_DOC=false" // Skip document + cppFlags "-std=c++17" + targets "fmt" + } + } + println(externalNativeBuild.cmake.cppFlags) + println(externalNativeBuild.cmake.arguments) + } + + // External Native build + // - Use existing CMakeList.txt + // - Give path to CMake. This gradle file should be + // neighbor of the top level cmake + externalNativeBuild { + cmake { + version "3.10.0+" + path "${rootDir}/CMakeLists.txt" + // buildStagingDirectory "./build" // Custom path for cmake output + } + } + + sourceSets{ + // Android Manifest for Gradle + main { + manifest.srcFile "AndroidManifest.xml" + } + } + + // https://developer.android.com/studio/build/native-dependencies#build_system_configuration + buildFeatures { + prefab true + prefabPublishing true + } + prefab { + fmt { + headers "${rootDir}/include" + } + } +} + +assemble.doLast +{ + // Instead of `ninja install`, Gradle will deploy the files. + // We are doing this since FMT is dependent to the ANDROID_STL after build + copy { + from "build/intermediates/cmake" + into "${rootDir}/libs" + } + // Copy debug binaries + copy { + from "${rootDir}/libs/debug/obj" + into "${rootDir}/libs/debug" + } + // Copy Release binaries + copy { + from "${rootDir}/libs/release/obj" + into "${rootDir}/libs/release" + } + // Remove empty directory + delete "${rootDir}/libs/debug/obj" + delete "${rootDir}/libs/release/obj" + + // Copy AAR files. Notice that the aar is named after the folder of this script. + copy { + from "build/outputs/aar/support-release.aar" + into "${rootDir}/libs" + rename "support-release.aar", "fmt-release.aar" + } + copy { + from "build/outputs/aar/support-debug.aar" + into "${rootDir}/libs" + rename "support-debug.aar", "fmt-debug.aar" + } +} diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/check-commits b/Engine/lib/openal-soft/fmt-11.1.1/support/check-commits new file mode 100644 index 000000000..11472d41f --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/check-commits @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +"""Compile source on a range of commits + +Usage: + check-commits +""" + +import docopt, os, sys, tempfile +from subprocess import check_call, check_output, run + +args = docopt.docopt(__doc__) +start = args.get('') +source = args.get('') + +cwd = os.getcwd() + +with tempfile.TemporaryDirectory() as work_dir: + check_call(['git', 'clone', 'https://github.com/fmtlib/fmt.git'], + cwd=work_dir) + repo_dir = os.path.join(work_dir, 'fmt') + commits = check_output( + ['git', 'rev-list', f'{start}..HEAD', '--abbrev-commit', + '--', 'include', 'src'], + text=True, cwd=repo_dir).rstrip().split('\n') + commits.reverse() + print('Time\tCommit') + for commit in commits: + check_call(['git', '-c', 'advice.detachedHead=false', 'checkout', commit], + cwd=repo_dir) + returncode = run( + ['c++', '-std=c++11', '-O3', '-DNDEBUG', '-I', 'include', + 'src/format.cc', os.path.join(cwd, source)], cwd=repo_dir).returncode + if returncode != 0: + continue + times = [] + for i in range(5): + output = check_output([os.path.join(repo_dir, 'a.out')], text=True) + times.append(float(output)) + message = check_output(['git', 'log', '-1', '--pretty=format:%s', commit], + cwd=repo_dir, text=True) + print(f'{min(times)}\t{commit} {message[:40]}') + sys.stdout.flush() diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/cmake/FindSetEnv.cmake b/Engine/lib/openal-soft/fmt-11.1.1/support/cmake/FindSetEnv.cmake new file mode 100644 index 000000000..4e2da5408 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/cmake/FindSetEnv.cmake @@ -0,0 +1,7 @@ +# A CMake script to find SetEnv.cmd. + +find_program(WINSDK_SETENV NAMES SetEnv.cmd + PATHS "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]/bin") +if (WINSDK_SETENV AND PRINT_PATH) + execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${WINSDK_SETENV}") +endif () diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/cmake/JoinPaths.cmake b/Engine/lib/openal-soft/fmt-11.1.1/support/cmake/JoinPaths.cmake new file mode 100644 index 000000000..32d6d6685 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/cmake/JoinPaths.cmake @@ -0,0 +1,26 @@ +# This module provides function for joining paths +# known from from most languages +# +# Original license: +# SPDX-License-Identifier: (MIT OR CC0-1.0) +# Explicit permission given to distribute this module under +# the terms of the project as described in /LICENSE.rst. +# Copyright 2020 Jan Tojnar +# https://github.com/jtojnar/cmake-snips +# +# Modelled after Python’s os.path.join +# https://docs.python.org/3.7/library/os.path.html#os.path.join +# Windows not supported +function(join_paths joined_path first_path_segment) + set(temp_path "${first_path_segment}") + foreach(current_segment IN LISTS ARGN) + if(NOT ("${current_segment}" STREQUAL "")) + if(IS_ABSOLUTE "${current_segment}") + set(temp_path "${current_segment}") + else() + set(temp_path "${temp_path}/${current_segment}") + endif() + endif() + endforeach() + set(${joined_path} "${temp_path}" PARENT_SCOPE) +endfunction() diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/cmake/fmt-config.cmake.in b/Engine/lib/openal-soft/fmt-11.1.1/support/cmake/fmt-config.cmake.in new file mode 100644 index 000000000..bc1684f24 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/cmake/fmt-config.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +if (NOT TARGET fmt::fmt) + include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake) +endif () + +check_required_components(fmt) diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/cmake/fmt.pc.in b/Engine/lib/openal-soft/fmt-11.1.1/support/cmake/fmt.pc.in new file mode 100644 index 000000000..29976a8af --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/cmake/fmt.pc.in @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@libdir_for_pc_file@ +includedir=@includedir_for_pc_file@ + +Name: fmt +Description: A modern formatting library +Version: @FMT_VERSION@ +Libs: -L${libdir} -l@FMT_LIB_NAME@ +Cflags: -I${includedir} + diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/docopt.py b/Engine/lib/openal-soft/fmt-11.1.1/support/docopt.py new file mode 100644 index 000000000..2e43f7cef --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/docopt.py @@ -0,0 +1,581 @@ +"""Pythonic command-line interface parser that will make you smile. + + * http://docopt.org + * Repository and issue-tracker: https://github.com/docopt/docopt + * Licensed under terms of MIT license (see LICENSE-MIT) + * Copyright (c) 2013 Vladimir Keleshev, vladimir@keleshev.com + +""" +import sys +import re + + +__all__ = ['docopt'] +__version__ = '0.6.1' + + +class DocoptLanguageError(Exception): + + """Error in construction of usage-message by developer.""" + + +class DocoptExit(SystemExit): + + """Exit in case user invoked program with incorrect arguments.""" + + usage = '' + + def __init__(self, message=''): + SystemExit.__init__(self, (message + '\n' + self.usage).strip()) + + +class Pattern(object): + + def __eq__(self, other): + return repr(self) == repr(other) + + def __hash__(self): + return hash(repr(self)) + + def fix(self): + self.fix_identities() + self.fix_repeating_arguments() + return self + + def fix_identities(self, uniq=None): + """Make pattern-tree tips point to same object if they are equal.""" + if not hasattr(self, 'children'): + return self + uniq = list(set(self.flat())) if uniq is None else uniq + for i, child in enumerate(self.children): + if not hasattr(child, 'children'): + assert child in uniq + self.children[i] = uniq[uniq.index(child)] + else: + child.fix_identities(uniq) + + def fix_repeating_arguments(self): + """Fix elements that should accumulate/increment values.""" + either = [list(child.children) for child in transform(self).children] + for case in either: + for e in [child for child in case if case.count(child) > 1]: + if type(e) is Argument or type(e) is Option and e.argcount: + if e.value is None: + e.value = [] + elif type(e.value) is not list: + e.value = e.value.split() + if type(e) is Command or type(e) is Option and e.argcount == 0: + e.value = 0 + return self + + +def transform(pattern): + """Expand pattern into an (almost) equivalent one, but with single Either. + + Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d) + Quirks: [-a] => (-a), (-a...) => (-a -a) + + """ + result = [] + groups = [[pattern]] + while groups: + children = groups.pop(0) + parents = [Required, Optional, OptionsShortcut, Either, OneOrMore] + if any(t in map(type, children) for t in parents): + child = [c for c in children if type(c) in parents][0] + children.remove(child) + if type(child) is Either: + for c in child.children: + groups.append([c] + children) + elif type(child) is OneOrMore: + groups.append(child.children * 2 + children) + else: + groups.append(child.children + children) + else: + result.append(children) + return Either(*[Required(*e) for e in result]) + + +class LeafPattern(Pattern): + + """Leaf/terminal node of a pattern tree.""" + + def __init__(self, name, value=None): + self.name, self.value = name, value + + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.name, self.value) + + def flat(self, *types): + return [self] if not types or type(self) in types else [] + + def match(self, left, collected=None): + collected = [] if collected is None else collected + pos, match = self.single_match(left) + if match is None: + return False, left, collected + left_ = left[:pos] + left[pos + 1:] + same_name = [a for a in collected if a.name == self.name] + if type(self.value) in (int, list): + if type(self.value) is int: + increment = 1 + else: + increment = ([match.value] if type(match.value) is str + else match.value) + if not same_name: + match.value = increment + return True, left_, collected + [match] + same_name[0].value += increment + return True, left_, collected + return True, left_, collected + [match] + + +class BranchPattern(Pattern): + + """Branch/inner node of a pattern tree.""" + + def __init__(self, *children): + self.children = list(children) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, + ', '.join(repr(a) for a in self.children)) + + def flat(self, *types): + if type(self) in types: + return [self] + return sum([child.flat(*types) for child in self.children], []) + + +class Argument(LeafPattern): + + def single_match(self, left): + for n, pattern in enumerate(left): + if type(pattern) is Argument: + return n, Argument(self.name, pattern.value) + return None, None + + @classmethod + def parse(class_, source): + name = re.findall('(<\S*?>)', source)[0] + value = re.findall('\[default: (.*)\]', source, flags=re.I) + return class_(name, value[0] if value else None) + + +class Command(Argument): + + def __init__(self, name, value=False): + self.name, self.value = name, value + + def single_match(self, left): + for n, pattern in enumerate(left): + if type(pattern) is Argument: + if pattern.value == self.name: + return n, Command(self.name, True) + else: + break + return None, None + + +class Option(LeafPattern): + + def __init__(self, short=None, long=None, argcount=0, value=False): + assert argcount in (0, 1) + self.short, self.long, self.argcount = short, long, argcount + self.value = None if value is False and argcount else value + + @classmethod + def parse(class_, option_description): + short, long, argcount, value = None, None, 0, False + options, _, description = option_description.strip().partition(' ') + options = options.replace(',', ' ').replace('=', ' ') + for s in options.split(): + if s.startswith('--'): + long = s + elif s.startswith('-'): + short = s + else: + argcount = 1 + if argcount: + matched = re.findall('\[default: (.*)\]', description, flags=re.I) + value = matched[0] if matched else None + return class_(short, long, argcount, value) + + def single_match(self, left): + for n, pattern in enumerate(left): + if self.name == pattern.name: + return n, pattern + return None, None + + @property + def name(self): + return self.long or self.short + + def __repr__(self): + return 'Option(%r, %r, %r, %r)' % (self.short, self.long, + self.argcount, self.value) + + +class Required(BranchPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + l = left + c = collected + for pattern in self.children: + matched, l, c = pattern.match(l, c) + if not matched: + return False, left, collected + return True, l, c + + +class Optional(BranchPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + for pattern in self.children: + m, left, collected = pattern.match(left, collected) + return True, left, collected + + +class OptionsShortcut(Optional): + + """Marker/placeholder for [options] shortcut.""" + + +class OneOrMore(BranchPattern): + + def match(self, left, collected=None): + assert len(self.children) == 1 + collected = [] if collected is None else collected + l = left + c = collected + l_ = None + matched = True + times = 0 + while matched: + # could it be that something didn't match but changed l or c? + matched, l, c = self.children[0].match(l, c) + times += 1 if matched else 0 + if l_ == l: + break + l_ = l + if times >= 1: + return True, l, c + return False, left, collected + + +class Either(BranchPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + outcomes = [] + for pattern in self.children: + matched, _, _ = outcome = pattern.match(left, collected) + if matched: + outcomes.append(outcome) + if outcomes: + return min(outcomes, key=lambda outcome: len(outcome[1])) + return False, left, collected + + +class Tokens(list): + + def __init__(self, source, error=DocoptExit): + self += source.split() if hasattr(source, 'split') else source + self.error = error + + @staticmethod + def from_pattern(source): + source = re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source) + source = [s for s in re.split('\s+|(\S*<.*?>)', source) if s] + return Tokens(source, error=DocoptLanguageError) + + def move(self): + return self.pop(0) if len(self) else None + + def current(self): + return self[0] if len(self) else None + + +def parse_long(tokens, options): + """long ::= '--' chars [ ( ' ' | '=' ) chars ] ;""" + long, eq, value = tokens.move().partition('=') + assert long.startswith('--') + value = None if eq == value == '' else value + similar = [o for o in options if o.long == long] + if tokens.error is DocoptExit and similar == []: # if no exact match + similar = [o for o in options if o.long and o.long.startswith(long)] + if len(similar) > 1: # might be simply specified ambiguously 2+ times? + raise tokens.error('%s is not a unique prefix: %s?' % + (long, ', '.join(o.long for o in similar))) + elif len(similar) < 1: + argcount = 1 if eq == '=' else 0 + o = Option(None, long, argcount) + options.append(o) + if tokens.error is DocoptExit: + o = Option(None, long, argcount, value if argcount else True) + else: + o = Option(similar[0].short, similar[0].long, + similar[0].argcount, similar[0].value) + if o.argcount == 0: + if value is not None: + raise tokens.error('%s must not have an argument' % o.long) + else: + if value is None: + if tokens.current() in [None, '--']: + raise tokens.error('%s requires argument' % o.long) + value = tokens.move() + if tokens.error is DocoptExit: + o.value = value if value is not None else True + return [o] + + +def parse_shorts(tokens, options): + """shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ;""" + token = tokens.move() + assert token.startswith('-') and not token.startswith('--') + left = token.lstrip('-') + parsed = [] + while left != '': + short, left = '-' + left[0], left[1:] + similar = [o for o in options if o.short == short] + if len(similar) > 1: + raise tokens.error('%s is specified ambiguously %d times' % + (short, len(similar))) + elif len(similar) < 1: + o = Option(short, None, 0) + options.append(o) + if tokens.error is DocoptExit: + o = Option(short, None, 0, True) + else: # why copying is necessary here? + o = Option(short, similar[0].long, + similar[0].argcount, similar[0].value) + value = None + if o.argcount != 0: + if left == '': + if tokens.current() in [None, '--']: + raise tokens.error('%s requires argument' % short) + value = tokens.move() + else: + value = left + left = '' + if tokens.error is DocoptExit: + o.value = value if value is not None else True + parsed.append(o) + return parsed + + +def parse_pattern(source, options): + tokens = Tokens.from_pattern(source) + result = parse_expr(tokens, options) + if tokens.current() is not None: + raise tokens.error('unexpected ending: %r' % ' '.join(tokens)) + return Required(*result) + + +def parse_expr(tokens, options): + """expr ::= seq ( '|' seq )* ;""" + seq = parse_seq(tokens, options) + if tokens.current() != '|': + return seq + result = [Required(*seq)] if len(seq) > 1 else seq + while tokens.current() == '|': + tokens.move() + seq = parse_seq(tokens, options) + result += [Required(*seq)] if len(seq) > 1 else seq + return [Either(*result)] if len(result) > 1 else result + + +def parse_seq(tokens, options): + """seq ::= ( atom [ '...' ] )* ;""" + result = [] + while tokens.current() not in [None, ']', ')', '|']: + atom = parse_atom(tokens, options) + if tokens.current() == '...': + atom = [OneOrMore(*atom)] + tokens.move() + result += atom + return result + + +def parse_atom(tokens, options): + """atom ::= '(' expr ')' | '[' expr ']' | 'options' + | long | shorts | argument | command ; + """ + token = tokens.current() + result = [] + if token in '([': + tokens.move() + matching, pattern = {'(': [')', Required], '[': [']', Optional]}[token] + result = pattern(*parse_expr(tokens, options)) + if tokens.move() != matching: + raise tokens.error("unmatched '%s'" % token) + return [result] + elif token == 'options': + tokens.move() + return [OptionsShortcut()] + elif token.startswith('--') and token != '--': + return parse_long(tokens, options) + elif token.startswith('-') and token not in ('-', '--'): + return parse_shorts(tokens, options) + elif token.startswith('<') and token.endswith('>') or token.isupper(): + return [Argument(tokens.move())] + else: + return [Command(tokens.move())] + + +def parse_argv(tokens, options, options_first=False): + """Parse command-line argument vector. + + If options_first: + argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ; + else: + argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ; + + """ + parsed = [] + while tokens.current() is not None: + if tokens.current() == '--': + return parsed + [Argument(None, v) for v in tokens] + elif tokens.current().startswith('--'): + parsed += parse_long(tokens, options) + elif tokens.current().startswith('-') and tokens.current() != '-': + parsed += parse_shorts(tokens, options) + elif options_first: + return parsed + [Argument(None, v) for v in tokens] + else: + parsed.append(Argument(None, tokens.move())) + return parsed + + +def parse_defaults(doc): + defaults = [] + for s in parse_section('options:', doc): + # FIXME corner case "bla: options: --foo" + _, _, s = s.partition(':') # get rid of "options:" + split = re.split('\n[ \t]*(-\S+?)', '\n' + s)[1:] + split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])] + options = [Option.parse(s) for s in split if s.startswith('-')] + defaults += options + return defaults + + +def parse_section(name, source): + pattern = re.compile('^([^\n]*' + name + '[^\n]*\n?(?:[ \t].*?(?:\n|$))*)', + re.IGNORECASE | re.MULTILINE) + return [s.strip() for s in pattern.findall(source)] + + +def formal_usage(section): + _, _, section = section.partition(':') # drop "usage:" + pu = section.split() + return '( ' + ' '.join(') | (' if s == pu[0] else s for s in pu[1:]) + ' )' + + +def extras(help, version, options, doc): + if help and any((o.name in ('-h', '--help')) and o.value for o in options): + print(doc.strip("\n")) + sys.exit() + if version and any(o.name == '--version' and o.value for o in options): + print(version) + sys.exit() + + +class Dict(dict): + def __repr__(self): + return '{%s}' % ',\n '.join('%r: %r' % i for i in sorted(self.items())) + + +def docopt(doc, argv=None, help=True, version=None, options_first=False): + """Parse `argv` based on command-line interface described in `doc`. + + `docopt` creates your command-line interface based on its + description that you pass as `doc`. Such description can contain + --options, , commands, which could be + [optional], (required), (mutually | exclusive) or repeated... + + Parameters + ---------- + doc : str + Description of your command-line interface. + argv : list of str, optional + Argument vector to be parsed. sys.argv[1:] is used if not + provided. + help : bool (default: True) + Set to False to disable automatic help on -h or --help + options. + version : any object + If passed, the object will be printed if --version is in + `argv`. + options_first : bool (default: False) + Set to True to require options precede positional arguments, + i.e. to forbid options and positional arguments intermix. + + Returns + ------- + args : dict + A dictionary, where keys are names of command-line elements + such as e.g. "--verbose" and "", and values are the + parsed values of those elements. + + Example + ------- + >>> from docopt import docopt + >>> doc = ''' + ... Usage: + ... my_program tcp [--timeout=] + ... my_program serial [--baud=] [--timeout=] + ... my_program (-h | --help | --version) + ... + ... Options: + ... -h, --help Show this screen and exit. + ... --baud= Baudrate [default: 9600] + ... ''' + >>> argv = ['tcp', '127.0.0.1', '80', '--timeout', '30'] + >>> docopt(doc, argv) + {'--baud': '9600', + '--help': False, + '--timeout': '30', + '--version': False, + '': '127.0.0.1', + '': '80', + 'serial': False, + 'tcp': True} + + See also + -------- + * For video introduction see http://docopt.org + * Full documentation is available in README.rst as well as online + at https://github.com/docopt/docopt#readme + + """ + argv = sys.argv[1:] if argv is None else argv + + usage_sections = parse_section('usage:', doc) + if len(usage_sections) == 0: + raise DocoptLanguageError('"usage:" (case-insensitive) not found.') + if len(usage_sections) > 1: + raise DocoptLanguageError('More than one "usage:" (case-insensitive).') + DocoptExit.usage = usage_sections[0] + + options = parse_defaults(doc) + pattern = parse_pattern(formal_usage(DocoptExit.usage), options) + # [default] syntax for argument is disabled + #for a in pattern.flat(Argument): + # same_name = [d for d in arguments if d.name == a.name] + # if same_name: + # a.value = same_name[0].value + argv = parse_argv(Tokens(argv), list(options), options_first) + pattern_options = set(pattern.flat(Option)) + for options_shortcut in pattern.flat(OptionsShortcut): + doc_options = parse_defaults(doc) + options_shortcut.children = list(set(doc_options) - pattern_options) + #if any_options: + # options_shortcut.children += [Option(o.short, o.long, o.argcount) + # for o in argv if type(o) is Option] + extras(help, version, argv, doc) + matched, left, collected = pattern.fix().match(argv) + if matched and left == []: # better error message if left? + return Dict((a.name, a.value) for a in (pattern.flat() + collected)) + raise DocoptExit() diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/mkdocs b/Engine/lib/openal-soft/fmt-11.1.1/support/mkdocs new file mode 100644 index 000000000..e554c1fdc --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/mkdocs @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# A script to invoke mkdocs with the correct environment. +# Additionally supports deploying via mike: +# ./mkdocs deploy [mike-deploy-options] + +import errno, os, shutil, sys +from subprocess import call + +support_dir = os.path.dirname(os.path.normpath(__file__)) +build_dir = os.path.join(os.path.dirname(support_dir), 'build') + +# Set PYTHONPATH for the mkdocstrings handler. +env = os.environ.copy() +path = env.get('PYTHONPATH') +env['PYTHONPATH'] = \ + (path + ':' if path else '') + os.path.join(support_dir, 'python') + +redirect_page = \ +''' + + + + Redirecting + + + + + Redirecting to api... + + +''' + +config_path = os.path.join(support_dir, 'mkdocs.yml') +args = sys.argv[1:] +if len(args) > 0: + command = args[0] + if command == 'deploy': + git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:' + site_repo = git_url + 'fmtlib/fmt.dev.git' + + site_dir = os.path.join(build_dir, 'fmt.dev') + try: + shutil.rmtree(site_dir) + except OSError as e: + if e.errno == errno.ENOENT: + pass + ret = call(['git', 'clone', '--depth=1', site_repo, site_dir]) + if ret != 0: + sys.exit(ret) + + # Copy the config to the build dir because the site is built relative to it. + config_build_path = os.path.join(build_dir, 'mkdocs.yml') + shutil.copyfile(config_path, config_build_path) + + version = args[1] + ret = call(['mike'] + args + ['--config-file', config_build_path, + '--branch', 'master'], cwd=site_dir, env=env) + if ret != 0 or version == 'dev': + sys.exit(ret) + redirect_page_path = os.path.join(site_dir, version, 'api.html') + with open(redirect_page_path, "w") as file: + file.write(redirect_page) + ret = call(['git', 'add', redirect_page_path], cwd=site_dir) + if ret != 0: + sys.exit(ret) + ret = call(['git', 'commit', '--amend', '--no-edit'], cwd=site_dir) + sys.exit(ret) + elif not command.startswith('-'): + args += ['-f', config_path] +sys.exit(call(['mkdocs'] + args, env=env)) diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/mkdocs.yml b/Engine/lib/openal-soft/fmt-11.1.1/support/mkdocs.yml new file mode 100644 index 000000000..17034cd1f --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/mkdocs.yml @@ -0,0 +1,48 @@ +site_name: '{fmt}' + +docs_dir: ../doc + +repo_url: https://github.com/fmtlib/fmt + +theme: + name: material + features: + - navigation.tabs + - navigation.top + - toc.integrate + +extra_javascript: + - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/highlight.min.js + - fmt.js + +extra_css: + - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/styles/default.min.css + - fmt.css + +markdown_extensions: + - pymdownx.highlight: + # Use JavaScript syntax highlighter instead of Pygments because it + # automatically applies to code blocks extracted through Doxygen. + use_pygments: false + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + +plugins: + - search + - mkdocstrings: + default_handler: cxx +nav: + - Home: index.md + - Get Started: get-started.md + - API: api.md + - Syntax: syntax.md + +exclude_docs: ChangeLog-old.md + +extra: + version: + provider: mike + generator: false diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/printable.py b/Engine/lib/openal-soft/fmt-11.1.1/support/printable.py new file mode 100644 index 000000000..8fa86b300 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/printable.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 + +# This script is based on +# https://github.com/rust-lang/rust/blob/master/library/core/src/unicode/printable.py +# distributed under https://github.com/rust-lang/rust/blob/master/LICENSE-MIT. + +# This script uses the following Unicode tables: +# - UnicodeData.txt + + +from collections import namedtuple +import csv +import os +import subprocess + +NUM_CODEPOINTS=0x110000 + +def to_ranges(iter): + current = None + for i in iter: + if current is None or i != current[1] or i in (0x10000, 0x20000): + if current is not None: + yield tuple(current) + current = [i, i + 1] + else: + current[1] += 1 + if current is not None: + yield tuple(current) + +def get_escaped(codepoints): + for c in codepoints: + if (c.class_ or "Cn") in "Cc Cf Cs Co Cn Zl Zp Zs".split() and c.value != ord(' '): + yield c.value + +def get_file(f): + try: + return open(os.path.basename(f)) + except FileNotFoundError: + subprocess.run(["curl", "-O", f], check=True) + return open(os.path.basename(f)) + +Codepoint = namedtuple('Codepoint', 'value class_') + +def get_codepoints(f): + r = csv.reader(f, delimiter=";") + prev_codepoint = 0 + class_first = None + for row in r: + codepoint = int(row[0], 16) + name = row[1] + class_ = row[2] + + if class_first is not None: + if not name.endswith("Last>"): + raise ValueError("Missing Last after First") + + for c in range(prev_codepoint + 1, codepoint): + yield Codepoint(c, class_first) + + class_first = None + if name.endswith("First>"): + class_first = class_ + + yield Codepoint(codepoint, class_) + prev_codepoint = codepoint + + if class_first is not None: + raise ValueError("Missing Last after First") + + for c in range(prev_codepoint + 1, NUM_CODEPOINTS): + yield Codepoint(c, None) + +def compress_singletons(singletons): + uppers = [] # (upper, # items in lowers) + lowers = [] + + for i in singletons: + upper = i >> 8 + lower = i & 0xff + if len(uppers) == 0 or uppers[-1][0] != upper: + uppers.append((upper, 1)) + else: + upper, count = uppers[-1] + uppers[-1] = upper, count + 1 + lowers.append(lower) + + return uppers, lowers + +def compress_normal(normal): + # lengths 0x00..0x7f are encoded as 00, 01, ..., 7e, 7f + # lengths 0x80..0x7fff are encoded as 80 80, 80 81, ..., ff fe, ff ff + compressed = [] # [truelen, (truelenaux), falselen, (falselenaux)] + + prev_start = 0 + for start, count in normal: + truelen = start - prev_start + falselen = count + prev_start = start + count + + assert truelen < 0x8000 and falselen < 0x8000 + entry = [] + if truelen > 0x7f: + entry.append(0x80 | (truelen >> 8)) + entry.append(truelen & 0xff) + else: + entry.append(truelen & 0x7f) + if falselen > 0x7f: + entry.append(0x80 | (falselen >> 8)) + entry.append(falselen & 0xff) + else: + entry.append(falselen & 0x7f) + + compressed.append(entry) + + return compressed + +def print_singletons(uppers, lowers, uppersname, lowersname): + print(" static constexpr singleton {}[] = {{".format(uppersname)) + for u, c in uppers: + print(" {{{:#04x}, {}}},".format(u, c)) + print(" };") + print(" static constexpr unsigned char {}[] = {{".format(lowersname)) + for i in range(0, len(lowers), 8): + print(" {}".format(" ".join("{:#04x},".format(l) for l in lowers[i:i+8]))) + print(" };") + +def print_normal(normal, normalname): + print(" static constexpr unsigned char {}[] = {{".format(normalname)) + for v in normal: + print(" {}".format(" ".join("{:#04x},".format(i) for i in v))) + print(" };") + +def main(): + file = get_file("https://www.unicode.org/Public/UNIDATA/UnicodeData.txt") + + codepoints = get_codepoints(file) + + CUTOFF=0x10000 + singletons0 = [] + singletons1 = [] + normal0 = [] + normal1 = [] + extra = [] + + for a, b in to_ranges(get_escaped(codepoints)): + if a > 2 * CUTOFF: + extra.append((a, b - a)) + elif a == b - 1: + if a & CUTOFF: + singletons1.append(a & ~CUTOFF) + else: + singletons0.append(a) + elif a == b - 2: + if a & CUTOFF: + singletons1.append(a & ~CUTOFF) + singletons1.append((a + 1) & ~CUTOFF) + else: + singletons0.append(a) + singletons0.append(a + 1) + else: + if a >= 2 * CUTOFF: + extra.append((a, b - a)) + elif a & CUTOFF: + normal1.append((a & ~CUTOFF, b - a)) + else: + normal0.append((a, b - a)) + + singletons0u, singletons0l = compress_singletons(singletons0) + singletons1u, singletons1l = compress_singletons(singletons1) + normal0 = compress_normal(normal0) + normal1 = compress_normal(normal1) + + print("""\ +FMT_FUNC auto is_printable(uint32_t cp) -> bool {\ +""") + print_singletons(singletons0u, singletons0l, 'singletons0', 'singletons0_lower') + print_singletons(singletons1u, singletons1l, 'singletons1', 'singletons1_lower') + print_normal(normal0, 'normal0') + print_normal(normal1, 'normal1') + print("""\ + auto lower = static_cast(cp); + if (cp < 0x10000) { + return is_printable(lower, singletons0, + sizeof(singletons0) / sizeof(*singletons0), + singletons0_lower, normal0, sizeof(normal0)); + } + if (cp < 0x20000) { + return is_printable(lower, singletons1, + sizeof(singletons1) / sizeof(*singletons1), + singletons1_lower, normal1, sizeof(normal1)); + }\ +""") + for a, b in extra: + print(" if (0x{:x} <= cp && cp < 0x{:x}) return false;".format(a, a + b)) + print("""\ + return cp < 0x{:x}; +}}\ +""".format(NUM_CODEPOINTS)) + +if __name__ == '__main__': + main() diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/__init__.py b/Engine/lib/openal-soft/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/__init__.py new file mode 100644 index 000000000..85d52a580 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/__init__.py @@ -0,0 +1,338 @@ +# A basic mkdocstrings handler for {fmt}. +# Copyright (c) 2012 - present, Victor Zverovich +# https://github.com/fmtlib/fmt/blob/master/LICENSE + +import os +import xml.etree.ElementTree as ElementTree +from pathlib import Path +from subprocess import PIPE, STDOUT, CalledProcessError, Popen +from typing import Any, List, Mapping, Optional + +from mkdocstrings.handlers.base import BaseHandler + + +class Definition: + """A definition extracted by Doxygen.""" + + def __init__(self, name: str, kind: Optional[str] = None, + node: Optional[ElementTree.Element] = None, + is_member: bool = False): + self.name = name + self.kind = kind if kind is not None else node.get('kind') + self.desc = None + self.id = name if not is_member else None + self.members = None + self.params = None + self.template_params = None + self.trailing_return_type = None + self.type = None + + +# A map from Doxygen to HTML tags. +tag_map = { + 'bold': 'b', + 'emphasis': 'em', + 'computeroutput': 'code', + 'para': 'p', + 'programlisting': 'pre', + 'verbatim': 'pre' +} + +# A map from Doxygen tags to text. +tag_text_map = { + 'codeline': '', + 'highlight': '', + 'sp': ' ' +} + + +def escape_html(s: str) -> str: + return s.replace("<", "<") + + +def doxyxml2html(nodes: List[ElementTree.Element]): + out = '' + for n in nodes: + tag = tag_map.get(n.tag) + if not tag: + out += tag_text_map[n.tag] + out += '<' + tag + '>' if tag else '' + out += '' if tag == 'pre' else '' + if n.text: + out += escape_html(n.text) + out += doxyxml2html(list(n)) + out += '' if tag == 'pre' else '' + out += '' if tag else '' + if n.tail: + out += n.tail + return out + + +def convert_template_params(node: ElementTree.Element) -> Optional[List[Definition]]: + template_param_list = node.find('templateparamlist') + if template_param_list is None: + return None + params = [] + for param_node in template_param_list.findall('param'): + name = param_node.find('declname') + param = Definition(name.text if name is not None else '', 'param') + param.type = param_node.find('type').text + params.append(param) + return params + + +def get_description(node: ElementTree.Element) -> List[ElementTree.Element]: + return node.findall('briefdescription/para') + \ + node.findall('detaileddescription/para') + + +def normalize_type(type_: str) -> str: + type_ = type_.replace('< ', '<').replace(' >', '>') + return type_.replace(' &', '&').replace(' *', '*') + + +def convert_type(type_: ElementTree.Element) -> Optional[str]: + if type_ is None: + return None + result = type_.text if type_.text else '' + for ref in type_: + result += ref.text + if ref.tail: + result += ref.tail + result += type_.tail.strip() + return normalize_type(result) + + +def convert_params(func: ElementTree.Element) -> List[Definition]: + params = [] + for p in func.findall('param'): + d = Definition(p.find('declname').text, 'param') + d.type = convert_type(p.find('type')) + params.append(d) + return params + + +def convert_return_type(d: Definition, node: ElementTree.Element) -> None: + d.trailing_return_type = None + if d.type == 'auto' or d.type == 'constexpr auto': + parts = node.find('argsstring').text.split(' -> ') + if len(parts) > 1: + d.trailing_return_type = normalize_type(parts[1]) + + +def render_param(param: Definition) -> str: + return param.type + (f' {param.name}' if len(param.name) > 0 else '') + + +def render_decl(d: Definition) -> str: + text = '' + if d.id is not None: + text += f'\n' + text += '

'
+
+    text += '
' + if d.template_params is not None: + text += 'template <' + text += ', '.join([render_param(p) for p in d.template_params]) + text += '>\n' + text += '
' + + text += '
' + end = ';' + if d.kind == 'function' or d.kind == 'variable': + text += d.type + ' ' if len(d.type) > 0 else '' + elif d.kind == 'typedef': + text += 'using ' + elif d.kind == 'define': + end = '' + else: + text += d.kind + ' ' + text += d.name + + if d.params is not None: + params = ', '.join([ + (p.type + ' ' if p.type else '') + p.name for p in d.params]) + text += '(' + escape_html(params) + ')' + if d.trailing_return_type: + text += ' -⁠> ' + escape_html(d.trailing_return_type) + elif d.kind == 'typedef': + text += ' = ' + escape_html(d.type) + + text += end + text += '
' + text += '
\n' + if d.id is not None: + text += f'\n' + return text + + +class CxxHandler(BaseHandler): + def __init__(self, **kwargs: Any) -> None: + super().__init__(handler='cxx', **kwargs) + + headers = [ + 'args.h', 'base.h', 'chrono.h', 'color.h', 'compile.h', 'format.h', + 'os.h', 'ostream.h', 'printf.h', 'ranges.h', 'std.h', 'xchar.h' + ] + + # Run doxygen. + cmd = ['doxygen', '-'] + support_dir = Path(__file__).parents[3] + top_dir = os.path.dirname(support_dir) + include_dir = os.path.join(top_dir, 'include', 'fmt') + self._ns2doxyxml = {} + build_dir = os.path.join(top_dir, 'build') + os.makedirs(build_dir, exist_ok=True) + self._doxyxml_dir = os.path.join(build_dir, 'doxyxml') + p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT) + _, _ = p.communicate(input=r''' + PROJECT_NAME = fmt + GENERATE_XML = YES + GENERATE_LATEX = NO + GENERATE_HTML = NO + INPUT = {0} + XML_OUTPUT = {1} + QUIET = YES + AUTOLINK_SUPPORT = NO + MACRO_EXPANSION = YES + PREDEFINED = _WIN32=1 \ + __linux__=1 \ + FMT_ENABLE_IF(...)= \ + FMT_USE_USER_LITERALS=1 \ + FMT_USE_ALIAS_TEMPLATES=1 \ + FMT_USE_NONTYPE_TEMPLATE_ARGS=1 \ + FMT_API= \ + "FMT_BEGIN_NAMESPACE=namespace fmt {{" \ + "FMT_END_NAMESPACE=}}" \ + "FMT_DOC=1" + '''.format( + ' '.join([os.path.join(include_dir, h) for h in headers]), + self._doxyxml_dir).encode('utf-8')) + if p.returncode != 0: + raise CalledProcessError(p.returncode, cmd) + + # Merge all file-level XMLs into one to simplify search. + self._file_doxyxml = None + for h in headers: + filename = h.replace(".h", "_8h.xml") + with open(os.path.join(self._doxyxml_dir, filename)) as f: + doxyxml = ElementTree.parse(f) + if self._file_doxyxml is None: + self._file_doxyxml = doxyxml + continue + root = self._file_doxyxml.getroot() + for node in doxyxml.getroot(): + root.append(node) + + def collect_compound(self, identifier: str, + cls: List[ElementTree.Element]) -> Definition: + """Collect a compound definition such as a struct.""" + path = os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml') + with open(path) as f: + xml = ElementTree.parse(f) + node = xml.find('compounddef') + d = Definition(identifier, node=node) + d.template_params = convert_template_params(node) + d.desc = get_description(node) + d.members = [] + for m in \ + node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \ + node.findall('sectiondef[@kind="public-func"]/memberdef'): + name = m.find('name').text + # Doxygen incorrectly classifies members of private unnamed unions as + # public members of the containing class. + if name.endswith('_'): + continue + desc = get_description(m) + if len(desc) == 0: + continue + kind = m.get('kind') + member = Definition(name if name else '', kind=kind, is_member=True) + type_text = m.find('type').text + member.type = type_text if type_text else '' + if kind == 'function': + member.params = convert_params(m) + convert_return_type(member, m) + member.template_params = None + member.desc = desc + d.members.append(member) + return d + + def collect(self, identifier: str, _config: Mapping[str, Any]) -> Definition: + qual_name = 'fmt::' + identifier + + param_str = None + paren = qual_name.find('(') + if paren > 0: + qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1] + + colons = qual_name.rfind('::') + namespace, name = qual_name[:colons], qual_name[colons + 2:] + + # Load XML. + doxyxml = self._ns2doxyxml.get(namespace) + if doxyxml is None: + path = f'namespace{namespace.replace("::", "_1_1")}.xml' + with open(os.path.join(self._doxyxml_dir, path)) as f: + doxyxml = ElementTree.parse(f) + self._ns2doxyxml[namespace] = doxyxml + + nodes = doxyxml.findall( + f"compounddef/sectiondef/memberdef/name[.='{name}']/..") + if len(nodes) == 0: + nodes = self._file_doxyxml.findall( + f"compounddef/sectiondef/memberdef/name[.='{name}']/..") + candidates = [] + for node in nodes: + # Process a function or a typedef. + params = None + d = Definition(name, node=node) + if d.kind == 'function': + params = convert_params(node) + node_param_str = ', '.join([p.type for p in params]) + if param_str and param_str != node_param_str: + candidates.append(f'{name}({node_param_str})') + continue + elif d.kind == 'define': + params = [] + for p in node.findall('param'): + param = Definition(p.find('defname').text, kind='param') + param.type = None + params.append(param) + d.type = convert_type(node.find('type')) + d.template_params = convert_template_params(node) + d.params = params + convert_return_type(d, node) + d.desc = get_description(node) + return d + + cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']") + if not cls: + raise Exception(f'Cannot find {identifier}. Candidates: {candidates}') + return self.collect_compound(identifier, cls) + + def render(self, d: Definition, config: dict) -> str: + if d.id is not None: + self.do_heading('', 0, id=d.id) + text = '
\n' + text += render_decl(d) + text += '
\n' + text += doxyxml2html(d.desc) + if d.members is not None: + for m in d.members: + text += self.render(m, config) + text += '
\n' + text += '
\n' + return text + + +def get_handler(theme: str, custom_templates: Optional[str] = None, + **_config: Any) -> CxxHandler: + """Return an instance of `CxxHandler`. + + Arguments: + theme: The theme to use when rendering contents. + custom_templates: Directory containing custom templates. + **_config: Configuration passed to the handler. + """ + return CxxHandler(theme=theme, custom_templates=custom_templates) diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/templates/README b/Engine/lib/openal-soft/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/templates/README new file mode 100644 index 000000000..7da18db54 --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/templates/README @@ -0,0 +1 @@ +mkdocsstrings requires a handler to have a templates directory. diff --git a/Engine/lib/openal-soft/fmt-11.1.1/support/release.py b/Engine/lib/openal-soft/fmt-11.1.1/support/release.py new file mode 100644 index 000000000..26de7f4fd --- /dev/null +++ b/Engine/lib/openal-soft/fmt-11.1.1/support/release.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 + +"""Make a release. + +Usage: + release.py [] + +For the release command $FMT_TOKEN should contain a GitHub personal access token +obtained from https://github.com/settings/tokens. +""" + +from __future__ import print_function +import datetime, docopt, errno, fileinput, json, os +import re, shutil, sys +from subprocess import check_call +import urllib.request + + +class Git: + def __init__(self, dir): + self.dir = dir + + def call(self, method, args, **kwargs): + return check_call(['git', method] + list(args), **kwargs) + + def add(self, *args): + return self.call('add', args, cwd=self.dir) + + def checkout(self, *args): + return self.call('checkout', args, cwd=self.dir) + + def clean(self, *args): + return self.call('clean', args, cwd=self.dir) + + def clone(self, *args): + return self.call('clone', list(args) + [self.dir]) + + def commit(self, *args): + return self.call('commit', args, cwd=self.dir) + + def pull(self, *args): + return self.call('pull', args, cwd=self.dir) + + def push(self, *args): + return self.call('push', args, cwd=self.dir) + + def reset(self, *args): + return self.call('reset', args, cwd=self.dir) + + def update(self, *args): + clone = not os.path.exists(self.dir) + if clone: + self.clone(*args) + return clone + + +def clean_checkout(repo, branch): + repo.clean('-f', '-d') + repo.reset('--hard') + repo.checkout(branch) + + +class Runner: + def __init__(self, cwd): + self.cwd = cwd + + def __call__(self, *args, **kwargs): + kwargs['cwd'] = kwargs.get('cwd', self.cwd) + check_call(args, **kwargs) + + +def create_build_env(): + """Create a build environment.""" + class Env: + pass + env = Env() + env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + env.build_dir = 'build' + env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt')) + return env + + +if __name__ == '__main__': + args = docopt.docopt(__doc__) + env = create_build_env() + fmt_repo = env.fmt_repo + + branch = args.get('') + if branch is None: + branch = 'master' + if not fmt_repo.update('-b', branch, 'git@github.com:fmtlib/fmt'): + clean_checkout(fmt_repo, branch) + + # Update the date in the changelog and extract the version and the first + # section content. + changelog = 'ChangeLog.md' + changelog_path = os.path.join(fmt_repo.dir, changelog) + is_first_section = True + first_section = [] + for i, line in enumerate(fileinput.input(changelog_path, inplace=True)): + if i == 0: + version = re.match(r'# (.*) - TBD', line).group(1) + line = '# {} - {}\n'.format( + version, datetime.date.today().isoformat()) + elif not is_first_section: + pass + elif line.startswith('#'): + is_first_section = False + else: + first_section.append(line) + sys.stdout.write(line) + if first_section[0] == '\n': + first_section.pop(0) + + ns_version = None + base_h_path = os.path.join(fmt_repo.dir, 'include', 'fmt', 'base.h') + for line in fileinput.input(base_h_path): + m = re.match(r'\s*inline namespace v(.*) .*', line) + if m: + ns_version = m.group(1) + break + major_version = version.split('.')[0] + if not ns_version or ns_version != major_version: + raise Exception(f'Version mismatch {ns_version} != {major_version}') + + # Workaround GitHub-flavored Markdown treating newlines as
. + changes = '' + code_block = False + stripped = False + for line in first_section: + if re.match(r'^\s*```', line): + code_block = not code_block + changes += line + stripped = False + continue + if code_block: + changes += line + continue + if line == '\n' or re.match(r'^\s*\|.*', line): + if stripped: + changes += '\n' + stripped = False + changes += line + continue + if stripped: + line = ' ' + line.lstrip() + changes += line.rstrip() + stripped = True + + fmt_repo.checkout('-B', 'release') + fmt_repo.add(changelog) + fmt_repo.commit('-m', 'Update version') + + # Build the docs and package. + run = Runner(fmt_repo.dir) + run('cmake', '.') + run('make', 'doc', 'package_source') + + # Create a release on GitHub. + fmt_repo.push('origin', 'release') + auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')} + req = urllib.request.Request( + 'https://api.github.com/repos/fmtlib/fmt/releases', + data=json.dumps({'tag_name': version, + 'target_commitish': 'release', + 'body': changes, 'draft': True}).encode('utf-8'), + headers=auth_headers, method='POST') + with urllib.request.urlopen(req) as response: + if response.status != 201: + raise Exception(f'Failed to create a release ' + + '{response.status} {response.reason}') + response_data = json.loads(response.read().decode('utf-8')) + id = response_data['id'] + + # Upload the package. + uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases' + package = 'fmt-{}.zip'.format(version) + req = urllib.request.Request( + f'{uploads_url}/{id}/assets?name={package}', + headers={'Content-Type': 'application/zip'} | auth_headers, + data=open('build/fmt/' + package, 'rb').read(), method='POST') + with urllib.request.urlopen(req) as response: + if response.status != 201: + raise Exception(f'Failed to upload an asset ' + '{response.status} {response.reason}') + + short_version = '.'.join(version.split('.')[:-1]) + check_call(['./mkdocs', 'deploy', short_version]) diff --git a/Engine/lib/openal-soft/include/AL/al.h b/Engine/lib/openal-soft/include/AL/al.h index e9f8f3b17..a4e3ad515 100644 --- a/Engine/lib/openal-soft/include/AL/al.h +++ b/Engine/lib/openal-soft/include/AL/al.h @@ -5,9 +5,19 @@ #ifdef __cplusplus extern "C" { +#ifdef _MSVC_LANG +#define AL_CPLUSPLUS _MSVC_LANG +#else +#define AL_CPLUSPLUS __cplusplus +#endif + #ifndef AL_DISABLE_NOEXCEPT +#if AL_CPLUSPLUS >= 201103L #define AL_API_NOEXCEPT noexcept -#if __cplusplus >= 201703L +#else +#define AL_API_NOEXCEPT +#endif +#if AL_CPLUSPLUS >= 201703L #define AL_API_NOEXCEPT17 noexcept #else #define AL_API_NOEXCEPT17 @@ -19,6 +29,8 @@ extern "C" { #define AL_API_NOEXCEPT17 #endif +#undef AL_CPLUSPLUS + #else /* __cplusplus */ #define AL_API_NOEXCEPT diff --git a/Engine/lib/openal-soft/include/AL/alc.h b/Engine/lib/openal-soft/include/AL/alc.h index 3311b57fb..d048ca04d 100644 --- a/Engine/lib/openal-soft/include/AL/alc.h +++ b/Engine/lib/openal-soft/include/AL/alc.h @@ -5,9 +5,19 @@ #ifdef __cplusplus extern "C" { +#ifdef _MSVC_LANG +#define ALC_CPLUSPLUS _MSVC_LANG +#else +#define ALC_CPLUSPLUS __cplusplus +#endif + #ifndef AL_DISABLE_NOEXCEPT +#if ALC_CPLUSPLUS >= 201103L #define ALC_API_NOEXCEPT noexcept -#if __cplusplus >= 201703L +#else +#define ALC_API_NOEXCEPT +#endif +#if ALC_CPLUSPLUS >= 201703L #define ALC_API_NOEXCEPT17 noexcept #else #define ALC_API_NOEXCEPT17 @@ -19,6 +29,8 @@ extern "C" { #define ALC_API_NOEXCEPT17 #endif +#undef ALC_CPLUSPLUS + #else /* __cplusplus */ #define ALC_API_NOEXCEPT diff --git a/Engine/lib/openal-soft/include/AL/alext.h b/Engine/lib/openal-soft/include/AL/alext.h index 02cfe2db7..3908e1940 100644 --- a/Engine/lib/openal-soft/include/AL/alext.h +++ b/Engine/lib/openal-soft/include/AL/alext.h @@ -704,15 +704,19 @@ typedef void (AL_APIENTRY*LPALPOPDEBUGGROUPEXT)(void) AL_API_NOEXCEPT17; typedef ALuint (AL_APIENTRY*LPALGETDEBUGMESSAGELOGEXT)(ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALOBJECTLABELEXT)(ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETOBJECTLABELEXT)(ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY*LPALGETPOINTEREXT)(ALenum pname) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETPOINTERVEXT)(ALenum pname, void **values) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES void AL_APIENTRY alDebugMessageCallbackEXT(ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT; void AL_APIENTRY alDebugMessageInsertEXT(ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; -void AL_APIENTRY alDebugMessageControlEXT(ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT; -void AL_APIENTRY alPushDebugGroupEXT(ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; -void AL_APIENTRY alPopDebugGroupEXT(void) AL_API_NOEXCEPT; -ALuint AL_APIENTRY alGetDebugMessageLogEXT(ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT; +void AL_APIENTRY alDebugMessageControlEXT(ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT; +void AL_APIENTRY alPushDebugGroupEXT(ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; +void AL_APIENTRY alPopDebugGroupEXT(void) AL_API_NOEXCEPT; +ALuint AL_APIENTRY alGetDebugMessageLogEXT(ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT; void AL_APIENTRY alObjectLabelEXT(ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT; void AL_APIENTRY alGetObjectLabelEXT(ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT; +void* AL_APIENTRY alGetPointerEXT(ALenum pname) AL_API_NOEXCEPT; +void AL_APIENTRY alGetPointervEXT(ALenum pname, void **values) AL_API_NOEXCEPT; #endif #endif @@ -858,6 +862,8 @@ typedef void (AL_APIENTRY*LPALPOPDEBUGGROUPDIRECTEXT)(ALCcontext *context) AL_AP typedef ALuint (AL_APIENTRY*LPALGETDEBUGMESSAGELOGDIRECTEXT)(ALCcontext *context, ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALOBJECTLABELDIRECTEXT)(ALCcontext *context, ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETOBJECTLABELDIRECTEXT)(ALCcontext *context, ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY*LPALGETPOINTERDIRECTEXT)(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETPOINTERVDIRECTEXT)(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT17; /* AL_EXT_FOLDBACK */ typedef void (AL_APIENTRY *LPALREQUESTFOLDBACKSTARTDIRECT)(ALCcontext *context, ALenum mode, ALsizei count, ALsizei length, ALfloat *mem, LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALREQUESTFOLDBACKSTOPDIRECT)(ALCcontext *context) AL_API_NOEXCEPT17; @@ -900,7 +906,7 @@ typedef ALenum (AL_APIENTRY *LPEAXGETDIRECT)(ALCcontext *context, const struct _ typedef ALboolean (AL_APIENTRY *LPEAXSETBUFFERMODEDIRECT)(ALCcontext *context, ALsizei n, const ALuint *buffers, ALint value) AL_API_NOEXCEPT17; typedef ALenum (AL_APIENTRY *LPEAXGETBUFFERMODEDIRECT)(ALCcontext *context, ALuint buffer, ALint *pReserved) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -ALCvoid* AL_APIENTRY alcGetProcAddress2(ALCdevice *device, const ALchar *funcName) AL_API_NOEXCEPT; +ALCvoid* ALC_APIENTRY alcGetProcAddress2(ALCdevice *device, const ALCchar *funcName) AL_API_NOEXCEPT; void AL_APIENTRY alEnableDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT; void AL_APIENTRY alDisableDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT; @@ -1019,14 +1025,16 @@ void AL_APIENTRY alGetAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint ef void AL_APIENTRY alBufferDataStaticDirect(ALCcontext *context, ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT; -void AL_APIENTRY alDebugMessageCallbackDirectEXT(ALCcontext *context, ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT; -void AL_APIENTRY alDebugMessageInsertDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; +void AL_APIENTRY alDebugMessageCallbackDirectEXT(ALCcontext *context, ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT; +void AL_APIENTRY alDebugMessageInsertDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; void AL_APIENTRY alDebugMessageControlDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT; void AL_APIENTRY alPushDebugGroupDirectEXT(ALCcontext *context, ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; void AL_APIENTRY alPopDebugGroupDirectEXT(ALCcontext *context) AL_API_NOEXCEPT; ALuint AL_APIENTRY alGetDebugMessageLogDirectEXT(ALCcontext *context, ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT; void AL_APIENTRY alObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT; void AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT; +void* AL_APIENTRY alGetPointerDirectEXT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT; +void AL_APIENTRY alGetPointervDirectEXT(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT; void AL_APIENTRY alRequestFoldbackStartDirect(ALCcontext *context, ALenum mode, ALsizei count, ALsizei length, ALfloat *mem, LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT; void AL_APIENTRY alRequestFoldbackStopDirect(ALCcontext *context) AL_API_NOEXCEPT; @@ -1071,6 +1079,11 @@ ALenum AL_APIENTRY EAXGetBufferModeDirect(ALCcontext *context, ALuint buffer, AL #endif #endif +#ifndef AL_SOFT_bformat_hoa +#define AL_SOFT_bformat_hoa +#define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D +#endif + #ifdef __cplusplus } #endif diff --git a/Engine/lib/openal-soft/router/alc.cpp b/Engine/lib/openal-soft/router/alc.cpp index 312ac6b73..29244d135 100644 --- a/Engine/lib/openal-soft/router/alc.cpp +++ b/Engine/lib/openal-soft/router/alc.cpp @@ -17,12 +17,16 @@ #include "almalloc.h" #include "router.h" +#include "strutils.h" namespace { using namespace std::string_view_literals; +std::once_flag InitOnce; +void LoadDrivers() { std::call_once(InitOnce, []{ LoadDriverList(); }); } + struct FuncExportEntry { const char *funcName; void *address; @@ -337,7 +341,7 @@ void EnumeratedList::AppendDeviceList(const ALCchar* names, ALCuint idx) size_t count{0}; while(*name_end) { - TRACE("Enumerated \"%s\", driver %u\n", name_end, idx); + TRACE("Enumerated \"{}\", driver {}", name_end, idx); ++count; name_end += strlen(name_end)+1; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ } @@ -375,8 +379,8 @@ void InitCtxFuncs(DriverIface &iface) #define LOAD_PROC(x) do { \ iface.x = reinterpret_cast(iface.alGetProcAddress(#x));\ if(!iface.x) \ - ERR("Failed to find entry point for %s in %ls\n", #x, \ - iface.Name.c_str()); \ + ERR("Failed to find entry point for {} in {}", #x, \ + wstr_to_utf8(iface.Name)); \ } while(0) if(iface.alcIsExtensionPresent(device, "ALC_EXT_EFX")) { @@ -422,6 +426,8 @@ void InitCtxFuncs(DriverIface &iface) ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) noexcept { + LoadDrivers(); + ALCdevice *device{nullptr}; std::optional idx; @@ -448,10 +454,10 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) noexcep if(!idx) { LastError.store(ALC_INVALID_VALUE); - TRACE("Failed to find driver for name \"%s\"\n", devicename); + TRACE("Failed to find driver for name \"{}\"", devicename); return nullptr; } - TRACE("Found driver %u for name \"%s\"\n", *idx, devicename); + TRACE("Found driver {} for name \"{}\"", *idx, devicename); device = DriverList[*idx]->alcOpenDevice(devicename); } else @@ -459,10 +465,10 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) noexcep ALCuint drvidx{0}; for(const auto &drv : DriverList) { - if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) { - TRACE("Using default device from driver %u\n", drvidx); + TRACE("Using default device from driver {}", drvidx); device = drv->alcOpenDevice(nullptr); idx = drvidx; break; @@ -685,6 +691,8 @@ ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *e ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param) noexcept { + LoadDrivers(); + if(device) { if(const auto idx = maybe_get(DeviceIfaceMap, device)) @@ -712,7 +720,7 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para for(const auto &drv : DriverList) { /* Only enumerate names from drivers that support it. */ - if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) DevicesList.AppendDeviceList(drv->alcGetString(nullptr,ALC_DEVICE_SPECIFIER), idx); ++idx; @@ -737,7 +745,7 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para if(drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) AllDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER), idx); - else if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + else if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) AllDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx); @@ -757,7 +765,7 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para ALCuint idx{0}; for(const auto &drv : DriverList) { - if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) CaptureDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER), idx); @@ -774,7 +782,7 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para { for(const auto &drv : DriverList) { - if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) return drv->alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); } @@ -795,7 +803,7 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para { for(const auto &drv : DriverList) { - if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) return drv->alcGetString(nullptr, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); } @@ -866,6 +874,8 @@ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsi ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize) noexcept { + LoadDrivers(); + ALCdevice *device{nullptr}; std::optional idx; @@ -881,10 +891,10 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, if(!idx) { LastError.store(ALC_INVALID_VALUE); - TRACE("Failed to find driver for name \"%s\"\n", devicename); + TRACE("Failed to find driver for name \"{}\"", devicename); return nullptr; } - TRACE("Found driver %u for name \"%s\"\n", *idx, devicename); + TRACE("Found driver {} for name \"{}\"", *idx, devicename); device = DriverList[*idx]->alcCaptureOpenDevice(devicename, frequency, format, buffersize); } else @@ -892,10 +902,10 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint drvidx{0}; for(const auto &drv : DriverList) { - if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) { - TRACE("Using default capture device from driver %u\n", drvidx); + TRACE("Using default capture device from driver {}", drvidx); device = drv->alcCaptureOpenDevice(nullptr, frequency, format, buffersize); idx = drvidx; break; diff --git a/Engine/lib/openal-soft/router/router.cpp b/Engine/lib/openal-soft/router/router.cpp index e6f4a62bf..d05d48e58 100644 --- a/Engine/lib/openal-soft/router/router.cpp +++ b/Engine/lib/openal-soft/router/router.cpp @@ -15,6 +15,7 @@ #include "AL/alc.h" #include "AL/al.h" +#include "albit.h" #include "alstring.h" #include "opthelpers.h" #include "strutils.h" @@ -37,13 +38,13 @@ void AddModule(HMODULE module, const std::wstring_view name) { if(drv->Module == module) { - TRACE("Skipping already-loaded module %p\n", decltype(std::declval()){module}); + TRACE("Skipping already-loaded module {}", decltype(std::declval()){module}); FreeLibrary(module); return; } if(drv->Name == name) { - TRACE("Skipping similarly-named module %.*ls\n", al::sizei(name), name.data()); + TRACE("Skipping similarly-named module {}", wstr_to_utf8(name)); FreeLibrary(module); return; } @@ -55,7 +56,7 @@ void AddModule(HMODULE module, const std::wstring_view name) { return al::case_compare(name, accept) == 0; }); if(iter == gAcceptList.cend()) { - TRACE("%.*ls not found in ALROUTER_ACCEPT, skipping\n", al::sizei(name), name.data()); + TRACE("{} not found in ALROUTER_ACCEPT, skipping", wstr_to_utf8(name)); FreeLibrary(module); return; } @@ -67,7 +68,7 @@ void AddModule(HMODULE module, const std::wstring_view name) { return al::case_compare(name, accept) == 0; }); if(iter != gRejectList.cend()) { - TRACE("%.*ls found in ALROUTER_REJECT, skipping\n", al::sizei(name), name.data()); + TRACE("{} found in ALROUTER_REJECT, skipping", wstr_to_utf8(name)); FreeLibrary(module); return; } @@ -83,12 +84,11 @@ void AddModule(HMODULE module, const std::wstring_view name) auto ptr = GetProcAddress(module, fname); if(!ptr) { - ERR("Failed to find entry point for %s in %.*ls\n", fname, al::sizei(name), - name.data()); + ERR("Failed to find entry point for {} in {}", fname, wstr_to_utf8(name)); return false; } - func = reinterpret_cast(reinterpret_cast(ptr)); + func = al::bit_cast(ptr); return true; }; #define LOAD_PROC(x) loadok &= do_load(newdrv.x, #x) @@ -181,12 +181,11 @@ void AddModule(HMODULE module, const std::wstring_view name) newdrv.alcGetIntegerv(nullptr, ALC_MAJOR_VERSION, 1, &alc_ver[0]); newdrv.alcGetIntegerv(nullptr, ALC_MINOR_VERSION, 1, &alc_ver[1]); if(newdrv.alcGetError(nullptr) == ALC_NO_ERROR) - newdrv.ALCVer = MAKE_ALC_VER(alc_ver[0], alc_ver[1]); + newdrv.ALCVer = MakeALCVer(alc_ver[0], alc_ver[1]); else { - WARN("Failed to query ALC version for %.*ls, assuming 1.0\n", al::sizei(name), - name.data()); - newdrv.ALCVer = MAKE_ALC_VER(1, 0); + WARN("Failed to query ALC version for {}, assuming 1.0", wstr_to_utf8(name)); + newdrv.ALCVer = MakeALCVer(1, 0); } auto do_load2 = [module,name](auto &func, const char *fname) -> void @@ -194,10 +193,10 @@ void AddModule(HMODULE module, const std::wstring_view name) using func_t = std::remove_reference_t; auto ptr = GetProcAddress(module, fname); if(!ptr) - WARN("Failed to find optional entry point for %s in %.*ls\n", fname, - al::sizei(name), name.data()); + WARN("Failed to find optional entry point for {} in {}", fname, + wstr_to_utf8(name)); else - func = reinterpret_cast(reinterpret_cast(ptr)); + func = al::bit_cast(ptr); }; #define LOAD_PROC(x) do_load2(newdrv.x, #x) LOAD_PROC(alBufferf); @@ -220,8 +219,7 @@ void AddModule(HMODULE module, const std::wstring_view name) auto ptr = newdrv.alcGetProcAddress(nullptr, fname); if(!ptr) { - ERR("Failed to find entry point for %s in %.*ls\n", fname, al::sizei(name), - name.data()); + ERR("Failed to find entry point for {} in {}", fname, wstr_to_utf8(name)); return false; } @@ -242,13 +240,13 @@ void AddModule(HMODULE module, const std::wstring_view name) DriverList.pop_back(); return; } - TRACE("Loaded module %p, %.*ls, ALC %d.%d\n", decltype(std::declval()){module}, - al::sizei(name), name.data(), newdrv.ALCVer>>8, newdrv.ALCVer&255); + TRACE("Loaded module {}, {}, ALC {}.{}", decltype(std::declval()){module}, + wstr_to_utf8(name), newdrv.ALCVer>>8, newdrv.ALCVer&255); } void SearchDrivers(const std::wstring_view path) { - TRACE("Searching for drivers in %.*ls...\n", al::sizei(path), path.data()); + TRACE("Searching for drivers in {}...", wstr_to_utf8(path)); std::wstring srchPath{path}; srchPath += L"\\*oal.dll"; @@ -260,11 +258,11 @@ void SearchDrivers(const std::wstring_view path) srchPath = path; srchPath += L"\\"; srchPath += std::data(fdata.cFileName); - TRACE("Found %ls\n", srchPath.c_str()); + TRACE("Found {}", wstr_to_utf8(srchPath)); HMODULE mod{LoadLibraryW(srchPath.c_str())}; if(!mod) - WARN("Could not load %ls\n", srchPath.c_str()); + WARN("Could not load {}", wstr_to_utf8(srchPath)); else AddModule(mod, std::data(fdata.cFileName)); } while(FindNextFileW(srchHdl, &fdata)); @@ -306,8 +304,12 @@ bool GetLoadedModuleDirectory(const WCHAR *name, std::wstring *moddir) return !moddir->empty(); } +} // namespace + void LoadDriverList() { + TRACE("Initializing router v0.1-{} {}", ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); + if(auto list = al::getenv(L"ALROUTER_ACCEPT")) { std::wstring_view namelist{*list}; @@ -339,7 +341,7 @@ void LoadDriverList() std::wstring dll_path; if(GetLoadedModuleDirectory(L"OpenAL32.dll", &dll_path)) - TRACE("Got DLL path %ls\n", dll_path.c_str()); + TRACE("Got DLL path {}", wstr_to_utf8(dll_path)); std::wstring cwd_path; if(DWORD pathlen{GetCurrentDirectoryW(0, nullptr)}) @@ -353,11 +355,11 @@ void LoadDriverList() if(!cwd_path.empty() && (cwd_path.back() == '\\' || cwd_path.back() == '/')) cwd_path.pop_back(); if(!cwd_path.empty()) - TRACE("Got current working directory %ls\n", cwd_path.c_str()); + TRACE("Got current working directory {}", wstr_to_utf8(cwd_path)); std::wstring proc_path; if(GetLoadedModuleDirectory(nullptr, &proc_path)) - TRACE("Got proc path %ls\n", proc_path.c_str()); + TRACE("Got proc path {}", wstr_to_utf8(proc_path)); std::wstring sys_path; if(UINT pathlen{GetSystemDirectoryW(nullptr, 0)}) @@ -371,39 +373,55 @@ void LoadDriverList() if(!sys_path.empty() && (sys_path.back() == '\\' || sys_path.back() == '/')) sys_path.pop_back(); if(!sys_path.empty()) - TRACE("Got system path %ls\n", sys_path.c_str()); + TRACE("Got system path {}", wstr_to_utf8(sys_path)); /* Don't search the DLL's path if it is the same as the current working * directory, app's path, or system path (don't want to do duplicate * searches, or increase the priority of the app or system path). */ - if(!dll_path.empty() && - (cwd_path.empty() || dll_path != cwd_path) && - (proc_path.empty() || dll_path != proc_path) && - (sys_path.empty() || dll_path != sys_path)) + if(!dll_path.empty() && (cwd_path.empty() || dll_path != cwd_path) + && (proc_path.empty() || dll_path != proc_path) + && (sys_path.empty() || dll_path != sys_path)) SearchDrivers(dll_path); - if(!cwd_path.empty() && - (proc_path.empty() || cwd_path != proc_path) && - (sys_path.empty() || cwd_path != sys_path)) + if(!cwd_path.empty() && (proc_path.empty() || cwd_path != proc_path) + && (sys_path.empty() || cwd_path != sys_path)) SearchDrivers(cwd_path); if(!proc_path.empty() && (sys_path.empty() || proc_path != sys_path)) SearchDrivers(proc_path); if(!sys_path.empty()) SearchDrivers(sys_path); -} -} // namespace + /* Sort drivers that can enumerate device names to the front. */ + static constexpr auto is_enumerable = [](DriverIfacePtr &drv) + { + return drv->ALCVer >= MakeALCVer(1, 1) + || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") + || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"); + }; + std::stable_partition(DriverList.begin(), DriverList.end(), is_enumerable); + + /* HACK: rapture3d_oal.dll isn't likely to work if it's one distributed for + * specific games licensed to use it. It will enumerate a Rapture3D device + * but fail to open. This isn't much of a problem, the device just won't + * work for users not allowed to use it. But if it's the first in the list + * where it gets used for the default device, the default device will fail + * to open. Move it down so it's not used for the default device. + */ + if(DriverList.size() > 1 + && al::case_compare(DriverList.front()->Name, L"rapture3d_oal.dll") == 0) + std::swap(*DriverList.begin(), *(DriverList.begin()+1)); +} BOOL APIENTRY DllMain(HINSTANCE, DWORD reason, void*) { switch(reason) { case DLL_PROCESS_ATTACH: - if(auto logfname = al::getenv("ALROUTER_LOGFILE")) + if(auto logfname = al::getenv(L"ALROUTER_LOGFILE")) { - gsl::owner f{fopen(logfname->c_str(), "w")}; + gsl::owner f{_wfopen(logfname->c_str(), L"w")}; if(f == nullptr) - ERR("Could not open log file: %s\n", logfname->c_str()); + ERR("Could not open log file: {}", wstr_to_utf8(*logfname)); else LogFile = f; } @@ -412,16 +430,13 @@ BOOL APIENTRY DllMain(HINSTANCE, DWORD reason, void*) char *end = nullptr; long l{strtol(loglev->c_str(), &end, 0)}; if(!end || *end != '\0') - ERR("Invalid log level value: %s\n", loglev->c_str()); + ERR("Invalid log level value: {}", *loglev); else if(l < al::to_underlying(eLogLevel::None) || l > al::to_underlying(eLogLevel::Trace)) - ERR("Log level out of range: %s\n", loglev->c_str()); + ERR("Log level out of range: {}", *loglev); else LogLevel = static_cast(l); } - TRACE("Initializing router v0.1-%s %s\n", ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); - LoadDriverList(); - break; case DLL_THREAD_ATTACH: diff --git a/Engine/lib/openal-soft/router/router.h b/Engine/lib/openal-soft/router/router.h index bd5c907a0..f030fe693 100644 --- a/Engine/lib/openal-soft/router/router.h +++ b/Engine/lib/openal-soft/router/router.h @@ -18,9 +18,10 @@ #include "AL/alext.h" #include "almalloc.h" +#include "fmt/core.h" -#define MAKE_ALC_VER(major, minor) (((major)<<8) | (minor)) +constexpr auto MakeALCVer(int major, int minor) noexcept -> int { return (major<<8) | minor; } struct DriverIface { LPALCCREATECONTEXT alcCreateContext{nullptr}; @@ -159,7 +160,7 @@ struct DriverIface { std::wstring Name; HMODULE Module{nullptr}; int ALCVer{0}; - std::once_flag InitOnceCtx{}; + std::once_flag InitOnceCtx; template DriverIface(T&& name, HMODULE mod) : Name(std::forward(name)), Module(mod) { } @@ -190,29 +191,32 @@ enum class eLogLevel { extern eLogLevel LogLevel; extern gsl::owner LogFile; -#define TRACE(...) do { \ - if(LogLevel >= eLogLevel::Trace) \ - { \ - std::FILE *file{LogFile ? LogFile : stderr}; \ - fprintf(file, "AL Router (II): " __VA_ARGS__); \ - fflush(file); \ - } \ +#define TRACE(...) do { \ + if(LogLevel >= eLogLevel::Trace) \ + { \ + std::FILE *file{LogFile ? LogFile : stderr}; \ + fmt::println(file, "AL Router (II): " __VA_ARGS__); \ + fflush(file); \ + } \ } while(0) -#define WARN(...) do { \ - if(LogLevel >= eLogLevel::Warn) \ - { \ - std::FILE *file{LogFile ? LogFile : stderr}; \ - fprintf(file, "AL Router (WW): " __VA_ARGS__); \ - fflush(file); \ - } \ +#define WARN(...) do { \ + if(LogLevel >= eLogLevel::Warn) \ + { \ + std::FILE *file{LogFile ? LogFile : stderr}; \ + fmt::println(file, "AL Router (WW): " __VA_ARGS__); \ + fflush(file); \ + } \ } while(0) -#define ERR(...) do { \ - if(LogLevel >= eLogLevel::Error) \ - { \ - std::FILE *file{LogFile ? LogFile : stderr}; \ - fprintf(file, "AL Router (EE): " __VA_ARGS__); \ - fflush(file); \ - } \ +#define ERR(...) do { \ + if(LogLevel >= eLogLevel::Error) \ + { \ + std::FILE *file{LogFile ? LogFile : stderr}; \ + fmt::println(file, "AL Router (EE): " __VA_ARGS__); \ + fflush(file); \ + } \ } while(0) + +void LoadDriverList(); + #endif /* ROUTER_ROUTER_H */ diff --git a/Engine/lib/openal-soft/utils/alsoft-config/CMakeLists.txt b/Engine/lib/openal-soft/utils/alsoft-config/CMakeLists.txt index cb896382c..62d6e10dd 100644 --- a/Engine/lib/openal-soft/utils/alsoft-config/CMakeLists.txt +++ b/Engine/lib/openal-soft/utils/alsoft-config/CMakeLists.txt @@ -12,14 +12,14 @@ if(Qt5Widgets_FOUND) verstr.cpp verstr.h ${UIS} ${RSCS} ${TRS} ${MOCS}) - target_link_libraries(alsoft-config PUBLIC Qt5::Widgets PRIVATE alcommon) + target_link_libraries(alsoft-config PUBLIC Qt5::Widgets PRIVATE alsoft.common) target_include_directories(alsoft-config PRIVATE "${alsoft-config_BINARY_DIR}" "${OpenAL_BINARY_DIR}") target_compile_definitions(alsoft-config PRIVATE QT_NO_KEYWORDS) set_target_properties(alsoft-config PROPERTIES ${DEFAULT_TARGET_PROPS} RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR}) - if(TARGET build_version) - add_dependencies(alsoft-config build_version) + if(TARGET alsoft.build_version) + add_dependencies(alsoft-config alsoft.build_version) endif() message(STATUS "Building configuration program") diff --git a/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp b/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp index 672c3d87d..14b396d5e 100644 --- a/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp +++ b/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp @@ -1,11 +1,13 @@ #include "config.h" +#include "config_backends.h" +#include "config_simd.h" #include "mainwindow.h" #include #include -#include +#include #include #include @@ -32,48 +34,48 @@ struct BackendNamePair { /* NOLINTEND(*-avoid-c-arrays) */ }; constexpr std::array backendList{ -#ifdef HAVE_PIPEWIRE +#if HAVE_PIPEWIRE BackendNamePair{ "pipewire", "PipeWire" }, #endif -#ifdef HAVE_PULSEAUDIO +#if HAVE_PULSEAUDIO BackendNamePair{ "pulse", "PulseAudio" }, #endif -#ifdef HAVE_ALSA - BackendNamePair{ "alsa", "ALSA" }, -#endif -#ifdef HAVE_JACK - BackendNamePair{ "jack", "JACK" }, -#endif -#ifdef HAVE_COREAUDIO - BackendNamePair{ "core", "CoreAudio" }, -#endif -#ifdef HAVE_OSS - BackendNamePair{ "oss", "OSS" }, -#endif -#ifdef HAVE_SOLARIS - BackendNamePair{ "solaris", "Solaris" }, -#endif -#ifdef HAVE_SNDIO - BackendNamePair{ "sndio", "SndIO" }, -#endif -#ifdef HAVE_WASAPI +#if HAVE_WASAPI BackendNamePair{ "wasapi", "WASAPI" }, #endif -#ifdef HAVE_DSOUND +#if HAVE_COREAUDIO + BackendNamePair{ "core", "CoreAudio" }, +#endif +#if HAVE_OPENSL + BackendNamePair{ "opensl", "OpenSL" }, +#endif +#if HAVE_ALSA + BackendNamePair{ "alsa", "ALSA" }, +#endif +#if HAVE_SOLARIS + BackendNamePair{ "solaris", "Solaris" }, +#endif +#if HAVE_SNDIO + BackendNamePair{ "sndio", "SndIO" }, +#endif +#if HAVE_OSS + BackendNamePair{ "oss", "OSS" }, +#endif +#if HAVE_DSOUND BackendNamePair{ "dsound", "DirectSound" }, #endif -#ifdef HAVE_WINMM +#if HAVE_WINMM BackendNamePair{ "winmm", "Windows Multimedia" }, #endif -#ifdef HAVE_PORTAUDIO +#if HAVE_PORTAUDIO BackendNamePair{ "port", "PortAudio" }, #endif -#ifdef HAVE_OPENSL - BackendNamePair{ "opensl", "OpenSL" }, +#if HAVE_JACK + BackendNamePair{ "jack", "JACK" }, #endif BackendNamePair{ "null", "Null Output" }, -#ifdef HAVE_WAVE +#if HAVE_WAVE BackendNamePair{ "wave", "Wave Writer" }, #endif }; @@ -112,12 +114,14 @@ constexpr std::array resamplerList{ NameValuePair{ "Point", "point" }, NameValuePair{ "Linear", "linear" }, NameValuePair{ "Cubic Spline", "spline" }, + NameValuePair{ "Default (Cubic Spline)", "" }, NameValuePair{ "4-point Gaussian", "gaussian" }, - NameValuePair{ "Default (4-point Gaussian)", "" }, NameValuePair{ "11th order Sinc (fast)", "fast_bsinc12" }, NameValuePair{ "11th order Sinc", "bsinc12" }, NameValuePair{ "23rd order Sinc (fast)", "fast_bsinc24" }, NameValuePair{ "23rd order Sinc", "bsinc24" }, + NameValuePair{ "47th order Sinc (fast)", "fast_bsinc48" }, + NameValuePair{ "47th order Sinc", "bsinc48" }, }; constexpr std::array stereoModeList{ NameValuePair{ "Autodetect", "" }, @@ -145,19 +149,35 @@ constexpr std::array hrtfModeList{ NameValuePair{ "Full", "full" }, }; +constexpr auto GetDefaultIndex(const al::span list) -> size_t +{ + for(size_t i{0};i < list.size();++i) + { + if(!list[i].value[0]) + return i; + } + throw std::runtime_error{"Failed to find default entry"}; +} + +#ifdef Q_OS_WIN32 +struct CoTaskMemDeleter { + void operator()(void *buffer) { CoTaskMemFree(buffer); } +}; +/* NOLINTNEXTLINE(*-avoid-c-arrays) */ +using WCharBufferPtr = std::unique_ptr; +#endif + QString getDefaultConfigName() { #ifdef Q_OS_WIN32 const char *fname{"alsoft.ini"}; - auto get_appdata_path = []() noexcept -> QString + static constexpr auto get_appdata_path = []() -> QString { - QString ret; - WCHAR *buffer{}; + auto buffer = WCharBufferPtr{}; if(const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, - nullptr, &buffer)}; SUCCEEDED(hr)) - ret = QString::fromWCharArray(buffer); - CoTaskMemFree(buffer); - return ret; + nullptr, al::out_ptr(buffer))}; SUCCEEDED(hr)) + return QString::fromWCharArray(buffer.get()); + return QString{}; }; QString base = get_appdata_path(); #else @@ -178,15 +198,13 @@ QString getDefaultConfigName() QString getBaseDataPath() { #ifdef Q_OS_WIN32 - auto get_appdata_path = []() noexcept -> QString + static constexpr auto get_appdata_path = []() -> QString { - QString ret; - WCHAR *buffer{}; + auto buffer = WCharBufferPtr{}; if(const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, - nullptr, &buffer)}; SUCCEEDED(hr)) - ret = QString::fromWCharArray(buffer); - CoTaskMemFree(buffer); - return ret; + nullptr, al::out_ptr(buffer))}; SUCCEEDED(hr)) + return QString::fromWCharArray(buffer.get()); + return QString{}; }; QString base = get_appdata_path(); #else @@ -297,18 +315,18 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent} ui->resamplerSlider->setRange(0, resamplerList.size()-1); ui->hrtfmodeSlider->setRange(0, hrtfModeList.size()-1); -#if !defined(HAVE_NEON) && !defined(HAVE_SSE) +#if !HAVE_NEON && !HAVE_SSE ui->cpuExtDisabledLabel->move(ui->cpuExtDisabledLabel->x(), ui->cpuExtDisabledLabel->y() - 60); #else ui->cpuExtDisabledLabel->setVisible(false); #endif -#ifndef HAVE_NEON +#if !HAVE_NEON -#ifndef HAVE_SSE4_1 -#ifndef HAVE_SSE3 -#ifndef HAVE_SSE2 -#ifndef HAVE_SSE +#if !HAVE_SSE4_1 +#if !HAVE_SSE3 +#if !HAVE_SSE2 +#if !HAVE_SSE ui->enableSSECheckBox->setVisible(false); #endif /* !SSE */ ui->enableSSE2CheckBox->setVisible(false); @@ -321,10 +339,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent} #else /* !Neon */ -#ifndef HAVE_SSE4_1 -#ifndef HAVE_SSE3 -#ifndef HAVE_SSE2 -#ifndef HAVE_SSE +#if !HAVE_SSE4_1 +#if !HAVE_SSE3 +#if !HAVE_SSE2 +#if !HAVE_SSE ui->enableNeonCheckBox->move(ui->enableNeonCheckBox->x(), ui->enableNeonCheckBox->y() - 30); ui->enableSSECheckBox->setVisible(false); #endif /* !SSE */ @@ -337,7 +355,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent} #endif -#ifndef ALSOFT_EAX +#if !ALSOFT_EAX ui->enableEaxCheck->setChecked(Qt::Unchecked); ui->enableEaxCheck->setEnabled(false); ui->enableEaxCheck->setVisible(false); @@ -662,14 +680,16 @@ void MainWindow::loadConfig(const QString &fname) ui->srcSendLineEdit->clear(); ui->srcSendLineEdit->insert(settings.value(QStringLiteral("sends")).toString()); - QString resampler = settings.value(QStringLiteral("resampler")).toString().trimmed(); - ui->resamplerSlider->setValue(2); - ui->resamplerLabel->setText(std::data(resamplerList[2].name)); - /* "Cubic" is an alias for the 4-point gaussian resampler. The "sinc4" and + auto resampler = settings.value(QStringLiteral("resampler")).toString().trimmed(); + static constexpr auto defaultResamplerIndex = GetDefaultIndex(resamplerList); + ui->resamplerSlider->setValue(defaultResamplerIndex); + ui->resamplerLabel->setText(std::data(resamplerList[defaultResamplerIndex].name)); + /* "Cubic" is an alias for the 4-point spline resampler. The "sinc4" and * "sinc8" resamplers are unsupported, use "gaussian" as a fallback. */ - if(resampler == QLatin1String{"cubic"} || resampler == QLatin1String{"sinc4"} - || resampler == QLatin1String{"sinc8"}) + if(resampler == QLatin1String{"cubic"}) + resampler = QStringLiteral("spline"); + else if(resampler == QLatin1String{"sinc4"} || resampler == QLatin1String{"sinc8"}) resampler = QStringLiteral("gaussian"); /* The "bsinc" resampler name is an alias for "bsinc12". */ else if(resampler == QLatin1String{"bsinc"}) @@ -762,9 +782,10 @@ void MainWindow::loadConfig(const QString &fname) ui->enableSSE41CheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse4.1"), Qt::CaseInsensitive)); ui->enableNeonCheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("neon"), Qt::CaseInsensitive)); - QString hrtfmode{settings.value(QStringLiteral("hrtf-mode")).toString().trimmed()}; - ui->hrtfmodeSlider->setValue(2); - ui->hrtfmodeLabel->setText(std::data(hrtfModeList[3].name)); + auto hrtfmode = settings.value(QStringLiteral("hrtf-mode")).toString().trimmed(); + static constexpr auto defaultHrtfModeIndex = GetDefaultIndex(hrtfModeList); + ui->hrtfmodeSlider->setValue(defaultHrtfModeIndex); + ui->hrtfmodeLabel->setText(std::data(hrtfModeList[defaultHrtfModeIndex].name)); /* The "basic" mode name is no longer supported. Use "ambi2" instead. */ if(hrtfmode == QLatin1String{"basic"}) hrtfmode = QStringLiteral("ambi2"); @@ -1192,11 +1213,7 @@ void MainWindow::updatePeriodSizeSlider() { int pos = ui->periodSizeEdit->text().toInt(); if(pos >= 64) - { - if(pos > 8192) - pos = 8192; - ui->periodSizeSlider->setSliderPosition(pos); - } + ui->periodSizeSlider->setSliderPosition(std::min(pos, 8192)); enableApplyButton(); } diff --git a/Engine/lib/openal-soft/utils/makemhr/loaddef.cpp b/Engine/lib/openal-soft/utils/makemhr/loaddef.cpp index 5938cfed8..e62f5aab0 100644 --- a/Engine/lib/openal-soft/utils/makemhr/loaddef.cpp +++ b/Engine/lib/openal-soft/utils/makemhr/loaddef.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -45,13 +44,18 @@ #include "alnumeric.h" #include "alspan.h" #include "alstring.h" +#include "filesystem.h" +#include "fmt/core.h" #include "makemhr.h" #include "polyphase_resampler.h" +#include "sofa-support.h" #include "mysofa.h" namespace { +using namespace std::string_view_literals; + // Constants for accessing the token reader's ring buffer. constexpr uint TRRingBits{16}; constexpr uint TRRingSize{1 << TRRingBits}; @@ -63,22 +67,18 @@ constexpr uint TRLoadSize{TRRingSize >> 2}; // Token reader state for parsing the data set definition. struct TokenReaderT { std::istream &mIStream; - std::string mName{}; + std::string mName; uint mLine{}; uint mColumn{}; std::array mRing{}; std::streamsize mIn{}; std::streamsize mOut{}; - TokenReaderT(std::istream &istream) noexcept : mIStream{istream} { } + explicit TokenReaderT(std::istream &istream) noexcept : mIStream{istream} { } TokenReaderT(const TokenReaderT&) = default; }; -// The maximum identifier length used when processing the data set -// definition. -constexpr uint MaxIdentLen{16}; - // The limits for the listener's head 'radius' in the data set definition. constexpr double MinRadius{0.05}; constexpr double MaxRadius{0.15}; @@ -156,7 +156,7 @@ struct SourceRefT { double mRadius; uint mSkip; uint mOffset; - std::array mPath; + std::string mPath; }; @@ -203,14 +203,14 @@ auto TrLoad(TokenReaderT *tr) -> int { std::istream &istream = tr->mIStream; - std::streamsize toLoad{TRRingSize - static_cast(tr->mIn - tr->mOut)}; + auto toLoad = std::streamsize{TRRingSize} - (tr->mIn - tr->mOut); if(toLoad >= TRLoadSize && istream.good()) { // Load TRLoadSize (or less if at the end of the file) per read. toLoad = TRLoadSize; - const auto in = tr->mIn&TRRingMask; - std::streamsize count{TRRingSize - in}; + const auto in = tr->mIn & std::streamsize{TRRingMask}; + const auto count = std::streamsize{TRRingSize} - in; if(count < toLoad) { istream.read(al::to_address(tr->mRing.begin() + in), count); @@ -236,34 +236,22 @@ auto TrLoad(TokenReaderT *tr) -> int } // Error display routine. Only displays when the base name is not NULL. -void TrErrorVA(const TokenReaderT *tr, uint line, uint column, const char *format, va_list argPtr) +// Used to display an error at a saved line/column. +template +void TrErrorAt(const TokenReaderT *tr, uint line, uint column, fmt::format_string fmt, + Args&& ...args) { if(tr->mName.empty()) return; - fprintf(stderr, "\nError (%s:%u:%u): ", tr->mName.c_str(), line, column); - vfprintf(stderr, format, argPtr); -} - -// Used to display an error at a saved line/column. -void TrErrorAt(const TokenReaderT *tr, uint line, uint column, const char *format, ...) -{ - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - va_list argPtr; - va_start(argPtr, format); - TrErrorVA(tr, line, column, format, argPtr); - va_end(argPtr); - /* NOLINTEND(*-array-to-pointer-decay) */ + fmt::print(stderr, "\nError ({}:{}:{}): ", tr->mName, line, column); + fmt::println(stderr, fmt, std::forward(args)...); } // Used to display an error at the current line/column. -void TrError(const TokenReaderT *tr, const char *format, ...) +template +void TrError(const TokenReaderT *tr, fmt::format_string fmt, Args&& ...args) { - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - va_list argPtr; - va_start(argPtr, format); - TrErrorVA(tr, tr->mLine, tr->mColumn, format, argPtr); - va_end(argPtr); - /* NOLINTEND(*-array-to-pointer-decay) */ + TrErrorAt(tr, tr->mLine, tr->mColumn, fmt, std::forward(args)...); } // Skips to the next line. @@ -355,40 +343,31 @@ auto TrIsOperator(TokenReaderT *tr, const std::string_view op) -> int */ // Reads and validates an identifier token. -auto TrReadIdent(TokenReaderT *tr, const al::span ident) -> int +auto TrReadIdent(TokenReaderT *tr) -> std::string { - assert(!ident.empty()); - const size_t maxLen{ident.size()-1}; - uint col{tr->mColumn}; + auto ret = std::string{}; + auto col = tr->mColumn; if(TrSkipWhitespace(tr)) { col = tr->mColumn; - char ch{tr->mRing[tr->mOut&TRRingMask]}; + auto ch = char{tr->mRing[tr->mOut&TRRingMask]}; if(ch == '_' || isalpha(ch)) { - size_t len{0}; do { - if(len < maxLen) - ident[len] = ch; - ++len; - tr->mOut++; + ret += ch; + tr->mColumn += 1; + tr->mOut += 1; if(!TrLoad(tr)) break; ch = tr->mRing[tr->mOut&TRRingMask]; - } while(ch == '_' || isdigit(ch) || isalpha(ch)); + } while(ch == '_' || std::isdigit(ch) || std::isalpha(ch)); - tr->mColumn += static_cast(len); - if(len < maxLen) - { - ident[len] = '\0'; - return 1; - } - TrErrorAt(tr, tr->mLine, col, "Identifier is too long.\n"); - return 0; + return ret; } } - TrErrorAt(tr, tr->mLine, col, "Expected an identifier.\n"); - return 0; + TrErrorAt(tr, tr->mLine, col, "Expected an identifier."); + ret.clear(); + return ret; } // Reads and validates (including bounds) an integer token. @@ -430,13 +409,13 @@ auto TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int *valu *value = static_cast(strtol(temp.data(), nullptr, 10)); if(*value < loBound || *value > hiBound) { - TrErrorAt(tr, tr->mLine, col, "Expected a value from %d to %d.\n", loBound, hiBound); + TrErrorAt(tr, tr->mLine, col, "Expected a value from {} to {}.", loBound, hiBound); return 0; } return 1; } } - TrErrorAt(tr, tr->mLine, col, "Expected an integer.\n"); + TrErrorAt(tr, tr->mLine, col, "Expected an integer."); return 0; } @@ -524,7 +503,8 @@ auto TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBound, d *value = strtod(temp.data(), nullptr); if(*value < loBound || *value > hiBound) { - TrErrorAt(tr, tr->mLine, col, "Expected a value from %f to %f.\n", loBound, hiBound); + TrErrorAt(tr, tr->mLine, col, "Expected a value from {:f} to {:f}.", loBound, + hiBound); return 0; } return 1; @@ -533,17 +513,16 @@ auto TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBound, d else tr->mColumn += len; } - TrErrorAt(tr, tr->mLine, col, "Expected a float.\n"); + TrErrorAt(tr, tr->mLine, col, "Expected a float."); return 0; } // Reads and validates a string token. -auto TrReadString(TokenReaderT *tr, const al::span text) -> int +auto TrReadString(TokenReaderT *tr) -> std::optional { - assert(!text.empty()); - const size_t maxLen{text.size()-1}; + auto ret = std::string{}; - uint col{tr->mColumn}; + auto col = tr->mColumn; if(TrSkipWhitespace(tr)) { col = tr->mColumn; @@ -559,31 +538,25 @@ auto TrReadString(TokenReaderT *tr, const al::span text) -> int break; if(ch == '\n') { - TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of line.\n"); - return 0; + TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of line."); + return std::nullopt; } - if(len < maxLen) - text[len] = ch; + ret += ch; len++; } if(ch != '\"') { tr->mColumn += static_cast(1 + len); - TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of input.\n"); - return 0; + TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of input."); + return std::nullopt; } tr->mColumn += static_cast(2 + len); - if(len > maxLen) - { - TrErrorAt(tr, tr->mLine, col, "String is too long.\n"); - return 0; - } - text[len] = '\0'; - return 1; + + return std::optional{std::move(ret)}; } } - TrErrorAt(tr, tr->mLine, col, "Expected a string.\n"); - return 0; + TrErrorAt(tr, tr->mLine, col, "Expected a string."); + return std::nullopt; } // Reads and validates the given operator. @@ -605,7 +578,7 @@ auto TrReadOperator(TokenReaderT *tr, const std::string_view op) -> int if(len == op.size()) return 1; } - TrErrorAt(tr, tr->mLine, col, "Expected '%s' operator.\n", op); + TrErrorAt(tr, tr->mLine, col, "Expected '{}' operator.", op); return 0; } @@ -616,14 +589,14 @@ auto TrReadOperator(TokenReaderT *tr, const std::string_view op) -> int // Read a binary value of the specified byte order and byte size from a file, // storing it as a 32-bit unsigned integer. -auto ReadBin4(std::istream &istream, const char *filename, const ByteOrderT order, +auto ReadBin4(std::istream &istream, const std::string_view filename, const ByteOrderT order, const uint bytes, uint32_t *out) -> int { std::array in{}; istream.read(reinterpret_cast(in.data()), static_cast(bytes)); if(istream.gcount() != bytes) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); + fmt::println(stderr, "\nError: Bad read from file '{}'.", filename); return 0; } uint32_t accum{0}; @@ -646,13 +619,14 @@ auto ReadBin4(std::istream &istream, const char *filename, const ByteOrderT orde // Read a binary value of the specified byte order from a file, storing it as // a 64-bit unsigned integer. -auto ReadBin8(std::istream &istream, const char *filename, const ByteOrderT order, uint64_t *out) -> int +auto ReadBin8(std::istream &istream, const std::string_view filename, const ByteOrderT order, + uint64_t *out) -> int { std::array in{}; istream.read(reinterpret_cast(in.data()), 8); if(istream.gcount() != 8) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); + fmt::println(stderr, "\nError: Bad read from file '{}'.", filename); return 0; } @@ -680,8 +654,9 @@ auto ReadBin8(std::istream &istream, const char *filename, const ByteOrderT orde * whether they are padded toward the MSB (negative) or LSB (positive). * Floating-point types are not normalized. */ -auto ReadBinAsDouble(std::istream &istream, const char *filename, const ByteOrderT order, - const ElementTypeT type, const uint bytes, const int bits, double *out) -> int +auto ReadBinAsDouble(std::istream &istream, const std::string_view filename, + const ByteOrderT order, const ElementTypeT type, const uint bytes, const int bits, double *out) + -> int { *out = 0.0; if(bytes > 4) @@ -719,7 +694,7 @@ auto ReadBinAsDouble(std::istream &istream, const char *filename, const ByteOrde * result. The sign of the bits should always be positive. This also skips * up to one separator character before the element itself. */ -auto ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const ElementTypeT type, +auto ReadAsciiAsDouble(TokenReaderT *tr, const std::string_view filename, const ElementTypeT type, const uint bits, double *out) -> int { if(TrIsOperator(tr, ",")) @@ -736,7 +711,7 @@ auto ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const ElementType if(!TrReadFloat(tr, -std::numeric_limits::infinity(), std::numeric_limits::infinity(), out)) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); + fmt::println(stderr, "\nError: Bad read from file '{}'.", filename); return 0; } } @@ -745,7 +720,7 @@ auto ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const ElementType int v; if(!TrReadInt(tr, -(1<<(bits-1)), (1<<(bits-1))-1, &v)) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); + fmt::println(stderr, "\nError: Bad read from file '{}'.", filename); return 0; } *out = v / static_cast((1<<(bits-1))-1); @@ -765,36 +740,34 @@ auto ReadWaveFormat(std::istream &istream, const ByteOrderT order, const uint hr do { if(chunkSize > 0) istream.seekg(static_cast(chunkSize), std::ios::cur); - if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath.data(), order, 4, &chunkSize)) + if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) return 0; } while(fourCC != FOURCC_FMT); - if(!ReadBin4(istream, src->mPath.data(), order, 2, &format) - || !ReadBin4(istream, src->mPath.data(), order, 2, &channels) - || !ReadBin4(istream, src->mPath.data(), order, 4, &rate) - || !ReadBin4(istream, src->mPath.data(), order, 4, &dummy) - || !ReadBin4(istream, src->mPath.data(), order, 2, &block)) + if(!ReadBin4(istream, src->mPath, order, 2, &format) + || !ReadBin4(istream, src->mPath, order, 2, &channels) + || !ReadBin4(istream, src->mPath, order, 4, &rate) + || !ReadBin4(istream, src->mPath, order, 4, &dummy) + || !ReadBin4(istream, src->mPath, order, 2, &block)) return 0; block /= channels; if(chunkSize > 14) { - if(!ReadBin4(istream, src->mPath.data(), order, 2, &size)) + if(!ReadBin4(istream, src->mPath, order, 2, &size)) return 0; - size /= 8; - if(block > size) - size = block; + size = std::max(size/8, block); } else size = block; if(format == WAVE_FORMAT_EXTENSIBLE) { istream.seekg(2, std::ios::cur); - if(!ReadBin4(istream, src->mPath.data(), order, 2, &bits)) + if(!ReadBin4(istream, src->mPath, order, 2, &bits)) return 0; if(bits == 0) bits = 8 * size; istream.seekg(4, std::ios::cur); - if(!ReadBin4(istream, src->mPath.data(), order, 2, &format)) + if(!ReadBin4(istream, src->mPath, order, 2, &format)) return 0; istream.seekg(static_cast(chunkSize - 26), std::ios::cur); } @@ -808,32 +781,32 @@ auto ReadWaveFormat(std::istream &istream, const ByteOrderT order, const uint hr } if(format != WAVE_FORMAT_PCM && format != WAVE_FORMAT_IEEE_FLOAT) { - fprintf(stderr, "\nError: Unsupported WAVE format in file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Unsupported WAVE format in file '{}'.", src->mPath); return 0; } if(src->mChannel >= channels) { - fprintf(stderr, "\nError: Missing source channel in WAVE file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Missing source channel in WAVE file '{}'.", src->mPath); return 0; } if(rate != hrirRate) { - fprintf(stderr, "\nError: Mismatched source sample rate in WAVE file '%s'.\n", - src->mPath.data()); + fmt::println(stderr, "\nError: Mismatched source sample rate in WAVE file '{}'.", + src->mPath); return 0; } if(format == WAVE_FORMAT_PCM) { if(size < 2 || size > 4) { - fprintf(stderr, "\nError: Unsupported sample size in WAVE file '%s'.\n", - src->mPath.data()); + fmt::println(stderr, "\nError: Unsupported sample size in WAVE file '{}'.", + src->mPath); return 0; } if(bits < 16 || bits > (8*size)) { - fprintf(stderr, "\nError: Bad significant bits in WAVE file '%s'.\n", - src->mPath.data()); + fmt::println(stderr, "\nError: Bad significant bits in WAVE file '{}'.", + src->mPath); return 0; } src->mType = ET_INT; @@ -842,8 +815,8 @@ auto ReadWaveFormat(std::istream &istream, const ByteOrderT order, const uint hr { if(size != 4 && size != 8) { - fprintf(stderr, "\nError: Unsupported sample size in WAVE file '%s'.\n", - src->mPath.data()); + fmt::println(stderr, "\nError: Unsupported sample size in WAVE file '{}'.", + src->mPath); return 0; } src->mType = ET_FP; @@ -860,13 +833,13 @@ auto ReadWaveData(std::istream &istream, const SourceRefT *src, const ByteOrderT { auto pre = static_cast(src->mSize * src->mChannel); auto post = static_cast(src->mSize * (src->mSkip - src->mChannel - 1)); - auto skip = int{0}; + auto skip = 0; for(size_t i{0};i < hrir.size();++i) { skip += pre; if(skip > 0) istream.seekg(skip, std::ios::cur); - if(!ReadBinAsDouble(istream, src->mPath.data(), order, src->mType, src->mSize, src->mBits, + if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) return 0; skip = post; @@ -887,8 +860,8 @@ auto ReadWaveList(std::istream &istream, const SourceRefT *src, const ByteOrderT for(;;) { - if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath.data(), order, 4, &chunkSize)) + if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) return 0; if(fourCC == FOURCC_DATA) @@ -897,7 +870,7 @@ auto ReadWaveList(std::istream &istream, const SourceRefT *src, const ByteOrderT count = chunkSize / block; if(count < (src->mOffset + hrir.size())) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Bad read from file '{}'.", src->mPath); return 0; } using off_type = std::istream::off_type; @@ -908,7 +881,7 @@ auto ReadWaveList(std::istream &istream, const SourceRefT *src, const ByteOrderT } if(fourCC == FOURCC_LIST) { - if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC)) + if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)) return 0; chunkSize -= 4; if(fourCC == FOURCC_WAVL) @@ -924,8 +897,8 @@ auto ReadWaveList(std::istream &istream, const SourceRefT *src, const ByteOrderT lastSample = 0.0; while(offset < hrir.size() && listSize > 8) { - if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath.data(), order, 4, &chunkSize)) + if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) return 0; listSize -= 8 + chunkSize; if(fourCC == FOURCC_DATA) @@ -954,7 +927,7 @@ auto ReadWaveList(std::istream &istream, const SourceRefT *src, const ByteOrderT } else if(fourCC == FOURCC_SLNT) { - if(!ReadBin4(istream, src->mPath.data(), order, 4, &count)) + if(!ReadBin4(istream, src->mPath, order, 4, &count)) return 0; chunkSize -= 4; if(count > skip) @@ -978,7 +951,7 @@ auto ReadWaveList(std::istream &istream, const SourceRefT *src, const ByteOrderT } if(offset < hrir.size()) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Bad read from file '{}'.", src->mPath); return 0; } return 1; @@ -994,20 +967,19 @@ auto LoadAsciiSource(std::istream &istream, const SourceRefT *src, const al::spa for(uint i{0};i < src->mOffset;++i) { double dummy{}; - if(!ReadAsciiAsDouble(&tr, src->mPath.data(), src->mType, static_cast(src->mBits), - &dummy)) + if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &dummy)) return 0; } for(size_t i{0};i < hrir.size();++i) { - if(!ReadAsciiAsDouble(&tr, src->mPath.data(), src->mType, static_cast(src->mBits), + if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &hrir[i])) return 0; for(uint j{0};j < src->mSkip;++j) { double dummy{}; - if(!ReadAsciiAsDouble(&tr, src->mPath.data(), src->mType, - static_cast(src->mBits), &dummy)) + if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), + &dummy)) return 0; } } @@ -1021,7 +993,7 @@ auto LoadBinarySource(std::istream &istream, const SourceRefT *src, const ByteOr istream.seekg(static_cast(src->mOffset), std::ios::beg); for(size_t i{0};i < hrir.size();++i) { - if(!ReadBinAsDouble(istream, src->mPath.data(), order, src->mType, src->mSize, src->mBits, + if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) return 0; if(src->mSkip > 0) @@ -1037,8 +1009,8 @@ auto LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hrirRate, uint32_t fourCC, dummy; ByteOrderT order; - if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &dummy)) + if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath, BO_LITTLE, 4, &dummy)) return 0; if(fourCC == FOURCC_RIFF) order = BO_LITTLE; @@ -1046,15 +1018,15 @@ auto LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hrirRate, order = BO_BIG; else { - fprintf(stderr, "\nError: No RIFF/RIFX chunk in file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: No RIFF/RIFX chunk in file '{}'.", src->mPath); return 0; } - if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC)) + if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)) return 0; if(fourCC != FOURCC_WAVE) { - fprintf(stderr, "\nError: Not a RIFF/RIFX WAVE file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Not a RIFF/RIFX WAVE file '{}'.", src->mPath); return 0; } if(!ReadWaveFormat(istream, order, hrirRate, src)) @@ -1086,7 +1058,7 @@ std::vector gSofaCache; // Load a Spatially Oriented Format for Accoustics (SOFA) file. auto LoadSofaFile(SourceRefT *src, const uint hrirRate, const uint n) -> MYSOFA_EASY* { - const std::string_view srcname{src->mPath.data()}; + const std::string_view srcname{src->mPath}; auto iter = std::find_if(gSofaCache.begin(), gSofaCache.end(), [srcname,hrirRate](SofaCacheEntry &entry) -> bool { return entry.mName == srcname && entry.mSampleRate == hrirRate; }); @@ -1095,40 +1067,40 @@ auto LoadSofaFile(SourceRefT *src, const uint hrirRate, const uint n) -> MYSOFA_ SofaEasyPtr sofa{new(std::nothrow) MYSOFA_EASY{}}; if(!sofa) { - fprintf(stderr, "\nError: Out of memory.\n"); + fmt::println(stderr, "\nError: Out of memory."); return nullptr; } sofa->lookup = nullptr; sofa->neighborhood = nullptr; int err; - sofa->hrtf = mysofa_load(src->mPath.data(), &err); + sofa->hrtf = mysofa_load(src->mPath.c_str(), &err); if(!sofa->hrtf) { - fprintf(stderr, "\nError: Could not load source file '%s' (error: %d).\n", - src->mPath.data(), err); + fmt::println(stderr, "\nError: Could not load source file '{}': {} ({}).", src->mPath, + SofaErrorStr(err), err); return nullptr; } /* NOTE: Some valid SOFA files are failing this check. */ err = mysofa_check(sofa->hrtf); if(err != MYSOFA_OK) - fprintf(stderr, "\nWarning: Supposedly malformed source file '%s' (error: %d).\n", - src->mPath.data(), err); + fmt::println(stderr, "\nWarning: Supposedly malformed source file '{}': {} ({}).", + src->mPath, SofaErrorStr(err), err); if((src->mOffset + n) > sofa->hrtf->N) { - fprintf(stderr, "\nError: Not enough samples in SOFA file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Not enough samples in SOFA file '{}'.", src->mPath); return nullptr; } if(src->mChannel >= sofa->hrtf->R) { - fprintf(stderr, "\nError: Missing source receiver in SOFA file '%s'.\n",src->mPath.data()); + fmt::println(stderr, "\nError: Missing source receiver in SOFA file '{}'.", src->mPath); return nullptr; } mysofa_tocartesian(sofa->hrtf); sofa->lookup = mysofa_lookup_init(sofa->hrtf); if(sofa->lookup == nullptr) { - fprintf(stderr, "\nError: Out of memory.\n"); + fmt::println(stderr, "\nError: Out of memory."); return nullptr; } gSofaCache.emplace_back(SofaCacheEntry{std::string{srcname}, hrirRate, std::move(sofa)}); @@ -1165,7 +1137,7 @@ auto LoadSofaSource(SourceRefT *src, const uint hrirRate, const al::span int nearest{mysofa_lookup(sofa->lookup, target.data())}; if(nearest < 0) { - fprintf(stderr, "\nError: Lookup failed in source file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Lookup failed in source file '{}'.", src->mPath); return 0; } @@ -1174,14 +1146,15 @@ auto LoadSofaSource(SourceRefT *src, const uint hrirRate, const al::span if(std::abs(coords[0] - target[0]) > 0.001 || std::abs(coords[1] - target[1]) > 0.001 || std::abs(coords[2] - target[2]) > 0.001) { - fprintf(stderr, "\nError: No impulse response at coordinates (%.3fr, %.1fev, %.1faz) in file '%s'.\n", - src->mRadius, src->mElevation, src->mAzimuth, src->mPath.data()); + fmt::println(stderr, + "\nError: No impulse response at coordinates ({:.3f}r, {:.1f}ev, {:.1f}az) in file '{}'.", + src->mRadius, src->mElevation, src->mAzimuth, src->mPath); target[0] = coords[0]; target[1] = coords[1]; target[2] = coords[2]; mysofa_c2s(target.data()); - fprintf(stderr, " Nearest candidate at (%.3fr, %.1fev, %.1faz).\n", target[2], - target[1], target[0]); + fmt::println(stderr, " Nearest candidate at ({:.3f}r, {:.1f}ev, {:.1f}az).", + target[2], target[1], target[0]); return 0; } @@ -1193,27 +1166,26 @@ auto LoadSofaSource(SourceRefT *src, const uint hrirRate, const al::span // Load a source HRIR from a supported file type. auto LoadSource(SourceRefT *src, const uint hrirRate, const al::span hrir) -> int { - std::unique_ptr istream; + auto istream = fs::ifstream{}; if(src->mFormat != SF_SOFA) { if(src->mFormat == SF_ASCII) - istream = std::make_unique(std::filesystem::u8path(src->mPath.data())); + istream.open(fs::u8path(src->mPath)); else - istream = std::make_unique(std::filesystem::u8path(src->mPath.data()), - std::ios::binary); - if(!istream->good()) + istream.open(fs::u8path(src->mPath), std::ios::binary); + if(!istream.good()) { - fprintf(stderr, "\nError: Could not open source file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Could not open source file '{}'.", src->mPath); return 0; } } switch(src->mFormat) { - case SF_ASCII: return LoadAsciiSource(*istream, src, hrir); - case SF_BIN_LE: return LoadBinarySource(*istream, src, BO_LITTLE, hrir); - case SF_BIN_BE: return LoadBinarySource(*istream, src, BO_BIG, hrir); - case SF_WAVE: return LoadWaveSource(*istream, src, hrirRate, hrir); + case SF_ASCII: return LoadAsciiSource(istream, src, hrir); + case SF_BIN_LE: return LoadBinarySource(istream, src, BO_LITTLE, hrir); + case SF_BIN_BE: return LoadBinarySource(istream, src, BO_BIG, hrir); + case SF_WAVE: return LoadWaveSource(istream, src, hrirRate, hrir); case SF_SOFA: return LoadSofaSource(src, hrirRate, hrir); case SF_NONE: break; } @@ -1222,11 +1194,11 @@ auto LoadSource(SourceRefT *src, const uint hrirRate, const al::span hri // Match the channel type from a given identifier. -auto MatchChannelType(const char *ident) -> ChannelTypeT +auto MatchChannelType(const std::string_view ident) -> ChannelTypeT { - if(al::strcasecmp(ident, "mono") == 0) + if(al::case_compare(ident, "mono"sv) == 0) return CT_MONO; - if(al::strcasecmp(ident, "stereo") == 0) + if(al::case_compare(ident, "stereo"sv) == 0) return CT_STEREO; return CT_NONE; } @@ -1238,7 +1210,6 @@ auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, { int hasRate = 0, hasType = 0, hasPoints = 0, hasRadius = 0; int hasDistance = 0, hasAzimuths = 0; - std::array ident{}; uint line, col; double fpVal; uint points; @@ -1253,13 +1224,14 @@ auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, while(TrIsIdent(tr)) { TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, ident)) + const auto ident = TrReadIdent(tr); + if(ident.empty()) return 0; - if(al::strcasecmp(ident.data(), "rate") == 0) + if(al::case_compare(ident, "rate"sv) == 0) { if(hasRate) { - TrErrorAt(tr, line, col, "Redefinition of 'rate'.\n"); + TrErrorAt(tr, line, col, "Redefinition of 'rate'."); return 0; } if(!TrReadOperator(tr, "=")) @@ -1269,24 +1241,23 @@ auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, hData->mIrRate = static_cast(intVal); hasRate = 1; } - else if(al::strcasecmp(ident.data(), "type") == 0) + else if(al::case_compare(ident, "type"sv) == 0) { - std::array type{}; - if(hasType) { - TrErrorAt(tr, line, col, "Redefinition of 'type'.\n"); + TrErrorAt(tr, line, col, "Redefinition of 'type'."); return 0; } if(!TrReadOperator(tr, "=")) return 0; - if(!TrReadIdent(tr, type)) + const auto type = TrReadIdent(tr); + if(type.empty()) return 0; - hData->mChannelType = MatchChannelType(type.data()); + hData->mChannelType = MatchChannelType(type); if(hData->mChannelType == CT_NONE) { - TrErrorAt(tr, line, col, "Expected a channel type.\n"); + TrErrorAt(tr, line, col, "Expected a channel type."); return 0; } if(hData->mChannelType == CT_STEREO) @@ -1296,11 +1267,11 @@ auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, } hasType = 1; } - else if(al::strcasecmp(ident.data(), "points") == 0) + else if(al::case_compare(ident, "points"sv) == 0) { if(hasPoints) { - TrErrorAt(tr, line, col, "Redefinition of 'points'.\n"); + TrErrorAt(tr, line, col, "Redefinition of 'points'."); return 0; } if(!TrReadOperator(tr, "=")) @@ -1311,26 +1282,24 @@ auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, points = static_cast(intVal); if(fftSize > 0 && points > fftSize) { - TrErrorAt(tr, line, col, "Value exceeds the overridden FFT size.\n"); + TrErrorAt(tr, line, col, "Value exceeds the overridden FFT size."); return 0; } if(points < truncSize) { - TrErrorAt(tr, line, col, "Value is below the truncation size.\n"); + TrErrorAt(tr, line, col, "Value is below the truncation size."); return 0; } hData->mIrPoints = points; hData->mFftSize = fftSize; - hData->mIrSize = 1 + (fftSize / 2); - if(points > hData->mIrSize) - hData->mIrSize = points; + hData->mIrSize = std::max(points, 1u + (fftSize/2u)); hasPoints = 1; } - else if(al::strcasecmp(ident.data(), "radius") == 0) + else if(al::case_compare(ident, "radius"sv) == 0) { if(hasRadius) { - TrErrorAt(tr, line, col, "Redefinition of 'radius'.\n"); + TrErrorAt(tr, line, col, "Redefinition of 'radius'."); return 0; } if(!TrReadOperator(tr, "=")) @@ -1340,13 +1309,13 @@ auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, hData->mRadius = fpVal; hasRadius = 1; } - else if(al::strcasecmp(ident.data(), "distance") == 0) + else if(al::case_compare(ident, "distance"sv) == 0) { - uint count = 0; + auto count = uint{0}; if(hasDistance) { - TrErrorAt(tr, line, col, "Redefinition of 'distance'.\n"); + TrErrorAt(tr, line, col, "Redefinition of 'distance'."); return 0; } if(!TrReadOperator(tr, "=")) @@ -1358,7 +1327,7 @@ auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, return 0; if(count > 0 && fpVal <= distances[count - 1]) { - TrError(tr, "Distances are not ascending.\n"); + TrError(tr, "Distances are not ascending."); return 0; } distances[count++] = fpVal; @@ -1366,26 +1335,26 @@ auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, break; if(count >= MAX_FD_COUNT) { - TrError(tr, "Exceeded the maximum of %d fields.\n", MAX_FD_COUNT); + TrError(tr, "Exceeded the maximum of {} fields.", MAX_FD_COUNT); return 0; } TrReadOperator(tr, ","); } if(fdCount != 0 && count != fdCount) { - TrError(tr, "Did not match the specified number of %d fields.\n", fdCount); + TrError(tr, "Did not match the specified number of {} fields.", fdCount); return 0; } fdCount = count; hasDistance = 1; } - else if(al::strcasecmp(ident.data(), "azimuths") == 0) + else if(al::case_compare(ident, "azimuths"sv) == 0) { - uint count = 0; + auto count = uint{0}; if(hasAzimuths) { - TrErrorAt(tr, line, col, "Redefinition of 'azimuths'.\n"); + TrErrorAt(tr, line, col, "Redefinition of 'azimuths'."); return 0; } if(!TrReadOperator(tr, "=")) @@ -1401,7 +1370,7 @@ auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, { if(evCounts[count] >= MAX_EV_COUNT) { - TrError(tr, "Exceeded the maximum of %d elevations.\n", MAX_EV_COUNT); + TrError(tr, "Exceeded the maximum of {} elevations.", MAX_EV_COUNT); return 0; } TrReadOperator(tr, ","); @@ -1410,12 +1379,13 @@ auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, { if(evCounts[count] < MIN_EV_COUNT) { - TrErrorAt(tr, line, col, "Did not reach the minimum of %d azimuth counts.\n", MIN_EV_COUNT); + TrErrorAt(tr, line, col, "Did not reach the minimum of {} azimuth counts.", + MIN_EV_COUNT); return 0; } if(azCounts[count][0] != 1 || azCounts[count][evCounts[count] - 1] != 1) { - TrError(tr, "Poles are not singular for field %d.\n", count - 1); + TrError(tr, "Poles are not singular for field {}.", count - 1); return 0; } count++; @@ -1424,7 +1394,7 @@ auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, if(count >= MAX_FD_COUNT) { - TrError(tr, "Exceeded the maximum number of %d fields.\n", MAX_FD_COUNT); + TrError(tr, "Exceeded the maximum number of %d fields.", MAX_FD_COUNT); return 0; } evCounts[count] = 0; @@ -1433,7 +1403,7 @@ auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, } if(fdCount != 0 && count != fdCount) { - TrError(tr, "Did not match the specified number of %d fields.\n", fdCount); + TrError(tr, "Did not match the specified number of %d fields.", fdCount); return 0; } fdCount = count; @@ -1441,19 +1411,19 @@ auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, } else { - TrErrorAt(tr, line, col, "Expected a metric name.\n"); + TrErrorAt(tr, line, col, "Expected a metric name."); return 0; } TrSkipWhitespace(tr); } if(!(hasRate && hasPoints && hasRadius && hasDistance && hasAzimuths)) { - TrErrorAt(tr, line, col, "Expected a metric name.\n"); + TrErrorAt(tr, line, col, "Expected a metric name."); return 0; } if(distances[0] < hData->mRadius) { - TrError(tr, "Distance cannot start below head radius.\n"); + TrError(tr, "Distance cannot start below head radius."); return 0; } if(hData->mChannelType == CT_NONE) @@ -1461,7 +1431,7 @@ auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, const auto azs = al::span{azCounts}.first(); if(!PrepareHrirData(al::span{distances}.first(fdCount), evCounts, azs, hData)) { - fprintf(stderr, "Error: Out of memory.\n"); + fmt::println(stderr, "Error: Out of memory."); exit(-1); } return 1; @@ -1496,27 +1466,27 @@ auto ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, uint * } // Match the source format from a given identifier. -auto MatchSourceFormat(const char *ident) -> SourceFormatT +auto MatchSourceFormat(const std::string_view ident) -> SourceFormatT { - if(al::strcasecmp(ident, "ascii") == 0) + if(al::case_compare(ident, "ascii"sv) == 0) return SF_ASCII; - if(al::strcasecmp(ident, "bin_le") == 0) + if(al::case_compare(ident, "bin_le"sv) == 0) return SF_BIN_LE; - if(al::strcasecmp(ident, "bin_be") == 0) + if(al::case_compare(ident, "bin_be"sv) == 0) return SF_BIN_BE; - if(al::strcasecmp(ident, "wave") == 0) + if(al::case_compare(ident, "wave"sv) == 0) return SF_WAVE; - if(al::strcasecmp(ident, "sofa") == 0) + if(al::case_compare(ident, "sofa"sv) == 0) return SF_SOFA; return SF_NONE; } // Match the source element type from a given identifier. -auto MatchElementType(const char *ident) -> ElementTypeT +auto MatchElementType(const std::string_view ident) -> ElementTypeT { - if(al::strcasecmp(ident, "int") == 0) + if(al::case_compare(ident, "int"sv) == 0) return ET_INT; - if(al::strcasecmp(ident, "fp") == 0) + if(al::case_compare(ident, "fp"sv) == 0) return ET_FP; return ET_NONE; } @@ -1524,18 +1494,18 @@ auto MatchElementType(const char *ident) -> ElementTypeT // Parse and validate a source reference from the data set definition. auto ReadSourceRef(TokenReaderT *tr, SourceRefT *src) -> int { - std::array ident{}; uint line, col; double fpVal; int intVal; TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, ident)) + auto ident = TrReadIdent(tr); + if(ident.empty()) return 0; - src->mFormat = MatchSourceFormat(ident.data()); + src->mFormat = MatchSourceFormat(ident); if(src->mFormat == SF_NONE) { - TrErrorAt(tr, line, col, "Expected a source format.\n"); + TrErrorAt(tr, line, col, "Expected a source format."); return 0; } if(!TrReadOperator(tr, "(")) @@ -1578,12 +1548,13 @@ auto ReadSourceRef(TokenReaderT *tr, SourceRefT *src) -> int else { TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, ident)) + ident = TrReadIdent(tr); + if(ident.empty()) return 0; - src->mType = MatchElementType(ident.data()); + src->mType = MatchElementType(ident); if(src->mType == ET_NONE) { - TrErrorAt(tr, line, col, "Expected a source element type.\n"); + TrErrorAt(tr, line, col, "Expected a source element type."); return 0; } if(src->mFormat == SF_BIN_LE || src->mFormat == SF_BIN_BE) @@ -1605,7 +1576,7 @@ auto ReadSourceRef(TokenReaderT *tr, SourceRefT *src) -> int return 0; if(std::abs(intVal) < int{MinBinSize}*8 || static_cast(std::abs(intVal)) > (8*src->mSize)) { - TrErrorAt(tr, line, col, "Expected a value of (+/-) %d to %d.\n", MinBinSize*8, 8*src->mSize); + TrErrorAt(tr, line, col, "Expected a value of (+/-) {} to {}.", MinBinSize*8, 8*src->mSize); return 0; } src->mBits = intVal; @@ -1618,7 +1589,7 @@ auto ReadSourceRef(TokenReaderT *tr, SourceRefT *src) -> int return 0; if(intVal != 4 && intVal != 8) { - TrErrorAt(tr, line, col, "Expected a value of 4 or 8.\n"); + TrErrorAt(tr, line, col, "Expected a value of 4 or 8."); return 0; } src->mSize = static_cast(intVal); @@ -1663,25 +1634,28 @@ auto ReadSourceRef(TokenReaderT *tr, SourceRefT *src) -> int src->mOffset = 0; if(!TrReadOperator(tr, ":")) return 0; - if(!TrReadString(tr, src->mPath)) + + auto srcpath = TrReadString(tr); + if(!srcpath) return 0; + src->mPath = std::move(*srcpath); return 1; } // Parse and validate a SOFA source reference from the data set definition. auto ReadSofaRef(TokenReaderT *tr, SourceRefT *src) -> int { - std::array ident{}; uint line, col; int intVal; TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, ident)) + const auto ident = TrReadIdent(tr); + if(ident.empty()) return 0; - src->mFormat = MatchSourceFormat(ident.data()); + src->mFormat = MatchSourceFormat(ident); if(src->mFormat != SF_SOFA) { - TrErrorAt(tr, line, col, "Expected the SOFA source format.\n"); + TrErrorAt(tr, line, col, "Expected the SOFA source format."); return 0; } @@ -1702,19 +1676,22 @@ auto ReadSofaRef(TokenReaderT *tr, SourceRefT *src) -> int src->mOffset = 0; if(!TrReadOperator(tr, ":")) return 0; - if(!TrReadString(tr, src->mPath)) + + auto srcpath = TrReadString(tr); + if(!srcpath) return 0; + src->mPath = std::move(*srcpath); return 1; } // Match the target ear (index) from a given identifier. -auto MatchTargetEar(const char *ident) -> int +auto MatchTargetEar(const std::string_view ident) -> std::optional { - if(al::strcasecmp(ident, "left") == 0) - return 0; - if(al::strcasecmp(ident, "right") == 0) - return 1; - return -1; + if(al::case_compare(ident, "left"sv) == 0) + return 0u; + if(al::case_compare(ident, "right"sv) == 0) + return 1u; + return std::nullopt; } // Calculate the onset time of an HRIR and average it with any existing @@ -1769,7 +1746,7 @@ auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> i ? std::min(static_cast(std::ceil(hData->mIrPoints*rateScale)), hData->mIrPoints) : hData->mIrPoints}; - printf("Loading sources..."); + fmt::print("Loading sources..."); fflush(stdout); int count{0}; while(TrIsOperator(tr, "[")) @@ -1792,16 +1769,14 @@ auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> i if(hData->mChannelType == CT_STEREO) { - std::array type{}; - - if(!TrReadIdent(tr, type)) + const auto type = TrReadIdent(tr); + if(type.empty()) return 0; - const ChannelTypeT channelType{MatchChannelType(type.data())}; - switch(channelType) + switch(MatchChannelType(type)) { case CT_NONE: - TrErrorAt(tr, line, col, "Expected a channel type.\n"); + TrErrorAt(tr, line, col, "Expected a channel type."); return 0; case CT_MONO: src.mChannel = 0; @@ -1813,14 +1788,13 @@ auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> i } else { - std::array type{}; - if(!TrReadIdent(tr, type)) + const auto type = TrReadIdent(tr); + if(type.empty()) return 0; - ChannelTypeT channelType{MatchChannelType(type.data())}; - if(channelType != CT_MONO) + if(MatchChannelType(type) != CT_MONO) { - TrErrorAt(tr, line, col, "Expected a mono channel type.\n"); + TrErrorAt(tr, line, col, "Expected a mono channel type."); return 0; } src.mChannel = 0; @@ -1833,7 +1807,7 @@ auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> i sofa->hrtf->M*3_uz}; for(uint si{0};si < sofa->hrtf->M;++si) { - printf("\rLoading sources... %d of %d", si+1, sofa->hrtf->M); + fmt::print("\rLoading sources... {} of {}", si+1, sofa->hrtf->M); fflush(stdout); std::array aer{srcPosValues[3_uz*si], srcPosValues[3_uz*si + 1], @@ -1870,7 +1844,7 @@ auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> i HrirAzT *azd = &field->mEvs[ei].mAzs[ai]; if(!azd->mIrs[0].empty()) { - TrErrorAt(tr, line, col, "Redefinition of source [ %d, %d, %d ].\n", fi, ei, ai); + TrErrorAt(tr, line, col, "Redefinition of source [ {}, {}, {} ].", fi, ei, ai); return 0; } @@ -1913,7 +1887,7 @@ auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> i if(!azd->mIrs[0].empty()) { - TrErrorAt(tr, line, col, "Redefinition of source.\n"); + TrErrorAt(tr, line, col, "Redefinition of source."); return 0; } if(!TrReadOperator(tr, "=")) @@ -1929,22 +1903,24 @@ auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> i // require preparing the source refs first to get a total count // before loading them. ++count; - printf("\rLoading sources... %d file%s", count, (count==1)?"":"s"); + fmt::print("\rLoading sources... {} file{}", count, (count==1)?"":"s"); fflush(stdout); if(!LoadSource(&src, hData->mIrRate, al::span{hrir}.first(hData->mIrPoints))) return 0; - uint ti{0}; + auto ti = uint{0}; if(hData->mChannelType == CT_STEREO) { - std::array ident{}; - if(!TrReadIdent(tr, ident)) + const auto ident = TrReadIdent(tr); + if(ident.empty()) return 0; - ti = static_cast(MatchTargetEar(ident.data())); - if(static_cast(ti) < 0) + + if(auto earopt = MatchTargetEar(ident)) + ti = *earopt; + else { - TrErrorAt(tr, line, col, "Expected a target ear.\n"); + TrErrorAt(tr, line, col, "Expected a target ear."); return 0; } } @@ -1955,7 +1931,7 @@ auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> i hrirPoints, 1.0/factor[ti], azd->mDelays[ti]); if(resampler) resampler->process(hrirPoints, hrir); - AverageHrirMagnitude(hData->mFftSize, al::span{hrir}.subspan(irPoints), 1.0/factor[ti], + AverageHrirMagnitude(hData->mFftSize, al::span{hrir}.first(irPoints), 1.0/factor[ti], azd->mIrs[ti]); factor[ti] += 1.0; if(!TrIsOperator(tr, "+")) @@ -1966,17 +1942,17 @@ auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> i { if(azd->mIrs[0].empty()) { - TrErrorAt(tr, line, col, "Missing left ear source reference(s).\n"); + TrErrorAt(tr, line, col, "Missing left ear source reference(s)."); return 0; } if(azd->mIrs[1].empty()) { - TrErrorAt(tr, line, col, "Missing right ear source reference(s).\n"); + TrErrorAt(tr, line, col, "Missing right ear source reference(s)."); return 0; } } } - printf("\n"); + fmt::println(""); hrir.clear(); if(resampler) { @@ -1999,7 +1975,7 @@ auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> i } if(ei >= hData->mFds[fi].mEvs.size()) { - TrError(tr, "Missing source references [ %d, *, * ].\n", fi); + TrError(tr, "Missing source references [ {}, *, * ].", fi); return 0; } hData->mFds[fi].mEvStart = ei; @@ -2011,7 +1987,7 @@ auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> i if(azd->mIrs[0].empty()) { - TrError(tr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai); + TrError(tr, "Missing source reference [ {}, {}, {} ].", fi, ei, ai); return 0; } } @@ -2039,7 +2015,7 @@ auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> i return 1; } - TrError(tr, "Errant data at end of source list.\n"); + TrError(tr, "Errant data at end of source list."); gSofaCache.clear(); return 0; } diff --git a/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp b/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp index 903019d86..70d03e639 100644 --- a/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp +++ b/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp @@ -32,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -40,8 +39,8 @@ #include #include "alspan.h" -#include "alstring.h" #include "alnumeric.h" +#include "fmt/core.h" #include "makemhr.h" #include "polyphase_resampler.h" #include "sofa-support.h" @@ -62,12 +61,12 @@ using uint = unsigned int; */ auto PrepareLayout(const al::span xyzs, HrirDataT *hData) -> bool { - fprintf(stdout, "Detecting compatible layout...\n"); + fmt::println("Detecting compatible layout..."); auto fds = GetCompatibleLayout(xyzs); if(fds.size() > MAX_FD_COUNT) { - fprintf(stdout, "Incompatible layout (inumerable radii).\n"); + fmt::println("Incompatible layout (inumerable radii)."); return false; } @@ -92,7 +91,7 @@ auto PrepareLayout(const al::span xyzs, HrirDataT *hData) -> bool ++fi; } - fprintf(stdout, "Using %u of %zu IRs.\n", ir_total, xyzs.size()/3); + fmt::println("Using {} of {} IRs.", ir_total, xyzs.size()/3); const auto azs = al::span{azCounts}.first(); return PrepareHrirData(al::span{distances}.first(fi), evCounts, azs, hData); } @@ -109,7 +108,7 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf) { if(srate_dim) { - fprintf(stderr, "Duplicate SampleRate.DIMENSION_LIST\n"); + fmt::println(stderr, "Duplicate SampleRate.DIMENSION_LIST"); return 0.0f; } srate_dim = srate_attrs->value; @@ -118,42 +117,42 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf) { if(srate_units) { - fprintf(stderr, "Duplicate SampleRate.Units\n"); + fmt::println(stderr, "Duplicate SampleRate.Units"); return 0.0f; } srate_units = srate_attrs->value; } else - fprintf(stderr, "Unexpected sample rate attribute: %s = %s\n", srate_attrs->name, + fmt::println(stderr, "Unexpected sample rate attribute: {} = {}", srate_attrs->name, srate_attrs->value); srate_attrs = srate_attrs->next; } if(!srate_dim) { - fprintf(stderr, "Missing sample rate dimensions\n"); + fmt::println(stderr, "Missing sample rate dimensions"); return 0.0f; } if(srate_dim != "I"sv) { - fprintf(stderr, "Unsupported sample rate dimensions: %s\n", srate_dim); + fmt::println(stderr, "Unsupported sample rate dimensions: {}", srate_dim); return 0.0f; } if(!srate_units) { - fprintf(stderr, "Missing sample rate unit type\n"); + fmt::println(stderr, "Missing sample rate unit type"); return 0.0f; } if(srate_units != "hertz"sv) { - fprintf(stderr, "Unsupported sample rate unit type: %s\n", srate_units); + fmt::println(stderr, "Unsupported sample rate unit type: {}", srate_units); return 0.0f; } /* I dimensions guarantees 1 element, so just extract it. */ const auto values = al::span{srate_array->values, sofaHrtf->I}; if(values[0] < float{MIN_RATE} || values[0] > float{MAX_RATE}) { - fprintf(stderr, "Sample rate out of range: %f (expected %u to %u)", values[0], MIN_RATE, - MAX_RATE); + fmt::println(stderr, "Sample rate out of range: {:f} (expected {} to {})", values[0], + MIN_RATE, MAX_RATE); return 0.0f; } return values[0]; @@ -175,19 +174,19 @@ auto PrepareDelay(MYSOFA_HRTF *sofaHrtf) -> std::optional { if(delay_dim) { - fprintf(stderr, "Duplicate Delay.DIMENSION_LIST\n"); + fmt::println(stderr, "Duplicate Delay.DIMENSION_LIST"); return std::nullopt; } delay_dim = delay_attrs->value; } else - fprintf(stderr, "Unexpected delay attribute: %s = %s\n", delay_attrs->name, + fmt::println(stderr, "Unexpected delay attribute: {} = {}", delay_attrs->name, delay_attrs->value ? delay_attrs->value : ""); delay_attrs = delay_attrs->next; } if(!delay_dim) { - fprintf(stderr, "Missing delay dimensions\n"); + fmt::println(stderr, "Missing delay dimensions"); return DelayType::None; } if(delay_dim == "I,R"sv) @@ -195,7 +194,7 @@ auto PrepareDelay(MYSOFA_HRTF *sofaHrtf) -> std::optional if(delay_dim == "M,R"sv) return DelayType::M_R; - fprintf(stderr, "Unsupported delay dimensions: %s\n", delay_dim); + fmt::println(stderr, "Unsupported delay dimensions: {}", delay_dim); return std::nullopt; } @@ -210,24 +209,24 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf) { if(ir_dim) { - fprintf(stderr, "Duplicate IR.DIMENSION_LIST\n"); + fmt::println(stderr, "Duplicate IR.DIMENSION_LIST"); return false; } ir_dim = ir_attrs->value; } else - fprintf(stderr, "Unexpected IR attribute: %s = %s\n", ir_attrs->name, + fmt::println(stderr, "Unexpected IR attribute: {} = {}", ir_attrs->name, ir_attrs->value ? ir_attrs->value : ""); ir_attrs = ir_attrs->next; } if(!ir_dim) { - fprintf(stderr, "Missing IR dimensions\n"); + fmt::println(stderr, "Missing IR dimensions"); return false; } if(ir_dim != "M,R,N"sv) { - fprintf(stderr, "Unsupported IR dimensions: %s\n", ir_dim); + fmt::println(stderr, "Unsupported IR dimensions: {}", ir_dim); return false; } return true; @@ -315,7 +314,7 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela HrirAzT &azd = field->mEvs[ei].mAzs[ai]; if(!azd.mIrs[0].empty()) { - fprintf(stderr, "\nMultiple measurements near [ a=%f, e=%f, r=%f ].\n", + fmt::println(stderr, "\nMultiple measurements near [ a={:f}, e={:f}, r={:f} ].", aer[0], aer[1], aer[2]); return false; } @@ -367,10 +366,10 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela auto load_future = std::async(std::launch::async, load_proc); do { load_status = load_future.wait_for(std::chrono::milliseconds{50}); - printf("\rLoading HRIRs... %u of %u", loaded_count.load(), sofaHrtf->M); + fmt::print("\rLoading HRIRs... {} of {}", loaded_count.load(), sofaHrtf->M); fflush(stdout); } while(load_status != std::future_status::ready); - fputc('\n', stdout); + fmt::println(""); return load_future.get(); } @@ -382,10 +381,13 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela struct MagCalculator { const uint mFftSize{}; const uint mIrPoints{}; - std::vector> mIrs{}; + std::vector> mIrs; std::atomic mCurrent{}; std::atomic mDone{}; + MagCalculator(const uint fftsize, const uint irpoints) : mFftSize{fftsize}, mIrPoints{irpoints} + { } + void Worker() { auto htemp = std::vector(mFftSize); @@ -422,28 +424,27 @@ bool LoadSofaFile(const std::string_view filename, const uint numThreads, const MySofaHrtfPtr sofaHrtf{mysofa_load(std::string{filename}.c_str(), &err)}; if(!sofaHrtf) { - fprintf(stdout, "Error: Could not load %.*s: %s\n", al::sizei(filename), filename.data(), - SofaErrorStr(err)); + fmt::println("Error: Could not load {}: {} ({})", filename, SofaErrorStr(err), err); return false; } /* NOTE: Some valid SOFA files are failing this check. */ err = mysofa_check(sofaHrtf.get()); if(err != MYSOFA_OK) - fprintf(stderr, "Warning: Supposedly malformed source file '%.*s' (%s).\n", - al::sizei(filename), filename.data(), SofaErrorStr(err)); + fmt::println(stderr, "Warning: Supposedly malformed source file '{}': {} ({})", filename, + SofaErrorStr(err), err); mysofa_tocartesian(sofaHrtf.get()); /* Make sure emitter and receiver counts are sane. */ if(sofaHrtf->E != 1) { - fprintf(stderr, "%u emitters not supported\n", sofaHrtf->E); + fmt::println(stderr, "{} emitters not supported", sofaHrtf->E); return false; } if(sofaHrtf->R > 2 || sofaHrtf->R < 1) { - fprintf(stderr, "%u receivers not supported\n", sofaHrtf->R); + fmt::println(stderr, "{} receivers not supported", sofaHrtf->R); return false; } /* Assume R=2 is a stereo measurement, and R=1 is mono left-ear-only. */ @@ -455,12 +456,14 @@ bool LoadSofaFile(const std::string_view filename, const uint numThreads, const /* Check and set the FFT and IR size. */ if(sofaHrtf->N > fftSize) { - fprintf(stderr, "Sample points exceeds the FFT size.\n"); + fmt::println(stderr, "Sample points exceeds the FFT size ({} > {}).", sofaHrtf->N, + fftSize); return false; } if(sofaHrtf->N < truncSize) { - fprintf(stderr, "Sample points is below the truncation size.\n"); + fmt::println(stderr, "Sample points is below the truncation size ({} < {}).", sofaHrtf->N, + truncSize); return false; } hData->mIrPoints = sofaHrtf->N; @@ -502,7 +505,7 @@ bool LoadSofaFile(const std::string_view filename, const uint numThreads, const } if(ei >= hData->mFds[fi].mEvs.size()) { - fprintf(stderr, "Missing source references [ %d, *, * ].\n", fi); + fmt::println(stderr, "Missing source references [ {}, *, * ].", fi); return false; } hData->mFds[fi].mEvStart = ei; @@ -513,7 +516,7 @@ bool LoadSofaFile(const std::string_view filename, const uint numThreads, const HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; if(azd.mIrs[0].empty()) { - fprintf(stderr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai); + fmt::println(stderr, "Missing source reference [ {}, {}, {} ].", fi, ei, ai); return false; } } @@ -572,10 +575,10 @@ bool LoadSofaFile(const std::string_view filename, const uint numThreads, const auto load_future = std::async(std::launch::async, onset_proc); do { load_status = load_future.wait_for(std::chrono::milliseconds{50}); - printf("\rCalculating HRIR onsets... %zu of %zu", hrir_done.load(), hrir_total); + fmt::print("\rCalculating HRIR onsets... {} of {}", hrir_done.load(), hrir_total); fflush(stdout); } while(load_status != std::future_status::ready); - fputc('\n', stdout); + fmt::println(""); if(!load_future.get()) return false; @@ -595,16 +598,16 @@ bool LoadSofaFile(const std::string_view filename, const uint numThreads, const std::vector thrds; thrds.reserve(numThreads); for(size_t i{0};i < numThreads;++i) - thrds.emplace_back(std::mem_fn(&MagCalculator::Worker), &calculator); + thrds.emplace_back(&MagCalculator::Worker, &calculator); size_t count; do { std::this_thread::sleep_for(std::chrono::milliseconds{50}); count = calculator.mDone.load(); - printf("\rCalculating HRIR magnitudes... %zu of %zu", count, calculator.mIrs.size()); + fmt::print("\rCalculating HRIR magnitudes... {} of {}", count, calculator.mIrs.size()); fflush(stdout); } while(count != calculator.mIrs.size()); - fputc('\n', stdout); + fmt::println(""); for(auto &thrd : thrds) { diff --git a/Engine/lib/openal-soft/utils/makemhr/makemhr.cpp b/Engine/lib/openal-soft/utils/makemhr/makemhr.cpp index b6bafba93..f51c574cc 100644 --- a/Engine/lib/openal-soft/utils/makemhr/makemhr.cpp +++ b/Engine/lib/openal-soft/utils/makemhr/makemhr.cpp @@ -73,7 +73,6 @@ #include #include #include -#include #include #include #include @@ -82,7 +81,6 @@ #include #include #include -#include #include #include "alcomplex.h" @@ -90,6 +88,8 @@ #include "alnumeric.h" #include "alspan.h" #include "alstring.h" +#include "filesystem.h" +#include "fmt/core.h" #include "loaddef.h" #include "loadsofa.h" @@ -283,8 +283,7 @@ auto WriteAscii(const std::string_view out, std::ostream &ostream, const std::st { if(!ostream.write(out.data(), std::streamsize(out.size())) || ostream.bad()) { - fprintf(stderr, "\nError: Bad write to file '%.*s'.\n", al::sizei(filename), - filename.data()); + fmt::println(stderr, "\nError: Bad write to file '{}'.", filename); return 0; } return 1; @@ -301,8 +300,7 @@ auto WriteBin4(const uint bytes, const uint32_t in, std::ostream &ostream, if(!ostream.write(out.data(), std::streamsize(bytes)) || ostream.bad()) { - fprintf(stderr, "\nError: Bad write to file '%.*s'.\n", al::sizei(filename), - filename.data()); + fmt::println(stderr, "\nError: Bad write to file '{}'.", filename); return 0; } return 1; @@ -315,11 +313,10 @@ auto StoreMhr(const HrirDataT *hData, const std::string_view filename) -> bool const uint n{hData->mIrPoints}; uint dither_seed{22222}; - std::ofstream ostream{std::filesystem::u8path(filename)}; + auto ostream = fs::ofstream{fs::u8path(filename), std::ios::binary}; if(!ostream.is_open()) { - fprintf(stderr, "\nError: Could not open MHR file '%.*s'.\n", al::sizei(filename), - filename.data()); + fmt::println(stderr, "\nError: Could not open MHR file '{}'.", filename); return false; } if(!WriteAscii(GetMHRMarker(), ostream, filename)) @@ -913,7 +910,7 @@ void ReconstructHrirs(const HrirDataT *hData, const uint numThreads) std::vector thrds; thrds.reserve(numThreads); for(size_t i{0};i < numThreads;++i) - thrds.emplace_back(std::mem_fn(&HrirReconstructor::Worker), &reconstructor); + thrds.emplace_back(&HrirReconstructor::Worker, &reconstructor); /* Keep track of the number of IRs done, periodically reporting it. */ size_t count; @@ -923,10 +920,10 @@ void ReconstructHrirs(const HrirDataT *hData, const uint numThreads) count = reconstructor.mDone.load(); size_t pcdone{count * 100 / reconstructor.mIrs.size()}; - printf("\r%3zu%% done (%zu of %zu)", pcdone, count, reconstructor.mIrs.size()); + fmt::print("\r{:3}% done ({} of {})", pcdone, count, reconstructor.mIrs.size()); fflush(stdout); } while(count < reconstructor.mIrs.size()); - fputc('\n', stdout); + fmt::println(""); for(auto &thrd : thrds) { @@ -1076,7 +1073,7 @@ void CalculateHrtds(const HeadModelT model, const double radius, HrirDataT *hDat } if(maxHrtd > MaxHrtd) { - fprintf(stdout, " Scaling for max delay of %f samples to %f\n...\n", maxHrtd, MaxHrtd); + fmt::println(" Scaling for max delay of {:f} samples to {:f}\n...", maxHrtd, MaxHrtd); const double scale{MaxHrtd / maxHrtd}; for(auto &field : hData->mFds) { @@ -1158,22 +1155,20 @@ bool ProcessDefinition(std::string_view inName, const uint outRate, const Channe { HrirDataT hData; - fprintf(stdout, "Using %u thread%s.\n", numThreads, (numThreads==1)?"":"s"); + fmt::println("Using {} thread{}.", numThreads, (numThreads==1)?"":"s"); if(inName.empty() || inName == "-"sv) { inName = "stdin"sv; - fprintf(stdout, "Reading HRIR definition from %.*s...\n", al::sizei(inName), - inName.data()); + fmt::println("Reading HRIR definition from {}...", inName); if(!LoadDefInput(std::cin, {}, inName, fftSize, truncSize, outRate, chanMode, &hData)) return false; } else { - auto input = std::make_unique(std::filesystem::u8path(inName)); + auto input = std::make_unique(fs::u8path(inName)); if(!input->is_open()) { - fprintf(stderr, "Error: Could not open input file '%.*s'\n", al::sizei(inName), - inName.data()); + fmt::println(stderr, "Error: Could not open input file '{}'", inName); return false; } @@ -1181,8 +1176,7 @@ bool ProcessDefinition(std::string_view inName, const uint outRate, const Channe input->read(startbytes.data(), startbytes.size()); if(input->gcount() != startbytes.size() || !input->good()) { - fprintf(stderr, "Error: Could not read input file '%.*s'\n", al::sizei(inName), - inName.data()); + fmt::println(stderr, "Error: Could not read input file '{}'", inName); return false; } @@ -1190,15 +1184,13 @@ bool ProcessDefinition(std::string_view inName, const uint outRate, const Channe && startbytes[3] == 'F') { input = nullptr; - fprintf(stdout, "Reading HRTF data from %.*s...\n", al::sizei(inName), - inName.data()); + fmt::println("Reading HRTF data from {}...", inName); if(!LoadSofaFile(inName, numThreads, fftSize, truncSize, outRate, chanMode, &hData)) return false; } else { - fprintf(stdout, "Reading HRIR definition from %.*s...\n", al::sizei(inName), - inName.data()); + fmt::println("Reading HRIR definition from {}...", inName); if(!LoadDefInput(*input, startbytes, inName, fftSize, truncSize, outRate, chanMode, &hData)) return false; @@ -1213,69 +1205,69 @@ bool ProcessDefinition(std::string_view inName, const uint outRate, const Channe if(hData.mFds.size() > 1) { - fprintf(stdout, "Balancing field magnitudes...\n"); + fmt::println("Balancing field magnitudes..."); BalanceFieldMagnitudes(&hData, c, m); } - fprintf(stdout, "Calculating diffuse-field average...\n"); + fmt::println("Calculating diffuse-field average..."); CalculateDiffuseFieldAverage(&hData, c, m, surface, limit, dfa); - fprintf(stdout, "Performing diffuse-field equalization...\n"); + fmt::println("Performing diffuse-field equalization..."); DiffuseFieldEqualize(c, m, dfa, &hData); } if(hData.mFds.size() > 1) { - fprintf(stdout, "Sorting %zu fields...\n", hData.mFds.size()); + fmt::println("Sorting {} fields...", hData.mFds.size()); std::sort(hData.mFds.begin(), hData.mFds.end(), [](const HrirFdT &lhs, const HrirFdT &rhs) noexcept { return lhs.mDistance < rhs.mDistance; }); if(farfield) { - fprintf(stdout, "Clearing %zu near field%s...\n", hData.mFds.size()-1, + fmt::println("Clearing {} near field{}...", hData.mFds.size()-1, (hData.mFds.size()-1 != 1) ? "s" : ""); hData.mFds.erase(hData.mFds.cbegin(), hData.mFds.cend()-1); } } - fprintf(stdout, "Synthesizing missing elevations...\n"); + fmt::println("Synthesizing missing elevations..."); if(model == HM_Dataset) SynthesizeOnsets(&hData); SynthesizeHrirs(&hData); - fprintf(stdout, "Performing minimum phase reconstruction...\n"); + fmt::println("Performing minimum phase reconstruction..."); ReconstructHrirs(&hData, numThreads); - fprintf(stdout, "Truncating minimum-phase HRIRs...\n"); + fmt::println("Truncating minimum-phase HRIRs..."); hData.mIrPoints = truncSize; - fprintf(stdout, "Normalizing final HRIRs...\n"); + fmt::println("Normalizing final HRIRs..."); NormalizeHrirs(&hData); - fprintf(stdout, "Calculating impulse delays...\n"); + fmt::println("Calculating impulse delays..."); CalculateHrtds(model, (radius > DefaultCustomRadius) ? radius : hData.mRadius, &hData); const auto rateStr = std::to_string(hData.mIrRate); const auto expName = StrSubst(outName, "%r"sv, rateStr); - fprintf(stdout, "Creating MHR data set %s...\n", expName.c_str()); + fmt::println("Creating MHR data set {}...", expName); return StoreMhr(&hData, expName); } void PrintHelp(const std::string_view argv0, FILE *ofile) { - fprintf(ofile, "Usage: %.*s [