diff --git a/CMakeLists.txt b/CMakeLists.txt index ad05468..4cd3c73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,8 +77,26 @@ ExternalProject_Add(SDL2 "-DCMAKE_INSTALL_PREFIX=${MODULE_INSTALL_DIR}" "-DSDL_SHARED=OFF" "-DSDL_STATIC=ON" - "-SDL_LIBC=ON" + "-DSDL_LIBC=ON" "-DSDL_FORCE_STATIC_VCRT=ON" + "-DSDL_ATOMIC=OFF" + "-DSDL_AUDIO=OFF" + "-DSDL_VIDEO=ON" + "-DSDL_RENDER=OFF" + "-DSDL_EVENTS=ON" + "-DSDL_JOYSTICK=ON" + "-DSDL_HAPTIC=OFF" + "-DSDL_HIDAPI=ON" + "-DSDL_POWER=OFF" + "-DSDL_THREADS=OFF" + "-DSDL_TIMERS=ON" + "-DSDL_FILE=OFF" + "-DSDL_LOADSO=ON" + "-DSDL_CPUINFO=OFF" + "-DSDL_FILESYSTEM=OFF" + "-DSDL_SENSOR=OFF" + "-DSDL_LOCALE=OFF" + "-DSDL_MISC=OFF" BUILD_ALWAYS 0) # Build OpenAL Soft module @@ -93,7 +111,6 @@ ExternalProject_Add(openal-soft "-DFORCE_STATIC_VCRT=ON" "-DALSOFT_UTILS=OFF" "-DALSOFT_EXAMPLES=OFF" - "-DALSOFT_TESTS=OFF" BUILD_ALWAYS 0) # Build EnTT module diff --git a/modules/openal-soft/.github/workflows/ci.yml b/modules/openal-soft/.github/workflows/ci.yml new file mode 100644 index 0000000..7f044ff --- /dev/null +++ b/modules/openal-soft/.github/workflows/ci.yml @@ -0,0 +1,66 @@ +name: CI + +on: [push] + +jobs: + build: + name: ${{matrix.config.name}} + runs-on: ${{matrix.config.os}} + strategy: + fail-fast: false + matrix: + config: + - { + name: "Visual Studio 64-bit", + os: windows-latest, + cmake_opts: "-A x64 \ + -DALSOFT_BUILD_ROUTER=ON \ + -DALSOFT_REQUIRE_WINMM=ON \ + -DALSOFT_REQUIRE_DSOUND=ON \ + -DALSOFT_REQUIRE_WASAPI=ON", + build_type: "Release" + } + - { + name: "macOS", + os: macos-latest, + cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON", + build_type: "Release" + } + - { + name: "Linux", + os: ubuntu-latest, + cmake_opts: "-DALSOFT_REQUIRE_RTKIT=ON \ + -DALSOFT_REQUIRE_ALSA=ON \ + -DALSOFT_REQUIRE_OSS=ON \ + -DALSOFT_REQUIRE_PORTAUDIO=ON \ + -DALSOFT_REQUIRE_PULSEAUDIO=ON \ + -DALSOFT_REQUIRE_JACK=ON", + deps_cmdline: "sudo apt update && sudo apt-get install -qq \ + libpulse-dev \ + portaudio19-dev \ + libasound2-dev \ + libjack-dev \ + qtbase5-dev \ + libdbus-1-dev", + build_type: "Release" + } + + steps: + - uses: actions/checkout@v1 + + - name: Install Dependencies + shell: bash + run: | + if [[ ! -z "${{matrix.config.deps_cmdline}}" ]]; then + eval ${{matrix.config.deps_cmdline}} + fi + + - name: Configure + shell: bash + run: | + cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} ${{matrix.config.cmake_opts}} . + + - name: Build + shell: bash + run: | + cmake --build build --config ${{matrix.config.build_type}} diff --git a/modules/openal-soft/.gitignore b/modules/openal-soft/.gitignore index de57ef2..4a8212b 100644 --- a/modules/openal-soft/.gitignore +++ b/modules/openal-soft/.gitignore @@ -1,5 +1,9 @@ build*/ -winbuild/ -win64build/ -openal-soft.kdev4 -.kdev4/ +winbuild +win64build + +## kdevelop +*.kdev4 + +## qt-creator +CMakeLists.txt.user* diff --git a/modules/openal-soft/.travis.yml b/modules/openal-soft/.travis.yml index e24ed7d..f85da59 100644 --- a/modules/openal-soft/.travis.yml +++ b/modules/openal-soft/.travis.yml @@ -1,4 +1,4 @@ -language: c +language: cpp matrix: include: - os: linux @@ -7,7 +7,13 @@ matrix: dist: trusty env: - BUILD_ANDROID=true + - os: freebsd + compiler: clang - os: osx + - os: osx + osx_image: xcode11 + env: + - BUILD_IOS=true sudo: required install: - > @@ -20,24 +26,49 @@ install: portaudio19-dev \ libasound2-dev \ libjack-dev \ - qtbase5-dev + qtbase5-dev \ + libdbus-1-dev fi - > if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then - curl -o ~/android-ndk.zip https://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip + curl -o ~/android-ndk.zip https://dl.google.com/android/repository/android-ndk-r21-linux-x86_64.zip unzip -q ~/android-ndk.zip -d ~ \ - 'android-ndk-r16b/build/cmake/*' \ - 'android-ndk-r16b/build/core/toolchains/arm-linux-androideabi-*/*' \ - 'android-ndk-r16b/platforms/android-14/arch-arm/*' \ - 'android-ndk-r16b/source.properties' \ - 'android-ndk-r16b/sources/android/support/include/*' \ - 'android-ndk-r16b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/*' \ - 'android-ndk-r16b/sources/cxx-stl/llvm-libc++/include/*' \ - 'android-ndk-r16b/sysroot/*' \ - 'android-ndk-r16b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/*' \ - 'android-ndk-r16b/toolchains/llvm/prebuilt/linux-x86_64/*' + 'android-ndk-r21/build/cmake/*' \ + 'android-ndk-r21/build/core/toolchains/arm-linux-androideabi-*/*' \ + 'android-ndk-r21/platforms/android-16/arch-arm/*' \ + 'android-ndk-r21/source.properties' \ + 'android-ndk-r21/sources/android/support/include/*' \ + 'android-ndk-r21/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/*' \ + 'android-ndk-r21/sources/cxx-stl/llvm-libc++/include/*' \ + 'android-ndk-r21/sysroot/*' \ + 'android-ndk-r21/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/*' \ + 'android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/*' + export OBOE_LOC=~/oboe + git clone --depth 1 -b 1.3-stable https://github.com/google/oboe "$OBOE_LOC" + fi + - > + if [[ "${TRAVIS_OS_NAME}" == "freebsd" ]]; then + # Install Ninja as it's used downstream. + # Install dependencies for all supported backends. + # Install Qt5 dependency for alsoft-config. + # Install ffmpeg for examples. + sudo pkg install -y \ + alsa-lib \ + ffmpeg \ + jackit \ + libmysofa \ + ninja \ + portaudio \ + pulseaudio \ + qt5-buildtools \ + qt5-qmake \ + qt5-widgets \ + sdl2 \ + sndio \ + $NULL fi script: + - cmake --version - > if [[ "${TRAVIS_OS_NAME}" == "linux" && -z "${BUILD_ANDROID}" ]]; then cmake \ @@ -53,16 +84,42 @@ script: if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then cmake \ -DANDROID_STL=c++_shared \ - -DCMAKE_TOOLCHAIN_FILE=~/android-ndk-r16b/build/cmake/android.toolchain.cmake \ + -DCMAKE_TOOLCHAIN_FILE=~/android-ndk-r21/build/cmake/android.toolchain.cmake \ + -DOBOE_SOURCE="$OBOE_LOC" \ + -DALSOFT_REQUIRE_OBOE=ON \ -DALSOFT_REQUIRE_OPENSL=ON \ -DALSOFT_EMBED_HRTF_DATA=YES \ . fi - > - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then + if [[ "${TRAVIS_OS_NAME}" == "freebsd" ]]; then + cmake -GNinja \ + -DALSOFT_REQUIRE_ALSA=ON \ + -DALSOFT_REQUIRE_JACK=ON \ + -DALSOFT_REQUIRE_OSS=ON \ + -DALSOFT_REQUIRE_PORTAUDIO=ON \ + -DALSOFT_REQUIRE_PULSEAUDIO=ON \ + -DALSOFT_REQUIRE_SDL2=ON \ + -DALSOFT_REQUIRE_SNDIO=ON \ + -DALSOFT_EMBED_HRTF_DATA=YES \ + . + fi + - > + if [[ "${TRAVIS_OS_NAME}" == "osx" && -z "${BUILD_IOS}" ]]; then + cmake \ + -DALSOFT_REQUIRE_COREAUDIO=ON \ + -DALSOFT_EMBED_HRTF_DATA=YES \ + . + fi + - > + if [[ "${TRAVIS_OS_NAME}" == "osx" && "${BUILD_IOS}" == "true" ]]; then cmake \ + -GXcode \ + -DCMAKE_SYSTEM_NAME=iOS \ + -DALSOFT_OSX_FRAMEWORK=ON \ -DALSOFT_REQUIRE_COREAUDIO=ON \ -DALSOFT_EMBED_HRTF_DATA=YES \ + "-DCMAKE_OSX_ARCHITECTURES=armv7;arm64" \ . fi - - make -j2 + - cmake --build . --clean-first diff --git a/modules/openal-soft/Alc/alc.cpp b/modules/openal-soft/Alc/alc.cpp index 8a340b1..3bbe43d 100644 --- a/modules/openal-soft/Alc/alc.cpp +++ b/modules/openal-soft/Alc/alc.cpp @@ -22,51 +22,93 @@ #include "version.h" -#include -#include -#include -#include -#include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif -#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include +#include +#include +#include #include -#include -#include -#include - -#include "alMain.h" -#include "alcontext.h" -#include "alSource.h" -#include "alListener.h" -#include "alSource.h" -#include "alBuffer.h" -#include "alFilter.h" -#include "alEffect.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "mastering.h" -#include "bformatdec.h" -#include "uhjfilter.h" -#include "alu.h" +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" +#include "AL/efx.h" + +#include "al/auxeffectslot.h" +#include "al/buffer.h" +#include "al/effect.h" +#include "al/filter.h" +#include "al/listener.h" +#include "al/source.h" +#include "albit.h" +#include "albyte.h" #include "alconfig.h" -#include "ringbuffer.h" -#include "filters/splitter.h" -#include "bs2b.h" - -#include "fpu_modes.h" -#include "cpu_caps.h" -#include "compat.h" -#include "threads.h" -#include "alexcpt.h" #include "almalloc.h" +#include "alnumeric.h" +#include "aloptional.h" +#include "alspan.h" +#include "alstring.h" +#include "alu.h" +#include "atomic.h" +#include "context.h" +#include "core/ambidefs.h" +#include "core/bformatdec.h" +#include "core/bs2b.h" +#include "core/context.h" +#include "core/cpu_caps.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/except.h" +#include "core/helpers.h" +#include "core/mastering.h" +#include "core/mixer/hrtfdefs.h" +#include "core/fpu_ctrl.h" +#include "core/front_stablizer.h" +#include "core/logging.h" +#include "core/uhjfilter.h" +#include "core/voice.h" +#include "core/voice_change.h" +#include "device.h" +#include "effects/base.h" +#include "inprogext.h" +#include "intrusive_ptr.h" +#include "opthelpers.h" +#include "strutils.h" +#include "threads.h" +#include "vector.h" #include "backends/base.h" #include "backends/null.h" #include "backends/loopback.h" +#ifdef HAVE_PIPEWIRE +#include "backends/pipewire.h" +#endif #ifdef HAVE_JACK #include "backends/jack.h" #endif @@ -85,6 +127,9 @@ #ifdef HAVE_OPENSL #include "backends/opensl.h" #endif +#ifdef HAVE_OBOE +#include "backends/oboe.h" +#endif #ifdef HAVE_SOLARIS #include "backends/solaris.h" #endif @@ -94,9 +139,6 @@ #ifdef HAVE_OSS #include "backends/oss.h" #endif -#ifdef HAVE_QSA -#include "backends/qsa.h" -#endif #ifdef HAVE_DSOUND #include "backends/dsound.h" #endif @@ -113,6 +155,36 @@ #include "backends/wave.h" #endif +#ifdef ALSOFT_EAX +#include "al/eax_globals.h" +#include "al/eax_x_ram.h" +#endif // ALSOFT_EAX + + +FILE *gLogFile{stderr}; +#ifdef _DEBUG +LogLevel gLogLevel{LogLevel::Warning}; +#else +LogLevel gLogLevel{LogLevel::Error}; +#endif + +/************************************************ + * Library initialization + ************************************************/ +#if defined(_WIN32) && !defined(AL_LIBTYPE_STATIC) +BOOL APIENTRY DllMain(HINSTANCE module, DWORD reason, LPVOID /*reserved*/) +{ + switch(reason) + { + case DLL_PROCESS_ATTACH: + /* Pin the DLL so we won't get unloaded until the process terminates */ + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + reinterpret_cast(module), &module); + break; + } + return TRUE; +} +#endif namespace { @@ -120,6 +192,9 @@ using namespace std::placeholders; using std::chrono::seconds; using std::chrono::nanoseconds; +using voidp = void*; +using float2 = std::array; + /************************************************ * Backends @@ -130,21 +205,21 @@ struct BackendInfo { }; BackendInfo BackendList[] = { -#ifdef HAVE_JACK - { "jack", JackBackendFactory::getFactory }, +#ifdef HAVE_PIPEWIRE + { "pipewire", PipeWireBackendFactory::getFactory }, #endif #ifdef HAVE_PULSEAUDIO { "pulse", PulseBackendFactory::getFactory }, #endif -#ifdef HAVE_ALSA - { "alsa", AlsaBackendFactory::getFactory }, -#endif #ifdef HAVE_WASAPI { "wasapi", WasapiBackendFactory::getFactory }, #endif #ifdef HAVE_COREAUDIO { "core", CoreAudioBackendFactory::getFactory }, #endif +#ifdef HAVE_OBOE + { "oboe", OboeBackendFactory::getFactory }, +#endif #ifdef HAVE_OPENSL { "opensl", OSLBackendFactory::getFactory }, #endif @@ -154,11 +229,14 @@ BackendInfo BackendList[] = { #ifdef HAVE_SNDIO { "sndio", SndIOBackendFactory::getFactory }, #endif +#ifdef HAVE_ALSA + { "alsa", AlsaBackendFactory::getFactory }, +#endif #ifdef HAVE_OSS { "oss", OSSBackendFactory::getFactory }, #endif -#ifdef HAVE_QSA - { "qsa", QSABackendFactory::getFactory }, +#ifdef HAVE_JACK + { "jack", JackBackendFactory::getFactory }, #endif #ifdef HAVE_DSOUND { "dsound", DSoundBackendFactory::getFactory }, @@ -178,19 +256,18 @@ BackendInfo BackendList[] = { { "wave", WaveBackendFactory::getFactory }, #endif }; -auto BackendListEnd = std::end(BackendList); -BackendInfo PlaybackBackend; -BackendInfo CaptureBackend; +BackendFactory *PlaybackFactory{}; +BackendFactory *CaptureFactory{}; /************************************************ * Functions, enums, and errors ************************************************/ -#define DECL(x) { #x, (ALCvoid*)(x) } -constexpr struct { - const ALCchar *funcName; - ALCvoid *address; +#define DECL(x) { #x, reinterpret_cast(x) } +const struct { + const char *funcName; + void *address; } alcFunctions[] = { DECL(alcCreateContext), DECL(alcMakeContextCurrent), @@ -228,6 +305,8 @@ constexpr struct { DECL(alcGetInteger64vSOFT), + DECL(alcReopenDeviceSOFT), + DECL(alEnable), DECL(alDisable), DECL(alIsEnabled), @@ -363,6 +442,23 @@ constexpr struct { DECL(alEventCallbackSOFT), DECL(alGetPointerSOFT), DECL(alGetPointervSOFT), + + DECL(alBufferCallbackSOFT), + DECL(alGetBufferPtrSOFT), + DECL(alGetBuffer3PtrSOFT), + DECL(alGetBufferPtrvSOFT), + + DECL(alAuxiliaryEffectSlotPlaySOFT), + DECL(alAuxiliaryEffectSlotPlayvSOFT), + DECL(alAuxiliaryEffectSlotStopSOFT), + DECL(alAuxiliaryEffectSlotStopvSOFT), +#ifdef ALSOFT_EAX +}, eaxFunctions[] = { + DECL(EAXGet), + DECL(EAXSet), + DECL(EAXGetBufferMode), + DECL(EAXSetBufferMode), +#endif }; #undef DECL @@ -440,6 +536,15 @@ constexpr struct { DECL(ALC_OUTPUT_LIMITER_SOFT), + DECL(ALC_OUTPUT_MODE_SOFT), + DECL(ALC_ANY_SOFT), + DECL(ALC_STEREO_BASIC_SOFT), + DECL(ALC_STEREO_UHJ_SOFT), + DECL(ALC_STEREO_HRTF_SOFT), + DECL(ALC_SURROUND_5_1_SOFT), + DECL(ALC_SURROUND_6_1_SOFT), + DECL(ALC_SURROUND_7_1_SOFT), + DECL(ALC_NO_ERROR), DECL(ALC_INVALID_DEVICE), DECL(ALC_INVALID_CONTEXT), @@ -617,9 +722,7 @@ constexpr struct { DECL(AL_EFFECT_FLANGER), DECL(AL_EFFECT_PITCH_SHIFTER), DECL(AL_EFFECT_FREQUENCY_SHIFTER), -#if 0 DECL(AL_EFFECT_VOCAL_MORPHER), -#endif DECL(AL_EFFECT_RING_MODULATOR), DECL(AL_EFFECT_AUTOWAH), DECL(AL_EFFECT_COMPRESSOR), @@ -727,6 +830,15 @@ constexpr struct { DECL(AL_AUTOWAH_RESONANCE), DECL(AL_AUTOWAH_PEAK_GAIN), + DECL(AL_VOCAL_MORPHER_PHONEMEA), + DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING), + DECL(AL_VOCAL_MORPHER_PHONEMEB), + DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING), + DECL(AL_VOCAL_MORPHER_WAVEFORM), + DECL(AL_VOCAL_MORPHER_RATE), + + DECL(AL_EFFECTSLOT_TARGET_SOFT), + DECL(AL_NUM_RESAMPLERS_SOFT), DECL(AL_DEFAULT_RESAMPLER_SOFT), DECL(AL_SOURCE_RESAMPLER_SOFT), @@ -744,9 +856,50 @@ constexpr struct { DECL(AL_EVENT_CALLBACK_USER_PARAM_SOFT), DECL(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT), DECL(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT), - DECL(AL_EVENT_TYPE_ERROR_SOFT), - DECL(AL_EVENT_TYPE_PERFORMANCE_SOFT), - DECL(AL_EVENT_TYPE_DEPRECATED_SOFT), + DECL(AL_EVENT_TYPE_DISCONNECTED_SOFT), + + DECL(AL_DROP_UNMATCHED_SOFT), + DECL(AL_REMIX_UNMATCHED_SOFT), + + DECL(AL_AMBISONIC_LAYOUT_SOFT), + DECL(AL_AMBISONIC_SCALING_SOFT), + DECL(AL_FUMA_SOFT), + DECL(AL_ACN_SOFT), + DECL(AL_SN3D_SOFT), + DECL(AL_N3D_SOFT), + + DECL(AL_BUFFER_CALLBACK_FUNCTION_SOFT), + DECL(AL_BUFFER_CALLBACK_USER_PARAM_SOFT), + + DECL(AL_UNPACK_AMBISONIC_ORDER_SOFT), + + DECL(AL_EFFECT_CONVOLUTION_REVERB_SOFT), + DECL(AL_EFFECTSLOT_STATE_SOFT), + + DECL(AL_FORMAT_UHJ2CHN8_SOFT), + DECL(AL_FORMAT_UHJ2CHN16_SOFT), + DECL(AL_FORMAT_UHJ2CHN_FLOAT32_SOFT), + DECL(AL_FORMAT_UHJ3CHN8_SOFT), + DECL(AL_FORMAT_UHJ3CHN16_SOFT), + DECL(AL_FORMAT_UHJ3CHN_FLOAT32_SOFT), + DECL(AL_FORMAT_UHJ4CHN8_SOFT), + DECL(AL_FORMAT_UHJ4CHN16_SOFT), + DECL(AL_FORMAT_UHJ4CHN_FLOAT32_SOFT), + DECL(AL_STEREO_MODE_SOFT), + DECL(AL_NORMAL_SOFT), + DECL(AL_SUPER_STEREO_SOFT), + DECL(AL_SUPER_STEREO_WIDTH_SOFT), + + DECL(AL_STOP_SOURCES_ON_DISCONNECT_SOFT), + +#ifdef ALSOFT_EAX +}, eaxEnumerations[] = { + DECL(AL_EAX_RAM_SIZE), + DECL(AL_EAX_RAM_FREE), + DECL(AL_STORAGE_AUTOMATIC), + DECL(AL_STORAGE_HARDWARE), + DECL(AL_STORAGE_ACCESSIBLE), +#endif // ALSOFT_EAX }; #undef DECL @@ -772,83 +925,22 @@ std::string alcCaptureDeviceList; std::string alcDefaultAllDevicesSpecifier; std::string alcCaptureDefaultDeviceSpecifier; -/* Default context extensions */ -constexpr ALchar alExtList[] = - "AL_EXT_ALAW " - "AL_EXT_BFORMAT " - "AL_EXT_DOUBLE " - "AL_EXT_EXPONENT_DISTANCE " - "AL_EXT_FLOAT32 " - "AL_EXT_IMA4 " - "AL_EXT_LINEAR_DISTANCE " - "AL_EXT_MCFORMATS " - "AL_EXT_MULAW " - "AL_EXT_MULAW_BFORMAT " - "AL_EXT_MULAW_MCFORMATS " - "AL_EXT_OFFSET " - "AL_EXT_source_distance_model " - "AL_EXT_SOURCE_RADIUS " - "AL_EXT_STEREO_ANGLES " - "AL_LOKI_quadriphonic " - "AL_SOFT_block_alignment " - "AL_SOFT_deferred_updates " - "AL_SOFT_direct_channels " - "AL_SOFTX_effect_chain " - "AL_SOFTX_events " - "AL_SOFTX_filter_gain_ex " - "AL_SOFT_gain_clamp_ex " - "AL_SOFT_loop_points " - "AL_SOFTX_map_buffer " - "AL_SOFT_MSADPCM " - "AL_SOFT_source_latency " - "AL_SOFT_source_length " - "AL_SOFT_source_resampler " - "AL_SOFT_source_spatialize"; - std::atomic LastNullDeviceError{ALC_NO_ERROR}; -/* Thread-local current context */ -void ReleaseThreadCtx(ALCcontext *context) -{ - auto ref = DecrementRef(&context->ref); - TRACEREF("%p decreasing refcount to %u\n", context, ref); - ERR("Context %p current for thread being destroyed, possible leak!\n", context); -} - -std::atomic ThreadCtxProc{ReleaseThreadCtx}; -class ThreadCtx { - ALCcontext *ctx{nullptr}; - -public: - ~ThreadCtx() - { - auto destruct = ThreadCtxProc.load(); - if(destruct && ctx) - destruct(ctx); - ctx = nullptr; - } - - ALCcontext *get() const noexcept { return ctx; } - void set(ALCcontext *ctx_) noexcept { ctx = ctx_; } -}; -thread_local ThreadCtx LocalContext; -/* Process-wide current context */ -std::atomic GlobalContext{nullptr}; - /* Flag to trap ALC device errors */ bool TrapALCError{false}; /* One-time configuration init control */ std::once_flag alc_config_once{}; -/* Default effect that applies to sources that don't have an effect on send 0 */ -ALeffect DefaultEffect; - /* Flag to specify if alcSuspendContext/alcProcessContext should defer/process * updates. */ bool SuspendDefers{true}; +/* Initial seed for dithering. */ +constexpr uint DitherRNGSeed{22222u}; + /************************************************ * ALC information @@ -857,8 +949,11 @@ constexpr ALCchar alcNoDeviceExtList[] = "ALC_ENUMERATE_ALL_EXT " "ALC_ENUMERATION_EXT " "ALC_EXT_CAPTURE " + "ALC_EXT_EFX " "ALC_EXT_thread_local_context " - "ALC_SOFT_loopback"; + "ALC_SOFT_loopback " + "ALC_SOFT_loopback_bformat " + "ALC_SOFT_reopen_device"; constexpr ALCchar alcExtensionList[] = "ALC_ENUMERATE_ALL_EXT " "ALC_ENUMERATION_EXT " @@ -870,13 +965,19 @@ constexpr ALCchar alcExtensionList[] = "ALC_SOFT_device_clock " "ALC_SOFT_HRTF " "ALC_SOFT_loopback " + "ALC_SOFT_loopback_bformat " "ALC_SOFT_output_limiter " - "ALC_SOFT_pause_device"; -constexpr ALCint alcMajorVersion = 1; -constexpr ALCint alcMinorVersion = 1; + "ALC_SOFT_output_mode " + "ALC_SOFT_pause_device " + "ALC_SOFT_reopen_device"; +constexpr int alcMajorVersion{1}; +constexpr int alcMinorVersion{1}; + +constexpr int alcEFXMajorVersion{1}; +constexpr int alcEFXMinorVersion{0}; -constexpr ALCint alcEFXMajorVersion = 1; -constexpr ALCint alcEFXMinorVersion = 0; + +using DeviceRef = al::intrusive_ptr; /************************************************ @@ -887,87 +988,69 @@ al::vector ContextList; std::recursive_mutex ListLock; -} // namespace - -/* Mixing thread piority level */ -ALint RTPrioLevel; - -FILE *gLogFile{stderr}; -#ifdef _DEBUG -LogLevel gLogLevel{LogWarning}; -#else -LogLevel gLogLevel{LogError}; -#endif - -/************************************************ - * Library initialization - ************************************************/ -#if defined(_WIN32) && !defined(AL_LIBTYPE_STATIC) -BOOL APIENTRY DllMain(HINSTANCE module, DWORD reason, LPVOID /*reserved*/) -{ - switch(reason) - { - case DLL_PROCESS_ATTACH: - /* Pin the DLL so we won't get unloaded until the process terminates */ - GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, - (WCHAR*)module, &module); - break; - - case DLL_PROCESS_DETACH: - break; - } - return TRUE; -} -#endif -static void alc_initconfig(void) +void alc_initconfig(void) { - const char *str{getenv("ALSOFT_LOGLEVEL")}; - if(str) + if(auto loglevel = al::getenv("ALSOFT_LOGLEVEL")) { - long lvl = strtol(str, nullptr, 0); - if(lvl >= NoLog && lvl <= LogRef) + long lvl = strtol(loglevel->c_str(), nullptr, 0); + if(lvl >= static_cast(LogLevel::Trace)) + gLogLevel = LogLevel::Trace; + else if(lvl <= static_cast(LogLevel::Disable)) + gLogLevel = LogLevel::Disable; + else gLogLevel = static_cast(lvl); } - str = getenv("ALSOFT_LOGFILE"); - if(str && str[0]) - { #ifdef _WIN32 - std::wstring wname{utf8_to_wstr(str)}; - FILE *logfile = _wfopen(wname.c_str(), L"wt"); + if(const auto logfile = al::getenv(L"ALSOFT_LOGFILE")) + { + FILE *logf{_wfopen(logfile->c_str(), L"wt")}; + if(logf) gLogFile = logf; + else + { + auto u8name = wstr_to_utf8(logfile->c_str()); + ERR("Failed to open log file '%s'\n", u8name.c_str()); + } + } #else - FILE *logfile = fopen(str, "wt"); -#endif - if(logfile) gLogFile = logfile; - else ERR("Failed to open log file '%s'\n", str); + if(const auto logfile = al::getenv("ALSOFT_LOGFILE")) + { + FILE *logf{fopen(logfile->c_str(), "wt")}; + if(logf) gLogFile = logf; + else ERR("Failed to open log file '%s'\n", logfile->c_str()); } +#endif - TRACE("Initializing library v%s-%s %s\n", ALSOFT_VERSION, - ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); + TRACE("Initializing library v%s-%s %s\n", ALSOFT_VERSION, ALSOFT_GIT_COMMIT_HASH, + ALSOFT_GIT_BRANCH); { std::string names; - if(std::begin(BackendList) != BackendListEnd) - names += BackendList[0].name; - for(auto backend = std::begin(BackendList)+1;backend != BackendListEnd;++backend) + if(al::size(BackendList) < 1) + names = "(none)"; + else { - names += ", "; - names += backend->name; + const al::span infos{BackendList}; + names = infos[0].name; + for(const auto &backend : infos.subspan<1>()) + { + names += ", "; + names += backend.name; + } } TRACE("Supported backends: %s\n", names.c_str()); } ReadALConfig(); - str = getenv("__ALSOFT_SUSPEND_CONTEXT"); - if(str && *str) + if(auto suspendmode = al::getenv("__ALSOFT_SUSPEND_CONTEXT")) { - if(strcasecmp(str, "ignore") == 0) + if(al::strcasecmp(suspendmode->c_str(), "ignore") == 0) { SuspendDefers = false; TRACE("Selected context suspend behavior, \"ignore\"\n"); } else - ERR("Unhandled context suspend behavior setting: \"%s\"\n", str); + ERR("Unhandled context suspend behavior setting: \"%s\"\n", suspendmode->c_str()); } int capfilter{0}; @@ -983,9 +1066,10 @@ static void alc_initconfig(void) #ifdef HAVE_NEON capfilter |= CPU_CAP_NEON; #endif - if(ConfigValueStr(nullptr, nullptr, "disable-cpu-exts", &str)) + if(auto cpuopt = ConfigValueStr(nullptr, nullptr, "disable-cpu-exts")) { - if(strcasecmp(str, "all") == 0) + const char *str{cpuopt->c_str()}; + if(al::strcasecmp(str, "all") == 0) capfilter = 0; else { @@ -1002,65 +1086,103 @@ static void alc_initconfig(void) size_t len{next ? static_cast(next-str) : strlen(str)}; while(len > 0 && isspace(str[len-1])) len--; - if(len == 3 && strncasecmp(str, "sse", len) == 0) + if(len == 3 && al::strncasecmp(str, "sse", len) == 0) capfilter &= ~CPU_CAP_SSE; - else if(len == 4 && strncasecmp(str, "sse2", len) == 0) + else if(len == 4 && al::strncasecmp(str, "sse2", len) == 0) capfilter &= ~CPU_CAP_SSE2; - else if(len == 4 && strncasecmp(str, "sse3", len) == 0) + else if(len == 4 && al::strncasecmp(str, "sse3", len) == 0) capfilter &= ~CPU_CAP_SSE3; - else if(len == 6 && strncasecmp(str, "sse4.1", len) == 0) + else if(len == 6 && al::strncasecmp(str, "sse4.1", len) == 0) capfilter &= ~CPU_CAP_SSE4_1; - else if(len == 4 && strncasecmp(str, "neon", len) == 0) + else if(len == 4 && al::strncasecmp(str, "neon", len) == 0) capfilter &= ~CPU_CAP_NEON; else WARN("Invalid CPU extension \"%s\"\n", str); } while(next++); } } - FillCPUCaps(capfilter); - -#ifdef _WIN32 - RTPrioLevel = 1; -#else - RTPrioLevel = 0; -#endif - ConfigValueInt(nullptr, nullptr, "rt-prio", &RTPrioLevel); + 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()); + } + 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-" : "")); + CPUCapFlags = caps & capfilter; + } + + if(auto priopt = ConfigValueInt(nullptr, nullptr, "rt-prio")) + RTPrioLevel = *priopt; + if(auto limopt = ConfigValueBool(nullptr, nullptr, "rt-time-limit")) + AllowRTTimeLimit = *limopt; + + CompatFlagBitset compatflags{}; + auto checkflag = [](const char *envname, const char *optname) -> bool + { + if(auto optval = al::getenv(envname)) + { + if(al::strcasecmp(optval->c_str(), "true") == 0 + || strtol(optval->c_str(), nullptr, 0) == 1) + return true; + return false; + } + return GetConfigValueBool(nullptr, "game_compat", optname, false); + }; + compatflags.set(CompatFlags::ReverseX, checkflag("__ALSOFT_REVERSE_X", "reverse-x")); + compatflags.set(CompatFlags::ReverseY, checkflag("__ALSOFT_REVERSE_Y", "reverse-y")); + compatflags.set(CompatFlags::ReverseZ, checkflag("__ALSOFT_REVERSE_Z", "reverse-z")); - aluInit(); - aluInitMixer(); + aluInit(compatflags); + Voice::InitMixer(ConfigValueStr(nullptr, nullptr, "resampler")); - str = getenv("ALSOFT_TRAP_ERROR"); - if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1)) + auto traperr = al::getenv("ALSOFT_TRAP_ERROR"); + if(traperr && (al::strcasecmp(traperr->c_str(), "true") == 0 + || std::strtol(traperr->c_str(), nullptr, 0) == 1)) { - TrapALError = AL_TRUE; + TrapALError = true; TrapALCError = true; } else { - str = getenv("ALSOFT_TRAP_AL_ERROR"); - if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1)) - TrapALError = AL_TRUE; - TrapALError = GetConfigValueBool(nullptr, nullptr, "trap-al-error", TrapALError); - - str = getenv("ALSOFT_TRAP_ALC_ERROR"); - if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1)) - TrapALCError = true; - TrapALCError = !!GetConfigValueBool(nullptr, nullptr, "trap-alc-error", TrapALCError); + traperr = al::getenv("ALSOFT_TRAP_AL_ERROR"); + if(traperr) + TrapALError = al::strcasecmp(traperr->c_str(), "true") == 0 + || strtol(traperr->c_str(), nullptr, 0) == 1; + else + TrapALError = !!GetConfigValueBool(nullptr, nullptr, "trap-al-error", false); + + traperr = al::getenv("ALSOFT_TRAP_ALC_ERROR"); + if(traperr) + TrapALCError = al::strcasecmp(traperr->c_str(), "true") == 0 + || strtol(traperr->c_str(), nullptr, 0) == 1; + else + TrapALCError = !!GetConfigValueBool(nullptr, nullptr, "trap-alc-error", false); } - float valf{}; - if(ConfigValueFloat(nullptr, "reverb", "boost", &valf)) + if(auto boostopt = ConfigValueFloat(nullptr, "reverb", "boost")) + { + const float valf{std::isfinite(*boostopt) ? clampf(*boostopt, -24.0f, 24.0f) : 0.0f}; ReverbBoost *= std::pow(10.0f, valf / 20.0f); + } - const char *devs{getenv("ALSOFT_DRIVERS")}; - if((devs && devs[0]) || ConfigValueStr(nullptr, nullptr, "drivers", &devs)) + auto BackendListEnd = std::end(BackendList); + auto devopt = al::getenv("ALSOFT_DRIVERS"); + if(devopt || (devopt=ConfigValueStr(nullptr, nullptr, "drivers"))) { auto backendlist_cur = std::begin(BackendList); bool endlist{true}; - const char *next = devs; + const char *next{devopt->c_str()}; do { - devs = next; + const char *devs{next}; while(isspace(devs[0])) devs++; next = strchr(devs, ','); @@ -1106,64 +1228,84 @@ static void alc_initconfig(void) BackendListEnd = backendlist_cur; } - auto init_backend = [](BackendInfo &backend) -> bool + auto init_backend = [](BackendInfo &backend) -> void { - if(PlaybackBackend.name && CaptureBackend.name) - return true; + if(PlaybackFactory && CaptureFactory) + return; BackendFactory &factory = backend.getFactory(); if(!factory.init()) { WARN("Failed to initialize backend \"%s\"\n", backend.name); - return true; + return; } TRACE("Initialized backend \"%s\"\n", backend.name); - if(!PlaybackBackend.name && factory.querySupport(BackendType::Playback)) + if(!PlaybackFactory && factory.querySupport(BackendType::Playback)) { - PlaybackBackend = backend; - TRACE("Added \"%s\" for playback\n", PlaybackBackend.name); + PlaybackFactory = &factory; + TRACE("Added \"%s\" for playback\n", backend.name); } - if(!CaptureBackend.name && factory.querySupport(BackendType::Capture)) + if(!CaptureFactory && factory.querySupport(BackendType::Capture)) { - CaptureBackend = backend; - TRACE("Added \"%s\" for capture\n", CaptureBackend.name); + CaptureFactory = &factory; + TRACE("Added \"%s\" for capture\n", backend.name); } - return false; }; - BackendListEnd = std::remove_if(std::begin(BackendList), BackendListEnd, init_backend); + std::for_each(std::begin(BackendList), BackendListEnd, init_backend); LoopbackBackendFactory::getFactory().init(); - if(!PlaybackBackend.name) + if(!PlaybackFactory) WARN("No playback backend available!\n"); - if(!CaptureBackend.name) + if(!CaptureFactory) WARN("No capture backend available!\n"); - if(ConfigValueStr(nullptr, nullptr, "excludefx", &str)) + if(auto exclopt = ConfigValueStr(nullptr, nullptr, "excludefx")) { - const char *next = str; + const char *next{exclopt->c_str()}; do { - str = next; + const char *str{next}; next = strchr(str, ','); if(!str[0] || next == str) continue; size_t len{next ? static_cast(next-str) : strlen(str)}; - for(size_t n{0u};n < countof(gEffectList);n++) + for(const EffectList &effectitem : gEffectList) { - if(len == strlen(gEffectList[n].name) && - strncmp(gEffectList[n].name, str, len) == 0) - DisabledEffects[gEffectList[n].type] = AL_TRUE; + if(len == strlen(effectitem.name) && + strncmp(effectitem.name, str, len) == 0) + DisabledEffects[effectitem.type] = true; } } while(next++); } - InitEffect(&DefaultEffect); - str = getenv("ALSOFT_DEFAULT_REVERB"); - if((str && str[0]) || ConfigValueStr(nullptr, nullptr, "default-reverb", &str)) - LoadReverbPreset(str, &DefaultEffect); + InitEffect(&ALCcontext::sDefaultEffect); + auto defrevopt = al::getenv("ALSOFT_DEFAULT_REVERB"); + if(defrevopt || (defrevopt=ConfigValueStr(nullptr, nullptr, "default-reverb"))) + LoadReverbPreset(defrevopt->c_str(), &ALCcontext::sDefaultEffect); + +#ifdef ALSOFT_EAX + { + static constexpr char eax_block_name[] = "eax"; + + if(const auto eax_enable_opt = ConfigValueBool(nullptr, eax_block_name, "enable")) + { + eax_g_is_enabled = *eax_enable_opt; + if(!eax_g_is_enabled) + TRACE("%s\n", "EAX disabled by a configuration."); + } + else + eax_g_is_enabled = true; + + if(eax_g_is_enabled && DisabledEffects[EAXREVERB_EFFECT]) + { + eax_g_is_enabled = false; + TRACE("%s\n", "EAX disabled because EAXReverb is disabled."); + } + } +#endif // ALSOFT_EAX } #define DO_INITCONFIG() std::call_once(alc_config_once, [](){alc_initconfig();}) @@ -1171,85 +1313,38 @@ static void alc_initconfig(void) /************************************************ * Device enumeration ************************************************/ -static void ProbeDevices(std::string *list, BackendInfo *backendinfo, DevProbe type) +void ProbeAllDevicesList() { DO_INITCONFIG(); std::lock_guard _{ListLock}; - list->clear(); - if(backendinfo->getFactory) - backendinfo->getFactory().probe(type, list); -} -static void ProbeAllDevicesList(void) -{ ProbeDevices(&alcAllDevicesList, &PlaybackBackend, DevProbe::Playback); } -static void ProbeCaptureDeviceList(void) -{ ProbeDevices(&alcCaptureDeviceList, &CaptureBackend, DevProbe::Capture); } - - -/************************************************ - * Device format information - ************************************************/ -const ALCchar *DevFmtTypeString(DevFmtType type) noexcept -{ - switch(type) + if(!PlaybackFactory) + decltype(alcAllDevicesList){}.swap(alcAllDevicesList); + else { - case DevFmtByte: return "Signed Byte"; - case DevFmtUByte: return "Unsigned Byte"; - case DevFmtShort: return "Signed Short"; - case DevFmtUShort: return "Unsigned Short"; - case DevFmtInt: return "Signed Int"; - case DevFmtUInt: return "Unsigned Int"; - case DevFmtFloat: return "Float"; + std::string names{PlaybackFactory->probe(BackendType::Playback)}; + if(names.empty()) names += '\0'; + names.swap(alcAllDevicesList); } - return "(unknown type)"; } -const ALCchar *DevFmtChannelsString(DevFmtChannels chans) noexcept +void ProbeCaptureDeviceList() { - switch(chans) - { - case DevFmtMono: return "Mono"; - case DevFmtStereo: return "Stereo"; - case DevFmtQuad: return "Quadraphonic"; - case DevFmtX51: return "5.1 Surround"; - case DevFmtX51Rear: return "5.1 Surround (Rear)"; - case DevFmtX61: return "6.1 Surround"; - case DevFmtX71: return "7.1 Surround"; - case DevFmtAmbi3D: return "Ambisonic 3D"; - } - return "(unknown channels)"; -} + DO_INITCONFIG(); -ALsizei BytesFromDevFmt(DevFmtType type) noexcept -{ - switch(type) - { - case DevFmtByte: return sizeof(ALbyte); - case DevFmtUByte: return sizeof(ALubyte); - case DevFmtShort: return sizeof(ALshort); - case DevFmtUShort: return sizeof(ALushort); - case DevFmtInt: return sizeof(ALint); - case DevFmtUInt: return sizeof(ALuint); - case DevFmtFloat: return sizeof(ALfloat); - } - return 0; -} -ALsizei ChannelsFromDevFmt(DevFmtChannels chans, ALsizei ambiorder) noexcept -{ - switch(chans) + std::lock_guard _{ListLock}; + if(!CaptureFactory) + decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList); + else { - case DevFmtMono: return 1; - case DevFmtStereo: return 2; - case DevFmtQuad: return 4; - case DevFmtX51: return 6; - case DevFmtX51Rear: return 6; - case DevFmtX61: return 7; - case DevFmtX71: return 8; - case DevFmtAmbi3D: return (ambiorder+1) * (ambiorder+1); + std::string names{CaptureFactory->probe(BackendType::Capture)}; + if(names.empty()) names += '\0'; + names.swap(alcCaptureDeviceList); } - return 0; } -static ALboolean DecomposeDevFormat(ALenum format, DevFmtChannels *chans, DevFmtType *type) + +struct DevFmtPair { DevFmtChannels chans; DevFmtType type; }; +al::optional DecomposeDevFormat(ALenum format) { static const struct { ALenum format; @@ -1280,259 +1375,155 @@ static ALboolean DecomposeDevFormat(ALenum format, DevFmtChannels *chans, DevFmt { AL_FORMAT_71CHN16, DevFmtX71, DevFmtShort }, { AL_FORMAT_71CHN32, DevFmtX71, DevFmtFloat }, }; - ALuint i; - for(i = 0;i < COUNTOF(list);i++) + for(const auto &item : list) { - if(list[i].format == format) - { - *chans = list[i].channels; - *type = list[i].type; - return AL_TRUE; - } + if(item.format == format) + return al::make_optional(DevFmtPair{item.channels, item.type}); } - return AL_FALSE; + return al::nullopt; } -static ALCboolean IsValidALCType(ALCenum type) +al::optional DevFmtTypeFromEnum(ALCenum type) { switch(type) { - case ALC_BYTE_SOFT: - case ALC_UNSIGNED_BYTE_SOFT: - case ALC_SHORT_SOFT: - case ALC_UNSIGNED_SHORT_SOFT: - case ALC_INT_SOFT: - case ALC_UNSIGNED_INT_SOFT: - case ALC_FLOAT_SOFT: - return ALC_TRUE; + case ALC_BYTE_SOFT: return al::make_optional(DevFmtByte); + case ALC_UNSIGNED_BYTE_SOFT: return al::make_optional(DevFmtUByte); + case ALC_SHORT_SOFT: return al::make_optional(DevFmtShort); + case ALC_UNSIGNED_SHORT_SOFT: return al::make_optional(DevFmtUShort); + case ALC_INT_SOFT: return al::make_optional(DevFmtInt); + case ALC_UNSIGNED_INT_SOFT: return al::make_optional(DevFmtUInt); + case ALC_FLOAT_SOFT: return al::make_optional(DevFmtFloat); } - return ALC_FALSE; + WARN("Unsupported format type: 0x%04x\n", type); + return al::nullopt; } - -static ALCboolean IsValidALCChannels(ALCenum channels) +ALCenum EnumFromDevFmt(DevFmtType type) { - switch(channels) + switch(type) { - case ALC_MONO_SOFT: - case ALC_STEREO_SOFT: - case ALC_QUAD_SOFT: - case ALC_5POINT1_SOFT: - case ALC_6POINT1_SOFT: - case ALC_7POINT1_SOFT: - case ALC_BFORMAT3D_SOFT: - return ALC_TRUE; + case DevFmtByte: return ALC_BYTE_SOFT; + case DevFmtUByte: return ALC_UNSIGNED_BYTE_SOFT; + case DevFmtShort: return ALC_SHORT_SOFT; + case DevFmtUShort: return ALC_UNSIGNED_SHORT_SOFT; + case DevFmtInt: return ALC_INT_SOFT; + case DevFmtUInt: return ALC_UNSIGNED_INT_SOFT; + case DevFmtFloat: return ALC_FLOAT_SOFT; } - return ALC_FALSE; + throw std::runtime_error{"Invalid DevFmtType: "+std::to_string(int(type))}; } -static ALCboolean IsValidAmbiLayout(ALCenum layout) +al::optional DevFmtChannelsFromEnum(ALCenum channels) { - switch(layout) + switch(channels) { - case ALC_ACN_SOFT: - case ALC_FUMA_SOFT: - return ALC_TRUE; + case ALC_MONO_SOFT: return al::make_optional(DevFmtMono); + case ALC_STEREO_SOFT: return al::make_optional(DevFmtStereo); + case ALC_QUAD_SOFT: return al::make_optional(DevFmtQuad); + case ALC_5POINT1_SOFT: return al::make_optional(DevFmtX51); + case ALC_6POINT1_SOFT: return al::make_optional(DevFmtX61); + case ALC_7POINT1_SOFT: return al::make_optional(DevFmtX71); + case ALC_BFORMAT3D_SOFT: return al::make_optional(DevFmtAmbi3D); } - return ALC_FALSE; + WARN("Unsupported format channels: 0x%04x\n", channels); + return al::nullopt; } - -static ALCboolean IsValidAmbiScaling(ALCenum scaling) +ALCenum EnumFromDevFmt(DevFmtChannels channels) { - switch(scaling) + switch(channels) { - case ALC_N3D_SOFT: - case ALC_SN3D_SOFT: - case ALC_FUMA_SOFT: - return ALC_TRUE; + case DevFmtMono: return ALC_MONO_SOFT; + case DevFmtStereo: return ALC_STEREO_SOFT; + case DevFmtQuad: return ALC_QUAD_SOFT; + case DevFmtX51: return ALC_5POINT1_SOFT; + case DevFmtX61: return ALC_6POINT1_SOFT; + case DevFmtX71: return ALC_7POINT1_SOFT; + case DevFmtAmbi3D: return ALC_BFORMAT3D_SOFT; } - return ALC_FALSE; + throw std::runtime_error{"Invalid DevFmtChannels: "+std::to_string(int(channels))}; } -/************************************************ - * Miscellaneous ALC helpers - ************************************************/ - -/* SetDefaultWFXChannelOrder - * - * Sets the default channel order used by WaveFormatEx. - */ -void SetDefaultWFXChannelOrder(ALCdevice *device) +al::optional DevAmbiLayoutFromEnum(ALCenum layout) { - device->RealOut.ChannelIndex.fill(-1); - - switch(device->FmtChans) + switch(layout) { - case DevFmtMono: - device->RealOut.ChannelIndex[FrontCenter] = 0; - break; - case DevFmtStereo: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - break; - case DevFmtQuad: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - device->RealOut.ChannelIndex[BackLeft] = 2; - device->RealOut.ChannelIndex[BackRight] = 3; - break; - case DevFmtX51: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - device->RealOut.ChannelIndex[FrontCenter] = 2; - device->RealOut.ChannelIndex[LFE] = 3; - device->RealOut.ChannelIndex[SideLeft] = 4; - device->RealOut.ChannelIndex[SideRight] = 5; - break; - case DevFmtX51Rear: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - device->RealOut.ChannelIndex[FrontCenter] = 2; - device->RealOut.ChannelIndex[LFE] = 3; - device->RealOut.ChannelIndex[BackLeft] = 4; - device->RealOut.ChannelIndex[BackRight] = 5; - break; - case DevFmtX61: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - device->RealOut.ChannelIndex[FrontCenter] = 2; - device->RealOut.ChannelIndex[LFE] = 3; - device->RealOut.ChannelIndex[BackCenter] = 4; - device->RealOut.ChannelIndex[SideLeft] = 5; - device->RealOut.ChannelIndex[SideRight] = 6; - break; - case DevFmtX71: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - device->RealOut.ChannelIndex[FrontCenter] = 2; - device->RealOut.ChannelIndex[LFE] = 3; - device->RealOut.ChannelIndex[BackLeft] = 4; - device->RealOut.ChannelIndex[BackRight] = 5; - device->RealOut.ChannelIndex[SideLeft] = 6; - device->RealOut.ChannelIndex[SideRight] = 7; - break; - case DevFmtAmbi3D: - device->RealOut.ChannelIndex[Aux0] = 0; - if(device->mAmbiOrder > 0) - { - device->RealOut.ChannelIndex[Aux1] = 1; - device->RealOut.ChannelIndex[Aux2] = 2; - device->RealOut.ChannelIndex[Aux3] = 3; - } - if(device->mAmbiOrder > 1) - { - device->RealOut.ChannelIndex[Aux4] = 4; - device->RealOut.ChannelIndex[Aux5] = 5; - device->RealOut.ChannelIndex[Aux6] = 6; - device->RealOut.ChannelIndex[Aux7] = 7; - device->RealOut.ChannelIndex[Aux8] = 8; - } - if(device->mAmbiOrder > 2) - { - device->RealOut.ChannelIndex[Aux9] = 9; - device->RealOut.ChannelIndex[Aux10] = 10; - device->RealOut.ChannelIndex[Aux11] = 11; - device->RealOut.ChannelIndex[Aux12] = 12; - device->RealOut.ChannelIndex[Aux13] = 13; - device->RealOut.ChannelIndex[Aux14] = 14; - device->RealOut.ChannelIndex[Aux15] = 15; - } - break; + case ALC_FUMA_SOFT: return al::make_optional(DevAmbiLayout::FuMa); + case ALC_ACN_SOFT: return al::make_optional(DevAmbiLayout::ACN); } + WARN("Unsupported ambisonic layout: 0x%04x\n", layout); + return al::nullopt; } - -/* SetDefaultChannelOrder - * - * Sets the default channel order used by most non-WaveFormatEx-based APIs. - */ -void SetDefaultChannelOrder(ALCdevice *device) +ALCenum EnumFromDevAmbi(DevAmbiLayout layout) { - device->RealOut.ChannelIndex.fill(-1); - - switch(device->FmtChans) + switch(layout) { - case DevFmtX51Rear: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - device->RealOut.ChannelIndex[BackLeft] = 2; - device->RealOut.ChannelIndex[BackRight] = 3; - device->RealOut.ChannelIndex[FrontCenter] = 4; - device->RealOut.ChannelIndex[LFE] = 5; - return; - case DevFmtX71: - device->RealOut.ChannelIndex[FrontLeft] = 0; - device->RealOut.ChannelIndex[FrontRight] = 1; - device->RealOut.ChannelIndex[BackLeft] = 2; - device->RealOut.ChannelIndex[BackRight] = 3; - device->RealOut.ChannelIndex[FrontCenter] = 4; - device->RealOut.ChannelIndex[LFE] = 5; - device->RealOut.ChannelIndex[SideLeft] = 6; - device->RealOut.ChannelIndex[SideRight] = 7; - return; - - /* Same as WFX order */ - case DevFmtMono: - case DevFmtStereo: - case DevFmtQuad: - case DevFmtX51: - case DevFmtX61: - case DevFmtAmbi3D: - SetDefaultWFXChannelOrder(device); - break; + 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))}; } - -/* ALCcontext_DeferUpdates - * - * Defers/suspends updates for the given context's listener and sources. This - * does *NOT* stop mixing, but rather prevents certain property changes from - * taking effect. - */ -void ALCcontext_DeferUpdates(ALCcontext *context) +al::optional DevAmbiScalingFromEnum(ALCenum scaling) { - context->DeferUpdates.store(true); + switch(scaling) + { + case ALC_FUMA_SOFT: return al::make_optional(DevAmbiScaling::FuMa); + case ALC_SN3D_SOFT: return al::make_optional(DevAmbiScaling::SN3D); + case ALC_N3D_SOFT: return al::make_optional(DevAmbiScaling::N3D); + } + WARN("Unsupported ambisonic scaling: 0x%04x\n", scaling); + return al::nullopt; } - -/* ALCcontext_ProcessUpdates - * - * Resumes update processing after being deferred. - */ -void ALCcontext_ProcessUpdates(ALCcontext *context) +ALCenum EnumFromDevAmbi(DevAmbiScaling scaling) { - std::lock_guard _{context->PropLock}; - if(context->DeferUpdates.exchange(false)) + switch(scaling) { - /* Tell the mixer to stop applying updates, then wait for any active - * updating to finish, before providing updates. - */ - context->HoldUpdates.store(true, std::memory_order_release); - while((context->UpdateCount.load(std::memory_order_acquire)&1) != 0) - std::this_thread::yield(); - - if(!context->PropsClean.test_and_set(std::memory_order_acq_rel)) - UpdateContextProps(context); - if(!context->Listener.PropsClean.test_and_set(std::memory_order_acq_rel)) - UpdateListenerProps(context); - UpdateAllEffectSlotProps(context); - UpdateAllSourceProps(context); - - /* Now with all updates declared, let the mixer continue applying them - * so they all happen at once. - */ - context->HoldUpdates.store(false, std::memory_order_release); + case DevAmbiScaling::FuMa: return ALC_FUMA_SOFT; + 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))}; } -/* alcSetError - * - * Stores the latest ALC device error +/* Downmixing channel arrays, to map the given format's missing channels to + * existing ones. Based on Wine's DSound downmix values, which are based on + * PulseAudio's. */ -static void alcSetError(ALCdevice *device, ALCenum errorCode) -{ - WARN("Error generated on device %p, code 0x%04x\n", device, errorCode); +const std::array StereoDownmix{{ + { FrontCenter, {{{FrontLeft, 0.5f}, {FrontRight, 0.5f}}} }, + { SideLeft, {{{FrontLeft, 1.0f/9.0f}, {FrontRight, 0.0f}}} }, + { SideRight, {{{FrontLeft, 0.0f}, {FrontRight, 1.0f/9.0f}}} }, + { BackLeft, {{{FrontLeft, 1.0f/9.0f}, {FrontRight, 0.0f}}} }, + { BackRight, {{{FrontLeft, 0.0f}, {FrontRight, 1.0f/9.0f}}} }, + { BackCenter, {{{FrontLeft, 0.5f/9.0f}, {FrontRight, 0.5f/9.0f}}} }, +}}; +const std::array QuadDownmix{{ + { FrontCenter, {{{FrontLeft, 0.5f}, {FrontRight, 0.5f}}} }, + { SideLeft, {{{FrontLeft, 0.5f}, {BackLeft, 0.5f}}} }, + { SideRight, {{{FrontRight, 0.5f}, {BackRight, 0.5f}}} }, + { BackCenter, {{{BackLeft, 0.5f}, {BackRight, 0.5f}}} }, +}}; +const std::array X51Downmix{{ + { BackLeft, {{{SideLeft, 1.0f}, {SideRight, 0.0f}}} }, + { BackRight, {{{SideLeft, 0.0f}, {SideRight, 1.0f}}} }, + { BackCenter, {{{SideLeft, 0.5f}, {SideRight, 0.5f}}} }, +}}; +const std::array X61Downmix{{ + { BackLeft, {{{BackCenter, 0.5f}, {SideLeft, 0.5f}}} }, + { BackRight, {{{BackCenter, 0.5f}, {SideRight, 0.5f}}} }, +}}; +const std::array X71Downmix{{ + { BackCenter, {{{BackLeft, 0.5f}, {BackRight, 0.5f}}} }, +}}; + + +/** Stores the latest ALC device error. */ +void alcSetError(ALCdevice *device, ALCenum errorCode) +{ + WARN("Error generated on device %p, code 0x%04x\n", voidp{device}, errorCode); if(TrapALCError) { #ifdef _WIN32 @@ -1551,15 +1542,28 @@ static void alcSetError(ALCdevice *device, ALCenum errorCode) } -static std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const ALfloat threshold) +std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const float threshold) { - return CompressorInit(device->RealOut.NumChannels, device->Frequency, - AL_TRUE, AL_TRUE, AL_TRUE, AL_TRUE, AL_TRUE, 0.001f, 0.002f, - 0.0f, 0.0f, threshold, INFINITY, 0.0f, 0.020f, 0.200f); + 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}; + static constexpr float PostGainDb{0.0f}; + static constexpr float Ratio{std::numeric_limits::infinity()}; + static constexpr float KneeDb{0.0f}; + 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); } -/* UpdateClockBase - * +/** * Updates the device's base clock time with however many samples have been * done. This is used so frequency changes on the device don't cause the time * to jump forward or back. Must not be called while the device is running/ @@ -1567,137 +1571,115 @@ static std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, */ static inline void UpdateClockBase(ALCdevice *device) { - IncrementRef(&device->MixCount); + IncrementRef(device->MixCount); device->ClockBase += nanoseconds{seconds{device->SamplesDone}} / device->Frequency; device->SamplesDone = 0; - IncrementRef(&device->MixCount); + IncrementRef(device->MixCount); } -/* UpdateDeviceParams - * +/** * Updates device parameters according to the attribute list (caller is * responsible for holding the list lock). */ -static ALCenum UpdateDeviceParams(ALCdevice *device, const ALCint *attrList) +ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) { - HrtfRequestMode hrtf_userreq = Hrtf_Default; - HrtfRequestMode hrtf_appreq = Hrtf_Default; - ALCenum gainLimiter = device->LimiterState; - const ALsizei old_sends = device->NumAuxSends; - ALsizei new_sends = device->NumAuxSends; - DevFmtChannels oldChans; - DevFmtType oldType; - ALboolean update_failed; - ALCsizei hrtf_id = -1; - ALCcontext *context; - ALCuint oldFreq; - int val; - - if((!attrList || !attrList[0]) && device->Type == Loopback) + if((!attrList || !attrList[0]) && device->Type == DeviceType::Loopback) { WARN("Missing attributes for loopback device\n"); return ALC_INVALID_VALUE; } + al::optional stereomode{}; + al::optional optlimit{}; + int hrtf_id{-1}; + // Check for attributes if(attrList && attrList[0]) { - ALCenum alayout = AL_NONE; - ALCenum ascale = AL_NONE; - ALCenum schans = AL_NONE; - ALCenum stype = AL_NONE; - ALCsizei attrIdx = 0; - ALCsizei aorder = 0; - ALCuint freq = 0; - - const char *devname{nullptr}; - const bool loopback{device->Type == Loopback}; - if(!loopback) - { - devname = device->DeviceName.c_str(); - /* If a context is already running on the device, stop playback so - * the device attributes can be updated. - */ - if((device->Flags&DEVICE_RUNNING)) - device->Backend->stop(); - device->Flags &= ~DEVICE_RUNNING; - } + uint numMono{device->NumMonoSources}; + uint numStereo{device->NumStereoSources}; + uint numSends{device->NumAuxSends}; - auto numMono = static_cast(device->NumMonoSources); - auto numStereo = static_cast(device->NumStereoSources); - auto numSends = ALsizei{old_sends}; + al::optional optchans; + al::optional opttype; + al::optional optlayout; + al::optional optscale; + al::optional opthrtf; -#define TRACE_ATTR(a, v) TRACE("%s = %d\n", #a, v) + ALenum outmode{ALC_ANY_SOFT}; + uint aorder{0u}; + uint freq{0u}; + +#define ATTRIBUTE(a) a: TRACE("%s = %d\n", #a, attrList[attrIdx + 1]); + size_t attrIdx{0}; while(attrList[attrIdx]) { switch(attrList[attrIdx]) { - case ALC_FORMAT_CHANNELS_SOFT: - schans = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_FORMAT_CHANNELS_SOFT, schans); + case ATTRIBUTE(ALC_FORMAT_CHANNELS_SOFT) + optchans = DevFmtChannelsFromEnum(attrList[attrIdx + 1]); break; - case ALC_FORMAT_TYPE_SOFT: - stype = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_FORMAT_TYPE_SOFT, stype); + case ATTRIBUTE(ALC_FORMAT_TYPE_SOFT) + opttype = DevFmtTypeFromEnum(attrList[attrIdx + 1]); break; - case ALC_FREQUENCY: - freq = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_FREQUENCY, freq); + case ATTRIBUTE(ALC_FREQUENCY) + freq = static_cast(attrList[attrIdx + 1]); break; - case ALC_AMBISONIC_LAYOUT_SOFT: - alayout = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_AMBISONIC_LAYOUT_SOFT, alayout); + case ATTRIBUTE(ALC_AMBISONIC_LAYOUT_SOFT) + optlayout = DevAmbiLayoutFromEnum(attrList[attrIdx + 1]); break; - case ALC_AMBISONIC_SCALING_SOFT: - ascale = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_AMBISONIC_SCALING_SOFT, ascale); + case ATTRIBUTE(ALC_AMBISONIC_SCALING_SOFT) + optscale = DevAmbiScalingFromEnum(attrList[attrIdx + 1]); break; - case ALC_AMBISONIC_ORDER_SOFT: - aorder = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_AMBISONIC_ORDER_SOFT, aorder); + case ATTRIBUTE(ALC_AMBISONIC_ORDER_SOFT) + aorder = static_cast(attrList[attrIdx + 1]); break; - case ALC_MONO_SOURCES: - numMono = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_MONO_SOURCES, numMono); - numMono = maxi(numMono, 0); + case ATTRIBUTE(ALC_MONO_SOURCES) + numMono = static_cast(attrList[attrIdx + 1]); + if(numMono > INT_MAX) numMono = 0; break; - case ALC_STEREO_SOURCES: - numStereo = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_STEREO_SOURCES, numStereo); - numStereo = maxi(numStereo, 0); + case ATTRIBUTE(ALC_STEREO_SOURCES) + numStereo = static_cast(attrList[attrIdx + 1]); + if(numStereo > INT_MAX) numStereo = 0; break; - case ALC_MAX_AUXILIARY_SENDS: - numSends = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_MAX_AUXILIARY_SENDS, numSends); - numSends = clampi(numSends, 0, MAX_SENDS); + case ATTRIBUTE(ALC_MAX_AUXILIARY_SENDS) + numSends = static_cast(attrList[attrIdx + 1]); + if(numSends > INT_MAX) numSends = 0; + else numSends = minu(numSends, MAX_SENDS); break; - case ALC_HRTF_SOFT: - TRACE_ATTR(ALC_HRTF_SOFT, attrList[attrIdx + 1]); + case ATTRIBUTE(ALC_HRTF_SOFT) if(attrList[attrIdx + 1] == ALC_FALSE) - hrtf_appreq = Hrtf_Disable; + opthrtf = false; else if(attrList[attrIdx + 1] == ALC_TRUE) - hrtf_appreq = Hrtf_Enable; - else - hrtf_appreq = Hrtf_Default; + opthrtf = true; + else if(attrList[attrIdx + 1] == ALC_DONT_CARE_SOFT) + opthrtf = al::nullopt; break; - case ALC_HRTF_ID_SOFT: + case ATTRIBUTE(ALC_HRTF_ID_SOFT) hrtf_id = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_HRTF_ID_SOFT, hrtf_id); break; - case ALC_OUTPUT_LIMITER_SOFT: - gainLimiter = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_OUTPUT_LIMITER_SOFT, gainLimiter); + case ATTRIBUTE(ALC_OUTPUT_LIMITER_SOFT) + if(attrList[attrIdx + 1] == ALC_FALSE) + optlimit = false; + else if(attrList[attrIdx + 1] == ALC_TRUE) + optlimit = true; + else if(attrList[attrIdx + 1] == ALC_DONT_CARE_SOFT) + optlimit = al::nullopt; + break; + + case ATTRIBUTE(ALC_OUTPUT_MODE_SOFT) + outmode = attrList[attrIdx + 1]; break; default: @@ -1708,278 +1690,444 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const ALCint *attrList) attrIdx += 2; } -#undef TRACE_ATTR +#undef ATTRIBUTE + const bool loopback{device->Type == DeviceType::Loopback}; if(loopback) { - if(!schans || !stype || !freq) - { - WARN("Missing format for loopback device\n"); + if(!optchans || !opttype) return ALC_INVALID_VALUE; - } - if(!IsValidALCChannels(schans) || !IsValidALCType(stype) || freq < MIN_OUTPUT_RATE) + if(freq < MIN_OUTPUT_RATE || freq > MAX_OUTPUT_RATE) return ALC_INVALID_VALUE; - if(schans == ALC_BFORMAT3D_SOFT) + if(*optchans == DevFmtAmbi3D) { - if(!alayout || !ascale || !aorder) - { - WARN("Missing ambisonic info for loopback device\n"); - return ALC_INVALID_VALUE; - } - if(!IsValidAmbiLayout(alayout) || !IsValidAmbiScaling(ascale)) + if(!optlayout || !optscale) return ALC_INVALID_VALUE; - if(aorder < 1 || aorder > MAX_AMBI_ORDER) + if(aorder < 1 || aorder > MaxAmbiOrder) return ALC_INVALID_VALUE; - if((alayout == ALC_FUMA_SOFT || ascale == ALC_FUMA_SOFT) && aorder > 3) + if((*optlayout == DevAmbiLayout::FuMa || *optscale == DevAmbiScaling::FuMa) + && aorder > 3) return ALC_INVALID_VALUE; } } - if((device->Flags&DEVICE_RUNNING)) + /* If a context is already running on the device, stop playback so the + * device attributes can be updated. + */ + if(device->Flags.test(DeviceRunning)) device->Backend->stop(); - device->Flags &= ~DEVICE_RUNNING; + device->Flags.reset(DeviceRunning); UpdateClockBase(device); - if(!loopback) + /* Calculate the max number of sources, and split them between the mono + * and stereo count given the requested number of stereo sources. + */ + if(auto srcsopt = device->configValue(nullptr, "sources")) { - device->BufferSize = DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES; - device->UpdateSize = DEFAULT_UPDATE_SIZE; - device->Frequency = DEFAULT_OUTPUT_RATE; - - ConfigValueUInt(devname, nullptr, "frequency", &freq); - if(freq < 1) - device->Flags &= ~DEVICE_FREQUENCY_REQUEST; - else - { - freq = maxi(freq, MIN_OUTPUT_RATE); - - device->UpdateSize = (device->UpdateSize*freq + device->Frequency/2) / - device->Frequency; - device->BufferSize = (device->BufferSize*freq + device->Frequency/2) / - device->Frequency; - - device->Frequency = freq; - device->Flags |= DEVICE_FREQUENCY_REQUEST; - } - - ConfigValueUInt(devname, nullptr, "period_size", &device->UpdateSize); - device->UpdateSize = clampu(device->UpdateSize, 64, 8192); - - ALuint periods{}; - if(ConfigValueUInt(devname, nullptr, "periods", &periods)) - device->BufferSize = device->UpdateSize * clampu(periods, 2, 16); - else - device->BufferSize = maxu(device->BufferSize, device->UpdateSize*2); + if(*srcsopt <= 0) numMono = 256; + else numMono = *srcsopt; } else + { + if(numMono > INT_MAX-numStereo) + numMono = INT_MAX-numStereo; + numMono = maxu(numMono+numStereo, 256); + } + numStereo = minu(numStereo, numMono); + numMono -= numStereo; + device->SourcesMax = numMono + numStereo; + device->NumMonoSources = numMono; + device->NumStereoSources = numStereo; + + if(auto sendsopt = device->configValue(nullptr, "sends")) + numSends = minu(numSends, static_cast(clampi(*sendsopt, 0, MAX_SENDS))); + device->NumAuxSends = numSends; + + if(loopback) { device->Frequency = freq; - device->FmtChans = static_cast(schans); - device->FmtType = static_cast(stype); - if(schans == ALC_BFORMAT3D_SOFT) + device->FmtChans = *optchans; + device->FmtType = *opttype; + if(device->FmtChans == DevFmtAmbi3D) { device->mAmbiOrder = aorder; - device->mAmbiLayout = static_cast(alayout); - device->mAmbiScale = static_cast(ascale); + device->mAmbiLayout = *optlayout; + device->mAmbiScale = *optscale; } - } - - if(numMono > INT_MAX-numStereo) - numMono = INT_MAX-numStereo; - numMono += numStereo; - if(ConfigValueInt(devname, nullptr, "sources", &numMono)) - { - if(numMono <= 0) - numMono = 256; + else if(device->FmtChans == DevFmtStereo) + { + if(opthrtf) + stereomode = *opthrtf ? StereoEncoding::Hrtf : StereoEncoding::Default; + + if(outmode == ALC_STEREO_BASIC_SOFT) + stereomode = StereoEncoding::Basic; + else if(outmode == ALC_STEREO_UHJ_SOFT) + stereomode = StereoEncoding::Uhj; + else if(outmode == ALC_STEREO_HRTF_SOFT) + stereomode = StereoEncoding::Hrtf; + } + device->Flags.set(FrequencyRequest).set(ChannelsRequest).set(SampleTypeRequest); } else - numMono = maxi(numMono, 256); - numStereo = mini(numStereo, numMono); - numMono -= numStereo; - device->SourcesMax = numMono + numStereo; + { + device->Flags.reset(FrequencyRequest).reset(ChannelsRequest).reset(SampleTypeRequest); + device->FmtType = DevFmtTypeDefault; + device->FmtChans = DevFmtChannelsDefault; + device->mAmbiOrder = 0; + device->BufferSize = DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES; + device->UpdateSize = DEFAULT_UPDATE_SIZE; + device->Frequency = DEFAULT_OUTPUT_RATE; - device->NumMonoSources = numMono; - device->NumStereoSources = numStereo; + freq = device->configValue(nullptr, "frequency").value_or(freq); + if(freq > 0) + { + freq = clampu(freq, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE); - if(ConfigValueInt(devname, nullptr, "sends", &new_sends)) - new_sends = mini(numSends, clampi(new_sends, 0, MAX_SENDS)); - else - new_sends = numSends; + const double scale{static_cast(freq) / device->Frequency}; + device->UpdateSize = static_cast(device->UpdateSize*scale + 0.5); + device->BufferSize = static_cast(device->BufferSize*scale + 0.5); + + device->Frequency = freq; + device->Flags.set(FrequencyRequest); + } + + auto set_device_mode = [device](DevFmtChannels chans) noexcept + { + device->FmtChans = chans; + device->Flags.set(ChannelsRequest); + }; + if(opthrtf) + { + if(*opthrtf) + { + set_device_mode(DevFmtStereo); + stereomode = StereoEncoding::Hrtf; + } + else + stereomode = StereoEncoding::Default; + } + + using OutputMode = ALCdevice::OutputMode; + switch(OutputMode(outmode)) + { + case OutputMode::Any: break; + case OutputMode::Mono: set_device_mode(DevFmtMono); break; + case OutputMode::Stereo: set_device_mode(DevFmtStereo); break; + case OutputMode::StereoBasic: + set_device_mode(DevFmtStereo); + stereomode = StereoEncoding::Basic; + break; + case OutputMode::Uhj2: + set_device_mode(DevFmtStereo); + stereomode = StereoEncoding::Uhj; + break; + case OutputMode::Hrtf: + set_device_mode(DevFmtStereo); + stereomode = StereoEncoding::Hrtf; + break; + case OutputMode::Quad: set_device_mode(DevFmtQuad); break; + case OutputMode::X51: set_device_mode(DevFmtX51); break; + case OutputMode::X61: set_device_mode(DevFmtX61); break; + case OutputMode::X71: set_device_mode(DevFmtX71); break; + } + } } - if((device->Flags&DEVICE_RUNNING)) + if(device->Flags.test(DeviceRunning)) return ALC_NO_ERROR; - device->Uhj_Encoder = nullptr; + device->AvgSpeakerDist = 0.0f; + device->mNFCtrlFilter = NfcFilter{}; + device->mUhjEncoder = nullptr; + device->AmbiDecoder = nullptr; device->Bs2b = nullptr; + device->PostProcess = nullptr; device->Limiter = nullptr; - device->ChannelDelay.clear(); - device->ChannelDelay.shrink_to_fit(); + device->ChannelDelays = nullptr; + + std::fill(std::begin(device->HrtfAccumData), std::end(device->HrtfAccumData), float2{}); - device->Dry.Buffer = nullptr; - device->Dry.NumChannels = 0; - device->RealOut.Buffer = nullptr; - device->RealOut.NumChannels = 0; + device->Dry.AmbiMap.fill(BFChannelConfig{}); + device->Dry.Buffer = {}; + std::fill(std::begin(device->NumChannelsPerOrder), std::end(device->NumChannelsPerOrder), 0u); + device->RealOut.RemixMap = {}; + device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); + device->RealOut.Buffer = {}; device->MixBuffer.clear(); device->MixBuffer.shrink_to_fit(); UpdateClockBase(device); device->FixedLatency = nanoseconds::zero(); - device->DitherSeed = DITHER_RNG_SEED; + device->DitherDepth = 0.0f; + device->DitherSeed = DitherRNGSeed; + + device->mHrtfStatus = ALC_HRTF_DISABLED_SOFT; /************************************************************************* - * Update device format request if HRTF is requested + * Update device format request from the user configuration */ - device->HrtfStatus = ALC_HRTF_DISABLED_SOFT; - if(device->Type != Loopback) + if(device->Type != DeviceType::Loopback) { - const char *hrtf; - if(ConfigValueStr(device->DeviceName.c_str(), nullptr, "hrtf", &hrtf)) + if(auto typeopt = device->configValue(nullptr, "sample-type")) { - if(strcasecmp(hrtf, "true") == 0) - hrtf_userreq = Hrtf_Enable; - else if(strcasecmp(hrtf, "false") == 0) - hrtf_userreq = Hrtf_Disable; - else if(strcasecmp(hrtf, "auto") != 0) - ERR("Unexpected hrtf value: %s\n", hrtf); + static constexpr struct TypeMap { + const char name[8]; + DevFmtType type; + } typelist[] = { + { "int8", DevFmtByte }, + { "uint8", DevFmtUByte }, + { "int16", DevFmtShort }, + { "uint16", DevFmtUShort }, + { "int32", DevFmtInt }, + { "uint32", DevFmtUInt }, + { "float32", DevFmtFloat }, + }; + + const ALCchar *fmt{typeopt->c_str()}; + auto iter = std::find_if(std::begin(typelist), std::end(typelist), + [fmt](const TypeMap &entry) -> bool + { return al::strcasecmp(entry.name, fmt) == 0; }); + if(iter == std::end(typelist)) + ERR("Unsupported sample-type: %s\n", fmt); + else + { + device->FmtType = iter->type; + device->Flags.set(SampleTypeRequest); + } } - - if(hrtf_userreq == Hrtf_Enable || (hrtf_userreq != Hrtf_Disable && hrtf_appreq == Hrtf_Enable)) + if(auto chanopt = device->configValue(nullptr, "channels")) + { + static constexpr struct ChannelMap { + const char name[16]; + DevFmtChannels chans; + uint8_t order; + } chanlist[] = { + { "mono", DevFmtMono, 0 }, + { "stereo", DevFmtStereo, 0 }, + { "quad", DevFmtQuad, 0 }, + { "surround51", DevFmtX51, 0 }, + { "surround61", DevFmtX61, 0 }, + { "surround71", DevFmtX71, 0 }, + { "surround51rear", DevFmtX51, 0 }, + { "ambi1", DevFmtAmbi3D, 1 }, + { "ambi2", DevFmtAmbi3D, 2 }, + { "ambi3", DevFmtAmbi3D, 3 }, + }; + + const ALCchar *fmt{chanopt->c_str()}; + auto iter = std::find_if(std::begin(chanlist), std::end(chanlist), + [fmt](const ChannelMap &entry) -> bool + { return al::strcasecmp(entry.name, fmt) == 0; }); + if(iter == std::end(chanlist)) + ERR("Unsupported channels: %s\n", fmt); + else + { + device->FmtChans = iter->chans; + device->mAmbiOrder = iter->order; + device->Flags.set(ChannelsRequest); + } + } + if(auto ambiopt = device->configValue(nullptr, "ambi-format")) { - HrtfEntry *hrtf{nullptr}; - if(device->HrtfList.empty()) - device->HrtfList = EnumerateHrtf(device->DeviceName.c_str()); - if(!device->HrtfList.empty()) + const ALCchar *fmt{ambiopt->c_str()}; + if(al::strcasecmp(fmt, "fuma") == 0) { - if(hrtf_id >= 0 && static_cast(hrtf_id) < device->HrtfList.size()) - hrtf = GetLoadedHrtf(device->HrtfList[hrtf_id].hrtf); + if(device->mAmbiOrder > 3) + ERR("FuMa is incompatible with %d%s order ambisonics (up to 3rd order only)\n", + device->mAmbiOrder, + (((device->mAmbiOrder%100)/10) == 1) ? "th" : + ((device->mAmbiOrder%10) == 1) ? "st" : + ((device->mAmbiOrder%10) == 2) ? "nd" : + ((device->mAmbiOrder%10) == 3) ? "rd" : "th"); else - hrtf = GetLoadedHrtf(device->HrtfList.front().hrtf); + { + device->mAmbiLayout = DevAmbiLayout::FuMa; + device->mAmbiScale = DevAmbiScaling::FuMa; + } + } + else if(al::strcasecmp(fmt, "acn+fuma") == 0) + { + if(device->mAmbiOrder > 3) + ERR("FuMa is incompatible with %d%s order ambisonics (up to 3rd order only)\n", + device->mAmbiOrder, + (((device->mAmbiOrder%100)/10) == 1) ? "th" : + ((device->mAmbiOrder%10) == 1) ? "st" : + ((device->mAmbiOrder%10) == 2) ? "nd" : + ((device->mAmbiOrder%10) == 3) ? "rd" : "th"); + else + { + device->mAmbiLayout = DevAmbiLayout::ACN; + device->mAmbiScale = DevAmbiScaling::FuMa; + } + } + else if(al::strcasecmp(fmt, "ambix") == 0 || al::strcasecmp(fmt, "acn+sn3d") == 0) + { + device->mAmbiLayout = DevAmbiLayout::ACN; + device->mAmbiScale = DevAmbiScaling::SN3D; + } + else if(al::strcasecmp(fmt, "acn+n3d") == 0) + { + device->mAmbiLayout = DevAmbiLayout::ACN; + device->mAmbiScale = DevAmbiScaling::N3D; } + else + ERR("Unsupported ambi-format: %s\n", fmt); + } - if(hrtf) + if(auto persizeopt = device->configValue(nullptr, "period_size")) + device->UpdateSize = clampu(*persizeopt, 64, 8192); + + if(auto peropt = device->configValue(nullptr, "periods")) + device->BufferSize = device->UpdateSize * clampu(*peropt, 2, 16); + else + device->BufferSize = maxu(device->BufferSize, device->UpdateSize*2); + + if(auto hrtfopt = device->configValue(nullptr, "hrtf")) + { + const char *hrtf{hrtfopt->c_str()}; + if(al::strcasecmp(hrtf, "true") == 0) { + stereomode = StereoEncoding::Hrtf; device->FmtChans = DevFmtStereo; - device->Frequency = hrtf->sampleRate; - device->Flags |= DEVICE_CHANNELS_REQUEST | DEVICE_FREQUENCY_REQUEST; - if(HrtfEntry *oldhrtf{device->mHrtf}) - oldhrtf->DecRef(); - device->mHrtf = hrtf; + device->Flags.set(ChannelsRequest); } - else + else if(al::strcasecmp(hrtf, "false") == 0) { - hrtf_userreq = Hrtf_Default; - hrtf_appreq = Hrtf_Disable; - device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; + if(!stereomode || *stereomode == StereoEncoding::Hrtf) + stereomode = StereoEncoding::Default; } + else if(al::strcasecmp(hrtf, "auto") != 0) + ERR("Unexpected hrtf value: %s\n", hrtf); } } - oldFreq = device->Frequency; - oldChans = device->FmtChans; - oldType = device->FmtType; - TRACE("Pre-reset: %s%s, %s%s, %s%uhz, %u / %u buffer\n", - (device->Flags&DEVICE_CHANNELS_REQUEST)?"*":"", DevFmtChannelsString(device->FmtChans), - (device->Flags&DEVICE_SAMPLE_TYPE_REQUEST)?"*":"", DevFmtTypeString(device->FmtType), - (device->Flags&DEVICE_FREQUENCY_REQUEST)?"*":"", device->Frequency, + device->Flags.test(ChannelsRequest)?"*":"", DevFmtChannelsString(device->FmtChans), + device->Flags.test(SampleTypeRequest)?"*":"", DevFmtTypeString(device->FmtType), + device->Flags.test(FrequencyRequest)?"*":"", device->Frequency, device->UpdateSize, device->BufferSize); + const uint oldFreq{device->Frequency}; + const DevFmtChannels oldChans{device->FmtChans}; + const DevFmtType oldType{device->FmtType}; try { - if(device->Backend->reset() == ALC_FALSE) - return ALC_INVALID_DEVICE; + auto backend = device->Backend.get(); + if(!backend->reset()) + throw al::backend_exception{al::backend_error::DeviceError, "Device reset failure"}; } catch(std::exception &e) { - ERR("Device reset failed: %s\n", e.what()); + ERR("Device error: %s\n", e.what()); + device->handleDisconnect("%s", e.what()); return ALC_INVALID_DEVICE; } - if(device->FmtChans != oldChans && (device->Flags&DEVICE_CHANNELS_REQUEST)) + if(device->FmtChans != oldChans && device->Flags.test(ChannelsRequest)) { ERR("Failed to set %s, got %s instead\n", DevFmtChannelsString(oldChans), DevFmtChannelsString(device->FmtChans)); - device->Flags &= ~DEVICE_CHANNELS_REQUEST; + device->Flags.reset(ChannelsRequest); } - if(device->FmtType != oldType && (device->Flags&DEVICE_SAMPLE_TYPE_REQUEST)) + if(device->FmtType != oldType && device->Flags.test(SampleTypeRequest)) { ERR("Failed to set %s, got %s instead\n", DevFmtTypeString(oldType), DevFmtTypeString(device->FmtType)); - device->Flags &= ~DEVICE_SAMPLE_TYPE_REQUEST; + device->Flags.reset(SampleTypeRequest); } - if(device->Frequency != oldFreq && (device->Flags&DEVICE_FREQUENCY_REQUEST)) + if(device->Frequency != oldFreq && device->Flags.test(FrequencyRequest)) { - ERR("Failed to set %uhz, got %uhz instead\n", oldFreq, device->Frequency); - device->Flags &= ~DEVICE_FREQUENCY_REQUEST; - } - - if((device->UpdateSize&3) != 0) - { - if((CPUCapFlags&CPU_CAP_SSE)) - WARN("SSE performs best with multiple of 4 update sizes (%u)\n", device->UpdateSize); - if((CPUCapFlags&CPU_CAP_NEON)) - WARN("NEON performs best with multiple of 4 update sizes (%u)\n", device->UpdateSize); + WARN("Failed to set %uhz, got %uhz instead\n", oldFreq, device->Frequency); + device->Flags.reset(FrequencyRequest); } TRACE("Post-reset: %s, %s, %uhz, %u / %u buffer\n", DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), device->Frequency, device->UpdateSize, device->BufferSize); - aluInitRenderer(device, hrtf_id, hrtf_appreq, hrtf_userreq); - TRACE("Channel config, Main: %d, Real: %d\n", device->Dry.NumChannels, - device->RealOut.NumChannels); + if(device->Type != DeviceType::Loopback) + { + if(auto modeopt = device->configValue(nullptr, "stereo-mode")) + { + const char *mode{modeopt->c_str()}; + if(al::strcasecmp(mode, "headphones") == 0) + device->Flags.set(DirectEar); + else if(al::strcasecmp(mode, "speakers") == 0) + device->Flags.reset(DirectEar); + else if(al::strcasecmp(mode, "auto") != 0) + ERR("Unexpected stereo-mode: %s\n", mode); + } - /* Allocate extra channels for any post-filter output. */ - const ALsizei num_chans{device->Dry.NumChannels + device->RealOut.NumChannels}; + if(auto encopt = device->configValue(nullptr, "stereo-encoding")) + { + const char *mode{encopt->c_str()}; + if(al::strcasecmp(mode, "panpot") == 0) + stereomode = al::make_optional(StereoEncoding::Basic); + else if(al::strcasecmp(mode, "uhj") == 0) + stereomode = al::make_optional(StereoEncoding::Uhj); + else if(al::strcasecmp(mode, "hrtf") == 0) + stereomode = al::make_optional(StereoEncoding::Hrtf); + else + ERR("Unexpected stereo-encoding: %s\n", mode); + } + } - TRACE("Allocating %d channels, %zu bytes\n", num_chans, - num_chans*sizeof(device->MixBuffer[0])); - device->MixBuffer.resize(num_chans); + aluInitRenderer(device, hrtf_id, stereomode); - device->Dry.Buffer = &reinterpret_cast(device->MixBuffer[0]); - if(device->RealOut.NumChannels != 0) - device->RealOut.Buffer = device->Dry.Buffer + device->Dry.NumChannels; - else + TRACE("Max sources: %d (%d + %d), effect slots: %d, sends: %d\n", + device->SourcesMax, device->NumMonoSources, device->NumStereoSources, + device->AuxiliaryEffectSlotMax, device->NumAuxSends); + + switch(device->FmtChans) { - device->RealOut.Buffer = device->Dry.Buffer; - device->RealOut.NumChannels = device->Dry.NumChannels; + case DevFmtMono: break; + case DevFmtStereo: + if(!device->mUhjEncoder) + device->RealOut.RemixMap = StereoDownmix; + break; + case DevFmtQuad: device->RealOut.RemixMap = QuadDownmix; break; + case DevFmtX51: device->RealOut.RemixMap = X51Downmix; break; + case DevFmtX61: device->RealOut.RemixMap = X61Downmix; break; + case DevFmtX71: device->RealOut.RemixMap = X71Downmix; break; + case DevFmtAmbi3D: break; } - device->NumAuxSends = new_sends; - TRACE("Max sources: %d (%d + %d), effect slots: %d, sends: %d\n", - device->SourcesMax, device->NumMonoSources, device->NumStereoSources, - device->AuxiliaryEffectSlotMax, device->NumAuxSends); + nanoseconds::rep sample_delay{0}; + if(device->mUhjEncoder) + sample_delay += UhjEncoder::sFilterDelay; + if(auto *ambidec = device->AmbiDecoder.get()) + { + if(ambidec->hasStablizer()) + sample_delay += FrontStablizer::DelayLength; + } - device->DitherDepth = 0.0f; - if(GetConfigValueBool(device->DeviceName.c_str(), nullptr, "dither", 1)) + if(device->getConfigValueBool(nullptr, "dither", true)) { - ALint depth = 0; - ConfigValueInt(device->DeviceName.c_str(), nullptr, "dither-depth", &depth); + int depth{device->configValue(nullptr, "dither-depth").value_or(0)}; if(depth <= 0) { switch(device->FmtType) { - case DevFmtByte: - case DevFmtUByte: - depth = 8; - break; - case DevFmtShort: - case DevFmtUShort: - depth = 16; - break; - case DevFmtInt: - case DevFmtUInt: - case DevFmtFloat: - break; + case DevFmtByte: + case DevFmtUByte: + depth = 8; + break; + case DevFmtShort: + case DevFmtUShort: + depth = 16; + break; + case DevFmtInt: + case DevFmtUInt: + case DevFmtFloat: + break; } } if(depth > 0) { depth = clampi(depth, 2, 24); - device->DitherDepth = std::pow(2.0f, static_cast(depth-1)); + device->DitherDepth = std::pow(2.0f, static_cast(depth-1)); } } if(!(device->DitherDepth > 0.0f)) @@ -1988,708 +2136,298 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const ALCint *attrList) TRACE("Dithering enabled (%d-bit, %g)\n", float2int(std::log2(device->DitherDepth)+0.5f)+1, device->DitherDepth); - device->LimiterState = gainLimiter; - if(ConfigValueBool(device->DeviceName.c_str(), nullptr, "output-limiter", &val)) - gainLimiter = val ? ALC_TRUE : ALC_FALSE; + if(auto limopt = device->configValue(nullptr, "output-limiter")) + optlimit = limopt; - /* Valid values for gainLimiter are ALC_DONT_CARE_SOFT, ALC_TRUE, and - * ALC_FALSE. For ALC_DONT_CARE_SOFT, use the limiter for integer-based - * output (where samples must be clamped), and don't for floating-point - * (which can take unclamped samples). + /* If the gain limiter is unset, use the limiter for integer-based output + * (where samples must be clamped), and don't for floating-point (which can + * take unclamped samples). */ - if(gainLimiter == ALC_DONT_CARE_SOFT) + if(!optlimit) { switch(device->FmtType) { - case DevFmtByte: - case DevFmtUByte: - case DevFmtShort: - case DevFmtUShort: - case DevFmtInt: - case DevFmtUInt: - gainLimiter = ALC_TRUE; - break; - case DevFmtFloat: - gainLimiter = ALC_FALSE; - break; + case DevFmtByte: + case DevFmtUByte: + case DevFmtShort: + case DevFmtUShort: + case DevFmtInt: + case DevFmtUInt: + optlimit = true; + break; + case DevFmtFloat: + break; } } - if(gainLimiter == ALC_FALSE) + if(optlimit.value_or(false) == false) TRACE("Output limiter disabled\n"); else { - ALfloat thrshld = 1.0f; + float thrshld{1.0f}; switch(device->FmtType) { - case DevFmtByte: - case DevFmtUByte: - thrshld = 127.0f / 128.0f; - break; - case DevFmtShort: - case DevFmtUShort: - thrshld = 32767.0f / 32768.0f; - break; - case DevFmtInt: - case DevFmtUInt: - case DevFmtFloat: - break; + case DevFmtByte: + case DevFmtUByte: + thrshld = 127.0f / 128.0f; + break; + case DevFmtShort: + case DevFmtUShort: + thrshld = 32767.0f / 32768.0f; + break; + case DevFmtInt: + case DevFmtUInt: + case DevFmtFloat: + break; } if(device->DitherDepth > 0.0f) thrshld -= 1.0f / device->DitherDepth; const float thrshld_dB{std::log10(thrshld) * 20.0f}; auto limiter = CreateDeviceLimiter(device, thrshld_dB); - /* Convert the lookahead from samples to nanosamples to nanoseconds. */ - device->FixedLatency += nanoseconds{seconds{limiter->getLookAhead()}} / device->Frequency; + + sample_delay += limiter->getLookAhead(); device->Limiter = std::move(limiter); TRACE("Output limiter enabled, %.4fdB limit\n", thrshld_dB); } - aluSelectPostProcess(device); - - TRACE("Fixed device latency: %ldns\n", (long)device->FixedLatency.count()); + /* Convert the sample delay from samples to nanosamples to nanoseconds. */ + device->FixedLatency += nanoseconds{seconds{sample_delay}} / device->Frequency; + TRACE("Fixed device latency: %" PRId64 "ns\n", int64_t{device->FixedLatency.count()}); - /* Need to delay returning failure until replacement Send arrays have been - * allocated with the appropriate size. - */ - update_failed = AL_FALSE; FPUCtl mixer_mode{}; - context = device->ContextList.load(); - while(context) + for(ContextBase *ctxbase : *device->mContexts.load()) { - if(context->DefaultSlot) + auto *context = static_cast(ctxbase); + + auto GetEffectBuffer = [](ALbuffer *buffer) noexcept -> EffectState::Buffer { - ALeffectslot *slot = context->DefaultSlot.get(); - aluInitEffectPanning(slot, device); - - EffectState *state{slot->Effect.State}; - state->mOutBuffer = device->Dry.Buffer; - state->mOutChannels = device->Dry.NumChannels; - if(state->deviceUpdate(device) == AL_FALSE) - update_failed = AL_TRUE; - else - UpdateEffectSlotProps(slot, context); + if(!buffer) return EffectState::Buffer{}; + return EffectState::Buffer{buffer, buffer->mData}; + }; + std::unique_lock proplock{context->mPropLock}; + std::unique_lock slotlock{context->mEffectSlotLock}; + + /* Clear out unused wet buffers. */ + auto buffer_not_in_use = [](WetBufferPtr &wetbuffer) noexcept -> bool + { return !wetbuffer->mInUse; }; + auto wetbuffer_iter = std::remove_if(context->mWetBuffers.begin(), + context->mWetBuffers.end(), buffer_not_in_use); + context->mWetBuffers.erase(wetbuffer_iter, context->mWetBuffers.end()); + + if(ALeffectslot *slot{context->mDefaultSlot.get()}) + { + aluInitEffectPanning(&slot->mSlot, context); + + EffectState *state{slot->Effect.State.get()}; + state->mOutTarget = device->Dry.Buffer; + state->deviceUpdate(device, GetEffectBuffer(slot->Buffer)); + slot->updateProps(context); } - std::unique_lock proplock{context->PropLock}; - std::unique_lock slotlock{context->EffectSlotLock}; - for(auto &sublist : context->EffectSlotList) + if(EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_relaxed)}) + std::fill_n(curarray->end(), curarray->size(), nullptr); + for(auto &sublist : context->mEffectSlotList) { - uint64_t usemask = ~sublist.FreeMask; + uint64_t usemask{~sublist.FreeMask}; while(usemask) { - ALsizei idx = CTZ64(usemask); - ALeffectslot *slot = sublist.EffectSlots + idx; - + const int idx{al::countr_zero(usemask)}; + ALeffectslot *slot{sublist.EffectSlots + idx}; usemask &= ~(1_u64 << idx); - aluInitEffectPanning(slot, device); + aluInitEffectPanning(&slot->mSlot, context); - EffectState *state{slot->Effect.State}; - state->mOutBuffer = device->Dry.Buffer; - state->mOutChannels = device->Dry.NumChannels; - if(state->deviceUpdate(device) == AL_FALSE) - update_failed = AL_TRUE; - else - UpdateEffectSlotProps(slot, context); + EffectState *state{slot->Effect.State.get()}; + state->mOutTarget = device->Dry.Buffer; + state->deviceUpdate(device, GetEffectBuffer(slot->Buffer)); + slot->updateProps(context); } } slotlock.unlock(); - std::unique_lock srclock{context->SourceLock}; - for(auto &sublist : context->SourceList) + const uint num_sends{device->NumAuxSends}; + std::unique_lock srclock{context->mSourceLock}; + for(auto &sublist : context->mSourceList) { - uint64_t usemask = ~sublist.FreeMask; + uint64_t usemask{~sublist.FreeMask}; while(usemask) { - ALsizei idx = CTZ64(usemask); - ALsource *source = sublist.Sources + idx; - + const int idx{al::countr_zero(usemask)}; + ALsource *source{sublist.Sources + idx}; usemask &= ~(1_u64 << idx); - if(old_sends != device->NumAuxSends) + auto clear_send = [](ALsource::SendData &send) -> void { - ALsizei s; - for(s = device->NumAuxSends;s < old_sends;s++) - { - if(source->Send[s].Slot) - DecrementRef(&source->Send[s].Slot->ref); - source->Send[s].Slot = nullptr; - } - source->Send.resize(device->NumAuxSends); - source->Send.shrink_to_fit(); - for(s = old_sends;s < device->NumAuxSends;s++) - { - source->Send[s].Slot = nullptr; - source->Send[s].Gain = 1.0f; - source->Send[s].GainHF = 1.0f; - source->Send[s].HFReference = LOWPASSFREQREF; - source->Send[s].GainLF = 1.0f; - source->Send[s].LFReference = HIGHPASSFREQREF; - } - } - - source->PropsClean.clear(std::memory_order_release); + if(send.Slot) + DecrementRef(send.Slot->ref); + send.Slot = nullptr; + send.Gain = 1.0f; + send.GainHF = 1.0f; + send.HFReference = LOWPASSFREQREF; + send.GainLF = 1.0f; + send.LFReference = HIGHPASSFREQREF; + }; + auto send_begin = source->Send.begin() + static_cast(num_sends); + std::for_each(send_begin, source->Send.end(), clear_send); + + source->mPropsDirty = true; } } - /* Clear any pre-existing voice property structs, in case the number of - * auxiliary sends is changing. Active sources will have updates - * respecified in UpdateAllSourceProps. - */ - ALvoiceProps *vprops{context->FreeVoiceProps.exchange(nullptr, std::memory_order_acq_rel)}; - while(vprops) + auto voicelist = context->getVoicesSpan(); + for(Voice *voice : voicelist) { - ALvoiceProps *next = vprops->next.load(std::memory_order_relaxed); - delete vprops; - vprops = next; - } + /* Clear extraneous property set sends. */ + std::fill(std::begin(voice->mProps.Send)+num_sends, std::end(voice->mProps.Send), + VoiceProps::SendData{}); - AllocateVoices(context, context->MaxVoices, old_sends); - auto voices_end = context->Voices + context->VoiceCount.load(std::memory_order_relaxed); - std::for_each(context->Voices, voices_end, - [device](ALvoice *voice) -> void + std::fill(voice->mSend.begin()+num_sends, voice->mSend.end(), Voice::TargetData{}); + for(auto &chandata : voice->mChans) { - delete voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel); + std::fill(chandata.mWetParams.begin()+num_sends, chandata.mWetParams.end(), + SendParams{}); + } - /* Force the voice to stopped if it was stopping. */ - ALvoice::State vstate{ALvoice::Stopping}; - voice->mPlayState.compare_exchange_strong(vstate, ALvoice::Stopped, - std::memory_order_acquire, std::memory_order_acquire); - if(voice->mSourceID.load(std::memory_order_relaxed) == 0u) - return; + if(VoicePropsItem *props{voice->mUpdate.exchange(nullptr, std::memory_order_relaxed)}) + AtomicReplaceHead(context->mFreeVoiceProps, props); - if(device->AvgSpeakerDist > 0.0f) - { - /* Reinitialize the NFC filters for new parameters. */ - ALfloat w1 = SPEEDOFSOUNDMETRESPERSEC / - (device->AvgSpeakerDist * device->Frequency); - std::for_each(voice->mDirect.Params, voice->mDirect.Params+voice->mNumChannels, - [w1](DirectParams ¶ms) noexcept -> void - { params.NFCtrlFilter.init(w1); } - ); - } - } - ); + /* Force the voice to stopped if it was stopping. */ + Voice::State vstate{Voice::Stopping}; + voice->mPlayState.compare_exchange_strong(vstate, Voice::Stopped, + std::memory_order_acquire, std::memory_order_acquire); + if(voice->mSourceID.load(std::memory_order_relaxed) == 0u) + continue; + + voice->prepare(device); + } + /* Clear all voice props to let them get allocated again. */ + context->mVoicePropClusters.clear(); + context->mFreeVoiceProps.store(nullptr, std::memory_order_relaxed); srclock.unlock(); - context->PropsClean.test_and_set(std::memory_order_release); + context->mPropsDirty = false; UpdateContextProps(context); - context->Listener.PropsClean.test_and_set(std::memory_order_release); - UpdateListenerProps(context); UpdateAllSourceProps(context); - - context = context->next.load(std::memory_order_relaxed); } mixer_mode.leave(); - if(update_failed) - return ALC_INVALID_DEVICE; - if(!(device->Flags&DEVICE_PAUSED)) + if(!device->Flags.test(DevicePaused)) { - if(device->Backend->start() == ALC_FALSE) + try { + auto backend = device->Backend.get(); + backend->start(); + device->Flags.set(DeviceRunning); + } + catch(al::backend_exception& e) { + ERR("%s\n", e.what()); + device->handleDisconnect("%s", e.what()); return ALC_INVALID_DEVICE; - device->Flags |= DEVICE_RUNNING; + } + TRACE("Post-start: %s, %s, %uhz, %u / %u buffer\n", + DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), + device->Frequency, device->UpdateSize, device->BufferSize); } return ALC_NO_ERROR; } - -ALCdevice::ALCdevice(DeviceType type) : Type{type} -{ -} - -/* ALCdevice::~ALCdevice - * - * Frees the device structure, and destroys any objects the app failed to - * delete. Called once there's no more references on the device. - */ -ALCdevice::~ALCdevice() -{ - TRACE("%p\n", this); - - Backend = nullptr; - - size_t count{std::accumulate(BufferList.cbegin(), BufferList.cend(), size_t{0u}, - [](size_t cur, const BufferSubList &sublist) noexcept -> size_t - { return cur + POPCNT64(~sublist.FreeMask); } - )}; - if(count > 0) - WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s"); - - count = std::accumulate(EffectList.cbegin(), EffectList.cend(), size_t{0u}, - [](size_t cur, const EffectSubList &sublist) noexcept -> size_t - { return cur + POPCNT64(~sublist.FreeMask); } - ); - if(count > 0) - WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s"); - - count = std::accumulate(FilterList.cbegin(), FilterList.cend(), size_t{0u}, - [](size_t cur, const FilterSubList &sublist) noexcept -> size_t - { return cur + POPCNT64(~sublist.FreeMask); } - ); - if(count > 0) - WARN("%zu Filter%s not deleted\n", count, (count==1)?"":"s"); - - if(mHrtf) - mHrtf->DecRef(); - mHrtf = nullptr; -} - - -static void ALCdevice_IncRef(ALCdevice *device) -{ - auto ref = IncrementRef(&device->ref); - TRACEREF("%p increasing refcount to %u\n", device, ref); -} - -static void ALCdevice_DecRef(ALCdevice *device) -{ - auto ref = DecrementRef(&device->ref); - TRACEREF("%p decreasing refcount to %u\n", device, ref); - if(UNLIKELY(ref == 0)) delete device; -} - -/* Simple RAII device reference. Takes the reference of the provided ALCdevice, - * and decrements it when leaving scope. Movable (transfer reference) but not - * copyable (no new references). - */ -class DeviceRef { - ALCdevice *mDev{nullptr}; - - void reset() noexcept - { - if(mDev) - ALCdevice_DecRef(mDev); - mDev = nullptr; - } - -public: - DeviceRef() noexcept = default; - DeviceRef(DeviceRef&& rhs) noexcept : mDev{rhs.mDev} - { rhs.mDev = nullptr; } - explicit DeviceRef(ALCdevice *dev) noexcept : mDev(dev) { } - ~DeviceRef() { reset(); } - - DeviceRef& operator=(const DeviceRef&) = delete; - DeviceRef& operator=(DeviceRef&& rhs) noexcept - { - reset(); - mDev = rhs.mDev; - rhs.mDev = nullptr; - return *this; - } - - operator bool() const noexcept { return mDev != nullptr; } - - ALCdevice* operator->() noexcept { return mDev; } - ALCdevice* get() noexcept { return mDev; } - - ALCdevice* release() noexcept - { - ALCdevice *ret{mDev}; - mDev = nullptr; - return ret; - } -}; - - -/* VerifyDevice - * - * Checks if the device handle is valid, and returns a new reference if so. - */ -static DeviceRef VerifyDevice(ALCdevice *device) -{ - std::lock_guard _{ListLock}; - auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device); - if(iter != DeviceList.cend() && *iter == device) - { - ALCdevice_IncRef(*iter); - return DeviceRef{*iter}; - } - return DeviceRef{}; -} - - -ALCcontext::ALCcontext(ALCdevice *device) : Device{device} -{ - PropsClean.test_and_set(std::memory_order_relaxed); -} - -/* InitContext - * - * Initializes context fields - */ -static ALvoid InitContext(ALCcontext *Context) -{ - ALlistener &listener = Context->Listener; - ALeffectslotArray *auxslots; - - //Validate Context - if(!Context->DefaultSlot) - auxslots = ALeffectslot::CreatePtrArray(0); - else - { - auxslots = ALeffectslot::CreatePtrArray(1); - (*auxslots)[0] = Context->DefaultSlot.get(); - } - Context->ActiveAuxSlots.store(auxslots, std::memory_order_relaxed); - - //Set globals - Context->mDistanceModel = DistanceModel::Default; - Context->SourceDistanceModel = AL_FALSE; - Context->DopplerFactor = 1.0f; - Context->DopplerVelocity = 1.0f; - Context->SpeedOfSound = SPEEDOFSOUNDMETRESPERSEC; - Context->MetersPerUnit = AL_DEFAULT_METERS_PER_UNIT; - - Context->ExtensionList = alExtList; - - - listener.Params.Matrix = alu::Matrix::Identity(); - listener.Params.Velocity = alu::Vector{}; - listener.Params.Gain = listener.Gain; - listener.Params.MetersPerUnit = Context->MetersPerUnit; - listener.Params.DopplerFactor = Context->DopplerFactor; - listener.Params.SpeedOfSound = Context->SpeedOfSound * Context->DopplerVelocity; - listener.Params.ReverbSpeedOfSound = listener.Params.SpeedOfSound * - listener.Params.MetersPerUnit; - listener.Params.SourceDistanceModel = Context->SourceDistanceModel; - listener.Params.mDistanceModel = Context->mDistanceModel; - - - Context->AsyncEvents = CreateRingBuffer(511, sizeof(AsyncEvent), false); - StartEventThrd(Context); -} - - -/* ALCcontext::~ALCcontext() - * - * Cleans up the context, and destroys any remaining objects the app failed to - * delete. Called once there's no more references on the context. +/** + * Updates device parameters as above, and also first clears the disconnected + * status, if set. */ -ALCcontext::~ALCcontext() +bool ResetDeviceParams(ALCdevice *device, const int *attrList) { - TRACE("%p\n", this); - - ALcontextProps *cprops{Update.exchange(nullptr, std::memory_order_relaxed)}; - if(cprops) - { - TRACE("Freed unapplied context update %p\n", cprops); - al_free(cprops); - } - size_t count{0}; - cprops = FreeContextProps.exchange(nullptr, std::memory_order_acquire); - while(cprops) - { - ALcontextProps *next{cprops->next.load(std::memory_order_relaxed)}; - al_free(cprops); - cprops = next; - ++count; - } - TRACE("Freed %zu context property object%s\n", count, (count==1)?"":"s"); - - count = std::accumulate(SourceList.cbegin(), SourceList.cend(), size_t{0u}, - [](size_t cur, const SourceSubList &sublist) noexcept -> size_t - { return cur + POPCNT64(~sublist.FreeMask); } - ); - if(count > 0) - WARN("%zu Source%s not deleted\n", count, (count==1)?"":"s"); - SourceList.clear(); - NumSources = 0; - - count = 0; - ALeffectslotProps *eprops{FreeEffectslotProps.exchange(nullptr, std::memory_order_acquire)}; - while(eprops) - { - ALeffectslotProps *next{eprops->next.load(std::memory_order_relaxed)}; - if(eprops->State) eprops->State->DecRef(); - al_free(eprops); - eprops = next; - ++count; - } - TRACE("Freed %zu AuxiliaryEffectSlot property object%s\n", count, (count==1)?"":"s"); - - delete ActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed); - DefaultSlot = nullptr; - - count = std::accumulate(EffectSlotList.cbegin(), EffectSlotList.cend(), size_t{0u}, - [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t - { return cur + POPCNT64(~sublist.FreeMask); } - ); - if(count > 0) - WARN("%zu AuxiliaryEffectSlot%s not deleted\n", count, (count==1)?"":"s"); - EffectSlotList.clear(); - NumEffectSlots = 0; - - count = 0; - ALvoiceProps *vprops{FreeVoiceProps.exchange(nullptr, std::memory_order_acquire)}; - while(vprops) + /* If the device was disconnected, reset it since we're opened anew. */ + if UNLIKELY(!device->Connected.load(std::memory_order_relaxed)) { - ALvoiceProps *next{vprops->next.load(std::memory_order_relaxed)}; - delete vprops; - vprops = next; - ++count; - } - TRACE("Freed %zu voice property object%s\n", count, (count==1)?"":"s"); - - std::for_each(Voices, Voices + MaxVoices, DeinitVoice); - al_free(Voices); - Voices = nullptr; - VoiceCount.store(0, std::memory_order_relaxed); - MaxVoices = 0; + /* Make sure disconnection is finished before continuing on. */ + device->waitForMix(); - ALlistenerProps *lprops{Listener.Update.exchange(nullptr, std::memory_order_relaxed)}; - if(lprops) - { - TRACE("Freed unapplied listener update %p\n", lprops); - al_free(lprops); - } - count = 0; - lprops = FreeListenerProps.exchange(nullptr, std::memory_order_acquire); - while(lprops) - { - ALlistenerProps *next{lprops->next.load(std::memory_order_relaxed)}; - al_free(lprops); - lprops = next; - ++count; - } - TRACE("Freed %zu listener property object%s\n", count, (count==1)?"":"s"); - - if(AsyncEvents) - { - count = 0; - auto evt_vec = AsyncEvents->getReadVector(); - while(evt_vec.first.len > 0) - { - reinterpret_cast(evt_vec.first.buf)->~AsyncEvent(); - evt_vec.first.buf += sizeof(AsyncEvent); - evt_vec.first.len -= 1; - ++count; - } - while(evt_vec.second.len > 0) + for(ContextBase *ctxbase : *device->mContexts.load(std::memory_order_acquire)) { - reinterpret_cast(evt_vec.second.buf)->~AsyncEvent(); - evt_vec.second.buf += sizeof(AsyncEvent); - evt_vec.second.len -= 1; - ++count; - } - if(count > 0) - TRACE("Destructed %zu orphaned event%s\n", count, (count==1)?"":"s"); - } - - ALCdevice_DecRef(Device); -} - -/* ReleaseContext - * - * Removes the context reference from the given device and removes it from - * being current on the running thread or globally. Returns true if other - * contexts still exist on the device. - */ -static bool ReleaseContext(ALCcontext *context, ALCdevice *device) -{ - if(LocalContext.get() == context) - { - WARN("%p released while current on thread\n", context); - LocalContext.set(nullptr); - ALCcontext_DecRef(context); - } - - ALCcontext *origctx{context}; - if(GlobalContext.compare_exchange_strong(origctx, nullptr)) - ALCcontext_DecRef(context); + auto *ctx = static_cast(ctxbase); + if(!ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) + continue; - bool ret{true}; - { BackendLockGuard _{*device->Backend}; - origctx = context; - ALCcontext *newhead{context->next.load(std::memory_order_relaxed)}; - if(!device->ContextList.compare_exchange_strong(origctx, newhead)) - { - ALCcontext *list; - do { - /* origctx is what the desired context failed to match. Try - * swapping out the next one in the list. - */ - list = origctx; - origctx = context; - } while(!list->next.compare_exchange_strong(origctx, newhead)); + /* Clear any pending voice changes and reallocate voices to get a + * clean restart. + */ + std::lock_guard __{ctx->mSourceLock}; + auto *vchg = ctx->mCurrentVoiceChange.load(std::memory_order_acquire); + while(auto *next = vchg->mNext.load(std::memory_order_acquire)) + vchg = next; + ctx->mCurrentVoiceChange.store(vchg, std::memory_order_release); + + ctx->mVoicePropClusters.clear(); + ctx->mFreeVoiceProps.store(nullptr, std::memory_order_relaxed); + + ctx->mVoiceClusters.clear(); + ctx->allocVoices(std::max(256, + ctx->mActiveVoiceCount.load(std::memory_order_relaxed))); } - else - ret = !!newhead; - } - /* Make sure the context is finished and no longer processing in the mixer - * before sending the message queue kill event. The backend's lock does - * this, although waiting for a non-odd mix count would work too. - */ + device->Connected.store(true); + } - StopEventThrd(context); + ALCenum err{UpdateDeviceParams(device, attrList)}; + if LIKELY(err == ALC_NO_ERROR) return ALC_TRUE; - ALCcontext_DecRef(context); - return ret; + alcSetError(device, err); + return ALC_FALSE; } -static void ALCcontext_IncRef(ALCcontext *context) -{ - auto ref = IncrementRef(&context->ref); - TRACEREF("%p increasing refcount to %u\n", context, ref); -} -void ALCcontext_DecRef(ALCcontext *context) +/** Checks if the device handle is valid, and returns a new reference if so. */ +DeviceRef VerifyDevice(ALCdevice *device) { - auto ref = DecrementRef(&context->ref); - TRACEREF("%p decreasing refcount to %u\n", context, ref); - if(UNLIKELY(ref == 0)) delete context; + std::lock_guard _{ListLock}; + auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); + if(iter != DeviceList.end() && *iter == device) + { + (*iter)->add_ref(); + return DeviceRef{*iter}; + } + return nullptr; } -/* VerifyContext - * + +/** * Checks if the given context is valid, returning a new reference to it if so. */ -static ContextRef VerifyContext(ALCcontext *context) +ContextRef VerifyContext(ALCcontext *context) { std::lock_guard _{ListLock}; - auto iter = std::lower_bound(ContextList.cbegin(), ContextList.cend(), context); - if(iter != ContextList.cend() && *iter == context) + auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), context); + if(iter != ContextList.end() && *iter == context) { - ALCcontext_IncRef(*iter); + (*iter)->add_ref(); return ContextRef{*iter}; } - return ContextRef{}; + return nullptr; } +} // namespace -/* GetContextRef - * - * Returns a new reference to the currently active context for this thread. - */ +/** Returns a new reference to the currently active context for this thread. */ ContextRef GetContextRef(void) { - ALCcontext *context{LocalContext.get()}; + ALCcontext *context{ALCcontext::getThreadContext()}; if(context) - ALCcontext_IncRef(context); + context->add_ref(); else { std::lock_guard _{ListLock}; - context = GlobalContext.load(std::memory_order_acquire); - if(context) ALCcontext_IncRef(context); + context = ALCcontext::sGlobalContext.load(std::memory_order_acquire); + if(context) context->add_ref(); } return ContextRef{context}; } -void AllocateVoices(ALCcontext *context, ALsizei num_voices, ALsizei old_sends) -{ - ALCdevice *device{context->Device}; - const ALsizei num_sends{device->NumAuxSends}; - - if(num_voices == context->MaxVoices && num_sends == old_sends) - return; - - /* Allocate the voice pointers, voices, and the voices' stored source - * property set (including the dynamically-sized Send[] array) in one - * chunk. - */ - const size_t sizeof_voice{RoundUp(ALvoice::Sizeof(num_sends), 16)}; - const size_t size{sizeof(ALvoice*) + sizeof_voice}; - - auto voices = static_cast(al_calloc(16, RoundUp(size*num_voices, 16))); - auto voice = reinterpret_cast(reinterpret_cast(voices) + RoundUp(num_voices*sizeof(ALvoice*), 16)); - - auto viter = voices; - if(context->Voices) - { - const ALsizei v_count = mini(context->VoiceCount.load(std::memory_order_relaxed), - num_voices); - const ALsizei s_count = mini(old_sends, num_sends); - - /* Copy the old voice data to the new storage. */ - auto copy_voice = [&voice,num_sends,sizeof_voice,s_count](ALvoice *old_voice) -> ALvoice* - { - voice = new (voice) ALvoice{static_cast(num_sends)}; - - /* Make sure the old voice's Update (if any) is cleared so it - * doesn't get deleted on deinit. - */ - voice->mUpdate.store(old_voice->mUpdate.exchange(nullptr, std::memory_order_relaxed), - std::memory_order_relaxed); - - voice->mSourceID.store(old_voice->mSourceID.load(std::memory_order_relaxed), - std::memory_order_relaxed); - voice->mPlayState.store(old_voice->mPlayState.load(std::memory_order_relaxed), - std::memory_order_relaxed); - - voice->mProps = old_voice->mProps; - /* Clear extraneous property set sends. */ - std::fill(std::begin(voice->mProps.Send)+s_count, std::end(voice->mProps.Send), - ALvoiceProps::SendData{}); - - voice->mPosition.store(old_voice->mPosition.load(std::memory_order_relaxed), - std::memory_order_relaxed); - voice->mPositionFrac.store(old_voice->mPositionFrac.load(std::memory_order_relaxed), - std::memory_order_relaxed); - - voice->mCurrentBuffer.store(old_voice->mCurrentBuffer.load(std::memory_order_relaxed), - std::memory_order_relaxed); - voice->mLoopBuffer.store(old_voice->mLoopBuffer.load(std::memory_order_relaxed), - std::memory_order_relaxed); - - voice->mFrequency = old_voice->mFrequency; - voice->mFmtChannels = old_voice->mFmtChannels; - voice->mNumChannels = old_voice->mNumChannels; - voice->mSampleSize = old_voice->mSampleSize; - - voice->mStep = old_voice->mStep; - voice->mResampler = old_voice->mResampler; - - voice->mResampleState = old_voice->mResampleState; - - voice->mFlags = old_voice->mFlags; - - std::copy(old_voice->mResampleData.begin(), old_voice->mResampleData.end(), - voice->mResampleData.end()); - - voice->mDirect = old_voice->mDirect; - std::copy_n(old_voice->mSend.begin(), s_count, voice->mSend.begin()); - - /* Set this voice's reference. */ - ALvoice *ret = voice; - /* Increment pointer to the next storage space. */ - voice = reinterpret_cast(reinterpret_cast(voice) + sizeof_voice); - return ret; - }; - viter = std::transform(context->Voices, context->Voices+v_count, viter, copy_voice); - - /* Deinit old voices. */ - auto voices_end = context->Voices + context->MaxVoices; - std::for_each(context->Voices, voices_end, DeinitVoice); - } - /* Finish setting the voices and references. */ - auto init_voice = [&voice,num_sends,sizeof_voice]() -> ALvoice* - { - ALvoice *ret = new (voice) ALvoice{static_cast(num_sends)}; - voice = reinterpret_cast(reinterpret_cast(voice) + sizeof_voice); - return ret; - }; - std::generate(viter, voices+num_voices, init_voice); - - al_free(context->Voices); - context->Voices = voices; - context->MaxVoices = num_voices; - context->VoiceCount = mini(context->VoiceCount.load(std::memory_order_relaxed), num_voices); -} - - /************************************************ * Standard ALC functions ************************************************/ -/* alcGetError - * - * Return last ALC generated error code for the given device - */ ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) START_API_FUNC { @@ -2700,11 +2438,7 @@ START_API_FUNC END_API_FUNC -/* alcSuspendContext - * - * Suspends updates for the given context - */ -ALC_API ALCvoid ALC_APIENTRY alcSuspendContext(ALCcontext *context) +ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) START_API_FUNC { if(!SuspendDefers) @@ -2714,15 +2448,14 @@ START_API_FUNC if(!ctx) alcSetError(nullptr, ALC_INVALID_CONTEXT); else - ALCcontext_DeferUpdates(ctx.get()); + { + std::lock_guard _{ctx->mPropLock}; + ctx->deferUpdates(); + } } END_API_FUNC -/* alcProcessContext - * - * Resumes processing updates for the given context - */ -ALC_API ALCvoid ALC_APIENTRY alcProcessContext(ALCcontext *context) +ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) START_API_FUNC { if(!SuspendDefers) @@ -2732,20 +2465,18 @@ START_API_FUNC if(!ctx) alcSetError(nullptr, ALC_INVALID_CONTEXT); else - ALCcontext_ProcessUpdates(ctx.get()); + { + std::lock_guard _{ctx->mPropLock}; + ctx->processUpdates(); + } } END_API_FUNC -/* alcGetString - * - * Returns information about the device, and error strings - */ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param) START_API_FUNC { - const ALCchar *value = nullptr; - DeviceRef dev; + const ALCchar *value{nullptr}; switch(param) { @@ -2778,9 +2509,18 @@ START_API_FUNC break; case ALC_ALL_DEVICES_SPECIFIER: - dev = VerifyDevice(Device); - if(dev) - value = dev->DeviceName.c_str(); + if(DeviceRef dev{VerifyDevice(Device)}) + { + if(dev->Type == DeviceType::Capture) + alcSetError(dev.get(), ALC_INVALID_ENUM); + else if(dev->Type == DeviceType::Loopback) + value = alcDefaultName; + else + { + std::lock_guard _{dev->StateLock}; + value = dev->DeviceName.c_str(); + } + } else { ProbeAllDevicesList(); @@ -2789,9 +2529,16 @@ START_API_FUNC break; case ALC_CAPTURE_DEVICE_SPECIFIER: - dev = VerifyDevice(Device); - if(dev) - value = dev->DeviceName.c_str(); + if(DeviceRef dev{VerifyDevice(Device)}) + { + if(dev->Type != DeviceType::Capture) + alcSetError(dev.get(), ALC_INVALID_ENUM); + else + { + std::lock_guard _{dev->StateLock}; + value = dev->DeviceName.c_str(); + } + } else { ProbeCaptureDeviceList(); @@ -2823,25 +2570,24 @@ START_API_FUNC break; case ALC_EXTENSIONS: - dev = VerifyDevice(Device); - if(dev) value = alcExtensionList; - else value = alcNoDeviceExtList; + if(VerifyDevice(Device)) + value = alcExtensionList; + else + value = alcNoDeviceExtList; break; case ALC_HRTF_SPECIFIER_SOFT: - dev = VerifyDevice(Device); - if(!dev) - alcSetError(nullptr, ALC_INVALID_DEVICE); - else + if(DeviceRef dev{VerifyDevice(Device)}) { std::lock_guard _{dev->StateLock}; - value = (dev->mHrtf ? dev->HrtfName.c_str() : ""); + value = (dev->mHrtf ? dev->mHrtfName.c_str() : ""); } + else + alcSetError(nullptr, ALC_INVALID_DEVICE); break; default: - dev = VerifyDevice(Device); - alcSetError(dev.get(), ALC_INVALID_ENUM); + alcSetError(VerifyDevice(Device).get(), ALC_INVALID_ENUM); break; } @@ -2850,20 +2596,11 @@ START_API_FUNC END_API_FUNC -static inline ALCsizei NumAttrsForDevice(ALCdevice *device) -{ - if(device->Type == Capture) return 9; - if(device->Type != Loopback) return 29; - if(device->FmtChans == DevFmtAmbi3D) - return 35; - return 29; -} - -static ALCsizei GetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) +static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values) { - ALCsizei i; + size_t i; - if(size <= 0 || values == nullptr) + if(values.empty()) { alcSetError(device, ALC_INVALID_VALUE); return 0; @@ -2873,307 +2610,313 @@ static ALCsizei GetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALC { switch(param) { - case ALC_MAJOR_VERSION: - values[0] = alcMajorVersion; - return 1; - case ALC_MINOR_VERSION: - values[0] = alcMinorVersion; - return 1; - - case ALC_ATTRIBUTES_SIZE: - case ALC_ALL_ATTRIBUTES: - case ALC_FREQUENCY: - case ALC_REFRESH: - case ALC_SYNC: - case ALC_MONO_SOURCES: - case ALC_STEREO_SOURCES: - case ALC_CAPTURE_SAMPLES: - case ALC_FORMAT_CHANNELS_SOFT: - case ALC_FORMAT_TYPE_SOFT: - case ALC_AMBISONIC_LAYOUT_SOFT: - case ALC_AMBISONIC_SCALING_SOFT: - case ALC_AMBISONIC_ORDER_SOFT: - case ALC_MAX_AMBISONIC_ORDER_SOFT: - alcSetError(nullptr, ALC_INVALID_DEVICE); - return 0; + case ALC_MAJOR_VERSION: + values[0] = alcMajorVersion; + return 1; + case ALC_MINOR_VERSION: + values[0] = alcMinorVersion; + return 1; - default: - alcSetError(nullptr, ALC_INVALID_ENUM); - return 0; + case ALC_EFX_MAJOR_VERSION: + values[0] = alcEFXMajorVersion; + return 1; + case ALC_EFX_MINOR_VERSION: + values[0] = alcEFXMinorVersion; + return 1; + case ALC_MAX_AUXILIARY_SENDS: + values[0] = MAX_SENDS; + return 1; + + case ALC_ATTRIBUTES_SIZE: + case ALC_ALL_ATTRIBUTES: + case ALC_FREQUENCY: + case ALC_REFRESH: + case ALC_SYNC: + case ALC_MONO_SOURCES: + case ALC_STEREO_SOURCES: + case ALC_CAPTURE_SAMPLES: + case ALC_FORMAT_CHANNELS_SOFT: + case ALC_FORMAT_TYPE_SOFT: + case ALC_AMBISONIC_LAYOUT_SOFT: + case ALC_AMBISONIC_SCALING_SOFT: + case ALC_AMBISONIC_ORDER_SOFT: + case ALC_MAX_AMBISONIC_ORDER_SOFT: + alcSetError(nullptr, ALC_INVALID_DEVICE); + return 0; + + default: + alcSetError(nullptr, ALC_INVALID_ENUM); } return 0; } - if(device->Type == Capture) + std::lock_guard _{device->StateLock}; + if(device->Type == DeviceType::Capture) { + static constexpr int MaxCaptureAttributes{9}; switch(param) { - case ALC_ATTRIBUTES_SIZE: - values[0] = NumAttrsForDevice(device); - return 1; - - case ALC_ALL_ATTRIBUTES: - i = 0; - if(size < NumAttrsForDevice(device)) - alcSetError(device, ALC_INVALID_VALUE); - else - { - std::lock_guard _{device->StateLock}; - values[i++] = ALC_MAJOR_VERSION; - values[i++] = alcMajorVersion; - values[i++] = ALC_MINOR_VERSION; - values[i++] = alcMinorVersion; - values[i++] = ALC_CAPTURE_SAMPLES; - values[i++] = device->Backend->availableSamples(); - values[i++] = ALC_CONNECTED; - values[i++] = device->Connected.load(std::memory_order_relaxed); - values[i++] = 0; - } - return i; - - case ALC_MAJOR_VERSION: - values[0] = alcMajorVersion; - return 1; - case ALC_MINOR_VERSION: - values[0] = alcMinorVersion; - return 1; - - case ALC_CAPTURE_SAMPLES: - { std::lock_guard _{device->StateLock}; - values[0] = device->Backend->availableSamples(); - } - return 1; + case ALC_ATTRIBUTES_SIZE: + values[0] = MaxCaptureAttributes; + return 1; + case ALC_ALL_ATTRIBUTES: + i = 0; + if(values.size() < MaxCaptureAttributes) + alcSetError(device, ALC_INVALID_VALUE); + else + { + values[i++] = ALC_MAJOR_VERSION; + values[i++] = alcMajorVersion; + values[i++] = ALC_MINOR_VERSION; + values[i++] = alcMinorVersion; + values[i++] = ALC_CAPTURE_SAMPLES; + values[i++] = static_cast(device->Backend->availableSamples()); + values[i++] = ALC_CONNECTED; + values[i++] = device->Connected.load(std::memory_order_relaxed); + values[i++] = 0; + assert(i == MaxCaptureAttributes); + } + return i; - case ALC_CONNECTED: - { std::lock_guard _{device->StateLock}; - values[0] = device->Connected.load(std::memory_order_acquire); - } - return 1; + case ALC_MAJOR_VERSION: + values[0] = alcMajorVersion; + return 1; + case ALC_MINOR_VERSION: + values[0] = alcMinorVersion; + return 1; - default: - alcSetError(device, ALC_INVALID_ENUM); - return 0; + case ALC_CAPTURE_SAMPLES: + values[0] = static_cast(device->Backend->availableSamples()); + return 1; + + case ALC_CONNECTED: + values[0] = device->Connected.load(std::memory_order_acquire); + return 1; + + default: + alcSetError(device, ALC_INVALID_ENUM); } return 0; } /* render device */ + auto NumAttrsForDevice = [](ALCdevice *aldev) noexcept + { + if(aldev->Type == DeviceType::Loopback && aldev->FmtChans == DevFmtAmbi3D) + return 37; + return 31; + }; switch(param) { - case ALC_ATTRIBUTES_SIZE: - values[0] = NumAttrsForDevice(device); - return 1; + case ALC_ATTRIBUTES_SIZE: + values[0] = NumAttrsForDevice(device); + return 1; - case ALC_ALL_ATTRIBUTES: - i = 0; - if(size < NumAttrsForDevice(device)) - alcSetError(device, ALC_INVALID_VALUE); + case ALC_ALL_ATTRIBUTES: + i = 0; + if(values.size() < static_cast(NumAttrsForDevice(device))) + alcSetError(device, ALC_INVALID_VALUE); + else + { + values[i++] = ALC_MAJOR_VERSION; + values[i++] = alcMajorVersion; + values[i++] = ALC_MINOR_VERSION; + values[i++] = alcMinorVersion; + values[i++] = ALC_EFX_MAJOR_VERSION; + values[i++] = alcEFXMajorVersion; + values[i++] = ALC_EFX_MINOR_VERSION; + values[i++] = alcEFXMinorVersion; + + values[i++] = ALC_FREQUENCY; + values[i++] = static_cast(device->Frequency); + if(device->Type != DeviceType::Loopback) + { + values[i++] = ALC_REFRESH; + values[i++] = static_cast(device->Frequency / device->UpdateSize); + + values[i++] = ALC_SYNC; + values[i++] = ALC_FALSE; + } else { - std::lock_guard _{device->StateLock}; - values[i++] = ALC_MAJOR_VERSION; - values[i++] = alcMajorVersion; - values[i++] = ALC_MINOR_VERSION; - values[i++] = alcMinorVersion; - values[i++] = ALC_EFX_MAJOR_VERSION; - values[i++] = alcEFXMajorVersion; - values[i++] = ALC_EFX_MINOR_VERSION; - values[i++] = alcEFXMinorVersion; - - values[i++] = ALC_FREQUENCY; - values[i++] = device->Frequency; - if(device->Type != Loopback) + if(device->FmtChans == DevFmtAmbi3D) { - values[i++] = ALC_REFRESH; - values[i++] = device->Frequency / device->UpdateSize; + values[i++] = ALC_AMBISONIC_LAYOUT_SOFT; + values[i++] = EnumFromDevAmbi(device->mAmbiLayout); + + values[i++] = ALC_AMBISONIC_SCALING_SOFT; + values[i++] = EnumFromDevAmbi(device->mAmbiScale); - values[i++] = ALC_SYNC; - values[i++] = ALC_FALSE; + values[i++] = ALC_AMBISONIC_ORDER_SOFT; + values[i++] = static_cast(device->mAmbiOrder); } - else - { - if(device->FmtChans == DevFmtAmbi3D) - { - values[i++] = ALC_AMBISONIC_LAYOUT_SOFT; - values[i++] = static_cast(device->mAmbiLayout); - values[i++] = ALC_AMBISONIC_SCALING_SOFT; - values[i++] = static_cast(device->mAmbiScale); + values[i++] = ALC_FORMAT_CHANNELS_SOFT; + values[i++] = EnumFromDevFmt(device->FmtChans); - values[i++] = ALC_AMBISONIC_ORDER_SOFT; - values[i++] = device->mAmbiOrder; - } + values[i++] = ALC_FORMAT_TYPE_SOFT; + values[i++] = EnumFromDevFmt(device->FmtType); + } - values[i++] = ALC_FORMAT_CHANNELS_SOFT; - values[i++] = device->FmtChans; + values[i++] = ALC_MONO_SOURCES; + values[i++] = static_cast(device->NumMonoSources); - values[i++] = ALC_FORMAT_TYPE_SOFT; - values[i++] = device->FmtType; - } + values[i++] = ALC_STEREO_SOURCES; + values[i++] = static_cast(device->NumStereoSources); - values[i++] = ALC_MONO_SOURCES; - values[i++] = device->NumMonoSources; + values[i++] = ALC_MAX_AUXILIARY_SENDS; + values[i++] = static_cast(device->NumAuxSends); - values[i++] = ALC_STEREO_SOURCES; - values[i++] = device->NumStereoSources; + values[i++] = ALC_HRTF_SOFT; + values[i++] = (device->mHrtf ? ALC_TRUE : ALC_FALSE); - values[i++] = ALC_MAX_AUXILIARY_SENDS; - values[i++] = device->NumAuxSends; + values[i++] = ALC_HRTF_STATUS_SOFT; + values[i++] = device->mHrtfStatus; - values[i++] = ALC_HRTF_SOFT; - values[i++] = (device->mHrtf ? ALC_TRUE : ALC_FALSE); + values[i++] = ALC_OUTPUT_LIMITER_SOFT; + values[i++] = device->Limiter ? ALC_TRUE : ALC_FALSE; - values[i++] = ALC_HRTF_STATUS_SOFT; - values[i++] = device->HrtfStatus; + values[i++] = ALC_MAX_AMBISONIC_ORDER_SOFT; + values[i++] = MaxAmbiOrder; - values[i++] = ALC_OUTPUT_LIMITER_SOFT; - values[i++] = device->Limiter ? ALC_TRUE : ALC_FALSE; + values[i++] = ALC_OUTPUT_MODE_SOFT; + values[i++] = static_cast(device->getOutputMode1()); - values[i++] = ALC_MAX_AMBISONIC_ORDER_SOFT; - values[i++] = MAX_AMBI_ORDER; + values[i++] = 0; + } + return i; - values[i++] = 0; - } - return i; + case ALC_MAJOR_VERSION: + values[0] = alcMajorVersion; + return 1; - case ALC_MAJOR_VERSION: - values[0] = alcMajorVersion; - return 1; + case ALC_MINOR_VERSION: + values[0] = alcMinorVersion; + return 1; - case ALC_MINOR_VERSION: - values[0] = alcMinorVersion; - return 1; + case ALC_EFX_MAJOR_VERSION: + values[0] = alcEFXMajorVersion; + return 1; - case ALC_EFX_MAJOR_VERSION: - values[0] = alcEFXMajorVersion; - return 1; + case ALC_EFX_MINOR_VERSION: + values[0] = alcEFXMinorVersion; + return 1; - case ALC_EFX_MINOR_VERSION: - values[0] = alcEFXMinorVersion; - return 1; + case ALC_FREQUENCY: + values[0] = static_cast(device->Frequency); + return 1; - case ALC_FREQUENCY: - values[0] = device->Frequency; - return 1; + case ALC_REFRESH: + if(device->Type == DeviceType::Loopback) + { + alcSetError(device, ALC_INVALID_DEVICE); + return 0; + } + values[0] = static_cast(device->Frequency / device->UpdateSize); + return 1; - case ALC_REFRESH: - if(device->Type == Loopback) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - { std::lock_guard _{device->StateLock}; - values[0] = device->Frequency / device->UpdateSize; - } - return 1; + case ALC_SYNC: + if(device->Type == DeviceType::Loopback) + { + alcSetError(device, ALC_INVALID_DEVICE); + return 0; + } + values[0] = ALC_FALSE; + return 1; - case ALC_SYNC: - if(device->Type == Loopback) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = ALC_FALSE; - return 1; + case ALC_FORMAT_CHANNELS_SOFT: + if(device->Type != DeviceType::Loopback) + { + alcSetError(device, ALC_INVALID_DEVICE); + return 0; + } + values[0] = EnumFromDevFmt(device->FmtChans); + return 1; - case ALC_FORMAT_CHANNELS_SOFT: - if(device->Type != Loopback) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = device->FmtChans; - return 1; + case ALC_FORMAT_TYPE_SOFT: + if(device->Type != DeviceType::Loopback) + { + alcSetError(device, ALC_INVALID_DEVICE); + return 0; + } + values[0] = EnumFromDevFmt(device->FmtType); + return 1; - case ALC_FORMAT_TYPE_SOFT: - if(device->Type != Loopback) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = device->FmtType; - return 1; + case ALC_AMBISONIC_LAYOUT_SOFT: + if(device->Type != DeviceType::Loopback || device->FmtChans != DevFmtAmbi3D) + { + alcSetError(device, ALC_INVALID_DEVICE); + return 0; + } + values[0] = EnumFromDevAmbi(device->mAmbiLayout); + return 1; - case ALC_AMBISONIC_LAYOUT_SOFT: - if(device->Type != Loopback || device->FmtChans != DevFmtAmbi3D) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = static_cast(device->mAmbiLayout); - return 1; + case ALC_AMBISONIC_SCALING_SOFT: + if(device->Type != DeviceType::Loopback || device->FmtChans != DevFmtAmbi3D) + { + alcSetError(device, ALC_INVALID_DEVICE); + return 0; + } + values[0] = EnumFromDevAmbi(device->mAmbiScale); + return 1; - case ALC_AMBISONIC_SCALING_SOFT: - if(device->Type != Loopback || device->FmtChans != DevFmtAmbi3D) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = static_cast(device->mAmbiScale); - return 1; + case ALC_AMBISONIC_ORDER_SOFT: + if(device->Type != DeviceType::Loopback || device->FmtChans != DevFmtAmbi3D) + { + alcSetError(device, ALC_INVALID_DEVICE); + return 0; + } + values[0] = static_cast(device->mAmbiOrder); + return 1; - case ALC_AMBISONIC_ORDER_SOFT: - if(device->Type != Loopback || device->FmtChans != DevFmtAmbi3D) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = device->mAmbiOrder; - return 1; + case ALC_MONO_SOURCES: + values[0] = static_cast(device->NumMonoSources); + return 1; - case ALC_MONO_SOURCES: - values[0] = device->NumMonoSources; - return 1; + case ALC_STEREO_SOURCES: + values[0] = static_cast(device->NumStereoSources); + return 1; - case ALC_STEREO_SOURCES: - values[0] = device->NumStereoSources; - return 1; + case ALC_MAX_AUXILIARY_SENDS: + values[0] = static_cast(device->NumAuxSends); + return 1; - case ALC_MAX_AUXILIARY_SENDS: - values[0] = device->NumAuxSends; - return 1; + case ALC_CONNECTED: + values[0] = device->Connected.load(std::memory_order_acquire); + return 1; - case ALC_CONNECTED: - { std::lock_guard _{device->StateLock}; - values[0] = device->Connected.load(std::memory_order_acquire); - } - return 1; + case ALC_HRTF_SOFT: + values[0] = (device->mHrtf ? ALC_TRUE : ALC_FALSE); + return 1; - case ALC_HRTF_SOFT: - values[0] = (device->mHrtf ? ALC_TRUE : ALC_FALSE); - return 1; + case ALC_HRTF_STATUS_SOFT: + values[0] = device->mHrtfStatus; + return 1; - case ALC_HRTF_STATUS_SOFT: - values[0] = device->HrtfStatus; - return 1; + case ALC_NUM_HRTF_SPECIFIERS_SOFT: + device->enumerateHrtfs(); + values[0] = static_cast(minz(device->mHrtfList.size(), + std::numeric_limits::max())); + return 1; - case ALC_NUM_HRTF_SPECIFIERS_SOFT: - { std::lock_guard _{device->StateLock}; - device->HrtfList.clear(); - device->HrtfList = EnumerateHrtf(device->DeviceName.c_str()); - values[0] = static_cast(device->HrtfList.size()); - } - return 1; + case ALC_OUTPUT_LIMITER_SOFT: + values[0] = device->Limiter ? ALC_TRUE : ALC_FALSE; + return 1; - case ALC_OUTPUT_LIMITER_SOFT: - values[0] = device->Limiter ? ALC_TRUE : ALC_FALSE; - return 1; + case ALC_MAX_AMBISONIC_ORDER_SOFT: + values[0] = MaxAmbiOrder; + return 1; - case ALC_MAX_AMBISONIC_ORDER_SOFT: - values[0] = MAX_AMBI_ORDER; - return 1; + case ALC_OUTPUT_MODE_SOFT: + values[0] = static_cast(device->getOutputMode1()); + return 1; - default: - alcSetError(device, ALC_INVALID_ENUM); - return 0; + default: + alcSetError(device, ALC_INVALID_ENUM); } return 0; } -/* alcGetIntegerv - * - * Returns information about the device and the version of OpenAL - */ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) START_API_FUNC { @@ -3181,7 +2924,7 @@ START_API_FUNC if(size <= 0 || values == nullptr) alcSetError(dev.get(), ALC_INVALID_VALUE); else - GetIntegerv(dev.get(), param, size, values); + GetIntegerv(dev.get(), param, {values, static_cast(size)}); } END_API_FUNC @@ -3190,139 +2933,140 @@ START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; if(size <= 0 || values == nullptr) + { alcSetError(dev.get(), ALC_INVALID_VALUE); - else if(!dev || dev->Type == Capture) + return; + } + if(!dev || dev->Type == DeviceType::Capture) { - al::vector ivals(size); - size = GetIntegerv(dev.get(), pname, size, ivals.data()); - std::copy(ivals.begin(), ivals.begin()+size, values); + auto ivals = al::vector(static_cast(size)); + if(size_t got{GetIntegerv(dev.get(), pname, ivals)}) + std::copy_n(ivals.begin(), got, values); + return; } - else /* render device */ + /* render device */ + auto NumAttrsForDevice = [](ALCdevice *aldev) noexcept { - switch(pname) - { - case ALC_ATTRIBUTES_SIZE: - *values = NumAttrsForDevice(dev.get())+4; - break; + if(aldev->Type == DeviceType::Loopback && aldev->FmtChans == DevFmtAmbi3D) + return 41; + return 35; + }; + std::lock_guard _{dev->StateLock}; + switch(pname) + { + case ALC_ATTRIBUTES_SIZE: + *values = NumAttrsForDevice(dev.get()); + break; - case ALC_ALL_ATTRIBUTES: - if(size < NumAttrsForDevice(dev.get())+4) - alcSetError(dev.get(), ALC_INVALID_VALUE); - else - { - ALsizei i{0}; - std::lock_guard _{dev->StateLock}; - values[i++] = ALC_FREQUENCY; - values[i++] = dev->Frequency; + case ALC_ALL_ATTRIBUTES: + if(size < NumAttrsForDevice(dev.get())) + alcSetError(dev.get(), ALC_INVALID_VALUE); + else + { + size_t i{0}; + values[i++] = ALC_FREQUENCY; + values[i++] = dev->Frequency; - if(dev->Type != Loopback) - { - values[i++] = ALC_REFRESH; - values[i++] = dev->Frequency / dev->UpdateSize; + if(dev->Type != DeviceType::Loopback) + { + values[i++] = ALC_REFRESH; + values[i++] = dev->Frequency / dev->UpdateSize; - values[i++] = ALC_SYNC; - values[i++] = ALC_FALSE; - } - else - { - if(dev->FmtChans == DevFmtAmbi3D) - { - values[i++] = ALC_AMBISONIC_LAYOUT_SOFT; - values[i++] = static_cast(dev->mAmbiLayout); + values[i++] = ALC_SYNC; + values[i++] = ALC_FALSE; + } + else + { + values[i++] = ALC_FORMAT_CHANNELS_SOFT; + values[i++] = EnumFromDevFmt(dev->FmtChans); - values[i++] = ALC_AMBISONIC_SCALING_SOFT; - values[i++] = static_cast(dev->mAmbiScale); + values[i++] = ALC_FORMAT_TYPE_SOFT; + values[i++] = EnumFromDevFmt(dev->FmtType); - values[i++] = ALC_AMBISONIC_ORDER_SOFT; - values[i++] = dev->mAmbiOrder; - } + if(dev->FmtChans == DevFmtAmbi3D) + { + values[i++] = ALC_AMBISONIC_LAYOUT_SOFT; + values[i++] = EnumFromDevAmbi(dev->mAmbiLayout); - values[i++] = ALC_FORMAT_CHANNELS_SOFT; - values[i++] = dev->FmtChans; + values[i++] = ALC_AMBISONIC_SCALING_SOFT; + values[i++] = EnumFromDevAmbi(dev->mAmbiScale); - values[i++] = ALC_FORMAT_TYPE_SOFT; - values[i++] = dev->FmtType; - } + values[i++] = ALC_AMBISONIC_ORDER_SOFT; + values[i++] = dev->mAmbiOrder; + } + } - values[i++] = ALC_MONO_SOURCES; - values[i++] = dev->NumMonoSources; + values[i++] = ALC_MONO_SOURCES; + values[i++] = dev->NumMonoSources; - values[i++] = ALC_STEREO_SOURCES; - values[i++] = dev->NumStereoSources; + values[i++] = ALC_STEREO_SOURCES; + values[i++] = dev->NumStereoSources; - values[i++] = ALC_MAX_AUXILIARY_SENDS; - values[i++] = dev->NumAuxSends; + values[i++] = ALC_MAX_AUXILIARY_SENDS; + values[i++] = dev->NumAuxSends; - values[i++] = ALC_HRTF_SOFT; - values[i++] = (dev->mHrtf ? ALC_TRUE : ALC_FALSE); + values[i++] = ALC_HRTF_SOFT; + values[i++] = (dev->mHrtf ? ALC_TRUE : ALC_FALSE); - values[i++] = ALC_HRTF_STATUS_SOFT; - values[i++] = dev->HrtfStatus; + values[i++] = ALC_HRTF_STATUS_SOFT; + values[i++] = dev->mHrtfStatus; - values[i++] = ALC_OUTPUT_LIMITER_SOFT; - values[i++] = dev->Limiter ? ALC_TRUE : ALC_FALSE; + values[i++] = ALC_OUTPUT_LIMITER_SOFT; + values[i++] = dev->Limiter ? ALC_TRUE : ALC_FALSE; - ClockLatency clock{GetClockLatency(dev.get())}; - values[i++] = ALC_DEVICE_CLOCK_SOFT; - values[i++] = clock.ClockTime.count(); + ClockLatency clock{GetClockLatency(dev.get(), dev->Backend.get())}; + values[i++] = ALC_DEVICE_CLOCK_SOFT; + values[i++] = clock.ClockTime.count(); - values[i++] = ALC_DEVICE_LATENCY_SOFT; - values[i++] = clock.Latency.count(); + values[i++] = ALC_DEVICE_LATENCY_SOFT; + values[i++] = clock.Latency.count(); - values[i++] = 0; - } - break; + values[i++] = ALC_OUTPUT_MODE_SOFT; + values[i++] = static_cast(device->getOutputMode1()); - case ALC_DEVICE_CLOCK_SOFT: - { std::lock_guard _{dev->StateLock}; - nanoseconds basecount; - ALuint samplecount; - ALuint refcount; - do { - while(((refcount=ReadRef(&dev->MixCount))&1) != 0) - std::this_thread::yield(); - basecount = dev->ClockBase; - samplecount = dev->SamplesDone; - } while(refcount != ReadRef(&dev->MixCount)); - basecount += nanoseconds{seconds{samplecount}} / dev->Frequency; - *values = basecount.count(); - } - break; + values[i++] = 0; + } + break; - case ALC_DEVICE_LATENCY_SOFT: - { std::lock_guard _{dev->StateLock}; - ClockLatency clock{GetClockLatency(dev.get())}; - *values = clock.Latency.count(); - } - break; + case ALC_DEVICE_CLOCK_SOFT: + { + uint samplecount, refcount; + nanoseconds basecount; + do { + refcount = dev->waitForMix(); + basecount = dev->ClockBase; + samplecount = dev->SamplesDone; + } while(refcount != ReadRef(dev->MixCount)); + basecount += nanoseconds{seconds{samplecount}} / dev->Frequency; + *values = basecount.count(); + } + break; - case ALC_DEVICE_CLOCK_LATENCY_SOFT: - if(size < 2) - alcSetError(dev.get(), ALC_INVALID_VALUE); - else - { - std::lock_guard _{dev->StateLock}; - ClockLatency clock{GetClockLatency(dev.get())}; - values[0] = clock.ClockTime.count(); - values[1] = clock.Latency.count(); - } - break; + case ALC_DEVICE_LATENCY_SOFT: + *values = GetClockLatency(dev.get(), dev->Backend.get()).Latency.count(); + break; - default: - al::vector ivals(size); - size = GetIntegerv(dev.get(), pname, size, ivals.data()); - std::copy(ivals.begin(), ivals.begin()+size, values); - break; + case ALC_DEVICE_CLOCK_LATENCY_SOFT: + if(size < 2) + alcSetError(dev.get(), ALC_INVALID_VALUE); + else + { + ClockLatency clock{GetClockLatency(dev.get(), dev->Backend.get())}; + values[0] = clock.ClockTime.count(); + values[1] = clock.Latency.count(); } + break; + + default: + auto ivals = al::vector(static_cast(size)); + if(size_t got{GetIntegerv(dev.get(), pname, ivals)}) + std::copy_n(ivals.begin(), got, values); + break; } } END_API_FUNC -/* alcIsExtensionPresent - * - * Determines if there is support for a particular extension - */ ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extName) START_API_FUNC { @@ -3335,8 +3079,7 @@ START_API_FUNC const char *ptr = (dev ? alcExtensionList : alcNoDeviceExtList); while(ptr && *ptr) { - if(strncasecmp(ptr, extName, len) == 0 && - (ptr[len] == '\0' || isspace(ptr[len]))) + if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len]))) return ALC_TRUE; if((ptr=strchr(ptr, ' ')) != nullptr) @@ -3352,10 +3095,6 @@ START_API_FUNC END_API_FUNC -/* alcGetProcAddress - * - * Retrieves the function address for a particular extension function - */ ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcName) START_API_FUNC { @@ -3363,24 +3102,28 @@ START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; alcSetError(dev.get(), ALC_INVALID_VALUE); + return nullptr; } - else +#ifdef ALSOFT_EAX + if(eax_g_is_enabled) { - for(const auto &func : alcFunctions) + for(const auto &func : eaxFunctions) { if(strcmp(func.funcName, funcName) == 0) return func.address; } } +#endif + for(const auto &func : alcFunctions) + { + if(strcmp(func.funcName, funcName) == 0) + return func.address; + } return nullptr; } END_API_FUNC -/* alcGetEnumValue - * - * Get the value for a particular ALC enumeration name - */ ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumName) START_API_FUNC { @@ -3388,24 +3131,29 @@ START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; alcSetError(dev.get(), ALC_INVALID_VALUE); + return 0; } - else +#ifdef ALSOFT_EAX + if(eax_g_is_enabled) { - for(const auto &enm : alcEnumerations) + for(const auto &enm : eaxEnumerations) { if(strcmp(enm.enumName, enumName) == 0) return enm.value; } } +#endif + for(const auto &enm : alcEnumerations) + { + if(strcmp(enm.enumName, enumName) == 0) + return enm.value; + } + return 0; } END_API_FUNC -/* alcCreateContext - * - * Create and attach a context to the given device. - */ ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrList) START_API_FUNC { @@ -3415,7 +3163,7 @@ START_API_FUNC */ std::unique_lock listlock{ListLock}; DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type == Capture || !dev->Connected.load(std::memory_order_relaxed)) + if(!dev || dev->Type == DeviceType::Capture || !dev->Connected.load(std::memory_order_relaxed)) { listlock.unlock(); alcSetError(dev.get(), ALC_INVALID_DEVICE); @@ -3426,143 +3174,125 @@ START_API_FUNC dev->LastError.store(ALC_NO_ERROR); - ContextRef context{new ALCcontext{dev.get()}}; - ALCdevice_IncRef(context->Device); - ALCenum err{UpdateDeviceParams(dev.get(), attrList)}; if(err != ALC_NO_ERROR) { alcSetError(dev.get(), err); - if(err == ALC_INVALID_DEVICE) - aluHandleDisconnect(dev.get(), "Device update failure"); - statelock.unlock(); - return nullptr; } - AllocateVoices(context.get(), 256, dev->NumAuxSends); - - if(DefaultEffect.type != AL_EFFECT_NULL && dev->Type == Playback) - { - void *ptr{al_calloc(16, sizeof(ALeffectslot))}; - context->DefaultSlot = std::unique_ptr{new (ptr) ALeffectslot{}}; - if(InitEffectSlot(context->DefaultSlot.get()) == AL_NO_ERROR) - aluInitEffectPanning(context->DefaultSlot.get(), dev.get()); - else - { - context->DefaultSlot = nullptr; - ERR("Failed to initialize the default effect slot\n"); - } - } - InitContext(context.get()); + ContextRef context{new ALCcontext{dev}}; + context->init(); - ALfloat valf{}; - if(ConfigValueFloat(dev->DeviceName.c_str(), nullptr, "volume-adjust", &valf)) + if(auto volopt = dev->configValue(nullptr, "volume-adjust")) { + const float valf{*volopt}; if(!std::isfinite(valf)) ERR("volume-adjust must be finite: %f\n", valf); else { - ALfloat db = clampf(valf, -24.0f, 24.0f); + const float db{clampf(valf, -24.0f, 24.0f)}; if(db != valf) WARN("volume-adjust clamped: %f, range: +/-%f\n", valf, 24.0f); - context->GainBoost = std::pow(10.0f, db/20.0f); - TRACE("volume-adjust gain: %f\n", context->GainBoost); + context->mGainBoost = std::pow(10.0f, db/20.0f); + TRACE("volume-adjust gain: %f\n", context->mGainBoost); } } - UpdateListenerProps(context.get()); { + using ContextArray = al::FlexArray; + + /* Allocate a new context array, which holds 1 more than the current/ + * old array. + */ + auto *oldarray = device->mContexts.load(); + const size_t newcount{oldarray->size()+1}; + std::unique_ptr newarray{ContextArray::Create(newcount)}; + + /* Copy the current/old context handles to the new array, appending the + * new context. + */ + auto iter = std::copy(oldarray->begin(), oldarray->end(), newarray->begin()); + *iter = context.get(); + + /* Store the new context array in the device. Wait for any current mix + * to finish before deleting the old array. + */ + dev->mContexts.store(newarray.release()); + if(oldarray != &DeviceBase::sEmptyContextArray) { - std::lock_guard _{ListLock}; - auto iter = std::lower_bound(ContextList.cbegin(), ContextList.cend(), context.get()); - ContextList.insert(iter, context.get()); - ALCcontext_IncRef(context.get()); + dev->waitForMix(); + delete oldarray; } - - ALCcontext *head = dev->ContextList.load(); - do { - context->next.store(head, std::memory_order_relaxed); - } while(!dev->ContextList.compare_exchange_weak(head, context.get())); } statelock.unlock(); - if(context->DefaultSlot) { - if(InitializeEffect(context.get(), context->DefaultSlot.get(), &DefaultEffect) == AL_NO_ERROR) - UpdateEffectSlotProps(context->DefaultSlot.get(), context.get()); + std::lock_guard _{ListLock}; + auto iter = std::lower_bound(ContextList.cbegin(), ContextList.cend(), context.get()); + ContextList.emplace(iter, context.get()); + } + + if(ALeffectslot *slot{context->mDefaultSlot.get()}) + { + ALenum sloterr{slot->initEffect(ALCcontext::sDefaultEffect.type, + ALCcontext::sDefaultEffect.Props, context.get())}; + if(sloterr == AL_NO_ERROR) + slot->updateProps(context.get()); else ERR("Failed to initialize the default effect\n"); } - TRACE("Created context %p\n", context.get()); - return context.get(); + TRACE("Created context %p\n", voidp{context.get()}); + return context.release(); } END_API_FUNC -/* alcDestroyContext - * - * Remove a context from its device - */ -ALC_API ALCvoid ALC_APIENTRY alcDestroyContext(ALCcontext *context) +ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) START_API_FUNC { std::unique_lock listlock{ListLock}; - auto iter = std::lower_bound(ContextList.cbegin(), ContextList.cend(), context); - if(iter == ContextList.cend() || *iter != context) + auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), context); + if(iter == ContextList.end() || *iter != context) { listlock.unlock(); alcSetError(nullptr, ALC_INVALID_CONTEXT); return; } - /* Hold an extra reference to this context so it remains valid until the - * ListLock is released. + + /* Hold a reference to this context so it remains valid until the ListLock + * is released. */ - ALCcontext_IncRef(*iter); ContextRef ctx{*iter}; ContextList.erase(iter); - if(ALCdevice *Device{ctx->Device}) + ALCdevice *Device{ctx->mALDevice.get()}; + + std::lock_guard _{Device->StateLock}; + if(!ctx->deinit() && Device->Flags.test(DeviceRunning)) { - std::lock_guard _{Device->StateLock}; - if(!ReleaseContext(ctx.get(), Device) && (Device->Flags&DEVICE_RUNNING)) - { - Device->Backend->stop(); - Device->Flags &= ~DEVICE_RUNNING; - } + Device->Backend->stop(); + Device->Flags.reset(DeviceRunning); } - listlock.unlock(); } END_API_FUNC -/* alcGetCurrentContext - * - * Returns the currently active context on the calling thread - */ ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void) START_API_FUNC { - ALCcontext *Context{LocalContext.get()}; - if(!Context) Context = GlobalContext.load(); + ALCcontext *Context{ALCcontext::getThreadContext()}; + if(!Context) Context = ALCcontext::sGlobalContext.load(); return Context; } END_API_FUNC -/* alcGetThreadContext - * - * Returns the currently active thread-local context - */ +/** Returns the currently active thread-local context. */ ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) START_API_FUNC -{ return LocalContext.get(); } +{ return ALCcontext::getThreadContext(); } END_API_FUNC -/* alcMakeContextCurrent - * - * Makes the given context the active process-wide context, and removes the - * thread-local context for the calling thread. - */ ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) START_API_FUNC { @@ -3581,24 +3311,21 @@ START_API_FUNC * pointer. Take ownership of the reference (if any) that was previously * stored there. */ - ctx = ContextRef{GlobalContext.exchange(ctx.release())}; + ctx = ContextRef{ALCcontext::sGlobalContext.exchange(ctx.release())}; /* Reset (decrement) the previous global reference by replacing it with the * thread-local context. Take ownership of the thread-local context * reference (if any), clearing the storage to null. */ - ctx = ContextRef{LocalContext.get()}; - if(ctx) LocalContext.set(nullptr); + ctx = ContextRef{ALCcontext::getThreadContext()}; + if(ctx) ALCcontext::setThreadContext(nullptr); /* Reset (decrement) the previous thread-local reference. */ return ALC_TRUE; } END_API_FUNC -/* alcSetThreadContext - * - * Makes the given context the active context for the current thread - */ +/** Makes the given context the active context for the current thread. */ ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) START_API_FUNC { @@ -3614,18 +3341,14 @@ START_API_FUNC } } /* context's reference count is already incremented */ - ContextRef old{LocalContext.get()}; - LocalContext.set(ctx.release()); + ContextRef old{ALCcontext::getThreadContext()}; + ALCcontext::setThreadContext(ctx.release()); return ALC_TRUE; } END_API_FUNC -/* alcGetContextsDevice - * - * Returns the device that a particular context is attached to - */ ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *Context) START_API_FUNC { @@ -3635,39 +3358,44 @@ START_API_FUNC alcSetError(nullptr, ALC_INVALID_CONTEXT); return nullptr; } - return ctx->Device; + return ctx->mALDevice.get(); } END_API_FUNC -/* alcOpenDevice - * - * Opens the named device. - */ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) START_API_FUNC { DO_INITCONFIG(); - if(!PlaybackBackend.name) + if(!PlaybackFactory) { alcSetError(nullptr, ALC_INVALID_VALUE); return nullptr; } - if(deviceName && (!deviceName[0] || strcasecmp(deviceName, alcDefaultName) == 0 || strcasecmp(deviceName, "openal-soft") == 0 + if(deviceName) + { + if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0 #ifdef _WIN32 - /* Some old Windows apps hardcode these expecting OpenAL to use a - * specific audio API, even when they're not enumerated. Creative's - * router effectively ignores them too. - */ - || strcasecmp(deviceName, "DirectSound3D") == 0 || strcasecmp(deviceName, "DirectSound") == 0 - || strcasecmp(deviceName, "MMSYSTEM") == 0 + /* Some old Windows apps hardcode these expecting OpenAL to use a + * specific audio API, even when they're not enumerated. Creative's + * router effectively ignores them too. + */ + || al::strcasecmp(deviceName, "DirectSound3D") == 0 + || al::strcasecmp(deviceName, "DirectSound") == 0 + || al::strcasecmp(deviceName, "MMSYSTEM") == 0 #endif - )) - deviceName = nullptr; + /* Some old Linux apps hardcode configuration strings that were + * supported by the OpenAL SI. We can't really do anything useful + * with them, so just ignore. + */ + || (deviceName[0] == '\'' && deviceName[1] == '(') + || al::strcasecmp(deviceName, "openal-soft") == 0) + deviceName = nullptr; + } - DeviceRef device{new ALCdevice{Playback}}; + DeviceRef device{new ALCdevice{DeviceType::Playback}}; /* Set output format */ device->FmtChans = DevFmtChannelsDefault; @@ -3675,224 +3403,112 @@ START_API_FUNC device->Frequency = DEFAULT_OUTPUT_RATE; device->UpdateSize = DEFAULT_UPDATE_SIZE; device->BufferSize = DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES; - device->LimiterState = ALC_TRUE; device->SourcesMax = 256; device->AuxiliaryEffectSlotMax = 64; device->NumAuxSends = DEFAULT_SENDS; +#ifdef ALSOFT_EAX + if(eax_g_is_enabled) + device->NumAuxSends = EAX_MAX_FXSLOTS; +#endif // ALSOFT_EAX try { - /* Create the device backend. */ - device->Backend = PlaybackBackend.getFactory().createBackend(device.get(), - BackendType::Playback); - - /* Find a playback device to open */ - ALCenum err{device->Backend->open(deviceName)}; - if(err != ALC_NO_ERROR) - { - alcSetError(nullptr, err); - return nullptr; - } + auto backend = PlaybackFactory->createBackend(device.get(), BackendType::Playback); + std::lock_guard _{ListLock}; + backend->open(deviceName); + device->Backend = std::move(backend); } catch(al::backend_exception &e) { WARN("Failed to open playback device: %s\n", e.what()); - alcSetError(nullptr, e.errorCode()); + alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) + ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; } - deviceName = device->DeviceName.c_str(); - const ALCchar *fmt{}; - if(ConfigValueStr(deviceName, nullptr, "channels", &fmt)) - { - static constexpr struct ChannelMap { - const char name[16]; - DevFmtChannels chans; - ALsizei order; - } chanlist[] = { - { "mono", DevFmtMono, 0 }, - { "stereo", DevFmtStereo, 0 }, - { "quad", DevFmtQuad, 0 }, - { "surround51", DevFmtX51, 0 }, - { "surround61", DevFmtX61, 0 }, - { "surround71", DevFmtX71, 0 }, - { "surround51rear", DevFmtX51Rear, 0 }, - { "ambi1", DevFmtAmbi3D, 1 }, - { "ambi2", DevFmtAmbi3D, 2 }, - { "ambi3", DevFmtAmbi3D, 3 }, - }; - - auto iter = std::find_if(std::begin(chanlist), std::end(chanlist), - [fmt](const ChannelMap &entry) -> bool - { return strcasecmp(entry.name, fmt) == 0; } - ); - if(iter == std::end(chanlist)) - ERR("Unsupported channels: %s\n", fmt); - else - { - device->FmtChans = iter->chans; - device->mAmbiOrder = iter->order; - device->Flags |= DEVICE_CHANNELS_REQUEST; - } - } - if(ConfigValueStr(deviceName, nullptr, "sample-type", &fmt)) - { - static constexpr struct TypeMap { - const char name[16]; - DevFmtType type; - } typelist[] = { - { "int8", DevFmtByte }, - { "uint8", DevFmtUByte }, - { "int16", DevFmtShort }, - { "uint16", DevFmtUShort }, - { "int32", DevFmtInt }, - { "uint32", DevFmtUInt }, - { "float32", DevFmtFloat }, - }; - - auto iter = std::find_if(std::begin(typelist), std::end(typelist), - [fmt](const TypeMap &entry) -> bool - { return strcasecmp(entry.name, fmt) == 0; } - ); - if(iter == std::end(typelist)) - ERR("Unsupported sample-type: %s\n", fmt); - else - { - device->FmtType = iter->type; - device->Flags |= DEVICE_SAMPLE_TYPE_REQUEST; - } - } - - ALuint freq{}; - if(ConfigValueUInt(deviceName, nullptr, "frequency", &freq) && freq > 0) + if(uint freq{device->configValue(nullptr, "frequency").value_or(0u)}) { - if(freq < MIN_OUTPUT_RATE) + if(freq < MIN_OUTPUT_RATE || freq > MAX_OUTPUT_RATE) { - ERR("%uhz request clamped to %uhz minimum\n", freq, MIN_OUTPUT_RATE); - freq = MIN_OUTPUT_RATE; + const uint newfreq{clampu(freq, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)}; + ERR("%uhz request clamped to %uhz\n", freq, newfreq); + freq = newfreq; } - device->UpdateSize = (device->UpdateSize*freq + device->Frequency/2) / device->Frequency; - device->BufferSize = (device->BufferSize*freq + device->Frequency/2) / device->Frequency; + const double scale{static_cast(freq) / device->Frequency}; + device->UpdateSize = static_cast(device->UpdateSize*scale + 0.5); + device->BufferSize = static_cast(device->BufferSize*scale + 0.5); device->Frequency = freq; - device->Flags |= DEVICE_FREQUENCY_REQUEST; + device->Flags.set(FrequencyRequest); } - ConfigValueUInt(deviceName, nullptr, "period_size", &device->UpdateSize); - device->UpdateSize = clampu(device->UpdateSize, 64, 8192); - - ALuint periods{}; - if(ConfigValueUInt(deviceName, nullptr, "periods", &periods)) - device->BufferSize = device->UpdateSize * clampu(periods, 2, 16); - else - device->BufferSize = maxu(device->BufferSize, device->UpdateSize*2); - - ConfigValueUInt(deviceName, nullptr, "sources", &device->SourcesMax); - if(device->SourcesMax == 0) device->SourcesMax = 256; + if(auto srcsmax = device->configValue(nullptr, "sources").value_or(0)) + device->SourcesMax = srcsmax; - ConfigValueUInt(deviceName, nullptr, "slots", &device->AuxiliaryEffectSlotMax); - if(device->AuxiliaryEffectSlotMax == 0) device->AuxiliaryEffectSlotMax = 64; - else device->AuxiliaryEffectSlotMax = minu(device->AuxiliaryEffectSlotMax, INT_MAX); + if(auto slotsmax = device->configValue(nullptr, "slots").value_or(0)) + device->AuxiliaryEffectSlotMax = minu(slotsmax, INT_MAX); - if(ConfigValueInt(deviceName, nullptr, "sends", &device->NumAuxSends)) - device->NumAuxSends = clampi( - DEFAULT_SENDS, 0, clampi(device->NumAuxSends, 0, MAX_SENDS) - ); + if(auto sendsopt = device->configValue(nullptr, "sends")) + device->NumAuxSends = minu(DEFAULT_SENDS, + static_cast(clampi(*sendsopt, 0, MAX_SENDS))); device->NumStereoSources = 1; device->NumMonoSources = device->SourcesMax - device->NumStereoSources; - if(ConfigValueStr(deviceName, nullptr, "ambi-format", &fmt)) - { - if(strcasecmp(fmt, "fuma") == 0) - { - if(device->mAmbiOrder > 3) - ERR("FuMa is incompatible with %d%s order ambisonics (up to third-order only)\n", - device->mAmbiOrder, - (((device->mAmbiOrder%100)/10) == 1) ? "th" : - ((device->mAmbiOrder%10) == 1) ? "st" : - ((device->mAmbiOrder%10) == 2) ? "nd" : - ((device->mAmbiOrder%10) == 3) ? "rd" : "th"); - else - { - device->mAmbiLayout = AmbiLayout::FuMa; - device->mAmbiScale = AmbiNorm::FuMa; - } - } - else if(strcasecmp(fmt, "ambix") == 0 || strcasecmp(fmt, "acn+sn3d") == 0) - { - device->mAmbiLayout = AmbiLayout::ACN; - device->mAmbiScale = AmbiNorm::SN3D; - } - else if(strcasecmp(fmt, "acn+n3d") == 0) - { - device->mAmbiLayout = AmbiLayout::ACN; - device->mAmbiScale = AmbiNorm::N3D; - } - else - ERR("Unsupported ambi-format: %s\n", fmt); - } - { std::lock_guard _{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); - DeviceList.insert(iter, device.get()); - ALCdevice_IncRef(device.get()); + DeviceList.emplace(iter, device.get()); } - TRACE("Created device %p, \"%s\"\n", device.get(), device->DeviceName.c_str()); - return device.get(); + TRACE("Created device %p, \"%s\"\n", voidp{device.get()}, device->DeviceName.c_str()); + return device.release(); } END_API_FUNC -/* alcCloseDevice - * - * Closes the given device. - */ ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) START_API_FUNC { std::unique_lock listlock{ListLock}; - auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device); - if(iter == DeviceList.cend() || *iter != device) + auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); + if(iter == DeviceList.end() || *iter != device) { alcSetError(nullptr, ALC_INVALID_DEVICE); return ALC_FALSE; } - if((*iter)->Type == Capture) + if((*iter)->Type == DeviceType::Capture) { alcSetError(*iter, ALC_INVALID_DEVICE); return ALC_FALSE; } - std::unique_lock statelock{device->StateLock}; /* Erase the device, and any remaining contexts left on it, from their * respective lists. */ + DeviceRef dev{*iter}; DeviceList.erase(iter); - ALCcontext *ctx{device->ContextList.load()}; - while(ctx != nullptr) + + std::unique_lock statelock{dev->StateLock}; + al::vector orphanctxs; + for(ContextBase *ctx : *dev->mContexts.load()) { - ALCcontext *next = ctx->next.load(std::memory_order_relaxed); - auto iter = std::lower_bound(ContextList.cbegin(), ContextList.cend(), ctx); - if(iter != ContextList.cend() && *iter == ctx) - ContextList.erase(iter); - ctx = next; + auto ctxiter = std::lower_bound(ContextList.begin(), ContextList.end(), ctx); + if(ctxiter != ContextList.end() && *ctxiter == ctx) + { + orphanctxs.emplace_back(ContextRef{*ctxiter}); + ContextList.erase(ctxiter); + } } listlock.unlock(); - ctx = device->ContextList.load(std::memory_order_relaxed); - while(ctx != nullptr) + for(ContextRef &context : orphanctxs) { - ALCcontext *next = ctx->next.load(std::memory_order_relaxed); - WARN("Releasing context %p\n", ctx); - ReleaseContext(ctx, device); - ctx = next; + WARN("Releasing orphaned context %p\n", voidp{context.get()}); + context->deinit(); } - if((device->Flags&DEVICE_RUNNING)) - device->Backend->stop(); - device->Flags &= ~DEVICE_RUNNING; - statelock.unlock(); + orphanctxs.clear(); - ALCdevice_DecRef(device); + if(dev->Flags.test(DeviceRunning)) + dev->Backend->stop(); + dev->Flags.reset(DeviceRunning); return ALC_TRUE; } @@ -3907,7 +3523,7 @@ START_API_FUNC { DO_INITCONFIG(); - if(!CaptureBackend.name) + if(!CaptureFactory) { alcSetError(nullptr, ALC_INVALID_VALUE); return nullptr; @@ -3919,53 +3535,57 @@ START_API_FUNC return nullptr; } - if(deviceName && (!deviceName[0] || strcasecmp(deviceName, alcDefaultName) == 0 || strcasecmp(deviceName, "openal-soft") == 0)) - deviceName = nullptr; - - DeviceRef device{new ALCdevice{Capture}}; + if(deviceName) + { + if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0 + || al::strcasecmp(deviceName, "openal-soft") == 0) + deviceName = nullptr; + } - device->Frequency = frequency; - device->Flags |= DEVICE_FREQUENCY_REQUEST; + DeviceRef device{new ALCdevice{DeviceType::Capture}}; - if(DecomposeDevFormat(format, &device->FmtChans, &device->FmtType) == AL_FALSE) + auto decompfmt = DecomposeDevFormat(format); + if(!decompfmt) { alcSetError(nullptr, ALC_INVALID_ENUM); return nullptr; } - device->Flags |= DEVICE_CHANNELS_REQUEST | DEVICE_SAMPLE_TYPE_REQUEST; - device->UpdateSize = samples; - device->BufferSize = samples; + device->Frequency = 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); try { - device->Backend = CaptureBackend.getFactory().createBackend(device.get(), - BackendType::Capture); - TRACE("Capture format: %s, %s, %uhz, %u / %u buffer\n", DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), device->Frequency, device->UpdateSize, device->BufferSize); - ALCenum err{device->Backend->open(deviceName)}; - if(err != ALC_NO_ERROR) - { - alcSetError(nullptr, err); - return nullptr; - } + + auto backend = CaptureFactory->createBackend(device.get(), BackendType::Capture); + std::lock_guard _{ListLock}; + backend->open(deviceName); + device->Backend = std::move(backend); } catch(al::backend_exception &e) { WARN("Failed to open capture device: %s\n", e.what()); - alcSetError(nullptr, e.errorCode()); + alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) + ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; } { std::lock_guard _{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); - DeviceList.insert(iter, device.get()); - ALCdevice_IncRef(device.get()); + DeviceList.emplace(iter, device.get()); } - TRACE("Created device %p, \"%s\"\n", device.get(), device->DeviceName.c_str()); - return device.get(); + TRACE("Created capture device %p, \"%s\"\n", voidp{device.get()}, device->DeviceName.c_str()); + return device.release(); } END_API_FUNC @@ -3973,28 +3593,26 @@ ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) START_API_FUNC { std::unique_lock listlock{ListLock}; - auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device); - if(iter == DeviceList.cend() || *iter != device) + auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); + if(iter == DeviceList.end() || *iter != device) { alcSetError(nullptr, ALC_INVALID_DEVICE); return ALC_FALSE; } - if((*iter)->Type != Capture) + if((*iter)->Type != DeviceType::Capture) { alcSetError(*iter, ALC_INVALID_DEVICE); return ALC_FALSE; } + DeviceRef dev{*iter}; DeviceList.erase(iter); listlock.unlock(); - { std::lock_guard _{device->StateLock}; - if((device->Flags&DEVICE_RUNNING)) - device->Backend->stop(); - device->Flags &= ~DEVICE_RUNNING; - } - - ALCdevice_DecRef(device); + std::lock_guard _{dev->StateLock}; + if(dev->Flags.test(DeviceRunning)) + dev->Backend->stop(); + dev->Flags.reset(DeviceRunning); return ALC_TRUE; } @@ -4004,7 +3622,7 @@ ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Capture) + if(!dev || dev->Type != DeviceType::Capture) { alcSetError(dev.get(), ALC_INVALID_DEVICE); return; @@ -4013,13 +3631,16 @@ START_API_FUNC std::lock_guard _{dev->StateLock}; if(!dev->Connected.load(std::memory_order_acquire)) alcSetError(dev.get(), ALC_INVALID_DEVICE); - else if(!(dev->Flags&DEVICE_RUNNING)) + else if(!dev->Flags.test(DeviceRunning)) { - if(dev->Backend->start()) - dev->Flags |= DEVICE_RUNNING; - else - { - aluHandleDisconnect(dev.get(), "Device start failure"); + try { + auto backend = dev->Backend.get(); + backend->start(); + dev->Flags.set(DeviceRunning); + } + catch(al::backend_exception& e) { + ERR("%s\n", e.what()); + dev->handleDisconnect("%s", e.what()); alcSetError(dev.get(), ALC_INVALID_DEVICE); } } @@ -4030,14 +3651,14 @@ ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Capture) + if(!dev || dev->Type != DeviceType::Capture) alcSetError(dev.get(), ALC_INVALID_DEVICE); else { std::lock_guard _{dev->StateLock}; - if((dev->Flags&DEVICE_RUNNING)) + if(dev->Flags.test(DeviceRunning)) dev->Backend->stop(); - dev->Flags &= ~DEVICE_RUNNING; + dev->Flags.reset(DeviceRunning); } } END_API_FUNC @@ -4046,20 +3667,31 @@ ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Capture) + if(!dev || dev->Type != DeviceType::Capture) { alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } - ALCenum err{ALC_INVALID_VALUE}; - { std::lock_guard _{dev->StateLock}; - BackendBase *backend{dev->Backend.get()}; - if(samples >= 0 && backend->availableSamples() >= static_cast(samples)) - err = backend->captureSamples(buffer, samples); + if(samples < 0 || (samples > 0 && buffer == nullptr)) + { + alcSetError(dev.get(), ALC_INVALID_VALUE); + return; } - if(err != ALC_NO_ERROR) - alcSetError(dev.get(), err); + if(samples < 1) + return; + + std::lock_guard _{dev->StateLock}; + BackendBase *backend{dev->Backend.get()}; + + const auto usamples = static_cast(samples); + if(usamples > backend->availableSamples()) + { + alcSetError(dev.get(), ALC_INVALID_VALUE); + return; + } + + backend->captureSamples(static_cast(buffer), usamples); } END_API_FUNC @@ -4068,10 +3700,7 @@ END_API_FUNC * ALC loopback functions ************************************************/ -/* alcLoopbackOpenDeviceSOFT - * - * Open a loopback device, for manual rendering. - */ +/** Open a loopback device, for manual rendering. */ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) START_API_FUNC { @@ -4084,7 +3713,7 @@ START_API_FUNC return nullptr; } - DeviceRef device{new ALCdevice{Loopback}}; + DeviceRef device{new ALCdevice{DeviceType::Loopback}}; device->SourcesMax = 256; device->AuxiliaryEffectSlotMax = 64; @@ -4098,61 +3727,58 @@ START_API_FUNC device->FmtChans = DevFmtChannelsDefault; device->FmtType = DevFmtTypeDefault; - ConfigValueUInt(nullptr, nullptr, "sources", &device->SourcesMax); - if(device->SourcesMax == 0) device->SourcesMax = 256; + if(auto srcsmax = ConfigValueUInt(nullptr, nullptr, "sources").value_or(0)) + device->SourcesMax = srcsmax; - ConfigValueUInt(nullptr, nullptr, "slots", &device->AuxiliaryEffectSlotMax); - if(device->AuxiliaryEffectSlotMax == 0) device->AuxiliaryEffectSlotMax = 64; - else device->AuxiliaryEffectSlotMax = minu(device->AuxiliaryEffectSlotMax, INT_MAX); + if(auto slotsmax = ConfigValueUInt(nullptr, nullptr, "slots").value_or(0)) + device->AuxiliaryEffectSlotMax = minu(slotsmax, INT_MAX); - if(ConfigValueInt(nullptr, nullptr, "sends", &device->NumAuxSends)) - device->NumAuxSends = clampi( - DEFAULT_SENDS, 0, clampi(device->NumAuxSends, 0, MAX_SENDS) - ); + if(auto sendsopt = ConfigValueInt(nullptr, nullptr, "sends")) + device->NumAuxSends = minu(DEFAULT_SENDS, + static_cast(clampi(*sendsopt, 0, MAX_SENDS))); device->NumStereoSources = 1; device->NumMonoSources = device->SourcesMax - device->NumStereoSources; try { - device->Backend = LoopbackBackendFactory::getFactory().createBackend(device.get(), + auto backend = LoopbackBackendFactory::getFactory().createBackend(device.get(), BackendType::Playback); - - // Open the "backend" - device->Backend->open("Loopback"); + backend->open("Loopback"); + device->Backend = std::move(backend); } catch(al::backend_exception &e) { WARN("Failed to open loopback device: %s\n", e.what()); - alcSetError(nullptr, e.errorCode()); + alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) + ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; } { std::lock_guard _{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); - DeviceList.insert(iter, device.get()); - ALCdevice_IncRef(device.get()); + DeviceList.emplace(iter, device.get()); } - TRACE("Created device %p\n", device.get()); - return device.get(); + TRACE("Created loopback device %p\n", voidp{device.get()}); + return device.release(); } END_API_FUNC -/* alcIsRenderFormatSupportedSOFT - * +/** * Determines if the loopback device supports the given format for rendering. */ ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Loopback) + if(!dev || dev->Type != DeviceType::Loopback) alcSetError(dev.get(), ALC_INVALID_DEVICE); else if(freq <= 0) alcSetError(dev.get(), ALC_INVALID_VALUE); else { - if(IsValidALCType(type) && IsValidALCChannels(channels) && freq >= MIN_OUTPUT_RATE) + if(DevFmtTypeFromEnum(type).has_value() && DevFmtChannelsFromEnum(channels).has_value() + && freq >= MIN_OUTPUT_RATE && freq <= MAX_OUTPUT_RATE) return ALC_TRUE; } @@ -4160,24 +3786,19 @@ START_API_FUNC } END_API_FUNC -/* alcRenderSamplesSOFT - * +/** * Renders some samples into a buffer, using the format last set by the * attributes given to alcCreateContext. */ FORCE_ALIGN ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) START_API_FUNC { - DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Loopback) - alcSetError(dev.get(), ALC_INVALID_DEVICE); + if(!device || device->Type != DeviceType::Loopback) + alcSetError(device, ALC_INVALID_DEVICE); else if(samples < 0 || (samples > 0 && buffer == nullptr)) - alcSetError(dev.get(), ALC_INVALID_VALUE); + alcSetError(device, ALC_INVALID_VALUE); else - { - BackendLockGuard _{*device->Backend}; - aluMixData(dev.get(), buffer, samples); - } + device->renderSamples(buffer, static_cast(samples), device->channelsFromFmt()); } END_API_FUNC @@ -4186,55 +3807,56 @@ END_API_FUNC * ALC DSP pause/resume functions ************************************************/ -/* alcDevicePauseSOFT - * - * Pause the DSP to stop audio processing. - */ +/** Pause the DSP to stop audio processing. */ ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device) START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Playback) + if(!dev || dev->Type != DeviceType::Playback) alcSetError(dev.get(), ALC_INVALID_DEVICE); else { std::lock_guard _{dev->StateLock}; - if((dev->Flags&DEVICE_RUNNING)) + if(dev->Flags.test(DeviceRunning)) dev->Backend->stop(); - dev->Flags &= ~DEVICE_RUNNING; - dev->Flags |= DEVICE_PAUSED; + dev->Flags.reset(DeviceRunning); + dev->Flags.set(DevicePaused); } } END_API_FUNC -/* alcDeviceResumeSOFT - * - * Resume the DSP to restart audio processing. - */ +/** Resume the DSP to restart audio processing. */ ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type != Playback) + if(!dev || dev->Type != DeviceType::Playback) { alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } std::lock_guard _{dev->StateLock}; - if(!(dev->Flags&DEVICE_PAUSED)) + if(!dev->Flags.test(DevicePaused)) return; - dev->Flags &= ~DEVICE_PAUSED; - if(dev->ContextList.load() == nullptr) + dev->Flags.reset(DevicePaused); + if(dev->mContexts.load()->empty()) return; - if(dev->Backend->start() == ALC_FALSE) - { - aluHandleDisconnect(dev.get(), "Device start failure"); + try { + auto backend = dev->Backend.get(); + backend->start(); + dev->Flags.set(DeviceRunning); + } + catch(al::backend_exception& e) { + ERR("%s\n", e.what()); + dev->handleDisconnect("%s", e.what()); alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } - dev->Flags |= DEVICE_RUNNING; + TRACE("Post-resume: %s, %s, %uhz, %u / %u buffer\n", + DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), + device->Frequency, device->UpdateSize, device->BufferSize); } END_API_FUNC @@ -4243,21 +3865,18 @@ END_API_FUNC * ALC HRTF functions ************************************************/ -/* alcGetStringiSOFT - * - * Gets a string parameter at the given index. - */ +/** Gets a string parameter at the given index. */ ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index) START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type == Capture) + if(!dev || dev->Type == DeviceType::Capture) alcSetError(dev.get(), ALC_INVALID_DEVICE); else switch(paramName) { case ALC_HRTF_SPECIFIER_SOFT: - if(index >= 0 && static_cast(index) < dev->HrtfList.size()) - return dev->HrtfList[index].name.c_str(); + if(index >= 0 && static_cast(index) < dev->mHrtfList.size()) + return dev->mHrtfList[static_cast(index)].c_str(); alcSetError(dev.get(), ALC_INVALID_VALUE); break; @@ -4270,16 +3889,13 @@ START_API_FUNC } END_API_FUNC -/* alcResetDeviceSOFT - * - * Resets the given device output, using the specified attribute list. - */ +/** Resets the given device output, using the specified attribute list. */ ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs) START_API_FUNC { std::unique_lock listlock{ListLock}; DeviceRef dev{VerifyDevice(device)}; - if(!dev || dev->Type == Capture) + if(!dev || dev->Type == DeviceType::Capture) { listlock.unlock(); alcSetError(dev.get(), ALC_INVALID_DEVICE); @@ -4291,17 +3907,95 @@ START_API_FUNC /* Force the backend to stop mixing first since we're resetting. Also reset * the connected state so lost devices can attempt recover. */ - if((dev->Flags&DEVICE_RUNNING)) + if(dev->Flags.test(DeviceRunning)) dev->Backend->stop(); - dev->Flags &= ~DEVICE_RUNNING; - device->Connected.store(true); + dev->Flags.reset(DeviceRunning); - ALCenum err{UpdateDeviceParams(dev.get(), attribs)}; - if(LIKELY(err == ALC_NO_ERROR)) return ALC_TRUE; + return ResetDeviceParams(dev.get(), attribs) ? ALC_TRUE : ALC_FALSE; +} +END_API_FUNC - alcSetError(dev.get(), err); - if(err == ALC_INVALID_DEVICE) - aluHandleDisconnect(dev.get(), "Device start failure"); - return ALC_FALSE; + +/************************************************ + * ALC device reopen functions + ************************************************/ + +/** Reopens the given device output, using the specified name and attribute list. */ +FORCE_ALIGN ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, + const ALCchar *deviceName, const ALCint *attribs) +START_API_FUNC +{ + if(deviceName) + { + if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0) + deviceName = nullptr; + } + + std::unique_lock listlock{ListLock}; + DeviceRef dev{VerifyDevice(device)}; + if(!dev || dev->Type != DeviceType::Playback) + { + listlock.unlock(); + alcSetError(dev.get(), ALC_INVALID_DEVICE); + return ALC_FALSE; + } + std::lock_guard _{dev->StateLock}; + + /* Force the backend to stop mixing first since we're reopening. */ + if(dev->Flags.test(DeviceRunning)) + { + auto backend = dev->Backend.get(); + backend->stop(); + dev->Flags.reset(DeviceRunning); + } + + BackendPtr newbackend; + try { + newbackend = PlaybackFactory->createBackend(dev.get(), BackendType::Playback); + newbackend->open(deviceName); + } + catch(al::backend_exception &e) { + listlock.unlock(); + newbackend = nullptr; + + WARN("Failed to reopen playback device: %s\n", e.what()); + alcSetError(dev.get(), (e.errorCode() == al::backend_error::OutOfMemory) + ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); + + /* If the device is connected, not paused, and has contexts, ensure it + * continues playing. + */ + if(dev->Connected.load(std::memory_order_relaxed) && !dev->Flags.test(DevicePaused) + && !dev->mContexts.load(std::memory_order_relaxed)->empty()) + { + try { + auto backend = dev->Backend.get(); + backend->start(); + dev->Flags.set(DeviceRunning); + } + catch(al::backend_exception &be) { + ERR("%s\n", be.what()); + dev->handleDisconnect("%s", be.what()); + } + } + return ALC_FALSE; + } + listlock.unlock(); + dev->Backend = std::move(newbackend); + TRACE("Reopened device %p, \"%s\"\n", voidp{dev.get()}, dev->DeviceName.c_str()); + + /* Always return true even if resetting fails. It shouldn't fail, but this + * is primarily to avoid confusion by the app seeing the function return + * false while the device is on the new output anyway. We could try to + * restore the old backend if this fails, but the configuration would be + * changed with the new backend and would need to be reset again with the + * old one, and the provided attributes may not be appropriate or desirable + * for the old device. + * + * In this way, we essentially act as if the function succeeded, but + * immediately disconnects following it. + */ + ResetDeviceParams(dev.get(), attribs); + return ALC_TRUE; } END_API_FUNC diff --git a/modules/openal-soft/Alc/alconfig.cpp b/modules/openal-soft/Alc/alconfig.cpp index c4fde63..7c1eec6 100644 --- a/modules/openal-soft/Alc/alconfig.cpp +++ b/modules/openal-soft/Alc/alconfig.cpp @@ -18,20 +18,14 @@ * Or go to http://www.gnu.org/copyleft/lgpl.html */ -#ifdef _WIN32 -#ifdef __MINGW32__ -#define _WIN32_IE 0x501 -#else -#define _WIN32_IE 0x400 -#endif -#endif - #include "config.h" +#include "alconfig.h" + #include #include #include -#ifdef _WIN32_IE +#ifdef _WIN32 #include #include #endif @@ -39,14 +33,17 @@ #include #endif -#include -#include #include +#include +#include +#include -#include "alMain.h" -#include "alconfig.h" -#include "logging.h" -#include "compat.h" +#include "alfstream.h" +#include "alstring.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "strutils.h" +#include "vector.h" namespace { @@ -54,11 +51,6 @@ namespace { struct ConfigEntry { std::string key; std::string value; - - template - ConfigEntry(T0&& key_, T1&& val_) - : key{std::forward(key_)}, value{std::forward(val_)} - { } }; al::vector ConfOpts; @@ -80,10 +72,11 @@ bool readline(std::istream &f, std::string &output) return std::getline(f, output) && !output.empty(); } -std:: string expdup(const char *str) +std::string expdup(const char *str) { std::string output; + std::string envval; while(*str != '\0') { const char *addstr; @@ -110,20 +103,20 @@ std:: string expdup(const char *str) } else { - bool hasbraces{(*str == '{')}; - if(hasbraces) str++; - - std::string envname; - while((std::isalnum(*str) || *str == '_')) - envname += *(str++); + const bool hasbraces{(*str == '{')}; + if(hasbraces) str++; + const char *envstart = str; + while(std::isalnum(*str) || *str == '_') + ++str; if(hasbraces && *str != '}') continue; - + const std::string envname{envstart, str}; if(hasbraces) str++; - if((addstr=std::getenv(envname.c_str())) == nullptr) - continue; - addstrlen = std::strlen(addstr); + + envval = al::getenv(envname.c_str()).value_or(std::string{}); + addstr = envval.data(); + addstrlen = envval.length(); } } if(addstrlen == 0) @@ -142,23 +135,19 @@ void LoadConfigFromFile(std::istream &f) while(readline(f, buffer)) { - while(!buffer.empty() && std::isspace(buffer.back())) - buffer.pop_back(); if(lstrip(buffer).empty()) continue; - buffer.push_back(0); - char *line{&buffer[0]}; - - if(line[0] == '[') + if(buffer[0] == '[') { + char *line{&buffer[0]}; char *section = line+1; char *endsection; endsection = std::strchr(section, ']'); if(!endsection || section == endsection) { - ERR("config parse error: bad line \"%s\"\n", line); + ERR(" config parse error: bad line \"%s\"\n", line); continue; } if(endsection[1] != 0) @@ -168,14 +157,14 @@ void LoadConfigFromFile(std::istream &f) ++end; if(*end != 0 && *end != '#') { - ERR("config parse error: bad line \"%s\"\n", line); + ERR(" config parse error: bad line \"%s\"\n", line); continue; } } *endsection = 0; curSection.clear(); - if(strcasecmp(section, "general") != 0) + if(al::strcasecmp(section, "general") != 0) { do { char *nextp = std::strchr(section, '%'); @@ -195,7 +184,7 @@ void LoadConfigFromFile(std::istream &f) (section[2] >= 'a' && section[2] <= 'f') || (section[2] >= 'A' && section[2] <= 'F'))) { - unsigned char b = 0; + int b{0}; if(section[1] >= '0' && section[1] <= '9') b = (section[1]-'0') << 4; else if(section[1] >= 'a' && section[1] <= 'f') @@ -227,31 +216,28 @@ void LoadConfigFromFile(std::istream &f) continue; } - char *comment{std::strchr(line, '#')}; - if(comment) *(comment++) = 0; - if(!line[0]) continue; + auto cmtpos = std::min(buffer.find('#'), buffer.size()); + while(cmtpos > 0 && std::isspace(buffer[cmtpos-1])) + --cmtpos; + if(!cmtpos) continue; + buffer.erase(cmtpos); - char key[256]{}; - char value[256]{}; - if(std::sscanf(line, "%255[^=] = \"%255[^\"]\"", key, value) == 2 || - std::sscanf(line, "%255[^=] = '%255[^\']'", key, value) == 2 || - std::sscanf(line, "%255[^=] = %255[^\n]", key, value) == 2) - { - /* sscanf doesn't handle '' or "" as empty values, so clip it - * manually. */ - if(std::strcmp(value, "\"\"") == 0 || std::strcmp(value, "''") == 0) - value[0] = 0; - } - else if(sscanf(line, "%255[^=] %255[=]", key, value) == 2) + auto sep = buffer.find('='); + if(sep == std::string::npos) { - /* Special case for 'key =' */ - value[0] = 0; + ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); + continue; } - else + auto keyend = sep++; + while(keyend > 0 && std::isspace(buffer[keyend-1])) + --keyend; + if(!keyend) { - ERR("config parse error: malformed option line: \"%s\"\n\n", line); + ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); continue; } + while(sep < buffer.size() && std::isspace(buffer[sep])) + sep++; std::string fullKey; if(!curSection.empty()) @@ -259,33 +245,89 @@ void LoadConfigFromFile(std::istream &f) fullKey += curSection; fullKey += '/'; } - fullKey += key; - while(!fullKey.empty() && std::isspace(fullKey.back())) - fullKey.pop_back(); + fullKey += buffer.substr(0u, keyend); + + std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}}; + if(value.size() > 1) + { + if((value.front() == '"' && value.back() == '"') + || (value.front() == '\'' && value.back() == '\'')) + { + value.pop_back(); + value.erase(value.begin()); + } + } + + TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value.c_str()); /* Check if we already have this option set */ - auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), - [&fullKey](const ConfigEntry &entry) -> bool - { return entry.key == fullKey; } - ); + auto find_key = [&fullKey](const ConfigEntry &entry) -> bool + { return entry.key == fullKey; }; + auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key); if(ent != ConfOpts.end()) - ent->value = expdup(value); - else { - ConfOpts.emplace_back(std::move(fullKey), expdup(value)); - ent = ConfOpts.end()-1; + if(!value.empty()) + ent->value = expdup(value.c_str()); + else + ConfOpts.erase(ent); } - - TRACE("found '%s' = '%s'\n", ent->key.c_str(), ent->value.c_str()); + else if(!value.empty()) + ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())}); } ConfOpts.shrink_to_fit(); } +const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName) +{ + if(!keyName) + return nullptr; + + std::string key; + if(blockName && al::strcasecmp(blockName, "general") != 0) + { + key = blockName; + if(devName) + { + key += '/'; + key += devName; + } + key += '/'; + key += keyName; + } + else + { + if(devName) + { + key = devName; + key += '/'; + } + key += keyName; + } + + auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(), + [&key](const ConfigEntry &entry) -> bool + { return entry.key == key; }); + if(iter != ConfOpts.cend()) + { + TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str()); + if(!iter->value.empty()) + return iter->value.c_str(); + return nullptr; + } + + if(!devName) + { + TRACE("Key %s not found\n", key.c_str()); + return nullptr; + } + return GetConfigValue(nullptr, blockName, keyName); +} + } // namespace #ifdef _WIN32 -void ReadALConfig(void) noexcept +void ReadALConfig() { WCHAR buffer[MAX_PATH]; if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE) @@ -309,19 +351,18 @@ void ReadALConfig(void) noexcept LoadConfigFromFile(f); } - const WCHAR *str{_wgetenv(L"ALSOFT_CONF")}; - if(str != nullptr && *str) + if(auto confpath = al::getenv(L"ALSOFT_CONF")) { - std::string filepath{wstr_to_utf8(str)}; - - TRACE("Loading config %s...\n", filepath.c_str()); - al::ifstream f{filepath}; + TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str()); + al::ifstream f{*confpath}; if(f.is_open()) LoadConfigFromFile(f); } } + #else -void ReadALConfig(void) noexcept + +void ReadALConfig() { const char *str{"/etc/openal/alsoft.conf"}; @@ -331,9 +372,7 @@ void ReadALConfig(void) noexcept LoadConfigFromFile(f); f.close(); - if(!(str=getenv("XDG_CONFIG_DIRS")) || str[0] == 0) - str = "/etc/xdg"; - std::string confpaths = str; + std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")}; /* Go through the list in reverse, since "the order of base directories * denotes their importance; the first directory listed is the most * important". Ergo, we need to load the settings from the later dirs @@ -362,7 +401,7 @@ void ReadALConfig(void) noexcept else fname += "alsoft.conf"; TRACE("Loading config %s...\n", fname.c_str()); - al::ifstream f{fname}; + f = al::ifstream{fname}; if(f.is_open()) LoadConfigFromFile(f); } @@ -379,37 +418,37 @@ void ReadALConfig(void) noexcept if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) && CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName))) { - al::ifstream f{reinterpret_cast(fileName)}; + f = al::ifstream{reinterpret_cast(fileName)}; if(f.is_open()) LoadConfigFromFile(f); } } #endif - if((str=getenv("HOME")) != nullptr && *str) + if(auto homedir = al::getenv("HOME")) { - fname = str; + fname = *homedir; if(fname.back() != '/') fname += "/.alsoftrc"; else fname += ".alsoftrc"; TRACE("Loading config %s...\n", fname.c_str()); - al::ifstream f{fname}; + f = al::ifstream{fname}; if(f.is_open()) LoadConfigFromFile(f); } - if((str=getenv("XDG_CONFIG_HOME")) != nullptr && str[0] != 0) + if(auto configdir = al::getenv("XDG_CONFIG_HOME")) { - fname = str; + fname = *configdir; if(fname.back() != '/') fname += "/alsoft.conf"; else fname += "alsoft.conf"; } else { fname.clear(); - if((str=getenv("HOME")) != nullptr && str[0] != 0) + if(auto homedir = al::getenv("HOME")) { - fname = str; + fname = *homedir; if(fname.back() != '/') fname += "/.config/alsoft.conf"; else fname += ".config/alsoft.conf"; } @@ -417,7 +456,7 @@ void ReadALConfig(void) noexcept if(!fname.empty()) { TRACE("Loading config %s...\n", fname.c_str()); - al::ifstream f{fname}; + f = al::ifstream{fname}; if(f.is_open()) LoadConfigFromFile(f); } @@ -429,125 +468,61 @@ void ReadALConfig(void) noexcept else ppath += "alsoft.conf"; TRACE("Loading config %s...\n", ppath.c_str()); - al::ifstream f{ppath}; + f = al::ifstream{ppath}; if(f.is_open()) LoadConfigFromFile(f); } - if((str=getenv("ALSOFT_CONF")) != nullptr && *str) + if(auto confname = al::getenv("ALSOFT_CONF")) { - TRACE("Loading config %s...\n", str); - al::ifstream f{str}; + TRACE("Loading config %s...\n", confname->c_str()); + f = al::ifstream{*confname}; if(f.is_open()) LoadConfigFromFile(f); } } #endif -const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def) -{ - if(!keyName) - return def; - - std::string key; - if(blockName && strcasecmp(blockName, "general") != 0) - { - key = blockName; - if(devName) - { - key += '/'; - key += devName; - } - key += '/'; - key += keyName; - } - else - { - if(devName) - { - key = devName; - key += '/'; - } - key += keyName; - } - - auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(), - [&key](const ConfigEntry &entry) -> bool - { return entry.key == key; } - ); - if(iter != ConfOpts.cend()) - { - TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str()); - if(!iter->value.empty()) - return iter->value.c_str(); - return def; - } - - if(!devName) - { - TRACE("Key %s not found\n", key.c_str()); - return def; - } - return GetConfigValue(nullptr, blockName, keyName, def); -} - -int ConfigValueExists(const char *devName, const char *blockName, const char *keyName) +al::optional ConfigValueStr(const char *devName, const char *blockName, const char *keyName) { - const char *val = GetConfigValue(devName, blockName, keyName, ""); - return val[0] != 0; + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return al::make_optional(val); + return al::nullopt; } -int ConfigValueStr(const char *devName, const char *blockName, const char *keyName, const char **ret) +al::optional ConfigValueInt(const char *devName, const char *blockName, const char *keyName) { - const char *val = GetConfigValue(devName, blockName, keyName, ""); - if(!val[0]) return 0; - - *ret = val; - return 1; + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return al::make_optional(static_cast(std::strtol(val, nullptr, 0))); + return al::nullopt; } -int ConfigValueInt(const char *devName, const char *blockName, const char *keyName, int *ret) +al::optional ConfigValueUInt(const char *devName, const char *blockName, const char *keyName) { - const char *val = GetConfigValue(devName, blockName, keyName, ""); - if(!val[0]) return 0; - - *ret = std::strtol(val, nullptr, 0); - return 1; + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return al::make_optional(static_cast(std::strtoul(val, nullptr, 0))); + return al::nullopt; } -int ConfigValueUInt(const char *devName, const char *blockName, const char *keyName, unsigned int *ret) +al::optional ConfigValueFloat(const char *devName, const char *blockName, const char *keyName) { - const char *val = GetConfigValue(devName, blockName, keyName, ""); - if(!val[0]) return 0; - - *ret = std::strtoul(val, nullptr, 0); - return 1; + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return al::make_optional(std::strtof(val, nullptr)); + return al::nullopt; } -int ConfigValueFloat(const char *devName, const char *blockName, const char *keyName, float *ret) +al::optional ConfigValueBool(const char *devName, const char *blockName, const char *keyName) { - const char *val = GetConfigValue(devName, blockName, keyName, ""); - if(!val[0]) return 0; - - *ret = std::strtof(val, nullptr); - return 1; + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return al::make_optional(al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 + || al::strcasecmp(val, "true")==0 || atoi(val) != 0); + return al::nullopt; } -int ConfigValueBool(const char *devName, const char *blockName, const char *keyName, int *ret) +bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def) { - const char *val = GetConfigValue(devName, blockName, keyName, ""); - if(!val[0]) return 0; - - *ret = (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 || - strcasecmp(val, "on") == 0 || atoi(val) != 0); - return 1; -} - -int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def) -{ - const char *val = GetConfigValue(devName, blockName, keyName, ""); - - if(!val[0]) return def != 0; - return (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 || - strcasecmp(val, "on") == 0 || atoi(val) != 0); + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return (al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 + || al::strcasecmp(val, "true") == 0 || atoi(val) != 0); + return def; } diff --git a/modules/openal-soft/Alc/alconfig.h b/modules/openal-soft/Alc/alconfig.h index 0e9bcec..df2830c 100644 --- a/modules/openal-soft/Alc/alconfig.h +++ b/modules/openal-soft/Alc/alconfig.h @@ -1,27 +1,18 @@ #ifndef ALCONFIG_H #define ALCONFIG_H -#ifdef __cplusplus -#define NOEXCEPT noexcept -extern "C" { -#else -#define NOEXCEPT -#endif +#include -void ReadALConfig(void) NOEXCEPT; +#include "aloptional.h" -int ConfigValueExists(const char *devName, const char *blockName, const char *keyName); -const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def); -int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def); +void ReadALConfig(); -int ConfigValueStr(const char *devName, const char *blockName, const char *keyName, const char **ret); -int ConfigValueInt(const char *devName, const char *blockName, const char *keyName, int *ret); -int ConfigValueUInt(const char *devName, const char *blockName, const char *keyName, unsigned int *ret); -int ConfigValueFloat(const char *devName, const char *blockName, const char *keyName, float *ret); -int ConfigValueBool(const char *devName, const char *blockName, const char *keyName, int *ret); +bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def); -#ifdef __cplusplus -} // extern "C" -#endif +al::optional ConfigValueStr(const char *devName, const char *blockName, const char *keyName); +al::optional ConfigValueInt(const char *devName, const char *blockName, const char *keyName); +al::optional ConfigValueUInt(const char *devName, const char *blockName, const char *keyName); +al::optional ConfigValueFloat(const char *devName, const char *blockName, const char *keyName); +al::optional ConfigValueBool(const char *devName, const char *blockName, const char *keyName); #endif /* ALCONFIG_H */ diff --git a/modules/openal-soft/Alc/alcontext.h b/modules/openal-soft/Alc/alcontext.h deleted file mode 100644 index 2c4ad1d..0000000 --- a/modules/openal-soft/Alc/alcontext.h +++ /dev/null @@ -1,214 +0,0 @@ -#ifndef ALCONTEXT_H -#define ALCONTEXT_H - -#include -#include -#include -#include - -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/alext.h" -#include "inprogext.h" - -#include "atomic.h" -#include "vector.h" -#include "threads.h" -#include "almalloc.h" -#include "alnumeric.h" - -#include "alListener.h" - - -struct ALsource; -struct ALeffectslot; -struct ALcontextProps; -struct ALlistenerProps; -struct ALvoiceProps; -struct ALeffectslotProps; -struct ALvoice; -struct RingBuffer; - -enum class DistanceModel { - InverseClamped = AL_INVERSE_DISTANCE_CLAMPED, - LinearClamped = AL_LINEAR_DISTANCE_CLAMPED, - ExponentClamped = AL_EXPONENT_DISTANCE_CLAMPED, - Inverse = AL_INVERSE_DISTANCE, - Linear = AL_LINEAR_DISTANCE, - Exponent = AL_EXPONENT_DISTANCE, - Disable = AL_NONE, - - Default = InverseClamped -}; - -struct SourceSubList { - uint64_t FreeMask{~0_u64}; - ALsource *Sources{nullptr}; /* 64 */ - - SourceSubList() noexcept = default; - SourceSubList(const SourceSubList&) = delete; - SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources} - { rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; } - ~SourceSubList(); - - SourceSubList& operator=(const SourceSubList&) = delete; - SourceSubList& operator=(SourceSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; } -}; - -struct EffectSlotSubList { - uint64_t FreeMask{~0_u64}; - ALeffectslot *EffectSlots{nullptr}; /* 64 */ - - EffectSlotSubList() noexcept = default; - EffectSlotSubList(const EffectSlotSubList&) = delete; - EffectSlotSubList(EffectSlotSubList&& rhs) noexcept - : FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots} - { rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; } - ~EffectSlotSubList(); - - EffectSlotSubList& operator=(const EffectSlotSubList&) = delete; - EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; } -}; - -struct ALCcontext { - RefCount ref{1u}; - - al::vector SourceList; - ALuint NumSources{0}; - std::mutex SourceLock; - - al::vector EffectSlotList; - ALuint NumEffectSlots{0u}; - std::mutex EffectSlotLock; - - std::atomic LastError{AL_NO_ERROR}; - - DistanceModel mDistanceModel{DistanceModel::Default}; - ALboolean SourceDistanceModel{AL_FALSE}; - - ALfloat DopplerFactor{1.0f}; - ALfloat DopplerVelocity{1.0f}; - ALfloat SpeedOfSound{}; - ALfloat MetersPerUnit{1.0f}; - - std::atomic_flag PropsClean; - std::atomic DeferUpdates{false}; - - std::mutex PropLock; - - /* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit - * indicates if updates are currently happening). - */ - RefCount UpdateCount{0u}; - std::atomic HoldUpdates{false}; - - ALfloat GainBoost{1.0f}; - - std::atomic Update{nullptr}; - - /* Linked lists of unused property containers, free to use for future - * updates. - */ - std::atomic FreeContextProps{nullptr}; - std::atomic FreeListenerProps{nullptr}; - std::atomic FreeVoiceProps{nullptr}; - std::atomic FreeEffectslotProps{nullptr}; - - ALvoice **Voices{nullptr}; - std::atomic VoiceCount{0}; - ALsizei MaxVoices{0}; - - using ALeffectslotArray = al::FlexArray; - std::atomic ActiveAuxSlots{nullptr}; - - std::thread EventThread; - al::semaphore EventSem; - std::unique_ptr AsyncEvents; - std::atomic EnabledEvts{0u}; - std::mutex EventCbLock; - ALEVENTPROCSOFT EventCb{}; - void *EventParam{nullptr}; - - /* Default effect slot */ - std::unique_ptr DefaultSlot; - - ALCdevice *const Device; - const ALCchar *ExtensionList{nullptr}; - - std::atomic next{nullptr}; - - ALlistener Listener{}; - - - ALCcontext(ALCdevice *device); - ALCcontext(const ALCcontext&) = delete; - ALCcontext& operator=(const ALCcontext&) = delete; - ~ALCcontext(); - - static constexpr inline const char *CurrentPrefix() noexcept { return "ALCcontext::"; } - DEF_NEWDEL(ALCcontext) -}; - -void ALCcontext_DecRef(ALCcontext *context); - -void UpdateContextProps(ALCcontext *context); - -void ALCcontext_DeferUpdates(ALCcontext *context); -void ALCcontext_ProcessUpdates(ALCcontext *context); - - -/* Simple RAII context reference. Takes the reference of the provided - * ALCcontext, and decrements it when leaving scope. Movable (transfer - * reference) but not copyable (no new references). - */ -class ContextRef { - ALCcontext *mCtx{nullptr}; - - void reset() noexcept - { - if(mCtx) - ALCcontext_DecRef(mCtx); - mCtx = nullptr; - } - -public: - ContextRef() noexcept = default; - ContextRef(ContextRef&& rhs) noexcept : mCtx{rhs.mCtx} - { rhs.mCtx = nullptr; } - explicit ContextRef(ALCcontext *ctx) noexcept : mCtx(ctx) { } - ~ContextRef() { reset(); } - - ContextRef& operator=(const ContextRef&) = delete; - ContextRef& operator=(ContextRef&& rhs) noexcept - { std::swap(mCtx, rhs.mCtx); return *this; } - - operator bool() const noexcept { return mCtx != nullptr; } - - ALCcontext* operator->() noexcept { return mCtx; } - ALCcontext* get() noexcept { return mCtx; } - - ALCcontext* release() noexcept - { - ALCcontext *ret{mCtx}; - mCtx = nullptr; - return ret; - } -}; - -ContextRef GetContextRef(void); - - -struct ALcontextProps { - ALfloat DopplerFactor; - ALfloat DopplerVelocity; - ALfloat SpeedOfSound; - ALboolean SourceDistanceModel; - DistanceModel mDistanceModel; - ALfloat MetersPerUnit; - - std::atomic next; -}; - -#endif /* ALCONTEXT_H */ diff --git a/modules/openal-soft/Alc/alu.cpp b/modules/openal-soft/Alc/alu.cpp index d1e0772..ef88515 100644 --- a/modules/openal-soft/Alc/alu.cpp +++ b/modules/openal-soft/Alc/alu.cpp @@ -20,96 +20,137 @@ #include "config.h" -#include -#include -#include -#include -#include +#include "alu.h" -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include - -#include "alMain.h" -#include "alcontext.h" -#include "alSource.h" -#include "alBuffer.h" -#include "alListener.h" -#include "alAuxEffectSlot.h" -#include "alu.h" -#include "bs2b.h" -#include "hrtf.h" -#include "mastering.h" -#include "uhjfilter.h" -#include "bformatdec.h" +#include +#include +#include +#include +#include +#include + +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "alstring.h" +#include "atomic.h" +#include "core/ambidefs.h" +#include "core/async_event.h" +#include "core/bformatdec.h" +#include "core/bs2b.h" +#include "core/bsinc_defs.h" +#include "core/bsinc_tables.h" +#include "core/bufferline.h" +#include "core/buffer_storage.h" +#include "core/context.h" +#include "core/cpu_caps.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effects/base.h" +#include "core/effectslot.h" +#include "core/filters/biquad.h" +#include "core/filters/nfc.h" +#include "core/fpu_ctrl.h" +#include "core/hrtf.h" +#include "core/mastering.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "core/mixer/hrtfdefs.h" +#include "core/resampler_limits.h" +#include "core/uhjfilter.h" +#include "core/voice.h" +#include "core/voice_change.h" +#include "intrusive_ptr.h" +#include "opthelpers.h" #include "ringbuffer.h" -#include "filters/splitter.h" +#include "strutils.h" +#include "threads.h" +#include "vecmat.h" +#include "vector.h" + +struct CTag; +#ifdef HAVE_SSE +struct SSETag; +#endif +#ifdef HAVE_SSE2 +struct SSE2Tag; +#endif +#ifdef HAVE_SSE4_1 +struct SSE4Tag; +#endif +#ifdef HAVE_NEON +struct NEONTag; +#endif +struct PointTag; +struct LerpTag; +struct CubicTag; +struct BSincTag; +struct FastBSincTag; -#include "mixer/defs.h" -#include "fpu_modes.h" -#include "cpu_caps.h" -#include "bsinc_inc.h" + +static_assert(!(MaxResamplerPadding&1), "MaxResamplerPadding is not a multiple of two"); namespace { -using namespace std::placeholders; +using uint = unsigned int; -ALfloat InitConeScale() -{ - ALfloat ret{1.0f}; - const char *str{getenv("__ALSOFT_HALF_ANGLE_CONES")}; - if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1)) - ret *= 0.5f; - return ret; -} +constexpr uint MaxPitch{10}; -ALfloat InitZScale() -{ - ALfloat ret{1.0f}; - const char *str{getenv("__ALSOFT_REVERSE_Z")}; - if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1)) - ret *= -1.0f; - return ret; -} +static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!"); +static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize, + "MaxPitch and/or BufferLineSize are too large for MixerFracBits!"); -ALboolean InitReverbSOS() +using namespace std::placeholders; + +float InitConeScale() { - ALboolean ret{AL_FALSE}; - const char *str{getenv("__ALSOFT_REVERB_IGNORES_SOUND_SPEED")}; - if(str && (strcasecmp(str, "true") == 0 || strtol(str, nullptr, 0) == 1)) - ret = AL_TRUE; + float ret{1.0f}; + if(auto optval = al::getenv("__ALSOFT_HALF_ANGLE_CONES")) + { + if(al::strcasecmp(optval->c_str(), "true") == 0 + || strtol(optval->c_str(), nullptr, 0) == 1) + ret *= 0.5f; + } return ret; } - -} // namespace - /* Cone scalar */ -const ALfloat ConeScale{InitConeScale()}; - -/* Localized Z scalar for mono sources */ -const ALfloat ZScale{InitZScale()}; +const float ConeScale{InitConeScale()}; -/* Force default speed of sound for distance-related reverb decay. */ -const ALboolean OverrideReverbSpeedOfSound{InitReverbSOS()}; +/* Localized scalars for mono sources (initialized in aluInit, after + * configuration is loaded). + */ +float XScale{1.0f}; +float YScale{1.0f}; +float ZScale{1.0f}; +} // namespace namespace { -void ClearArray(ALfloat (&f)[MAX_OUTPUT_CHANNELS]) -{ - std::fill(std::begin(f), std::end(f), 0.0f); -} - struct ChanMap { Channel channel; - ALfloat angle; - ALfloat elevation; + float angle; + float elevation; }; -HrtfDirectMixerFunc MixDirectHrtf = MixDirectHrtf_; +using HrtfDirectMixerFunc = void(*)(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, + const al::span InSamples, float2 *AccumSamples, float *TempBuf, + HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize); + +HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_}; + inline HrtfDirectMixerFunc SelectHrtfMixer(void) { #ifdef HAVE_NEON @@ -125,107 +166,163 @@ inline HrtfDirectMixerFunc SelectHrtfMixer(void) } -void ProcessHrtf(ALCdevice *device, const ALsizei SamplesToDo) +inline void BsincPrepare(const uint increment, BsincState *state, const BSincTable *table) { - /* HRTF is stereo output only. */ - const int lidx{device->RealOut.ChannelIndex[FrontLeft]}; - const int ridx{device->RealOut.ChannelIndex[FrontRight]}; - ASSUME(lidx >= 0 && ridx >= 0); + size_t si{BSincScaleCount - 1}; + float sf{0.0f}; - DirectHrtfState *state{device->mHrtfState.get()}; - MixDirectHrtf(device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx], device->Dry.Buffer, - device->HrtfAccumData, state, device->Dry.NumChannels, SamplesToDo); -} + if(increment > MixerFracOne) + { + sf = MixerFracOne/static_cast(increment) - table->scaleBase; + sf = maxf(0.0f, BSincScaleCount*sf*table->scaleRange - 1.0f); + si = float2uint(sf); + /* The interpolation factor is fit to this diagonally-symmetric curve + * to reduce the transition ripple caused by interpolating different + * scales of the sinc function. + */ + sf = 1.0f - std::cos(std::asin(sf - static_cast(si))); + } -void ProcessAmbiDec(ALCdevice *device, const ALsizei SamplesToDo) -{ - BFormatDec *ambidec{device->AmbiDecoder.get()}; - ambidec->process(device->RealOut.Buffer, device->RealOut.NumChannels, device->Dry.Buffer, - SamplesToDo); + state->sf = sf; + state->m = table->m[si]; + state->l = (state->m/2) - 1; + state->filter = table->Tab + table->filterOffset[si]; } -void ProcessUhj(ALCdevice *device, const ALsizei SamplesToDo) +inline ResamplerFunc SelectResampler(Resampler resampler, uint increment) { - /* UHJ is stereo output only. */ - const int lidx{device->RealOut.ChannelIndex[FrontLeft]}; - const int ridx{device->RealOut.ChannelIndex[FrontRight]}; - ASSUME(lidx >= 0 && ridx >= 0); + switch(resampler) + { + case Resampler::Point: + return Resample_; + case Resampler::Linear: +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return Resample_; +#endif +#ifdef HAVE_SSE4_1 + if((CPUCapFlags&CPU_CAP_SSE4_1)) + return Resample_; +#endif +#ifdef HAVE_SSE2 + if((CPUCapFlags&CPU_CAP_SSE2)) + return Resample_; +#endif + return Resample_; + case Resampler::Cubic: + return Resample_; + case Resampler::BSinc12: + case Resampler::BSinc24: + if(increment <= MixerFracOne) + { + /* fall-through */ + case Resampler::FastBSinc12: + case Resampler::FastBSinc24: +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return Resample_; +#endif +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return Resample_; +#endif + return Resample_; + } +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return Resample_; +#endif +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return Resample_; +#endif + return Resample_; + } - /* Encode to stereo-compatible 2-channel UHJ output. */ - Uhj2Encoder *uhj2enc{device->Uhj_Encoder.get()}; - uhj2enc->encode(device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx], - device->Dry.Buffer, SamplesToDo); + return Resample_; } -void ProcessBs2b(ALCdevice *device, const ALsizei SamplesToDo) -{ - /* BS2B is stereo output only. */ - const int lidx{device->RealOut.ChannelIndex[FrontLeft]}; - const int ridx{device->RealOut.ChannelIndex[FrontRight]}; - ASSUME(lidx >= 0 && ridx >= 0); +} // namespace - /* Apply binaural/crossfeed filter */ - bs2b_cross_feed(device->Bs2b.get(), device->RealOut.Buffer[lidx], - device->RealOut.Buffer[ridx], SamplesToDo); +void aluInit(CompatFlagBitset flags) +{ + MixDirectHrtf = SelectHrtfMixer(); + XScale = flags.test(CompatFlags::ReverseX) ? -1.0f : 1.0f; + YScale = flags.test(CompatFlags::ReverseY) ? -1.0f : 1.0f; + ZScale = flags.test(CompatFlags::ReverseZ) ? -1.0f : 1.0f; } -} // namespace -void aluInit(void) +ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState *state) { - MixDirectHrtf = SelectHrtfMixer(); + switch(resampler) + { + case Resampler::Point: + case Resampler::Linear: + case Resampler::Cubic: + break; + case Resampler::FastBSinc12: + case Resampler::BSinc12: + BsincPrepare(increment, &state->bsinc, &bsinc12); + break; + case Resampler::FastBSinc24: + case Resampler::BSinc24: + BsincPrepare(increment, &state->bsinc, &bsinc24); + break; + } + return SelectResampler(resampler, increment); } -void DeinitVoice(ALvoice *voice) noexcept +void DeviceBase::ProcessHrtf(const size_t SamplesToDo) { - delete voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel); - voice->~ALvoice(); + /* HRTF is stereo output only. */ + const uint lidx{RealOut.ChannelIndex[FrontLeft]}; + const uint ridx{RealOut.ChannelIndex[FrontRight]}; + + MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData, + mHrtfState->mTemp.data(), mHrtfState->mChannels.data(), mHrtfState->mIrSize, SamplesToDo); } +void DeviceBase::ProcessAmbiDec(const size_t SamplesToDo) +{ + AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo); +} -void aluSelectPostProcess(ALCdevice *device) +void DeviceBase::ProcessAmbiDecStablized(const size_t SamplesToDo) { - if(device->mHrtf) - device->PostProcess = ProcessHrtf; - else if(device->AmbiDecoder) - device->PostProcess = ProcessAmbiDec; - else if(device->Uhj_Encoder) - device->PostProcess = ProcessUhj; - else if(device->Bs2b) - device->PostProcess = ProcessBs2b; - else - device->PostProcess = nullptr; + /* Decode with front image stablization. */ + const uint lidx{RealOut.ChannelIndex[FrontLeft]}; + const uint ridx{RealOut.ChannelIndex[FrontRight]}; + const uint cidx{RealOut.ChannelIndex[FrontCenter]}; + + AmbiDecoder->processStablize(RealOut.Buffer, Dry.Buffer.data(), lidx, ridx, cidx, + SamplesToDo); } +void DeviceBase::ProcessUhj(const size_t SamplesToDo) +{ + /* UHJ is stereo output only. */ + const uint lidx{RealOut.ChannelIndex[FrontLeft]}; + const uint ridx{RealOut.ChannelIndex[FrontRight]}; -/* Prepares the interpolator for a given rate (determined by increment). - * - * With a bit of work, and a trade of memory for CPU cost, this could be - * modified for use with an interpolated increment for buttery-smooth pitch - * changes. - */ -void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table) + /* Encode to stereo-compatible 2-channel UHJ output. */ + mUhjEncoder->encode(RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), + Dry.Buffer.data(), SamplesToDo); +} + +void DeviceBase::ProcessBs2b(const size_t SamplesToDo) { - ALsizei si{BSINC_SCALE_COUNT - 1}; - ALfloat sf{0.0f}; + /* First, decode the ambisonic mix to the "real" output. */ + AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo); - if(increment > FRACTIONONE) - { - sf = static_castFRACTIONONE / increment; - sf = maxf(0.0f, (BSINC_SCALE_COUNT-1) * (sf-table->scaleBase) * table->scaleRange); - si = float2int(sf); - /* The interpolation factor is fit to this diagonally-symmetric curve - * to reduce the transition ripple caused by interpolating different - * scales of the sinc function. - */ - sf = 1.0f - std::cos(std::asin(sf - si)); - } + /* BS2B is stereo output only. */ + const uint lidx{RealOut.ChannelIndex[FrontLeft]}; + const uint ridx{RealOut.ChannelIndex[FrontRight]}; - state->sf = sf; - state->m = table->m[si]; - state->l = (state->m/2) - 1; - state->filter = table->Tab + table->filterOffset[si]; + /* Now apply the BS2B binaural/crossfeed filter. */ + bs2b_cross_feed(Bs2b.get(), RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), + SamplesToDo); } @@ -235,67 +332,45 @@ namespace { * and starting with a seed value of 22222, is suitable for generating * whitenoise. */ -inline ALuint dither_rng(ALuint *seed) noexcept +inline uint dither_rng(uint *seed) noexcept { *seed = (*seed * 96314165) + 907633515; return *seed; } -inline alu::Vector aluCrossproduct(const alu::Vector &in1, const alu::Vector &in2) +inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept { - return alu::Vector{ - in1[1]*in2[2] - in1[2]*in2[1], - in1[2]*in2[0] - in1[0]*in2[2], - in1[0]*in2[1] - in1[1]*in2[0], - 0.0f - }; + switch(scaletype) + { + case AmbiScaling::FuMa: return AmbiScale::FromFuMa(); + case AmbiScaling::SN3D: return AmbiScale::FromSN3D(); + case AmbiScaling::UHJ: return AmbiScale::FromUHJ(); + case AmbiScaling::N3D: break; + } + return AmbiScale::FromN3D(); } -inline ALfloat aluDotproduct(const alu::Vector &vec1, const alu::Vector &vec2) +inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept { - return vec1[0]*vec2[0] + vec1[1]*vec2[1] + vec1[2]*vec2[2]; + if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa(); + return AmbiIndex::FromACN(); } - -alu::Vector operator*(const alu::Matrix &mtx, const alu::Vector &vec) noexcept +inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept { - return alu::Vector{ - vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0], - vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1], - vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2], - vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3] - }; + if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D(); + return AmbiIndex::FromACN2D(); } -bool CalcContextParams(ALCcontext *Context) +bool CalcContextParams(ContextBase *ctx) { - ALcontextProps *props{Context->Update.exchange(nullptr, std::memory_order_acq_rel)}; + ContextProps *props{ctx->mParams.ContextUpdate.exchange(nullptr, std::memory_order_acq_rel)}; if(!props) return false; - ALlistener &Listener = Context->Listener; - Listener.Params.MetersPerUnit = props->MetersPerUnit; - - Listener.Params.DopplerFactor = props->DopplerFactor; - Listener.Params.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity; - if(!OverrideReverbSpeedOfSound) - Listener.Params.ReverbSpeedOfSound = Listener.Params.SpeedOfSound * - Listener.Params.MetersPerUnit; - - Listener.Params.SourceDistanceModel = props->SourceDistanceModel; - Listener.Params.mDistanceModel = props->mDistanceModel; - - AtomicReplaceHead(Context->FreeContextProps, props); - return true; -} - -bool CalcListenerParams(ALCcontext *Context) -{ - ALlistener &Listener = Context->Listener; - - ALlistenerProps *props{Listener.Update.exchange(nullptr, std::memory_order_acq_rel)}; - if(!props) return false; + const alu::Vector pos{props->Position[0], props->Position[1], props->Position[2], 1.0f}; + ctx->mParams.Position = pos; /* AT then UP */ alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f}; @@ -303,118 +378,108 @@ bool CalcListenerParams(ALCcontext *Context) alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f}; V.normalize(); /* Build and normalize right-vector */ - alu::Vector U{aluCrossproduct(N, V)}; + alu::Vector U{N.cross_product(V)}; U.normalize(); - Listener.Params.Matrix = alu::Matrix{ - U[0], V[0], -N[0], 0.0f, - U[1], V[1], -N[1], 0.0f, - U[2], V[2], -N[2], 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; + const alu::Matrix rot{ + U[0], V[0], -N[0], 0.0, + U[1], V[1], -N[1], 0.0, + U[2], V[2], -N[2], 0.0, + 0.0, 0.0, 0.0, 1.0}; + const alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0}; + + ctx->mParams.Matrix = rot; + ctx->mParams.Velocity = rot * vel; - const alu::Vector P{Listener.Params.Matrix * - alu::Vector{props->Position[0], props->Position[1], props->Position[2], 1.0f}}; - Listener.Params.Matrix.setRow(3, -P[0], -P[1], -P[2], 1.0f); + ctx->mParams.Gain = props->Gain * ctx->mGainBoost; + ctx->mParams.MetersPerUnit = props->MetersPerUnit; + ctx->mParams.AirAbsorptionGainHF = props->AirAbsorptionGainHF; - const alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f}; - Listener.Params.Velocity = Listener.Params.Matrix * vel; + ctx->mParams.DopplerFactor = props->DopplerFactor; + ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity; - Listener.Params.Gain = props->Gain * Context->GainBoost; + ctx->mParams.SourceDistanceModel = props->SourceDistanceModel; + ctx->mParams.mDistanceModel = props->mDistanceModel; - AtomicReplaceHead(Context->FreeListenerProps, props); + AtomicReplaceHead(ctx->mFreeContextProps, props); return true; } -bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context, bool force) +bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBase *context) { - ALeffectslotProps *props{slot->Update.exchange(nullptr, std::memory_order_acq_rel)}; - if(!props && !force) return false; + EffectSlotProps *props{slot->Update.exchange(nullptr, std::memory_order_acq_rel)}; + if(!props) return false; - EffectState *state; - if(!props) - state = slot->Params.mEffectState; + /* If the effect slot target changed, clear the first sorted entry to force + * a re-sort. + */ + if(slot->Target != props->Target) + *sorted_slots = nullptr; + slot->Gain = props->Gain; + slot->AuxSendAuto = props->AuxSendAuto; + slot->Target = props->Target; + slot->EffectType = props->Type; + slot->mEffectProps = props->Props; + if(props->Type == EffectSlotType::Reverb || props->Type == EffectSlotType::EAXReverb) + { + slot->RoomRolloff = props->Props.Reverb.RoomRolloffFactor; + slot->DecayTime = props->Props.Reverb.DecayTime; + slot->DecayLFRatio = props->Props.Reverb.DecayLFRatio; + slot->DecayHFRatio = props->Props.Reverb.DecayHFRatio; + slot->DecayHFLimit = props->Props.Reverb.DecayHFLimit; + slot->AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF; + } else { - slot->Params.Gain = props->Gain; - slot->Params.AuxSendAuto = props->AuxSendAuto; - slot->Params.Target = props->Target; - slot->Params.EffectType = props->Type; - slot->Params.mEffectProps = props->Props; - if(IsReverbEffect(props->Type)) - { - slot->Params.RoomRolloff = props->Props.Reverb.RoomRolloffFactor; - slot->Params.DecayTime = props->Props.Reverb.DecayTime; - slot->Params.DecayLFRatio = props->Props.Reverb.DecayLFRatio; - slot->Params.DecayHFRatio = props->Props.Reverb.DecayHFRatio; - slot->Params.DecayHFLimit = props->Props.Reverb.DecayHFLimit; - slot->Params.AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF; - } - else - { - slot->Params.RoomRolloff = 0.0f; - slot->Params.DecayTime = 0.0f; - slot->Params.DecayLFRatio = 0.0f; - slot->Params.DecayHFRatio = 0.0f; - slot->Params.DecayHFLimit = AL_FALSE; - slot->Params.AirAbsorptionGainHF = 1.0f; - } + slot->RoomRolloff = 0.0f; + slot->DecayTime = 0.0f; + slot->DecayLFRatio = 0.0f; + slot->DecayHFRatio = 0.0f; + slot->DecayHFLimit = false; + slot->AirAbsorptionGainHF = 1.0f; + } - state = props->State; - props->State = nullptr; - EffectState *oldstate{slot->Params.mEffectState}; - slot->Params.mEffectState = state; + EffectState *state{props->State.release()}; + EffectState *oldstate{slot->mEffectState}; + slot->mEffectState = state; - /* Manually decrement the old effect state's refcount if it's greater - * than 1. We need to be a bit clever here to avoid the refcount - * reaching 0 since it can't be deleted in the mixer. - */ - ALuint oldval{oldstate->mRef.load(std::memory_order_acquire)}; - while(oldval > 1 && !oldstate->mRef.compare_exchange_weak(oldval, oldval-1, - std::memory_order_acq_rel, std::memory_order_acquire)) + /* Only release the old state if it won't get deleted, since we can't be + * deleting/freeing anything in the mixer. + */ + if(!oldstate->releaseIfNoDelete()) + { + /* Otherwise, if it would be deleted send it off with a release event. */ + RingBuffer *ring{context->mAsyncEvents.get()}; + auto evt_vec = ring->getWriteVector(); + if LIKELY(evt_vec.first.len > 0) { - /* oldval was updated with the current value on failure, so just - * try again. - */ + AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), + AsyncEvent::ReleaseEffectState)}; + evt->u.mEffectState = oldstate; + ring->writeAdvance(1); } - - if(oldval < 2) + else { - /* Otherwise, if it would be deleted, send it off with a release - * event. + /* If writing the event failed, the queue was probably full. Store + * the old state in the property object where it can eventually be + * cleaned up sometime later (not ideal, but better than blocking + * or leaking). */ - RingBuffer *ring{context->AsyncEvents.get()}; - auto evt_vec = ring->getWriteVector(); - if(LIKELY(evt_vec.first.len > 0)) - { - AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_ReleaseEffectState}}; - evt->u.mEffectState = oldstate; - ring->writeAdvance(1); - context->EventSem.post(); - } - else - { - /* If writing the event failed, the queue was probably full. - * Store the old state in the property object where it can - * eventually be cleaned up sometime later (not ideal, but - * better than blocking or leaking). - */ - props->State = oldstate; - } + props->State.reset(oldstate); } - - AtomicReplaceHead(context->FreeEffectslotProps, props); } + AtomicReplaceHead(context->mFreeEffectslotProps, props); + EffectTarget output; - if(ALeffectslot *target{slot->Params.Target}) + if(EffectSlot *target{slot->Target}) output = EffectTarget{&target->Wet, nullptr}; else { - ALCdevice *device{context->Device}; + DeviceBase *device{context->mDevice}; output = EffectTarget{&device->Dry, &device->RealOut}; } - state->update(context, slot, &slot->Params.mEffectProps, output); + state->update(context, slot, &slot->mEffectProps, output); return true; } @@ -424,18 +489,188 @@ bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context, bool force) */ inline float ScaleAzimuthFront(float azimuth, float scale) { - const ALfloat abs_azi{std::fabs(azimuth)}; - if(!(abs_azi > al::MathDefs::Pi()*0.5f)) - return minf(abs_azi*scale, al::MathDefs::Pi()*0.5f) * std::copysign(1.0f, azimuth); + const float abs_azi{std::fabs(azimuth)}; + if(!(abs_azi >= al::numbers::pi_v*0.5f)) + return std::copysign(minf(abs_azi*scale, al::numbers::pi_v*0.5f), azimuth); return azimuth; } -void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypos, - const ALfloat zpos, const ALfloat Distance, const ALfloat Spread, const ALfloat DryGain, - const ALfloat DryGainHF, const ALfloat DryGainLF, const ALfloat (&WetGain)[MAX_SENDS], - const ALfloat (&WetGainLF)[MAX_SENDS], const ALfloat (&WetGainHF)[MAX_SENDS], - ALeffectslot *(&SendSlots)[MAX_SENDS], const ALvoicePropsBase *props, - const ALlistener &Listener, const ALCdevice *Device) +/* Wraps the given value in radians to stay between [-pi,+pi] */ +inline float WrapRadians(float r) +{ + static constexpr float Pi{al::numbers::pi_v}; + static constexpr float Pi2{Pi*2.0f}; + if(r > Pi) return std::fmod(Pi+r, Pi2) - Pi; + if(r < -Pi) return Pi - std::fmod(Pi-r, Pi2); + return r; +} + +/* Begin ambisonic rotation helpers. + * + * Rotating first-order B-Format just needs a straight-forward X/Y/Z rotation + * matrix. Higher orders, however, are more complicated. The method implemented + * here is a recursive algorithm (the rotation for first-order is used to help + * generate the second-order rotation, which helps generate the third-order + * rotation, etc). + * + * Adapted from + * , + * provided under the BSD 3-Clause license. + * + * Copyright (c) 2015, Archontis Politis + * Copyright (c) 2019, Christopher Robinson + * + * The u, v, and w coefficients used for generating higher-order rotations are + * precomputed since they're constant. The second-order coefficients are + * followed by the third-order coefficients, etc. + */ +struct RotatorCoeffs { + float u, v, w; + + template + static std::array ConcatArrays(const std::array &lhs, + const std::array &rhs) + { + std::array ret; + auto iter = std::copy(lhs.cbegin(), lhs.cend(), ret.begin()); + std::copy(rhs.cbegin(), rhs.cend(), iter); + return ret; + } + + template + static std::array GenCoeffs() + { + std::array ret{}; + auto coeffs = ret.begin(); + + for(int m{-l};m <= l;++m) + { + for(int n{-l};n <= l;++n) + { + // compute u,v,w terms of Eq.8.1 (Table I) + const bool d{m == 0}; // the delta function d_m0 + const float denom{static_cast((std::abs(n) == l) ? + (2*l) * (2*l - 1) : (l*l - n*n))}; + + const int abs_m{std::abs(m)}; + coeffs->u = std::sqrt(static_cast(l*l - m*m)/denom); + coeffs->v = std::sqrt(static_cast(l+abs_m-1) * static_cast(l+abs_m) / + denom) * (1.0f+d) * (1.0f - 2.0f*d) * 0.5f; + coeffs->w = std::sqrt(static_cast(l-abs_m-1) * static_cast(l-abs_m) / + denom) * (1.0f-d) * -0.5f; + ++coeffs; + } + } + + return ret; + } +}; +const auto RotatorCoeffArray = RotatorCoeffs::ConcatArrays(RotatorCoeffs::GenCoeffs<2>(), + RotatorCoeffs::GenCoeffs<3>()); + +/** + * Given the matrix, pre-filled with the (zeroth- and) first-order rotation + * coefficients, this fills in the coefficients for the higher orders up to and + * including the given order. The matrix is in ACN layout. + */ +void AmbiRotator(std::array,MaxAmbiChannels> &matrix, + const int order) +{ + /* Don't do anything for < 2nd order. */ + if(order < 2) return; + + auto P = [](const int i, const int l, const int a, const int n, const size_t last_band, + const std::array,MaxAmbiChannels> &R) + { + const float ri1{ R[static_cast(i+2)][ 1+2]}; + const float rim1{R[static_cast(i+2)][-1+2]}; + const float ri0{ R[static_cast(i+2)][ 0+2]}; + + auto vec = R[static_cast(a+l-1) + last_band].cbegin() + last_band; + if(n == -l) + return ri1*vec[0] + rim1*vec[static_cast(l-1)*size_t{2}]; + if(n == l) + return ri1*vec[static_cast(l-1)*size_t{2}] - rim1*vec[0]; + return ri0*vec[static_cast(n+l-1)]; + }; + + auto U = [P](const int l, const int m, const int n, const size_t last_band, + const std::array,MaxAmbiChannels> &R) + { + return P(0, l, m, n, last_band, R); + }; + auto V = [P](const int l, const int m, const int n, const size_t last_band, + const std::array,MaxAmbiChannels> &R) + { + using namespace al::numbers; + if(m > 0) + { + const bool d{m == 1}; + const float p0{P( 1, l, m-1, n, last_band, R)}; + const float p1{P(-1, l, -m+1, n, last_band, R)}; + return d ? p0*sqrt2_v : (p0 - p1); + } + const bool d{m == -1}; + const float p0{P( 1, l, m+1, n, last_band, R)}; + 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, + const std::array,MaxAmbiChannels> &R) + { + assert(m != 0); + if(m > 0) + { + const float p0{P( 1, l, m+1, n, last_band, R)}; + const float p1{P(-1, l, -m-1, n, last_band, R)}; + return p0 + p1; + } + const float p0{P( 1, l, m-1, n, last_band, R)}; + const float p1{P(-1, l, -m+1, n, last_band, R)}; + return p0 - p1; + }; + + // compute rotation matrix of each subsequent band recursively + auto coeffs = RotatorCoeffArray.cbegin(); + size_t band_idx{4}, last_band{1}; + for(int l{2};l <= order;++l) + { + size_t y{band_idx}; + for(int m{-l};m <= l;++m,++y) + { + size_t x{band_idx}; + for(int n{-l};n <= l;++n,++x) + { + float r{0.0f}; + + // computes Eq.8.1 + const float u{coeffs->u}; + if(u != 0.0f) r += u * U(l, m, n, last_band, matrix); + const float v{coeffs->v}; + if(v != 0.0f) r += v * V(l, m, n, last_band, matrix); + const float w{coeffs->w}; + if(w != 0.0f) r += w * W(l, m, n, last_band, matrix); + + matrix[y][x] = r; + ++coeffs; + } + } + last_band = band_idx; + band_idx += static_cast(l)*size_t{2} + 1; + } +} +/* End ambisonic rotation helpers. */ + + +constexpr float Deg2Rad(float x) noexcept +{ return static_cast(al::numbers::pi / 180.0 * x); } + +struct GainTriplet { float Base, HF, LF; }; + +void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, const float zpos, + const float Distance, const float Spread, const GainTriplet &DryGain, + const al::span WetGain, EffectSlot *(&SendSlots)[MAX_SENDS], + const VoiceProps *props, const ContextParams &Context, const DeviceBase *Device) { static constexpr ChanMap MonoMap[1]{ { FrontCenter, 0.0f, 0.0f } @@ -478,174 +713,135 @@ void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypo { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) } }; - const auto Frequency = static_cast(Device->Frequency); - const ALsizei NumSends{Device->NumAuxSends}; - ASSUME(NumSends >= 0); + const auto Frequency = static_cast(Device->Frequency); + const uint NumSends{Device->NumAuxSends}; + + const size_t num_channels{voice->mChans.size()}; + ASSUME(num_channels > 0); + + for(auto &chandata : voice->mChans) + { + chandata.mDryParams.Hrtf.Target = HrtfFilter{}; + chandata.mDryParams.Gains.Target.fill(0.0f); + std::for_each(chandata.mWetParams.begin(), chandata.mWetParams.begin()+NumSends, + [](SendParams ¶ms) -> void { params.Gains.Target.fill(0.0f); }); + } - bool DirectChannels{props->DirectChannels != AL_FALSE}; + DirectMode DirectChannels{props->DirectChannels}; const ChanMap *chans{nullptr}; - ALsizei num_channels{0}; - bool isbformat{false}; - ALfloat downmix_gain{1.0f}; switch(voice->mFmtChannels) { case FmtMono: chans = MonoMap; - num_channels = 1; /* Mono buffers are never played direct. */ - DirectChannels = false; + DirectChannels = DirectMode::Off; break; case FmtStereo: - /* Convert counter-clockwise to clockwise. */ - StereoMap[0].angle = -props->StereoPan[0]; - StereoMap[1].angle = -props->StereoPan[1]; - + if(DirectChannels == DirectMode::Off) + { + /* Convert counter-clockwise to clock-wise, and wrap between + * [-pi,+pi]. + */ + StereoMap[0].angle = WrapRadians(-props->StereoPan[0]); + StereoMap[1].angle = WrapRadians(-props->StereoPan[1]); + } chans = StereoMap; - num_channels = 2; - downmix_gain = 1.0f / 2.0f; - break; - - case FmtRear: - chans = RearMap; - num_channels = 2; - downmix_gain = 1.0f / 2.0f; - break; - - case FmtQuad: - chans = QuadMap; - num_channels = 4; - downmix_gain = 1.0f / 4.0f; - break; - - case FmtX51: - chans = X51Map; - num_channels = 6; - /* NOTE: Excludes LFE. */ - downmix_gain = 1.0f / 5.0f; - break; - - case FmtX61: - chans = X61Map; - num_channels = 7; - /* NOTE: Excludes LFE. */ - downmix_gain = 1.0f / 6.0f; break; - case FmtX71: - chans = X71Map; - num_channels = 8; - /* NOTE: Excludes LFE. */ - downmix_gain = 1.0f / 7.0f; - break; + case FmtRear: chans = RearMap; break; + case FmtQuad: chans = QuadMap; break; + case FmtX51: chans = X51Map; break; + case FmtX61: chans = X61Map; break; + case FmtX71: chans = X71Map; break; case FmtBFormat2D: - num_channels = 3; - isbformat = true; - DirectChannels = false; - break; - case FmtBFormat3D: - num_channels = 4; - isbformat = true; - DirectChannels = false; + case FmtUHJ2: + case FmtUHJ3: + case FmtUHJ4: + case FmtSuperStereo: + DirectChannels = DirectMode::Off; break; } - ASSUME(num_channels > 0); - std::for_each(std::begin(voice->mDirect.Params), - std::begin(voice->mDirect.Params)+num_channels, - [](DirectParams ¶ms) -> void - { - params.Hrtf.Target = HrtfParams{}; - ClearArray(params.Gains.Target); - } - ); - std::for_each(voice->mSend.begin(), voice->mSend.end(), - [num_channels](ALvoice::SendData &send) -> void - { - std::for_each(std::begin(send.Params), std::begin(send.Params)+num_channels, - [](SendParams ¶ms) -> void { ClearArray(params.Gains.Target); } - ); - } - ); + voice->mFlags.reset(VoiceHasHrtf).reset(VoiceHasNfc); + if(auto *decoder{voice->mDecoder.get()}) + decoder->mWidthControl = minf(props->EnhWidth, 0.7f); - voice->mFlags &= ~(VOICE_HAS_HRTF | VOICE_HAS_NFC); - if(isbformat) + if(IsAmbisonic(voice->mFmtChannels)) { - /* Special handling for B-Format sources. */ + /* Special handling for B-Format and UHJ sources. */ - if(Distance > std::numeric_limits::epsilon()) + if(Device->AvgSpeakerDist > 0.0f && voice->mFmtChannels != FmtUHJ2 + && voice->mFmtChannels != FmtSuperStereo) { - /* Panning a B-Format sound toward some direction is easy. Just pan - * the first (W) channel as a normal mono sound and silence the - * others. - */ - - if(Device->AvgSpeakerDist > 0.0f) + if(!(Distance > std::numeric_limits::epsilon())) { - /* Clamp the distance for really close sources, to prevent - * excessive bass. + /* NOTE: The NFCtrlFilters were created with a w0 of 0, which + * is what we want for FOA input. The first channel may have + * been previously re-adjusted if panned, so reset it. */ - const ALfloat mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)}; - const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (mdist * Frequency)}; - - /* Only need to adjust the first channel of a B-Format source. */ - voice->mDirect.Params[0].NFCtrlFilter.adjust(w0); - - std::copy(std::begin(Device->NumChannelsPerOrder), - std::end(Device->NumChannelsPerOrder), - std::begin(voice->mDirect.ChannelsPerOrder)); - voice->mFlags |= VOICE_HAS_NFC; + voice->mChans[0].mDryParams.NFCtrlFilter.adjust(0.0f); } - - ALfloat coeffs[MAX_AMBI_CHANNELS]; - if(Device->mRenderMode != StereoPair) - CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs); else { - /* Clamp Y, in case rounding errors caused it to end up outside - * of -1...+1. + /* Clamp the distance for really close sources, to prevent + * excessive bass. */ - const ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - /* Negate Z for right-handed coords with -Z in front. */ - const ALfloat az{std::atan2(xpos, -zpos)}; + const float mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)}; + const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)}; - /* A scalar of 1.5 for plain stereo results in +/-60 degrees - * being moved to +/-90 degrees for direct right and left - * speaker responses. - */ - CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread, coeffs); + /* Only need to adjust the first channel of a B-Format source. */ + voice->mChans[0].mDryParams.NFCtrlFilter.adjust(w0); } - /* NOTE: W needs to be scaled due to FuMa normalization. */ - const ALfloat &scale0 = AmbiScale::FromFuMa[0]; - ComputePanGains(&Device->Dry, coeffs, DryGain*scale0, - voice->mDirect.Params[0].Gains.Target); - for(ALsizei i{0};i < NumSends;i++) - { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i]*scale0, - voice->mSend[i].Params[0].Gains.Target); - } + voice->mFlags.set(VoiceHasNfc); } - else + + /* Panning a B-Format sound toward some direction is easy. Just pan the + * first (W) channel as a normal mono sound. The angular spread is used + * as a directional scalar to blend between full coverage and full + * panning. + */ + const float coverage{!(Distance > std::numeric_limits::epsilon()) ? 1.0f : + (al::numbers::inv_pi_v/2.0f * Spread)}; + + auto calc_coeffs = [xpos,ypos,zpos](RenderMode mode) { - if(Device->AvgSpeakerDist > 0.0f) - { - /* NOTE: The NFCtrlFilters were created with a w0 of 0, which - * is what we want for FOA input. The first channel may have - * been previously re-adjusted if panned, so reset it. - */ - voice->mDirect.Params[0].NFCtrlFilter.adjust(0.0f); + if(mode != RenderMode::Pairwise) + return CalcDirectionCoeffs({xpos, ypos, zpos}, 0.0f); - voice->mDirect.ChannelsPerOrder[0] = 1; - voice->mDirect.ChannelsPerOrder[1] = mini(voice->mDirect.Channels-1, 3); - std::fill(std::begin(voice->mDirect.ChannelsPerOrder)+2, - std::end(voice->mDirect.ChannelsPerOrder), 0); - voice->mFlags |= VOICE_HAS_NFC; - } + /* Clamp Y, in case rounding errors caused it to end up outside + * of -1...+1. + */ + const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; + /* Negate Z for right-handed coords with -Z in front. */ + const float az{std::atan2(xpos, -zpos)}; + + /* A scalar of 1.5 for plain stereo results in +/-60 degrees + * being moved to +/-90 degrees for direct right and left + * speaker responses. + */ + return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, 0.0f); + }; + auto coeffs = calc_coeffs(Device->mRenderMode); + std::transform(coeffs.begin()+1, coeffs.end(), coeffs.begin()+1, + std::bind(std::multiplies{}, _1, 1.0f-coverage)); + + /* NOTE: W needs to be scaled according to channel scaling. */ + auto&& scales = GetAmbiScales(voice->mAmbiScaling); + ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base*scales[0], + voice->mChans[0].mDryParams.Gains.Target); + for(uint i{0};i < NumSends;i++) + { + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base*scales[0], + voice->mChans[0].mWetParams[i].Gains.Target); + } + if(coverage > 0.0f) + { /* Local B-Format sources have their XYZ channels rotated according * to the orientation. */ @@ -656,116 +852,148 @@ void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypo V.normalize(); if(!props->HeadRelative) { - N = Listener.Params.Matrix * N; - V = Listener.Params.Matrix * V; + N = Context.Matrix * N; + V = Context.Matrix * V; } /* Build and normalize right-vector */ - alu::Vector U{aluCrossproduct(N, V)}; + alu::Vector U{N.cross_product(V)}; U.normalize(); - /* Build a rotate + conversion matrix (FuMa -> ACN+N3D). NOTE: This - * matrix is transposed, for the inputs to align on the rows and - * outputs on the columns. + /* Build a rotation matrix. Manually fill the zeroth- and first- + * order elements, then construct the rotation for the higher + * orders. */ - const ALfloat &wscale = AmbiScale::FromFuMa[0]; - const ALfloat &yscale = AmbiScale::FromFuMa[1]; - const ALfloat &zscale = AmbiScale::FromFuMa[2]; - const ALfloat &xscale = AmbiScale::FromFuMa[3]; - const ALfloat matrix[4][MAX_AMBI_CHANNELS]{ - // ACN0 ACN1 ACN2 ACN3 - { wscale, 0.0f, 0.0f, 0.0f }, // FuMa W - { 0.0f, -N[0]*xscale, N[1]*xscale, -N[2]*xscale }, // FuMa X - { 0.0f, U[0]*yscale, -U[1]*yscale, U[2]*yscale }, // FuMa Y - { 0.0f, -V[0]*zscale, V[1]*zscale, -V[2]*zscale } // FuMa Z - }; + std::array,MaxAmbiChannels> shrot{}; + shrot[0][0] = 1.0f; + shrot[1][1] = U[0]; shrot[1][2] = -V[0]; shrot[1][3] = -N[0]; + shrot[2][1] = -U[1]; shrot[2][2] = V[1]; shrot[2][3] = N[1]; + shrot[3][1] = U[2]; shrot[3][2] = -V[2]; shrot[3][3] = -N[2]; + AmbiRotator(shrot, static_cast(minu(voice->mAmbiOrder, Device->mAmbiOrder))); + + /* Convert the rotation matrix for input ordering and scaling, and + * whether input is 2D or 3D. + */ + const uint8_t *index_map{Is2DAmbisonic(voice->mFmtChannels) ? + GetAmbi2DLayout(voice->mAmbiLayout).data() : + GetAmbiLayout(voice->mAmbiLayout).data()}; - for(ALsizei c{0};c < num_channels;c++) - ComputePanGains(&Device->Dry, matrix[c], DryGain, - voice->mDirect.Params[c].Gains.Target); - for(ALsizei i{0};i < NumSends;i++) + static const uint8_t ChansPerOrder[MaxAmbiOrder+1]{1, 3, 5, 7,}; + static const uint8_t OrderOffset[MaxAmbiOrder+1]{0, 1, 4, 9,}; + for(size_t c{1};c < num_channels;c++) { - if(const ALeffectslot *Slot{SendSlots[i]}) - for(ALsizei c{0};c < num_channels;c++) - ComputePanGains(&Slot->Wet, matrix[c], WetGain[i], - voice->mSend[i].Params[c].Gains.Target); + const size_t acn{index_map[c]}; + const size_t order{AmbiIndex::OrderFromChannel()[acn]}; + const size_t tocopy{ChansPerOrder[order]}; + const size_t offset{OrderOffset[order]}; + const float scale{scales[acn] * coverage}; + auto in = shrot.cbegin() + offset; + + coeffs = std::array{}; + for(size_t x{0};x < tocopy;++x) + coeffs[offset+x] = in[x][acn] * scale; + + ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, + voice->mChans[c].mDryParams.Gains.Target); + + for(uint i{0};i < NumSends;i++) + { + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + voice->mChans[c].mWetParams[i].Gains.Target); + } } } } - else if(DirectChannels) + else if(DirectChannels != DirectMode::Off && !Device->RealOut.RemixMap.empty()) { /* Direct source channels always play local. Skip the virtual channels * and write inputs to the matching real outputs. */ voice->mDirect.Buffer = Device->RealOut.Buffer; - voice->mDirect.Channels = Device->RealOut.NumChannels; - for(ALsizei c{0};c < num_channels;c++) + for(size_t c{0};c < num_channels;c++) { - int idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)}; - if(idx != -1) voice->mDirect.Params[c].Gains.Target[idx] = DryGain; + uint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)}; + if(idx != INVALID_CHANNEL_INDEX) + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; + else if(DirectChannels == DirectMode::RemixMismatch) + { + auto match_channel = [chans,c](const InputRemixMap &map) noexcept -> bool + { return chans[c].channel == map.channel; }; + auto remap = std::find_if(Device->RealOut.RemixMap.cbegin(), + Device->RealOut.RemixMap.cend(), match_channel); + if(remap != Device->RealOut.RemixMap.cend()) + { + for(const auto &target : remap->targets) + { + idx = GetChannelIdxByName(Device->RealOut, target.channel); + if(idx != INVALID_CHANNEL_INDEX) + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * + target.mix; + } + } + } } /* Auxiliary sends still use normal channel panning since they mix to * B-Format, which can't channel-match. */ - for(ALsizei c{0};c < num_channels;c++) + for(size_t c{0};c < num_channels;c++) { - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f, coeffs); + const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f); - for(ALsizei i{0};i < NumSends;i++) + for(uint i{0};i < NumSends;i++) { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i], - voice->mSend[i].Params[c].Gains.Target); + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + voice->mChans[c].mWetParams[i].Gains.Target); } } } - else if(Device->mRenderMode == HrtfRender) + else if(Device->mRenderMode == RenderMode::Hrtf) { /* Full HRTF rendering. Skip the virtual channels and render to the * real outputs. */ voice->mDirect.Buffer = Device->RealOut.Buffer; - voice->mDirect.Channels = Device->RealOut.NumChannels; if(Distance > std::numeric_limits::epsilon()) { - const ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - const ALfloat az{std::atan2(xpos, -zpos)}; + const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; + const float az{std::atan2(xpos, -zpos)}; /* Get the HRIR coefficients and delays just once, for the given * source direction. */ - GetHrtfCoeffs(Device->mHrtf, ev, az, Distance, Spread, - voice->mDirect.Params[0].Hrtf.Target.Coeffs, - voice->mDirect.Params[0].Hrtf.Target.Delay); - voice->mDirect.Params[0].Hrtf.Target.Gain = DryGain * downmix_gain; + GetHrtfCoeffs(Device->mHrtf.get(), ev, az, Distance, Spread, + voice->mChans[0].mDryParams.Hrtf.Target.Coeffs, + voice->mChans[0].mDryParams.Hrtf.Target.Delay); + voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain.Base; /* Remaining channels use the same results as the first. */ - for(ALsizei c{1};c < num_channels;c++) + for(size_t c{1};c < num_channels;c++) { /* Skip LFE */ - if(chans[c].channel != LFE) - voice->mDirect.Params[c].Hrtf.Target = voice->mDirect.Params[0].Hrtf.Target; + if(chans[c].channel == LFE) continue; + voice->mChans[c].mDryParams.Hrtf.Target = voice->mChans[0].mDryParams.Hrtf.Target; } /* Calculate the directional coefficients once, which apply to all * input channels of the source sends. */ - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs); + const auto coeffs = CalcDirectionCoeffs({xpos, ypos, zpos}, Spread); - for(ALsizei i{0};i < NumSends;i++) + for(size_t c{0};c < num_channels;c++) { - if(const ALeffectslot *Slot{SendSlots[i]}) - for(ALsizei c{0};c < num_channels;c++) - { - /* Skip LFE */ - if(chans[c].channel != LFE) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i] * downmix_gain, - voice->mSend[i].Params[c].Gains.Target); - } + /* Skip LFE */ + if(chans[c].channel == LFE) + continue; + for(uint i{0};i < NumSends;i++) + { + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + voice->mChans[c].mWetParams[i].Gains.Target); + } } } else @@ -774,7 +1002,7 @@ void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypo * relative location around the listener, providing "virtual * speaker" responses. */ - for(ALsizei c{0};c < num_channels;c++) + for(size_t c{0};c < num_channels;c++) { /* Skip LFE */ if(chans[c].channel == LFE) @@ -783,26 +1011,25 @@ void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypo /* Get the HRIR coefficients and delays for this channel * position. */ - GetHrtfCoeffs(Device->mHrtf, chans[c].elevation, chans[c].angle, + GetHrtfCoeffs(Device->mHrtf.get(), chans[c].elevation, chans[c].angle, std::numeric_limits::infinity(), Spread, - voice->mDirect.Params[c].Hrtf.Target.Coeffs, - voice->mDirect.Params[c].Hrtf.Target.Delay); - voice->mDirect.Params[c].Hrtf.Target.Gain = DryGain; + voice->mChans[c].mDryParams.Hrtf.Target.Coeffs, + voice->mChans[c].mDryParams.Hrtf.Target.Delay); + voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base; /* Normal panning for auxiliary sends. */ - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs); + const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread); - for(ALsizei i{0};i < NumSends;i++) + for(uint i{0};i < NumSends;i++) { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i], - voice->mSend[i].Params[c].Gains.Target); + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + voice->mChans[c].mWetParams[i].Gains.Target); } } } - voice->mFlags |= VOICE_HAS_HRTF; + voice->mFlags.set(VoiceHasHrtf); } else { @@ -816,497 +1043,438 @@ void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypo /* Clamp the distance for really close sources, to prevent * excessive bass. */ - const ALfloat mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)}; - const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (mdist * Frequency)}; + const float mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)}; + const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)}; /* Adjust NFC filters. */ - for(ALsizei c{0};c < num_channels;c++) - voice->mDirect.Params[c].NFCtrlFilter.adjust(w0); + for(size_t c{0};c < num_channels;c++) + voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0); - std::copy(std::begin(Device->NumChannelsPerOrder), - std::end(Device->NumChannelsPerOrder), - std::begin(voice->mDirect.ChannelsPerOrder)); - voice->mFlags |= VOICE_HAS_NFC; + voice->mFlags.set(VoiceHasNfc); } /* Calculate the directional coefficients once, which apply to all * input channels. */ - ALfloat coeffs[MAX_AMBI_CHANNELS]; - if(Device->mRenderMode != StereoPair) - CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs); - else + auto calc_coeffs = [xpos,ypos,zpos,Spread](RenderMode mode) { - const ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - const ALfloat az{std::atan2(xpos, -zpos)}; - CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread, coeffs); - } + if(mode != RenderMode::Pairwise) + return CalcDirectionCoeffs({xpos, ypos, zpos}, Spread); + const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; + const float az{std::atan2(xpos, -zpos)}; + return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread); + }; + const auto coeffs = calc_coeffs(Device->mRenderMode); - for(ALsizei c{0};c < num_channels;c++) + for(size_t c{0};c < num_channels;c++) { /* Special-case LFE */ if(chans[c].channel == LFE) { - if(Device->Dry.Buffer == Device->RealOut.Buffer) + if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) { - int idx = GetChannelIdxByName(Device->RealOut, chans[c].channel); - if(idx != -1) voice->mDirect.Params[c].Gains.Target[idx] = DryGain; + const uint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)}; + if(idx != INVALID_CHANNEL_INDEX) + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; } continue; } - ComputePanGains(&Device->Dry, coeffs, DryGain * downmix_gain, - voice->mDirect.Params[c].Gains.Target); - } - - for(ALsizei i{0};i < NumSends;i++) - { - if(const ALeffectslot *Slot{SendSlots[i]}) - for(ALsizei c{0};c < num_channels;c++) - { - /* Skip LFE */ - if(chans[c].channel != LFE) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i] * downmix_gain, - voice->mSend[i].Params[c].Gains.Target); - } + ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, + voice->mChans[c].mDryParams.Gains.Target); + for(uint i{0};i < NumSends;i++) + { + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + voice->mChans[c].mWetParams[i].Gains.Target); + } } } else { if(Device->AvgSpeakerDist > 0.0f) { - /* If the source distance is 0, set w0 to w1 to act as a pass- - * through. We still want to pass the signal through the - * filters so they keep an appropriate history, in case the - * source moves away from the listener. + /* If the source distance is 0, simulate a plane-wave by using + * infinite distance, which results in a w0 of 0. */ - const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (Device->AvgSpeakerDist * Frequency)}; - - for(ALsizei c{0};c < num_channels;c++) - voice->mDirect.Params[c].NFCtrlFilter.adjust(w0); + static constexpr float w0{0.0f}; + for(size_t c{0};c < num_channels;c++) + voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0); - std::copy(std::begin(Device->NumChannelsPerOrder), - std::end(Device->NumChannelsPerOrder), - std::begin(voice->mDirect.ChannelsPerOrder)); - voice->mFlags |= VOICE_HAS_NFC; + voice->mFlags.set(VoiceHasNfc); } - for(ALsizei c{0};c < num_channels;c++) + for(size_t c{0};c < num_channels;c++) { /* Special-case LFE */ if(chans[c].channel == LFE) { - if(Device->Dry.Buffer == Device->RealOut.Buffer) + if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) { - int idx = GetChannelIdxByName(Device->RealOut, chans[c].channel); - if(idx != -1) voice->mDirect.Params[c].Gains.Target[idx] = DryGain; + const uint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)}; + if(idx != INVALID_CHANNEL_INDEX) + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; } continue; } - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcAngleCoeffs( - (Device->mRenderMode==StereoPair) ? ScaleAzimuthFront(chans[c].angle, 3.0f) - : chans[c].angle, - chans[c].elevation, Spread, coeffs - ); + const auto coeffs = CalcAngleCoeffs((Device->mRenderMode == RenderMode::Pairwise) + ? ScaleAzimuthFront(chans[c].angle, 3.0f) : chans[c].angle, + chans[c].elevation, Spread); - ComputePanGains(&Device->Dry, coeffs, DryGain, - voice->mDirect.Params[c].Gains.Target); - for(ALsizei i{0};i < NumSends;i++) + ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, + voice->mChans[c].mDryParams.Gains.Target); + for(uint i{0};i < NumSends;i++) { - if(const ALeffectslot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i], - voice->mSend[i].Params[c].Gains.Target); + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + voice->mChans[c].mWetParams[i].Gains.Target); } } } } { - const ALfloat hfScale{props->Direct.HFReference / Frequency}; - const ALfloat lfScale{props->Direct.LFReference / Frequency}; - const ALfloat gainHF{maxf(DryGainHF, 0.001f)}; /* Limit -60dB */ - const ALfloat gainLF{maxf(DryGainLF, 0.001f)}; + const float hfNorm{props->Direct.HFReference / Frequency}; + const float lfNorm{props->Direct.LFReference / Frequency}; voice->mDirect.FilterType = AF_None; - if(gainHF != 1.0f) voice->mDirect.FilterType |= AF_LowPass; - if(gainLF != 1.0f) voice->mDirect.FilterType |= AF_HighPass; - voice->mDirect.Params[0].LowPass.setParams(BiquadType::HighShelf, - gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f) - ); - voice->mDirect.Params[0].HighPass.setParams(BiquadType::LowShelf, - gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f) - ); - for(ALsizei c{1};c < num_channels;c++) + if(DryGain.HF != 1.0f) voice->mDirect.FilterType |= AF_LowPass; + if(DryGain.LF != 1.0f) voice->mDirect.FilterType |= AF_HighPass; + + auto &lowpass = voice->mChans[0].mDryParams.LowPass; + auto &highpass = voice->mChans[0].mDryParams.HighPass; + lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, DryGain.HF, 1.0f); + highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, DryGain.LF, 1.0f); + for(size_t c{1};c < num_channels;c++) { - voice->mDirect.Params[c].LowPass.copyParamsFrom(voice->mDirect.Params[0].LowPass); - voice->mDirect.Params[c].HighPass.copyParamsFrom(voice->mDirect.Params[0].HighPass); + voice->mChans[c].mDryParams.LowPass.copyParamsFrom(lowpass); + voice->mChans[c].mDryParams.HighPass.copyParamsFrom(highpass); } } - for(ALsizei i{0};i < NumSends;i++) + for(uint i{0};i < NumSends;i++) { - const ALfloat hfScale{props->Send[i].HFReference / Frequency}; - const ALfloat lfScale{props->Send[i].LFReference / Frequency}; - const ALfloat gainHF{maxf(WetGainHF[i], 0.001f)}; - const ALfloat gainLF{maxf(WetGainLF[i], 0.001f)}; + const float hfNorm{props->Send[i].HFReference / Frequency}; + const float lfNorm{props->Send[i].LFReference / Frequency}; voice->mSend[i].FilterType = AF_None; - if(gainHF != 1.0f) voice->mSend[i].FilterType |= AF_LowPass; - if(gainLF != 1.0f) voice->mSend[i].FilterType |= AF_HighPass; - voice->mSend[i].Params[0].LowPass.setParams(BiquadType::HighShelf, - gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f) - ); - voice->mSend[i].Params[0].HighPass.setParams(BiquadType::LowShelf, - gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f) - ); - for(ALsizei c{1};c < num_channels;c++) + if(WetGain[i].HF != 1.0f) voice->mSend[i].FilterType |= AF_LowPass; + if(WetGain[i].LF != 1.0f) voice->mSend[i].FilterType |= AF_HighPass; + + auto &lowpass = voice->mChans[0].mWetParams[i].LowPass; + auto &highpass = voice->mChans[0].mWetParams[i].HighPass; + lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, WetGain[i].HF, 1.0f); + highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, WetGain[i].LF, 1.0f); + for(size_t c{1};c < num_channels;c++) { - voice->mSend[i].Params[c].LowPass.copyParamsFrom(voice->mSend[i].Params[0].LowPass); - voice->mSend[i].Params[c].HighPass.copyParamsFrom(voice->mSend[i].Params[0].HighPass); + voice->mChans[c].mWetParams[i].LowPass.copyParamsFrom(lowpass); + voice->mChans[c].mWetParams[i].HighPass.copyParamsFrom(highpass); } } } -void CalcNonAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALCcontext *ALContext) +void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context) { - const ALCdevice *Device{ALContext->Device}; - ALeffectslot *SendSlots[MAX_SENDS]; + const DeviceBase *Device{context->mDevice}; + EffectSlot *SendSlots[MAX_SENDS]; voice->mDirect.Buffer = Device->Dry.Buffer; - voice->mDirect.Channels = Device->Dry.NumChannels; - for(ALsizei i{0};i < Device->NumAuxSends;i++) + for(uint i{0};i < Device->NumAuxSends;i++) { SendSlots[i] = props->Send[i].Slot; - if(!SendSlots[i] && i == 0) - SendSlots[i] = ALContext->DefaultSlot.get(); - if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL) + if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None) { SendSlots[i] = nullptr; - voice->mSend[i].Buffer = nullptr; - voice->mSend[i].Channels = 0; + voice->mSend[i].Buffer = {}; } else - { voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer; - voice->mSend[i].Channels = SendSlots[i]->Wet.NumChannels; - } } /* Calculate the stepping value */ - const auto Pitch = static_cast(voice->mFrequency) / - static_cast(Device->Frequency) * props->Pitch; - if(Pitch > static_cast(MAX_PITCH)) - voice->mStep = MAX_PITCH<(voice->mFrequency) / + static_cast(Device->Frequency) * props->Pitch; + if(Pitch > float{MaxPitch}) + voice->mStep = MaxPitch<mStep = maxi(fastf2i(Pitch * FRACTIONONE), 1); - if(props->mResampler == BSinc24Resampler) - BsincPrepare(voice->mStep, &voice->mResampleState.bsinc, &bsinc24); - else if(props->mResampler == BSinc12Resampler) - BsincPrepare(voice->mStep, &voice->mResampleState.bsinc, &bsinc12); - voice->mResampler = SelectResampler(props->mResampler); + voice->mStep = maxu(fastf2u(Pitch * MixerFracOne), 1); + voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState); /* Calculate gains */ - const ALlistener &Listener = ALContext->Listener; - ALfloat DryGain{clampf(props->Gain, props->MinGain, props->MaxGain)}; - DryGain *= props->Direct.Gain * Listener.Params.Gain; - DryGain = minf(DryGain, GAIN_MIX_MAX); - ALfloat DryGainHF{props->Direct.GainHF}; - ALfloat DryGainLF{props->Direct.GainLF}; - ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS]; - for(ALsizei i{0};i < Device->NumAuxSends;i++) + GainTriplet DryGain; + DryGain.Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) * props->Direct.Gain * + context->mParams.Gain, GainMixMax); + DryGain.HF = props->Direct.GainHF; + DryGain.LF = props->Direct.GainLF; + GainTriplet WetGain[MAX_SENDS]; + for(uint i{0};i < Device->NumAuxSends;i++) { - WetGain[i] = clampf(props->Gain, props->MinGain, props->MaxGain); - WetGain[i] *= props->Send[i].Gain * Listener.Params.Gain; - WetGain[i] = minf(WetGain[i], GAIN_MIX_MAX); - WetGainHF[i] = props->Send[i].GainHF; - WetGainLF[i] = props->Send[i].GainLF; + WetGain[i].Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) * + props->Send[i].Gain * context->mParams.Gain, GainMixMax); + WetGain[i].HF = props->Send[i].GainHF; + WetGain[i].LF = props->Send[i].GainLF; } - CalcPanningAndFilters(voice, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, DryGain, DryGainHF, DryGainLF, - WetGain, WetGainLF, WetGainHF, SendSlots, props, Listener, Device); + CalcPanningAndFilters(voice, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, DryGain, WetGain, SendSlots, props, + context->mParams, Device); } -void CalcAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALCcontext *ALContext) +void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context) { - const ALCdevice *Device{ALContext->Device}; - const ALsizei NumSends{Device->NumAuxSends}; - const ALlistener &Listener = ALContext->Listener; + const DeviceBase *Device{context->mDevice}; + const uint NumSends{Device->NumAuxSends}; /* Set mixing buffers and get send parameters. */ voice->mDirect.Buffer = Device->Dry.Buffer; - voice->mDirect.Channels = Device->Dry.NumChannels; - ALeffectslot *SendSlots[MAX_SENDS]; - ALfloat RoomRolloff[MAX_SENDS]; - ALfloat DecayDistance[MAX_SENDS]; - ALfloat DecayLFDistance[MAX_SENDS]; - ALfloat DecayHFDistance[MAX_SENDS]; - for(ALsizei i{0};i < NumSends;i++) + EffectSlot *SendSlots[MAX_SENDS]; + uint UseDryAttnForRoom{0}; + for(uint i{0};i < NumSends;i++) { SendSlots[i] = props->Send[i].Slot; - if(!SendSlots[i] && i == 0) - SendSlots[i] = ALContext->DefaultSlot.get(); - if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL) - { + if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None) SendSlots[i] = nullptr; - RoomRolloff[i] = 0.0f; - DecayDistance[i] = 0.0f; - DecayLFDistance[i] = 0.0f; - DecayHFDistance[i] = 0.0f; - } - else if(SendSlots[i]->Params.AuxSendAuto) - { - RoomRolloff[i] = SendSlots[i]->Params.RoomRolloff + props->RoomRolloffFactor; - /* Calculate the distances to where this effect's decay reaches - * -60dB. - */ - DecayDistance[i] = SendSlots[i]->Params.DecayTime * - Listener.Params.ReverbSpeedOfSound; - DecayLFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayLFRatio; - DecayHFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayHFRatio; - if(SendSlots[i]->Params.DecayHFLimit) - { - ALfloat airAbsorption{SendSlots[i]->Params.AirAbsorptionGainHF}; - if(airAbsorption < 1.0f) - { - /* Calculate the distance to where this effect's air - * absorption reaches -60dB, and limit the effect's HF - * decay distance (so it doesn't take any longer to decay - * than the air would allow). - */ - ALfloat absorb_dist{std::log10(REVERB_DECAY_GAIN) / std::log10(airAbsorption)}; - DecayHFDistance[i] = minf(absorb_dist, DecayHFDistance[i]); - } - } - } - else + else if(!SendSlots[i]->AuxSendAuto) { /* If the slot's auxiliary send auto is off, the data sent to the - * effect slot is the same as the dry path, sans filter effects */ - RoomRolloff[i] = props->RolloffFactor; - DecayDistance[i] = 0.0f; - DecayLFDistance[i] = 0.0f; - DecayHFDistance[i] = 0.0f; + * effect slot is the same as the dry path, sans filter effects. + */ + UseDryAttnForRoom |= 1u<mSend[i].Buffer = nullptr; - voice->mSend[i].Channels = 0; - } + voice->mSend[i].Buffer = {}; else - { voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer; - voice->mSend[i].Channels = SendSlots[i]->Wet.NumChannels; - } } /* Transform source to listener space (convert to head relative) */ alu::Vector Position{props->Position[0], props->Position[1], props->Position[2], 1.0f}; alu::Vector Velocity{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f}; alu::Vector Direction{props->Direction[0], props->Direction[1], props->Direction[2], 0.0f}; - if(props->HeadRelative == AL_FALSE) + if(!props->HeadRelative) { /* Transform source vectors */ - Position = Listener.Params.Matrix * Position; - Velocity = Listener.Params.Matrix * Velocity; - Direction = Listener.Params.Matrix * Direction; + Position = context->mParams.Matrix * (Position - context->mParams.Position); + Velocity = context->mParams.Matrix * Velocity; + Direction = context->mParams.Matrix * Direction; } else { /* Offset the source velocity to be relative of the listener velocity */ - Velocity += Listener.Params.Velocity; + Velocity += context->mParams.Velocity; } const bool directional{Direction.normalize() > 0.0f}; alu::Vector ToSource{Position[0], Position[1], Position[2], 0.0f}; - const ALfloat Distance{ToSource.normalize()}; - - /* Initial source gain */ - ALfloat DryGain{props->Gain}; - ALfloat DryGainHF{1.0f}; - ALfloat DryGainLF{1.0f}; - ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS]; - for(ALsizei i{0};i < NumSends;i++) - { - WetGain[i] = props->Gain; - WetGainHF[i] = 1.0f; - WetGainLF[i] = 1.0f; - } + const float Distance{ToSource.normalize()}; /* Calculate distance attenuation */ - ALfloat ClampedDist{Distance}; + float ClampedDist{Distance}; + float DryGainBase{props->Gain}; + float WetGainBase{props->Gain}; - switch(Listener.Params.SourceDistanceModel ? - props->mDistanceModel : Listener.Params.mDistanceModel) + switch(context->mParams.SourceDistanceModel ? props->mDistanceModel + : context->mParams.mDistanceModel) { case DistanceModel::InverseClamped: - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); if(props->MaxDistance < props->RefDistance) break; + ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); /*fall-through*/ case DistanceModel::Inverse: - if(!(props->RefDistance > 0.0f)) - ClampedDist = props->RefDistance; - else + if(props->RefDistance > 0.0f) { - ALfloat dist = lerp(props->RefDistance, ClampedDist, props->RolloffFactor); - if(dist > 0.0f) DryGain *= props->RefDistance / dist; - for(ALsizei i{0};i < NumSends;i++) - { - dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]); - if(dist > 0.0f) WetGain[i] *= props->RefDistance / dist; - } + float dist{lerpf(props->RefDistance, ClampedDist, props->RolloffFactor)}; + if(dist > 0.0f) DryGainBase *= props->RefDistance / dist; + + dist = lerpf(props->RefDistance, ClampedDist, props->RoomRolloffFactor); + if(dist > 0.0f) WetGainBase *= props->RefDistance / dist; } break; case DistanceModel::LinearClamped: - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); if(props->MaxDistance < props->RefDistance) break; + ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); /*fall-through*/ case DistanceModel::Linear: - if(!(props->MaxDistance != props->RefDistance)) - ClampedDist = props->RefDistance; - else + if(props->MaxDistance != props->RefDistance) { - ALfloat attn = props->RolloffFactor * (ClampedDist-props->RefDistance) / - (props->MaxDistance-props->RefDistance); - DryGain *= maxf(1.0f - attn, 0.0f); - for(ALsizei i{0};i < NumSends;i++) - { - attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) / - (props->MaxDistance-props->RefDistance); - WetGain[i] *= maxf(1.0f - attn, 0.0f); - } + float attn{(ClampedDist-props->RefDistance) / + (props->MaxDistance-props->RefDistance) * props->RolloffFactor}; + DryGainBase *= maxf(1.0f - attn, 0.0f); + + attn = (ClampedDist-props->RefDistance) / + (props->MaxDistance-props->RefDistance) * props->RoomRolloffFactor; + WetGainBase *= maxf(1.0f - attn, 0.0f); } break; case DistanceModel::ExponentClamped: - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); if(props->MaxDistance < props->RefDistance) break; + ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); /*fall-through*/ case DistanceModel::Exponent: - if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f)) - ClampedDist = props->RefDistance; - else + if(ClampedDist > 0.0f && props->RefDistance > 0.0f) { - DryGain *= std::pow(ClampedDist/props->RefDistance, -props->RolloffFactor); - for(ALsizei i{0};i < NumSends;i++) - WetGain[i] *= std::pow(ClampedDist/props->RefDistance, -RoomRolloff[i]); + const float dist_ratio{ClampedDist/props->RefDistance}; + DryGainBase *= std::pow(dist_ratio, -props->RolloffFactor); + WetGainBase *= std::pow(dist_ratio, -props->RoomRolloffFactor); } break; case DistanceModel::Disable: - ClampedDist = props->RefDistance; break; } /* Calculate directional soundcones */ + float ConeHF{1.0f}, WetConeHF{1.0f}; if(directional && props->InnerAngle < 360.0f) { - const ALfloat Angle{Rad2Deg(std::acos(-aluDotproduct(Direction, ToSource)) * - ConeScale * 2.0f)}; + static constexpr float Rad2Deg{static_cast(180.0 / al::numbers::pi)}; + const float Angle{Rad2Deg*2.0f * std::acos(-Direction.dot_product(ToSource)) * ConeScale}; - ALfloat ConeVolume, ConeHF; - if(!(Angle > props->InnerAngle)) - { - ConeVolume = 1.0f; - ConeHF = 1.0f; - } - else if(Angle < props->OuterAngle) + float ConeGain{1.0f}; + if(Angle >= props->OuterAngle) { - ALfloat scale = ( Angle-props->InnerAngle) / - (props->OuterAngle-props->InnerAngle); - ConeVolume = lerp(1.0f, props->OuterGain, scale); - ConeHF = lerp(1.0f, props->OuterGainHF, scale); + ConeGain = props->OuterGain; + ConeHF = lerpf(1.0f, props->OuterGainHF, props->DryGainHFAuto); } - else + else if(Angle >= props->InnerAngle) { - ConeVolume = props->OuterGain; - ConeHF = props->OuterGainHF; + const float scale{(Angle-props->InnerAngle) / (props->OuterAngle-props->InnerAngle)}; + ConeGain = lerpf(1.0f, props->OuterGain, scale); + ConeHF = lerpf(1.0f, props->OuterGainHF, scale * props->DryGainHFAuto); } - DryGain *= ConeVolume; - if(props->DryGainHFAuto) - DryGainHF *= ConeHF; - if(props->WetGainAuto) - std::transform(std::begin(WetGain), std::begin(WetGain)+NumSends, std::begin(WetGain), - [ConeVolume](ALfloat gain) noexcept -> ALfloat { return gain * ConeVolume; } - ); - if(props->WetGainHFAuto) - std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends, - std::begin(WetGainHF), - [ConeHF](ALfloat gain) noexcept -> ALfloat { return gain * ConeHF; } - ); + DryGainBase *= ConeGain; + WetGainBase *= lerpf(1.0f, ConeGain, props->WetGainAuto); + + WetConeHF = lerpf(1.0f, ConeHF, props->WetGainHFAuto); } /* Apply gain and frequency filters */ - DryGain = clampf(DryGain, props->MinGain, props->MaxGain); - DryGain = minf(DryGain*props->Direct.Gain*Listener.Params.Gain, GAIN_MIX_MAX); - DryGainHF *= props->Direct.GainHF; - DryGainLF *= props->Direct.GainLF; - for(ALsizei i{0};i < NumSends;i++) + DryGainBase = clampf(DryGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; + WetGainBase = clampf(WetGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; + + GainTriplet DryGain{}; + DryGain.Base = minf(DryGainBase * props->Direct.Gain, GainMixMax); + DryGain.HF = ConeHF * props->Direct.GainHF; + DryGain.LF = props->Direct.GainLF; + GainTriplet WetGain[MAX_SENDS]{}; + for(uint i{0};i < NumSends;i++) { - WetGain[i] = clampf(WetGain[i], props->MinGain, props->MaxGain); - WetGain[i] = minf(WetGain[i]*props->Send[i].Gain*Listener.Params.Gain, GAIN_MIX_MAX); - WetGainHF[i] *= props->Send[i].GainHF; - WetGainLF[i] *= props->Send[i].GainLF; + /* If this effect slot's Auxiliary Send Auto is off, then use the dry + * path distance and cone attenuation, otherwise use the wet (room) + * path distance and cone attenuation. The send filter is used instead + * of the direct filter, regardless. + */ + const bool use_room{!(UseDryAttnForRoom&(1u<Send[i].Gain, GainMixMax); + WetGain[i].HF = (use_room ? WetConeHF : ConeHF) * props->Send[i].GainHF; + WetGain[i].LF = props->Send[i].GainLF; } /* Distance-based air absorption and initial send decay. */ - if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f) + if(likely(Distance > props->RefDistance)) { - ALfloat meters_base{(ClampedDist-props->RefDistance) * props->RolloffFactor * - Listener.Params.MetersPerUnit}; - if(props->AirAbsorptionFactor > 0.0f) + const float distance_base{(Distance-props->RefDistance) * props->RolloffFactor}; + const float absorption{distance_base * context->mParams.MetersPerUnit * + props->AirAbsorptionFactor}; + if(absorption > std::numeric_limits::epsilon()) { - ALfloat hfattn{std::pow(AIRABSORBGAINHF, meters_base * props->AirAbsorptionFactor)}; - DryGainHF *= hfattn; - std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends, - std::begin(WetGainHF), - [hfattn](ALfloat gain) noexcept -> ALfloat { return gain * hfattn; } - ); + const float hfattn{std::pow(context->mParams.AirAbsorptionGainHF, absorption)}; + DryGain.HF *= hfattn; + for(uint i{0u};i < NumSends;++i) + WetGain[i].HF *= hfattn; } - if(props->WetGainAuto) + /* If the source's Auxiliary Send Filter Gain Auto is off, no extra + * adjustment is applied to the send gains. + */ + for(uint i{props->WetGainAuto ? 0u : NumSends};i < NumSends;++i) { - /* Apply a decay-time transformation to the wet path, based on the - * source distance in meters. The initial decay of the reverb - * effect is calculated and applied to the wet path. - */ - for(ALsizei i{0};i < NumSends;i++) + if(!SendSlots[i]) + continue; + + auto calc_attenuation = [](float distance, float refdist, float rolloff) noexcept { - if(!(DecayDistance[i] > 0.0f)) - continue; + const float dist{lerpf(refdist, distance, rolloff)}; + if(dist > refdist) return refdist / dist; + return 1.0f; + }; - const ALfloat gain{std::pow(REVERB_DECAY_GAIN, meters_base/DecayDistance[i])}; - WetGain[i] *= gain; - /* Yes, the wet path's air absorption is applied with - * WetGainAuto on, rather than WetGainHFAuto. - */ - if(gain > 0.0f) + /* The reverb effect's room rolloff factor always applies to an + * inverse distance rolloff model. + */ + WetGain[i].Base *= calc_attenuation(Distance, props->RefDistance, + SendSlots[i]->RoomRolloff); + + /* If this effect slot's Auxiliary Send Auto is off, don't apply + * the automatic initial reverb decay (should the reverb's room + * rolloff still apply?). + */ + if(!SendSlots[i]->AuxSendAuto) + continue; + + GainTriplet DecayDistance; + /* Calculate the distances to where this effect's decay reaches + * -60dB. + */ + DecayDistance.Base = SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec; + DecayDistance.LF = DecayDistance.Base * SendSlots[i]->DecayLFRatio; + DecayDistance.HF = DecayDistance.Base * SendSlots[i]->DecayHFRatio; + if(SendSlots[i]->DecayHFLimit) + { + const float airAbsorption{SendSlots[i]->AirAbsorptionGainHF}; + if(airAbsorption < 1.0f) { - ALfloat gainhf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayHFDistance[i])}; - WetGainHF[i] *= minf(gainhf / gain, 1.0f); - ALfloat gainlf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayLFDistance[i])}; - WetGainLF[i] *= minf(gainlf / gain, 1.0f); + /* Calculate the distance to where this effect's air + * absorption reaches -60dB, and limit the effect's HF + * decay distance (so it doesn't take any longer to decay + * than the air would allow). + */ + static constexpr float log10_decaygain{-3.0f/*std::log10(ReverbDecayGain)*/}; + const float absorb_dist{log10_decaygain / std::log10(airAbsorption)}; + DecayDistance.HF = minf(absorb_dist, DecayDistance.HF); } } + + const float baseAttn = calc_attenuation(Distance, props->RefDistance, + props->RolloffFactor); + + /* Apply a decay-time transformation to the wet path, based on the + * source distance. The initial decay of the reverb effect is + * calculated and applied to the wet path. + */ + const float fact{distance_base / DecayDistance.Base}; + const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn}; + WetGain[i].Base *= gain; + + if(gain > 0.0f) + { + const float hffact{distance_base / DecayDistance.HF}; + const float gainhf{std::pow(ReverbDecayGain, hffact)*(1.0f-baseAttn) + baseAttn}; + WetGain[i].HF *= minf(gainhf/gain, 1.0f); + const float lffact{distance_base / DecayDistance.LF}; + const float gainlf{std::pow(ReverbDecayGain, lffact)*(1.0f-baseAttn) + baseAttn}; + WetGain[i].LF *= minf(gainlf/gain, 1.0f); + } } } /* Initial source pitch */ - ALfloat Pitch{props->Pitch}; + float Pitch{props->Pitch}; /* Calculate velocity-based doppler effect */ - ALfloat DopplerFactor{props->DopplerFactor * Listener.Params.DopplerFactor}; + float DopplerFactor{props->DopplerFactor * context->mParams.DopplerFactor}; if(DopplerFactor > 0.0f) { - const alu::Vector &lvelocity = Listener.Params.Velocity; - ALfloat vss{aluDotproduct(Velocity, ToSource) * -DopplerFactor}; - ALfloat vls{aluDotproduct(lvelocity, ToSource) * -DopplerFactor}; + const alu::Vector &lvelocity = context->mParams.Velocity; + float vss{Velocity.dot_product(ToSource) * -DopplerFactor}; + float vls{lvelocity.dot_product(ToSource) * -DopplerFactor}; - const ALfloat SpeedOfSound{Listener.Params.SpeedOfSound}; + const float SpeedOfSound{context->mParams.SpeedOfSound}; if(!(vls < SpeedOfSound)) { /* Listener moving away from the source at the speed of sound. @@ -1333,264 +1501,311 @@ void CalcAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const A /* Adjust pitch based on the buffer and output frequencies, and calculate * fixed-point stepping value. */ - Pitch *= static_cast(voice->mFrequency)/static_cast(Device->Frequency); - if(Pitch > static_cast(MAX_PITCH)) - voice->mStep = MAX_PITCH<(voice->mFrequency) / static_cast(Device->Frequency); + if(Pitch > float{MaxPitch}) + voice->mStep = MaxPitch<mStep = maxi(fastf2i(Pitch * FRACTIONONE), 1); - if(props->mResampler == BSinc24Resampler) - BsincPrepare(voice->mStep, &voice->mResampleState.bsinc, &bsinc24); - else if(props->mResampler == BSinc12Resampler) - BsincPrepare(voice->mStep, &voice->mResampleState.bsinc, &bsinc12); - voice->mResampler = SelectResampler(props->mResampler); - - ALfloat spread{0.0f}; + voice->mStep = maxu(fastf2u(Pitch * MixerFracOne), 1); + voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState); + + float spread{0.0f}; if(props->Radius > Distance) - spread = al::MathDefs::Tau() - Distance/props->Radius*al::MathDefs::Pi(); + spread = al::numbers::pi_v*2.0f - Distance/props->Radius*al::numbers::pi_v; else if(Distance > 0.0f) spread = std::asin(props->Radius/Distance) * 2.0f; - CalcPanningAndFilters(voice, ToSource[0], ToSource[1], ToSource[2]*ZScale, - Distance*Listener.Params.MetersPerUnit, spread, DryGain, DryGainHF, DryGainLF, WetGain, - WetGainLF, WetGainHF, SendSlots, props, Listener, Device); + CalcPanningAndFilters(voice, ToSource[0]*XScale, ToSource[1]*YScale, ToSource[2]*ZScale, + Distance*context->mParams.MetersPerUnit, spread, DryGain, WetGain, SendSlots, props, + context->mParams, Device); } -void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force) +void CalcSourceParams(Voice *voice, ContextBase *context, bool force) { - ALvoiceProps *props{voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel)}; + VoicePropsItem *props{voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel)}; if(!props && !force) return; if(props) { voice->mProps = *props; - AtomicReplaceHead(context->FreeVoiceProps, props); + AtomicReplaceHead(context->mFreeVoiceProps, props); } - if((voice->mProps.mSpatializeMode == SpatializeAuto && voice->mFmtChannels == FmtMono) || - voice->mProps.mSpatializeMode == SpatializeOn) - CalcAttnSourceParams(voice, &voice->mProps, context); - else + if((voice->mProps.DirectChannels != DirectMode::Off && voice->mFmtChannels != FmtMono + && !IsAmbisonic(voice->mFmtChannels)) + || voice->mProps.mSpatializeMode == SpatializeMode::Off + || (voice->mProps.mSpatializeMode==SpatializeMode::Auto && voice->mFmtChannels != FmtMono)) CalcNonAttnSourceParams(voice, &voice->mProps, context); + else + CalcAttnSourceParams(voice, &voice->mProps, context); } -void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray *slots) +void SendSourceStateEvent(ContextBase *context, uint id, VChangeState state) { - IncrementRef(&ctx->UpdateCount); - if(LIKELY(!ctx->HoldUpdates.load(std::memory_order_acquire))) + RingBuffer *ring{context->mAsyncEvents.get()}; + auto evt_vec = ring->getWriteVector(); + if(evt_vec.first.len < 1) return; + + AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), + AsyncEvent::SourceStateChange)}; + evt->u.srcstate.id = id; + switch(state) { - bool cforce{CalcContextParams(ctx)}; - bool force{CalcListenerParams(ctx) || cforce}; - force = std::accumulate(slots->begin(), slots->end(), force, - [ctx,cforce](bool force, ALeffectslot *slot) -> bool - { return CalcEffectSlotParams(slot, ctx, cforce) | force; } - ); - - std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire), - [ctx,force](ALvoice *voice) -> void - { - ALuint sid{voice->mSourceID.load(std::memory_order_acquire)}; - if(sid) CalcSourceParams(voice, ctx, force); - } - ); + case VChangeState::Reset: + evt->u.srcstate.state = AsyncEvent::SrcState::Reset; + break; + case VChangeState::Stop: + evt->u.srcstate.state = AsyncEvent::SrcState::Stop; + break; + case VChangeState::Play: + evt->u.srcstate.state = AsyncEvent::SrcState::Play; + break; + case VChangeState::Pause: + evt->u.srcstate.state = AsyncEvent::SrcState::Pause; + break; + /* Shouldn't happen. */ + case VChangeState::Restart: + ASSUME(0); } - IncrementRef(&ctx->UpdateCount); + + ring->writeAdvance(1); } -void ProcessContext(ALCcontext *ctx, const ALsizei SamplesToDo) +void ProcessVoiceChanges(ContextBase *ctx) { - ASSUME(SamplesToDo > 0); + VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)}; + VoiceChange *next{cur->mNext.load(std::memory_order_acquire)}; + if(!next) return; - const ALeffectslotArray *auxslots{ctx->ActiveAuxSlots.load(std::memory_order_acquire)}; + const uint enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)}; + do { + cur = next; - /* Process pending propery updates for objects on the context. */ - ProcessParamUpdates(ctx, auxslots); - - /* Clear auxiliary effect slot mixing buffers. */ - std::for_each(auxslots->begin(), auxslots->end(), - [SamplesToDo](ALeffectslot *slot) -> void + bool sendevt{false}; + if(cur->mState == VChangeState::Reset || cur->mState == VChangeState::Stop) { - for(auto &buffer : slot->MixBuffer) - std::fill_n(buffer.begin(), SamplesToDo, 0.0f); + if(Voice *voice{cur->mVoice}) + { + voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); + voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); + /* A source ID indicates the voice was playing or paused, which + * gets a reset/stop event. + */ + sendevt = voice->mSourceID.exchange(0u, std::memory_order_relaxed) != 0u; + Voice::State oldvstate{Voice::Playing}; + voice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping, + std::memory_order_relaxed, std::memory_order_acquire); + voice->mPendingChange.store(false, std::memory_order_release); + } + /* Reset state change events are always sent, even if the voice is + * already stopped or even if there is no voice. + */ + sendevt |= (cur->mState == VChangeState::Reset); } - ); + else if(cur->mState == VChangeState::Pause) + { + Voice *voice{cur->mVoice}; + Voice::State oldvstate{Voice::Playing}; + sendevt = voice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping, + std::memory_order_release, std::memory_order_acquire); + } + else if(cur->mState == VChangeState::Play) + { + /* NOTE: When playing a voice, sending a source state change event + * depends if there's an old voice to stop and if that stop is + * successful. If there is no old voice, a playing event is always + * sent. If there is an old voice, an event is sent only if the + * voice is already stopped. + */ + if(Voice *oldvoice{cur->mOldVoice}) + { + oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); + oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); + oldvoice->mSourceID.store(0u, std::memory_order_relaxed); + Voice::State oldvstate{Voice::Playing}; + sendevt = !oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping, + std::memory_order_relaxed, std::memory_order_acquire); + oldvoice->mPendingChange.store(false, std::memory_order_release); + } + else + sendevt = true; - /* Process voices that have a playing source. */ - std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire), - [SamplesToDo,ctx](ALvoice *voice) -> void + Voice *voice{cur->mVoice}; + voice->mPlayState.store(Voice::Playing, std::memory_order_release); + } + else if(cur->mState == VChangeState::Restart) { - const ALvoice::State vstate{voice->mPlayState.load(std::memory_order_acquire)}; - if(vstate == ALvoice::Stopped) return; - const ALuint sid{voice->mSourceID.load(std::memory_order_relaxed)}; - if(voice->mStep < 1) return; + /* Restarting a voice never sends a source change event. */ + Voice *oldvoice{cur->mOldVoice}; + oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); + oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); + /* If there's no sourceID, the old voice finished so don't start + * the new one at its new offset. + */ + if(oldvoice->mSourceID.exchange(0u, std::memory_order_relaxed) != 0u) + { + /* Otherwise, set the voice to stopping if it's not already (it + * might already be, if paused), and play the new voice as + * appropriate. + */ + Voice::State oldvstate{Voice::Playing}; + oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping, + std::memory_order_relaxed, std::memory_order_acquire); - MixVoice(voice, vstate, sid, ctx, SamplesToDo); + Voice *voice{cur->mVoice}; + voice->mPlayState.store((oldvstate == Voice::Playing) ? Voice::Playing + : Voice::Stopped, std::memory_order_release); + } + oldvoice->mPendingChange.store(false, std::memory_order_release); } - ); + if(sendevt && (enabledevt&AsyncEvent::SourceStateChange)) + SendSourceStateEvent(ctx, cur->mSourceID, cur->mState); - /* Process effects. */ - if(auxslots->size() < 1) return; - auto slots = auxslots->data(); - auto slots_end = slots + auxslots->size(); + next = cur->mNext.load(std::memory_order_acquire); + } while(next); + ctx->mCurrentVoiceChange.store(cur, std::memory_order_release); +} - /* First sort the slots into scratch storage, so that effects come before - * their effect target (or their targets' target). - */ - auto sorted_slots = const_cast(slots_end); - auto sorted_slots_end = sorted_slots; - auto in_chain = [](const ALeffectslot *slot1, const ALeffectslot *slot2) noexcept -> bool - { - while((slot1=slot1->Params.Target) != nullptr) { - if(slot1 == slot2) return true; - } - return false; - }; +void ProcessParamUpdates(ContextBase *ctx, const EffectSlotArray &slots, + const al::span voices) +{ + ProcessVoiceChanges(ctx); - *sorted_slots_end = *slots; - ++sorted_slots_end; - while(++slots != slots_end) + IncrementRef(ctx->mUpdateCount); + if LIKELY(!ctx->mHoldUpdates.load(std::memory_order_acquire)) { - /* If this effect slot targets an effect slot already in the list (i.e. - * slots outputs to something in sorted_slots), directly or indirectly, - * insert it prior to that element. - */ - auto checker = sorted_slots; - do { - if(in_chain(*slots, *checker)) break; - } while(++checker != sorted_slots_end); - - checker = std::move_backward(checker, sorted_slots_end, sorted_slots_end+1); - *--checker = *slots; - ++sorted_slots_end; - } + bool force{CalcContextParams(ctx)}; + auto sorted_slots = const_cast(slots.data() + slots.size()); + for(EffectSlot *slot : slots) + force |= CalcEffectSlotParams(slot, sorted_slots, ctx); - std::for_each(sorted_slots, sorted_slots_end, - [SamplesToDo](const ALeffectslot *slot) -> void + for(Voice *voice : voices) { - EffectState *state{slot->Params.mEffectState}; - state->process(SamplesToDo, slot->Wet.Buffer, slot->Wet.NumChannels, - state->mOutBuffer, state->mOutChannels); + /* Only update voices that have a source. */ + if(voice->mSourceID.load(std::memory_order_relaxed) != 0) + CalcSourceParams(voice, ctx, force); } - ); + } + IncrementRef(ctx->mUpdateCount); } - -void ApplyStablizer(FrontStablizer *Stablizer, ALfloat (*RESTRICT Buffer)[BUFFERSIZE], - int lidx, int ridx, int cidx, const ALsizei SamplesToDo, - const ALsizei NumChannels) +void ProcessContexts(DeviceBase *device, const uint SamplesToDo) { ASSUME(SamplesToDo > 0); - ASSUME(NumChannels > 0); - /* Apply a delay to all channels, except the front-left and front-right, so - * they maintain correct timing. - */ - for(ALsizei i{0};i < NumChannels;i++) + for(ContextBase *ctx : *device->mContexts.load(std::memory_order_acquire)) { - if(i == lidx || i == ridx) - continue; + const EffectSlotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire); + const al::span voices{ctx->getVoicesSpanAcquired()}; + + /* Process pending propery updates for objects on the context. */ + ProcessParamUpdates(ctx, auxslots, voices); - auto &DelayBuf = Stablizer->DelayBuf[i]; - auto buffer_end = Buffer[i] + SamplesToDo; - if(LIKELY(SamplesToDo >= ALsizei{FrontStablizer::DelayLength})) + /* Clear auxiliary effect slot mixing buffers. */ + for(EffectSlot *slot : auxslots) { - auto delay_end = std::rotate(Buffer[i], buffer_end - FrontStablizer::DelayLength, - buffer_end); - std::swap_ranges(Buffer[i], delay_end, std::begin(DelayBuf)); + for(auto &buffer : slot->Wet.Buffer) + buffer.fill(0.0f); } - else + + /* Process voices that have a playing source. */ + for(Voice *voice : voices) { - auto delay_start = std::swap_ranges(Buffer[i], buffer_end, std::begin(DelayBuf)); - std::rotate(std::begin(DelayBuf), delay_start, std::end(DelayBuf)); + const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)}; + if(vstate != Voice::Stopped && vstate != Voice::Pending) + voice->mix(vstate, ctx, SamplesToDo); } - } - - SplitterAllpass &APFilter = Stablizer->APFilter; - ALfloat (&lsplit)[2][BUFFERSIZE] = Stablizer->LSplit; - ALfloat (&rsplit)[2][BUFFERSIZE] = Stablizer->RSplit; - auto &tmpbuf = Stablizer->TempBuf; - /* This applies the band-splitter, preserving phase at the cost of some - * delay. The shorter the delay, the more error seeps into the result. - */ - auto apply_splitter = [&APFilter,&tmpbuf,SamplesToDo](const ALfloat *RESTRICT Buffer, - ALfloat (&DelayBuf)[FrontStablizer::DelayLength], BandSplitter &Filter, - ALfloat (&splitbuf)[2][BUFFERSIZE]) -> void - { - /* Combine the delayed samples and the input samples into the temp - * buffer, in reverse. Then copy the final samples back into the delay - * buffer for next time. Note that the delay buffer's samples are - * stored backwards here. - */ - auto tmpbuf_end = std::begin(tmpbuf) + SamplesToDo; - std::copy_n(std::begin(DelayBuf), FrontStablizer::DelayLength, tmpbuf_end); - std::reverse_copy(Buffer, Buffer+SamplesToDo, std::begin(tmpbuf)); - std::copy_n(std::begin(tmpbuf), FrontStablizer::DelayLength, std::begin(DelayBuf)); - - /* Apply an all-pass on the reversed signal, then reverse the samples - * to get the forward signal with a reversed phase shift. Note that the - * all-pass filter is copied to a local for use, since each pass is - * indepedent because the signal's processed backwards (with a delay - * being used to hide discontinuities). - */ - SplitterAllpass allpass{APFilter}; - allpass.process(tmpbuf, SamplesToDo+FrontStablizer::DelayLength); - std::reverse(std::begin(tmpbuf), tmpbuf_end+FrontStablizer::DelayLength); + /* Process effects. */ + if(const size_t num_slots{auxslots.size()}) + { + auto slots = auxslots.data(); + auto slots_end = slots + num_slots; - /* Now apply the band-splitter, combining its phase shift with the - * reversed phase shift, restoring the original phase on the split - * signal. - */ - Filter.process(splitbuf[1], splitbuf[0], tmpbuf, SamplesToDo); - }; - apply_splitter(Buffer[lidx], Stablizer->DelayBuf[lidx], Stablizer->LFilter, lsplit); - apply_splitter(Buffer[ridx], Stablizer->DelayBuf[ridx], Stablizer->RFilter, rsplit); + /* Sort the slots into extra storage, so that effect slots come + * before their effect slot target (or their targets' target). + */ + const al::span sorted_slots{const_cast(slots_end), + num_slots}; + /* Skip sorting if it has already been done. */ + if(!sorted_slots[0]) + { + /* First, copy the slots to the sorted list, then partition the + * sorted list so that all slots without a target slot go to + * the end. + */ + std::copy(slots, slots_end, sorted_slots.begin()); + auto split_point = std::partition(sorted_slots.begin(), sorted_slots.end(), + [](const EffectSlot *slot) noexcept -> bool + { return slot->Target != nullptr; }); + /* 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. + */ + if(split_point - sorted_slots.begin() > 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. + */ + 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 UNLIKELY(next_target == split_point) + 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); + } + } - for(ALsizei i{0};i < SamplesToDo;i++) - { - ALfloat lfsum{lsplit[0][i] + rsplit[0][i]}; - ALfloat hfsum{lsplit[1][i] + rsplit[1][i]}; - ALfloat s{lsplit[0][i] + lsplit[1][i] - rsplit[0][i] - rsplit[1][i]}; - - /* This pans the separate low- and high-frequency sums between being on - * the center channel and the left/right channels. The low-frequency - * sum is 1/3rd toward center (2/3rds on left/right) and the high- - * frequency sum is 1/4th toward center (3/4ths on left/right). These - * values can be tweaked. - */ - ALfloat m{lfsum*std::cos(1.0f/3.0f * (al::MathDefs::Pi()*0.5f)) + - hfsum*std::cos(1.0f/4.0f * (al::MathDefs::Pi()*0.5f))}; - ALfloat c{lfsum*std::sin(1.0f/3.0f * (al::MathDefs::Pi()*0.5f)) + - hfsum*std::sin(1.0f/4.0f * (al::MathDefs::Pi()*0.5f))}; + for(const EffectSlot *slot : sorted_slots) + { + EffectState *state{slot->mEffectState}; + state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget); + } + } - /* The generated center channel signal adds to the existing signal, - * while the modified left and right channels replace. - */ - Buffer[lidx][i] = (m + s) * 0.5f; - Buffer[ridx][i] = (m - s) * 0.5f; - Buffer[cidx][i] += c * 0.5f; + /* Signal the event handler if there are any events to read. */ + RingBuffer *ring{ctx->mAsyncEvents.get()}; + if(ring->readSpace() > 0) + ctx->mEventSem.post(); } } -void ApplyDistanceComp(ALfloat (*Samples)[BUFFERSIZE], const DistanceComp &distcomp, - const ALsizei SamplesToDo, const ALsizei numchans) + +void ApplyDistanceComp(const al::span Samples, const size_t SamplesToDo, + const DistanceComp::ChanData *distcomp) { ASSUME(SamplesToDo > 0); - ASSUME(numchans > 0); - for(ALsizei c{0};c < numchans;c++) + for(auto &chanbuffer : Samples) { - const ALfloat gain{distcomp[c].Gain}; - const ALsizei base{distcomp[c].Length}; - ALfloat *distbuf{al::assume_aligned<16>(distcomp[c].Buffer)}; + const float gain{distcomp->Gain}; + const size_t base{distcomp->Length}; + float *distbuf{al::assume_aligned<16>(distcomp->Buffer)}; + ++distcomp; if(base < 1) continue; - ALfloat *inout{al::assume_aligned<16>(Samples[c])}; + float *inout{al::assume_aligned<16>(chanbuffer.data())}; auto inout_end = inout + SamplesToDo; - if(LIKELY(SamplesToDo >= base)) + if LIKELY(SamplesToDo >= base) { auto delay_end = std::rotate(inout, inout_end - base, inout_end); std::swap_ranges(inout, delay_end, distbuf); @@ -1604,32 +1819,27 @@ void ApplyDistanceComp(ALfloat (*Samples)[BUFFERSIZE], const DistanceComp &distc } } -void ApplyDither(ALfloat (*Samples)[BUFFERSIZE], ALuint *dither_seed, const ALfloat quant_scale, - const ALsizei SamplesToDo, const ALsizei numchans) +void ApplyDither(const al::span Samples, uint *dither_seed, + const float quant_scale, const size_t SamplesToDo) { - ASSUME(numchans > 0); + ASSUME(SamplesToDo > 0); /* Dithering. Generate whitenoise (uniform distribution of random values * between -1 and +1) and add it to the sample values, after scaling up to * the desired quantization depth amd before rounding. */ - const ALfloat invscale{1.0f / quant_scale}; - ALuint seed{*dither_seed}; - auto dither_channel = [&seed,invscale,quant_scale,SamplesToDo](ALfloat *input) -> void + const float invscale{1.0f / quant_scale}; + uint seed{*dither_seed}; + auto dither_sample = [&seed,invscale,quant_scale](const float sample) noexcept -> float { - ASSUME(SamplesToDo > 0); - ALfloat *buffer{al::assume_aligned<16>(input)}; - auto dither_sample = [&seed,invscale,quant_scale](ALfloat sample) noexcept -> ALfloat - { - ALfloat val{sample * quant_scale}; - ALuint rng0{dither_rng(&seed)}; - ALuint rng1{dither_rng(&seed)}; - val += static_cast(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX)); - return fast_roundf(val) * invscale; - }; - std::transform(buffer, buffer+SamplesToDo, buffer, dither_sample); + float val{sample * quant_scale}; + uint rng0{dither_rng(&seed)}; + uint rng1{dither_rng(&seed)}; + val += static_cast(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX)); + return fast_roundf(val) * invscale; }; - std::for_each(Samples, Samples+numchans, dither_channel); + for(FloatBufferLine &inout : Samples) + std::transform(inout.begin(), inout.begin()+SamplesToDo, inout.begin(), dither_sample); *dither_seed = seed; } @@ -1638,11 +1848,11 @@ void ApplyDither(ALfloat (*Samples)[BUFFERSIZE], ALuint *dither_seed, const ALfl * chokes on that given the inline specializations. */ template -inline T SampleConv(ALfloat) noexcept; +inline T SampleConv(float) noexcept; -template<> inline ALfloat SampleConv(ALfloat val) noexcept +template<> inline float SampleConv(float val) noexcept { return val; } -template<> inline ALint SampleConv(ALfloat val) noexcept +template<> inline int32_t SampleConv(float val) noexcept { /* Floats have a 23-bit mantissa, plus an implied 1 bit and a sign bit. * This means a normalized float has at most 25 bits of signed precision. @@ -1651,188 +1861,195 @@ template<> inline ALint SampleConv(ALfloat val) noexcept */ return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); } -template<> inline ALshort SampleConv(ALfloat val) noexcept -{ return fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f)); } -template<> inline ALbyte SampleConv(ALfloat val) noexcept -{ return fastf2i(clampf(val*128.0f, -128.0f, 127.0f)); } +template<> inline int16_t SampleConv(float val) noexcept +{ return static_cast(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); } +template<> inline int8_t SampleConv(float val) noexcept +{ return static_cast(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); } /* Define unsigned output variations. */ -template<> inline ALuint SampleConv(ALfloat val) noexcept -{ return SampleConv(val) + 2147483648u; } -template<> inline ALushort SampleConv(ALfloat val) noexcept -{ return SampleConv(val) + 32768; } -template<> inline ALubyte SampleConv(ALfloat val) noexcept -{ return SampleConv(val) + 128; } +template<> inline uint32_t SampleConv(float val) noexcept +{ return static_cast(SampleConv(val)) + 2147483648u; } +template<> inline uint16_t SampleConv(float val) noexcept +{ return static_cast(SampleConv(val) + 32768); } +template<> inline uint8_t SampleConv(float val) noexcept +{ return static_cast(SampleConv(val) + 128); } template -void Write(const ALfloat (*InBuffer)[BUFFERSIZE], ALvoid *OutBuffer, ALsizei Offset, - ALsizei SamplesToDo, ALsizei numchans) +void Write(const al::span InBuffer, void *OutBuffer, const size_t Offset, + const size_t SamplesToDo, const size_t FrameStep) { - using SampleType = typename DevFmtTypeTraits::Type; + ASSUME(FrameStep > 0); + ASSUME(SamplesToDo > 0); - ASSUME(numchans > 0); - SampleType *outbase = static_cast(OutBuffer) + Offset*numchans; - auto conv_channel = [&outbase,SamplesToDo,numchans](const ALfloat *inbuf) -> void + DevFmtType_t *outbase{static_cast*>(OutBuffer) + Offset*FrameStep}; + size_t c{0}; + for(const FloatBufferLine &inbuf : InBuffer) { - ASSUME(SamplesToDo > 0); - SampleType *out{outbase++}; - std::for_each(inbuf, inbuf+SamplesToDo, - [numchans,&out](const ALfloat s) noexcept -> void - { - *out = SampleConv(s); - out += numchans; - } - ); - }; - std::for_each(InBuffer, InBuffer+numchans, conv_channel); + DevFmtType_t *out{outbase++}; + auto conv_sample = [FrameStep,&out](const float s) noexcept -> void + { + *out = SampleConv>(s); + out += FrameStep; + }; + std::for_each(inbuf.begin(), inbuf.begin()+SamplesToDo, conv_sample); + ++c; + } + if(const size_t extra{FrameStep - c}) + { + const auto silence = SampleConv>(0.0f); + for(size_t i{0};i < SamplesToDo;++i) + { + std::fill_n(outbase, extra, silence); + outbase += FrameStep; + } + } } } // namespace -void aluMixData(ALCdevice *device, ALvoid *OutBuffer, ALsizei NumSamples) +uint DeviceBase::renderSamples(const uint numSamples) { - FPUCtl mixer_mode{}; - for(ALsizei SamplesDone{0};SamplesDone < NumSamples;) - { - const ALsizei SamplesToDo{mini(NumSamples-SamplesDone, BUFFERSIZE)}; - - /* Clear main mixing buffers. */ - std::for_each(device->MixBuffer.begin(), device->MixBuffer.end(), - [SamplesToDo](std::array &buffer) -> void - { std::fill_n(buffer.begin(), SamplesToDo, 0.0f); } - ); + const uint samplesToDo{minu(numSamples, BufferLineSize)}; - /* Increment the mix count at the start (lsb should now be 1). */ - IncrementRef(&device->MixCount); + /* Clear main mixing buffers. */ + for(FloatBufferLine &buffer : MixBuffer) + buffer.fill(0.0f); - /* For each context on this device, process and mix its sources and - * effects. - */ - ALCcontext *ctx{device->ContextList.load(std::memory_order_acquire)}; - while(ctx) - { - ProcessContext(ctx, SamplesToDo); + /* Increment the mix count at the start (lsb should now be 1). */ + IncrementRef(MixCount); - ctx = ctx->next.load(std::memory_order_relaxed); - } + /* Process and mix each context's sources and effects. */ + ProcessContexts(this, samplesToDo); - /* Increment the clock time. Every second's worth of samples is - * converted and added to clock base so that large sample counts don't - * overflow during conversion. This also guarantees a stable - * conversion. - */ - device->SamplesDone += SamplesToDo; - device->ClockBase += std::chrono::seconds{device->SamplesDone / device->Frequency}; - device->SamplesDone %= device->Frequency; + /* Increment the clock time. Every second's worth of samples is converted + * and added to clock base so that large sample counts don't overflow + * during conversion. This also guarantees a stable conversion. + */ + SamplesDone += samplesToDo; + ClockBase += std::chrono::seconds{SamplesDone / Frequency}; + SamplesDone %= Frequency; - /* Increment the mix count at the end (lsb should now be 0). */ - IncrementRef(&device->MixCount); + /* Increment the mix count at the end (lsb should now be 0). */ + IncrementRef(MixCount); - /* Apply any needed post-process for finalizing the Dry mix to the - * RealOut (Ambisonic decode, UHJ encode, etc). - */ - if(LIKELY(device->PostProcess)) - device->PostProcess(device, SamplesToDo); + /* Apply any needed post-process for finalizing the Dry mix to the RealOut + * (Ambisonic decode, UHJ encode, etc). + */ + postProcess(samplesToDo); - /* Apply front image stablization for surround sound, if applicable. */ - if(device->Stablizer) - { - const int lidx{GetChannelIdxByName(device->RealOut, FrontLeft)}; - const int ridx{GetChannelIdxByName(device->RealOut, FrontRight)}; - const int cidx{GetChannelIdxByName(device->RealOut, FrontCenter)}; - assert(lidx >= 0 && ridx >= 0 && cidx >= 0); + /* Apply compression, limiting sample amplitude if needed or desired. */ + if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data()); - ApplyStablizer(device->Stablizer.get(), device->RealOut.Buffer, lidx, ridx, cidx, - SamplesToDo, device->RealOut.NumChannels); - } + /* Apply delays and attenuation for mismatched speaker distances. */ + if(ChannelDelays) + ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelays->mChannels.data()); - /* Apply compression, limiting sample amplitude if needed or desired. */ - if(Compressor *comp{device->Limiter.get()}) - comp->process(SamplesToDo, device->RealOut.Buffer); + /* Apply dithering. The compressor should have left enough headroom for the + * dither noise to not saturate. + */ + if(DitherDepth > 0.0f) + ApplyDither(RealOut.Buffer, &DitherSeed, DitherDepth, samplesToDo); - /* Apply delays and attenuation for mismatched speaker distances. */ - ApplyDistanceComp(device->RealOut.Buffer, device->ChannelDelay, SamplesToDo, - device->RealOut.NumChannels); + return samplesToDo; +} - /* Apply dithering. The compressor should have left enough headroom for - * the dither noise to not saturate. - */ - if(device->DitherDepth > 0.0f) - ApplyDither(device->RealOut.Buffer, &device->DitherSeed, device->DitherDepth, - SamplesToDo, device->RealOut.NumChannels); +void DeviceBase::renderSamples(const al::span outBuffers, const uint numSamples) +{ + FPUCtl mixer_mode{}; + uint total{0}; + while(const uint todo{numSamples - total}) + { + const uint samplesToDo{renderSamples(todo)}; - if(LIKELY(OutBuffer)) + auto *srcbuf = RealOut.Buffer.data(); + for(auto *dstbuf : outBuffers) { - ALfloat (*Buffer)[BUFFERSIZE]{device->RealOut.Buffer}; - ALsizei Channels{device->RealOut.NumChannels}; + std::copy_n(srcbuf->data(), samplesToDo, dstbuf + total); + ++srcbuf; + } + + total += samplesToDo; + } +} + +void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep) +{ + FPUCtl mixer_mode{}; + uint total{0}; + while(const uint todo{numSamples - total}) + { + const uint samplesToDo{renderSamples(todo)}; + if LIKELY(outBuffer) + { /* Finally, interleave and convert samples, writing to the device's * output buffer. */ - switch(device->FmtType) + switch(FmtType) { -#define HANDLE_WRITE(T) case T: \ - Write(Buffer, OutBuffer, SamplesDone, SamplesToDo, Channels); break; - HANDLE_WRITE(DevFmtByte) - HANDLE_WRITE(DevFmtUByte) - HANDLE_WRITE(DevFmtShort) - HANDLE_WRITE(DevFmtUShort) - HANDLE_WRITE(DevFmtInt) - HANDLE_WRITE(DevFmtUInt) - HANDLE_WRITE(DevFmtFloat) +#define HANDLE_WRITE(T) case T: \ + Write(RealOut.Buffer, outBuffer, total, samplesToDo, frameStep); break; + HANDLE_WRITE(DevFmtByte) + HANDLE_WRITE(DevFmtUByte) + HANDLE_WRITE(DevFmtShort) + HANDLE_WRITE(DevFmtUShort) + HANDLE_WRITE(DevFmtInt) + HANDLE_WRITE(DevFmtUInt) + HANDLE_WRITE(DevFmtFloat) #undef HANDLE_WRITE } } - SamplesDone += SamplesToDo; + total += samplesToDo; } } - -void aluHandleDisconnect(ALCdevice *device, const char *msg, ...) +void DeviceBase::handleDisconnect(const char *msg, ...) { - if(!device->Connected.exchange(false, std::memory_order_acq_rel)) + if(!Connected.exchange(false, std::memory_order_acq_rel)) return; - AsyncEvent evt{EventType_Disconnected}; - evt.u.user.type = AL_EVENT_TYPE_DISCONNECTED_SOFT; - evt.u.user.id = 0; - evt.u.user.param = 0; + AsyncEvent evt{AsyncEvent::Disconnected}; va_list args; va_start(args, msg); - int msglen{vsnprintf(evt.u.user.msg, sizeof(evt.u.user.msg), msg, args)}; + int msglen{vsnprintf(evt.u.disconnect.msg, sizeof(evt.u.disconnect.msg), msg, args)}; va_end(args); - if(msglen < 0 || static_cast(msglen) >= sizeof(evt.u.user.msg)) - evt.u.user.msg[sizeof(evt.u.user.msg)-1] = 0; + if(msglen < 0 || static_cast(msglen) >= sizeof(evt.u.disconnect.msg)) + evt.u.disconnect.msg[sizeof(evt.u.disconnect.msg)-1] = 0; - ALCcontext *ctx{device->ContextList.load()}; - while(ctx) + IncrementRef(MixCount); + for(ContextBase *ctx : *mContexts.load()) { - const ALbitfieldSOFT enabledevt{ctx->EnabledEvts.load(std::memory_order_acquire)}; - if((enabledevt&EventType_Disconnected)) + const uint enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)}; + if((enabledevt&AsyncEvent::Disconnected)) { - RingBuffer *ring{ctx->AsyncEvents.get()}; + RingBuffer *ring{ctx->mAsyncEvents.get()}; auto evt_data = ring->getWriteVector().first; if(evt_data.len > 0) { - new (evt_data.buf) AsyncEvent{evt}; + al::construct_at(reinterpret_cast(evt_data.buf), evt); ring->writeAdvance(1); - ctx->EventSem.post(); + ctx->mEventSem.post(); } } - auto stop_voice = [](ALvoice *voice) -> void + if(!ctx->mStopVoicesOnDisconnect) + { + ProcessVoiceChanges(ctx); + continue; + } + + auto voicelist = ctx->getVoicesSpanAcquired(); + auto stop_voice = [](Voice *voice) -> void { voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); voice->mSourceID.store(0u, std::memory_order_relaxed); - voice->mPlayState.store(ALvoice::Stopped, std::memory_order_release); + voice->mPlayState.store(Voice::Stopped, std::memory_order_release); }; - std::for_each(ctx->Voices, ctx->Voices+ctx->VoiceCount.load(std::memory_order_acquire), - stop_voice); - - ctx = ctx->next.load(std::memory_order_relaxed); + std::for_each(voicelist.begin(), voicelist.end(), stop_voice); } + IncrementRef(MixCount); } diff --git a/modules/openal-soft/Alc/alu.h b/modules/openal-soft/Alc/alu.h new file mode 100644 index 0000000..f3796a8 --- /dev/null +++ b/modules/openal-soft/Alc/alu.h @@ -0,0 +1,38 @@ +#ifndef ALU_H +#define ALU_H + +#include + +#include "aloptional.h" + +struct ALCcontext; +struct ALCdevice; +struct EffectSlot; + +enum class StereoEncoding : unsigned char; + + +constexpr float GainMixMax{1000.0f}; /* +60dB */ + + +enum CompatFlags : uint8_t { + ReverseX, + ReverseY, + ReverseZ, + + Count +}; +using CompatFlagBitset = std::bitset; + +void aluInit(CompatFlagBitset flags); + +/* aluInitRenderer + * + * Set up the appropriate panning method and mixing method given the device + * properties. + */ +void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional stereomode); + +void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context); + +#endif diff --git a/modules/openal-soft/Alc/ambidefs.h b/modules/openal-soft/Alc/ambidefs.h deleted file mode 100644 index 17a9815..0000000 --- a/modules/openal-soft/Alc/ambidefs.h +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef AMBIDEFS_H -#define AMBIDEFS_H - -#include - -/* The maximum number of Ambisonics channels. For a given order (o), the size - * needed will be (o+1)**2, thus zero-order has 1, first-order has 4, second- - * order has 9, third-order has 16, and fourth-order has 25. - */ -#define MAX_AMBI_ORDER 3 -constexpr inline size_t AmbiChannelsFromOrder(size_t order) noexcept -{ return (order+1) * (order+1); } -#define MAX_AMBI_CHANNELS AmbiChannelsFromOrder(MAX_AMBI_ORDER) - -/* A bitmask of ambisonic channels for 0 to 4th order. This only specifies up - * to 4th order, which is the highest order a 32-bit mask value can specify (a - * 64-bit mask could handle up to 7th order). - */ -#define AMBI_0ORDER_MASK 0x00000001 -#define AMBI_1ORDER_MASK 0x0000000f -#define AMBI_2ORDER_MASK 0x000001ff -#define AMBI_3ORDER_MASK 0x0000ffff -#define AMBI_4ORDER_MASK 0x01ffffff - -/* A bitmask of ambisonic channels with height information. If none of these - * channels are used/needed, there's no height (e.g. with most surround sound - * speaker setups). This is ACN ordering, with bit 0 being ACN 0, etc. - */ -#define AMBI_PERIPHONIC_MASK (0xfe7ce4) - -/* The maximum number of ambisonic channels for 2D (non-periphonic) - * representation. This is 2 per each order above zero-order, plus 1 for zero- - * order. Or simply, o*2 + 1. - */ -constexpr inline size_t Ambi2DChannelsFromOrder(size_t order) noexcept -{ return order*2 + 1; } -#define MAX_AMBI2D_CHANNELS Ambi2DChannelsFromOrder(MAX_AMBI_ORDER) - - -/* NOTE: These are scale factors as applied to Ambisonics content. Decoder - * coefficients should be divided by these values to get proper scalings. - */ -struct AmbiScale { - static constexpr std::array FromN3D{{ - 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f - }}; - static constexpr std::array FromSN3D{{ - 1.000000000f, /* ACN 0, sqrt(1) */ - 1.732050808f, /* ACN 1, sqrt(3) */ - 1.732050808f, /* ACN 2, sqrt(3) */ - 1.732050808f, /* ACN 3, sqrt(3) */ - 2.236067978f, /* ACN 4, sqrt(5) */ - 2.236067978f, /* ACN 5, sqrt(5) */ - 2.236067978f, /* ACN 6, sqrt(5) */ - 2.236067978f, /* ACN 7, sqrt(5) */ - 2.236067978f, /* ACN 8, sqrt(5) */ - 2.645751311f, /* ACN 9, sqrt(7) */ - 2.645751311f, /* ACN 10, sqrt(7) */ - 2.645751311f, /* ACN 11, sqrt(7) */ - 2.645751311f, /* ACN 12, sqrt(7) */ - 2.645751311f, /* ACN 13, sqrt(7) */ - 2.645751311f, /* ACN 14, sqrt(7) */ - 2.645751311f, /* ACN 15, sqrt(7) */ - }}; - static constexpr std::array FromFuMa{{ - 1.414213562f, /* ACN 0 (W), sqrt(2) */ - 1.732050808f, /* ACN 1 (Y), sqrt(3) */ - 1.732050808f, /* ACN 2 (Z), sqrt(3) */ - 1.732050808f, /* ACN 3 (X), sqrt(3) */ - 1.936491673f, /* ACN 4 (V), sqrt(15)/2 */ - 1.936491673f, /* ACN 5 (T), sqrt(15)/2 */ - 2.236067978f, /* ACN 6 (R), sqrt(5) */ - 1.936491673f, /* ACN 7 (S), sqrt(15)/2 */ - 1.936491673f, /* ACN 8 (U), sqrt(15)/2 */ - 2.091650066f, /* ACN 9 (Q), sqrt(35/8) */ - 1.972026594f, /* ACN 10 (O), sqrt(35)/3 */ - 2.231093404f, /* ACN 11 (M), sqrt(224/45) */ - 2.645751311f, /* ACN 12 (K), sqrt(7) */ - 2.231093404f, /* ACN 13 (L), sqrt(224/45) */ - 1.972026594f, /* ACN 14 (N), sqrt(35)/3 */ - 2.091650066f, /* ACN 15 (P), sqrt(35/8) */ - }}; -}; - -struct AmbiIndex { - static constexpr std::array FromFuMa{{ - 0, /* W */ - 3, /* X */ - 1, /* Y */ - 2, /* Z */ - 6, /* R */ - 7, /* S */ - 5, /* T */ - 8, /* U */ - 4, /* V */ - 12, /* K */ - 13, /* L */ - 11, /* M */ - 14, /* N */ - 10, /* O */ - 15, /* P */ - 9, /* Q */ - }}; - static constexpr std::array FromACN{{ - 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15 - }}; - - static constexpr std::array From2D{{ - 0, 1,3, 4,8, 9,15 - }}; - static constexpr std::array From3D{{ - 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15 - }}; -}; - -#endif /* AMBIDEFS_H */ diff --git a/modules/openal-soft/Alc/backends/alsa.cpp b/modules/openal-soft/Alc/backends/alsa.cpp index a293765..9c78b6c 100644 --- a/modules/openal-soft/Alc/backends/alsa.cpp +++ b/modules/openal-soft/Alc/backends/alsa.cpp @@ -20,31 +20,40 @@ #include "config.h" -#include "backends/alsa.h" +#include "alsa.h" -#include -#include -#include - -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include #include - -#include "alMain.h" -#include "alu.h" -#include "alconfig.h" +#include +#include +#include +#include + +#include "albyte.h" +#include "alc/alconfig.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "aloptional.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "dynload.h" #include "ringbuffer.h" -#include "compat.h" +#include "threads.h" +#include "vector.h" #include namespace { -constexpr ALCchar alsaDevice[] = "ALSA Default"; +constexpr char alsaDevice[] = "ALSA Default"; #ifdef HAVE_DYNLOAD @@ -59,35 +68,37 @@ constexpr ALCchar alsaDevice[] = "ALSA Default"; MAGIC(snd_pcm_hw_params_free); \ MAGIC(snd_pcm_hw_params_any); \ MAGIC(snd_pcm_hw_params_current); \ + MAGIC(snd_pcm_hw_params_get_access); \ + MAGIC(snd_pcm_hw_params_get_buffer_size); \ + MAGIC(snd_pcm_hw_params_get_buffer_time_min); \ + MAGIC(snd_pcm_hw_params_get_buffer_time_max); \ + MAGIC(snd_pcm_hw_params_get_channels); \ + MAGIC(snd_pcm_hw_params_get_period_size); \ + MAGIC(snd_pcm_hw_params_get_period_time_max); \ + MAGIC(snd_pcm_hw_params_get_period_time_min); \ + MAGIC(snd_pcm_hw_params_get_periods); \ MAGIC(snd_pcm_hw_params_set_access); \ - MAGIC(snd_pcm_hw_params_set_format); \ + MAGIC(snd_pcm_hw_params_set_buffer_size_min); \ + MAGIC(snd_pcm_hw_params_set_buffer_size_near); \ + MAGIC(snd_pcm_hw_params_set_buffer_time_near); \ MAGIC(snd_pcm_hw_params_set_channels); \ + MAGIC(snd_pcm_hw_params_set_channels_near); \ + MAGIC(snd_pcm_hw_params_set_format); \ + MAGIC(snd_pcm_hw_params_set_period_time_near); \ + MAGIC(snd_pcm_hw_params_set_period_size_near); \ MAGIC(snd_pcm_hw_params_set_periods_near); \ MAGIC(snd_pcm_hw_params_set_rate_near); \ MAGIC(snd_pcm_hw_params_set_rate); \ MAGIC(snd_pcm_hw_params_set_rate_resample); \ - MAGIC(snd_pcm_hw_params_set_buffer_time_near); \ - MAGIC(snd_pcm_hw_params_set_period_time_near); \ - MAGIC(snd_pcm_hw_params_set_buffer_size_near); \ - MAGIC(snd_pcm_hw_params_set_period_size_near); \ - MAGIC(snd_pcm_hw_params_set_buffer_size_min); \ - MAGIC(snd_pcm_hw_params_get_buffer_time_min); \ - MAGIC(snd_pcm_hw_params_get_buffer_time_max); \ - MAGIC(snd_pcm_hw_params_get_period_time_min); \ - MAGIC(snd_pcm_hw_params_get_period_time_max); \ - MAGIC(snd_pcm_hw_params_get_buffer_size); \ - MAGIC(snd_pcm_hw_params_get_period_size); \ - MAGIC(snd_pcm_hw_params_get_access); \ - MAGIC(snd_pcm_hw_params_get_periods); \ MAGIC(snd_pcm_hw_params_test_format); \ MAGIC(snd_pcm_hw_params_test_channels); \ MAGIC(snd_pcm_hw_params); \ - MAGIC(snd_pcm_sw_params_malloc); \ + MAGIC(snd_pcm_sw_params); \ MAGIC(snd_pcm_sw_params_current); \ + MAGIC(snd_pcm_sw_params_free); \ + MAGIC(snd_pcm_sw_params_malloc); \ MAGIC(snd_pcm_sw_params_set_avail_min); \ MAGIC(snd_pcm_sw_params_set_stop_threshold); \ - MAGIC(snd_pcm_sw_params); \ - MAGIC(snd_pcm_sw_params_free); \ MAGIC(snd_pcm_prepare); \ MAGIC(snd_pcm_start); \ MAGIC(snd_pcm_resume); \ @@ -96,7 +107,6 @@ constexpr ALCchar alsaDevice[] = "ALSA Default"; MAGIC(snd_pcm_delay); \ MAGIC(snd_pcm_state); \ MAGIC(snd_pcm_avail_update); \ - MAGIC(snd_pcm_areas_silence); \ MAGIC(snd_pcm_mmap_begin); \ MAGIC(snd_pcm_mmap_commit); \ MAGIC(snd_pcm_readi); \ @@ -141,6 +151,7 @@ ALSA_FUNCS(MAKE_FUNC); #define snd_pcm_hw_params_set_access psnd_pcm_hw_params_set_access #define snd_pcm_hw_params_set_format psnd_pcm_hw_params_set_format #define snd_pcm_hw_params_set_channels psnd_pcm_hw_params_set_channels +#define snd_pcm_hw_params_set_channels_near psnd_pcm_hw_params_set_channels_near #define snd_pcm_hw_params_set_periods_near psnd_pcm_hw_params_set_periods_near #define snd_pcm_hw_params_set_rate_near psnd_pcm_hw_params_set_rate_near #define snd_pcm_hw_params_set_rate psnd_pcm_hw_params_set_rate @@ -158,6 +169,7 @@ ALSA_FUNCS(MAKE_FUNC); #define snd_pcm_hw_params_get_period_size psnd_pcm_hw_params_get_period_size #define snd_pcm_hw_params_get_access psnd_pcm_hw_params_get_access #define snd_pcm_hw_params_get_periods psnd_pcm_hw_params_get_periods +#define snd_pcm_hw_params_get_channels psnd_pcm_hw_params_get_channels #define snd_pcm_hw_params_test_format psnd_pcm_hw_params_test_format #define snd_pcm_hw_params_test_channels psnd_pcm_hw_params_test_channels #define snd_pcm_hw_params psnd_pcm_hw_params @@ -175,7 +187,6 @@ ALSA_FUNCS(MAKE_FUNC); #define snd_pcm_delay psnd_pcm_delay #define snd_pcm_state psnd_pcm_state #define snd_pcm_avail_update psnd_pcm_avail_update -#define snd_pcm_areas_silence psnd_pcm_areas_silence #define snd_pcm_mmap_begin psnd_pcm_mmap_begin #define snd_pcm_mmap_commit psnd_pcm_mmap_commit #define snd_pcm_readi psnd_pcm_readi @@ -204,14 +215,32 @@ ALSA_FUNCS(MAKE_FUNC); #endif +struct HwParamsDeleter { + void operator()(snd_pcm_hw_params_t *ptr) { snd_pcm_hw_params_free(ptr); } +}; +using HwParamsPtr = std::unique_ptr; +HwParamsPtr CreateHwParams() +{ + snd_pcm_hw_params_t *hp{}; + snd_pcm_hw_params_malloc(&hp); + return HwParamsPtr{hp}; +} + +struct SwParamsDeleter { + void operator()(snd_pcm_sw_params_t *ptr) { snd_pcm_sw_params_free(ptr); } +}; +using SwParamsPtr = std::unique_ptr; +SwParamsPtr CreateSwParams() +{ + snd_pcm_sw_params_t *sp{}; + snd_pcm_sw_params_malloc(&sp); + return SwParamsPtr{sp}; +} + + struct DevMap { std::string name; std::string device_name; - - template - DevMap(StrT0&& name_, StrT1&& devname_) - : name{std::forward(name_)}, device_name{std::forward(devname_)} - { } }; al::vector PlaybackDevices; @@ -233,36 +262,40 @@ al::vector probe_devices(snd_pcm_stream_t stream) snd_pcm_info_t *pcminfo; snd_pcm_info_malloc(&pcminfo); - devlist.emplace_back(alsaDevice, - GetConfigValue(nullptr, "alsa", (stream==SND_PCM_STREAM_PLAYBACK) ? "device" : "capture", - "default") - ); + auto defname = ConfigValueStr(nullptr, "alsa", + (stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture"); + devlist.emplace_back(DevMap{alsaDevice, defname ? defname->c_str() : "default"}); - if(stream == SND_PCM_STREAM_PLAYBACK) + if(auto customdevs = ConfigValueStr(nullptr, "alsa", + (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures")) { - const char *customdevs; - const char *next{GetConfigValue(nullptr, "alsa", "custom-devices", "")}; - while((customdevs=next) != nullptr && customdevs[0]) + size_t nextpos{customdevs->find_first_not_of(';')}; + size_t curpos; + while((curpos=nextpos) < customdevs->length()) { - next = strchr(customdevs, ';'); - const char *sep{strchr(customdevs, '=')}; - if(!sep) + nextpos = customdevs->find_first_of(';', curpos+1); + + size_t seppos{customdevs->find_first_of('=', curpos)}; + if(seppos == curpos || seppos >= nextpos) { - std::string spec{next ? std::string(customdevs, next++) : std::string(customdevs)}; + std::string spec{customdevs->substr(curpos, nextpos-curpos)}; ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str()); - continue; + } + else + { + devlist.emplace_back(DevMap{customdevs->substr(curpos, seppos-curpos), + customdevs->substr(seppos+1, nextpos-seppos-1)}); + const auto &entry = devlist.back(); + TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } - const char *oldsep{sep++}; - devlist.emplace_back(std::string(customdevs, oldsep), - next ? std::string(sep, next++) : std::string(sep)); - const auto &entry = devlist.back(); - TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); + if(nextpos < customdevs->length()) + nextpos = customdevs->find_first_not_of(';', nextpos+1); } } - const char *main_prefix{"plughw:"}; - ConfigValueStr(nullptr, "alsa", prefix_name(stream), &main_prefix); + const std::string main_prefix{ + ConfigValueStr(nullptr, "alsa", prefix_name(stream)).value_or("plughw:")}; int card{-1}; int err{snd_card_next(&card)}; @@ -288,9 +321,8 @@ al::vector probe_devices(snd_pcm_stream_t stream) name = prefix_name(stream); name += '-'; name += cardid; - - const char *card_prefix{main_prefix}; - ConfigValueStr(nullptr, "alsa", name.c_str(), &card_prefix); + const std::string card_prefix{ + ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)}; int dev{-1}; while(1) @@ -299,7 +331,7 @@ al::vector probe_devices(snd_pcm_stream_t stream) ERR("snd_ctl_pcm_next_device failed\n"); if(dev < 0) break; - snd_pcm_info_set_device(pcminfo, dev); + snd_pcm_info_set_device(pcminfo, static_cast(dev)); snd_pcm_info_set_subdevice(pcminfo, 0); snd_pcm_info_set_stream(pcminfo, stream); if((err=snd_ctl_pcm_info(handle, pcminfo)) < 0) @@ -315,8 +347,8 @@ al::vector probe_devices(snd_pcm_stream_t stream) name += cardid; name += '-'; name += std::to_string(dev); - const char *device_prefix{card_prefix}; - ConfigValueStr(nullptr, "alsa", name.c_str(), &device_prefix); + const std::string device_prefix{ + ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(card_prefix)}; /* "CardName, PcmName (CARD=cardid,DEV=dev)" */ name = cardname; @@ -335,7 +367,7 @@ al::vector probe_devices(snd_pcm_stream_t stream) device += ",DEV="; device += std::to_string(dev); - devlist.emplace_back(std::move(name), std::move(device)); + devlist.emplace_back(DevMap{std::move(name), std::move(device)}); const auto &entry = devlist.back(); TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } @@ -384,27 +416,29 @@ int verify_state(snd_pcm_t *handle) struct AlsaPlayback final : public BackendBase { - AlsaPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~AlsaPlayback() override; int mixerProc(); int mixerNoMMapProc(); - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; ClockLatency getClockLatency() override; snd_pcm_t *mPcmHandle{nullptr}; - al::vector mBuffer; + std::mutex mMutex; + + uint mFrameStep{}; + al::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "AlsaPlayback::"; } DEF_NEWDEL(AlsaPlayback) }; @@ -422,25 +456,26 @@ int AlsaPlayback::mixerProc() althrd_setname(MIXER_THREAD_NAME); const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; - const snd_pcm_uframes_t num_updates{mDevice->BufferSize / update_size}; + const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; while(!mKillNow.load(std::memory_order_acquire)) { int state{verify_state(mPcmHandle)}; if(state < 0) { ERR("Invalid state detected: %s\n", snd_strerror(state)); - aluHandleDisconnect(mDevice, "Bad state: %s", snd_strerror(state)); + mDevice->handleDisconnect("Bad state: %s", snd_strerror(state)); break; } - snd_pcm_sframes_t avail{snd_pcm_avail_update(mPcmHandle)}; - if(avail < 0) + snd_pcm_sframes_t avails{snd_pcm_avail_update(mPcmHandle)}; + if(avails < 0) { - ERR("available update failed: %s\n", snd_strerror(avail)); + ERR("available update failed: %s\n", snd_strerror(static_cast(avails))); continue; } + snd_pcm_uframes_t avail{static_cast(avails)}; - if(static_cast(avail) > update_size*(num_updates+1)) + if(avail > buffer_size) { WARN("available samples exceeds the buffer size\n"); snd_pcm_reset(mPcmHandle); @@ -448,7 +483,7 @@ int AlsaPlayback::mixerProc() } // make sure there's frames to process - if(static_cast(avail) < update_size) + if(avail < update_size) { if(state != SND_PCM_STATE_RUNNING) { @@ -466,10 +501,10 @@ int AlsaPlayback::mixerProc() avail -= avail%update_size; // it is possible that contiguous areas are smaller, thus we use a loop - lock(); + std::lock_guard _{mMutex}; while(avail > 0) { - snd_pcm_uframes_t frames{static_cast(avail)}; + snd_pcm_uframes_t frames{avail}; const snd_pcm_channel_area_t *areas{}; snd_pcm_uframes_t offset{}; @@ -481,19 +516,18 @@ int AlsaPlayback::mixerProc() } char *WritePtr{static_cast(areas->addr) + (offset * areas->step / 8)}; - aluMixData(mDevice, WritePtr, frames); + mDevice->renderSamples(WritePtr, static_cast(frames), mFrameStep); snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)}; - if(commitres < 0 || (commitres-frames) != 0) + if(commitres < 0 || static_cast(commitres) != frames) { ERR("mmap commit error: %s\n", - snd_strerror(commitres >= 0 ? -EPIPE : commitres)); + snd_strerror(commitres >= 0 ? -EPIPE : static_cast(commitres))); break; } avail -= frames; } - unlock(); } return 0; @@ -512,14 +546,14 @@ int AlsaPlayback::mixerNoMMapProc() if(state < 0) { ERR("Invalid state detected: %s\n", snd_strerror(state)); - aluHandleDisconnect(mDevice, "Bad state: %s", snd_strerror(state)); + mDevice->handleDisconnect("Bad state: %s", snd_strerror(state)); break; } snd_pcm_sframes_t avail{snd_pcm_avail_update(mPcmHandle)}; if(avail < 0) { - ERR("available update failed: %s\n", snd_strerror(avail)); + ERR("available update failed: %s\n", snd_strerror(static_cast(avail))); continue; } @@ -546,13 +580,14 @@ int AlsaPlayback::mixerNoMMapProc() continue; } - lock(); - char *WritePtr{mBuffer.data()}; - avail = snd_pcm_bytes_to_frames(mPcmHandle, mBuffer.size()); - aluMixData(mDevice, WritePtr, avail); + al::byte *WritePtr{mBuffer.data()}; + avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast(mBuffer.size())); + std::lock_guard _{mMutex}; + mDevice->renderSamples(WritePtr, static_cast(avail), mFrameStep); while(avail > 0) { - snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr, avail)}; + snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr, + static_cast(avail))}; switch(ret) { case -EAGAIN: @@ -562,7 +597,7 @@ int AlsaPlayback::mixerNoMMapProc() #endif case -EPIPE: case -EINTR: - ret = snd_pcm_recover(mPcmHandle, ret, 1); + ret = snd_pcm_recover(mPcmHandle, static_cast(ret), 1); if(ret < 0) avail = 0; break; @@ -580,103 +615,101 @@ int AlsaPlayback::mixerNoMMapProc() if(ret < 0) break; } } - unlock(); } return 0; } -ALCenum AlsaPlayback::open(const ALCchar *name) +void AlsaPlayback::open(const char *name) { - const char *driver{}; + al::optional driveropt; + const char *driver{"default"}; if(name) { if(PlaybackDevices.empty()) PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); + [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; driver = iter->device_name.c_str(); } else { name = alsaDevice; - driver = GetConfigValue(nullptr, "alsa", "device", "default"); + if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "device")}) + driver = driveropt->c_str(); } - TRACE("Opening device \"%s\"\n", driver); - int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)}; + + snd_pcm_t *pcmHandle{}; + int err{snd_pcm_open(&pcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)}; if(err < 0) - { - ERR("Could not open playback device '%s': %s\n", driver, snd_strerror(err)); - return ALC_OUT_OF_MEMORY; - } + throw al::backend_exception{al::backend_error::NoDevice, + "Could not open ALSA device \"%s\"", driver}; + if(mPcmHandle) + snd_pcm_close(mPcmHandle); + mPcmHandle = pcmHandle; /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ snd_config_update_free_global(); mDevice->DeviceName = name; - - return ALC_NO_ERROR; } -ALCboolean AlsaPlayback::reset() +bool AlsaPlayback::reset() { snd_pcm_format_t format{SND_PCM_FORMAT_UNKNOWN}; switch(mDevice->FmtType) { - case DevFmtByte: - format = SND_PCM_FORMAT_S8; - break; - case DevFmtUByte: - format = SND_PCM_FORMAT_U8; - break; - case DevFmtShort: - format = SND_PCM_FORMAT_S16; - break; - case DevFmtUShort: - format = SND_PCM_FORMAT_U16; - break; - case DevFmtInt: - format = SND_PCM_FORMAT_S32; - break; - case DevFmtUInt: - format = SND_PCM_FORMAT_U32; - break; - case DevFmtFloat: - format = SND_PCM_FORMAT_FLOAT; - break; + case DevFmtByte: + format = SND_PCM_FORMAT_S8; + break; + case DevFmtUByte: + format = SND_PCM_FORMAT_U8; + break; + case DevFmtShort: + format = SND_PCM_FORMAT_S16; + break; + case DevFmtUShort: + format = SND_PCM_FORMAT_U16; + break; + case DevFmtInt: + format = SND_PCM_FORMAT_S32; + break; + case DevFmtUInt: + format = SND_PCM_FORMAT_U32; + break; + case DevFmtFloat: + format = SND_PCM_FORMAT_FLOAT; + break; } bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", 1)}; - ALuint periodLen{static_cast(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)}; - ALuint bufferLen{static_cast(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)}; - ALuint rate{mDevice->Frequency}; + uint periodLen{static_cast(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)}; + uint bufferLen{static_cast(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)}; + uint rate{mDevice->Frequency}; - snd_pcm_uframes_t periodSizeInFrames{}; - snd_pcm_uframes_t bufferSizeInFrames{}; - snd_pcm_sw_params_t *sp{}; - snd_pcm_hw_params_t *hp{}; - snd_pcm_access_t access{}; - const char *funcerr{}; int err{}; - - snd_pcm_hw_params_malloc(&hp); -#define CHECK(x) if((funcerr=#x),(err=(x)) < 0) goto error - CHECK(snd_pcm_hw_params_any(mPcmHandle, hp)); + HwParamsPtr hp{CreateHwParams()}; +#define CHECK(x) do { \ + if((err=(x)) < 0) \ + throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ + snd_strerror(err)}; \ +} while(0) + CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get())); /* set interleaved access */ - if(!allowmmap || snd_pcm_hw_params_set_access(mPcmHandle, hp, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) + if(!allowmmap + || snd_pcm_hw_params_set_access(mPcmHandle, hp.get(), SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) { /* No mmap */ - CHECK(snd_pcm_hw_params_set_access(mPcmHandle, hp, SND_PCM_ACCESS_RW_INTERLEAVED)); + CHECK(snd_pcm_hw_params_set_access(mPcmHandle, hp.get(), SND_PCM_ACCESS_RW_INTERLEAVED)); } /* test and set format (implicitly sets sample bits) */ - if(snd_pcm_hw_params_test_format(mPcmHandle, hp, format) < 0) + if(snd_pcm_hw_params_test_format(mPcmHandle, hp.get(), format) < 0) { static const struct { snd_pcm_format_t format; @@ -694,138 +727,108 @@ ALCboolean AlsaPlayback::reset() for(const auto &fmt : formatlist) { format = fmt.format; - if(snd_pcm_hw_params_test_format(mPcmHandle, hp, format) >= 0) + if(snd_pcm_hw_params_test_format(mPcmHandle, hp.get(), format) >= 0) { mDevice->FmtType = fmt.fmttype; break; } } } - CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp, format)); - /* test and set channels (implicitly sets frame bits) */ - if(snd_pcm_hw_params_test_channels(mPcmHandle, hp, mDevice->channelsFromFmt()) < 0) + CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp.get(), format)); + /* set channels (implicitly sets frame bits) */ + if(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()) < 0) { - static const DevFmtChannels channellist[] = { - DevFmtStereo, - DevFmtQuad, - DevFmtX51, - DevFmtX71, - DevFmtMono, - }; - - for(const auto &chan : channellist) - { - if(snd_pcm_hw_params_test_channels(mPcmHandle, hp, ChannelsFromDevFmt(chan, 0)) >= 0) - { - mDevice->FmtChans = chan; - mDevice->mAmbiOrder = 0; - break; - } - } + uint numchans{2u}; + CHECK(snd_pcm_hw_params_set_channels_near(mPcmHandle, hp.get(), &numchans)); + if(numchans < 1) + throw al::backend_exception{al::backend_error::DeviceError, "Got 0 device channels"}; + if(numchans == 1) mDevice->FmtChans = DevFmtMono; + else mDevice->FmtChans = DevFmtStereo; } - CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp, mDevice->channelsFromFmt())); /* set rate (implicitly constrains period/buffer parameters) */ - if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", 0) || - !(mDevice->Flags&DEVICE_FREQUENCY_REQUEST)) + if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", 0) + || !mDevice->Flags.test(FrequencyRequest)) { - if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp, 0) < 0) - ERR("Failed to disable ALSA resampler\n"); + if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0) + WARN("Failed to disable ALSA resampler\n"); } - else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp, 1) < 0) - ERR("Failed to enable ALSA resampler\n"); - CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp, &rate, nullptr)); + else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 1) < 0) + WARN("Failed to enable ALSA resampler\n"); + CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr)); /* set period time (implicitly constrains period/buffer parameters) */ - if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp, &periodLen, nullptr)) < 0) + if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)) < 0) ERR("snd_pcm_hw_params_set_period_time_near failed: %s\n", snd_strerror(err)); /* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */ - if((err=snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp, &bufferLen, nullptr)) < 0) + if((err=snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)) < 0) ERR("snd_pcm_hw_params_set_buffer_time_near failed: %s\n", snd_strerror(err)); /* install and prepare hardware configuration */ - CHECK(snd_pcm_hw_params(mPcmHandle, hp)); + CHECK(snd_pcm_hw_params(mPcmHandle, hp.get())); /* retrieve configuration info */ - CHECK(snd_pcm_hw_params_get_access(hp, &access)); - CHECK(snd_pcm_hw_params_get_period_size(hp, &periodSizeInFrames, nullptr)); - CHECK(snd_pcm_hw_params_get_buffer_size(hp, &bufferSizeInFrames)); - snd_pcm_hw_params_free(hp); + snd_pcm_uframes_t periodSizeInFrames{}; + snd_pcm_uframes_t bufferSizeInFrames{}; + snd_pcm_access_t access{}; + + CHECK(snd_pcm_hw_params_get_access(hp.get(), &access)); + CHECK(snd_pcm_hw_params_get_period_size(hp.get(), &periodSizeInFrames, nullptr)); + CHECK(snd_pcm_hw_params_get_buffer_size(hp.get(), &bufferSizeInFrames)); + CHECK(snd_pcm_hw_params_get_channels(hp.get(), &mFrameStep)); hp = nullptr; - snd_pcm_sw_params_malloc(&sp); - CHECK(snd_pcm_sw_params_current(mPcmHandle, sp)); - CHECK(snd_pcm_sw_params_set_avail_min(mPcmHandle, sp, periodSizeInFrames)); - CHECK(snd_pcm_sw_params_set_stop_threshold(mPcmHandle, sp, bufferSizeInFrames)); - CHECK(snd_pcm_sw_params(mPcmHandle, sp)); + SwParamsPtr sp{CreateSwParams()}; + CHECK(snd_pcm_sw_params_current(mPcmHandle, sp.get())); + CHECK(snd_pcm_sw_params_set_avail_min(mPcmHandle, sp.get(), periodSizeInFrames)); + CHECK(snd_pcm_sw_params_set_stop_threshold(mPcmHandle, sp.get(), bufferSizeInFrames)); + CHECK(snd_pcm_sw_params(mPcmHandle, sp.get())); #undef CHECK - snd_pcm_sw_params_free(sp); sp = nullptr; - mDevice->BufferSize = bufferSizeInFrames; - mDevice->UpdateSize = periodSizeInFrames; + mDevice->BufferSize = static_cast(bufferSizeInFrames); + mDevice->UpdateSize = static_cast(periodSizeInFrames); mDevice->Frequency = rate; - SetDefaultChannelOrder(mDevice); - - return ALC_TRUE; + setDefaultChannelOrder(); -error: - ERR("%s failed: %s\n", funcerr, snd_strerror(err)); - if(hp) snd_pcm_hw_params_free(hp); - if(sp) snd_pcm_sw_params_free(sp); - return ALC_FALSE; + return true; } -ALCboolean AlsaPlayback::start() +void AlsaPlayback::start() { - snd_pcm_hw_params_t *hp{}; - snd_pcm_access_t access; - const char *funcerr; - int err; - - snd_pcm_hw_params_malloc(&hp); -#define CHECK(x) if((funcerr=#x),(err=(x)) < 0) goto error - CHECK(snd_pcm_hw_params_current(mPcmHandle, hp)); + int err{}; + snd_pcm_access_t access{}; + HwParamsPtr hp{CreateHwParams()}; +#define CHECK(x) do { \ + if((err=(x)) < 0) \ + throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ + snd_strerror(err)}; \ +} while(0) + CHECK(snd_pcm_hw_params_current(mPcmHandle, hp.get())); /* retrieve configuration info */ - CHECK(snd_pcm_hw_params_get_access(hp, &access)); -#undef CHECK - if(0) - { - error: - ERR("%s failed: %s\n", funcerr, snd_strerror(err)); - if(hp) snd_pcm_hw_params_free(hp); - return ALC_FALSE; - } - snd_pcm_hw_params_free(hp); + CHECK(snd_pcm_hw_params_get_access(hp.get(), &access)); hp = nullptr; int (AlsaPlayback::*thread_func)(){}; if(access == SND_PCM_ACCESS_RW_INTERLEAVED) { - mBuffer.resize(snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize)); + auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize); + mBuffer.resize(static_cast(datalen)); thread_func = &AlsaPlayback::mixerNoMMapProc; } else { - err = snd_pcm_prepare(mPcmHandle); - if(err < 0) - { - ERR("snd_pcm_prepare(data->mPcmHandle) failed: %s\n", snd_strerror(err)); - return ALC_FALSE; - } + CHECK(snd_pcm_prepare(mPcmHandle)); thread_func = &AlsaPlayback::mixerProc; } +#undef CHECK try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(thread_func), this}; - return ALC_TRUE; } catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - catch(...) { - } - mBuffer.clear(); - return ALC_FALSE; } void AlsaPlayback::stop() @@ -835,13 +838,16 @@ void AlsaPlayback::stop() mThread.join(); mBuffer.clear(); + int err{snd_pcm_drop(mPcmHandle)}; + if(err < 0) + ERR("snd_pcm_drop failed: %s\n", snd_strerror(err)); } ClockLatency AlsaPlayback::getClockLatency() { ClockLatency ret; - lock(); + std::lock_guard _{mMutex}; ret.ClockTime = GetDeviceClockTime(mDevice); snd_pcm_sframes_t delay{}; int err{snd_pcm_delay(mPcmHandle, &delay)}; @@ -852,33 +858,31 @@ ClockLatency AlsaPlayback::getClockLatency() } ret.Latency = std::chrono::seconds{std::max(0, delay)}; ret.Latency /= mDevice->Frequency; - unlock(); return ret; } struct AlsaCapture final : public BackendBase { - AlsaCapture(ALCdevice *device) noexcept : BackendBase{device} { } + AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~AlsaCapture() override; - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; ClockLatency getClockLatency() override; snd_pcm_t *mPcmHandle{nullptr}; - al::vector mBuffer; + al::vector mBuffer; bool mDoCapture{false}; RingBufferPtr mRing{nullptr}; snd_pcm_sframes_t mLastAvail{0}; - static constexpr inline const char *CurrentPrefix() noexcept { return "AlsaCapture::"; } DEF_NEWDEL(AlsaCapture) }; @@ -890,35 +894,34 @@ AlsaCapture::~AlsaCapture() } -ALCenum AlsaCapture::open(const ALCchar *name) +void AlsaCapture::open(const char *name) { - const char *driver{}; + al::optional driveropt; + const char *driver{"default"}; if(name) { if(CaptureDevices.empty()) CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); + [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; driver = iter->device_name.c_str(); } else { name = alsaDevice; - driver = GetConfigValue(nullptr, "alsa", "capture", "default"); + if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "capture")}) + driver = driveropt->c_str(); } TRACE("Opening device \"%s\"\n", driver); int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; if(err < 0) - { - ERR("Could not open capture device '%s': %s\n", driver, snd_strerror(err)); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::NoDevice, + "Could not open ALSA device \"%s\"", driver}; /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ snd_config_update_free_global(); @@ -926,109 +929,84 @@ ALCenum AlsaCapture::open(const ALCchar *name) snd_pcm_format_t format{SND_PCM_FORMAT_UNKNOWN}; switch(mDevice->FmtType) { - case DevFmtByte: - format = SND_PCM_FORMAT_S8; - break; - case DevFmtUByte: - format = SND_PCM_FORMAT_U8; - break; - case DevFmtShort: - format = SND_PCM_FORMAT_S16; - break; - case DevFmtUShort: - format = SND_PCM_FORMAT_U16; - break; - case DevFmtInt: - format = SND_PCM_FORMAT_S32; - break; - case DevFmtUInt: - format = SND_PCM_FORMAT_U32; - break; - case DevFmtFloat: - format = SND_PCM_FORMAT_FLOAT; - break; + case DevFmtByte: + format = SND_PCM_FORMAT_S8; + break; + case DevFmtUByte: + format = SND_PCM_FORMAT_U8; + break; + case DevFmtShort: + format = SND_PCM_FORMAT_S16; + break; + case DevFmtUShort: + format = SND_PCM_FORMAT_U16; + break; + case DevFmtInt: + format = SND_PCM_FORMAT_S32; + break; + case DevFmtUInt: + format = SND_PCM_FORMAT_U32; + break; + case DevFmtFloat: + format = SND_PCM_FORMAT_FLOAT; + break; } snd_pcm_uframes_t bufferSizeInFrames{maxu(mDevice->BufferSize, 100*mDevice->Frequency/1000)}; - snd_pcm_uframes_t periodSizeInFrames{minu(bufferSizeInFrames, 25*mDevice->Frequency/1000)}; + snd_pcm_uframes_t periodSizeInFrames{minu(mDevice->BufferSize, 25*mDevice->Frequency/1000)}; bool needring{false}; - const char *funcerr{}; - snd_pcm_hw_params_t *hp{}; - snd_pcm_hw_params_malloc(&hp); -#define CHECK(x) if((funcerr=#x),(err=(x)) < 0) goto error - CHECK(snd_pcm_hw_params_any(mPcmHandle, hp)); + HwParamsPtr hp{CreateHwParams()}; +#define CHECK(x) do { \ + if((err=(x)) < 0) \ + throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ + snd_strerror(err)}; \ +} while(0) + CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get())); /* set interleaved access */ - CHECK(snd_pcm_hw_params_set_access(mPcmHandle, hp, SND_PCM_ACCESS_RW_INTERLEAVED)); + CHECK(snd_pcm_hw_params_set_access(mPcmHandle, hp.get(), SND_PCM_ACCESS_RW_INTERLEAVED)); /* set format (implicitly sets sample bits) */ - CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp, format)); + CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp.get(), format)); /* set channels (implicitly sets frame bits) */ - CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp, mDevice->channelsFromFmt())); + 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, mDevice->Frequency, 0)); + CHECK(snd_pcm_hw_params_set_rate(mPcmHandle, hp.get(), mDevice->Frequency, 0)); /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */ - if(snd_pcm_hw_params_set_buffer_size_min(mPcmHandle, hp, &bufferSizeInFrames) < 0) + if(snd_pcm_hw_params_set_buffer_size_min(mPcmHandle, hp.get(), &bufferSizeInFrames) < 0) { TRACE("Buffer too large, using intermediate ring buffer\n"); needring = true; - CHECK(snd_pcm_hw_params_set_buffer_size_near(mPcmHandle, hp, &bufferSizeInFrames)); + CHECK(snd_pcm_hw_params_set_buffer_size_near(mPcmHandle, hp.get(), &bufferSizeInFrames)); } /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */ - CHECK(snd_pcm_hw_params_set_period_size_near(mPcmHandle, hp, &periodSizeInFrames, nullptr)); + CHECK(snd_pcm_hw_params_set_period_size_near(mPcmHandle, hp.get(), &periodSizeInFrames, nullptr)); /* install and prepare hardware configuration */ - CHECK(snd_pcm_hw_params(mPcmHandle, hp)); + CHECK(snd_pcm_hw_params(mPcmHandle, hp.get())); /* retrieve configuration info */ - CHECK(snd_pcm_hw_params_get_period_size(hp, &periodSizeInFrames, nullptr)); + CHECK(snd_pcm_hw_params_get_period_size(hp.get(), &periodSizeInFrames, nullptr)); #undef CHECK - snd_pcm_hw_params_free(hp); hp = nullptr; if(needring) - { - mRing = CreateRingBuffer(mDevice->BufferSize, mDevice->frameSizeFromFmt(), false); - if(!mRing) - { - ERR("ring buffer create failed\n"); - goto error2; - } - } + mRing = RingBuffer::Create(mDevice->BufferSize, mDevice->frameSizeFromFmt(), false); mDevice->DeviceName = name; - - return ALC_NO_ERROR; - -error: - ERR("%s failed: %s\n", funcerr, snd_strerror(err)); - if(hp) snd_pcm_hw_params_free(hp); - -error2: - mRing = nullptr; - snd_pcm_close(mPcmHandle); - mPcmHandle = nullptr; - - return ALC_INVALID_VALUE; } -ALCboolean AlsaCapture::start() +void AlsaCapture::start() { int err{snd_pcm_prepare(mPcmHandle)}; if(err < 0) - ERR("prepare failed: %s\n", snd_strerror(err)); - else - { - err = snd_pcm_start(mPcmHandle); - if(err < 0) - ERR("start failed: %s\n", snd_strerror(err)); - } + throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s", + snd_strerror(err)}; + + err = snd_pcm_start(mPcmHandle); if(err < 0) - { - aluHandleDisconnect(mDevice, "Capture state failure: %s", snd_strerror(err)); - return ALC_FALSE; - } + throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s", + snd_strerror(err)}; mDoCapture = true; - return ALC_TRUE; } void AlsaCapture::stop() @@ -1037,12 +1015,14 @@ void AlsaCapture::stop() * snd_pcm_drain is unreliable and snd_pcm_drop drops it. Capture what's * available now so it'll be available later after the drop. */ - ALCuint avail{availableSamples()}; + uint avail{availableSamples()}; if(!mRing && avail > 0) { /* The ring buffer implicitly captures when checking availability. - * Direct access needs to explicitly capture it into temp storage. */ - al::vector temp(snd_pcm_frames_to_bytes(mPcmHandle, avail)); + * Direct access needs to explicitly capture it into temp storage. + */ + auto temp = al::vector( + static_cast(snd_pcm_frames_to_bytes(mPcmHandle, avail))); captureSamples(temp.data(), avail); mBuffer = std::move(temp); } @@ -1052,12 +1032,12 @@ void AlsaCapture::stop() mDoCapture = false; } -ALCenum AlsaCapture::captureSamples(ALCvoid *buffer, ALCuint samples) +void AlsaCapture::captureSamples(al::byte *buffer, uint samples) { if(mRing) { mRing->read(buffer, samples); - return ALC_NO_ERROR; + return; } mLastAvail -= samples; @@ -1068,11 +1048,11 @@ ALCenum AlsaCapture::captureSamples(ALCvoid *buffer, ALCuint samples) if(!mBuffer.empty()) { /* First get any data stored from the last stop */ - amt = snd_pcm_bytes_to_frames(mPcmHandle, mBuffer.size()); + amt = snd_pcm_bytes_to_frames(mPcmHandle, static_cast(mBuffer.size())); if(static_cast(amt) > samples) amt = samples; amt = snd_pcm_frames_to_bytes(mPcmHandle, amt); - memcpy(buffer, mBuffer.data(), amt); + std::copy_n(mBuffer.begin(), amt, buffer); mBuffer.erase(mBuffer.begin(), mBuffer.begin()+amt); amt = snd_pcm_bytes_to_frames(mPcmHandle, amt); @@ -1081,11 +1061,11 @@ ALCenum AlsaCapture::captureSamples(ALCvoid *buffer, ALCuint samples) amt = snd_pcm_readi(mPcmHandle, buffer, samples); if(amt < 0) { - ERR("read error: %s\n", snd_strerror(amt)); + ERR("read error: %s\n", snd_strerror(static_cast(amt))); if(amt == -EAGAIN) continue; - if((amt=snd_pcm_recover(mPcmHandle, amt, 1)) >= 0) + if((amt=snd_pcm_recover(mPcmHandle, static_cast(amt), 1)) >= 0) { amt = snd_pcm_start(mPcmHandle); if(amt >= 0) @@ -1093,8 +1073,9 @@ ALCenum AlsaCapture::captureSamples(ALCvoid *buffer, ALCuint samples) } if(amt < 0) { - ERR("restore error: %s\n", snd_strerror(amt)); - aluHandleDisconnect(mDevice, "Capture recovery failure: %s", snd_strerror(amt)); + const char *err{snd_strerror(static_cast(amt))}; + ERR("restore error: %s\n", err); + mDevice->handleDisconnect("Capture recovery failure: %s", err); break; } /* If the amount available is less than what's asked, we lost it @@ -1104,26 +1085,24 @@ ALCenum AlsaCapture::captureSamples(ALCvoid *buffer, ALCuint samples) continue; } - buffer = static_cast(buffer) + amt; - samples -= amt; + buffer = buffer + amt; + samples -= static_cast(amt); } if(samples > 0) - memset(buffer, ((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0), - snd_pcm_frames_to_bytes(mPcmHandle, samples)); - - return ALC_NO_ERROR; + std::fill_n(buffer, snd_pcm_frames_to_bytes(mPcmHandle, samples), + al::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0)); } -ALCuint AlsaCapture::availableSamples() +uint AlsaCapture::availableSamples() { snd_pcm_sframes_t avail{0}; if(mDevice->Connected.load(std::memory_order_acquire) && mDoCapture) avail = snd_pcm_avail_update(mPcmHandle); if(avail < 0) { - ERR("avail update failed: %s\n", snd_strerror(avail)); + ERR("avail update failed: %s\n", snd_strerror(static_cast(avail))); - if((avail=snd_pcm_recover(mPcmHandle, avail, 1)) >= 0) + if((avail=snd_pcm_recover(mPcmHandle, static_cast(avail), 1)) >= 0) { if(mDoCapture) avail = snd_pcm_start(mPcmHandle); @@ -1132,17 +1111,18 @@ ALCuint AlsaCapture::availableSamples() } if(avail < 0) { - ERR("restore error: %s\n", snd_strerror(avail)); - aluHandleDisconnect(mDevice, "Capture recovery failure: %s", snd_strerror(avail)); + const char *err{snd_strerror(static_cast(avail))}; + ERR("restore error: %s\n", err); + mDevice->handleDisconnect("Capture recovery failure: %s", err); } } if(!mRing) { if(avail < 0) avail = 0; - avail += snd_pcm_bytes_to_frames(mPcmHandle, mBuffer.size()); + avail += snd_pcm_bytes_to_frames(mPcmHandle, static_cast(mBuffer.size())); if(avail > mLastAvail) mLastAvail = avail; - return mLastAvail; + return static_cast(mLastAvail); } while(avail > 0) @@ -1150,15 +1130,15 @@ ALCuint AlsaCapture::availableSamples() auto vec = mRing->getWriteVector(); if(vec.first.len == 0) break; - snd_pcm_sframes_t amt{std::min(vec.first.len, avail)}; - amt = snd_pcm_readi(mPcmHandle, vec.first.buf, amt); + snd_pcm_sframes_t amt{std::min(static_cast(vec.first.len), avail)}; + amt = snd_pcm_readi(mPcmHandle, vec.first.buf, static_cast(amt)); if(amt < 0) { - ERR("read error: %s\n", snd_strerror(amt)); + ERR("read error: %s\n", snd_strerror(static_cast(amt))); if(amt == -EAGAIN) continue; - if((amt=snd_pcm_recover(mPcmHandle, amt, 1)) >= 0) + if((amt=snd_pcm_recover(mPcmHandle, static_cast(amt), 1)) >= 0) { if(mDoCapture) amt = snd_pcm_start(mPcmHandle); @@ -1167,26 +1147,26 @@ ALCuint AlsaCapture::availableSamples() } if(amt < 0) { - ERR("restore error: %s\n", snd_strerror(amt)); - aluHandleDisconnect(mDevice, "Capture recovery failure: %s", snd_strerror(amt)); + const char *err{snd_strerror(static_cast(amt))}; + ERR("restore error: %s\n", err); + mDevice->handleDisconnect("Capture recovery failure: %s", err); break; } avail = amt; continue; } - mRing->writeAdvance(amt); + mRing->writeAdvance(static_cast(amt)); avail -= amt; } - return mRing->readSpace(); + return static_cast(mRing->readSpace()); } ClockLatency AlsaCapture::getClockLatency() { ClockLatency ret; - lock(); ret.ClockTime = GetDeviceClockTime(mDevice); snd_pcm_sframes_t delay{}; int err{snd_pcm_delay(mPcmHandle, &delay)}; @@ -1197,7 +1177,6 @@ ClockLatency AlsaCapture::getClockLatency() } ret.Latency = std::chrono::seconds{std::max(0, delay)}; ret.Latency /= mDevice->Frequency; - unlock(); return ret; } @@ -1218,10 +1197,10 @@ bool AlsaBackendFactory::init() if(!alsa_handle) { WARN("Failed to load %s\n", "libasound.so.2"); - return ALC_FALSE; + return false; } - error = ALC_FALSE; + error = false; #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast(GetSymbol(alsa_handle, #f)); \ if(p##f == nullptr) { \ @@ -1247,30 +1226,34 @@ bool AlsaBackendFactory::init() bool AlsaBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -void AlsaBackendFactory::probe(DevProbe type, std::string *outnames) +std::string AlsaBackendFactory::probe(BackendType type) { - auto add_device = [outnames](const DevMap &entry) -> void + std::string outnames; + + auto add_device = [&outnames](const DevMap &entry) -> void { /* +1 to also append the null char (to ensure a null-separated list and * double-null terminated list). */ - outnames->append(entry.name.c_str(), entry.name.length()+1); + outnames.append(entry.name.c_str(), entry.name.length()+1); }; switch(type) { - case DevProbe::Playback: - PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; - - case DevProbe::Capture: - CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; + case BackendType::Playback: + PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); + std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); + break; + + case BackendType::Capture: + CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); + std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); + break; } + + return outnames; } -BackendPtr AlsaBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr AlsaBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new AlsaPlayback{device}}; diff --git a/modules/openal-soft/Alc/backends/alsa.h b/modules/openal-soft/Alc/backends/alsa.h index fb9de00..b256dcf 100644 --- a/modules/openal-soft/Alc/backends/alsa.h +++ b/modules/openal-soft/Alc/backends/alsa.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_ALSA_H #define BACKENDS_ALSA_H -#include "backends/base.h" +#include "base.h" struct AlsaBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/base.cpp b/modules/openal-soft/Alc/backends/base.cpp index 5239ae5..cd1b76b 100644 --- a/modules/openal-soft/Alc/backends/base.cpp +++ b/modules/openal-soft/Alc/backends/base.cpp @@ -1,58 +1,194 @@ #include "config.h" -#include +#include "base.h" -#include +#include +#include +#include -#include "alMain.h" -#include "alu.h" +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include -#include "backends/base.h" +#include "albit.h" +#include "core/logging.h" +#include "aloptional.h" +#endif +#include "atomic.h" +#include "core/devformat.h" -ClockLatency GetClockLatency(ALCdevice *device) -{ - BackendBase *backend{device->Backend.get()}; - ClockLatency ret{backend->getClockLatency()}; - ret.Latency += device->FixedLatency; - return ret; -} +bool BackendBase::reset() +{ throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; } -/* BackendBase method implementations. */ -BackendBase::BackendBase(ALCdevice *device) noexcept : mDevice{device} +void BackendBase::captureSamples(al::byte*, uint) { } -BackendBase::~BackendBase() = default; - -ALCboolean BackendBase::reset() -{ return ALC_FALSE; } - -ALCenum BackendBase::captureSamples(void* UNUSED(buffer), ALCuint UNUSED(samples)) -{ return ALC_INVALID_DEVICE; } - -ALCuint BackendBase::availableSamples() +uint BackendBase::availableSamples() { return 0; } ClockLatency BackendBase::getClockLatency() { ClockLatency ret; - ALuint refcount; + uint refcount; do { - while(((refcount=mDevice->MixCount.load(std::memory_order_acquire))&1)) - std::this_thread::yield(); + refcount = mDevice->waitForMix(); ret.ClockTime = GetDeviceClockTime(mDevice); std::atomic_thread_fence(std::memory_order_acquire); - } while(refcount != mDevice->MixCount.load(std::memory_order_relaxed)); + } while(refcount != ReadRef(mDevice->MixCount)); /* NOTE: The device will generally have about all but one periods filled at * any given time during playback. Without a more accurate measurement from * the output, this is an okay approximation. */ - ret.Latency = std::chrono::seconds{maxi(mDevice->BufferSize-mDevice->UpdateSize, 0)}; + ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize}, + std::chrono::seconds::zero()); ret.Latency /= mDevice->Frequency; return ret; } + +void BackendBase::setDefaultWFXChannelOrder() +{ + mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); + + switch(mDevice->FmtChans) + { + case DevFmtMono: + mDevice->RealOut.ChannelIndex[FrontCenter] = 0; + break; + case DevFmtStereo: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + break; + case DevFmtQuad: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[BackLeft] = 2; + mDevice->RealOut.ChannelIndex[BackRight] = 3; + break; + case DevFmtX51: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[FrontCenter] = 2; + mDevice->RealOut.ChannelIndex[LFE] = 3; + mDevice->RealOut.ChannelIndex[SideLeft] = 4; + mDevice->RealOut.ChannelIndex[SideRight] = 5; + break; + case DevFmtX61: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[FrontCenter] = 2; + mDevice->RealOut.ChannelIndex[LFE] = 3; + mDevice->RealOut.ChannelIndex[BackCenter] = 4; + mDevice->RealOut.ChannelIndex[SideLeft] = 5; + mDevice->RealOut.ChannelIndex[SideRight] = 6; + break; + case DevFmtX71: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[FrontCenter] = 2; + mDevice->RealOut.ChannelIndex[LFE] = 3; + mDevice->RealOut.ChannelIndex[BackLeft] = 4; + mDevice->RealOut.ChannelIndex[BackRight] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + break; + case DevFmtAmbi3D: + break; + } +} + +void BackendBase::setDefaultChannelOrder() +{ + mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); + + switch(mDevice->FmtChans) + { + case DevFmtX51: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[SideLeft] = 2; + mDevice->RealOut.ChannelIndex[SideRight] = 3; + mDevice->RealOut.ChannelIndex[FrontCenter] = 4; + mDevice->RealOut.ChannelIndex[LFE] = 5; + return; + case DevFmtX71: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[BackLeft] = 2; + mDevice->RealOut.ChannelIndex[BackRight] = 3; + mDevice->RealOut.ChannelIndex[FrontCenter] = 4; + mDevice->RealOut.ChannelIndex[LFE] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + return; + + /* Same as WFX order */ + case DevFmtMono: + case DevFmtStereo: + case DevFmtQuad: + case DevFmtX61: + case DevFmtAmbi3D: + setDefaultWFXChannelOrder(); + break; + } +} + +#ifdef _WIN32 +void BackendBase::setChannelOrderFromWFXMask(uint chanmask) +{ + static constexpr uint x51{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER + | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT}; + static constexpr uint x51rear{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER + | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT}; + /* Swap a 5.1 mask using the back channels for one with the sides. */ + if(chanmask == x51rear) chanmask = x51; + + auto get_channel = [](const DWORD chanbit) noexcept -> al::optional + { + switch(chanbit) + { + case SPEAKER_FRONT_LEFT: return al::make_optional(FrontLeft); + case SPEAKER_FRONT_RIGHT: return al::make_optional(FrontRight); + case SPEAKER_FRONT_CENTER: return al::make_optional(FrontCenter); + case SPEAKER_LOW_FREQUENCY: return al::make_optional(LFE); + case SPEAKER_BACK_LEFT: return al::make_optional(BackLeft); + case SPEAKER_BACK_RIGHT: return al::make_optional(BackRight); + case SPEAKER_FRONT_LEFT_OF_CENTER: break; + case SPEAKER_FRONT_RIGHT_OF_CENTER: break; + case SPEAKER_BACK_CENTER: return al::make_optional(BackCenter); + case SPEAKER_SIDE_LEFT: return al::make_optional(SideLeft); + case SPEAKER_SIDE_RIGHT: return al::make_optional(SideRight); + case SPEAKER_TOP_CENTER: return al::make_optional(TopCenter); + case SPEAKER_TOP_FRONT_LEFT: return al::make_optional(TopFrontLeft); + case SPEAKER_TOP_FRONT_CENTER: return al::make_optional(TopFrontCenter); + case SPEAKER_TOP_FRONT_RIGHT: return al::make_optional(TopFrontRight); + case SPEAKER_TOP_BACK_LEFT: return al::make_optional(TopBackLeft); + case SPEAKER_TOP_BACK_CENTER: return al::make_optional(TopBackCenter); + case SPEAKER_TOP_BACK_RIGHT: return al::make_optional(TopBackRight); + } + WARN("Unhandled WFX channel bit 0x%lx\n", chanbit); + return al::nullopt; + }; + + const uint numchans{mDevice->channelsFromFmt()}; + uint idx{0}; + while(chanmask) + { + const int bit{al::countr_zero(chanmask)}; + const uint mask{1u << bit}; + chanmask &= ~mask; + + if(auto label = get_channel(mask)) + { + mDevice->RealOut.ChannelIndex[*label] = idx; + if(++idx == numchans) break; + } + } +} +#endif diff --git a/modules/openal-soft/Alc/backends/base.h b/modules/openal-soft/Alc/backends/base.h index 7a9232a..a3562f5 100644 --- a/modules/openal-soft/Alc/backends/base.h +++ b/modules/openal-soft/Alc/backends/base.h @@ -1,68 +1,81 @@ #ifndef ALC_BACKENDS_BASE_H #define ALC_BACKENDS_BASE_H -#include #include +#include +#include +#include #include -#include -#include "alMain.h" +#include "albyte.h" +#include "core/device.h" +#include "core/except.h" + +using uint = unsigned int; struct ClockLatency { std::chrono::nanoseconds ClockTime; std::chrono::nanoseconds Latency; }; -/* Helper to get the current clock time from the device's ClockBase, and - * SamplesDone converted from the sample rate. - */ -inline std::chrono::nanoseconds GetDeviceClockTime(ALCdevice *device) -{ - using std::chrono::seconds; - using std::chrono::nanoseconds; - - auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency; - return device->ClockBase + ns; -} - -ClockLatency GetClockLatency(ALCdevice *device); - struct BackendBase { - virtual ALCenum open(const ALCchar *name) = 0; + virtual void open(const char *name) = 0; - virtual ALCboolean reset(); - virtual ALCboolean start() = 0; + virtual bool reset(); + virtual void start() = 0; virtual void stop() = 0; - virtual ALCenum captureSamples(void *buffer, ALCuint samples); - virtual ALCuint availableSamples(); + virtual void captureSamples(al::byte *buffer, uint samples); + virtual uint availableSamples(); virtual ClockLatency getClockLatency(); - virtual void lock() { mMutex.lock(); } - virtual void unlock() { mMutex.unlock(); } + DeviceBase *const mDevice; - ALCdevice *mDevice; + BackendBase(DeviceBase *device) noexcept : mDevice{device} { } + virtual ~BackendBase() = default; - std::recursive_mutex mMutex; +protected: + /** Sets the default channel order used by most non-WaveFormatEx-based APIs. */ + void setDefaultChannelOrder(); + /** Sets the default channel order used by WaveFormatEx. */ + void setDefaultWFXChannelOrder(); - BackendBase(ALCdevice *device) noexcept; - virtual ~BackendBase(); +#ifdef _WIN32 + /** Sets the channel order given the WaveFormatEx mask. */ + void setChannelOrderFromWFXMask(uint chanmask); +#endif }; using BackendPtr = std::unique_ptr; -using BackendUniqueLock = std::unique_lock; -using BackendLockGuard = std::lock_guard; enum class BackendType { Playback, Capture }; -enum class DevProbe { - Playback, - Capture -}; + +/* Helper to get the current clock time from the device's ClockBase, and + * SamplesDone converted from the sample rate. + */ +inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device) +{ + using std::chrono::seconds; + using std::chrono::nanoseconds; + + auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency; + return device->ClockBase + ns; +} + +/* Helper to get the device latency from the backend, including any fixed + * latency from post-processing. + */ +inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend) +{ + ClockLatency ret{backend->getClockLatency()}; + ret.Latency += device->FixedLatency; + return ret; +} struct BackendFactory { @@ -70,9 +83,41 @@ struct BackendFactory { virtual bool querySupport(BackendType type) = 0; - virtual void probe(DevProbe type, std::string *outnames) = 0; + virtual std::string probe(BackendType type) = 0; + + virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0; + +protected: + virtual ~BackendFactory() = default; +}; + +namespace al { - virtual BackendPtr createBackend(ALCdevice *device, BackendType type) = 0; +enum class backend_error { + NoDevice, + DeviceError, + OutOfMemory }; +class backend_exception final : public base_exception { + backend_error mErrorCode; + +public: +#ifdef __USE_MINGW_ANSI_STDIO + [[gnu::format(gnu_printf, 3, 4)]] +#else + [[gnu::format(printf, 3, 4)]] +#endif + backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code} + { + std::va_list args; + va_start(args, msg); + setMessage(msg, args); + va_end(args); + } + backend_error errorCode() const noexcept { return mErrorCode; } +}; + +} // namespace al + #endif /* ALC_BACKENDS_BASE_H */ diff --git a/modules/openal-soft/Alc/backends/coreaudio.cpp b/modules/openal-soft/Alc/backends/coreaudio.cpp index d8b4500..ed85e2a 100644 --- a/modules/openal-soft/Alc/backends/coreaudio.cpp +++ b/modules/openal-soft/Alc/backends/coreaudio.cpp @@ -20,50 +20,262 @@ #include "config.h" -#include "backends/coreaudio.h" +#include "coreaudio.h" +#include +#include #include #include #include +#include + +#include +#include +#include -#include "alMain.h" -#include "alu.h" +#include "alnumeric.h" +#include "core/converter.h" +#include "core/device.h" +#include "core/logging.h" #include "ringbuffer.h" -#include "converter.h" -#include "backends/base.h" -#include #include #include namespace { -static const ALCchar ca_device[] = "CoreAudio Default"; +#if TARGET_OS_IOS || TARGET_OS_TV +#define CAN_ENUMERATE 0 +#else +#define CAN_ENUMERATE 1 +#endif + + +#if CAN_ENUMERATE +struct DeviceEntry { + AudioDeviceID mId; + std::string mName; +}; + +std::vector PlaybackList; +std::vector CaptureList; + + +OSStatus GetHwProperty(AudioHardwarePropertyID propId, UInt32 dataSize, void *propData) +{ + const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster}; + return AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, nullptr, &dataSize, + propData); +} + +OSStatus GetHwPropertySize(AudioHardwarePropertyID propId, UInt32 *outSize) +{ + const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster}; + return AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, nullptr, outSize); +} + +OSStatus GetDevProperty(AudioDeviceID devId, AudioDevicePropertyID propId, bool isCapture, + UInt32 elem, UInt32 dataSize, void *propData) +{ + static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput, + kAudioDevicePropertyScopeInput}; + const AudioObjectPropertyAddress addr{propId, scopes[isCapture], elem}; + return AudioObjectGetPropertyData(devId, &addr, 0, nullptr, &dataSize, propData); +} + +OSStatus GetDevPropertySize(AudioDeviceID devId, AudioDevicePropertyID inPropertyID, + bool isCapture, UInt32 elem, UInt32 *outSize) +{ + static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput, + kAudioDevicePropertyScopeInput}; + const AudioObjectPropertyAddress addr{inPropertyID, scopes[isCapture], elem}; + return AudioObjectGetPropertyDataSize(devId, &addr, 0, nullptr, outSize); +} + + +std::string GetDeviceName(AudioDeviceID devId) +{ + std::string devname; + CFStringRef nameRef; + + /* Try to get the device name as a CFString, for Unicode name support. */ + OSStatus err{GetDevProperty(devId, kAudioDevicePropertyDeviceNameCFString, false, 0, + sizeof(nameRef), &nameRef)}; + if(err == noErr) + { + const CFIndex propSize{CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef), + kCFStringEncodingUTF8)}; + devname.resize(static_cast(propSize)+1, '\0'); + + CFStringGetCString(nameRef, &devname[0], propSize+1, kCFStringEncodingUTF8); + CFRelease(nameRef); + } + else + { + /* If that failed, just get the C string. Hopefully there's nothing bad + * with this. + */ + UInt32 propSize{}; + if(GetDevPropertySize(devId, kAudioDevicePropertyDeviceName, false, 0, &propSize)) + return devname; + + devname.resize(propSize+1, '\0'); + if(GetDevProperty(devId, kAudioDevicePropertyDeviceName, false, 0, propSize, &devname[0])) + { + devname.clear(); + return devname; + } + } + + /* Clear extraneous nul chars that may have been written with the name + * string, and return it. + */ + while(!devname.back()) + devname.pop_back(); + return devname; +} + +UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) +{ + UInt32 propSize{}; + auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, + &propSize); + if(err) + { + ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err); + return 0; + } + + auto buflist_data = std::make_unique(propSize); + auto *buflist = reinterpret_cast(buflist_data.get()); + + err = GetDevProperty(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, propSize, + buflist); + if(err) + { + ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err); + return 0; + } + + UInt32 numChannels{0}; + for(size_t i{0};i < buflist->mNumberBuffers;++i) + numChannels += buflist->mBuffers[i].mNumberChannels; + + return numChannels; +} + + +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); + return; + } + + auto devIds = std::vector(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown); + if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data())) + { + ERR("Failed to get device list: %u\n", err); + return; + } + + std::vector newdevs; + newdevs.reserve(devIds.size()); + + AudioDeviceID defaultId{kAudioDeviceUnknown}; + GetHwProperty(isCapture ? kAudioHardwarePropertyDefaultInputDevice : + kAudioHardwarePropertyDefaultOutputDevice, sizeof(defaultId), &defaultId); + + if(defaultId != kAudioDeviceUnknown) + { + 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); + } + for(const AudioDeviceID devId : devIds) + { + if(devId == kAudioDeviceUnknown) + continue; + + auto match_devid = [devId](const DeviceEntry &entry) noexcept -> bool + { return entry.mId == devId; }; + auto match = std::find_if(newdevs.cbegin(), newdevs.cend(), match_devid); + if(match != newdevs.cend()) continue; + + auto numChannels = GetDeviceChannelCount(devId, isCapture); + if(numChannels > 0) + { + 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); + } + } + + if(newdevs.size() > 1) + { + /* Rename entries that have matching names, by appending '#2', '#3', + * etc, as needed. + */ + for(auto curitem = newdevs.begin()+1;curitem != newdevs.end();++curitem) + { + auto check_match = [curitem](const DeviceEntry &entry) -> bool + { 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 check_name = [&name](const DeviceEntry &entry) -> bool + { return entry.mName == name; }; + do { + name = curitem->mName; + name += " #"; + name += std::to_string(++count); + } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem); + curitem->mName = std::move(name); + } + } + } + + newdevs.shrink_to_fit(); + newdevs.swap(list); +} + +#else + +static constexpr char ca_device[] = "CoreAudio Default"; +#endif struct CoreAudioPlayback final : public BackendBase { - CoreAudioPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~CoreAudioPlayback() override; - static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData); OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData); + AudioBufferList *ioData) noexcept; + static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, + AudioBufferList *ioData) noexcept + { + return static_cast(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, + inBusNumber, inNumberFrames, ioData); + } - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; - AudioUnit mAudioUnit; + AudioUnit mAudioUnit{}; - ALuint mFrameSize{0u}; + uint mFrameSize{0u}; AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD - static constexpr inline const char *CurrentPrefix() noexcept { return "CoreAudioPlayback::"; } DEF_NEWDEL(CoreAudioPlayback) }; @@ -74,39 +286,56 @@ CoreAudioPlayback::~CoreAudioPlayback() } -OSStatus CoreAudioPlayback::MixerProcC(void *inRefCon, - AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, - UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) +OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32, + UInt32, AudioBufferList *ioData) noexcept { - return static_cast(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, - inBusNumber, inNumberFrames, ioData); -} - -OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags* UNUSED(ioActionFlags), - const AudioTimeStamp* UNUSED(inTimeStamp), UInt32 UNUSED(inBusNumber), - UInt32 UNUSED(inNumberFrames), AudioBufferList *ioData) -{ - lock(); - aluMixData(mDevice, ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize/mFrameSize); - unlock(); + for(size_t i{0};i < ioData->mNumberBuffers;++i) + { + auto &buffer = ioData->mBuffers[i]; + mDevice->renderSamples(buffer.mData, buffer.mDataByteSize/mFrameSize, + buffer.mNumberChannels); + } return noErr; } -ALCenum CoreAudioPlayback::open(const ALCchar *name) +void CoreAudioPlayback::open(const char *name) { +#if CAN_ENUMERATE + AudioDeviceID audioDevice{kAudioDeviceUnknown}; + if(!name) + GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice), + &audioDevice); + else + { + if(PlaybackList.empty()) + EnumerateDevices(PlaybackList, false); + + auto find_name = [name](const DeviceEntry &entry) -> bool + { return entry.mName == name; }; + auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name); + if(devmatch == PlaybackList.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + + audioDevice = devmatch->mId; + } +#else if(!name) name = ca_device; else if(strcmp(name, ca_device) != 0) - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; +#endif /* open the default output unit */ AudioComponentDescription desc{}; desc.componentType = kAudioUnitType_Output; -#if TARGET_OS_IOS - desc.componentSubType = kAudioUnitSubType_RemoteIO; +#if CAN_ENUMERATE + desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ? + kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput; #else - desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentSubType = kAudioUnitSubType_RemoteIO; #endif desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; @@ -114,32 +343,55 @@ ALCenum CoreAudioPlayback::open(const ALCchar *name) AudioComponent comp{AudioComponentFindNext(NULL, &desc)}; if(comp == nullptr) - { - ERR("AudioComponentFindNext failed\n"); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"}; - OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; + AudioUnit audioUnit{}; + OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)}; if(err != noErr) - { - ERR("AudioComponentInstanceNew failed\n"); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::NoDevice, + "Could not create component instance: %u", err}; - /* init and start the default audio unit... */ - err = AudioUnitInitialize(mAudioUnit); +#if CAN_ENUMERATE + if(audioDevice != kAudioDeviceUnknown) + AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID)); +#endif + + err = AudioUnitInitialize(audioUnit); if(err != noErr) + throw al::backend_exception{al::backend_error::DeviceError, + "Could not initialize audio unit: %u", err}; + + /* WARNING: I don't know if "valid" audio unit values are guaranteed to be + * non-0. If not, this logic is broken. + */ + if(mAudioUnit) { - ERR("AudioUnitInitialize failed\n"); + AudioUnitUninitialize(mAudioUnit); AudioComponentInstanceDispose(mAudioUnit); - return ALC_INVALID_VALUE; } + mAudioUnit = audioUnit; + +#if CAN_ENUMERATE + if(name) + mDevice->DeviceName = name; + else + { + UInt32 propSize{sizeof(audioDevice)}; + audioDevice = kAudioDeviceUnknown; + AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, 0, &audioDevice, &propSize); + std::string devname{GetDeviceName(audioDevice)}; + if(!devname.empty()) mDevice->DeviceName = std::move(devname); + else mDevice->DeviceName = "Unknown Device Name"; + } +#else mDevice->DeviceName = name; - return ALC_NO_ERROR; +#endif } -ALCboolean CoreAudioPlayback::reset() +bool CoreAudioPlayback::reset() { OSStatus err{AudioUnitUninitialize(mAudioUnit)}; if(err != noErr) @@ -147,13 +399,13 @@ ALCboolean CoreAudioPlayback::reset() /* retrieve default output unit's properties (output side) */ AudioStreamBasicDescription streamFormat{}; - auto size = static_cast(sizeof(AudioStreamBasicDescription)); + UInt32 size{sizeof(streamFormat)}; err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamFormat, &size); - if(err != noErr || size != sizeof(AudioStreamBasicDescription)) + if(err != noErr || size != sizeof(streamFormat)) { ERR("AudioUnitGetProperty failed\n"); - return ALC_FALSE; + return false; } #if 0 @@ -166,99 +418,65 @@ ALCboolean CoreAudioPlayback::reset() TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate); #endif - /* set default output unit's input side to match output side */ - err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, - 0, &streamFormat, size); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_FALSE; - } - + /* Use the sample rate from the output unit's current parameters, but reset + * everything else. + */ if(mDevice->Frequency != streamFormat.mSampleRate) { - mDevice->BufferSize = static_cast(uint64_t{mDevice->BufferSize} * + mDevice->BufferSize = static_cast(uint64_t{mDevice->BufferSize} * streamFormat.mSampleRate / mDevice->Frequency); - mDevice->Frequency = streamFormat.mSampleRate; + mDevice->Frequency = static_cast(streamFormat.mSampleRate); } /* FIXME: How to tell what channels are what in the output device, and how - * to specify what we're giving? eg, 6.0 vs 5.1 */ - switch(streamFormat.mChannelsPerFrame) - { - case 1: - mDevice->FmtChans = DevFmtMono; - break; - case 2: - mDevice->FmtChans = DevFmtStereo; - break; - case 4: - mDevice->FmtChans = DevFmtQuad; - break; - case 6: - mDevice->FmtChans = DevFmtX51; - break; - case 7: - mDevice->FmtChans = DevFmtX61; - break; - case 8: - mDevice->FmtChans = DevFmtX71; - break; - default: - ERR("Unhandled channel count (%d), using Stereo\n", streamFormat.mChannelsPerFrame); - mDevice->FmtChans = DevFmtStereo; - streamFormat.mChannelsPerFrame = 2; - break; - } - SetDefaultWFXChannelOrder(mDevice); + * to specify what we're giving? e.g. 6.0 vs 5.1 + */ + streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt(); - /* use channel count and sample rate from the default output unit's current - * parameters, but reset everything else */ streamFormat.mFramesPerPacket = 1; - streamFormat.mFormatFlags = 0; + streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked; + streamFormat.mFormatID = kAudioFormatLinearPCM; switch(mDevice->FmtType) { case DevFmtUByte: mDevice->FmtType = DevFmtByte; /* fall-through */ case DevFmtByte: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; streamFormat.mBitsPerChannel = 8; break; case DevFmtUShort: mDevice->FmtType = DevFmtShort; /* fall-through */ case DevFmtShort: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; streamFormat.mBitsPerChannel = 16; break; case DevFmtUInt: mDevice->FmtType = DevFmtInt; /* fall-through */ case DevFmtInt: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; streamFormat.mBitsPerChannel = 32; break; case DevFmtFloat: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat; + streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat; streamFormat.mBitsPerChannel = 32; break; } - streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame * - streamFormat.mBitsPerChannel / 8; - streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame; - streamFormat.mFormatID = kAudioFormatLinearPCM; - streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian | - kLinearPCMFormatFlagIsPacked; + streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame*streamFormat.mBitsPerChannel/8; + streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket; err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, - 0, &streamFormat, sizeof(AudioStreamBasicDescription)); + 0, &streamFormat, sizeof(streamFormat)); if(err != noErr) { ERR("AudioUnitSetProperty failed\n"); - return ALC_FALSE; + return false; } + setDefaultWFXChannelOrder(); + /* setup callback */ mFrameSize = mDevice->frameSizeFromFmt(); AURenderCallbackStruct input{}; @@ -270,7 +488,7 @@ ALCboolean CoreAudioPlayback::reset() if(err != noErr) { ERR("AudioUnitSetProperty failed\n"); - return ALC_FALSE; + return false; } /* init the default audio unit... */ @@ -278,21 +496,18 @@ ALCboolean CoreAudioPlayback::reset() if(err != noErr) { ERR("AudioUnitInitialize failed\n"); - return ALC_FALSE; + return false; } - return ALC_TRUE; + return true; } -ALCboolean CoreAudioPlayback::start() +void CoreAudioPlayback::start() { - OSStatus err{AudioOutputUnitStart(mAudioUnit)}; + const OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) - { - ERR("AudioOutputUnitStart failed\n"); - return ALC_FALSE; - } - return ALC_TRUE; + throw al::backend_exception{al::backend_error::DeviceError, + "AudioOutputUnitStart failed: %d", err}; } void CoreAudioPlayback::stop() @@ -304,32 +519,35 @@ void CoreAudioPlayback::stop() struct CoreAudioCapture final : public BackendBase { - CoreAudioCapture(ALCdevice *device) noexcept : BackendBase{device} { } + CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~CoreAudioCapture() override; - static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData); OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, - UInt32 inNumberFrames, AudioBufferList *ioData); + UInt32 inNumberFrames, AudioBufferList *ioData) noexcept; + static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, + AudioBufferList *ioData) noexcept + { + return static_cast(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, + inBusNumber, inNumberFrames, ioData); + } - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; AudioUnit mAudioUnit{0}; - ALuint mFrameSize{0u}; + uint mFrameSize{0u}; AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD SampleConverterPtr mConverter; RingBufferPtr mRing{nullptr}; - static constexpr inline const char *CurrentPrefix() noexcept { return "CoreAudioCapture::"; } DEF_NEWDEL(CoreAudioCapture) }; @@ -341,26 +559,19 @@ CoreAudioCapture::~CoreAudioCapture() } -OSStatus CoreAudioCapture::RecordProcC(void *inRefCon, - AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, - UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) -{ - return static_cast(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, - inBusNumber, inNumberFrames, ioData); -} - -OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags* UNUSED(ioActionFlags), - const AudioTimeStamp *inTimeStamp, UInt32 UNUSED(inBusNumber), UInt32 inNumberFrames, - AudioBufferList* UNUSED(ioData)) +OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*, + const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames, + AudioBufferList*) noexcept { AudioUnitRenderActionFlags flags = 0; union { - ALbyte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2]; + al::byte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2]; AudioBufferList list; - } audiobuf = { { 0 } }; + } audiobuf{}; auto rec_vec = mRing->getWriteVector(); - inNumberFrames = minz(inNumberFrames, rec_vec.first.len+rec_vec.second.len); + inNumberFrames = static_cast(minz(inNumberFrames, + rec_vec.first.len+rec_vec.second.len)); // Fill the ringbuffer's two segments with data from the input device if(rec_vec.first.len >= inNumberFrames) @@ -372,11 +583,12 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags* UNUSED(ioActio } else { - const size_t remaining{inNumberFrames-rec_vec.first.len}; + const auto remaining = static_cast(inNumberFrames - rec_vec.first.len); audiobuf.list.mNumberBuffers = 2; audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame; audiobuf.list.mBuffers[0].mData = rec_vec.first.buf; - audiobuf.list.mBuffers[0].mDataByteSize = rec_vec.first.len * mFormat.mBytesPerFrame; + audiobuf.list.mBuffers[0].mDataByteSize = static_cast(rec_vec.first.len) * + mFormat.mBytesPerFrame; audiobuf.list.mBuffers[1].mNumberChannels = mFormat.mChannelsPerFrame; audiobuf.list.mBuffers[1].mData = rec_vec.second.buf; audiobuf.list.mBuffers[1].mDataByteSize = remaining * mFormat.mBytesPerFrame; @@ -394,177 +606,167 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags* UNUSED(ioActio } -ALCenum CoreAudioCapture::open(const ALCchar *name) +void CoreAudioCapture::open(const char *name) { - AudioStreamBasicDescription requestedFormat; // The application requested format - AudioStreamBasicDescription hardwareFormat; // The hardware format - AudioStreamBasicDescription outputFormat; // The AudioUnit output format - AURenderCallbackStruct input; - AudioComponentDescription desc; - UInt32 outputFrameCount; - UInt32 propertySize; - AudioObjectPropertyAddress propertyAddress; - UInt32 enableIO; - AudioComponent comp; - OSStatus err; +#if CAN_ENUMERATE + AudioDeviceID audioDevice{kAudioDeviceUnknown}; + if(!name) + GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice), + &audioDevice); + else + { + if(CaptureList.empty()) + EnumerateDevices(CaptureList, true); + + auto find_name = [name](const DeviceEntry &entry) -> bool + { return entry.mName == name; }; + auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name); + if(devmatch == CaptureList.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + audioDevice = devmatch->mId; + } +#else if(!name) name = ca_device; else if(strcmp(name, ca_device) != 0) - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; +#endif + AudioComponentDescription desc{}; desc.componentType = kAudioUnitType_Output; -#if TARGET_OS_IOS - desc.componentSubType = kAudioUnitSubType_RemoteIO; +#if CAN_ENUMERATE + desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ? + kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput; #else - desc.componentSubType = kAudioUnitSubType_HALOutput; + desc.componentSubType = kAudioUnitSubType_RemoteIO; #endif desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; // Search for component with given description - comp = AudioComponentFindNext(NULL, &desc); + AudioComponent comp{AudioComponentFindNext(NULL, &desc)}; if(comp == NULL) - { - ERR("AudioComponentFindNext failed\n"); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"}; // Open the component - err = AudioComponentInstanceNew(comp, &mAudioUnit); + OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; if(err != noErr) - { - ERR("AudioComponentInstanceNew failed\n"); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::NoDevice, + "Could not create component instance: %u", err}; + +#if CAN_ENUMERATE + if(audioDevice != kAudioDeviceUnknown) + AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID)); +#endif // Turn off AudioUnit output - enableIO = 0; + UInt32 enableIO{0}; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Output, 0, &enableIO, sizeof(ALuint)); + kAudioUnitScope_Output, 0, &enableIO, sizeof(enableIO)); if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::DeviceError, + "Could not disable audio unit output property: %u", err}; // Turn on AudioUnit input enableIO = 1; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Input, 1, &enableIO, sizeof(ALuint)); + kAudioUnitScope_Input, 1, &enableIO, sizeof(enableIO)); if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } - -#if !TARGET_OS_IOS - { - // Get the default input device - AudioDeviceID inputDevice = kAudioDeviceUnknown; - - propertySize = sizeof(AudioDeviceID); - propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; - propertyAddress.mScope = kAudioObjectPropertyScopeGlobal; - propertyAddress.mElement = kAudioObjectPropertyElementMaster; - - err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propertySize, &inputDevice); - if(err != noErr) - { - ERR("AudioObjectGetPropertyData failed\n"); - return ALC_INVALID_VALUE; - } - if(inputDevice == kAudioDeviceUnknown) - { - ERR("No input device found\n"); - return ALC_INVALID_VALUE; - } - - // Track the input device - err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } - } -#endif + throw al::backend_exception{al::backend_error::DeviceError, + "Could not enable audio unit input property: %u", err}; // set capture callback + AURenderCallbackStruct input{}; input.inputProc = CoreAudioCapture::RecordProcC; input.inputProcRefCon = this; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::DeviceError, + "Could not set capture callback: %u", err}; + + // Disable buffer allocation for capture + UInt32 flag{0}; + err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer, + kAudioUnitScope_Output, 1, &flag, sizeof(flag)); + if(err != noErr) + throw al::backend_exception{al::backend_error::DeviceError, + "Could not disable buffer allocation property: %u", err}; // Initialize the device err = AudioUnitInitialize(mAudioUnit); if(err != noErr) - { - ERR("AudioUnitInitialize failed\n"); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::DeviceError, + "Could not initialize audio unit: %u", err}; // Get the hardware format - propertySize = sizeof(AudioStreamBasicDescription); + AudioStreamBasicDescription hardwareFormat{}; + UInt32 propertySize{sizeof(hardwareFormat)}; err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1, &hardwareFormat, &propertySize); - if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription)) - { - ERR("AudioUnitGetProperty failed\n"); - return ALC_INVALID_VALUE; - } + if(err != noErr || propertySize != sizeof(hardwareFormat)) + throw al::backend_exception{al::backend_error::DeviceError, + "Could not get input format: %u", err}; // Set up the requested format description + AudioStreamBasicDescription requestedFormat{}; switch(mDevice->FmtType) { - case DevFmtUByte: - requestedFormat.mBitsPerChannel = 8; - requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked; - break; - case DevFmtShort: - requestedFormat.mBitsPerChannel = 16; - requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; - break; - case DevFmtInt: - requestedFormat.mBitsPerChannel = 32; - requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; - break; - case DevFmtFloat: - requestedFormat.mBitsPerChannel = 32; - requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked; - break; - case DevFmtByte: - case DevFmtUShort: - case DevFmtUInt: - ERR("%s samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_VALUE; + case DevFmtByte: + requestedFormat.mBitsPerChannel = 8; + requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + break; + case DevFmtUByte: + requestedFormat.mBitsPerChannel = 8; + requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked; + break; + case DevFmtShort: + requestedFormat.mBitsPerChannel = 16; + requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger + | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + break; + case DevFmtUShort: + requestedFormat.mBitsPerChannel = 16; + requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + break; + case DevFmtInt: + requestedFormat.mBitsPerChannel = 32; + requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger + | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + break; + case DevFmtUInt: + requestedFormat.mBitsPerChannel = 32; + requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + break; + case DevFmtFloat: + requestedFormat.mBitsPerChannel = 32; + requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian + | kAudioFormatFlagIsPacked; + break; } switch(mDevice->FmtChans) { - case DevFmtMono: - requestedFormat.mChannelsPerFrame = 1; - break; - case DevFmtStereo: - requestedFormat.mChannelsPerFrame = 2; - break; + case DevFmtMono: + requestedFormat.mChannelsPerFrame = 1; + break; + case DevFmtStereo: + requestedFormat.mChannelsPerFrame = 2; + break; - case DevFmtQuad: - case DevFmtX51: - case DevFmtX51Rear: - case DevFmtX61: - case DevFmtX71: - case DevFmtAmbi3D: - ERR("%s not supported\n", DevFmtChannelsString(mDevice->FmtChans)); - return ALC_INVALID_VALUE; + case DevFmtQuad: + case DevFmtX51: + case DevFmtX61: + case DevFmtX71: + case DevFmtAmbi3D: + throw al::backend_exception{al::backend_error::DeviceError, "%s not supported", + DevFmtChannelsString(mDevice->FmtChans)}; } requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8; @@ -580,62 +782,71 @@ ALCenum CoreAudioCapture::open(const ALCchar *name) // Use intermediate format for sample rate conversion (outputFormat) // Set sample rate to the same as hardware for resampling later - outputFormat = requestedFormat; + AudioStreamBasicDescription outputFormat{requestedFormat}; outputFormat.mSampleRate = hardwareFormat.mSampleRate; // The output format should be the requested format, but using the hardware sample rate // This is because the AudioUnit will automatically scale other properties, except for sample rate err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, - 1, (void*)&outputFormat, sizeof(outputFormat)); + 1, &outputFormat, sizeof(outputFormat)); if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return ALC_INVALID_VALUE; - } - - // Set the AudioUnit output format frame count - uint64_t FrameCount64{mDevice->UpdateSize}; - FrameCount64 = (FrameCount64*outputFormat.mSampleRate + mDevice->Frequency-1) / - mDevice->Frequency; - FrameCount64 += MAX_RESAMPLE_PADDING*2; - if(FrameCount64 > std::numeric_limits::max()/2) - { - ERR("FrameCount too large\n"); - return ALC_INVALID_VALUE; - } - - outputFrameCount = static_cast(FrameCount64); - err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, - kAudioUnitScope_Output, 0, &outputFrameCount, sizeof(outputFrameCount)); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed: %d\n", err); - return ALC_INVALID_VALUE; - } - - // Set up sample converter if needed + throw al::backend_exception{al::backend_error::DeviceError, + "Could not set input format: %u", err}; + + /* Calculate the minimum AudioUnit output format frame count for the pre- + * conversion ring buffer. Ensure at least 100ms for the total buffer. + */ + double srateScale{double{outputFormat.mSampleRate} / mDevice->Frequency}; + auto FrameCount64 = maxu64(static_cast(std::ceil(mDevice->BufferSize*srateScale)), + static_cast(outputFormat.mSampleRate)/10); + FrameCount64 += MaxResamplerPadding; + if(FrameCount64 > std::numeric_limits::max()) + throw al::backend_exception{al::backend_error::DeviceError, + "Calculated frame count is too large: %" PRIu64, FrameCount64}; + + UInt32 outputFrameCount{}; + propertySize = sizeof(outputFrameCount); + err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, 0, &outputFrameCount, &propertySize); + if(err != noErr || propertySize != sizeof(outputFrameCount)) + throw al::backend_exception{al::backend_error::DeviceError, + "Could not get input frame count: %u", err}; + + outputFrameCount = static_cast(maxu64(outputFrameCount, FrameCount64)); + mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false); + + /* Set up sample converter if needed */ if(outputFormat.mSampleRate != mDevice->Frequency) mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType, - mFormat.mChannelsPerFrame, hardwareFormat.mSampleRate, mDevice->Frequency, - BSinc24Resampler); + mFormat.mChannelsPerFrame, static_cast(hardwareFormat.mSampleRate), + mDevice->Frequency, Resampler::FastBSinc24); - mRing = CreateRingBuffer(outputFrameCount, mFrameSize, false); - if(!mRing) return ALC_INVALID_VALUE; +#if CAN_ENUMERATE + if(name) + mDevice->DeviceName = name; + else + { + UInt32 propSize{sizeof(audioDevice)}; + audioDevice = kAudioDeviceUnknown; + AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, 0, &audioDevice, &propSize); + std::string devname{GetDeviceName(audioDevice)}; + if(!devname.empty()) mDevice->DeviceName = std::move(devname); + else mDevice->DeviceName = "Unknown Device Name"; + } +#else mDevice->DeviceName = name; - return ALC_NO_ERROR; +#endif } -ALCboolean CoreAudioCapture::start() +void CoreAudioCapture::start() { OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) - { - ERR("AudioOutputUnitStart failed\n"); - return ALC_FALSE; - } - return ALC_TRUE; + throw al::backend_exception{al::backend_error::DeviceError, + "AudioOutputUnitStart failed: %d", err}; } void CoreAudioCapture::stop() @@ -645,36 +856,34 @@ void CoreAudioCapture::stop() ERR("AudioOutputUnitStop failed\n"); } -ALCenum CoreAudioCapture::captureSamples(void *buffer, ALCuint samples) +void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples) { if(!mConverter) { mRing->read(buffer, samples); - return ALC_NO_ERROR; + return; } auto rec_vec = mRing->getReadVector(); const void *src0{rec_vec.first.buf}; - auto src0len = static_cast(rec_vec.first.len); - auto got = static_cast(mConverter->convert(&src0, &src0len, buffer, samples)); + auto src0len = static_cast(rec_vec.first.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) { const void *src1{rec_vec.second.buf}; - auto src1len = static_cast(rec_vec.second.len); - got += static_cast(mConverter->convert(&src1, &src1len, - static_cast(buffer)+got, samples-got)); + auto src1len = static_cast(rec_vec.second.len); + got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got); total_read += rec_vec.second.len - src1len; } mRing->readAdvance(total_read); - return ALC_NO_ERROR; } -ALCuint CoreAudioCapture::availableSamples() +uint CoreAudioCapture::availableSamples() { - if(!mConverter) return mRing->readSpace(); - return mConverter->availableOut(mRing->readSpace()); + if(!mConverter) return static_cast(mRing->readSpace()); + return mConverter->availableOut(static_cast(mRing->readSpace())); } } // namespace @@ -690,19 +899,42 @@ bool CoreAudioBackendFactory::init() { return true; } bool CoreAudioBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -void CoreAudioBackendFactory::probe(DevProbe type, std::string *outnames) +std::string CoreAudioBackendFactory::probe(BackendType type) { + std::string outnames; +#if CAN_ENUMERATE + auto append_name = [&outnames](const DeviceEntry &entry) -> void + { + /* Includes null char. */ + outnames.append(entry.mName.c_str(), entry.mName.length()+1); + }; switch(type) { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(ca_device, sizeof(ca_device)); - break; + case BackendType::Playback: + EnumerateDevices(PlaybackList, false); + std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); + break; + case BackendType::Capture: + EnumerateDevices(CaptureList, true); + std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name); + break; } + +#else + + switch(type) + { + case BackendType::Playback: + case BackendType::Capture: + /* Includes null char. */ + outnames.append(ca_device, sizeof(ca_device)); + break; + } +#endif + return outnames; } -BackendPtr CoreAudioBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new CoreAudioPlayback{device}}; diff --git a/modules/openal-soft/Alc/backends/coreaudio.h b/modules/openal-soft/Alc/backends/coreaudio.h index 37b9ebe..1252edd 100644 --- a/modules/openal-soft/Alc/backends/coreaudio.h +++ b/modules/openal-soft/Alc/backends/coreaudio.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_COREAUDIO_H #define BACKENDS_COREAUDIO_H -#include "backends/base.h" +#include "base.h" struct CoreAudioBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/dsound.cpp b/modules/openal-soft/Alc/backends/dsound.cpp index 38b1b9f..0edc286 100644 --- a/modules/openal-soft/Alc/backends/dsound.cpp +++ b/modules/openal-soft/Alc/backends/dsound.cpp @@ -20,7 +20,10 @@ #include "config.h" -#include "backends/dsound.h" +#include "dsound.h" + +#define WIN32_LEAN_AND_MEAN +#include #include #include @@ -34,16 +37,22 @@ #endif #include +#include #include #include #include #include #include -#include "alMain.h" -#include "alu.h" +#include "alnumeric.h" +#include "comptr.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "dynload.h" #include "ringbuffer.h" -#include "compat.h" +#include "strutils.h" +#include "threads.h" /* MinGW-w64 needs this for some unknown reason now. */ using LPCWAVEFORMATEX = const WAVEFORMATEX*; @@ -99,6 +108,14 @@ HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallbac #endif +#define MONO SPEAKER_FRONT_CENTER +#define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT) +#define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) +#define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) +#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) + #define MAX_UPDATES 128 struct DevMap { @@ -116,13 +133,12 @@ al::vector CaptureDevices; bool checkName(const al::vector &list, const std::string &name) { - return std::find_if(list.cbegin(), list.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ) != list.cend(); + auto match_name = [&name](const DevMap &entry) -> bool + { return entry.name == name; }; + return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } -BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR* UNUSED(drvname), void *data) +BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept { if(!guid) return TRUE; @@ -154,44 +170,35 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR* UNUS struct DSoundPlayback final : public BackendBase { - DSoundPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~DSoundPlayback() override; int mixerProc(); - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; - IDirectSound *mDS{nullptr}; - IDirectSoundBuffer *mPrimaryBuffer{nullptr}; - IDirectSoundBuffer *mBuffer{nullptr}; - IDirectSoundNotify *mNotifies{nullptr}; - HANDLE mNotifyEvent{nullptr}; + ComPtr mDS; + ComPtr mPrimaryBuffer; + ComPtr mBuffer; + ComPtr mNotifies; + HANDLE mNotifyEvent{nullptr}; std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "DSoundPlayback::"; } DEF_NEWDEL(DSoundPlayback) }; DSoundPlayback::~DSoundPlayback() { - if(mNotifies) - mNotifies->Release(); mNotifies = nullptr; - if(mBuffer) - mBuffer->Release(); mBuffer = nullptr; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); mPrimaryBuffer = nullptr; - - if(mDS) - mDS->Release(); mDS = nullptr; + if(mNotifyEvent) CloseHandle(mNotifyEvent); mNotifyEvent = nullptr; @@ -209,18 +216,19 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() if(FAILED(err)) { ERR("Failed to get buffer caps: 0x%lx\n", err); - aluHandleDisconnect(mDevice, "Failure retrieving playback buffer info: 0x%lx", err); + mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err); return 1; } - ALsizei FrameSize{mDevice->frameSizeFromFmt()}; + const size_t FrameStep{mDevice->channelsFromFmt()}; + uint FrameSize{mDevice->frameSizeFromFmt()}; DWORD FragSize{mDevice->UpdateSize * FrameSize}; bool Playing{false}; DWORD LastCursor{0u}; mBuffer->GetCurrentPosition(&LastCursor, nullptr); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { // Get current play cursor DWORD PlayCursor; @@ -235,7 +243,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() if(FAILED(err)) { ERR("Failed to play buffer: 0x%lx\n", err); - aluHandleDisconnect(mDevice, "Failure starting playback: 0x%lx", err); + mDevice->handleDisconnect("Failure starting playback: 0x%lx", err); return 1; } Playing = true; @@ -269,18 +277,16 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() if(SUCCEEDED(err)) { - lock(); - aluMixData(mDevice, WritePtr1, WriteCnt1/FrameSize); + mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep); if(WriteCnt2 > 0) - aluMixData(mDevice, WritePtr2, WriteCnt2/FrameSize); - unlock(); + mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep); mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2); } else { ERR("Buffer lock error: %#lx\n", err); - aluHandleDisconnect(mDevice, "Failed to lock output buffer: 0x%lx", err); + mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err); return 1; } @@ -292,7 +298,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() return 0; } -ALCenum DSoundPlayback::open(const ALCchar *name) +void DSoundPlayback::open(const char *name) { HRESULT hr; if(PlaybackDevices.empty()) @@ -315,64 +321,71 @@ ALCenum DSoundPlayback::open(const ALCchar *name) else { auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); + [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) - return ALC_INVALID_VALUE; + { + GUID id{}; + hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id); + if(SUCCEEDED(hr)) + iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + [&id](const DevMap &entry) -> bool { return entry.guid == id; }); + if(iter == PlaybackDevices.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + } guid = &iter->guid; } hr = DS_OK; - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(!mNotifyEvent) hr = E_FAIL; + if(!mNotifyEvent) + { + mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if(!mNotifyEvent) hr = E_FAIL; + } //DirectSound Init code + ComPtr ds; if(SUCCEEDED(hr)) - hr = DirectSoundCreate(guid, &mDS, nullptr); + hr = DirectSoundCreate(guid, ds.getPtr(), nullptr); if(SUCCEEDED(hr)) - hr = mDS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); + hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); if(FAILED(hr)) - { - ERR("Device init failed: 0x%08lx\n", hr); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", + hr}; + + mNotifies = nullptr; + mBuffer = nullptr; + mPrimaryBuffer = nullptr; + mDS = std::move(ds); mDevice->DeviceName = name; - return ALC_NO_ERROR; } -ALCboolean DSoundPlayback::reset() +bool DSoundPlayback::reset() { - if(mNotifies) - mNotifies->Release(); mNotifies = nullptr; - if(mBuffer) - mBuffer->Release(); mBuffer = nullptr; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); mPrimaryBuffer = nullptr; switch(mDevice->FmtType) { - case DevFmtByte: - mDevice->FmtType = DevFmtUByte; - break; - case DevFmtFloat: - if((mDevice->Flags&DEVICE_SAMPLE_TYPE_REQUEST)) - break; - /* fall-through */ - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - break; - case DevFmtUByte: - case DevFmtShort: - case DevFmtInt: + case DevFmtByte: + mDevice->FmtType = DevFmtUByte; + break; + case DevFmtFloat: + if(mDevice->Flags.test(SampleTypeRequest)) break; + /* fall-through */ + case DevFmtUShort: + mDevice->FmtType = DevFmtShort; + break; + case DevFmtUInt: + mDevice->FmtType = DevFmtInt; + break; + case DevFmtUByte: + case DevFmtShort: + case DevFmtInt: + break; } WAVEFORMATEXTENSIBLE OutputType{}; @@ -381,7 +394,7 @@ ALCboolean DSoundPlayback::reset() if(SUCCEEDED(hr)) { speakers = DSSPEAKER_CONFIG(speakers); - if(!(mDevice->Flags&DEVICE_CHANNELS_REQUEST)) + if(!mDevice->Flags.test(ChannelsRequest)) { if(speakers == DSSPEAKER_MONO) mDevice->FmtChans = DevFmtMono; @@ -389,81 +402,37 @@ ALCboolean DSoundPlayback::reset() mDevice->FmtChans = DevFmtStereo; else if(speakers == DSSPEAKER_QUAD) mDevice->FmtChans = DevFmtQuad; - else if(speakers == DSSPEAKER_5POINT1_SURROUND) + else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK) mDevice->FmtChans = DevFmtX51; - else if(speakers == DSSPEAKER_5POINT1_BACK) - mDevice->FmtChans = DevFmtX51Rear; else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND) mDevice->FmtChans = DevFmtX71; else ERR("Unknown system speaker config: 0x%lx\n", speakers); } - mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo && - speakers == DSSPEAKER_HEADPHONE); + mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE)); switch(mDevice->FmtChans) { - case DevFmtMono: - OutputType.dwChannelMask = SPEAKER_FRONT_CENTER; - break; - case DevFmtAmbi3D: - mDevice->FmtChans = DevFmtStereo; - /*fall-through*/ - case DevFmtStereo: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT; - break; - case DevFmtQuad: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT; - break; - case DevFmtX51: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtX51Rear: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT; - break; - case DevFmtX61: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_CENTER | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtX71: - OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; + case DevFmtMono: OutputType.dwChannelMask = MONO; break; + case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo; + /*fall-through*/ + case DevFmtStereo: OutputType.dwChannelMask = STEREO; break; + case DevFmtQuad: OutputType.dwChannelMask = QUAD; break; + case DevFmtX51: OutputType.dwChannelMask = X5DOT1; break; + case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break; + case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break; } retry_open: hr = S_OK; OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; - OutputType.Format.nChannels = mDevice->channelsFromFmt(); - OutputType.Format.wBitsPerSample = mDevice->bytesFromFmt() * 8; - OutputType.Format.nBlockAlign = OutputType.Format.nChannels*OutputType.Format.wBitsPerSample/8; + OutputType.Format.nChannels = static_cast(mDevice->channelsFromFmt()); + OutputType.Format.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); + OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels * + OutputType.Format.wBitsPerSample / 8); OutputType.Format.nSamplesPerSec = mDevice->Frequency; - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec*OutputType.Format.nBlockAlign; + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * + OutputType.Format.nBlockAlign; OutputType.Format.cbSize = 0; } @@ -477,8 +446,6 @@ retry_open: else OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); mPrimaryBuffer = nullptr; } else @@ -488,7 +455,7 @@ retry_open: DSBUFFERDESC DSBDescription{}; DSBDescription.dwSize = sizeof(DSBDescription); DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; - hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr); + hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr); } if(SUCCEEDED(hr)) hr = mPrimaryBuffer->SetFormat(&OutputType.Format); @@ -496,19 +463,19 @@ retry_open: if(SUCCEEDED(hr)) { - ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; + uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; if(num_updates > MAX_UPDATES) num_updates = MAX_UPDATES; mDevice->BufferSize = mDevice->UpdateSize * num_updates; DSBUFFERDESC DSBDescription{}; DSBDescription.dwSize = sizeof(DSBDescription); - DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | - DSBCAPS_GLOBALFOCUS; + DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 + | DSBCAPS_GLOBALFOCUS; DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign; DSBDescription.lpwfxFormat = &OutputType.Format; - hr = mDS->CreateSoundBuffer(&DSBDescription, &mBuffer, nullptr); + hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr); if(FAILED(hr) && mDevice->FmtType == DevFmtFloat) { mDevice->FmtType = DevFmtShort; @@ -522,56 +489,46 @@ retry_open: hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr); if(SUCCEEDED(hr)) { - auto Notifies = static_cast(ptr); - mNotifies = Notifies; + mNotifies = ComPtr{static_cast(ptr)}; - ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; + uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; assert(num_updates <= MAX_UPDATES); std::array nots; - for(ALuint i{0};i < num_updates;++i) + for(uint i{0};i < num_updates;++i) { nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign; nots[i].hEventNotify = mNotifyEvent; } - if(Notifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK) + if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK) hr = E_FAIL; } } if(FAILED(hr)) { - if(mNotifies) - mNotifies->Release(); mNotifies = nullptr; - if(mBuffer) - mBuffer->Release(); mBuffer = nullptr; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); mPrimaryBuffer = nullptr; - return ALC_FALSE; + return false; } ResetEvent(mNotifyEvent); - SetDefaultWFXChannelOrder(mDevice); + setChannelOrderFromWFXMask(OutputType.dwChannelMask); - return ALC_TRUE; + return true; } -ALCboolean DSoundPlayback::start() +void DSoundPlayback::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this}; - return ALC_TRUE; } catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - return ALC_FALSE; } void DSoundPlayback::stop() @@ -585,23 +542,22 @@ void DSoundPlayback::stop() struct DSoundCapture final : public BackendBase { - DSoundCapture(ALCdevice *device) noexcept : BackendBase{device} { } + DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~DSoundCapture() override; - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; - IDirectSoundCapture *mDSC{nullptr}; - IDirectSoundCaptureBuffer *mDSCbuffer{nullptr}; + ComPtr mDSC; + ComPtr mDSCbuffer; DWORD mBufferBytes{0u}; DWORD mCursor{0u}; RingBufferPtr mRing; - static constexpr inline const char *CurrentPrefix() noexcept { return "DSoundCapture::"; } DEF_NEWDEL(DSoundCapture) }; @@ -610,17 +566,13 @@ DSoundCapture::~DSoundCapture() if(mDSCbuffer) { mDSCbuffer->Stop(); - mDSCbuffer->Release(); mDSCbuffer = nullptr; } - - if(mDSC) - mDSC->Release(); mDSC = nullptr; } -ALCenum DSoundCapture::open(const ALCchar *name) +void DSoundCapture::open(const char *name) { HRESULT hr; if(CaptureDevices.empty()) @@ -643,91 +595,60 @@ ALCenum DSoundCapture::open(const ALCchar *name) else { auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); + [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) - return ALC_INVALID_VALUE; + { + GUID id{}; + hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id); + if(SUCCEEDED(hr)) + iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + [&id](const DevMap &entry) -> bool { return entry.guid == id; }); + if(iter == CaptureDevices.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + } guid = &iter->guid; } switch(mDevice->FmtType) { - case DevFmtByte: - case DevFmtUShort: - case DevFmtUInt: - WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_ENUM; - - case DevFmtUByte: - case DevFmtShort: - case DevFmtInt: - case DevFmtFloat: - break; + case DevFmtByte: + case DevFmtUShort: + case DevFmtUInt: + WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType)); + throw al::backend_exception{al::backend_error::DeviceError, + "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; + + case DevFmtUByte: + case DevFmtShort: + case DevFmtInt: + case DevFmtFloat: + break; } WAVEFORMATEXTENSIBLE InputType{}; switch(mDevice->FmtChans) { - case DevFmtMono: - InputType.dwChannelMask = SPEAKER_FRONT_CENTER; - break; - case DevFmtStereo: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT; - break; - case DevFmtQuad: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT; - break; - case DevFmtX51: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtX51Rear: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT; - break; - case DevFmtX61: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_CENTER | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtX71: - InputType.dwChannelMask = SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT; - break; - case DevFmtAmbi3D: - WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans)); - return ALC_INVALID_ENUM; + case DevFmtMono: InputType.dwChannelMask = MONO; break; + case DevFmtStereo: InputType.dwChannelMask = STEREO; break; + case DevFmtQuad: InputType.dwChannelMask = QUAD; break; + case DevFmtX51: InputType.dwChannelMask = X5DOT1; break; + case DevFmtX61: InputType.dwChannelMask = X6DOT1; break; + case DevFmtX71: InputType.dwChannelMask = X7DOT1; break; + case DevFmtAmbi3D: + WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans)); + throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", + DevFmtChannelsString(mDevice->FmtChans)}; } InputType.Format.wFormatTag = WAVE_FORMAT_PCM; - InputType.Format.nChannels = mDevice->channelsFromFmt(); - InputType.Format.wBitsPerSample = mDevice->bytesFromFmt() * 8; - InputType.Format.nBlockAlign = InputType.Format.nChannels*InputType.Format.wBitsPerSample/8; + InputType.Format.nChannels = static_cast(mDevice->channelsFromFmt()); + 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.nAvgBytesPerSec = InputType.Format.nSamplesPerSec*InputType.Format.nBlockAlign; + InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec * + InputType.Format.nBlockAlign; InputType.Format.cbSize = 0; InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample; if(mDevice->FmtType == DevFmtFloat) @@ -741,7 +662,7 @@ ALCenum DSoundCapture::open(const ALCchar *name) InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); } - ALuint samples{mDevice->BufferSize}; + uint samples{mDevice->BufferSize}; samples = maxu(samples, 100 * mDevice->Frequency / 1000); DSCBUFFERDESC DSCBDescription{}; @@ -751,47 +672,34 @@ ALCenum DSoundCapture::open(const ALCchar *name) DSCBDescription.lpwfxFormat = &InputType.Format; //DirectSoundCapture Init code - hr = DirectSoundCaptureCreate(guid, &mDSC, nullptr); + hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr); if(SUCCEEDED(hr)) - mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, nullptr); + mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr); if(SUCCEEDED(hr)) - { - mRing = CreateRingBuffer(mDevice->BufferSize, InputType.Format.nBlockAlign, false); - if(!mRing) hr = DSERR_OUTOFMEMORY; - } + mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false); if(FAILED(hr)) { - ERR("Device init failed: 0x%08lx\n", hr); - mRing = nullptr; - if(mDSCbuffer) - mDSCbuffer->Release(); mDSCbuffer = nullptr; - if(mDSC) - mDSC->Release(); mDSC = nullptr; - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", + hr}; } mBufferBytes = DSCBDescription.dwBufferBytes; - SetDefaultWFXChannelOrder(mDevice); + setChannelOrderFromWFXMask(InputType.dwChannelMask); mDevice->DeviceName = name; - return ALC_NO_ERROR; } -ALCboolean DSoundCapture::start() +void DSoundCapture::start() { - HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)}; + const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)}; if(FAILED(hr)) - { - ERR("start failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failure starting capture: 0x%lx", hr); - return ALC_FALSE; - } - return ALC_TRUE; + throw al::backend_exception{al::backend_error::DeviceError, + "Failure starting capture: 0x%lx", hr}; } void DSoundCapture::stop() @@ -800,33 +708,30 @@ void DSoundCapture::stop() if(FAILED(hr)) { ERR("stop failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failure stopping capture: 0x%lx", hr); + mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr); } } -ALCenum DSoundCapture::captureSamples(void *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} +void DSoundCapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } -ALCuint DSoundCapture::availableSamples() +uint DSoundCapture::availableSamples() { if(!mDevice->Connected.load(std::memory_order_acquire)) - return static_cast(mRing->readSpace()); + return static_cast(mRing->readSpace()); - ALsizei FrameSize{mDevice->frameSizeFromFmt()}; - DWORD BufferBytes{mBufferBytes}; - DWORD LastCursor{mCursor}; + const uint FrameSize{mDevice->frameSizeFromFmt()}; + const DWORD BufferBytes{mBufferBytes}; + const DWORD LastCursor{mCursor}; - DWORD ReadCursor; - void *ReadPtr1, *ReadPtr2; - DWORD ReadCnt1, ReadCnt2; + DWORD ReadCursor{}; + void *ReadPtr1{}, *ReadPtr2{}; + DWORD ReadCnt1{}, ReadCnt2{}; HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)}; if(SUCCEEDED(hr)) { - DWORD NumBytes{(ReadCursor-LastCursor + BufferBytes) % BufferBytes}; - if(!NumBytes) return static_cast(mRing->readSpace()); + const DWORD NumBytes{(BufferBytes+ReadCursor-LastCursor) % BufferBytes}; + if(!NumBytes) return static_cast(mRing->readSpace()); hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0); } if(SUCCEEDED(hr)) @@ -835,16 +740,16 @@ ALCuint DSoundCapture::availableSamples() if(ReadPtr2 != nullptr && ReadCnt2 > 0) mRing->write(ReadPtr2, ReadCnt2/FrameSize); hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2); - mCursor = (LastCursor+ReadCnt1+ReadCnt2) % BufferBytes; + mCursor = ReadCursor; } if(FAILED(hr)) { ERR("update failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failure retrieving capture data: 0x%lx", hr); + mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr); } - return static_cast(mRing->readSpace()); + return static_cast(mRing->readSpace()); } } // namespace @@ -890,14 +795,15 @@ bool DSoundBackendFactory::init() bool DSoundBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -void DSoundBackendFactory::probe(DevProbe type, std::string *outnames) +std::string DSoundBackendFactory::probe(BackendType type) { - auto add_device = [outnames](const DevMap &entry) -> void + std::string outnames; + auto add_device = [&outnames](const DevMap &entry) -> void { /* +1 to also append the null char (to ensure a null-separated list and * double-null terminated list). */ - outnames->append(entry.name.c_str(), entry.name.length()+1); + outnames.append(entry.name.c_str(), entry.name.length()+1); }; /* Initialize COM to prevent name truncation */ @@ -905,27 +811,29 @@ void DSoundBackendFactory::probe(DevProbe type, std::string *outnames) HRESULT hrcom{CoInitialize(nullptr)}; switch(type) { - case DevProbe::Playback: - PlaybackDevices.clear(); - hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); - if(FAILED(hr)) - ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; + case BackendType::Playback: + PlaybackDevices.clear(); + hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); + if(FAILED(hr)) + ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr); + std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); + break; - case DevProbe::Capture: - CaptureDevices.clear(); - hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); - if(FAILED(hr)) - ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; + case BackendType::Capture: + CaptureDevices.clear(); + hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); + if(FAILED(hr)) + ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr); + std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); + break; } if(SUCCEEDED(hrcom)) CoUninitialize(); + + return outnames; } -BackendPtr DSoundBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr DSoundBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new DSoundPlayback{device}}; diff --git a/modules/openal-soft/Alc/backends/dsound.h b/modules/openal-soft/Alc/backends/dsound.h index 6bef0bf..787f227 100644 --- a/modules/openal-soft/Alc/backends/dsound.h +++ b/modules/openal-soft/Alc/backends/dsound.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_DSOUND_H #define BACKENDS_DSOUND_H -#include "backends/base.h" +#include "base.h" struct DSoundBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/jack.cpp b/modules/openal-soft/Alc/backends/jack.cpp index 74364f6..2b0131b 100644 --- a/modules/openal-soft/Alc/backends/jack.cpp +++ b/modules/openal-soft/Alc/backends/jack.cpp @@ -20,21 +20,25 @@ #include "config.h" -#include "backends/jack.h" +#include "jack.h" #include #include +#include #include +#include #include #include -#include "alMain.h" -#include "alu.h" -#include "alconfig.h" +#include "alc/alconfig.h" +#include "alnumeric.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "dynload.h" #include "ringbuffer.h" #include "threads.h" -#include "compat.h" #include #include @@ -42,9 +46,6 @@ namespace { -constexpr ALCchar jackDevice[] = "JACK Default"; - - #ifdef HAVE_DYNLOAD #define JACK_FUNCS(MAGIC) \ MAGIC(jack_client_open); \ @@ -69,7 +70,7 @@ constexpr ALCchar jackDevice[] = "JACK Default"; void *jack_handle; #define MAKE_FUNC(f) decltype(f) * p##f -JACK_FUNCS(MAKE_FUNC); +JACK_FUNCS(MAKE_FUNC) decltype(jack_error_callback) * pjack_error_callback; #undef MAKE_FUNC @@ -98,11 +99,13 @@ decltype(jack_error_callback) * pjack_error_callback; #endif +constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE; + jack_options_t ClientOptions = JackNullOption; -ALCboolean jack_load() +bool jack_load() { - ALCboolean error = ALC_FALSE; + bool error{false}; #ifdef HAVE_DYNLOAD if(!jack_handle) @@ -118,14 +121,14 @@ ALCboolean jack_load() if(!jack_handle) { WARN("Failed to load %s\n", JACKLIB); - return ALC_FALSE; + return false; } - error = ALC_FALSE; + error = false; #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast(GetSymbol(jack_handle, #f)); \ if(p##f == nullptr) { \ - error = ALC_TRUE; \ + error = true; \ missing_funcs += "\n" #f; \ } \ } while(0) @@ -149,34 +152,165 @@ ALCboolean jack_load() } +struct JackDeleter { + void operator()(void *ptr) { jack_free(ptr); } +}; +using JackPortsPtr = std::unique_ptr; + +struct DeviceEntry { + std::string mName; + std::string mPattern; +}; + +al::vector PlaybackList; + + +void EnumerateDevices(jack_client_t *client, al::vector &list) +{ + std::remove_reference_t{}.swap(list); + + if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)}) + { + for(size_t i{0};ports[i];++i) + { + const char *sep{std::strchr(ports[i], ':')}; + if(!sep || ports[i] == sep) continue; + + const al::span portdev{ports[i], sep}; + auto check_name = [portdev](const DeviceEntry &entry) -> bool + { + const size_t len{portdev.size()}; + return entry.mName.length() == len + && entry.mName.compare(0, len, portdev.data(), len) == 0; + }; + if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend()) + continue; + + std::string name{portdev.data(), portdev.size()}; + list.emplace_back(DeviceEntry{name, name+":"}); + const auto &entry = list.back(); + TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); + } + /* There are ports but couldn't get device names from them. Add a + * generic entry. + */ + if(ports[0] && list.empty()) + { + WARN("No device names found in available ports, adding a generic name.\n"); + list.emplace_back(DeviceEntry{"JACK", ""}); + } + } + + if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices")) + { + for(size_t strpos{0};strpos < listopt->size();) + { + size_t nextpos{listopt->find(';', strpos)}; + size_t seppos{listopt->find('=', strpos)}; + if(seppos >= nextpos || seppos == strpos) + { + const std::string entry{listopt->substr(strpos, nextpos-strpos)}; + ERR("Invalid device entry: \"%s\"\n", entry.c_str()); + if(nextpos != std::string::npos) ++nextpos; + strpos = nextpos; + continue; + } + + const al::span name{listopt->data()+strpos, seppos-strpos}; + const al::span pattern{listopt->data()+(seppos+1), + std::min(nextpos, listopt->size())-(seppos+1)}; + + /* Check if this custom pattern already exists in the list. */ + auto check_pattern = [pattern](const DeviceEntry &entry) -> bool + { + const size_t len{pattern.size()}; + return entry.mPattern.length() == len + && entry.mPattern.compare(0, len, pattern.data(), len) == 0; + }; + auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern); + if(itemmatch != list.end()) + { + /* If so, replace the name with this custom one. */ + itemmatch->mName.assign(name.data(), name.size()); + TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(), + itemmatch->mPattern.c_str()); + } + else + { + /* Otherwise, add a new device entry. */ + list.emplace_back(DeviceEntry{std::string{name.data(), name.size()}, + std::string{pattern.data(), pattern.size()}}); + const auto &entry = list.back(); + TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); + } + + if(nextpos != std::string::npos) ++nextpos; + strpos = nextpos; + } + } + + if(list.size() > 1) + { + /* Rename entries that have matching names, by appending '#2', '#3', + * etc, as needed. + */ + for(auto curitem = list.begin()+1;curitem != list.end();++curitem) + { + auto check_match = [curitem](const DeviceEntry &entry) -> bool + { return entry.mName == curitem->mName; }; + if(std::find_if(list.begin(), curitem, check_match) != curitem) + { + std::string name{curitem->mName}; + size_t count{1}; + auto check_name = [&name](const DeviceEntry &entry) -> bool + { return entry.mName == name; }; + do { + name = curitem->mName; + name += " #"; + name += std::to_string(++count); + } while(std::find_if(list.begin(), curitem, check_name) != curitem); + curitem->mName = std::move(name); + } + } + } +} + + struct JackPlayback final : public BackendBase { - JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~JackPlayback() override; - static int bufferSizeNotifyC(jack_nframes_t numframes, void *arg); - int bufferSizeNotify(jack_nframes_t numframes); + int processRt(jack_nframes_t numframes) noexcept; + static int processRtC(jack_nframes_t numframes, void *arg) noexcept + { return static_cast(arg)->processRt(numframes); } - static int processC(jack_nframes_t numframes, void *arg); - int process(jack_nframes_t numframes); + int process(jack_nframes_t numframes) noexcept; + static int processC(jack_nframes_t numframes, void *arg) noexcept + { return static_cast(arg)->process(numframes); } int mixerProc(); - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; ClockLatency getClockLatency() override; + std::string mPortPattern; + jack_client_t *mClient{nullptr}; - jack_port_t *mPort[MAX_OUTPUT_CHANNELS]{}; + std::array mPort{}; + + std::mutex mMutex; + std::atomic mPlaying{false}; + bool mRTMixing{false}; RingBufferPtr mRing; al::semaphore mSem; std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "JackPlayback::"; } DEF_NEWDEL(JackPlayback) }; @@ -185,111 +319,100 @@ JackPlayback::~JackPlayback() if(!mClient) return; - std::for_each(std::begin(mPort), std::end(mPort), - [this](jack_port_t *port) -> void - { if(port) jack_port_unregister(mClient, port); } - ); - std::fill(std::begin(mPort), std::end(mPort), nullptr); + auto unregister_port = [this](jack_port_t *port) -> void + { if(port) jack_port_unregister(mClient, port); }; + std::for_each(mPort.begin(), mPort.end(), unregister_port); + mPort.fill(nullptr); + jack_client_close(mClient); mClient = nullptr; } -int JackPlayback::bufferSizeNotifyC(jack_nframes_t numframes, void *arg) -{ return static_cast(arg)->bufferSizeNotify(numframes); } - -int JackPlayback::bufferSizeNotify(jack_nframes_t numframes) +int JackPlayback::processRt(jack_nframes_t numframes) noexcept { - std::lock_guard _{mDevice->StateLock}; - mDevice->UpdateSize = numframes; - mDevice->BufferSize = numframes*2; - - ALuint bufsize{mDevice->UpdateSize}; - if(ConfigValueUInt(mDevice->DeviceName.c_str(), "jack", "buffer-size", &bufsize)) - bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); - mDevice->BufferSize = bufsize + mDevice->UpdateSize; - - TRACE("%u / %u buffer\n", mDevice->UpdateSize, mDevice->BufferSize); + std::array out; + size_t numchans{0}; + for(auto port : mPort) + { + if(!port || numchans == mDevice->RealOut.Buffer.size()) + break; + out[numchans++] = static_cast(jack_port_get_buffer(port, numframes)); + } - mRing = nullptr; - mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true); - if(!mRing) + if LIKELY(mPlaying.load(std::memory_order_acquire)) + mDevice->renderSamples({out.data(), numchans}, static_cast(numframes)); + else { - ERR("Failed to reallocate ringbuffer\n"); - aluHandleDisconnect(mDevice, "Failed to reallocate %u-sample buffer", bufsize); + auto clear_buf = [numframes](float *outbuf) -> void + { std::fill_n(outbuf, numframes, 0.0f); }; + std::for_each(out.begin(), out.begin()+numchans, clear_buf); } + return 0; } -int JackPlayback::processC(jack_nframes_t numframes, void *arg) -{ return static_cast(arg)->process(numframes); } - -int JackPlayback::process(jack_nframes_t numframes) +int JackPlayback::process(jack_nframes_t numframes) noexcept { - jack_default_audio_sample_t *out[MAX_OUTPUT_CHANNELS]; - ALsizei numchans{0}; + std::array out; + size_t numchans{0}; for(auto port : mPort) { if(!port) break; out[numchans++] = static_cast(jack_port_get_buffer(port, numframes)); } - auto data = mRing->getReadVector(); - jack_nframes_t todo{minu(numframes, data.first.len)}; - std::transform(out, out+numchans, out, - [&data,numchans,todo](ALfloat *outbuf) -> ALfloat* + jack_nframes_t total{0}; + if LIKELY(mPlaying.load(std::memory_order_acquire)) + { + auto data = mRing->getReadVector(); + jack_nframes_t todo{minu(numframes, static_cast(data.first.len))}; + auto write_first = [&data,numchans,todo](float *outbuf) -> float* { - const ALfloat *RESTRICT in = reinterpret_cast(data.first.buf); - std::generate_n(outbuf, todo, - [&in,numchans]() noexcept -> ALfloat + const float *RESTRICT in = reinterpret_cast(data.first.buf); + auto deinterlace_input = [&in,numchans]() noexcept -> float + { + float ret{*in}; + in += numchans; + return ret; + }; + std::generate_n(outbuf, todo, deinterlace_input); + data.first.buf += sizeof(float); + return outbuf + todo; + }; + std::transform(out.begin(), out.begin()+numchans, out.begin(), write_first); + total += todo; + + todo = minu(numframes-total, static_cast(data.second.len)); + if(todo > 0) + { + auto write_second = [&data,numchans,todo](float *outbuf) -> float* + { + const float *RESTRICT in = reinterpret_cast(data.second.buf); + auto deinterlace_input = [&in,numchans]() noexcept -> float { - ALfloat ret{*in}; + float ret{*in}; in += numchans; return ret; - } - ); - data.first.buf += sizeof(ALfloat); - return outbuf + todo; + }; + std::generate_n(outbuf, todo, deinterlace_input); + data.second.buf += sizeof(float); + return outbuf + todo; + }; + std::transform(out.begin(), out.begin()+numchans, out.begin(), write_second); + total += todo; } - ); - jack_nframes_t total{todo}; - todo = minu(numframes-total, data.second.len); - if(todo > 0) - { - std::transform(out, out+numchans, out, - [&data,numchans,todo](ALfloat *outbuf) -> ALfloat* - { - const ALfloat *RESTRICT in = reinterpret_cast(data.second.buf); - std::generate_n(outbuf, todo, - [&in,numchans]() noexcept -> ALfloat - { - ALfloat ret{*in}; - in += numchans; - return ret; - } - ); - data.second.buf += sizeof(ALfloat); - return outbuf + todo; - } - ); - total += todo; + mRing->readAdvance(total); + mSem.post(); } - mRing->readAdvance(total); - mSem.post(); - if(numframes > total) { - todo = numframes-total; - std::transform(out, out+numchans, out, - [todo](ALfloat *outbuf) -> ALfloat* - { - std::fill_n(outbuf, todo, 0.0f); - return outbuf + todo; - } - ); + const jack_nframes_t todo{numframes - total}; + auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); }; + std::for_each(out.begin(), out.begin()+numchans, clear_buf); } return 0; @@ -300,110 +423,134 @@ int JackPlayback::mixerProc() SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - lock(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + const size_t frame_step{mDevice->channelsFromFmt()}; + + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { if(mRing->writeSpace() < mDevice->UpdateSize) { - unlock(); mSem.wait(); - lock(); continue; } auto data = mRing->getWriteVector(); - auto todo = static_cast(data.first.len + data.second.len); + size_t todo{data.first.len + data.second.len}; todo -= todo%mDevice->UpdateSize; - ALuint len1{minu(data.first.len, todo)}; - ALuint len2{minu(data.second.len, todo-len1)}; + const auto len1 = static_cast(minz(data.first.len, todo)); + const auto len2 = static_cast(minz(data.second.len, todo-len1)); - aluMixData(mDevice, data.first.buf, len1); + std::lock_guard _{mMutex}; + mDevice->renderSamples(data.first.buf, len1, frame_step); if(len2 > 0) - aluMixData(mDevice, data.second.buf, len2); + mDevice->renderSamples(data.second.buf, len2, frame_step); mRing->writeAdvance(todo); } - unlock(); return 0; } -ALCenum JackPlayback::open(const ALCchar *name) +void JackPlayback::open(const char *name) { - if(!name) - name = jackDevice; - else if(strcmp(name, jackDevice) != 0) - return ALC_INVALID_VALUE; + if(!mClient) + { + const PathNamePair &binname = GetProcBinary(); + const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; + + jack_status_t status; + 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}; + if((status&JackServerStarted)) + TRACE("JACK server started\n"); + if((status&JackNameNotUnique)) + { + client_name = jack_get_client_name(mClient); + TRACE("Client name not unique, got '%s' instead\n", client_name); + } + } - const char *client_name{"alsoft"}; - jack_status_t status; - mClient = jack_client_open(client_name, ClientOptions, &status, nullptr); - if(mClient == nullptr) + if(PlaybackList.empty()) + EnumerateDevices(mClient, PlaybackList); + + if(!name && !PlaybackList.empty()) { - ERR("jack_client_open() failed, status = 0x%02x\n", status); - return ALC_INVALID_VALUE; + name = PlaybackList[0].mName.c_str(); + mPortPattern = PlaybackList[0].mPattern; } - if((status&JackServerStarted)) - TRACE("JACK server started\n"); - if((status&JackNameNotUnique)) + else { - client_name = jack_get_client_name(mClient); - TRACE("Client name not unique, got `%s' instead\n", client_name); + auto check_name = [name](const DeviceEntry &entry) -> bool + { return entry.mName == name; }; + auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name); + if(iter == PlaybackList.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name?name:""}; + mPortPattern = iter->mPattern; } - jack_set_process_callback(mClient, &JackPlayback::processC, this); - jack_set_buffer_size_callback(mClient, &JackPlayback::bufferSizeNotifyC, this); + mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", 1); + jack_set_process_callback(mClient, + mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); mDevice->DeviceName = name; - return ALC_NO_ERROR; } -ALCboolean JackPlayback::reset() +bool JackPlayback::reset() { - std::for_each(std::begin(mPort), std::end(mPort), - [this](jack_port_t *port) -> void - { if(port) jack_port_unregister(mClient, port); } - ); - std::fill(std::begin(mPort), std::end(mPort), nullptr); + auto unregister_port = [this](jack_port_t *port) -> void + { if(port) jack_port_unregister(mClient, port); }; + std::for_each(mPort.begin(), mPort.end(), unregister_port); + mPort.fill(nullptr); /* Ignore the requested buffer metrics and just keep one JACK-sized buffer * ready for when requested. */ mDevice->Frequency = jack_get_sample_rate(mClient); mDevice->UpdateSize = jack_get_buffer_size(mClient); - mDevice->BufferSize = mDevice->UpdateSize * 2; - - ALuint bufsize{mDevice->UpdateSize}; - if(ConfigValueUInt(mDevice->DeviceName.c_str(), "jack", "buffer-size", &bufsize)) + if(mRTMixing) + { + /* Assume only two periods when directly mixing. Should try to query + * the total port latency when connected. + */ + mDevice->BufferSize = mDevice->UpdateSize * 2; + } + else + { + const char *devname{mDevice->DeviceName.c_str()}; + uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); - mDevice->BufferSize = bufsize + mDevice->UpdateSize; + mDevice->BufferSize = bufsize + mDevice->UpdateSize; + } /* Force 32-bit float output. */ mDevice->FmtType = DevFmtFloat; - ALsizei numchans{mDevice->channelsFromFmt()}; - auto ports_end = std::begin(mPort) + numchans; - auto bad_port = std::find_if_not(std::begin(mPort), ports_end, - [this](jack_port_t *&port) -> bool - { - std::string name{"channel_" + std::to_string(&port - mPort + 1)}; - port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - return port != nullptr; - } - ); + int port_num{0}; + auto ports_end = mPort.begin() + mDevice->channelsFromFmt(); + auto bad_port = mPort.begin(); + while(bad_port != ports_end) + { + std::string name{"channel_" + std::to_string(++port_num)}; + *bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType, + JackPortIsOutput | JackPortIsTerminal, 0); + if(!*bad_port) break; + ++bad_port; + } if(bad_port != ports_end) { - ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans)); - if(bad_port == std::begin(mPort)) return ALC_FALSE; + ERR("Failed to register enough JACK ports for %s output\n", + DevFmtChannelsString(mDevice->FmtChans)); + if(bad_port == mPort.begin()) return false; - if(bad_port == std::begin(mPort)+1) + if(bad_port == mPort.begin()+1) mDevice->FmtChans = DevFmtMono; else { - ports_end = mPort+2; + ports_end = mPort.begin()+2; while(bad_port != ports_end) { jack_port_unregister(mClient, *(--bad_port)); @@ -411,78 +558,89 @@ ALCboolean JackPlayback::reset() } mDevice->FmtChans = DevFmtStereo; } - numchans = std::distance(std::begin(mPort), bad_port); } - mRing = nullptr; - mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true); - if(!mRing) - { - ERR("Failed to allocate ringbuffer\n"); - return ALC_FALSE; - } - - SetDefaultChannelOrder(mDevice); + setDefaultChannelOrder(); - return ALC_TRUE; + return true; } -ALCboolean JackPlayback::start() +void JackPlayback::start() { if(jack_activate(mClient)) - { - ERR("Failed to activate client\n"); - return ALC_FALSE; - } + throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"}; - const char **ports{jack_get_ports(mClient, nullptr, nullptr, - JackPortIsPhysical|JackPortIsInput)}; - if(ports == nullptr) + const char *devname{mDevice->DeviceName.c_str()}; + if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true)) { - ERR("No physical playback ports found\n"); - jack_deactivate(mClient); - return ALC_FALSE; - } - std::mismatch(std::begin(mPort), std::end(mPort), ports, - [this](const jack_port_t *port, const char *pname) -> bool + JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType, + JackPortIsInput)}; + if(!pnames) { - if(!port) return false; - if(!pname) + jack_deactivate(mClient); + throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"}; + } + + for(size_t i{0};i < al::size(mPort) && mPort[i];++i) + { + if(!pnames[i]) { - ERR("No physical playback port for \"%s\"\n", jack_port_name(port)); - return false; + ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i])); + break; } - if(jack_connect(mClient, jack_port_name(port), pname)) - ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(port), - pname); - return true; + 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]), + pnames[i]); } - ); - jack_free(ports); - - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this}; - return ALC_TRUE; } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { + + /* Reconfigure buffer metrics in case the server changed it since the reset + * (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; + + mRing = nullptr; + if(mRTMixing) + mPlaying.store(true, std::memory_order_release); + else + { + uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; + bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); + mDevice->BufferSize = bufsize + mDevice->UpdateSize; + + 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}; + } + 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()}; + } } - jack_deactivate(mClient); - return ALC_FALSE; } void JackPlayback::stop() { - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; - - mSem.post(); - mThread.join(); + if(mPlaying.load(std::memory_order_acquire)) + { + mKillNow.store(true, std::memory_order_release); + if(mThread.joinable()) + { + mSem.post(); + mThread.join(); + } - jack_deactivate(mClient); + jack_deactivate(mClient); + mPlaying.store(false, std::memory_order_release); + } } @@ -490,11 +648,10 @@ ClockLatency JackPlayback::getClockLatency() { ClockLatency ret; - lock(); + std::lock_guard _{mMutex}; ret.ClockTime = GetDeviceClockTime(mDevice); - ret.Latency = std::chrono::seconds{mRing->readSpace()}; + ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize}; ret.Latency /= mDevice->Frequency; - unlock(); return ret; } @@ -515,10 +672,13 @@ bool JackBackendFactory::init() if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0)) ClientOptions = static_cast(ClientOptions | JackNoStartServer); + const PathNamePair &binname = GetProcBinary(); + const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; + void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr}; jack_set_error_function(jack_msg_handler); jack_status_t status; - jack_client_t *client{jack_client_open("alsoft", ClientOptions, &status, nullptr)}; + jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)}; jack_set_error_function(old_error_cb); if(!client) { @@ -535,21 +695,37 @@ bool JackBackendFactory::init() bool JackBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback); } -void JackBackendFactory::probe(DevProbe type, std::string *outnames) +std::string JackBackendFactory::probe(BackendType type) { - switch(type) + std::string outnames; + auto append_name = [&outnames](const DeviceEntry &entry) -> void { - case DevProbe::Playback: - /* Includes null char. */ - outnames->append(jackDevice, sizeof(jackDevice)); - break; + /* Includes null char. */ + outnames.append(entry.mName.c_str(), entry.mName.length()+1); + }; - case DevProbe::Capture: - break; + const PathNamePair &binname = GetProcBinary(); + const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; + jack_status_t status; + switch(type) + { + case BackendType::Playback: + if(jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)}) + { + EnumerateDevices(client, PlaybackList); + jack_client_close(client); + } + else + WARN("jack_client_open() failed, 0x%02x\n", status); + std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); + break; + case BackendType::Capture: + break; } + return outnames; } -BackendPtr JackBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr JackBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new JackPlayback{device}}; diff --git a/modules/openal-soft/Alc/backends/jack.h b/modules/openal-soft/Alc/backends/jack.h index 10beebf..b83f24d 100644 --- a/modules/openal-soft/Alc/backends/jack.h +++ b/modules/openal-soft/Alc/backends/jack.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_JACK_H #define BACKENDS_JACK_H -#include "backends/base.h" +#include "base.h" struct JackBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/loopback.cpp b/modules/openal-soft/Alc/backends/loopback.cpp index 5d133d8..bf4ab24 100644 --- a/modules/openal-soft/Alc/backends/loopback.cpp +++ b/modules/openal-soft/Alc/backends/loopback.cpp @@ -20,40 +20,38 @@ #include "config.h" -#include "backends/loopback.h" +#include "loopback.h" -#include "alMain.h" -#include "alu.h" +#include "core/device.h" namespace { struct LoopbackBackend final : public BackendBase { - LoopbackBackend(ALCdevice *device) noexcept : BackendBase{device} { } + LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { } - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; DEF_NEWDEL(LoopbackBackend) }; -ALCenum LoopbackBackend::open(const ALCchar *name) +void LoopbackBackend::open(const char *name) { mDevice->DeviceName = name; - return ALC_NO_ERROR; } -ALCboolean LoopbackBackend::reset() +bool LoopbackBackend::reset() { - SetDefaultWFXChannelOrder(mDevice); - return ALC_TRUE; + setDefaultWFXChannelOrder(); + return true; } -ALCboolean LoopbackBackend::start() -{ return ALC_TRUE; } +void LoopbackBackend::start() +{ } void LoopbackBackend::stop() { } @@ -64,13 +62,13 @@ void LoopbackBackend::stop() bool LoopbackBackendFactory::init() { return true; } -bool LoopbackBackendFactory::querySupport(BackendType UNUSED(type)) +bool LoopbackBackendFactory::querySupport(BackendType) { return true; } -void LoopbackBackendFactory::probe(DevProbe, std::string*) -{ } +std::string LoopbackBackendFactory::probe(BackendType) +{ return std::string{}; } -BackendPtr LoopbackBackendFactory::createBackend(ALCdevice *device, BackendType UNUSED(type)) +BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType) { return BackendPtr{new LoopbackBackend{device}}; } BackendFactory &LoopbackBackendFactory::getFactory() diff --git a/modules/openal-soft/Alc/backends/loopback.h b/modules/openal-soft/Alc/backends/loopback.h index 09c085b..cb42b3c 100644 --- a/modules/openal-soft/Alc/backends/loopback.h +++ b/modules/openal-soft/Alc/backends/loopback.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_LOOPBACK_H #define BACKENDS_LOOPBACK_H -#include "backends/base.h" +#include "base.h" struct LoopbackBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/null.cpp b/modules/openal-soft/Alc/backends/null.cpp index 07907de..5a8fc25 100644 --- a/modules/openal-soft/Alc/backends/null.cpp +++ b/modules/openal-soft/Alc/backends/null.cpp @@ -20,20 +20,20 @@ #include "config.h" -#include "backends/null.h" - -#include -#ifdef HAVE_WINDOWS_H -#include -#endif +#include "null.h" +#include +#include #include -#include +#include +#include #include +#include -#include "alMain.h" -#include "alu.h" -#include "compat.h" +#include "core/device.h" +#include "almalloc.h" +#include "core/helpers.h" +#include "threads.h" namespace { @@ -42,23 +42,22 @@ using std::chrono::seconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; -constexpr ALCchar nullDevice[] = "No Output"; +constexpr char nullDevice[] = "No Output"; struct NullBackend final : public BackendBase { - NullBackend(ALCdevice *device) noexcept : BackendBase{device} { } + NullBackend(DeviceBase *device) noexcept : BackendBase{device} { } int mixerProc(); - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "NullBackend::"; } DEF_NEWDEL(NullBackend) }; @@ -71,8 +70,8 @@ int NullBackend::mixerProc() int64_t done{0}; auto start = std::chrono::steady_clock::now(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { auto now = std::chrono::steady_clock::now(); @@ -85,9 +84,7 @@ int NullBackend::mixerProc() } while(avail-done >= mDevice->UpdateSize) { - lock(); - aluMixData(mDevice, nullptr, mDevice->UpdateSize); - unlock(); + mDevice->renderSamples(nullptr, mDevice->UpdateSize, 0u); done += mDevice->UpdateSize; } @@ -108,37 +105,33 @@ int NullBackend::mixerProc() } -ALCenum NullBackend::open(const ALCchar *name) +void NullBackend::open(const char *name) { if(!name) name = nullDevice; else if(strcmp(name, nullDevice) != 0) - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; mDevice->DeviceName = name; - - return ALC_NO_ERROR; } -ALCboolean NullBackend::reset() +bool NullBackend::reset() { - SetDefaultWFXChannelOrder(mDevice); - return ALC_TRUE; + setDefaultWFXChannelOrder(); + return true; } -ALCboolean NullBackend::start() +void NullBackend::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this}; - return ALC_TRUE; } catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - return ALC_FALSE; } void NullBackend::stop() @@ -157,20 +150,22 @@ bool NullBackendFactory::init() bool NullBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback); } -void NullBackendFactory::probe(DevProbe type, std::string *outnames) +std::string NullBackendFactory::probe(BackendType type) { + std::string outnames; switch(type) { - case DevProbe::Playback: - /* Includes null char. */ - outnames->append(nullDevice, sizeof(nullDevice)); - break; - case DevProbe::Capture: - break; + case BackendType::Playback: + /* Includes null char. */ + outnames.append(nullDevice, sizeof(nullDevice)); + break; + case BackendType::Capture: + break; } + return outnames; } -BackendPtr NullBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new NullBackend{device}}; diff --git a/modules/openal-soft/Alc/backends/null.h b/modules/openal-soft/Alc/backends/null.h index f19d5b4..7048cad 100644 --- a/modules/openal-soft/Alc/backends/null.h +++ b/modules/openal-soft/Alc/backends/null.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_NULL_H #define BACKENDS_NULL_H -#include "backends/base.h" +#include "base.h" struct NullBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/oboe.cpp b/modules/openal-soft/Alc/backends/oboe.cpp new file mode 100644 index 0000000..38f048c --- /dev/null +++ b/modules/openal-soft/Alc/backends/oboe.cpp @@ -0,0 +1,384 @@ + +#include "config.h" + +#include "oboe.h" + +#include +#include +#include + +#include "alnumeric.h" +#include "core/device.h" +#include "core/logging.h" + +#include "oboe/Oboe.h" + + +namespace { + +constexpr char device_name[] = "Oboe Default"; + + +struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback { + OboePlayback(DeviceBase *device) : BackendBase{device} { } + + oboe::ManagedStream mStream; + + oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, + int32_t numFrames) override; + + void open(const char *name) override; + bool reset() override; + void start() override; + void stop() override; +}; + + +oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData, + int32_t numFrames) +{ + assert(numFrames > 0); + const int32_t numChannels{oboeStream->getChannelCount()}; + + mDevice->renderSamples(audioData, static_cast(numFrames), + static_cast(numChannels)); + return oboe::DataCallbackResult::Continue; +} + + +void OboePlayback::open(const char *name) +{ + if(!name) + name = device_name; + else if(std::strcmp(name, device_name) != 0) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; + + /* Open a basic output stream, just to ensure it can work. */ + oboe::ManagedStream stream; + oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output) + ->setPerformanceMode(oboe::PerformanceMode::LowLatency) + ->openManagedStream(stream)}; + if(result != oboe::Result::OK) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", + oboe::convertToText(result)}; + + mDevice->DeviceName = name; +} + +bool OboePlayback::reset() +{ + oboe::AudioStreamBuilder builder; + builder.setDirection(oboe::Direction::Output); + builder.setPerformanceMode(oboe::PerformanceMode::LowLatency); + /* Don't let Oboe convert. We should be able to handle anything it gives + * back. + */ + builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None); + builder.setChannelConversionAllowed(false); + builder.setFormatConversionAllowed(false); + builder.setCallback(this); + + if(mDevice->Flags.test(FrequencyRequest)) + builder.setSampleRate(static_cast(mDevice->Frequency)); + if(mDevice->Flags.test(ChannelsRequest)) + { + /* Only use mono or stereo at user request. There's no telling what + * other counts may be inferred as. + */ + builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono + : (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo + : oboe::ChannelCount::Unspecified); + } + if(mDevice->Flags.test(SampleTypeRequest)) + { + oboe::AudioFormat format{oboe::AudioFormat::Unspecified}; + switch(mDevice->FmtType) + { + case DevFmtByte: + case DevFmtUByte: + case DevFmtShort: + case DevFmtUShort: + format = oboe::AudioFormat::I16; + break; + case DevFmtInt: + case DevFmtUInt: + case DevFmtFloat: + format = oboe::AudioFormat::Float; + break; + } + builder.setFormat(format); + } + + oboe::Result result{builder.openManagedStream(mStream)}; + /* If the format failed, try asking for the defaults. */ + while(result == oboe::Result::ErrorInvalidFormat) + { + if(builder.getFormat() != oboe::AudioFormat::Unspecified) + builder.setFormat(oboe::AudioFormat::Unspecified); + else if(builder.getSampleRate() != oboe::kUnspecified) + builder.setSampleRate(oboe::kUnspecified); + else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified) + builder.setChannelCount(oboe::ChannelCount::Unspecified); + else + break; + result = builder.openManagedStream(mStream); + } + if(result != oboe::Result::OK) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", + oboe::convertToText(result)}; + mStream->setBufferSizeInFrames(mini(static_cast(mDevice->BufferSize), + mStream->getBufferCapacityInFrames())); + TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); + + if(static_cast(mStream->getChannelCount()) != mDevice->channelsFromFmt()) + { + if(mStream->getChannelCount() >= 2) + mDevice->FmtChans = DevFmtStereo; + else if(mStream->getChannelCount() == 1) + mDevice->FmtChans = DevFmtMono; + else + throw al::backend_exception{al::backend_error::DeviceError, + "Got unhandled channel count: %d", mStream->getChannelCount()}; + } + setDefaultWFXChannelOrder(); + + switch(mStream->getFormat()) + { + case oboe::AudioFormat::I16: + mDevice->FmtType = DevFmtShort; + break; + case oboe::AudioFormat::Float: + mDevice->FmtType = DevFmtFloat; + break; + 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())}; + } + mDevice->Frequency = 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 = maxu(mDevice->Frequency / 100, + static_cast(mStream->getFramesPerBurst())); + mDevice->BufferSize = maxu(mDevice->UpdateSize * 2, + static_cast(mStream->getBufferSizeInFrames())); + + return true; +} + +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", + oboe::convertToText(result)}; +} + +void OboePlayback::stop() +{ + oboe::Result result{mStream->stop()}; + if(result != oboe::Result::OK) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", + oboe::convertToText(result)}; +} + + +struct OboeCapture final : public BackendBase { + OboeCapture(DeviceBase *device) : BackendBase{device} { } + + oboe::ManagedStream mStream; + + std::vector mSamples; + uint mLastAvail{0u}; + + void open(const char *name) override; + void start() override; + void stop() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; +}; + +void OboeCapture::open(const char *name) +{ + if(!name) + name = device_name; + else if(std::strcmp(name, device_name) != 0) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; + + oboe::AudioStreamBuilder builder; + builder.setDirection(oboe::Direction::Input) + ->setPerformanceMode(oboe::PerformanceMode::LowLatency) + ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High) + ->setChannelConversionAllowed(true) + ->setFormatConversionAllowed(true) + ->setBufferCapacityInFrames(static_cast(mDevice->BufferSize)) + ->setSampleRate(static_cast(mDevice->Frequency)); + /* Only use mono or stereo at user request. There's no telling what + * other counts may be inferred as. + */ + switch(mDevice->FmtChans) + { + case DevFmtMono: + builder.setChannelCount(oboe::ChannelCount::Mono); + break; + case DevFmtStereo: + builder.setChannelCount(oboe::ChannelCount::Stereo); + break; + case DevFmtQuad: + case DevFmtX51: + case DevFmtX61: + case DevFmtX71: + case DevFmtAmbi3D: + throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", + DevFmtChannelsString(mDevice->FmtChans)}; + } + + /* FIXME: This really should support UByte, but Oboe doesn't. We'll need to + * use a temp buffer and convert. + */ + switch(mDevice->FmtType) + { + case DevFmtShort: + builder.setFormat(oboe::AudioFormat::I16); + break; + case DevFmtFloat: + builder.setFormat(oboe::AudioFormat::Float); + break; + case DevFmtByte: + case DevFmtUByte: + case DevFmtUShort: + case DevFmtInt: + case DevFmtUInt: + throw al::backend_exception{al::backend_error::DeviceError, + "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; + } + + oboe::Result result{builder.openManagedStream(mStream)}; + if(result != oboe::Result::OK) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", + oboe::convertToText(result)}; + if(static_cast(mDevice->BufferSize) > mStream->getBufferCapacityInFrames()) + throw al::backend_exception{al::backend_error::DeviceError, + "Buffer size too large (%u > %d)", mDevice->BufferSize, + mStream->getBufferCapacityInFrames()}; + auto buffer_result = mStream->setBufferSizeInFrames(static_cast(mDevice->BufferSize)); + if(!buffer_result) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set buffer size: %s", oboe::convertToText(buffer_result.error())}; + else if(buffer_result.value() < static_cast(mDevice->BufferSize)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set large enough buffer size (%u > %d)", mDevice->BufferSize, + buffer_result.value()}; + mDevice->BufferSize = static_cast(buffer_result.value()); + + TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); + + mDevice->DeviceName = 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", + oboe::convertToText(result)}; +} + +void OboeCapture::stop() +{ + /* Capture any unread samples before stopping. Oboe drops whatever's left + * in the stream. + */ + if(auto availres = mStream->getAvailableFrames()) + { + const auto avail = std::max(static_cast(availres.value()), mLastAvail); + const size_t frame_size{static_cast(mStream->getBytesPerFrame())}; + const size_t pos{mSamples.size()}; + mSamples.resize(pos + avail*frame_size); + + auto result = mStream->read(&mSamples[pos], availres.value(), 0); + uint got{bool{result} ? static_cast(result.value()) : 0u}; + if(got < avail) + std::fill_n(&mSamples[pos + got*frame_size], (avail-got)*frame_size, al::byte{}); + mLastAvail = 0; + } + + const oboe::Result result{mStream->stop()}; + if(result != oboe::Result::OK) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", + oboe::convertToText(result)}; +} + +uint OboeCapture::availableSamples() +{ + /* Keep track of the max available frame count, to ensure it doesn't go + * backwards. + */ + if(auto result = mStream->getAvailableFrames()) + mLastAvail = std::max(static_cast(result.value()), mLastAvail); + + const auto frame_size = static_cast(mStream->getBytesPerFrame()); + return static_cast(mSamples.size()/frame_size) + mLastAvail; +} + +void OboeCapture::captureSamples(al::byte *buffer, uint samples) +{ + const auto frame_size = static_cast(mStream->getBytesPerFrame()); + if(const size_t storelen{mSamples.size()}) + { + const auto instore = static_cast(storelen / frame_size); + const uint tocopy{std::min(samples, instore) * frame_size}; + std::copy_n(mSamples.begin(), tocopy, buffer); + mSamples.erase(mSamples.begin(), mSamples.begin() + tocopy); + + buffer += tocopy; + samples -= tocopy/frame_size; + if(!samples) return; + } + + auto result = mStream->read(buffer, static_cast(samples), 0); + uint got{bool{result} ? static_cast(result.value()) : 0u}; + if(got < samples) + std::fill_n(buffer + got*frame_size, (samples-got)*frame_size, al::byte{}); + mLastAvail = std::max(mLastAvail, samples) - samples; +} + +} // namespace + +bool OboeBackendFactory::init() { return true; } + +bool OboeBackendFactory::querySupport(BackendType type) +{ return type == BackendType::Playback || type == BackendType::Capture; } + +std::string OboeBackendFactory::probe(BackendType type) +{ + switch(type) + { + case BackendType::Playback: + case BackendType::Capture: + /* Includes null char. */ + return std::string{device_name, sizeof(device_name)}; + } + return std::string{}; +} + +BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type) +{ + if(type == BackendType::Playback) + return BackendPtr{new OboePlayback{device}}; + if(type == BackendType::Capture) + return BackendPtr{new OboeCapture{device}}; + return BackendPtr{}; +} + +BackendFactory &OboeBackendFactory::getFactory() +{ + static OboeBackendFactory factory{}; + return factory; +} diff --git a/modules/openal-soft/Alc/backends/oboe.h b/modules/openal-soft/Alc/backends/oboe.h new file mode 100644 index 0000000..a39c244 --- /dev/null +++ b/modules/openal-soft/Alc/backends/oboe.h @@ -0,0 +1,19 @@ +#ifndef BACKENDS_OBOE_H +#define BACKENDS_OBOE_H + +#include "base.h" + +struct OboeBackendFactory final : public BackendFactory { +public: + bool init() override; + + bool querySupport(BackendType type) override; + + std::string probe(BackendType type) override; + + BackendPtr createBackend(DeviceBase *device, BackendType type) override; + + static BackendFactory &getFactory(); +}; + +#endif /* BACKENDS_OBOE_H */ diff --git a/modules/openal-soft/Alc/backends/opensl.cpp b/modules/openal-soft/Alc/backends/opensl.cpp index 818b381..85a5f48 100644 --- a/modules/openal-soft/Alc/backends/opensl.cpp +++ b/modules/openal-soft/Alc/backends/opensl.cpp @@ -21,20 +21,25 @@ #include "config.h" -#include "backends/opensl.h" +#include "opensl.h" #include #include #include +#include +#include #include #include -#include "alMain.h" -#include "alu.h" +#include "albit.h" +#include "alnumeric.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "opthelpers.h" #include "ringbuffer.h" #include "threads.h" -#include "compat.h" #include #include @@ -49,109 +54,112 @@ namespace { #define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS -constexpr ALCchar opensl_device[] = "OpenSL"; +constexpr char opensl_device[] = "OpenSL"; -SLuint32 GetChannelMask(DevFmtChannels chans) +constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept { switch(chans) { - case DevFmtMono: return SL_SPEAKER_FRONT_CENTER; - case DevFmtStereo: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT; - case DevFmtQuad: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT; - case DevFmtX51: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| - SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT; - case DevFmtX51Rear: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| - SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT; - case DevFmtX61: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| - SL_SPEAKER_BACK_CENTER| - SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT; - case DevFmtX71: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| - SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| - SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT| - SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT; - case DevFmtAmbi3D: - break; + case DevFmtMono: return SL_SPEAKER_FRONT_CENTER; + case DevFmtStereo: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + case DevFmtQuad: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | + SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT; + case DevFmtX51: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | + SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_SIDE_LEFT | + SL_SPEAKER_SIDE_RIGHT; + case DevFmtX61: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | + SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER | + SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT; + case DevFmtX71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | + SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT | + SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT; + case DevFmtAmbi3D: + break; } return 0; } -#ifdef SL_DATAFORMAT_PCM_EX -SLuint32 GetTypeRepresentation(DevFmtType type) +#ifdef SL_ANDROID_DATAFORMAT_PCM_EX +constexpr SLuint32 GetTypeRepresentation(DevFmtType type) noexcept { switch(type) { - case DevFmtUByte: - case DevFmtUShort: - case DevFmtUInt: - return SL_PCM_REPRESENTATION_UNSIGNED_INT; - case DevFmtByte: - case DevFmtShort: - case DevFmtInt: - return SL_PCM_REPRESENTATION_SIGNED_INT; - case DevFmtFloat: - return SL_PCM_REPRESENTATION_FLOAT; + case DevFmtUByte: + case DevFmtUShort: + case DevFmtUInt: + return SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT; + case DevFmtByte: + case DevFmtShort: + case DevFmtInt: + return SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; + case DevFmtFloat: + return SL_ANDROID_PCM_REPRESENTATION_FLOAT; } return 0; } #endif -const char *res_str(SLresult result) +constexpr SLuint32 GetByteOrderEndianness() noexcept +{ + if(al::endian::native == al::endian::little) + return SL_BYTEORDER_LITTLEENDIAN; + return SL_BYTEORDER_BIGENDIAN; +} + +const char *res_str(SLresult result) noexcept { switch(result) { - case SL_RESULT_SUCCESS: return "Success"; - case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated"; - case SL_RESULT_PARAMETER_INVALID: return "Parameter invalid"; - case SL_RESULT_MEMORY_FAILURE: return "Memory failure"; - case SL_RESULT_RESOURCE_ERROR: return "Resource error"; - case SL_RESULT_RESOURCE_LOST: return "Resource lost"; - case SL_RESULT_IO_ERROR: return "I/O error"; - case SL_RESULT_BUFFER_INSUFFICIENT: return "Buffer insufficient"; - case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted"; - case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported"; - case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found"; - case SL_RESULT_PERMISSION_DENIED: return "Permission denied"; - case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported"; - case SL_RESULT_INTERNAL_ERROR: return "Internal error"; - case SL_RESULT_UNKNOWN_ERROR: return "Unknown error"; - case SL_RESULT_OPERATION_ABORTED: return "Operation aborted"; - case SL_RESULT_CONTROL_LOST: return "Control lost"; + case SL_RESULT_SUCCESS: return "Success"; + case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated"; + case SL_RESULT_PARAMETER_INVALID: return "Parameter invalid"; + case SL_RESULT_MEMORY_FAILURE: return "Memory failure"; + case SL_RESULT_RESOURCE_ERROR: return "Resource error"; + case SL_RESULT_RESOURCE_LOST: return "Resource lost"; + case SL_RESULT_IO_ERROR: return "I/O error"; + case SL_RESULT_BUFFER_INSUFFICIENT: return "Buffer insufficient"; + case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted"; + case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported"; + case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found"; + case SL_RESULT_PERMISSION_DENIED: return "Permission denied"; + case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported"; + case SL_RESULT_INTERNAL_ERROR: return "Internal error"; + case SL_RESULT_UNKNOWN_ERROR: return "Unknown error"; + case SL_RESULT_OPERATION_ABORTED: return "Operation aborted"; + case SL_RESULT_CONTROL_LOST: return "Control lost"; #ifdef SL_RESULT_READONLY - case SL_RESULT_READONLY: return "ReadOnly"; + case SL_RESULT_READONLY: return "ReadOnly"; #endif #ifdef SL_RESULT_ENGINEOPTION_UNSUPPORTED - case SL_RESULT_ENGINEOPTION_UNSUPPORTED: return "Engine option unsupported"; + case SL_RESULT_ENGINEOPTION_UNSUPPORTED: return "Engine option unsupported"; #endif #ifdef SL_RESULT_SOURCE_SINK_INCOMPATIBLE - case SL_RESULT_SOURCE_SINK_INCOMPATIBLE: return "Source/Sink incompatible"; + case SL_RESULT_SOURCE_SINK_INCOMPATIBLE: return "Source/Sink incompatible"; #endif } return "Unknown error code"; } #define PRINTERR(x, s) do { \ - if(UNLIKELY((x) != SL_RESULT_SUCCESS)) \ + if UNLIKELY((x) != SL_RESULT_SUCCESS) \ ERR("%s: %s\n", (s), res_str((x))); \ } while(0) struct OpenSLPlayback final : public BackendBase { - OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~OpenSLPlayback() override; - static void processC(SLAndroidSimpleBufferQueueItf bq, void *context); - void process(SLAndroidSimpleBufferQueueItf bq); + void process(SLAndroidSimpleBufferQueueItf bq) noexcept; + static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept + { static_cast(context)->process(bq); } int mixerProc(); - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; ClockLatency getClockLatency() override; @@ -168,12 +176,13 @@ struct OpenSLPlayback final : public BackendBase { RingBufferPtr mRing{nullptr}; al::semaphore mSem; - ALsizei mFrameSize{0}; + std::mutex mMutex; + + uint mFrameSize{0}; std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "OpenSLPlayback::"; } DEF_NEWDEL(OpenSLPlayback) }; @@ -195,10 +204,7 @@ OpenSLPlayback::~OpenSLPlayback() /* this callback handler is called every time a buffer finishes playing */ -void OpenSLPlayback::processC(SLAndroidSimpleBufferQueueItf bq, void *context) -{ static_cast(context)->process(bq); } - -void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf UNUSED(bq)) +void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept { /* A note on the ringbuffer usage: The buffer queue seems to hold on to the * pointer passed to the Enqueue method, rather than copying the audio. @@ -229,12 +235,13 @@ int OpenSLPlayback::mixerProc() PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY"); } - lock(); + const size_t frame_step{mDevice->channelsFromFmt()}; + if(SL_RESULT_SUCCESS != result) - aluHandleDisconnect(mDevice, "Failed to get playback buffer: 0x%08x", result); + mDevice->handleDisconnect("Failed to get playback buffer: 0x%08x", result); - while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { if(mRing->writeSpace() == 0) { @@ -249,26 +256,28 @@ int OpenSLPlayback::mixerProc() } if(SL_RESULT_SUCCESS != result) { - aluHandleDisconnect(mDevice, "Failed to start platback: 0x%08x", result); + mDevice->handleDisconnect("Failed to start playback: 0x%08x", result); break; } if(mRing->writeSpace() == 0) { - unlock(); mSem.wait(); - lock(); continue; } } + std::unique_lock dlock{mMutex}; auto data = mRing->getWriteVector(); - aluMixData(mDevice, data.first.buf, data.first.len*mDevice->UpdateSize); + mDevice->renderSamples(data.first.buf, + static_cast(data.first.len)*mDevice->UpdateSize, frame_step); if(data.second.len > 0) - aluMixData(mDevice, data.second.buf, data.second.len*mDevice->UpdateSize); + mDevice->renderSamples(data.second.buf, + static_cast(data.second.len)*mDevice->UpdateSize, frame_step); size_t todo{data.first.len + data.second.len}; mRing->writeAdvance(todo); + dlock.unlock(); for(size_t i{0};i < todo;i++) { @@ -283,7 +292,7 @@ int OpenSLPlayback::mixerProc() PRINTERR(result, "bufferQueue->Enqueue"); if(SL_RESULT_SUCCESS != result) { - aluHandleDisconnect(mDevice, "Failed to queue audio: 0x%08x", result); + mDevice->handleDisconnect("Failed to queue audio: 0x%08x", result); break; } @@ -291,18 +300,21 @@ int OpenSLPlayback::mixerProc() data.first.buf += mDevice->UpdateSize*mFrameSize; } } - unlock(); return 0; } -ALCenum OpenSLPlayback::open(const ALCchar *name) +void OpenSLPlayback::open(const char *name) { if(!name) name = opensl_device; else if(strcmp(name, opensl_device) != 0) - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; + + /* There's only one device, so if it's already open, there's nothing to do. */ + if(mEngineObj) return; // create engine SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; @@ -339,21 +351,15 @@ ALCenum OpenSLPlayback::open(const ALCchar *name) mEngineObj = nullptr; mEngine = nullptr; - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to initialize OpenSL device: 0x%08x", result}; } mDevice->DeviceName = name; - return ALC_NO_ERROR; } -ALCboolean OpenSLPlayback::reset() +bool OpenSLPlayback::reset() { - SLDataLocator_AndroidSimpleBufferQueue loc_bufq; - SLDataLocator_OutputMix loc_outmix; - SLDataSource audioSrc; - SLDataSink audioSnk; - SLInterfaceID ids[2]; - SLboolean reqs[2]; SLresult result; if(mBufferQueueObj) @@ -363,7 +369,7 @@ ALCboolean OpenSLPlayback::reset() mRing = nullptr; #if 0 - if(!(mDevice->Flags&DEVICE_FREQUENCY_REQUEST)) + if(!mDevice->Flags.get()) { /* FIXME: Disabled until I figure out how to get the Context needed for * the getSystemService call. @@ -433,53 +439,74 @@ ALCboolean OpenSLPlayback::reset() mDevice->FmtChans = DevFmtStereo; mDevice->FmtType = DevFmtShort; - SetDefaultWFXChannelOrder(mDevice); + setDefaultWFXChannelOrder(); mFrameSize = mDevice->frameSizeFromFmt(); + const std::array ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }}; + const std::array reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }}; + + SLDataLocator_OutputMix loc_outmix{}; + loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; + loc_outmix.outputMix = mOutputMix; + + SLDataSink audioSnk{}; + audioSnk.pLocator = &loc_outmix; + audioSnk.pFormat = nullptr; + + SLDataLocator_AndroidSimpleBufferQueue loc_bufq{}; loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; loc_bufq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize; -#ifdef SL_DATAFORMAT_PCM_EX - SLDataFormat_PCM_EX format_pcm; - format_pcm.formatType = SL_DATAFORMAT_PCM_EX; - format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.sampleRate = mDevice->Frequency * 1000; - format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; - format_pcm.containerSize = format_pcm.bitsPerSample; - format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : - SL_BYTEORDER_BIGENDIAN; - format_pcm.representation = GetTypeRepresentation(mDevice->FmtType); -#else - SLDataFormat_PCM format_pcm; - format_pcm.formatType = SL_DATAFORMAT_PCM; - format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.samplesPerSec = mDevice->Frequency * 1000; - format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; - format_pcm.containerSize = format_pcm.bitsPerSample; - format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : - SL_BYTEORDER_BIGENDIAN; -#endif + SLDataSource audioSrc{}; +#ifdef SL_ANDROID_DATAFORMAT_PCM_EX + SLAndroidDataFormat_PCM_EX format_pcm_ex{}; + format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; + format_pcm_ex.numChannels = mDevice->channelsFromFmt(); + format_pcm_ex.sampleRate = mDevice->Frequency * 1000; + format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8; + format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample; + format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans); + format_pcm_ex.endianness = GetByteOrderEndianness(); + format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType); audioSrc.pLocator = &loc_bufq; - audioSrc.pFormat = &format_pcm; + audioSrc.pFormat = &format_pcm_ex; - loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; - loc_outmix.outputMix = mOutputMix; - audioSnk.pLocator = &loc_outmix; - audioSnk.pFormat = nullptr; + result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(), + ids.data(), reqs.data()); + if(SL_RESULT_SUCCESS != result) +#endif + { + /* Alter sample type according to what SLDataFormat_PCM can support. */ + switch(mDevice->FmtType) + { + case DevFmtByte: mDevice->FmtType = DevFmtUByte; break; + case DevFmtUInt: mDevice->FmtType = DevFmtInt; break; + case DevFmtFloat: + case DevFmtUShort: mDevice->FmtType = DevFmtShort; break; + case DevFmtUByte: + case DevFmtShort: + case DevFmtInt: + break; + } + SLDataFormat_PCM format_pcm{}; + format_pcm.formatType = SL_DATAFORMAT_PCM; + format_pcm.numChannels = mDevice->channelsFromFmt(); + format_pcm.samplesPerSec = mDevice->Frequency * 1000; + format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; + format_pcm.containerSize = format_pcm.bitsPerSample; + format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); + format_pcm.endianness = GetByteOrderEndianness(); - ids[0] = SL_IID_ANDROIDSIMPLEBUFFERQUEUE; - reqs[0] = SL_BOOLEAN_TRUE; - ids[1] = SL_IID_ANDROIDCONFIGURATION; - reqs[1] = SL_BOOLEAN_FALSE; + audioSrc.pLocator = &loc_bufq; + audioSrc.pFormat = &format_pcm; - result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, COUNTOF(ids), - ids, reqs); - PRINTERR(result, "engine->CreateAudioPlayer"); + result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(), + ids.data(), reqs.data()); + PRINTERR(result, "engine->CreateAudioPlayer"); + } if(SL_RESULT_SUCCESS == result) { /* Set the stream type to "media" (games, music, etc), if possible. */ @@ -504,15 +531,8 @@ ALCboolean OpenSLPlayback::reset() } if(SL_RESULT_SUCCESS == result) { - const ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; - try { - mRing = CreateRingBuffer(num_updates, mFrameSize*mDevice->UpdateSize, true); - } - catch(std::exception& e) { - ERR("Failed allocating ring buffer %ux%ux%u: %s\n", mDevice->UpdateSize, - num_updates, mFrameSize, e.what()); - result = SL_RESULT_MEMORY_FAILURE; - } + const uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; + mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->UpdateSize, true); } if(SL_RESULT_SUCCESS != result) @@ -521,13 +541,13 @@ ALCboolean OpenSLPlayback::reset() VCALL0(mBufferQueueObj,Destroy)(); mBufferQueueObj = nullptr; - return ALC_FALSE; + return false; } - return ALC_TRUE; + return true; } -ALCboolean OpenSLPlayback::start() +void OpenSLPlayback::start() { mRing->reset(); @@ -535,24 +555,23 @@ ALCboolean OpenSLPlayback::start() SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue)}; PRINTERR(result, "bufferQueue->GetInterface"); + if(SL_RESULT_SUCCESS == result) + { + result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this); + PRINTERR(result, "bufferQueue->RegisterCallback"); + } if(SL_RESULT_SUCCESS != result) - return ALC_FALSE; - - result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this); - PRINTERR(result, "bufferQueue->RegisterCallback"); - if(SL_RESULT_SUCCESS != result) return ALC_FALSE; + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to register callback: 0x%08x", result}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this); - return ALC_TRUE; } catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - return ALC_FALSE; } void OpenSLPlayback::stop() @@ -600,28 +619,28 @@ ClockLatency OpenSLPlayback::getClockLatency() { ClockLatency ret; - lock(); + std::lock_guard _{mMutex}; ret.ClockTime = GetDeviceClockTime(mDevice); ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize}; ret.Latency /= mDevice->Frequency; - unlock(); return ret; } struct OpenSLCapture final : public BackendBase { - OpenSLCapture(ALCdevice *device) noexcept : BackendBase{device} { } + OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~OpenSLCapture() override; - static void processC(SLAndroidSimpleBufferQueueItf bq, void *context); - void process(SLAndroidSimpleBufferQueueItf bq); + void process(SLAndroidSimpleBufferQueueItf bq) noexcept; + static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept + { static_cast(context)->process(bq); } - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; /* engine interfaces */ SLObjectItf mEngineObj{nullptr}; @@ -631,11 +650,10 @@ struct OpenSLCapture final : public BackendBase { SLObjectItf mRecordObj{nullptr}; RingBufferPtr mRing{nullptr}; - ALCuint mSplOffset{0u}; + uint mSplOffset{0u}; - ALsizei mFrameSize{0}; + uint mFrameSize{0}; - static constexpr inline const char *CurrentPrefix() noexcept { return "OpenSLCapture::"; } DEF_NEWDEL(OpenSLCapture) }; @@ -652,22 +670,20 @@ OpenSLCapture::~OpenSLCapture() } -void OpenSLCapture::processC(SLAndroidSimpleBufferQueueItf bq, void *context) -{ static_cast(context)->process(bq); } - -void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf UNUSED(bq)) +void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept { /* A new chunk has been written into the ring buffer, advance it. */ mRing->writeAdvance(1); } -ALCenum OpenSLCapture::open(const ALCchar* name) +void OpenSLCapture::open(const char* name) { if(!name) name = opensl_device; else if(strcmp(name, opensl_device) != 0) - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; PRINTERR(result, "slCreateEngine"); @@ -685,27 +701,21 @@ ALCenum OpenSLCapture::open(const ALCchar* name) { mFrameSize = mDevice->frameSizeFromFmt(); /* Ensure the total length is at least 100ms */ - ALsizei length{maxi(mDevice->BufferSize, mDevice->Frequency/10)}; + uint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)}; /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */ - ALsizei update_len{clampi(mDevice->BufferSize/3, mDevice->Frequency/100, + uint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100, mDevice->Frequency/100*5)}; - ALsizei num_updates{(length+update_len-1) / update_len}; + uint num_updates{(length+update_len-1) / update_len}; - try { - mRing = CreateRingBuffer(num_updates, update_len*mFrameSize, false); + mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false); - mDevice->UpdateSize = update_len; - mDevice->BufferSize = mRing->writeSpace() * update_len; - } - catch(std::exception& e) { - ERR("Failed to allocate ring buffer: %s\n", e.what()); - result = SL_RESULT_MEMORY_FAILURE; - } + mDevice->UpdateSize = update_len; + mDevice->BufferSize = static_cast(mRing->writeSpace() * update_len); } if(SL_RESULT_SUCCESS == result) { - const SLInterfaceID ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }; - const SLboolean reqs[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }; + const std::array ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }}; + const std::array reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }}; SLDataLocator_IODevice loc_dev{}; loc_dev.locatorType = SL_DATALOCATOR_IODEVICE; @@ -721,34 +731,47 @@ ALCenum OpenSLCapture::open(const ALCchar* name) loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; loc_bq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize; -#ifdef SL_DATAFORMAT_PCM_EX - SLDataFormat_PCM_EX format_pcm{}; - format_pcm.formatType = SL_DATAFORMAT_PCM_EX; - format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.sampleRate = mDevice->Frequency * 1000; - format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; - format_pcm.containerSize = format_pcm.bitsPerSample; - format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; - format_pcm.representation = GetTypeRepresentation(mDevice->FmtType); -#else - SLDataFormat_PCM format_pcm{}; - format_pcm.formatType = SL_DATAFORMAT_PCM; - format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.samplesPerSec = mDevice->Frequency * 1000; - format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; - format_pcm.containerSize = format_pcm.bitsPerSample; - format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); - format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; -#endif - SLDataSink audioSnk{}; - audioSnk.pLocator = &loc_bq; - audioSnk.pFormat = &format_pcm; +#ifdef SL_ANDROID_DATAFORMAT_PCM_EX + SLAndroidDataFormat_PCM_EX format_pcm_ex{}; + format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; + format_pcm_ex.numChannels = mDevice->channelsFromFmt(); + format_pcm_ex.sampleRate = mDevice->Frequency * 1000; + format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8; + format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample; + format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans); + format_pcm_ex.endianness = GetByteOrderEndianness(); + format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType); + audioSnk.pLocator = &loc_bq; + audioSnk.pFormat = &format_pcm_ex; result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk, - COUNTOF(ids), ids, reqs); - PRINTERR(result, "engine->CreateAudioRecorder"); + ids.size(), ids.data(), reqs.data()); + if(SL_RESULT_SUCCESS != result) +#endif + { + /* Fallback to SLDataFormat_PCM only if it supports the desired + * sample type. + */ + if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtShort + || mDevice->FmtType == DevFmtInt) + { + SLDataFormat_PCM format_pcm{}; + format_pcm.formatType = SL_DATAFORMAT_PCM; + format_pcm.numChannels = mDevice->channelsFromFmt(); + format_pcm.samplesPerSec = mDevice->Frequency * 1000; + format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; + format_pcm.containerSize = format_pcm.bitsPerSample; + format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); + format_pcm.endianness = GetByteOrderEndianness(); + + audioSnk.pLocator = &loc_bq; + audioSnk.pFormat = &format_pcm; + result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk, + ids.size(), ids.data(), reqs.data()); + } + PRINTERR(result, "engine->CreateAudioRecorder"); + } } if(SL_RESULT_SUCCESS == result) { @@ -786,9 +809,12 @@ ALCenum OpenSLCapture::open(const ALCchar* name) } if(SL_RESULT_SUCCESS == result) { - const ALuint chunk_size{mDevice->UpdateSize * mFrameSize}; + const uint chunk_size{mDevice->UpdateSize * mFrameSize}; + const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::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++) { result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size); @@ -812,14 +838,14 @@ ALCenum OpenSLCapture::open(const ALCchar* name) mEngineObj = nullptr; mEngine = nullptr; - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to initialize OpenSL device: 0x%08x", result}; } mDevice->DeviceName = name; - return ALC_NO_ERROR; } -ALCboolean OpenSLCapture::start() +void OpenSLCapture::start() { SLRecordItf record; SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)}; @@ -830,14 +856,9 @@ ALCboolean OpenSLCapture::start() result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING); PRINTERR(result, "record->SetRecordState"); } - if(SL_RESULT_SUCCESS != result) - { - aluHandleDisconnect(mDevice, "Failed to start capture: 0x%08x", result); - return ALC_FALSE; - } - - return ALC_TRUE; + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start capture: 0x%08x", result}; } void OpenSLCapture::stop() @@ -853,58 +874,78 @@ void OpenSLCapture::stop() } } -ALCenum OpenSLCapture::captureSamples(void* buffer, ALCuint samples) +void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) { - ALsizei chunk_size = mDevice->UpdateSize * mFrameSize; - SLAndroidSimpleBufferQueueItf bufferQueue; - SLresult result; - ALCuint i; - - result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue); - PRINTERR(result, "recordObj->GetInterface"); + const uint update_size{mDevice->UpdateSize}; + const uint chunk_size{update_size * mFrameSize}; + const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0}; /* Read the desired samples from the ring buffer then advance its read * pointer. */ - auto data = mRing->getReadVector(); - for(i = 0;i < samples;) + size_t adv_count{0}; + auto rdata = mRing->getReadVector(); + for(uint i{0};i < samples;) { - ALCuint rem{minu(samples - i, mDevice->UpdateSize - mSplOffset)}; - memcpy((ALCbyte*)buffer + i*mFrameSize, data.first.buf + mSplOffset*mFrameSize, - rem * mFrameSize); + const uint rem{minu(samples - i, update_size - mSplOffset)}; + std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize}, + buffer + i*size_t{mFrameSize}); mSplOffset += rem; - if(mSplOffset == mDevice->UpdateSize) + if(mSplOffset == update_size) { /* Finished a chunk, reset the offset and advance the read pointer. */ mSplOffset = 0; - mRing->readAdvance(1); - result = VCALL(bufferQueue,Enqueue)(data.first.buf, chunk_size); - PRINTERR(result, "bufferQueue->Enqueue"); - if(SL_RESULT_SUCCESS != result) break; - - data.first.len--; - if(!data.first.len) - data.first = data.second; + ++adv_count; + rdata.first.len -= 1; + if(!rdata.first.len) + rdata.first = rdata.second; else - data.first.buf += chunk_size; + rdata.first.buf += chunk_size; } i += rem; } + mRing->readAdvance(adv_count); - if(SL_RESULT_SUCCESS != result) + SLAndroidSimpleBufferQueueItf bufferQueue{}; + if LIKELY(mDevice->Connected.load(std::memory_order_acquire)) { - aluHandleDisconnect(mDevice, "Failed to update capture buffer: 0x%08x", result); - return ALC_INVALID_DEVICE; + const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &bufferQueue)}; + PRINTERR(result, "recordObj->GetInterface"); + if UNLIKELY(SL_RESULT_SUCCESS != result) + { + mDevice->handleDisconnect("Failed to get capture buffer queue: 0x%08x", result); + bufferQueue = nullptr; + } } - return ALC_NO_ERROR; + if LIKELY(bufferQueue) + { + SLresult result{SL_RESULT_SUCCESS}; + auto wdata = mRing->getWriteVector(); + std::fill_n(wdata.first.buf, wdata.first.len*chunk_size, silence); + for(size_t i{0u};i < wdata.first.len && SL_RESULT_SUCCESS == result;i++) + { + result = VCALL(bufferQueue,Enqueue)(wdata.first.buf + chunk_size*i, chunk_size); + PRINTERR(result, "bufferQueue->Enqueue"); + } + if(wdata.second.len > 0) + { + std::fill_n(wdata.second.buf, wdata.second.len*chunk_size, silence); + for(size_t i{0u};i < wdata.second.len && SL_RESULT_SUCCESS == result;i++) + { + result = VCALL(bufferQueue,Enqueue)(wdata.second.buf + chunk_size*i, chunk_size); + PRINTERR(result, "bufferQueue->Enqueue"); + } + } + } } -ALCuint OpenSLCapture::availableSamples() -{ return mRing->readSpace()*mDevice->UpdateSize - mSplOffset; } +uint OpenSLCapture::availableSamples() +{ return static_cast(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); } } // namespace @@ -913,19 +954,21 @@ bool OSLBackendFactory::init() { return true; } bool OSLBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -void OSLBackendFactory::probe(DevProbe type, std::string *outnames) +std::string OSLBackendFactory::probe(BackendType type) { + std::string outnames; switch(type) { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(opensl_device, sizeof(opensl_device)); - break; + case BackendType::Playback: + case BackendType::Capture: + /* Includes null char. */ + outnames.append(opensl_device, sizeof(opensl_device)); + break; } + return outnames; } -BackendPtr OSLBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new OpenSLPlayback{device}}; diff --git a/modules/openal-soft/Alc/backends/opensl.h b/modules/openal-soft/Alc/backends/opensl.h index 809aa33..b816244 100644 --- a/modules/openal-soft/Alc/backends/opensl.h +++ b/modules/openal-soft/Alc/backends/opensl.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_OSL_H #define BACKENDS_OSL_H -#include "backends/base.h" +#include "base.h" struct OSLBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/oss.cpp b/modules/openal-soft/Alc/backends/oss.cpp index cc385ed..6d4fa26 100644 --- a/modules/openal-soft/Alc/backends/oss.cpp +++ b/modules/openal-soft/Alc/backends/oss.cpp @@ -20,34 +20,38 @@ #include "config.h" -#include "backends/oss.h" +#include "oss.h" +#include +#include #include -#include -#include #include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include #include - -#include "alMain.h" -#include "alu.h" -#include "alconfig.h" +#include +#include +#include +#include +#include + +#include "albyte.h" +#include "alc/alconfig.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "aloptional.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" #include "ringbuffer.h" -#include "compat.h" +#include "threads.h" +#include "vector.h" #include @@ -80,27 +84,14 @@ namespace { constexpr char DefaultName[] = "OSS Default"; -const char *DefaultPlayback{"/dev/dsp"}; -const char *DefaultCapture{"/dev/dsp"}; +std::string DefaultPlayback{"/dev/dsp"}; +std::string DefaultCapture{"/dev/dsp"}; struct DevMap { std::string name; std::string device_name; - - template - DevMap(StrT0&& name_, StrT1&& devname_) - : name{std::forward(name_)}, device_name{std::forward(devname_)} - { } }; -bool checkName(const al::vector &list, const std::string &name) -{ - return std::find_if(list.cbegin(), list.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ) != list.cend(); -} - al::vector PlaybackDevices; al::vector CaptureDevices; @@ -109,60 +100,59 @@ al::vector CaptureDevices; #define DSP_CAP_OUTPUT 0x00020000 #define DSP_CAP_INPUT 0x00010000 -void ALCossListPopulate(al::vector *devlist, int type) +void ALCossListPopulate(al::vector &devlist, int type) { - devlist->emplace_back(DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback); + devlist.emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback}); } #else -void ALCossListAppend(al::vector *list, const char *handle, size_t hlen, const char *path, size_t plen) +void ALCossListAppend(al::vector &list, al::span handle, al::span path) { #ifdef ALC_OSS_DEVNODE_TRUC - for(size_t i{0};i < plen;i++) + for(size_t i{0};i < path.size();++i) { - if(path[i] == '.') + if(path[i] == '.' && handle.size() + i >= path.size()) { - if(strncmp(path + i, handle + hlen + i - plen, plen - i) == 0) - hlen = hlen + i - plen; - plen = i; + const size_t hoffset{handle.size() + i - path.size()}; + if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0) + handle = handle.first(hoffset); + path = path.first(i); } } #endif - if(handle[0] == '\0') - { + if(handle.empty()) handle = path; - hlen = plen; - } - std::string basename{handle, hlen}; - basename.erase(std::find(basename.begin(), basename.end(), '\0'), basename.end()); - std::string devname{path, plen}; - devname.erase(std::find(devname.begin(), devname.end(), '\0'), devname.end()); + std::string basename{handle.data(), handle.size()}; + std::string devname{path.data(), path.size()}; - auto iter = std::find_if(list->cbegin(), list->cend(), - [&devname](const DevMap &entry) -> bool - { return entry.device_name == devname; } - ); - if(iter != list->cend()) + auto match_devname = [&devname](const DevMap &entry) -> bool + { return entry.device_name == devname; }; + if(std::find_if(list.cbegin(), list.cend(), match_devname) != list.cend()) return; + auto checkName = [&list](const std::string &name) -> bool + { + auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; }; + return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); + }; int count{1}; std::string newname{basename}; - while(checkName(PlaybackDevices, newname)) + while(checkName(newname)) { newname = basename; newname += " #"; newname += std::to_string(++count); } - list->emplace_back(std::move(newname), std::move(devname)); - const DevMap &entry = list->back(); + list.emplace_back(DevMap{std::move(newname), std::move(devname)}); + const DevMap &entry = list.back(); TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } -void ALCossListPopulate(al::vector *devlist, int type_flag) +void ALCossListPopulate(al::vector &devlist, int type_flag) { int fd{open("/dev/mixer", O_RDONLY)}; if(fd < 0) @@ -190,21 +180,14 @@ void ALCossListPopulate(al::vector *devlist, int type_flag) if(!(ai.caps&type_flag) || ai.devnode[0] == '\0') continue; - const char *handle; - size_t len; + al::span handle; if(ai.handle[0] != '\0') - { - len = strnlen(ai.handle, sizeof(ai.handle)); - handle = ai.handle; - } + handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))}; else - { - len = strnlen(ai.name, sizeof(ai.name)); - handle = ai.name; - } + handle = {ai.name, strnlen(ai.name, sizeof(ai.name))}; + al::span devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))}; - ALCossListAppend(devlist, handle, len, ai.devnode, - strnlen(ai.devnode, sizeof(ai.devnode))); + ALCossListAppend(devlist, handle, devnode); } done: @@ -212,28 +195,28 @@ done: close(fd); fd = -1; - const char *defdev{(type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback}; - auto iter = std::find_if(devlist->cbegin(), devlist->cend(), + const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()}; + auto iter = std::find_if(devlist.cbegin(), devlist.cend(), [defdev](const DevMap &entry) -> bool { return entry.device_name == defdev; } ); - if(iter == devlist->cend()) - devlist->insert(devlist->begin(), DevMap{DefaultName, defdev}); + if(iter == devlist.cend()) + devlist.insert(devlist.begin(), DevMap{DefaultName, defdev}); else { DevMap entry{std::move(*iter)}; - devlist->erase(iter); - devlist->insert(devlist->begin(), std::move(entry)); + devlist.erase(iter); + devlist.insert(devlist.begin(), std::move(entry)); } - devlist->shrink_to_fit(); + devlist.shrink_to_fit(); } #endif -int log2i(ALCuint x) +uint log2i(uint x) { - int y = 0; - while (x > 1) + uint y{0}; + while(x > 1) { x >>= 1; y++; @@ -243,31 +226,30 @@ int log2i(ALCuint x) struct OSSPlayback final : public BackendBase { - OSSPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~OSSPlayback() override; int mixerProc(); - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; int mFd{-1}; - al::vector mMixData; + al::vector mMixData; std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "OSSPlayback::"; } DEF_NEWDEL(OSSPlayback) }; OSSPlayback::~OSSPlayback() { if(mFd != -1) - close(mFd); + ::close(mFd); mFd = -1; } @@ -277,25 +259,23 @@ int OSSPlayback::mixerProc() SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - const int frame_size{mDevice->frameSizeFromFmt()}; + const size_t frame_step{mDevice->channelsFromFmt()}; + const size_t frame_size{mDevice->frameSizeFromFmt()}; - lock(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { pollfd pollitem{}; pollitem.fd = mFd; pollitem.events = POLLOUT; - unlock(); int pret{poll(&pollitem, 1, 1000)}; - lock(); if(pret < 0) { if(errno == EINTR || errno == EAGAIN) continue; ERR("poll failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed waiting for playback buffer: %s", strerror(errno)); + mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno)); break; } else if(pret == 0) @@ -304,9 +284,9 @@ int OSSPlayback::mixerProc() continue; } - ALubyte *write_ptr{mMixData.data()}; + al::byte *write_ptr{mMixData.data()}; size_t to_write{mMixData.size()}; - aluMixData(mDevice, write_ptr, to_write/frame_size); + mDevice->renderSamples(write_ptr, static_cast(to_write/frame_size), frame_step); while(to_write > 0 && !mKillNow.load(std::memory_order_acquire)) { ssize_t wrote{write(mFd, write_ptr, to_write)}; @@ -315,63 +295,54 @@ int OSSPlayback::mixerProc() if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; ERR("write failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed writing playback samples: %s", - strerror(errno)); + mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno)); break; } - to_write -= wrote; + to_write -= static_cast(wrote); write_ptr += wrote; } } - unlock(); return 0; } -ALCenum OSSPlayback::open(const ALCchar *name) +void OSSPlayback::open(const char *name) { - const char *devname{DefaultPlayback}; + const char *devname{DefaultPlayback.c_str()}; if(!name) name = DefaultName; else { if(PlaybackDevices.empty()) - ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT); + ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT); auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), [&name](const DevMap &entry) -> bool { return entry.name == name; } ); if(iter == PlaybackDevices.cend()) - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; devname = iter->device_name.c_str(); } - mFd = ::open(devname, O_WRONLY); - if(mFd == -1) - { - ERR("Could not open %s: %s\n", devname, strerror(errno)); - return ALC_INVALID_VALUE; - } + int fd{::open(devname, O_WRONLY)}; + if(fd == -1) + throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname, + strerror(errno)}; + + if(mFd != -1) + ::close(mFd); + mFd = fd; mDevice->DeviceName = name; - return ALC_NO_ERROR; } -ALCboolean OSSPlayback::reset() +bool OSSPlayback::reset() { - int numFragmentsLogSize; - int log2FragmentSize; - unsigned int periods; - audio_buf_info info; - ALuint frameSize; - int numChannels; - int ossFormat; - int ossSpeed; - const char *err; - + int ossFormat{}; switch(mDevice->FmtType) { case DevFmtByte: @@ -391,14 +362,16 @@ ALCboolean OSSPlayback::reset() break; } - periods = mDevice->BufferSize / mDevice->UpdateSize; - numChannels = mDevice->channelsFromFmt(); - ossSpeed = mDevice->Frequency; - frameSize = numChannels * mDevice->bytesFromFmt(); + uint periods{mDevice->BufferSize / mDevice->UpdateSize}; + uint numChannels{mDevice->channelsFromFmt()}; + uint ossSpeed{mDevice->Frequency}; + uint frameSize{numChannels * mDevice->bytesFromFmt()}; /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */ - log2FragmentSize = maxi(log2i(mDevice->UpdateSize*frameSize), 4); - numFragmentsLogSize = (periods << 16) | log2FragmentSize; + uint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)}; + uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; + audio_buf_info info{}; + const char *err; #define CHECKERR(func) if((func) < 0) { \ err = #func; \ goto err; \ @@ -414,7 +387,7 @@ ALCboolean OSSPlayback::reset() { err: ERR("%s failed: %s\n", err, strerror(errno)); - return ALC_FALSE; + return false; } #undef CHECKERR @@ -422,7 +395,7 @@ ALCboolean OSSPlayback::reset() { ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans), numChannels); - return ALC_FALSE; + return false; } if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) || @@ -431,33 +404,30 @@ ALCboolean OSSPlayback::reset() { ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType), ossFormat); - return ALC_FALSE; + return false; } mDevice->Frequency = ossSpeed; - mDevice->UpdateSize = info.fragsize / frameSize; - mDevice->BufferSize = info.fragments * mDevice->UpdateSize; + mDevice->UpdateSize = static_cast(info.fragsize) / frameSize; + mDevice->BufferSize = static_cast(info.fragments) * mDevice->UpdateSize; - SetDefaultChannelOrder(mDevice); + setDefaultChannelOrder(); mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); - return ALC_TRUE; + return true; } -ALCboolean OSSPlayback::start() +void OSSPlayback::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this}; - return ALC_TRUE; } catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - return ALC_FALSE; } void OSSPlayback::stop() @@ -472,16 +442,16 @@ void OSSPlayback::stop() struct OSScapture final : public BackendBase { - OSScapture(ALCdevice *device) noexcept : BackendBase{device} { } + OSScapture(DeviceBase *device) noexcept : BackendBase{device} { } ~OSScapture() override; int recordProc(); - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; int mFd{-1}; @@ -490,7 +460,6 @@ struct OSScapture final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "OSScapture::"; } DEF_NEWDEL(OSScapture) }; @@ -507,7 +476,7 @@ int OSScapture::recordProc() SetRTPriority(); althrd_setname(RECORD_THREAD_NAME); - const int frame_size{mDevice->frameSizeFromFmt()}; + const size_t frame_size{mDevice->frameSizeFromFmt()}; while(!mKillNow.load(std::memory_order_acquire)) { pollfd pollitem{}; @@ -520,7 +489,7 @@ int OSScapture::recordProc() if(errno == EINTR || errno == EAGAIN) continue; ERR("poll failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed to check capture samples: %s", strerror(errno)); + mDevice->handleDisconnect("Failed to check capture samples: %s", strerror(errno)); break; } else if(sret == 0) @@ -536,11 +505,10 @@ int OSScapture::recordProc() if(amt < 0) { ERR("read failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed reading capture samples: %s", - strerror(errno)); + mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno)); break; } - mRing->writeAdvance(amt/frame_size); + mRing->writeAdvance(static_cast(amt)/frame_size); } } @@ -548,128 +516,98 @@ int OSScapture::recordProc() } -ALCenum OSScapture::open(const ALCchar *name) +void OSScapture::open(const char *name) { - const char *devname{DefaultCapture}; + const char *devname{DefaultCapture.c_str()}; if(!name) name = DefaultName; else { if(CaptureDevices.empty()) - ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT); + ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT); auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), [&name](const DevMap &entry) -> bool { return entry.name == name; } ); if(iter == CaptureDevices.cend()) - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; devname = iter->device_name.c_str(); } mFd = ::open(devname, O_RDONLY); if(mFd == -1) - { - ERR("Could not open %s: %s\n", devname, strerror(errno)); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname, + strerror(errno)}; int ossFormat{}; switch(mDevice->FmtType) { - case DevFmtByte: - ossFormat = AFMT_S8; - break; - case DevFmtUByte: - ossFormat = AFMT_U8; - break; - case DevFmtShort: - ossFormat = AFMT_S16_NE; - break; - case DevFmtUShort: - case DevFmtInt: - case DevFmtUInt: - case DevFmtFloat: - ERR("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_VALUE; + case DevFmtByte: + ossFormat = AFMT_S8; + break; + case DevFmtUByte: + ossFormat = AFMT_U8; + break; + case DevFmtShort: + ossFormat = AFMT_S16_NE; + break; + case DevFmtUShort: + case DevFmtInt: + case DevFmtUInt: + case DevFmtFloat: + throw al::backend_exception{al::backend_error::DeviceError, + "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } - int periods{4}; - int numChannels{mDevice->channelsFromFmt()}; - int frameSize{numChannels * mDevice->bytesFromFmt()}; - int ossSpeed{static_cast(mDevice->Frequency)}; - int log2FragmentSize{log2i(mDevice->BufferSize * frameSize / periods)}; - + uint periods{4}; + uint numChannels{mDevice->channelsFromFmt()}; + uint frameSize{numChannels * mDevice->bytesFromFmt()}; + uint ossSpeed{mDevice->Frequency}; /* according to the OSS spec, 16 bytes are the minimum */ - log2FragmentSize = std::max(log2FragmentSize, 4); - int numFragmentsLogSize{(periods << 16) | log2FragmentSize}; + uint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)}; + uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; - audio_buf_info info; - const char *err; + audio_buf_info info{}; #define CHECKERR(func) if((func) < 0) { \ - err = #func; \ - goto err; \ + throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \ + strerror(errno)}; \ } CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed)); CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info)); - if(0) - { - err: - ERR("%s failed: %s\n", err, strerror(errno)); - close(mFd); - mFd = -1; - return ALC_INVALID_VALUE; - } #undef CHECKERR if(mDevice->channelsFromFmt() != numChannels) - { - ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans), - numChannels); - close(mFd); - mFd = -1; - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set %s, got %d channels instead", DevFmtChannelsString(mDevice->FmtChans), + numChannels}; - if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) || - (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) || - (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort))) - { - ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType), ossFormat); - close(mFd); - mFd = -1; - return ALC_INVALID_VALUE; - } + 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}; - mRing = CreateRingBuffer(mDevice->BufferSize, frameSize, false); - if(!mRing) - { - ERR("Ring buffer create failed\n"); - close(mFd); - mFd = -1; - return ALC_OUT_OF_MEMORY; - } + mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false); mDevice->DeviceName = name; - return ALC_NO_ERROR; } -ALCboolean OSScapture::start() +void OSScapture::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this}; - return ALC_TRUE; } catch(std::exception& e) { - ERR("Could not create record thread: %s\n", e.what()); - } - catch(...) { + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start recording thread: %s", e.what()}; } - return ALC_FALSE; } void OSScapture::stop() @@ -682,14 +620,11 @@ void OSScapture::stop() ERR("Error resetting device: %s\n", strerror(errno)); } -ALCenum OSScapture::captureSamples(ALCvoid *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} +void OSScapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } -ALCuint OSScapture::availableSamples() -{ return mRing->readSpace(); } +uint OSScapture::availableSamples() +{ return static_cast(mRing->readSpace()); } } // namespace @@ -702,8 +637,10 @@ BackendFactory &OSSBackendFactory::getFactory() bool OSSBackendFactory::init() { - ConfigValueStr(nullptr, "oss", "device", &DefaultPlayback); - ConfigValueStr(nullptr, "oss", "capture", &DefaultCapture); + if(auto devopt = ConfigValueStr(nullptr, "oss", "device")) + DefaultPlayback = std::move(*devopt); + if(auto capopt = ConfigValueStr(nullptr, "oss", "capture")) + DefaultCapture = std::move(*capopt); return true; } @@ -711,37 +648,39 @@ bool OSSBackendFactory::init() bool OSSBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -void OSSBackendFactory::probe(DevProbe type, std::string *outnames) +std::string OSSBackendFactory::probe(BackendType type) { - auto add_device = [outnames](const DevMap &entry) -> void + std::string outnames; + + auto add_device = [&outnames](const DevMap &entry) -> void { -#ifdef HAVE_STAT struct stat buf; if(stat(entry.device_name.c_str(), &buf) == 0) -#endif { /* Includes null char. */ - outnames->append(entry.name.c_str(), entry.name.length()+1); + outnames.append(entry.name.c_str(), entry.name.length()+1); } }; switch(type) { - case DevProbe::Playback: - PlaybackDevices.clear(); - ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; - - case DevProbe::Capture: - CaptureDevices.clear(); - ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; + case BackendType::Playback: + PlaybackDevices.clear(); + ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT); + std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); + break; + + case BackendType::Capture: + CaptureDevices.clear(); + ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT); + std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); + break; } + + return outnames; } -BackendPtr OSSBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr OSSBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new OSSPlayback{device}}; diff --git a/modules/openal-soft/Alc/backends/oss.h b/modules/openal-soft/Alc/backends/oss.h index 9e63d7b..4f2c00b 100644 --- a/modules/openal-soft/Alc/backends/oss.h +++ b/modules/openal-soft/Alc/backends/oss.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_OSS_H #define BACKENDS_OSS_H -#include "backends/base.h" +#include "base.h" struct OSSBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/pipewire.cpp b/modules/openal-soft/Alc/backends/pipewire.cpp new file mode 100644 index 0000000..a19dcb6 --- /dev/null +++ b/modules/openal-soft/Alc/backends/pipewire.cpp @@ -0,0 +1,2008 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2010 by Chris Robinson + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "pipewire.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "albyte.h" +#include "alc/alconfig.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "aloptional.h" +#include "alspan.h" +#include "alstring.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "dynload.h" +#include "opthelpers.h" +#include "ringbuffer.h" + +/* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). */ +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Weverything\"") +#include "pipewire/pipewire.h" +#include "pipewire/extensions/metadata.h" +#include "spa/buffer/buffer.h" +#include "spa/param/audio/format-utils.h" +#include "spa/param/audio/raw.h" +#include "spa/param/param.h" +#include "spa/pod/builder.h" +#include "spa/utils/json.h" + +namespace { +/* Wrap some nasty macros here too... */ +template +auto ppw_core_add_listener(pw_core *core, Args&& ...args) +{ return pw_core_add_listener(core, std::forward(args)...); } +template +auto ppw_core_sync(pw_core *core, Args&& ...args) +{ return pw_core_sync(core, std::forward(args)...); } +template +auto ppw_registry_add_listener(pw_registry *reg, Args&& ...args) +{ return pw_registry_add_listener(reg, std::forward(args)...); } +template +auto ppw_node_add_listener(pw_node *node, Args&& ...args) +{ return pw_node_add_listener(node, std::forward(args)...); } +template +auto ppw_node_subscribe_params(pw_node *node, Args&& ...args) +{ return pw_node_subscribe_params(node, std::forward(args)...); } +template +auto ppw_metadata_add_listener(pw_metadata *mdata, Args&& ...args) +{ return pw_metadata_add_listener(mdata, std::forward(args)...); } + + +constexpr auto get_pod_type(const spa_pod *pod) noexcept +{ return SPA_POD_TYPE(pod); } + +template +constexpr auto get_pod_body(const spa_pod *pod, size_t count) noexcept +{ return al::span{static_cast(SPA_POD_BODY(pod)), count}; } +template +constexpr auto get_pod_body(const spa_pod *pod) noexcept +{ return al::span{static_cast(SPA_POD_BODY(pod)), N}; } + +constexpr auto make_pod_builder(void *data, uint32_t size) noexcept +{ return SPA_POD_BUILDER_INIT(data, size); } + +constexpr auto get_array_value_type(const spa_pod *pod) noexcept +{ return SPA_POD_ARRAY_VALUE_TYPE(pod); } + +constexpr auto PwIdAny = PW_ID_ANY; + +} // namespace +_Pragma("GCC diagnostic pop") + +namespace { + +using std::chrono::seconds; +using std::chrono::nanoseconds; +using uint = unsigned int; + +constexpr char pwireDevice[] = "PipeWire Output"; +constexpr char pwireInput[] = "PipeWire Input"; + + +#ifdef HAVE_DYNLOAD +#define PWIRE_FUNCS(MAGIC) \ + MAGIC(pw_context_connect) \ + MAGIC(pw_context_destroy) \ + MAGIC(pw_context_new) \ + MAGIC(pw_core_disconnect) \ + MAGIC(pw_init) \ + MAGIC(pw_properties_free) \ + MAGIC(pw_properties_new) \ + MAGIC(pw_properties_set) \ + MAGIC(pw_properties_setf) \ + MAGIC(pw_proxy_add_object_listener) \ + MAGIC(pw_proxy_destroy) \ + MAGIC(pw_proxy_get_user_data) \ + MAGIC(pw_stream_add_listener) \ + MAGIC(pw_stream_connect) \ + MAGIC(pw_stream_dequeue_buffer) \ + MAGIC(pw_stream_destroy) \ + MAGIC(pw_stream_get_state) \ + MAGIC(pw_stream_get_time) \ + MAGIC(pw_stream_new) \ + MAGIC(pw_stream_queue_buffer) \ + MAGIC(pw_stream_set_active) \ + MAGIC(pw_thread_loop_new) \ + MAGIC(pw_thread_loop_destroy) \ + MAGIC(pw_thread_loop_get_loop) \ + MAGIC(pw_thread_loop_start) \ + MAGIC(pw_thread_loop_stop) \ + MAGIC(pw_thread_loop_lock) \ + MAGIC(pw_thread_loop_wait) \ + MAGIC(pw_thread_loop_signal) \ + MAGIC(pw_thread_loop_unlock) \ + +void *pwire_handle; +#define MAKE_FUNC(f) decltype(f) * p##f; +PWIRE_FUNCS(MAKE_FUNC) +#undef MAKE_FUNC + +bool pwire_load() +{ + if(pwire_handle) + return true; + + static constexpr char pwire_library[] = "libpipewire-0.3.so.0"; + std::string missing_funcs; + + pwire_handle = LoadLib(pwire_library); + if(!pwire_handle) + { + WARN("Failed to load %s\n", pwire_library); + return false; + } + +#define LOAD_FUNC(f) do { \ + p##f = reinterpret_cast(GetSymbol(pwire_handle, #f)); \ + if(p##f == nullptr) missing_funcs += "\n" #f; \ +} while(0); + PWIRE_FUNCS(LOAD_FUNC) +#undef LOAD_FUNC + + if(!missing_funcs.empty()) + { + WARN("Missing expected functions:%s\n", missing_funcs.c_str()); + CloseLib(pwire_handle); + pwire_handle = nullptr; + return false; + } + + return true; +} + +#ifndef IN_IDE_PARSER +#define pw_context_connect ppw_context_connect +#define pw_context_destroy ppw_context_destroy +#define pw_context_new ppw_context_new +#define pw_core_disconnect ppw_core_disconnect +#define pw_init ppw_init +#define pw_properties_free ppw_properties_free +#define pw_properties_new ppw_properties_new +#define pw_properties_set ppw_properties_set +#define pw_properties_setf ppw_properties_setf +#define pw_proxy_add_object_listener ppw_proxy_add_object_listener +#define pw_proxy_destroy ppw_proxy_destroy +#define pw_proxy_get_user_data ppw_proxy_get_user_data +#define pw_stream_add_listener ppw_stream_add_listener +#define pw_stream_connect ppw_stream_connect +#define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer +#define pw_stream_destroy ppw_stream_destroy +#define pw_stream_get_state ppw_stream_get_state +#define pw_stream_get_time ppw_stream_get_time +#define pw_stream_new ppw_stream_new +#define pw_stream_queue_buffer ppw_stream_queue_buffer +#define pw_stream_set_active ppw_stream_set_active +#define pw_thread_loop_destroy ppw_thread_loop_destroy +#define pw_thread_loop_get_loop ppw_thread_loop_get_loop +#define pw_thread_loop_lock ppw_thread_loop_lock +#define pw_thread_loop_new ppw_thread_loop_new +#define pw_thread_loop_signal ppw_thread_loop_signal +#define pw_thread_loop_start ppw_thread_loop_start +#define pw_thread_loop_stop ppw_thread_loop_stop +#define pw_thread_loop_unlock ppw_thread_loop_unlock +#define pw_thread_loop_wait ppw_thread_loop_wait +#endif + +#else + +constexpr bool pwire_load() { return true; } +#endif + +/* Helpers for retrieving values from params */ +template struct PodInfo { }; + +template<> +struct PodInfo { + using Type = int32_t; + static auto get_value(const spa_pod *pod, int32_t *val) + { return spa_pod_get_int(pod, val); } +}; +template<> +struct PodInfo { + using Type = uint32_t; + static auto get_value(const spa_pod *pod, uint32_t *val) + { return spa_pod_get_id(pod, val); } +}; + +template +using Pod_t = typename PodInfo::Type; + +template +al::span> get_array_span(const spa_pod *pod) +{ + uint32_t nvals; + if(void *v{spa_pod_get_array(pod, &nvals)}) + { + if(get_array_value_type(pod) == T) + return {static_cast*>(v), nvals}; + } + return {}; +} + +template +al::optional> get_value(const spa_pod *value) +{ + Pod_t val{}; + if(PodInfo::get_value(value, &val) == 0) + return val; + return al::nullopt; +} + +/* Internally, PipeWire types "inherit" from each other, but this is hidden + * from the API and the caller is expected to C-style cast to inherited types + * as needed. It's also not made very clear what types a given type can be + * casted to. To make it a bit safer, this as() method allows casting pw_* + * types to known inherited types, generating a compile-time error for + * unexpected/invalid casts. + */ +template +To as(From) noexcept = delete; + +/* pw_proxy + * - pw_registry + * - pw_node + * - pw_metadata + */ +template<> +pw_proxy* as(pw_registry *reg) noexcept { return reinterpret_cast(reg); } +template<> +pw_proxy* as(pw_node *node) noexcept { return reinterpret_cast(node); } +template<> +pw_proxy* as(pw_metadata *mdata) noexcept { return reinterpret_cast(mdata); } + + +struct PwContextDeleter { + void operator()(pw_context *context) const { pw_context_destroy(context); } +}; +using PwContextPtr = std::unique_ptr; + +struct PwCoreDeleter { + void operator()(pw_core *core) const { pw_core_disconnect(core); } +}; +using PwCorePtr = std::unique_ptr; + +struct PwRegistryDeleter { + void operator()(pw_registry *reg) const { pw_proxy_destroy(as(reg)); } +}; +using PwRegistryPtr = std::unique_ptr; + +struct PwNodeDeleter { + void operator()(pw_node *node) const { pw_proxy_destroy(as(node)); } +}; +using PwNodePtr = std::unique_ptr; + +struct PwMetadataDeleter { + void operator()(pw_metadata *mdata) const { pw_proxy_destroy(as(mdata)); } +}; +using PwMetadataPtr = std::unique_ptr; + +struct PwStreamDeleter { + void operator()(pw_stream *stream) const { pw_stream_destroy(stream); } +}; +using PwStreamPtr = std::unique_ptr; + +/* Enums for bitflags... again... *sigh* */ +constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept +{ return static_cast(lhs | std::underlying_type_t{rhs}); } + +class ThreadMainloop { + pw_thread_loop *mLoop{}; + +public: + ThreadMainloop() = default; + ThreadMainloop(const ThreadMainloop&) = delete; + ThreadMainloop(ThreadMainloop&& rhs) noexcept : mLoop{rhs.mLoop} { rhs.mLoop = nullptr; } + explicit ThreadMainloop(pw_thread_loop *loop) noexcept : mLoop{loop} { } + ~ThreadMainloop() { if(mLoop) pw_thread_loop_destroy(mLoop); } + + ThreadMainloop& operator=(const ThreadMainloop&) = delete; + ThreadMainloop& operator=(ThreadMainloop&& rhs) noexcept + { std::swap(mLoop, rhs.mLoop); return *this; } + ThreadMainloop& operator=(std::nullptr_t) noexcept + { + if(mLoop) + pw_thread_loop_destroy(mLoop); + mLoop = nullptr; + return *this; + } + + explicit operator bool() const noexcept { return mLoop != nullptr; } + + auto start() const { return pw_thread_loop_start(mLoop); } + auto stop() const { return pw_thread_loop_stop(mLoop); } + + auto getLoop() const { return pw_thread_loop_get_loop(mLoop); } + + auto lock() const { return pw_thread_loop_lock(mLoop); } + auto unlock() const { return pw_thread_loop_unlock(mLoop); } + + auto signal(bool wait) const { return pw_thread_loop_signal(mLoop, wait); } + + auto newContext(pw_properties *props=nullptr, size_t user_data_size=0) + { return PwContextPtr{pw_context_new(getLoop(), props, user_data_size)}; } + + static auto Create(const char *name, spa_dict *props=nullptr) + { return ThreadMainloop{pw_thread_loop_new(name, props)}; } + + friend struct MainloopUniqueLock; +}; +struct MainloopUniqueLock : public std::unique_lock { + using std::unique_lock::unique_lock; + MainloopUniqueLock& operator=(MainloopUniqueLock&&) = default; + + auto wait() const -> void + { pw_thread_loop_wait(mutex()->mLoop); } + + template + auto wait(Predicate done_waiting) const -> void + { while(!done_waiting()) wait(); } +}; +using MainloopLockGuard = std::lock_guard; + + +/* There's quite a mess here, but the purpose is to track active devices and + * their default formats, so playback devices can be configured to match. The + * device list is updated asynchronously, so it will have the latest list of + * devices provided by the server. + */ + +struct NodeProxy; +struct MetadataProxy; + +/* The global thread watching for global events. This particular class responds + * to objects being added to or removed from the registry. + */ +struct EventManager { + ThreadMainloop mLoop{}; + PwContextPtr mContext{}; + PwCorePtr mCore{}; + PwRegistryPtr mRegistry{}; + spa_hook mRegistryListener{}; + spa_hook mCoreListener{}; + + /* A list of proxy objects watching for events about changes to objects in + * the registry. + */ + std::vector mNodeList; + MetadataProxy *mDefaultMetadata{nullptr}; + + /* Initialization handling. When init() is called, mInitSeq is set to a + * SequenceID that marks the end of populating the registry. As objects of + * interest are found, events to parse them are generated and mInitSeq is + * updated with a newer ID. When mInitSeq stops being updated and the event + * corresponding to it is reached, mInitDone will be set to true. + */ + std::atomic mInitDone{false}; + std::atomic mHasAudio{false}; + int mInitSeq{}; + + bool init(); + ~EventManager(); + + void kill(); + + auto lock() const { return mLoop.lock(); } + auto unlock() const { return mLoop.unlock(); } + + /** + * Waits for initialization to finish. The event manager must *NOT* be + * locked when calling this. + */ + void waitForInit() + { + if(unlikely(!mInitDone.load(std::memory_order_acquire))) + { + MainloopUniqueLock plock{mLoop}; + plock.wait([this](){ return mInitDone.load(std::memory_order_acquire); }); + } + } + + /** + * Waits for audio support to be detected, or initialization to finish, + * whichever is first. Returns true if audio support was detected. The + * event manager must *NOT* be locked when calling this. + */ + bool waitForAudio() + { + MainloopUniqueLock plock{mLoop}; + bool has_audio{}; + plock.wait([this,&has_audio]() + { + has_audio = mHasAudio.load(std::memory_order_acquire); + return has_audio || mInitDone.load(std::memory_order_acquire); + }); + return has_audio; + } + + void syncInit() + { + /* If initialization isn't done, update the sequence ID so it won't + * complete until after currently scheduled events. + */ + if(!mInitDone.load(std::memory_order_relaxed)) + mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, mInitSeq); + } + + void addCallback(uint32_t id, uint32_t permissions, const char *type, uint32_t version, + const spa_dict *props); + static void addCallbackC(void *object, uint32_t id, uint32_t permissions, const char *type, + uint32_t version, const spa_dict *props) + { static_cast(object)->addCallback(id, permissions, type, version, props); } + + void removeCallback(uint32_t id); + static void removeCallbackC(void *object, uint32_t id) + { static_cast(object)->removeCallback(id); } + + static constexpr pw_registry_events CreateRegistryEvents() + { + pw_registry_events ret{}; + ret.version = PW_VERSION_REGISTRY_EVENTS; + ret.global = &EventManager::addCallbackC; + ret.global_remove = &EventManager::removeCallbackC; + return ret; + } + + void coreCallback(uint32_t id, int seq); + static void coreCallbackC(void *object, uint32_t id, int seq) + { static_cast(object)->coreCallback(id, seq); } + + static constexpr pw_core_events CreateCoreEvents() + { + pw_core_events ret{}; + ret.version = PW_VERSION_CORE_EVENTS; + ret.done = &EventManager::coreCallbackC; + return ret; + } +}; +using EventWatcherUniqueLock = std::unique_lock; +using EventWatcherLockGuard = std::lock_guard; + +EventManager gEventHandler; + +/* Enumerated devices. This is updated asynchronously as the app runs, and the + * gEventHandler thread loop must be locked when accessing the list. + */ +enum class NodeType : unsigned char { + Sink, Source, Duplex +}; +constexpr auto InvalidChannelConfig = DevFmtChannels(255); +struct DeviceNode { + std::string mName; + std::string mDevName; + + uint32_t mId{}; + NodeType mType{}; + bool mIsHeadphones{}; + bool mIs51Rear{}; + + uint mSampleRate{}; + DevFmtChannels mChannels{InvalidChannelConfig}; + + static std::vector sList; + static DeviceNode &Add(uint32_t id); + static DeviceNode *Find(uint32_t id); + static void Remove(uint32_t id); + static std::vector &GetList() noexcept { return sList; } + + void parseSampleRate(const spa_pod *value) noexcept; + void parsePositions(const spa_pod *value) noexcept; + void parseChannelCount(const spa_pod *value) noexcept; +}; +std::vector DeviceNode::sList; +std::string DefaultSinkDevice; +std::string DefaultSourceDevice; + +const char *AsString(NodeType type) noexcept +{ + switch(type) + { + case NodeType::Sink: return "sink"; + case NodeType::Source: return "source"; + case NodeType::Duplex: return "duplex"; + } + return ""; +} + +DeviceNode &DeviceNode::Add(uint32_t id) +{ + auto match_id = [id](DeviceNode &n) noexcept -> bool + { return n.mId == id; }; + + /* If the node is already in the list, return the existing entry. */ + auto match = std::find_if(sList.begin(), sList.end(), match_id); + if(match != sList.end()) return *match; + + sList.emplace_back(); + auto &n = sList.back(); + n.mId = id; + return n; +} + +DeviceNode *DeviceNode::Find(uint32_t id) +{ + auto match_id = [id](DeviceNode &n) noexcept -> bool + { return n.mId == id; }; + + auto match = std::find_if(sList.begin(), sList.end(), match_id); + if(match != sList.end()) return std::addressof(*match); + + return nullptr; +} + +void DeviceNode::Remove(uint32_t id) +{ + auto match_id = [id](DeviceNode &n) noexcept -> bool + { + if(n.mId != id) + return false; + TRACE("Removing device \"%s\"\n", n.mDevName.c_str()); + return true; + }; + + auto end = std::remove_if(sList.begin(), sList.end(), match_id); + sList.erase(end, sList.end()); +} + + +const spa_audio_channel MonoMap[]{ + SPA_AUDIO_CHANNEL_MONO +}, StereoMap[] { + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR +}, QuadMap[]{ + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR +}, X51Map[]{ + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR +}, X51RearMap[]{ + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR +}, X61Map[]{ + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR +}, X71Map[]{ + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR +}; + +/** + * Checks if every channel in 'map1' exists in 'map0' (that is, map0 is equal + * to or a superset of map1). + */ +template +bool MatchChannelMap(const al::span map0, const spa_audio_channel (&map1)[N]) +{ + if(map0.size() < N) + return false; + for(const spa_audio_channel chid : map1) + { + if(std::find(map0.begin(), map0.end(), chid) == map0.end()) + return false; + } + return true; +} + +void DeviceNode::parseSampleRate(const spa_pod *value) noexcept +{ + /* TODO: Can this be anything else? Long, Float, Double? */ + uint32_t nvals{}, choiceType{}; + value = spa_pod_get_values(value, &nvals, &choiceType); + + const uint podType{get_pod_type(value)}; + if(podType != SPA_TYPE_Int) + { + WARN("Unhandled sample rate POD type: %u\n", podType); + return; + } + + if(choiceType == SPA_CHOICE_Range) + { + if(nvals != 3) + { + WARN("Unexpected SPA_CHOICE_Range count: %u\n", nvals); + return; + } + auto srates = get_pod_body(value); + + /* [0] is the default, [1] is the min, and [2] is the max. */ + TRACE("Device ID %u sample rate: %d (range: %d -> %d)\n", mId, srates[0], srates[1], + srates[2]); + mSampleRate = static_cast(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)); + return; + } + + if(choiceType == SPA_CHOICE_Enum) + { + if(nvals == 0) + { + WARN("Unexpected SPA_CHOICE_Enum count: %u\n", 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("Device ID %u sample rate: %d (%s)\n", mId, srates[0], others.c_str()); + /* Pick the first rate listed that's within the allowed range (default + * rate if possible). + */ + for(const auto &rate : srates) + { + if(rate >= MIN_OUTPUT_RATE && rate <= MAX_OUTPUT_RATE) + { + mSampleRate = static_cast(rate); + break; + } + } + return; + } + + if(choiceType == SPA_CHOICE_None) + { + if(nvals != 1) + { + WARN("Unexpected SPA_CHOICE_None count: %u\n", nvals); + return; + } + auto srates = get_pod_body(value); + + TRACE("Device ID %u sample rate: %d\n", mId, srates[0]); + mSampleRate = static_cast(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)); + return; + } + + WARN("Unhandled sample rate choice type: %u\n", choiceType); +} + +void DeviceNode::parsePositions(const spa_pod *value) noexcept +{ + const auto chanmap = get_array_span(value); + if(chanmap.empty()) return; + + mIs51Rear = false; + + if(MatchChannelMap(chanmap, X71Map)) + mChannels = DevFmtX71; + else if(MatchChannelMap(chanmap, X61Map)) + mChannels = DevFmtX61; + else if(MatchChannelMap(chanmap, X51Map)) + mChannels = DevFmtX51; + else if(MatchChannelMap(chanmap, X51RearMap)) + { + mChannels = DevFmtX51; + mIs51Rear = true; + } + else if(MatchChannelMap(chanmap, QuadMap)) + mChannels = DevFmtQuad; + else if(MatchChannelMap(chanmap, StereoMap)) + mChannels = DevFmtStereo; + else + mChannels = DevFmtMono; + TRACE("Device ID %u got %zu position%s for %s%s\n", mId, chanmap.size(), + (chanmap.size()==1)?"":"s", DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":""); +} + +void DeviceNode::parseChannelCount(const spa_pod *value) noexcept +{ + /* As a fallback with just a channel count, just assume mono or stereo. */ + const auto chancount = get_value(value); + if(!chancount) return; + + mIs51Rear = false; + + if(*chancount >= 2) + mChannels = DevFmtStereo; + else if(*chancount >= 1) + mChannels = DevFmtMono; + TRACE("Device ID %u got %d channel%s for %s\n", mId, *chancount, (*chancount==1)?"":"s", + DevFmtChannelsString(mChannels)); +} + + +constexpr char MonitorPrefix[]{"Monitor of "}; +constexpr auto MonitorPrefixLen = al::size(MonitorPrefix) - 1; +constexpr char AudioSinkClass[]{"Audio/Sink"}; +constexpr char AudioSourceClass[]{"Audio/Source"}; +constexpr char AudioDuplexClass[]{"Audio/Duplex"}; +constexpr char StreamClass[]{"Stream/"}; + +/* A generic PipeWire node proxy object used to track changes to sink and + * source nodes. + */ +struct NodeProxy { + static constexpr pw_node_events CreateNodeEvents() + { + pw_node_events ret{}; + ret.version = PW_VERSION_NODE_EVENTS; + ret.info = &NodeProxy::infoCallbackC; + ret.param = &NodeProxy::paramCallbackC; + return ret; + } + + uint32_t mId{}; + + PwNodePtr mNode{}; + spa_hook mListener{}; + + NodeProxy(uint32_t id, PwNodePtr node) + : mId{id}, mNode{std::move(node)} + { + static constexpr pw_node_events nodeEvents{CreateNodeEvents()}; + ppw_node_add_listener(mNode.get(), &mListener, &nodeEvents, this); + + /* Track changes to the enumerable formats (indicates the default + * format, which is what we're interested in). + */ + uint32_t fmtids[]{SPA_PARAM_EnumFormat}; + ppw_node_subscribe_params(mNode.get(), al::data(fmtids), al::size(fmtids)); + } + ~NodeProxy() + { spa_hook_remove(&mListener); } + + + void infoCallback(const pw_node_info *info); + static void infoCallbackC(void *object, const pw_node_info *info) + { static_cast(object)->infoCallback(info); } + + void paramCallback(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param); + static void paramCallbackC(void *object, int seq, uint32_t id, uint32_t index, uint32_t next, + const spa_pod *param) + { static_cast(object)->paramCallback(seq, id, index, next, param); } +}; + +void NodeProxy::infoCallback(const pw_node_info *info) +{ + /* We only care about property changes here (media class, name/desc). + * Format changes will automatically invoke the param callback. + * + * TODO: Can the media class or name/desc change without being removed and + * readded? + */ + if((info->change_mask&PW_NODE_CHANGE_MASK_PROPS)) + { + /* Can this actually change? */ + const char *media_class{spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS)}; + if(unlikely(!media_class)) return; + + NodeType ntype{}; + if(al::strcasecmp(media_class, AudioSinkClass) == 0) + ntype = NodeType::Sink; + else if(al::strcasecmp(media_class, AudioSourceClass) == 0) + ntype = NodeType::Source; + else if(al::strcasecmp(media_class, AudioDuplexClass) == 0) + ntype = NodeType::Duplex; + else + { + TRACE("Dropping device node %u which became type \"%s\"\n", info->id, media_class); + DeviceNode::Remove(info->id); + return; + } + + const char *devName{spa_dict_lookup(info->props, PW_KEY_NODE_NAME)}; + const char *nodeName{spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION)}; + if(!nodeName || !*nodeName) nodeName = spa_dict_lookup(info->props, PW_KEY_NODE_NICK); + if(!nodeName || !*nodeName) nodeName = devName; + + const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)}; + TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)", + form_factor?" (":"", form_factor?form_factor:"", form_factor?")":""); + TRACE(" \"%s\" = ID %u\n", nodeName ? nodeName : "(nil)", info->id); + + DeviceNode &node = DeviceNode::Add(info->id); + if(nodeName && *nodeName) node.mName = nodeName; + else node.mName = "PipeWire node #"+std::to_string(info->id); + node.mDevName = devName ? devName : ""; + node.mType = ntype; + node.mIsHeadphones = form_factor && (al::strcasecmp(form_factor, "headphones") == 0 + || al::strcasecmp(form_factor, "headset") == 0); + } +} + +void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_pod *param) +{ + if(id == SPA_PARAM_EnumFormat) + { + DeviceNode *node{DeviceNode::Find(mId)}; + if(unlikely(!node)) return; + + if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)}) + node->parseSampleRate(&prop->value); + + if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_position)}) + node->parsePositions(&prop->value); + else if((prop=spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_channels)) != nullptr) + node->parseChannelCount(&prop->value); + } +} + + +/* A metadata proxy object used to query the default sink and source. */ +struct MetadataProxy { + static constexpr pw_metadata_events CreateMetadataEvents() + { + pw_metadata_events ret{}; + ret.version = PW_VERSION_METADATA_EVENTS; + ret.property = &MetadataProxy::propertyCallbackC; + return ret; + } + + uint32_t mId{}; + + PwMetadataPtr mMetadata{}; + spa_hook mListener{}; + + MetadataProxy(uint32_t id, PwMetadataPtr mdata) + : mId{id}, mMetadata{std::move(mdata)} + { + static constexpr pw_metadata_events metadataEvents{CreateMetadataEvents()}; + ppw_metadata_add_listener(mMetadata.get(), &mListener, &metadataEvents, this); + } + ~MetadataProxy() + { spa_hook_remove(&mListener); } + + + int propertyCallback(uint32_t id, const char *key, const char *type, const char *value); + static int propertyCallbackC(void *object, uint32_t id, const char *key, const char *type, + const char *value) + { return static_cast(object)->propertyCallback(id, key, type, value); } +}; + +int MetadataProxy::propertyCallback(uint32_t id, const char *key, const char *type, + const char *value) +{ + if(id != PW_ID_CORE) + return 0; + + bool isCapture{}; + if(std::strcmp(key, "default.audio.sink") == 0) + isCapture = false; + else if(std::strcmp(key, "default.audio.source") == 0) + isCapture = true; + else + return 0; + + if(!type) + { + TRACE("Default %s device cleared\n", isCapture ? "capture" : "playback"); + if(!isCapture) DefaultSinkDevice.clear(); + else DefaultSourceDevice.clear(); + return 0; + } + if(std::strcmp(type, "Spa:String:JSON") != 0) + { + ERR("Unexpected %s property type: %s\n", key, type); + return 0; + } + + spa_json it[2]{}; + spa_json_init(&it[0], value, strlen(value)); + if(spa_json_enter_object(&it[0], &it[1]) <= 0) + return 0; + + auto get_json_string = [](spa_json *iter) + { + al::optional str; + + const char *val{}; + int len{spa_json_next(iter, &val)}; + if(len <= 0) return str; + + str.emplace().resize(static_cast(len), '\0'); + if(spa_json_parse_string(val, len, &str->front()) <= 0) + str.reset(); + else while(!str->empty() && str->back() == '\0') + str->pop_back(); + return str; + }; + while(auto propKey = get_json_string(&it[1])) + { + if(*propKey == "name") + { + auto propValue = get_json_string(&it[1]); + if(!propValue) break; + + TRACE("Got default %s device \"%s\"\n", isCapture ? "capture" : "playback", + propValue->c_str()); + if(!isCapture) + DefaultSinkDevice = std::move(*propValue); + else + DefaultSourceDevice = std::move(*propValue); + } + else + { + const char *v{}; + if(spa_json_next(&it[1], &v) <= 0) + break; + } + } + return 0; +} + + +bool EventManager::init() +{ + mLoop = ThreadMainloop::Create("PWEventThread"); + if(!mLoop) + { + ERR("Failed to create PipeWire event thread loop (errno: %d)\n", errno); + return false; + } + + mContext = mLoop.newContext(pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)); + if(!mContext) + { + ERR("Failed to create PipeWire event context (errno: %d)\n", 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); + 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); + return false; + } + + static constexpr pw_core_events coreEvents{CreateCoreEvents()}; + static constexpr pw_registry_events registryEvents{CreateRegistryEvents()}; + + ppw_core_add_listener(mCore.get(), &mCoreListener, &coreEvents, this); + ppw_registry_add_listener(mRegistry.get(), &mRegistryListener, ®istryEvents, this); + + /* Set an initial sequence ID for initialization, to trigger after the + * registry is first populated. + */ + mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, 0); + + if(int res{mLoop.start()}) + { + ERR("Failed to start PipeWire event thread loop (res: %d)\n", res); + return false; + } + + return true; +} + +EventManager::~EventManager() +{ + if(mLoop) mLoop.stop(); + + for(NodeProxy *node : mNodeList) + al::destroy_at(node); + if(mDefaultMetadata) + al::destroy_at(mDefaultMetadata); +} + +void EventManager::kill() +{ + if(mLoop) mLoop.stop(); + + for(NodeProxy *node : mNodeList) + al::destroy_at(node); + mNodeList.clear(); + if(mDefaultMetadata) + al::destroy_at(mDefaultMetadata); + mDefaultMetadata = nullptr; + + mRegistry = nullptr; + mCore = nullptr; + mContext = nullptr; + mLoop = nullptr; +} + +void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t version, + const spa_dict *props) +{ + /* We're only interested in interface nodes. */ + if(std::strcmp(type, PW_TYPE_INTERFACE_Node) == 0) + { + const char *media_class{spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)}; + if(!media_class) return; + + /* Specifically, audio sinks and sources (and duplexes). */ + const bool isGood{al::strcasecmp(media_class, AudioSinkClass) == 0 + || al::strcasecmp(media_class, AudioSourceClass) == 0 + || al::strcasecmp(media_class, AudioDuplexClass) == 0}; + if(!isGood) + { + if(std::strstr(media_class, "/Video") == nullptr + && std::strncmp(media_class, StreamClass, sizeof(StreamClass)-1) != 0) + TRACE("Ignoring node class %s\n", media_class); + return; + } + + /* Create the proxy object. */ + auto node = PwNodePtr{static_cast(pw_registry_bind(mRegistry.get(), id, type, + version, sizeof(NodeProxy)))}; + if(!node) + { + ERR("Failed to create node proxy object (errno: %d)\n", errno); + return; + } + + /* Initialize the NodeProxy to hold the node object, add it to the + * active node list, and update the sync point. + */ + auto *proxy = static_cast(pw_proxy_get_user_data(as(node.get()))); + mNodeList.emplace_back(al::construct_at(proxy, id, std::move(node))); + syncInit(); + + /* Signal any waiters that we have found a source or sink for audio + * support. + */ + if(!mHasAudio.exchange(true, std::memory_order_acq_rel)) + mLoop.signal(false); + } + else if(std::strcmp(type, PW_TYPE_INTERFACE_Metadata) == 0) + { + const char *data_class{spa_dict_lookup(props, PW_KEY_METADATA_NAME)}; + if(!data_class) return; + + if(std::strcmp(data_class, "default") != 0) + { + TRACE("Ignoring metadata \"%s\"\n", data_class); + return; + } + + if(mDefaultMetadata) + { + ERR("Duplicate default metadata\n"); + return; + } + + auto mdata = PwMetadataPtr{static_cast(pw_registry_bind(mRegistry.get(), id, + type, version, sizeof(MetadataProxy)))}; + if(!mdata) + { + ERR("Failed to create metadata proxy object (errno: %d)\n", errno); + return; + } + + auto *proxy = static_cast( + pw_proxy_get_user_data(as(mdata.get()))); + mDefaultMetadata = al::construct_at(proxy, id, std::move(mdata)); + syncInit(); + } +} + +void EventManager::removeCallback(uint32_t id) +{ + DeviceNode::Remove(id); + + auto clear_node = [id](NodeProxy *node) noexcept + { + if(node->mId != id) + return false; + al::destroy_at(node); + return true; + }; + auto node_end = std::remove_if(mNodeList.begin(), mNodeList.end(), clear_node); + mNodeList.erase(node_end, mNodeList.end()); + + if(mDefaultMetadata && mDefaultMetadata->mId == id) + { + al::destroy_at(mDefaultMetadata); + mDefaultMetadata = nullptr; + } +} + +void EventManager::coreCallback(uint32_t id, int seq) +{ + if(id == PW_ID_CORE && seq == mInitSeq) + { + /* Initialization done. Remove this callback and signal anyone that may + * be waiting. + */ + spa_hook_remove(&mCoreListener); + + mInitDone.store(true); + mLoop.signal(false); + } +} + + +enum use_f32p_e : bool { UseDevType=false, ForceF32Planar=true }; +spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e use_f32p) +{ + spa_audio_info_raw info{}; + if(use_f32p) + { + device->FmtType = DevFmtFloat; + info.format = SPA_AUDIO_FORMAT_F32P; + } + else switch(device->FmtType) + { + case DevFmtByte: info.format = SPA_AUDIO_FORMAT_S8; break; + case DevFmtUByte: info.format = SPA_AUDIO_FORMAT_U8; break; + case DevFmtShort: info.format = SPA_AUDIO_FORMAT_S16; break; + case DevFmtUShort: info.format = SPA_AUDIO_FORMAT_U16; break; + case DevFmtInt: info.format = SPA_AUDIO_FORMAT_S32; break; + case DevFmtUInt: info.format = SPA_AUDIO_FORMAT_U32; break; + case DevFmtFloat: info.format = SPA_AUDIO_FORMAT_F32; break; + } + + info.rate = device->Frequency; + + al::span map{}; + switch(device->FmtChans) + { + case DevFmtMono: map = MonoMap; break; + case DevFmtStereo: map = StereoMap; break; + case DevFmtQuad: map = QuadMap; break; + case DevFmtX51: + if(is51rear) map = X51RearMap; + else map = X51Map; + break; + case DevFmtX61: map = X61Map; break; + case DevFmtX71: map = X71Map; break; + case DevFmtAmbi3D: + info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED; + info.channels = device->channelsFromFmt(); + break; + } + if(!map.empty()) + { + info.channels = static_cast(map.size()); + std::copy(map.begin(), map.end(), info.position); + } + + return info; +} + +class PipeWirePlayback final : public BackendBase { + void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error); + static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state, + const char *error) + { static_cast(data)->stateChangedCallback(old, state, error); } + + void ioChangedCallback(uint32_t id, void *area, uint32_t size); + static void ioChangedCallbackC(void *data, uint32_t id, void *area, uint32_t size) + { static_cast(data)->ioChangedCallback(id, area, size); } + + void outputCallback(); + static void outputCallbackC(void *data) + { static_cast(data)->outputCallback(); } + + void open(const char *name) override; + bool reset() override; + void start() override; + void stop() override; + ClockLatency getClockLatency() override; + + uint32_t mTargetId{PwIdAny}; + nanoseconds mTimeBase{0}; + ThreadMainloop mLoop; + PwContextPtr mContext; + PwCorePtr mCore; + PwStreamPtr mStream; + spa_hook mStreamListener{}; + spa_io_rate_match *mRateMatch{}; + std::unique_ptr mChannelPtrs; + uint mNumChannels{}; + + static constexpr pw_stream_events CreateEvents() + { + pw_stream_events ret{}; + ret.version = PW_VERSION_STREAM_EVENTS; + ret.state_changed = &PipeWirePlayback::stateChangedCallbackC; + ret.io_changed = &PipeWirePlayback::ioChangedCallbackC; + ret.process = &PipeWirePlayback::outputCallbackC; + return ret; + } + +public: + PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { } + ~PipeWirePlayback() + { + /* Stop the mainloop so the stream can be properly destroyed. */ + if(mLoop) mLoop.stop(); + } + + DEF_NEWDEL(PipeWirePlayback) +}; + + +void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) +{ mLoop.signal(false); } + +void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size) +{ + switch(id) + { + case SPA_IO_RateMatch: + if(size >= sizeof(spa_io_rate_match)) + mRateMatch = static_cast(area); + break; + } +} + +void PipeWirePlayback::outputCallback() +{ + pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())}; + if(unlikely(!pw_buf)) return; + + /* For planar formats, each datas[] seems to contain one channel, so store + * the pointers in an array. Limit the render length in case the available + * buffer length in any one channel is smaller than we wanted (shouldn't + * be, but just in case). + */ + spa_data *datas{pw_buf->buffer->datas}; + const size_t chancount{minu(mNumChannels, pw_buf->buffer->n_datas)}; + /* TODO: How many samples should actually be written? 'maxsize' can be 16k + * samples, which is excessive (~341ms @ 48khz). SPA_IO_RateMatch contains + * a 'size' field that apparently indicates how many samples should be + * written per update, but it's not obviously right. + */ + uint length{mRateMatch ? mRateMatch->size : mDevice->UpdateSize}; + for(size_t i{0};i < chancount;++i) + { + length = minu(length, datas[i].maxsize/sizeof(float)); + mChannelPtrs[i] = static_cast(datas[i].data); + } + + mDevice->renderSamples({mChannelPtrs.get(), chancount}, length); + + for(size_t i{0};i < chancount;++i) + { + datas[i].chunk->offset = 0; + datas[i].chunk->stride = sizeof(float); + datas[i].chunk->size = length * sizeof(float); + } + pw_buf->size = length; + pw_stream_queue_buffer(mStream.get(), pw_buf); +} + + +void PipeWirePlayback::open(const char *name) +{ + static std::atomic OpenCount{0}; + + uint32_t targetid{PwIdAny}; + std::string devname{}; + gEventHandler.waitForInit(); + if(!name) + { + EventWatcherLockGuard _{gEventHandler}; + auto&& devlist = DeviceNode::GetList(); + + auto match = devlist.cend(); + if(!DefaultSinkDevice.empty()) + { + auto match_default = [](const DeviceNode &n) -> bool + { return n.mDevName == DefaultSinkDevice; }; + match = std::find_if(devlist.cbegin(), devlist.cend(), match_default); + } + if(match == devlist.cend()) + { + auto match_playback = [](const DeviceNode &n) -> bool + { return n.mType != NodeType::Source; }; + match = std::find_if(devlist.cbegin(), devlist.cend(), match_playback); + if(match == devlist.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "No PipeWire playback device found"}; + } + + targetid = match->mId; + devname = match->mName; + } + else + { + EventWatcherLockGuard _{gEventHandler}; + auto&& devlist = DeviceNode::GetList(); + + auto match_name = [name](const DeviceNode &n) -> bool + { return n.mType != NodeType::Source && n.mName == name; }; + auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name); + if(match == devlist.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + + targetid = match->mId; + devname = match->mName; + } + + if(!mLoop) + { + const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)}; + const std::string thread_name{"ALSoftP" + std::to_string(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}; + if(int res{mLoop.start()}) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start PipeWire mainloop (res: %d)", res}; + } + MainloopUniqueLock mlock{mLoop}; + if(!mContext) + { + pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)}; + mContext = mLoop.newContext(cprops); + if(!mContext) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create PipeWire event context (errno: %d)\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}; + } + mlock.unlock(); + + /* TODO: Ensure the target ID is still valid/usable and accepts streams. */ + + mTargetId = targetid; + if(!devname.empty()) + mDevice->DeviceName = std::move(devname); + else + mDevice->DeviceName = pwireDevice; +} + +bool PipeWirePlayback::reset() +{ + if(mStream) + { + MainloopLockGuard _{mLoop}; + mStream = nullptr; + } + mStreamListener = {}; + mRateMatch = nullptr; + mTimeBase = GetDeviceClockTime(mDevice); + + /* If connecting to a specific device, update various device parameters to + * match its format. + */ + bool is51rear{false}; + mDevice->Flags.reset(DirectEar); + if(mTargetId != PwIdAny) + { + EventWatcherLockGuard _{gEventHandler}; + auto&& devlist = DeviceNode::GetList(); + + auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool + { return targetid == n.mId; }; + auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id); + if(match != devlist.cend()) + { + 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}; + mDevice->Frequency = match->mSampleRate; + mDevice->UpdateSize = static_cast(clampd(mDevice->UpdateSize*scale + 0.5, + 64.0, 8192.0)); + mDevice->BufferSize = mDevice->UpdateSize * 2; + } + if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig) + mDevice->FmtChans = match->mChannels; + if(match->mChannels == DevFmtStereo && match->mIsHeadphones) + mDevice->Flags.set(DirectEar); + is51rear = match->mIs51Rear; + } + } + /* 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)}; + + /* TODO: How to tell what an appropriate size is? Examples just use this + * magic value. + */ + constexpr uint32_t pod_buffer_size{1024}; + auto pod_buffer = std::make_unique(pod_buffer_size); + spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)}; + + const spa_pod *params{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)}; + if(!params) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set PipeWire audio format parameters"}; + + pw_properties *props{pw_properties_new( + PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Playback", + PW_KEY_MEDIA_ROLE, "Game", + PW_KEY_NODE_ALWAYS_PROCESS, "true", + nullptr)}; + if(!props) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create PipeWire stream properties (errno: %d)", errno}; + + auto&& binary = GetProcBinary(); + const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; + /* TODO: Which properties are actually needed here? Any others that could + * be useful? + */ + pw_properties_set(props, PW_KEY_NODE_NAME, appname); + pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, appname); + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->UpdateSize, + mDevice->Frequency); + pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency); + + MainloopUniqueLock plock{mLoop}; + /* The stream takes overship of 'props', even in the case of failure. */ + 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}; + static constexpr pw_stream_events streamEvents{CreateEvents()}; + pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this); + + constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE + | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS}; + if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, mTargetId, Flags, ¶ms, 1)}) + throw al::backend_exception{al::backend_error::DeviceError, + "Error connecting PipeWire stream (res: %d)", res}; + + /* Wait for the stream to become paused (ready to start streaming). */ + pw_stream_state state{}; + const char *error{}; + plock.wait([stream=mStream.get(),&state,&error]() + { + state = pw_stream_get_state(stream, &error); + if(state == PW_STREAM_STATE_ERROR) + throw al::backend_exception{al::backend_error::DeviceError, + "Error connecting PipeWire stream: \"%s\"", error}; + return state == PW_STREAM_STATE_PAUSED; + }); + + /* TODO: Update mDevice->BufferSize with the total known buffering delay + * from the head of this playback stream to the tail of the device output. + */ + mDevice->BufferSize = mDevice->UpdateSize * 2; + plock.unlock(); + + mNumChannels = mDevice->channelsFromFmt(); + mChannelPtrs = std::make_unique(mNumChannels); + + setDefaultWFXChannelOrder(); + + return true; +} + +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}; + + /* Wait for the stream to start playing (would be nice to not, but we need + * the actual update size which is only available after starting). + */ + pw_stream_state state{}; + const char *error{}; + plock.wait([stream=mStream.get(),&state,&error]() + { + state = pw_stream_get_state(stream, &error); + return state != PW_STREAM_STATE_PAUSED; + }); + + if(state == PW_STREAM_STATE_ERROR) + throw al::backend_exception{al::backend_error::DeviceError, + "PipeWire stream error: %s", error ? error : "(unknown)"}; + if(state == PW_STREAM_STATE_STREAMING && mRateMatch && mRateMatch->size) + { + mDevice->UpdateSize = mRateMatch->size; + mDevice->BufferSize = mDevice->UpdateSize * 2; + } +} + +void PipeWirePlayback::stop() +{ + MainloopUniqueLock plock{mLoop}; + if(int res{pw_stream_set_active(mStream.get(), false)}) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to stop PipeWire stream (res: %d)", res}; + + /* Wait for the stream to stop playing. */ + plock.wait([stream=mStream.get()]() + { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; }); +} + +ClockLatency PipeWirePlayback::getClockLatency() +{ + /* Given a real-time low-latency output, this is rather complicated to get + * accurate timing. So, here we go. + */ + + /* First, get the stream time info (tick delay, ticks played, and the + * CLOCK_MONOTONIC time closest to when that last tick was played). + */ + pw_time ptime{}; + if(mStream) + { + MainloopLockGuard _{mLoop}; + if(int res{pw_stream_get_time(mStream.get(), &ptime)}) + ERR("Failed to get PipeWire stream time (res: %d)\n", res); + } + + /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the + * monotonic clock closest to 'now', and the last mixer time at 'now'). + */ + nanoseconds mixtime{}; + timespec tspec{}; + uint refcount; + do { + refcount = mDevice->waitForMix(); + mixtime = GetDeviceClockTime(mDevice); + clock_gettime(CLOCK_MONOTONIC, &tspec); + std::atomic_thread_fence(std::memory_order_acquire); + } while(refcount != ReadRef(mDevice->MixCount)); + + /* Convert the monotonic clock, stream ticks, and stream delay to + * nanoseconds. + */ + nanoseconds monoclock{seconds{tspec.tv_sec} + nanoseconds{tspec.tv_nsec}}; + nanoseconds curtic{}, delay{}; + if(unlikely(ptime.rate.denom < 1)) + { + /* If there's no stream rate, the stream hasn't had a chance to get + * going and return time info yet. Just use dummy values. + */ + ptime.now = monoclock.count(); + curtic = mixtime; + delay = nanoseconds{seconds{mDevice->BufferSize}} / mDevice->Frequency; + } + else + { + /* The stream gets recreated with each reset, so include the time that + * had already passed with previous streams. + */ + curtic = mTimeBase; + /* More safely scale the ticks to avoid overflowing the pre-division + * temporary as it gets larger. + */ + curtic += seconds{ptime.ticks / ptime.rate.denom} * ptime.rate.num; + curtic += nanoseconds{seconds{ptime.ticks%ptime.rate.denom} * ptime.rate.num} / + ptime.rate.denom; + + /* The delay should be small enough to not worry about overflow. */ + delay = nanoseconds{seconds{ptime.delay} * ptime.rate.num} / ptime.rate.denom; + } + + /* If the mixer time is ahead of the stream time, there's that much more + * delay relative to the stream delay. + */ + if(mixtime > curtic) + delay += mixtime - curtic; + /* Reduce the delay according to how much time has passed since the known + * stream time. This isn't 100% accurate since the system monotonic clock + * doesn't tick at the exact same rate as the audio device, but it should + * be good enough with ptime.now being constantly updated every few + * milliseconds with ptime.ticks. + */ + delay -= monoclock - nanoseconds{ptime.now}; + + /* Return the mixer time and delay. Clamp the delay to no less than 0, + * incase timer drift got that severe. + */ + ClockLatency ret{}; + ret.ClockTime = mixtime; + ret.Latency = std::max(delay, nanoseconds{}); + + return ret; +} + + +class PipeWireCapture final : public BackendBase { + void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error); + static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state, + const char *error) + { static_cast(data)->stateChangedCallback(old, state, error); } + + void inputCallback(); + static void inputCallbackC(void *data) + { static_cast(data)->inputCallback(); } + + void open(const char *name) override; + void start() override; + void stop() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; + + uint32_t mTargetId{PwIdAny}; + ThreadMainloop mLoop; + PwContextPtr mContext; + PwCorePtr mCore; + PwStreamPtr mStream; + spa_hook mStreamListener{}; + + RingBufferPtr mRing{}; + + static constexpr pw_stream_events CreateEvents() + { + pw_stream_events ret{}; + ret.version = PW_VERSION_STREAM_EVENTS; + ret.state_changed = &PipeWireCapture::stateChangedCallbackC; + ret.process = &PipeWireCapture::inputCallbackC; + return ret; + } + +public: + PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { } + ~PipeWireCapture() { if(mLoop) mLoop.stop(); } + + DEF_NEWDEL(PipeWireCapture) +}; + + +void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) +{ mLoop.signal(false); } + +void PipeWireCapture::inputCallback() +{ + pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())}; + if(unlikely(!pw_buf)) return; + + spa_data *bufdata{pw_buf->buffer->datas}; + const uint offset{minu(bufdata->chunk->offset, bufdata->maxsize)}; + const uint size{minu(bufdata->chunk->size, bufdata->maxsize - offset)}; + + mRing->write(static_cast(bufdata->data) + offset, size / mRing->getElemSize()); + + pw_stream_queue_buffer(mStream.get(), pw_buf); +} + + +void PipeWireCapture::open(const char *name) +{ + static std::atomic OpenCount{0}; + + uint32_t targetid{PwIdAny}; + std::string devname{}; + gEventHandler.waitForInit(); + if(!name) + { + EventWatcherLockGuard _{gEventHandler}; + auto&& devlist = DeviceNode::GetList(); + + auto match = devlist.cend(); + if(!DefaultSourceDevice.empty()) + { + auto match_default = [](const DeviceNode &n) -> bool + { return n.mDevName == DefaultSourceDevice; }; + match = std::find_if(devlist.cbegin(), devlist.cend(), match_default); + } + if(match == devlist.cend()) + { + auto match_capture = [](const DeviceNode &n) -> bool + { return n.mType != NodeType::Sink; }; + match = std::find_if(devlist.cbegin(), devlist.cend(), match_capture); + } + if(match == devlist.cend()) + { + match = devlist.cbegin(); + if(match == devlist.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "No PipeWire capture device found"}; + } + + targetid = match->mId; + if(match->mType != NodeType::Sink) devname = match->mName; + else devname = MonitorPrefix+match->mName; + } + else + { + EventWatcherLockGuard _{gEventHandler}; + auto&& devlist = DeviceNode::GetList(); + + auto match_name = [name](const DeviceNode &n) -> bool + { return n.mType != NodeType::Sink && n.mName == name; }; + auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name); + if(match == devlist.cend() && std::strncmp(name, MonitorPrefix, MonitorPrefixLen) == 0) + { + const char *sinkname{name + MonitorPrefixLen}; + auto match_sinkname = [sinkname](const DeviceNode &n) -> bool + { return n.mType == NodeType::Sink && n.mName == sinkname; }; + match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname); + } + if(match == devlist.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + + targetid = match->mId; + devname = name; + } + + if(!mLoop) + { + const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)}; + const std::string thread_name{"ALSoftC" + std::to_string(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}; + if(int res{mLoop.start()}) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start PipeWire mainloop (res: %d)", res}; + } + MainloopUniqueLock mlock{mLoop}; + if(!mContext) + { + pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)}; + mContext = mLoop.newContext(cprops); + if(!mContext) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create PipeWire event context (errno: %d)\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}; + } + mlock.unlock(); + + /* TODO: Ensure the target ID is still valid/usable and accepts streams. */ + + mTargetId = targetid; + if(!devname.empty()) + mDevice->DeviceName = std::move(devname); + else + mDevice->DeviceName = pwireInput; + + + bool is51rear{false}; + if(mTargetId != PwIdAny) + { + EventWatcherLockGuard _{gEventHandler}; + auto&& devlist = DeviceNode::GetList(); + + auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool + { return targetid == n.mId; }; + auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id); + if(match != devlist.cend()) + is51rear = match->mIs51Rear; + } + spa_audio_info_raw info{make_spa_info(mDevice, is51rear, UseDevType)}; + + constexpr uint32_t pod_buffer_size{1024}; + auto pod_buffer = std::make_unique(pod_buffer_size); + spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)}; + + const spa_pod *params[]{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)}; + if(!params[0]) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set PipeWire audio format parameters"}; + + pw_properties *props{pw_properties_new( + PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Game", + PW_KEY_NODE_ALWAYS_PROCESS, "true", + nullptr)}; + if(!props) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create PipeWire stream properties (errno: %d)", errno}; + + auto&& binary = GetProcBinary(); + const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; + pw_properties_set(props, PW_KEY_NODE_NAME, appname); + pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, appname); + /* We don't actually care what the latency/update size is, as long as it's + * reasonable. Unfortunately, when unspecified PipeWire seems to default to + * around 40ms, which isn't great. So request 20ms instead. + */ + 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); + + MainloopUniqueLock plock{mLoop}; + 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}; + 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, mTargetId, Flags, params, 1)}) + throw al::backend_exception{al::backend_error::DeviceError, + "Error connecting PipeWire stream (res: %d)", res}; + + /* Wait for the stream to become paused (ready to start streaming). */ + pw_stream_state state{}; + const char *error{}; + plock.wait([stream=mStream.get(),&state,&error]() + { + state = pw_stream_get_state(stream, &error); + if(state == PW_STREAM_STATE_ERROR) + throw al::backend_exception{al::backend_error::DeviceError, + "Error connecting PipeWire stream: \"%s\"", error}; + return state == PW_STREAM_STATE_PAUSED; + }); + plock.unlock(); + + setDefaultWFXChannelOrder(); + + /* Ensure at least a 100ms capture buffer. */ + mRing = RingBuffer::Create(maxu(mDevice->Frequency/10, mDevice->BufferSize), + mDevice->frameSizeFromFmt(), false); +} + + +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}; + + pw_stream_state state{}; + const char *error{}; + plock.wait([stream=mStream.get(),&state,&error]() + { + state = pw_stream_get_state(stream, &error); + return state != PW_STREAM_STATE_PAUSED; + }); + + if(state == PW_STREAM_STATE_ERROR) + throw al::backend_exception{al::backend_error::DeviceError, + "PipeWire stream error: %s", error ? error : "(unknown)"}; +} + +void PipeWireCapture::stop() +{ + MainloopUniqueLock plock{mLoop}; + if(int res{pw_stream_set_active(mStream.get(), false)}) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to stop PipeWire stream (res: %d)", res}; + + plock.wait([stream=mStream.get()]() + { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; }); +} + +uint PipeWireCapture::availableSamples() +{ return static_cast(mRing->readSpace()); } + +void PipeWireCapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } + +} // namespace + + +bool PipeWireBackendFactory::init() +{ + if(!pwire_load()) + return false; + + pw_init(0, nullptr); + if(!gEventHandler.init()) + return false; + + if(!GetConfigValueBool(nullptr, "pipewire", "assume-audio", false) + && !gEventHandler.waitForAudio()) + { + gEventHandler.kill(); + /* 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"); + return false; + } + return true; +} + +bool PipeWireBackendFactory::querySupport(BackendType type) +{ return type == BackendType::Playback || type == BackendType::Capture; } + +std::string PipeWireBackendFactory::probe(BackendType type) +{ + std::string outnames; + + gEventHandler.waitForInit(); + EventWatcherLockGuard _{gEventHandler}; + auto&& devlist = DeviceNode::GetList(); + + auto match_defsink = [](const DeviceNode &n) -> bool + { return n.mDevName == DefaultSinkDevice; }; + auto match_defsource = [](const DeviceNode &n) -> bool + { return n.mDevName == DefaultSourceDevice; }; + + auto sort_devnode = [](DeviceNode &lhs, DeviceNode &rhs) noexcept -> bool + { return lhs.mId < rhs.mId; }; + std::sort(devlist.begin(), devlist.end(), sort_devnode); + + auto defmatch = devlist.cbegin(); + switch(type) + { + case BackendType::Playback: + defmatch = std::find_if(defmatch, devlist.cend(), match_defsink); + if(defmatch != devlist.cend()) + { + /* Includes null char. */ + outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1); + } + for(auto iter = devlist.cbegin();iter != devlist.cend();++iter) + { + if(iter != defmatch && iter->mType != NodeType::Source) + outnames.append(iter->mName.c_str(), iter->mName.length()+1); + } + break; + case BackendType::Capture: + defmatch = std::find_if(defmatch, devlist.cend(), match_defsource); + if(defmatch != devlist.cend()) + { + if(defmatch->mType == NodeType::Sink) + outnames.append(MonitorPrefix); + outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1); + } + for(auto iter = devlist.cbegin();iter != devlist.cend();++iter) + { + if(iter != defmatch) + { + if(iter->mType == NodeType::Sink) + outnames.append(MonitorPrefix); + outnames.append(iter->mName.c_str(), iter->mName.length()+1); + } + } + break; + } + + return outnames; +} + +BackendPtr PipeWireBackendFactory::createBackend(DeviceBase *device, BackendType type) +{ + if(type == BackendType::Playback) + return BackendPtr{new PipeWirePlayback{device}}; + if(type == BackendType::Capture) + return BackendPtr{new PipeWireCapture{device}}; + return nullptr; +} + +BackendFactory &PipeWireBackendFactory::getFactory() +{ + static PipeWireBackendFactory factory{}; + return factory; +} diff --git a/modules/openal-soft/Alc/backends/pipewire.h b/modules/openal-soft/Alc/backends/pipewire.h new file mode 100644 index 0000000..5f93023 --- /dev/null +++ b/modules/openal-soft/Alc/backends/pipewire.h @@ -0,0 +1,23 @@ +#ifndef BACKENDS_PIPEWIRE_H +#define BACKENDS_PIPEWIRE_H + +#include + +#include "base.h" + +struct DeviceBase; + +struct PipeWireBackendFactory final : public BackendFactory { +public: + bool init() override; + + bool querySupport(BackendType type) override; + + std::string probe(BackendType type) override; + + BackendPtr createBackend(DeviceBase *device, BackendType type) override; + + static BackendFactory &getFactory(); +}; + +#endif /* BACKENDS_PIPEWIRE_H */ diff --git a/modules/openal-soft/Alc/backends/portaudio.cpp b/modules/openal-soft/Alc/backends/portaudio.cpp index 6df9843..9c94587 100644 --- a/modules/openal-soft/Alc/backends/portaudio.cpp +++ b/modules/openal-soft/Alc/backends/portaudio.cpp @@ -20,24 +20,25 @@ #include "config.h" -#include "backends/portaudio.h" +#include "portaudio.h" #include #include #include -#include "alMain.h" -#include "alu.h" -#include "alconfig.h" +#include "alc/alconfig.h" +#include "alnumeric.h" +#include "core/device.h" +#include "core/logging.h" +#include "dynload.h" #include "ringbuffer.h" -#include "compat.h" #include namespace { -constexpr ALCchar pa_device[] = "PortAudio Default"; +constexpr char pa_device[] = "PortAudio Default"; #ifdef HAVE_DYNLOAD @@ -71,25 +72,28 @@ MAKE_FUNC(Pa_GetStreamInfo); struct PortPlayback final : public BackendBase { - PortPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + 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; static int writeCallbackC(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData); - int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags); + const PaStreamCallbackFlags statusFlags, void *userData) noexcept + { + return static_cast(userData)->writeCallback(inputBuffer, outputBuffer, + framesPerBuffer, timeInfo, statusFlags); + } - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; PaStream *mStream{nullptr}; PaStreamParameters mParams{}; - ALuint mUpdateSize{0u}; + uint mUpdateSize{0u}; - static constexpr inline const char *CurrentPrefix() noexcept { return "PortPlayback::"; } DEF_NEWDEL(PortPlayback) }; @@ -102,88 +106,82 @@ PortPlayback::~PortPlayback() } -int PortPlayback::writeCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData) +int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept { - return static_cast(userData)->writeCallback(inputBuffer, outputBuffer, - framesPerBuffer, timeInfo, statusFlags); -} - -int PortPlayback::writeCallback(const void* UNUSED(inputBuffer), void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* UNUSED(timeInfo), - const PaStreamCallbackFlags UNUSED(statusFlags)) -{ - lock(); - aluMixData(mDevice, outputBuffer, framesPerBuffer); - unlock(); + mDevice->renderSamples(outputBuffer, static_cast(framesPerBuffer), + static_cast(mParams.channelCount)); return 0; } -ALCenum PortPlayback::open(const ALCchar *name) +void PortPlayback::open(const char *name) { if(!name) name = pa_device; else if(strcmp(name, pa_device) != 0) - return ALC_INVALID_VALUE; - - mUpdateSize = mDevice->UpdateSize; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; - mParams.device = -1; - if(!ConfigValueInt(nullptr, "port", "device", &mParams.device) || mParams.device < 0) - mParams.device = Pa_GetDefaultOutputDevice(); - mParams.suggestedLatency = mDevice->BufferSize / static_cast(mDevice->Frequency); - mParams.hostApiSpecificStreamInfo = nullptr; + PaStreamParameters params{}; + auto devidopt = ConfigValueInt(nullptr, "port", "device"); + if(devidopt && *devidopt >= 0) params.device = *devidopt; + else params.device = Pa_GetDefaultOutputDevice(); + params.suggestedLatency = mDevice->BufferSize / static_cast(mDevice->Frequency); + params.hostApiSpecificStreamInfo = nullptr; - mParams.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); + params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); switch(mDevice->FmtType) { - case DevFmtByte: - mParams.sampleFormat = paInt8; - break; - case DevFmtUByte: - mParams.sampleFormat = paUInt8; - break; - case DevFmtUShort: - /* fall-through */ - case DevFmtShort: - mParams.sampleFormat = paInt16; - break; - case DevFmtUInt: - /* fall-through */ - case DevFmtInt: - mParams.sampleFormat = paInt32; - break; - case DevFmtFloat: - mParams.sampleFormat = paFloat32; - break; + 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; } retry_open: - PaError err{Pa_OpenStream(&mStream, nullptr, &mParams, mDevice->Frequency, mDevice->UpdateSize, + PaStream *stream{}; + PaError err{Pa_OpenStream(&stream, nullptr, ¶ms, mDevice->Frequency, mDevice->UpdateSize, paNoFlag, &PortPlayback::writeCallbackC, this)}; if(err != paNoError) { - if(mParams.sampleFormat == paFloat32) + if(params.sampleFormat == paFloat32) { - mParams.sampleFormat = paInt16; + params.sampleFormat = paInt16; goto retry_open; } - ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err)); - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", + Pa_GetErrorText(err)}; } - mDevice->DeviceName = name; - return ALC_NO_ERROR; + Pa_CloseStream(mStream); + mStream = stream; + mParams = params; + mUpdateSize = mDevice->UpdateSize; + mDevice->DeviceName = name; } -ALCboolean PortPlayback::reset() +bool PortPlayback::reset() { const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)}; - mDevice->Frequency = streamInfo->sampleRate; + mDevice->Frequency = static_cast(streamInfo->sampleRate); mDevice->UpdateSize = mUpdateSize; if(mParams.sampleFormat == paInt8) @@ -199,32 +197,29 @@ ALCboolean PortPlayback::reset() else { ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat); - return ALC_FALSE; + return false; } - if(mParams.channelCount == 2) + if(mParams.channelCount >= 2) mDevice->FmtChans = DevFmtStereo; else if(mParams.channelCount == 1) mDevice->FmtChans = DevFmtMono; else { ERR("Unexpected channel count: %u\n", mParams.channelCount); - return ALC_FALSE; + return false; } - SetDefaultChannelOrder(mDevice); + setDefaultChannelOrder(); - return ALC_TRUE; + return true; } -ALCboolean PortPlayback::start() +void PortPlayback::start() { - PaError err{Pa_StartStream(mStream)}; - if(err != paNoError) - { - ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err)); - return ALC_FALSE; - } - return ALC_TRUE; + const PaError err{Pa_StartStream(mStream)}; + if(err == paNoError) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s", + Pa_GetErrorText(err)}; } void PortPlayback::stop() @@ -236,27 +231,30 @@ void PortPlayback::stop() struct PortCapture final : public BackendBase { - PortCapture(ALCdevice *device) noexcept : BackendBase{device} { } + PortCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~PortCapture() override; + int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept; static int readCallbackC(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData); - int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags); + const PaStreamCallbackFlags statusFlags, void *userData) noexcept + { + return static_cast(userData)->readCallback(inputBuffer, outputBuffer, + framesPerBuffer, timeInfo, statusFlags); + } - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; PaStream *mStream{nullptr}; PaStreamParameters mParams; RingBufferPtr mRing{nullptr}; - static constexpr inline const char *CurrentPrefix() noexcept { return "PortCapture::"; } DEF_NEWDEL(PortCapture) }; @@ -269,89 +267,74 @@ PortCapture::~PortCapture() } -int PortCapture::readCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void* userData) -{ - return static_cast(userData)->readCallback(inputBuffer, outputBuffer, - framesPerBuffer, timeInfo, statusFlags); -} - -int PortCapture::readCallback(const void *inputBuffer, void *UNUSED(outputBuffer), - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *UNUSED(timeInfo), - const PaStreamCallbackFlags UNUSED(statusFlags)) +int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept { mRing->write(inputBuffer, framesPerBuffer); return 0; } -ALCenum PortCapture::open(const ALCchar *name) +void PortCapture::open(const char *name) { if(!name) name = pa_device; else if(strcmp(name, pa_device) != 0) - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; - ALuint samples{mDevice->BufferSize}; + uint samples{mDevice->BufferSize}; samples = maxu(samples, 100 * mDevice->Frequency / 1000); - ALsizei frame_size{mDevice->frameSizeFromFmt()}; + uint frame_size{mDevice->frameSizeFromFmt()}; - mRing = CreateRingBuffer(samples, frame_size, false); - if(!mRing) return ALC_INVALID_VALUE; + mRing = RingBuffer::Create(samples, frame_size, false); - mParams.device = -1; - if(!ConfigValueInt(nullptr, "port", "capture", &mParams.device) || mParams.device < 0) - mParams.device = Pa_GetDefaultInputDevice(); + auto devidopt = ConfigValueInt(nullptr, "port", "capture"); + if(devidopt && *devidopt >= 0) mParams.device = *devidopt; + else mParams.device = Pa_GetDefaultOutputDevice(); mParams.suggestedLatency = 0.0f; mParams.hostApiSpecificStreamInfo = nullptr; switch(mDevice->FmtType) { - case DevFmtByte: - mParams.sampleFormat = paInt8; - break; - case DevFmtUByte: - mParams.sampleFormat = paUInt8; - break; - case DevFmtShort: - mParams.sampleFormat = paInt16; - break; - case DevFmtInt: - mParams.sampleFormat = paInt32; - break; - case DevFmtFloat: - mParams.sampleFormat = paFloat32; - break; - case DevFmtUInt: - case DevFmtUShort: - ERR("%s samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_VALUE; + case DevFmtByte: + mParams.sampleFormat = paInt8; + break; + case DevFmtUByte: + mParams.sampleFormat = paUInt8; + break; + case DevFmtShort: + mParams.sampleFormat = paInt16; + break; + case DevFmtInt: + mParams.sampleFormat = paInt32; + break; + case DevFmtFloat: + mParams.sampleFormat = paFloat32; + break; + case DevFmtUInt: + case DevFmtUShort: + throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported", + DevFmtTypeString(mDevice->FmtType)}; } - mParams.channelCount = mDevice->channelsFromFmt(); + mParams.channelCount = static_cast(mDevice->channelsFromFmt()); PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency, paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)}; if(err != paNoError) - { - ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err)); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", + Pa_GetErrorText(err)}; mDevice->DeviceName = name; - return ALC_NO_ERROR; } -ALCboolean PortCapture::start() +void PortCapture::start() { - PaError err{Pa_StartStream(mStream)}; + const PaError err{Pa_StartStream(mStream)}; if(err != paNoError) - { - ERR("Error starting stream: %s\n", Pa_GetErrorText(err)); - return ALC_FALSE; - } - return ALC_TRUE; + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start recording: %s", Pa_GetErrorText(err)}; } void PortCapture::stop() @@ -362,14 +345,11 @@ void PortCapture::stop() } -ALCuint PortCapture::availableSamples() -{ return mRing->readSpace(); } +uint PortCapture::availableSamples() +{ return static_cast(mRing->readSpace()); } -ALCenum PortCapture::captureSamples(ALCvoid *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} +void PortCapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } } // namespace @@ -437,19 +417,21 @@ bool PortBackendFactory::init() bool PortBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -void PortBackendFactory::probe(DevProbe type, std::string *outnames) +std::string PortBackendFactory::probe(BackendType type) { + std::string outnames; switch(type) { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(pa_device, sizeof(pa_device)); - break; + case BackendType::Playback: + case BackendType::Capture: + /* Includes null char. */ + outnames.append(pa_device, sizeof(pa_device)); + break; } + return outnames; } -BackendPtr PortBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new PortPlayback{device}}; diff --git a/modules/openal-soft/Alc/backends/portaudio.h b/modules/openal-soft/Alc/backends/portaudio.h index 082e902..c35ccff 100644 --- a/modules/openal-soft/Alc/backends/portaudio.h +++ b/modules/openal-soft/Alc/backends/portaudio.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_PORTAUDIO_H #define BACKENDS_PORTAUDIO_H -#include "backends/base.h" +#include "base.h" struct PortBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/pulseaudio.cpp b/modules/openal-soft/Alc/backends/pulseaudio.cpp index f548381..67e0023 100644 --- a/modules/openal-soft/Alc/backends/pulseaudio.cpp +++ b/modules/openal-soft/Alc/backends/pulseaudio.cpp @@ -21,36 +21,56 @@ #include "config.h" -#include "backends/pulseaudio.h" - -#include -#include +#include "pulseaudio.h" +#include #include -#include -#include #include -#include -#include +#include +#include #include - -#include "alMain.h" -#include "alu.h" -#include "alconfig.h" -#include "compat.h" -#include "alexcpt.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "albyte.h" +#include "alc/alconfig.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "aloptional.h" +#include "alspan.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "dynload.h" +#include "opthelpers.h" +#include "strutils.h" +#include "vector.h" #include namespace { +using uint = unsigned int; + #ifdef HAVE_DYNLOAD #define PULSE_FUNCS(MAGIC) \ MAGIC(pa_mainloop_new); \ MAGIC(pa_mainloop_free); \ MAGIC(pa_mainloop_set_poll_func); \ MAGIC(pa_mainloop_run); \ + MAGIC(pa_mainloop_quit); \ MAGIC(pa_mainloop_get_api); \ MAGIC(pa_context_new); \ MAGIC(pa_context_unref); \ @@ -97,6 +117,7 @@ namespace { MAGIC(pa_channel_map_snprint); \ MAGIC(pa_channel_map_equal); \ MAGIC(pa_channel_map_superset); \ + MAGIC(pa_channel_position_to_string); \ MAGIC(pa_operation_get_state); \ MAGIC(pa_operation_unref); \ MAGIC(pa_sample_spec_valid); \ @@ -117,6 +138,7 @@ PULSE_FUNCS(MAKE_FUNC) #define pa_mainloop_free ppa_mainloop_free #define pa_mainloop_set_poll_func ppa_mainloop_set_poll_func #define pa_mainloop_run ppa_mainloop_run +#define pa_mainloop_quit ppa_mainloop_quit #define pa_mainloop_get_api ppa_mainloop_get_api #define pa_context_new ppa_context_new #define pa_context_unref ppa_context_unref @@ -153,12 +175,13 @@ PULSE_FUNCS(MAKE_FUNC) #define pa_stream_get_device_name ppa_stream_get_device_name #define pa_stream_get_latency ppa_stream_get_latency #define pa_stream_set_buffer_attr_callback ppa_stream_set_buffer_attr_callback -#define pa_stream_begin_write ppa_stream_begin_write*/ +#define pa_stream_begin_write ppa_stream_begin_write #define pa_channel_map_init_auto ppa_channel_map_init_auto #define pa_channel_map_parse ppa_channel_map_parse #define pa_channel_map_snprint ppa_channel_map_snprint #define pa_channel_map_equal ppa_channel_map_equal #define pa_channel_map_superset ppa_channel_map_superset +#define pa_channel_position_to_string ppa_channel_position_to_string #define pa_operation_get_state ppa_operation_get_state #define pa_operation_unref ppa_operation_unref #define pa_sample_spec_valid ppa_sample_spec_valid @@ -213,183 +236,222 @@ constexpr pa_channel_map MonoChanMap{ } }; -size_t ChannelFromPulse(pa_channel_position_t chan) -{ - switch(chan) - { - case PA_CHANNEL_POSITION_INVALID: break; - case PA_CHANNEL_POSITION_MONO: return FrontCenter; - case PA_CHANNEL_POSITION_FRONT_LEFT: return FrontLeft; - case PA_CHANNEL_POSITION_FRONT_RIGHT: return FrontRight; - case PA_CHANNEL_POSITION_FRONT_CENTER: return FrontCenter; - case PA_CHANNEL_POSITION_REAR_CENTER: return BackCenter; - case PA_CHANNEL_POSITION_REAR_LEFT: return BackLeft; - case PA_CHANNEL_POSITION_REAR_RIGHT: return BackRight; - case PA_CHANNEL_POSITION_LFE: return LFE; - case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: break; - case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: break; - case PA_CHANNEL_POSITION_SIDE_LEFT: return SideLeft; - case PA_CHANNEL_POSITION_SIDE_RIGHT: return SideRight; - case PA_CHANNEL_POSITION_AUX0: return Aux0; - case PA_CHANNEL_POSITION_AUX1: return Aux1; - case PA_CHANNEL_POSITION_AUX2: return Aux2; - case PA_CHANNEL_POSITION_AUX3: return Aux3; - case PA_CHANNEL_POSITION_AUX4: return Aux4; - case PA_CHANNEL_POSITION_AUX5: return Aux5; - case PA_CHANNEL_POSITION_AUX6: return Aux6; - case PA_CHANNEL_POSITION_AUX7: return Aux7; - case PA_CHANNEL_POSITION_AUX8: return Aux8; - case PA_CHANNEL_POSITION_AUX9: return Aux9; - case PA_CHANNEL_POSITION_AUX10: return Aux10; - case PA_CHANNEL_POSITION_AUX11: return Aux11; - case PA_CHANNEL_POSITION_AUX12: return Aux12; - case PA_CHANNEL_POSITION_AUX13: return Aux13; - case PA_CHANNEL_POSITION_AUX14: return Aux14; - case PA_CHANNEL_POSITION_AUX15: return Aux15; - case PA_CHANNEL_POSITION_AUX16: break; - case PA_CHANNEL_POSITION_AUX17: break; - case PA_CHANNEL_POSITION_AUX18: break; - case PA_CHANNEL_POSITION_AUX19: break; - case PA_CHANNEL_POSITION_AUX20: break; - case PA_CHANNEL_POSITION_AUX21: break; - case PA_CHANNEL_POSITION_AUX22: break; - case PA_CHANNEL_POSITION_AUX23: break; - case PA_CHANNEL_POSITION_AUX24: break; - case PA_CHANNEL_POSITION_AUX25: break; - case PA_CHANNEL_POSITION_AUX26: break; - case PA_CHANNEL_POSITION_AUX27: break; - case PA_CHANNEL_POSITION_AUX28: break; - case PA_CHANNEL_POSITION_AUX29: break; - case PA_CHANNEL_POSITION_AUX30: break; - case PA_CHANNEL_POSITION_AUX31: break; - case PA_CHANNEL_POSITION_TOP_CENTER: break; - case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return UpperFrontLeft; - case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return UpperFrontRight; - case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: break; - case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return UpperBackLeft; - case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return UpperBackRight; - case PA_CHANNEL_POSITION_TOP_REAR_CENTER: break; - case PA_CHANNEL_POSITION_MAX: break; - } - throw al::backend_exception{ALC_INVALID_VALUE, "Unexpected channel enum %d", chan}; -} - -void SetChannelOrderFromMap(ALCdevice *device, const pa_channel_map &chanmap) -{ - device->RealOut.ChannelIndex.fill(-1); - for(int i{0};i < chanmap.channels;++i) - device->RealOut.ChannelIndex[ChannelFromPulse(chanmap.map[i])] = i; -} - /* *grumble* Don't use enums for bitflags. */ -inline pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs) +constexpr inline pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs) { return pa_stream_flags_t(int(lhs) | int(rhs)); } inline pa_stream_flags_t& operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) { - lhs = pa_stream_flags_t(int(lhs) | int(rhs)); + lhs = lhs | rhs; return lhs; } +inline pa_stream_flags_t& operator&=(pa_stream_flags_t &lhs, int rhs) +{ + lhs = pa_stream_flags_t(int(lhs) & rhs); + return lhs; +} + inline pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_flags_t rhs) { lhs = pa_context_flags_t(int(lhs) | int(rhs)); return lhs; } -inline pa_stream_flags_t& operator&=(pa_stream_flags_t &lhs, int rhs) + +struct DevMap { + std::string name; + std::string device_name; +}; + +bool checkName(const al::span list, const std::string &name) { - lhs = pa_stream_flags_t(int(lhs) & rhs); - return lhs; + auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; }; + return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } +al::vector PlaybackDevices; +al::vector CaptureDevices; + /* Global flags and properties */ pa_context_flags_t pulse_ctx_flags; -pa_mainloop *pulse_mainloop{nullptr}; +class PulseMainloop { + std::thread mThread; + std::mutex mMutex; + std::condition_variable mCondVar; + pa_mainloop *mMainloop{nullptr}; -std::mutex pulse_lock; -std::condition_variable pulse_condvar; + static int poll(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) noexcept + { + auto plock = static_cast*>(userdata); + plock->unlock(); + int r{::poll(ufds, nfds, timeout)}; + plock->lock(); + return r; + } -int pulse_poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) -{ - auto plock = static_cast*>(userdata); - plock->unlock(); - int r{poll(ufds, nfds, timeout)}; - plock->lock(); - return r; -} + int mainloop_proc() + { + SetRTPriority(); -int pulse_mainloop_thread() -{ - SetRTPriority(); + std::unique_lock plock{mMutex}; + mMainloop = pa_mainloop_new(); - std::unique_lock plock{pulse_lock}; - pulse_mainloop = pa_mainloop_new(); + pa_mainloop_set_poll_func(mMainloop, poll, &plock); + mCondVar.notify_all(); - pa_mainloop_set_poll_func(pulse_mainloop, pulse_poll_func, &plock); - pulse_condvar.notify_all(); + int ret{}; + pa_mainloop_run(mMainloop, &ret); - int ret{}; - pa_mainloop_run(pulse_mainloop, &ret); + pa_mainloop_free(mMainloop); + mMainloop = nullptr; - pa_mainloop_free(pulse_mainloop); - pulse_mainloop = nullptr; + return ret; + } - return ret; -} +public: + ~PulseMainloop() + { + if(mThread.joinable()) + { + { + std::lock_guard _{mMutex}; + pa_mainloop_quit(mMainloop, 0); + } + mThread.join(); + } + } + std::unique_lock getUniqueLock() { return std::unique_lock{mMutex}; } + std::condition_variable &getCondVar() noexcept { return mCondVar; } -/* PulseAudio Event Callbacks */ -void context_state_callback(pa_context *context, void* /*pdata*/) -{ - pa_context_state_t state{pa_context_get_state(context)}; - if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state)) - pulse_condvar.notify_all(); -} + void contextStateCallback(pa_context *context) noexcept + { + pa_context_state_t state{pa_context_get_state(context)}; + if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state)) + mCondVar.notify_all(); + } + static void contextStateCallbackC(pa_context *context, void *pdata) noexcept + { static_cast(pdata)->contextStateCallback(context); } -void stream_state_callback(pa_stream *stream, void* /*pdata*/) -{ - pa_stream_state_t state{pa_stream_get_state(stream)}; - if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state)) - pulse_condvar.notify_all(); -} + void streamStateCallback(pa_stream *stream) noexcept + { + pa_stream_state_t state{pa_stream_get_state(stream)}; + if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state)) + mCondVar.notify_all(); + } + static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept + { static_cast(pdata)->streamStateCallback(stream); } -void stream_success_callback(pa_stream* /*stream*/, int /*success*/, void* /*pdata*/) -{ - pulse_condvar.notify_all(); -} + void streamSuccessCallback(pa_stream*, int) noexcept + { mCondVar.notify_all(); } + static void streamSuccessCallbackC(pa_stream *stream, int success, void *pdata) noexcept + { static_cast(pdata)->streamSuccessCallback(stream, success); } -void wait_for_operation(pa_operation *op, std::unique_lock &plock) -{ - if(op) + void waitForOperation(pa_operation *op, std::unique_lock &plock) { - while(pa_operation_get_state(op) == PA_OPERATION_RUNNING) - pulse_condvar.wait(plock); - pa_operation_unref(op); + if(op) + { + mCondVar.wait(plock, + [op]() -> bool { return pa_operation_get_state(op) != PA_OPERATION_RUNNING; }); + pa_operation_unref(op); + } } -} + pa_context *connectContext(std::unique_lock &plock); -pa_context *connect_context(std::unique_lock &plock) -{ - const char *name{"OpenAL Soft"}; + pa_stream *connectStream(const char *device_name, std::unique_lock &plock, + pa_context *context, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, + pa_channel_map *chanmap, BackendType type); + + void close(pa_context *context, pa_stream *stream); - const PathNamePair &binname = GetProcBinary(); - if(!binname.fname.empty()) - name = binname.fname.c_str(); - if(UNLIKELY(!pulse_mainloop)) + void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) noexcept { - std::thread{pulse_mainloop_thread}.detach(); - while(!pulse_mainloop) - pulse_condvar.wait(plock); + if(eol) + { + mCondVar.notify_all(); + return; + } + + /* Skip this device is if it's already in the list. */ + auto match_devname = [info](const DevMap &entry) -> bool + { return entry.device_name == info->name; }; + if(std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), match_devname) != PlaybackDevices.cend()) + return; + + /* Make sure the display name (description) is unique. Append a number + * counter as needed. + */ + int count{1}; + std::string newname{info->description}; + while(checkName(PlaybackDevices, newname)) + { + newname = info->description; + newname += " #"; + newname += std::to_string(++count); + } + PlaybackDevices.emplace_back(DevMap{std::move(newname), info->name}); + DevMap &newentry = PlaybackDevices.back(); + + TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); } + static void deviceSinkCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept + { static_cast(pdata)->deviceSinkCallback(context, info, eol); } - pa_context *context{pa_context_new(pa_mainloop_get_api(pulse_mainloop), name)}; - if(!context) throw al::backend_exception{ALC_OUT_OF_MEMORY, "pa_context_new() failed"}; + void deviceSourceCallback(pa_context*, const pa_source_info *info, int eol) noexcept + { + if(eol) + { + mCondVar.notify_all(); + return; + } - pa_context_set_state_callback(context, context_state_callback, nullptr); + /* Skip this device is if it's already in the list. */ + auto match_devname = [info](const DevMap &entry) -> bool + { return entry.device_name == info->name; }; + if(std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), match_devname) != CaptureDevices.cend()) + return; + + /* Make sure the display name (description) is unique. Append a number + * counter as needed. + */ + int count{1}; + std::string newname{info->description}; + while(checkName(CaptureDevices, newname)) + { + newname = info->description; + newname += " #"; + newname += std::to_string(++count); + } + CaptureDevices.emplace_back(DevMap{std::move(newname), info->name}); + DevMap &newentry = CaptureDevices.back(); + + TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); + } + static void deviceSourceCallbackC(pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept + { static_cast(pdata)->deviceSourceCallback(context, info, eol); } + + void probePlaybackDevices(); + void probeCaptureDevices(); +}; + + +pa_context *PulseMainloop::connectContext(std::unique_lock &plock) +{ + if(!mMainloop) + { + mThread = std::thread{std::mem_fn(&PulseMainloop::mainloop_proc), this}; + mCondVar.wait(plock, [this]() noexcept { return mMainloop; }); + } + + pa_context *context{pa_context_new(pa_mainloop_get_api(mMainloop), nullptr)}; + if(!context) throw al::backend_exception{al::backend_error::OutOfMemory, + "pa_context_new() failed"}; + + pa_context_set_state_callback(context, &contextStateCallbackC, this); int err; if((err=pa_context_connect(context, nullptr, pulse_ctx_flags, nullptr)) >= 0) @@ -404,7 +466,7 @@ pa_context *connect_context(std::unique_lock &plock) break; } - pulse_condvar.wait(plock); + mCondVar.wait(plock); } } pa_context_set_state_callback(context, nullptr, nullptr); @@ -412,60 +474,24 @@ pa_context *connect_context(std::unique_lock &plock) if(err < 0) { pa_context_unref(context); - throw al::backend_exception{ALC_INVALID_VALUE, "Context did not connect (%s)", + throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect (%s)", pa_strerror(err)}; } return context; } - -void pulse_close(pa_context *context, pa_stream *stream) -{ - std::lock_guard _{pulse_lock}; - if(stream) - { - pa_stream_set_state_callback(stream, nullptr, nullptr); - pa_stream_set_moved_callback(stream, nullptr, nullptr); - pa_stream_set_write_callback(stream, nullptr, nullptr); - pa_stream_set_buffer_attr_callback(stream, nullptr, nullptr); - pa_stream_disconnect(stream); - pa_stream_unref(stream); - } - - pa_context_disconnect(context); - pa_context_unref(context); -} - - -struct DevMap { - std::string name; - std::string device_name; -}; - -bool checkName(const al::vector &list, const std::string &name) -{ - return std::find_if(list.cbegin(), list.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ) != list.cend(); -} - -al::vector PlaybackDevices; -al::vector CaptureDevices; - - -pa_stream *pulse_connect_stream(const char *device_name, std::unique_lock &plock, - pa_context *context, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, - pa_channel_map *chanmap, BackendType type) +pa_stream *PulseMainloop::connectStream(const char *device_name, + std::unique_lock &plock, pa_context *context, pa_stream_flags_t flags, + pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type) { const char *stream_id{(type==BackendType::Playback) ? "Playback Stream" : "Capture Stream"}; pa_stream *stream{pa_stream_new(context, stream_id, spec, chanmap)}; if(!stream) - throw al::backend_exception{ALC_OUT_OF_MEMORY, "pa_stream_new() failed (%s)", + throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed (%s)", pa_strerror(pa_context_errno(context))}; - pa_stream_set_state_callback(stream, stream_state_callback, nullptr); + pa_stream_set_state_callback(stream, &streamStateCallbackC, this); int err{(type==BackendType::Playback) ? pa_stream_connect_playback(stream, device_name, attr, flags, nullptr, nullptr) : @@ -473,8 +499,8 @@ pa_stream *pulse_connect_stream(const char *device_name, std::unique_lock bool - { return entry.device_name == info->name; } - ) != PlaybackDevices.cend()) - return; - - /* Make sure the display name (description) is unique. Append a number - * counter as needed. - */ - int count{1}; - std::string newname{info->description}; - while(checkName(PlaybackDevices, newname)) + std::lock_guard _{mMutex}; + if(stream) { - newname = info->description; - newname += " #"; - newname += std::to_string(++count); + pa_stream_set_state_callback(stream, nullptr, nullptr); + pa_stream_set_moved_callback(stream, nullptr, nullptr); + pa_stream_set_write_callback(stream, nullptr, nullptr); + pa_stream_set_buffer_attr_callback(stream, nullptr, nullptr); + pa_stream_disconnect(stream); + pa_stream_unref(stream); } - PlaybackDevices.emplace_back(DevMap{std::move(newname), info->name}); - DevMap &newentry = PlaybackDevices.back(); - TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); + pa_context_disconnect(context); + pa_context_unref(context); } -void probePlaybackDevices() + +void PulseMainloop::probePlaybackDevices() { - PlaybackDevices.clear(); + pa_context *context{}; + pa_stream *stream{}; + PlaybackDevices.clear(); try { - std::unique_lock plock{pulse_lock}; + std::unique_lock plock{mMutex}; - pa_context *context{connect_context(plock)}; + context = connectContext(plock); + pa_operation *op{pa_context_get_sink_info_by_name(context, nullptr, + &deviceSinkCallbackC, this)}; + waitForOperation(op, plock); - const pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | - PA_STREAM_FIX_CHANNELS | PA_STREAM_DONT_MOVE}; - - pa_sample_spec spec{}; - spec.format = PA_SAMPLE_S16NE; - spec.rate = 44100; - spec.channels = 2; - - pa_stream *stream{pulse_connect_stream(nullptr, plock, context, flags, nullptr, &spec, - nullptr, BackendType::Playback)}; - pa_operation *op{pa_context_get_sink_info_by_name(context, - pa_stream_get_device_name(stream), device_sink_callback, nullptr)}; - wait_for_operation(op, plock); - - pa_stream_disconnect(stream); - pa_stream_unref(stream); - stream = nullptr; - - op = pa_context_get_sink_info_list(context, device_sink_callback, nullptr); - wait_for_operation(op, plock); + op = pa_context_get_sink_info_list(context, &deviceSinkCallbackC, this); + waitForOperation(op, plock); pa_context_disconnect(context); pa_context_unref(context); + context = nullptr; } catch(std::exception &e) { ERR("Error enumerating devices: %s\n", e.what()); + if(context) close(context, stream); } } - -void device_source_callback(pa_context *UNUSED(context), const pa_source_info *info, int eol, void* /*pdata*/) +void PulseMainloop::probeCaptureDevices() { - if(eol) - { - pulse_condvar.notify_all(); - return; - } - - /* Skip this device is if it's already in the list. */ - if(std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [info](const DevMap &entry) -> bool - { return entry.device_name == info->name; } - ) != CaptureDevices.cend()) - return; - - /* Make sure the display name (description) is unique. Append a number - * counter as needed. - */ - int count{1}; - std::string newname{info->description}; - while(checkName(CaptureDevices, newname)) - { - newname = info->description; - newname += " #"; - newname += std::to_string(++count); - } - CaptureDevices.emplace_back(DevMap{std::move(newname), info->name}); - DevMap &newentry = CaptureDevices.back(); - - TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); -} + pa_context *context{}; + pa_stream *stream{}; -void probeCaptureDevices() -{ CaptureDevices.clear(); - try { - std::unique_lock plock{pulse_lock}; - - pa_context *context{connect_context(plock)}; + std::unique_lock plock{mMutex}; - const pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | - PA_STREAM_FIX_CHANNELS | PA_STREAM_DONT_MOVE}; + context = connectContext(plock); + pa_operation *op{pa_context_get_source_info_by_name(context, nullptr, + &deviceSourceCallbackC, this)}; + waitForOperation(op, plock); - pa_sample_spec spec{}; - spec.format = PA_SAMPLE_S16NE; - spec.rate = 44100; - spec.channels = 1; - - pa_stream *stream{pulse_connect_stream(nullptr, plock, context, flags, nullptr, &spec, nullptr, - BackendType::Capture)}; - pa_operation *op{pa_context_get_source_info_by_name(context, - pa_stream_get_device_name(stream), device_source_callback, nullptr)}; - wait_for_operation(op, plock); - - pa_stream_disconnect(stream); - pa_stream_unref(stream); - stream = nullptr; - - op = pa_context_get_source_info_list(context, device_source_callback, nullptr); - wait_for_operation(op, plock); + op = pa_context_get_source_info_list(context, &deviceSourceCallbackC, this); + waitForOperation(op, plock); pa_context_disconnect(context); pa_context_unref(context); + context = nullptr; } catch(std::exception &e) { ERR("Error enumerating devices: %s\n", e.what()); + if(context) close(context, stream); } } +/* Used for initial connection test and enumeration. */ +PulseMainloop gGlobalMainloop; + + struct PulsePlayback final : public BackendBase { - PulsePlayback(ALCdevice *device) noexcept : BackendBase{device} { } + PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~PulsePlayback() override; - static void bufferAttrCallbackC(pa_stream *stream, void *pdata); - void bufferAttrCallback(pa_stream *stream); - - static void contextStateCallbackC(pa_context *context, void *pdata); - void contextStateCallback(pa_context *context); + void bufferAttrCallback(pa_stream *stream) noexcept; + static void bufferAttrCallbackC(pa_stream *stream, void *pdata) noexcept + { static_cast(pdata)->bufferAttrCallback(stream); } - static void streamStateCallbackC(pa_stream *stream, void *pdata); - void streamStateCallback(pa_stream *stream); + void streamStateCallback(pa_stream *stream) noexcept; + static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept + { static_cast(pdata)->streamStateCallback(stream); } - static void streamWriteCallbackC(pa_stream *stream, size_t nbytes, void *pdata); - void streamWriteCallback(pa_stream *stream, size_t nbytes); + void streamWriteCallback(pa_stream *stream, size_t nbytes) noexcept; + static void streamWriteCallbackC(pa_stream *stream, size_t nbytes, void *pdata) noexcept + { static_cast(pdata)->streamWriteCallback(stream, nbytes); } - static void sinkInfoCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata); - void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int eol); + void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept; + static void sinkInfoCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept + { static_cast(pdata)->sinkInfoCallback(context, info, eol); } - static void sinkNameCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata); - void sinkNameCallback(pa_context *context, const pa_sink_info *info, int eol); + void sinkNameCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept; + static void sinkNameCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept + { static_cast(pdata)->sinkNameCallback(context, info, eol); } - static void streamMovedCallbackC(pa_stream *stream, void *pdata); - void streamMovedCallback(pa_stream *stream); + void streamMovedCallback(pa_stream *stream) noexcept; + static void streamMovedCallbackC(pa_stream *stream, void *pdata) noexcept + { static_cast(pdata)->streamMovedCallback(stream); } - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; ClockLatency getClockLatency() override; - void lock() override; - void unlock() override; - std::string mDeviceName; + PulseMainloop mMainloop; + + al::optional mDeviceName{al::nullopt}; + bool mIs51Rear{false}; pa_buffer_attr mAttr; pa_sample_spec mSpec; pa_stream *mStream{nullptr}; pa_context *mContext{nullptr}; - ALuint mFrameSize{0u}; + uint mFrameSize{0u}; - static constexpr inline const char *CurrentPrefix() noexcept { return "PulsePlayback::"; } DEF_NEWDEL(PulsePlayback) }; @@ -690,16 +653,13 @@ PulsePlayback::~PulsePlayback() if(!mContext) return; - pulse_close(mContext, mStream); + mMainloop.close(mContext, mStream); mContext = nullptr; mStream = nullptr; } -void PulsePlayback::bufferAttrCallbackC(pa_stream *stream, void *pdata) -{ static_cast(pdata)->bufferAttrCallback(stream); } - -void PulsePlayback::bufferAttrCallback(pa_stream *stream) +void PulsePlayback::bufferAttrCallback(pa_stream *stream) noexcept { /* FIXME: Update the device's UpdateSize (and/or BufferSize) using the new * buffer attributes? Changing UpdateSize will change the ALC_REFRESH @@ -710,81 +670,76 @@ void PulsePlayback::bufferAttrCallback(pa_stream *stream) TRACE("minreq=%d, tlength=%d, prebuf=%d\n", mAttr.minreq, mAttr.tlength, mAttr.prebuf); } -void PulsePlayback::contextStateCallbackC(pa_context *context, void *pdata) -{ static_cast(pdata)->contextStateCallback(context); } - -void PulsePlayback::contextStateCallback(pa_context *context) -{ - if(pa_context_get_state(context) == PA_CONTEXT_FAILED) - { - ERR("Received context failure!\n"); - aluHandleDisconnect(mDevice, "Playback state failure"); - } - pulse_condvar.notify_all(); -} - -void PulsePlayback::streamStateCallbackC(pa_stream *stream, void *pdata) -{ static_cast(pdata)->streamStateCallback(stream); } - -void PulsePlayback::streamStateCallback(pa_stream *stream) +void PulsePlayback::streamStateCallback(pa_stream *stream) noexcept { if(pa_stream_get_state(stream) == PA_STREAM_FAILED) { ERR("Received stream failure!\n"); - aluHandleDisconnect(mDevice, "Playback stream failure"); + mDevice->handleDisconnect("Playback stream failure"); } - pulse_condvar.notify_all(); + mMainloop.getCondVar().notify_all(); } -void PulsePlayback::streamWriteCallbackC(pa_stream *stream, size_t nbytes, void *pdata) -{ static_cast(pdata)->streamWriteCallback(stream, nbytes); } - -void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) +void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexcept { - void *buf{pa_xmalloc(nbytes)}; - aluMixData(mDevice, buf, nbytes/mFrameSize); + do { + pa_free_cb_t free_func{nullptr}; + auto buflen = static_cast(-1); + void *buf; + if UNLIKELY(pa_stream_begin_write(stream, &buf, &buflen) || !buf) + { + buflen = nbytes; + buf = pa_xmalloc(buflen); + free_func = pa_xfree; + } + else + buflen = minz(buflen, nbytes); + nbytes -= buflen; - int ret{pa_stream_write(stream, buf, nbytes, pa_xfree, 0, PA_SEEK_RELATIVE)}; - if(UNLIKELY(ret != PA_OK)) - ERR("Failed to write to stream: %d, %s\n", ret, pa_strerror(ret)); -} + mDevice->renderSamples(buf, static_cast(buflen/mFrameSize), mSpec.channels); -void PulsePlayback::sinkInfoCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) -{ static_cast(pdata)->sinkInfoCallback(context, info, eol); } + int ret{pa_stream_write(stream, buf, buflen, free_func, 0, PA_SEEK_RELATIVE)}; + if UNLIKELY(ret != PA_OK) + ERR("Failed to write to stream: %d, %s\n", ret, pa_strerror(ret)); + } while(nbytes > 0); +} -void PulsePlayback::sinkInfoCallback(pa_context* UNUSED(context), const pa_sink_info *info, int eol) +void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int eol) noexcept { struct ChannelMap { - DevFmtChannels chans; + DevFmtChannels fmt; pa_channel_map map; + bool is_51rear; }; static constexpr std::array chanmaps{{ - { DevFmtX71, X71ChanMap }, - { DevFmtX61, X61ChanMap }, - { DevFmtX51, X51ChanMap }, - { DevFmtX51Rear, X51RearChanMap }, - { DevFmtQuad, QuadChanMap }, - { DevFmtStereo, StereoChanMap }, - { DevFmtMono, MonoChanMap } + { DevFmtX71, X71ChanMap, false }, + { DevFmtX61, X61ChanMap, false }, + { DevFmtX51, X51ChanMap, false }, + { DevFmtX51, X51RearChanMap, true }, + { DevFmtQuad, QuadChanMap, false }, + { DevFmtStereo, StereoChanMap, false }, + { DevFmtMono, MonoChanMap, false } }}; if(eol) { - pulse_condvar.notify_all(); + mMainloop.getCondVar().notify_all(); return; } - auto chanmap = std::find_if(chanmaps.cbegin(), chanmaps.cend(), + auto chaniter = std::find_if(chanmaps.cbegin(), chanmaps.cend(), [info](const ChannelMap &chanmap) -> bool { return pa_channel_map_superset(&info->channel_map, &chanmap.map); } ); - if(chanmap != chanmaps.cend()) + if(chaniter != chanmaps.cend()) { - if(!(mDevice->Flags&DEVICE_CHANNELS_REQUEST)) - mDevice->FmtChans = chanmap->chans; + if(!mDevice->Flags.test(ChannelsRequest)) + mDevice->FmtChans = chaniter->fmt; + mIs51Rear = chaniter->is_51rear; } else { + mIs51Rear = false; char chanmap_str[PA_CHANNEL_MAP_SNPRINT_MAX]{}; pa_channel_map_snprint(chanmap_str, sizeof(chanmap_str), &info->channel_map); WARN("Failed to find format for channel map:\n %s\n", chanmap_str); @@ -792,34 +747,28 @@ void PulsePlayback::sinkInfoCallback(pa_context* UNUSED(context), const pa_sink_ if(info->active_port) TRACE("Active port: %s (%s)\n", info->active_port->name, info->active_port->description); - mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo && - info->active_port && strcmp(info->active_port->name, "analog-output-headphones") == 0); + mDevice->Flags.set(DirectEar, (info->active_port + && strcmp(info->active_port->name, "analog-output-headphones") == 0)); } -void PulsePlayback::sinkNameCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) -{ static_cast(pdata)->sinkNameCallback(context, info, eol); } - -void PulsePlayback::sinkNameCallback(pa_context* UNUSED(context), const pa_sink_info *info, int eol) +void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int eol) noexcept { if(eol) { - pulse_condvar.notify_all(); + mMainloop.getCondVar().notify_all(); return; } mDevice->DeviceName = info->description; } -void PulsePlayback::streamMovedCallbackC(pa_stream *stream, void *pdata) -{ static_cast(pdata)->streamMovedCallback(stream); } - -void PulsePlayback::streamMovedCallback(pa_stream *stream) +void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept { mDeviceName = pa_stream_get_device_name(stream); - TRACE("Stream moved to %s\n", mDeviceName.c_str()); + TRACE("Stream moved to %s\n", mDeviceName->c_str()); } -ALCenum PulsePlayback::open(const ALCchar *name) +void PulsePlayback::open(const char *name) { const char *pulse_name{nullptr}; const char *dev_name{nullptr}; @@ -827,24 +776,23 @@ ALCenum PulsePlayback::open(const ALCchar *name) if(name) { if(PlaybackDevices.empty()) - probePlaybackDevices(); + mMainloop.probePlaybackDevices(); auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); + [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; pulse_name = iter->device_name.c_str(); dev_name = iter->name.c_str(); } - std::unique_lock plock{pulse_lock}; - - mContext = connect_context(plock); - pa_context_set_state_callback(mContext, &PulsePlayback::contextStateCallbackC, this); + auto plock = mMainloop.getUniqueLock(); + if(!mContext) + mContext = mMainloop.connectContext(plock); - pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | PA_STREAM_FIX_CHANNELS}; + pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | + PA_STREAM_FIX_CHANNELS}; if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1)) flags |= PA_STREAM_DONT_MOVE; @@ -855,32 +803,41 @@ ALCenum PulsePlayback::open(const ALCchar *name) if(!pulse_name) { - pulse_name = getenv("ALSOFT_PULSE_DEFAULT"); - if(pulse_name && !pulse_name[0]) pulse_name = nullptr; + static const auto defname = al::getenv("ALSOFT_PULSE_DEFAULT"); + if(defname) pulse_name = defname->c_str(); } TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = pulse_connect_stream(pulse_name, plock, mContext, flags, nullptr, &spec, nullptr, - BackendType::Playback); + pa_stream *stream{mMainloop.connectStream(pulse_name, plock, mContext, flags, nullptr, &spec, + nullptr, BackendType::Playback)}; + if(mStream) + { + pa_stream_set_state_callback(mStream, nullptr, nullptr); + pa_stream_set_moved_callback(mStream, nullptr, nullptr); + pa_stream_set_write_callback(mStream, nullptr, nullptr); + pa_stream_set_buffer_attr_callback(mStream, nullptr, nullptr); + pa_stream_disconnect(mStream); + pa_stream_unref(mStream); + } + mStream = stream; pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this); - mFrameSize = pa_frame_size(pa_stream_get_sample_spec(mStream)); + mFrameSize = static_cast(pa_frame_size(pa_stream_get_sample_spec(mStream))); - mDeviceName = pa_stream_get_device_name(mStream); + mDeviceName = pulse_name ? al::make_optional(pulse_name) : al::nullopt; if(!dev_name) { - pa_operation *op{pa_context_get_sink_info_by_name(mContext, mDeviceName.c_str(), - &PulsePlayback::sinkNameCallbackC, this)}; - wait_for_operation(op, plock); + pa_operation *op{pa_context_get_sink_info_by_name(mContext, + pa_stream_get_device_name(mStream), &PulsePlayback::sinkNameCallbackC, this)}; + mMainloop.waitForOperation(op, plock); } else mDevice->DeviceName = dev_name; - - return ALC_NO_ERROR; } -ALCboolean PulsePlayback::reset() +bool PulsePlayback::reset() { - std::unique_lock plock{pulse_lock}; + auto plock = mMainloop.getUniqueLock(); + const auto deviceName = mDeviceName ? mDeviceName->c_str() : nullptr; if(mStream) { @@ -893,9 +850,9 @@ ALCboolean PulsePlayback::reset() mStream = nullptr; } - pa_operation *op{pa_context_get_sink_info_by_name(mContext, mDeviceName.c_str(), + pa_operation *op{pa_context_get_sink_info_by_name(mContext, deviceName, &PulsePlayback::sinkInfoCallbackC, this)}; - wait_for_operation(op, plock); + mMainloop.waitForOperation(op, plock); pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS}; @@ -910,83 +867,81 @@ ALCboolean PulsePlayback::reset() flags &= ~PA_STREAM_EARLY_REQUESTS; flags |= PA_STREAM_ADJUST_LATENCY; } - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "fix-rate", 0) || - !(mDevice->Flags&DEVICE_FREQUENCY_REQUEST)) + if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "fix-rate", 0) + || !mDevice->Flags.test(FrequencyRequest)) flags |= PA_STREAM_FIX_RATE; pa_channel_map chanmap{}; switch(mDevice->FmtChans) { - case DevFmtMono: - chanmap = MonoChanMap; - break; - case DevFmtAmbi3D: - mDevice->FmtChans = DevFmtStereo; - /*fall-through*/ - case DevFmtStereo: - chanmap = StereoChanMap; - break; - case DevFmtQuad: - chanmap = QuadChanMap; - break; - case DevFmtX51: - chanmap = X51ChanMap; - break; - case DevFmtX51Rear: - chanmap = X51RearChanMap; - break; - case DevFmtX61: - chanmap = X61ChanMap; - break; - case DevFmtX71: - chanmap = X71ChanMap; - break; + case DevFmtMono: + chanmap = MonoChanMap; + break; + case DevFmtAmbi3D: + mDevice->FmtChans = DevFmtStereo; + /*fall-through*/ + case DevFmtStereo: + chanmap = StereoChanMap; + break; + case DevFmtQuad: + chanmap = QuadChanMap; + break; + case DevFmtX51: + chanmap = (mIs51Rear ? X51RearChanMap : X51ChanMap); + break; + case DevFmtX61: + chanmap = X61ChanMap; + break; + case DevFmtX71: + chanmap = X71ChanMap; + break; } - SetChannelOrderFromMap(mDevice, chanmap); + setDefaultWFXChannelOrder(); switch(mDevice->FmtType) { - case DevFmtByte: - mDevice->FmtType = DevFmtUByte; - /* fall-through */ - case DevFmtUByte: - mSpec.format = PA_SAMPLE_U8; - break; - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - mSpec.format = PA_SAMPLE_S16NE; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - /* fall-through */ - case DevFmtInt: - mSpec.format = PA_SAMPLE_S32NE; - break; - case DevFmtFloat: - mSpec.format = PA_SAMPLE_FLOAT32NE; - break; + case DevFmtByte: + mDevice->FmtType = DevFmtUByte; + /* fall-through */ + case DevFmtUByte: + mSpec.format = PA_SAMPLE_U8; + break; + case DevFmtUShort: + mDevice->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + mSpec.format = PA_SAMPLE_S16NE; + break; + case DevFmtUInt: + mDevice->FmtType = DevFmtInt; + /* fall-through */ + case DevFmtInt: + mSpec.format = PA_SAMPLE_S32NE; + break; + case DevFmtFloat: + mSpec.format = PA_SAMPLE_FLOAT32NE; + break; } mSpec.rate = mDevice->Frequency; - mSpec.channels = mDevice->channelsFromFmt(); + mSpec.channels = static_cast(mDevice->channelsFromFmt()); if(pa_sample_spec_valid(&mSpec) == 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Invalid sample spec"}; + throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample spec"}; - mAttr.maxlength = -1; - mAttr.tlength = mDevice->BufferSize * pa_frame_size(&mSpec); - mAttr.prebuf = 0; - mAttr.minreq = mDevice->UpdateSize * pa_frame_size(&mSpec); - mAttr.fragsize = -1; + const auto frame_size = static_cast(pa_frame_size(&mSpec)); + mAttr.maxlength = ~0u; + mAttr.tlength = mDevice->BufferSize * frame_size; + mAttr.prebuf = 0u; + mAttr.minreq = mDevice->UpdateSize * frame_size; + mAttr.fragsize = ~0u; - mStream = pulse_connect_stream(mDeviceName.c_str(), plock, mContext, flags, &mAttr, &mSpec, + mStream = mMainloop.connectStream(deviceName, plock, mContext, flags, &mAttr, &mSpec, &chanmap, BackendType::Playback); pa_stream_set_state_callback(mStream, &PulsePlayback::streamStateCallbackC, this); pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this); mSpec = *(pa_stream_get_sample_spec(mStream)); - mFrameSize = pa_frame_size(&mSpec); + mFrameSize = static_cast(pa_frame_size(&mSpec)); if(mDevice->Frequency != mSpec.rate) { @@ -994,18 +949,19 @@ ALCboolean PulsePlayback::reset() * accordingly. */ const auto scale = static_cast(mSpec.rate) / mDevice->Frequency; - const ALuint perlen{static_cast(clampd(scale*mDevice->UpdateSize + 0.5, 64.0, - 8192.0))}; - const ALuint buflen{static_cast(clampd(scale*mDevice->BufferSize + 0.5, perlen*2, - std::numeric_limits::max()/mFrameSize))}; + const auto perlen = static_cast(clampd(scale*mDevice->UpdateSize + 0.5, 64.0, + 8192.0)); + const auto buflen = static_cast(clampd(scale*mDevice->BufferSize + 0.5, perlen*2, + std::numeric_limits::max()/mFrameSize)); - mAttr.maxlength = -1; + mAttr.maxlength = ~0u; mAttr.tlength = buflen * mFrameSize; - mAttr.prebuf = 0; + mAttr.prebuf = 0u; mAttr.minreq = perlen * mFrameSize; - op = pa_stream_set_buffer_attr(mStream, &mAttr, stream_success_callback, nullptr); - wait_for_operation(op, plock); + op = pa_stream_set_buffer_attr(mStream, &mAttr, &PulseMainloop::streamSuccessCallbackC, + &mMainloop); + mMainloop.waitForOperation(op, plock); mDevice->Frequency = mSpec.rate; } @@ -1016,41 +972,52 @@ ALCboolean PulsePlayback::reset() mDevice->BufferSize = mAttr.tlength / mFrameSize; mDevice->UpdateSize = mAttr.minreq / mFrameSize; - /* HACK: prebuf should be 0 as that's what we set it to. However on some - * systems it comes back as non-0, so we have to make sure the device will - * write enough audio to start playback. The lack of manual start control - * may have unintended consequences, but it's better than not starting at - * all. - */ - if(mAttr.prebuf != 0) - { - ALuint len{mAttr.prebuf / mFrameSize}; - if(len <= mDevice->BufferSize) - ERR("Non-0 prebuf, %u samples (%u bytes), device has %u samples\n", - len, mAttr.prebuf, mDevice->BufferSize); - } - - return ALC_TRUE; + return true; } -ALCboolean PulsePlayback::start() +void PulsePlayback::start() { - std::unique_lock plock{pulse_lock}; + auto plock = mMainloop.getUniqueLock(); + + /* Write some (silent) samples to fill the buffer before we start feeding + * it newly mixed samples. + */ + if(size_t todo{pa_stream_writable_size(mStream)}) + { + void *buf{pa_xmalloc(todo)}; + switch(mSpec.format) + { + case PA_SAMPLE_U8: + std::fill_n(static_cast(buf), todo, 0x80); + break; + case PA_SAMPLE_ALAW: + std::fill_n(static_cast(buf), todo, 0xD5); + break; + case PA_SAMPLE_ULAW: + std::fill_n(static_cast(buf), todo, 0x7f); + break; + default: + std::fill_n(static_cast(buf), todo, 0x00); + break; + } + pa_stream_write(mStream, buf, todo, pa_xfree, 0, PA_SEEK_RELATIVE); + } pa_stream_set_write_callback(mStream, &PulsePlayback::streamWriteCallbackC, this); - pa_operation *op{pa_stream_cork(mStream, 0, stream_success_callback, nullptr)}; - wait_for_operation(op, plock); + pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, + &mMainloop)}; - return ALC_TRUE; + mMainloop.waitForOperation(op, plock); } void PulsePlayback::stop() { - std::unique_lock plock{pulse_lock}; + auto plock = mMainloop.getUniqueLock(); + pa_operation *op{pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC, + &mMainloop)}; + mMainloop.waitForOperation(op, plock); pa_stream_set_write_callback(mStream, nullptr, nullptr); - pa_operation *op{pa_stream_cork(mStream, 1, stream_success_callback, nullptr)}; - wait_for_operation(op, plock); } @@ -1060,23 +1027,24 @@ ClockLatency PulsePlayback::getClockLatency() pa_usec_t latency; int neg, err; - { std::lock_guard _{pulse_lock}; + { + auto plock = mMainloop.getUniqueLock(); ret.ClockTime = GetDeviceClockTime(mDevice); err = pa_stream_get_latency(mStream, &latency, &neg); } - if(UNLIKELY(err != 0)) + if UNLIKELY(err != 0) { - /* FIXME: if err = -PA_ERR_NODATA, it means we were called too soon - * after starting the stream and no timing info has been received from - * the server yet. Should we wait, possibly stalling the app, or give a - * dummy value? Either way, it shouldn't be 0. */ + /* If err = -PA_ERR_NODATA, it means we were called too soon after + * starting the stream and no timing info has been received from the + * 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 = 0; + latency = mDevice->BufferSize - mDevice->UpdateSize; neg = 0; } - else if(UNLIKELY(neg)) + else if UNLIKELY(neg) latency = 0; ret.Latency = std::chrono::microseconds{latency}; @@ -1084,45 +1052,38 @@ ClockLatency PulsePlayback::getClockLatency() } -void PulsePlayback::lock() -{ pulse_lock.lock(); } - -void PulsePlayback::unlock() -{ pulse_lock.unlock(); } - - struct PulseCapture final : public BackendBase { - PulseCapture(ALCdevice *device) noexcept : BackendBase{device} { } + PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~PulseCapture() override; - static void contextStateCallbackC(pa_context *context, void *pdata); - void contextStateCallback(pa_context *context); + void streamStateCallback(pa_stream *stream) noexcept; + static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept + { static_cast(pdata)->streamStateCallback(stream); } - static void streamStateCallbackC(pa_stream *stream, void *pdata); - void streamStateCallback(pa_stream *stream); + void sourceNameCallback(pa_context *context, const pa_source_info *info, int eol) noexcept; + static void sourceNameCallbackC(pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept + { static_cast(pdata)->sourceNameCallback(context, info, eol); } - static void sourceNameCallbackC(pa_context *context, const pa_source_info *info, int eol, void *pdata); - void sourceNameCallback(pa_context *context, const pa_source_info *info, int eol); + void streamMovedCallback(pa_stream *stream) noexcept; + static void streamMovedCallbackC(pa_stream *stream, void *pdata) noexcept + { static_cast(pdata)->streamMovedCallback(stream); } - static void streamMovedCallbackC(pa_stream *stream, void *pdata); - void streamMovedCallback(pa_stream *stream); - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; ClockLatency getClockLatency() override; - void lock() override; - void unlock() override; - std::string mDeviceName; + PulseMainloop mMainloop; + + al::optional mDeviceName{al::nullopt}; - const void *mCapStore{nullptr}; - size_t mCapLen{0u}; - size_t mCapRemain{0u}; + uint mLastReadable{0u}; + al::byte mSilentVal{}; - ALCuint mLastReadable{0u}; + al::span mCapBuffer; + ssize_t mCapLen{0}; pa_buffer_attr mAttr{}; pa_sample_spec mSpec{}; @@ -1130,7 +1091,6 @@ struct PulseCapture final : public BackendBase { pa_stream *mStream{nullptr}; pa_context *mContext{nullptr}; - static constexpr inline const char *CurrentPrefix() noexcept { return "PulseCapture::"; } DEF_NEWDEL(PulseCapture) }; @@ -1139,258 +1099,239 @@ PulseCapture::~PulseCapture() if(!mContext) return; - pulse_close(mContext, mStream); + mMainloop.close(mContext, mStream); mContext = nullptr; mStream = nullptr; } -void PulseCapture::contextStateCallbackC(pa_context *context, void *pdata) -{ static_cast(pdata)->contextStateCallback(context); } -void PulseCapture::contextStateCallback(pa_context *context) -{ - if(pa_context_get_state(context) == PA_CONTEXT_FAILED) - { - ERR("Received context failure!\n"); - aluHandleDisconnect(mDevice, "Capture state failure"); - } - pulse_condvar.notify_all(); -} - -void PulseCapture::streamStateCallbackC(pa_stream *stream, void *pdata) -{ static_cast(pdata)->streamStateCallback(stream); } - -void PulseCapture::streamStateCallback(pa_stream *stream) +void PulseCapture::streamStateCallback(pa_stream *stream) noexcept { if(pa_stream_get_state(stream) == PA_STREAM_FAILED) { ERR("Received stream failure!\n"); - aluHandleDisconnect(mDevice, "Capture stream failure"); + mDevice->handleDisconnect("Capture stream failure"); } - pulse_condvar.notify_all(); + mMainloop.getCondVar().notify_all(); } -void PulseCapture::sourceNameCallbackC(pa_context *context, const pa_source_info *info, int eol, void *pdata) -{ static_cast(pdata)->sourceNameCallback(context, info, eol); } - -void PulseCapture::sourceNameCallback(pa_context* UNUSED(context), const pa_source_info *info, int eol) +void PulseCapture::sourceNameCallback(pa_context*, const pa_source_info *info, int eol) noexcept { if(eol) { - pulse_condvar.notify_all(); + mMainloop.getCondVar().notify_all(); return; } mDevice->DeviceName = info->description; } -void PulseCapture::streamMovedCallbackC(pa_stream *stream, void *pdata) -{ static_cast(pdata)->streamMovedCallback(stream); } - -void PulseCapture::streamMovedCallback(pa_stream *stream) +void PulseCapture::streamMovedCallback(pa_stream *stream) noexcept { mDeviceName = pa_stream_get_device_name(stream); - TRACE("Stream moved to %s\n", mDeviceName.c_str()); + TRACE("Stream moved to %s\n", mDeviceName->c_str()); } -ALCenum PulseCapture::open(const ALCchar *name) +void PulseCapture::open(const char *name) { const char *pulse_name{nullptr}; if(name) { if(CaptureDevices.empty()) - probeCaptureDevices(); + mMainloop.probeCaptureDevices(); auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); + [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) - throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name}; + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; pulse_name = iter->device_name.c_str(); mDevice->DeviceName = iter->name; } - std::unique_lock plock{pulse_lock}; - - mContext = connect_context(plock); - pa_context_set_state_callback(mContext, &PulseCapture::contextStateCallbackC, this); + auto plock = mMainloop.getUniqueLock(); + mContext = mMainloop.connectContext(plock); pa_channel_map chanmap{}; switch(mDevice->FmtChans) { - case DevFmtMono: - chanmap = MonoChanMap; - break; - case DevFmtStereo: - chanmap = StereoChanMap; - break; - case DevFmtQuad: - chanmap = QuadChanMap; - break; - case DevFmtX51: - chanmap = X51ChanMap; - break; - case DevFmtX51Rear: - chanmap = X51RearChanMap; - break; - case DevFmtX61: - chanmap = X61ChanMap; - break; - case DevFmtX71: - chanmap = X71ChanMap; - break; - case DevFmtAmbi3D: - throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported", - DevFmtChannelsString(mDevice->FmtChans)}; + case DevFmtMono: + chanmap = MonoChanMap; + break; + case DevFmtStereo: + chanmap = StereoChanMap; + break; + case DevFmtQuad: + chanmap = QuadChanMap; + break; + case DevFmtX51: + chanmap = X51ChanMap; + break; + case DevFmtX61: + chanmap = X61ChanMap; + break; + case DevFmtX71: + chanmap = X71ChanMap; + break; + case DevFmtAmbi3D: + throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", + DevFmtChannelsString(mDevice->FmtChans)}; } - SetChannelOrderFromMap(mDevice, chanmap); + setDefaultWFXChannelOrder(); switch(mDevice->FmtType) { - case DevFmtUByte: - mSpec.format = PA_SAMPLE_U8; - break; - case DevFmtShort: - mSpec.format = PA_SAMPLE_S16NE; - break; - case DevFmtInt: - mSpec.format = PA_SAMPLE_S32NE; - break; - case DevFmtFloat: - mSpec.format = PA_SAMPLE_FLOAT32NE; - break; - case DevFmtByte: - case DevFmtUShort: - case DevFmtUInt: - throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported", - DevFmtTypeString(mDevice->FmtType)}; + case DevFmtUByte: + mSilentVal = al::byte(0x80); + mSpec.format = PA_SAMPLE_U8; + break; + case DevFmtShort: + mSpec.format = PA_SAMPLE_S16NE; + break; + case DevFmtInt: + mSpec.format = PA_SAMPLE_S32NE; + break; + case DevFmtFloat: + mSpec.format = PA_SAMPLE_FLOAT32NE; + break; + case DevFmtByte: + case DevFmtUShort: + case DevFmtUInt: + throw al::backend_exception{al::backend_error::DeviceError, + "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } mSpec.rate = mDevice->Frequency; - mSpec.channels = mDevice->channelsFromFmt(); + mSpec.channels = static_cast(mDevice->channelsFromFmt()); if(pa_sample_spec_valid(&mSpec) == 0) - throw al::backend_exception{ALC_INVALID_VALUE, "Invalid sample format"}; - - ALuint samples{mDevice->BufferSize}; - samples = maxu(samples, 100 * mDevice->Frequency / 1000); + throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample format"}; - mAttr.minreq = -1; - mAttr.prebuf = -1; - mAttr.maxlength = samples * pa_frame_size(&mSpec); - mAttr.tlength = -1; - mAttr.fragsize = minu(samples, 50*mDevice->Frequency/1000) * pa_frame_size(&mSpec); + const auto frame_size = static_cast(pa_frame_size(&mSpec)); + const uint samples{maxu(mDevice->BufferSize, 100 * mDevice->Frequency / 1000)}; + mAttr.minreq = ~0u; + mAttr.prebuf = ~0u; + mAttr.maxlength = samples * frame_size; + mAttr.tlength = ~0u; + mAttr.fragsize = minu(samples, 50*mDevice->Frequency/1000) * frame_size; pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY}; if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1)) flags |= PA_STREAM_DONT_MOVE; TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = pulse_connect_stream(pulse_name, plock, mContext, flags, &mAttr, &mSpec, &chanmap, + mStream = mMainloop.connectStream(pulse_name, plock, mContext, flags, &mAttr, &mSpec, &chanmap, BackendType::Capture); pa_stream_set_moved_callback(mStream, &PulseCapture::streamMovedCallbackC, this); pa_stream_set_state_callback(mStream, &PulseCapture::streamStateCallbackC, this); - mDeviceName = pa_stream_get_device_name(mStream); + mDeviceName = pulse_name ? al::make_optional(pulse_name) : al::nullopt; if(mDevice->DeviceName.empty()) { - pa_operation *op{pa_context_get_source_info_by_name(mContext, mDeviceName.c_str(), - &PulseCapture::sourceNameCallbackC, this)}; - wait_for_operation(op, plock); + pa_operation *op{pa_context_get_source_info_by_name(mContext, + pa_stream_get_device_name(mStream), &PulseCapture::sourceNameCallbackC, this)}; + mMainloop.waitForOperation(op, plock); } - - return ALC_NO_ERROR; } -ALCboolean PulseCapture::start() +void PulseCapture::start() { - std::unique_lock plock{pulse_lock}; - pa_operation *op{pa_stream_cork(mStream, 0, stream_success_callback, nullptr)}; - wait_for_operation(op, plock); - return ALC_TRUE; + auto plock = mMainloop.getUniqueLock(); + pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, + &mMainloop)}; + mMainloop.waitForOperation(op, plock); } void PulseCapture::stop() { - std::unique_lock plock{pulse_lock}; - pa_operation *op{pa_stream_cork(mStream, 1, stream_success_callback, nullptr)}; - wait_for_operation(op, plock); + auto plock = mMainloop.getUniqueLock(); + pa_operation *op{pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC, + &mMainloop)}; + mMainloop.waitForOperation(op, plock); } -ALCenum PulseCapture::captureSamples(ALCvoid *buffer, ALCuint samples) +void PulseCapture::captureSamples(al::byte *buffer, uint samples) { - ALCuint todo{samples * static_cast(pa_frame_size(&mSpec))}; + al::span dstbuf{buffer, samples * pa_frame_size(&mSpec)}; /* Capture is done in fragment-sized chunks, so we loop until we get all * that's available */ - mLastReadable -= todo; - std::lock_guard _{pulse_lock}; - while(todo > 0) + mLastReadable -= static_cast(dstbuf.size()); + while(!dstbuf.empty()) { - if(mCapLen == 0) + if(!mCapBuffer.empty()) { - if(UNLIKELY(!mDevice->Connected.load(std::memory_order_acquire))) - break; - const pa_stream_state_t state{pa_stream_get_state(mStream)}; - if(UNLIKELY(!PA_STREAM_IS_GOOD(state))) - { - aluHandleDisconnect(mDevice, "Bad capture state: %u", state); - break; - } - if(UNLIKELY(pa_stream_peek(mStream, &mCapStore, &mCapLen) < 0)) - { - aluHandleDisconnect(mDevice, "Failed retrieving capture samples: %s", - pa_strerror(pa_context_errno(mContext))); - break; - } - if(mCapLen == 0) break; - mCapRemain = mCapLen; + const size_t rem{minz(dstbuf.size(), mCapBuffer.size())}; + if UNLIKELY(mCapLen < 0) + std::fill_n(dstbuf.begin(), rem, mSilentVal); + else + std::copy_n(mCapBuffer.begin(), rem, dstbuf.begin()); + dstbuf = dstbuf.subspan(rem); + mCapBuffer = mCapBuffer.subspan(rem); + + continue; } - const size_t rem{minz(todo, mCapRemain)}; - if(LIKELY(mCapStore)) - memcpy(buffer, mCapStore, rem); - else - memset(buffer, ((mDevice->FmtType==DevFmtUByte) ? 0x80 : 0), rem); - - buffer = static_cast(buffer) + rem; - todo -= rem; + if UNLIKELY(!mDevice->Connected.load(std::memory_order_acquire)) + break; - if(LIKELY(mCapStore)) - mCapStore = reinterpret_cast(mCapStore) + rem; - mCapRemain -= rem; - if(mCapRemain == 0) + auto plock = mMainloop.getUniqueLock(); + if(mCapLen != 0) { pa_stream_drop(mStream); + mCapBuffer = {}; mCapLen = 0; } - } - if(todo > 0) - memset(buffer, ((mDevice->FmtType==DevFmtUByte) ? 0x80 : 0), todo); + const pa_stream_state_t state{pa_stream_get_state(mStream)}; + if UNLIKELY(!PA_STREAM_IS_GOOD(state)) + { + mDevice->handleDisconnect("Bad capture state: %u", state); + break; + } + const void *capbuf; + size_t caplen; + if UNLIKELY(pa_stream_peek(mStream, &capbuf, &caplen) < 0) + { + mDevice->handleDisconnect("Failed retrieving capture samples: %s", + pa_strerror(pa_context_errno(mContext))); + break; + } + plock.unlock(); - return ALC_NO_ERROR; + if(caplen == 0) break; + if UNLIKELY(!capbuf) + mCapLen = -static_cast(caplen); + else + mCapLen = static_cast(caplen); + mCapBuffer = {static_cast(capbuf), caplen}; + } + if(!dstbuf.empty()) + std::fill(dstbuf.begin(), dstbuf.end(), mSilentVal); } -ALCuint PulseCapture::availableSamples() +uint PulseCapture::availableSamples() { - size_t readable{mCapRemain}; + size_t readable{mCapBuffer.size()}; if(mDevice->Connected.load(std::memory_order_acquire)) { - std::lock_guard _{pulse_lock}; + auto plock = mMainloop.getUniqueLock(); size_t got{pa_stream_readable_size(mStream)}; - if(static_cast(got) < 0) + if UNLIKELY(static_cast(got) < 0) { - ERR("pa_stream_readable_size() failed: %s\n", pa_strerror(got)); - aluHandleDisconnect(mDevice, "Failed getting readable size: %s", pa_strerror(got)); + 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); + } + else + { + const auto caplen = static_cast(std::abs(mCapLen)); + if(got > caplen) readable += got - caplen; } - else if(got > mCapLen) - readable += got - mCapLen; } - if(mLastReadable < readable) - mLastReadable = readable; - return mLastReadable / pa_frame_size(&mSpec); + readable = std::min(readable, std::numeric_limits::max()); + mLastReadable = std::max(mLastReadable, static_cast(readable)); + return mLastReadable / static_cast(pa_frame_size(&mSpec)); } @@ -1400,31 +1341,25 @@ ClockLatency PulseCapture::getClockLatency() pa_usec_t latency; int neg, err; - { std::lock_guard _{pulse_lock}; + { + auto plock = mMainloop.getUniqueLock(); ret.ClockTime = GetDeviceClockTime(mDevice); err = pa_stream_get_latency(mStream, &latency, &neg); } - if(UNLIKELY(err != 0)) + if UNLIKELY(err != 0) { ERR("Failed to get stream latency: 0x%x\n", err); latency = 0; neg = 0; } - else if(UNLIKELY(neg)) + else if UNLIKELY(neg) latency = 0; ret.Latency = std::chrono::microseconds{latency}; return ret; } - -void PulseCapture::lock() -{ pulse_lock.lock(); } - -void PulseCapture::unlock() -{ pulse_lock.unlock(); } - } // namespace @@ -1475,8 +1410,8 @@ bool PulseBackendFactory::init() pulse_ctx_flags |= PA_CONTEXT_NOAUTOSPAWN; try { - std::unique_lock plock{pulse_lock}; - pa_context *context{connect_context(plock)}; + auto plock = gGlobalMainloop.getUniqueLock(); + pa_context *context{gGlobalMainloop.connectContext(plock)}; pa_context_disconnect(context); pa_context_unref(context); return true; @@ -1489,30 +1424,35 @@ bool PulseBackendFactory::init() bool PulseBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -void PulseBackendFactory::probe(DevProbe type, std::string *outnames) +std::string PulseBackendFactory::probe(BackendType type) { - auto add_device = [outnames](const DevMap &entry) -> void + std::string outnames; + + auto add_device = [&outnames](const DevMap &entry) -> void { /* +1 to also append the null char (to ensure a null-separated list and * double-null terminated list). */ - outnames->append(entry.name.c_str(), entry.name.length()+1); + outnames.append(entry.name.c_str(), entry.name.length()+1); }; + switch(type) { - case DevProbe::Playback: - probePlaybackDevices(); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; - - case DevProbe::Capture: - probeCaptureDevices(); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; + case BackendType::Playback: + gGlobalMainloop.probePlaybackDevices(); + std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); + break; + + case BackendType::Capture: + gGlobalMainloop.probeCaptureDevices(); + std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); + break; } + + return outnames; } -BackendPtr PulseBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr PulseBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new PulsePlayback{device}}; diff --git a/modules/openal-soft/Alc/backends/pulseaudio.h b/modules/openal-soft/Alc/backends/pulseaudio.h index 40f3e30..6690fe8 100644 --- a/modules/openal-soft/Alc/backends/pulseaudio.h +++ b/modules/openal-soft/Alc/backends/pulseaudio.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_PULSEAUDIO_H #define BACKENDS_PULSEAUDIO_H -#include "backends/base.h" +#include "base.h" class PulseBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/qsa.cpp b/modules/openal-soft/Alc/backends/qsa.cpp deleted file mode 100644 index 534d579..0000000 --- a/modules/openal-soft/Alc/backends/qsa.cpp +++ /dev/null @@ -1,955 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2011-2013 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "backends/qsa.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "alMain.h" -#include "alu.h" -#include "threads.h" - -#include -#include - - -namespace { - -struct qsa_data { - snd_pcm_t* pcmHandle{nullptr}; - int audio_fd{-1}; - - snd_pcm_channel_setup_t csetup{}; - snd_pcm_channel_params_t cparams{}; - - ALvoid* buffer{nullptr}; - ALsizei size{0}; - - std::atomic mKillNow{AL_TRUE}; - std::thread mThread; -}; - -struct DevMap { - ALCchar* name; - int card; - int dev; -}; - -al::vector DeviceNameMap; -al::vector CaptureNameMap; - -constexpr ALCchar qsaDevice[] = "QSA Default"; - -constexpr struct { - int32_t format; -} formatlist[] = { - {SND_PCM_SFMT_FLOAT_LE}, - {SND_PCM_SFMT_S32_LE}, - {SND_PCM_SFMT_U32_LE}, - {SND_PCM_SFMT_S16_LE}, - {SND_PCM_SFMT_U16_LE}, - {SND_PCM_SFMT_S8}, - {SND_PCM_SFMT_U8}, - {0}, -}; - -constexpr struct { - int32_t rate; -} ratelist[] = { - {192000}, - {176400}, - {96000}, - {88200}, - {48000}, - {44100}, - {32000}, - {24000}, - {22050}, - {16000}, - {12000}, - {11025}, - {8000}, - {0}, -}; - -constexpr struct { - int32_t channels; -} channellist[] = { - {8}, - {7}, - {6}, - {4}, - {2}, - {1}, - {0}, -}; - -void deviceList(int type, al::vector *devmap) -{ - snd_ctl_t* handle; - snd_pcm_info_t pcminfo; - int max_cards, card, err, dev; - DevMap entry; - char name[1024]; - snd_ctl_hw_info info; - - max_cards = snd_cards(); - if(max_cards < 0) - return; - - std::for_each(devmap->begin(), devmap->end(), - [](const DevMap &entry) -> void - { free(entry.name); } - ); - devmap->clear(); - - entry.name = strdup(qsaDevice); - entry.card = 0; - entry.dev = 0; - devmap->push_back(entry); - - for(card = 0;card < max_cards;card++) - { - if((err=snd_ctl_open(&handle, card)) < 0) - continue; - - if((err=snd_ctl_hw_info(handle, &info)) < 0) - { - snd_ctl_close(handle); - continue; - } - - for(dev = 0;dev < (int)info.pcmdevs;dev++) - { - if((err=snd_ctl_pcm_info(handle, dev, &pcminfo)) < 0) - continue; - - if((type==SND_PCM_CHANNEL_PLAYBACK && (pcminfo.flags&SND_PCM_INFO_PLAYBACK)) || - (type==SND_PCM_CHANNEL_CAPTURE && (pcminfo.flags&SND_PCM_INFO_CAPTURE))) - { - snprintf(name, sizeof(name), "%s [%s] (hw:%d,%d)", info.name, pcminfo.name, card, dev); - entry.name = strdup(name); - entry.card = card; - entry.dev = dev; - - devmap->push_back(entry); - TRACE("Got device \"%s\", card %d, dev %d\n", name, card, dev); - } - } - snd_ctl_close(handle); - } -} - - -/* Wrappers to use an old-style backend with the new interface. */ -struct PlaybackWrapper final : public BackendBase { - PlaybackWrapper(ALCdevice *device) noexcept : BackendBase{device} { } - ~PlaybackWrapper() override; - - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; - void stop() override; - - std::unique_ptr mExtraData; - - static constexpr inline const char *CurrentPrefix() noexcept { return "PlaybackWrapper::"; } - DEF_NEWDEL(PlaybackWrapper) -}; - - -FORCE_ALIGN static int qsa_proc_playback(void *ptr) -{ - PlaybackWrapper *self = static_cast(ptr); - ALCdevice *device = self->mDevice; - qsa_data *data = self->mExtraData.get(); - snd_pcm_channel_status_t status; - sched_param param; - char* write_ptr; - ALint len; - int sret; - - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); - - /* Increase default 10 priority to 11 to avoid jerky sound */ - SchedGet(0, 0, ¶m); - param.sched_priority=param.sched_curpriority+1; - SchedSet(0, 0, SCHED_NOCHANGE, ¶m); - - const ALint frame_size = device->frameSizeFromFmt(); - - self->lock(); - while(!data->mKillNow.load(std::memory_order_acquire)) - { - pollfd pollitem{}; - pollitem.fd = data->audio_fd; - pollitem.events = POLLOUT; - - /* Select also works like time slice to OS */ - self->unlock(); - sret = poll(&pollitem, 1, 2000); - self->lock(); - if(sret == -1) - { - if(errno == EINTR || errno == EAGAIN) - continue; - ERR("poll error: %s\n", strerror(errno)); - aluHandleDisconnect(device, "Failed waiting for playback buffer: %s", strerror(errno)); - break; - } - if(sret == 0) - { - ERR("poll timeout\n"); - continue; - } - - len = data->size; - write_ptr = static_cast(data->buffer); - aluMixData(device, write_ptr, len/frame_size); - while(len>0 && !data->mKillNow.load(std::memory_order_acquire)) - { - int wrote = snd_pcm_plugin_write(data->pcmHandle, write_ptr, len); - if(wrote <= 0) - { - if(errno==EAGAIN || errno==EWOULDBLOCK) - continue; - - memset(&status, 0, sizeof(status)); - status.channel = SND_PCM_CHANNEL_PLAYBACK; - - snd_pcm_plugin_status(data->pcmHandle, &status); - - /* we need to reinitialize the sound channel if we've underrun the buffer */ - if(status.status == SND_PCM_STATUS_UNDERRUN || - status.status == SND_PCM_STATUS_READY) - { - if(snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK) < 0) - { - aluHandleDisconnect(device, "Playback recovery failed"); - break; - } - } - } - else - { - write_ptr += wrote; - len -= wrote; - } - } - } - self->unlock(); - - return 0; -} - -/************/ -/* Playback */ -/************/ - -static ALCenum qsa_open_playback(PlaybackWrapper *self, const ALCchar* deviceName) -{ - ALCdevice *device = self->mDevice; - int card, dev; - int status; - - std::unique_ptr data{new qsa_data{}}; - data->mKillNow.store(AL_TRUE, std::memory_order_relaxed); - - if(!deviceName) - deviceName = qsaDevice; - - if(strcmp(deviceName, qsaDevice) == 0) - status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_PLAYBACK); - else - { - if(DeviceNameMap.empty()) - deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap); - - auto iter = std::find_if(DeviceNameMap.begin(), DeviceNameMap.end(), - [deviceName](const DevMap &entry) -> bool - { return entry.name && strcmp(deviceName, entry.name) == 0; } - ); - if(iter == DeviceNameMap.cend()) - return ALC_INVALID_DEVICE; - - status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_PLAYBACK); - } - - if(status < 0) - return ALC_INVALID_DEVICE; - - data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK); - if(data->audio_fd < 0) - { - snd_pcm_close(data->pcmHandle); - return ALC_INVALID_DEVICE; - } - - device->DeviceName = deviceName; - self->mExtraData = std::move(data); - - return ALC_NO_ERROR; -} - -static void qsa_close_playback(PlaybackWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - - if (data->buffer!=NULL) - { - free(data->buffer); - data->buffer=NULL; - } - - snd_pcm_close(data->pcmHandle); - - self->mExtraData = nullptr; -} - -static ALCboolean qsa_reset_playback(PlaybackWrapper *self) -{ - ALCdevice *device = self->mDevice; - qsa_data *data = self->mExtraData.get(); - int32_t format=-1; - - switch(device->FmtType) - { - case DevFmtByte: - format=SND_PCM_SFMT_S8; - break; - case DevFmtUByte: - format=SND_PCM_SFMT_U8; - break; - case DevFmtShort: - format=SND_PCM_SFMT_S16_LE; - break; - case DevFmtUShort: - format=SND_PCM_SFMT_U16_LE; - break; - case DevFmtInt: - format=SND_PCM_SFMT_S32_LE; - break; - case DevFmtUInt: - format=SND_PCM_SFMT_U32_LE; - break; - case DevFmtFloat: - format=SND_PCM_SFMT_FLOAT_LE; - break; - } - - /* we actually don't want to block on writes */ - snd_pcm_nonblock_mode(data->pcmHandle, 1); - /* Disable mmap to control data transfer to the audio device */ - snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP); - snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_BUFFER_PARTIAL_BLOCKS); - - // configure a sound channel - memset(&data->cparams, 0, sizeof(data->cparams)); - data->cparams.channel=SND_PCM_CHANNEL_PLAYBACK; - data->cparams.mode=SND_PCM_MODE_BLOCK; - data->cparams.start_mode=SND_PCM_START_FULL; - data->cparams.stop_mode=SND_PCM_STOP_STOP; - - data->cparams.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt(); - data->cparams.buf.block.frags_max=device->BufferSize / device->UpdateSize; - data->cparams.buf.block.frags_min=data->cparams.buf.block.frags_max; - - data->cparams.format.interleave=1; - data->cparams.format.rate=device->Frequency; - data->cparams.format.voices=device->channelsFromFmt(); - data->cparams.format.format=format; - - if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0) - { - int original_rate=data->cparams.format.rate; - int original_voices=data->cparams.format.voices; - int original_format=data->cparams.format.format; - int it; - int jt; - - for (it=0; it<1; it++) - { - /* Check for second pass */ - if (it==1) - { - original_rate=ratelist[0].rate; - original_voices=channellist[0].channels; - original_format=formatlist[0].format; - } - - do { - /* At first downgrade sample format */ - jt=0; - do { - if (formatlist[jt].format==data->cparams.format.format) - { - data->cparams.format.format=formatlist[jt+1].format; - break; - } - if (formatlist[jt].format==0) - { - data->cparams.format.format=0; - break; - } - jt++; - } while(1); - - if (data->cparams.format.format==0) - { - data->cparams.format.format=original_format; - - /* At secod downgrade sample rate */ - jt=0; - do { - if (ratelist[jt].rate==data->cparams.format.rate) - { - data->cparams.format.rate=ratelist[jt+1].rate; - break; - } - if (ratelist[jt].rate==0) - { - data->cparams.format.rate=0; - break; - } - jt++; - } while(1); - - if (data->cparams.format.rate==0) - { - data->cparams.format.rate=original_rate; - data->cparams.format.format=original_format; - - /* At third downgrade channels number */ - jt=0; - do { - if(channellist[jt].channels==data->cparams.format.voices) - { - data->cparams.format.voices=channellist[jt+1].channels; - break; - } - if (channellist[jt].channels==0) - { - data->cparams.format.voices=0; - break; - } - jt++; - } while(1); - } - - if (data->cparams.format.voices==0) - { - break; - } - } - - data->cparams.buf.block.frag_size=device->UpdateSize* - data->cparams.format.voices* - snd_pcm_format_width(data->cparams.format.format)/8; - data->cparams.buf.block.frags_max=device->NumUpdates; - data->cparams.buf.block.frags_min=device->NumUpdates; - if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0) - { - continue; - } - else - { - break; - } - } while(1); - - if (data->cparams.format.voices!=0) - { - break; - } - } - - if (data->cparams.format.voices==0) - { - return ALC_FALSE; - } - } - - if ((snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK))<0) - { - return ALC_FALSE; - } - - memset(&data->csetup, 0, sizeof(data->csetup)); - data->csetup.channel=SND_PCM_CHANNEL_PLAYBACK; - if (snd_pcm_plugin_setup(data->pcmHandle, &data->csetup)<0) - { - return ALC_FALSE; - } - - /* now fill back to the our AL device */ - device->Frequency=data->cparams.format.rate; - - switch (data->cparams.format.voices) - { - case 1: - device->FmtChans=DevFmtMono; - break; - case 2: - device->FmtChans=DevFmtStereo; - break; - case 4: - device->FmtChans=DevFmtQuad; - break; - case 6: - device->FmtChans=DevFmtX51; - break; - case 7: - device->FmtChans=DevFmtX61; - break; - case 8: - device->FmtChans=DevFmtX71; - break; - default: - device->FmtChans=DevFmtMono; - break; - } - - switch (data->cparams.format.format) - { - case SND_PCM_SFMT_S8: - device->FmtType=DevFmtByte; - break; - case SND_PCM_SFMT_U8: - device->FmtType=DevFmtUByte; - break; - case SND_PCM_SFMT_S16_LE: - device->FmtType=DevFmtShort; - break; - case SND_PCM_SFMT_U16_LE: - device->FmtType=DevFmtUShort; - break; - case SND_PCM_SFMT_S32_LE: - device->FmtType=DevFmtInt; - break; - case SND_PCM_SFMT_U32_LE: - device->FmtType=DevFmtUInt; - break; - case SND_PCM_SFMT_FLOAT_LE: - device->FmtType=DevFmtFloat; - break; - default: - device->FmtType=DevFmtShort; - break; - } - - SetDefaultChannelOrder(device); - - device->UpdateSize=data->csetup.buf.block.frag_size / device->frameSizeFromFmt(); - device->NumUpdates=data->csetup.buf.block.frags; - - data->size=data->csetup.buf.block.frag_size; - data->buffer=malloc(data->size); - if (!data->buffer) - { - return ALC_FALSE; - } - - return ALC_TRUE; -} - -static ALCboolean qsa_start_playback(PlaybackWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - - try { - data->mKillNow.store(AL_FALSE, std::memory_order_release); - data->mThread = std::thread(qsa_proc_playback, self); - return ALC_TRUE; - } - catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); - } - catch(...) { - } - return ALC_FALSE; -} - -static void qsa_stop_playback(PlaybackWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - - if(data->mKillNow.exchange(AL_TRUE, std::memory_order_acq_rel) || !data->mThread.joinable()) - return; - data->mThread.join(); -} - - -PlaybackWrapper::~PlaybackWrapper() -{ - if(mExtraData) - qsa_close_playback(this); -} - -ALCenum PlaybackWrapper::open(const ALCchar *name) -{ return qsa_open_playback(this, name); } - -ALCboolean PlaybackWrapper::reset() -{ return qsa_reset_playback(this); } - -ALCboolean PlaybackWrapper::start() -{ return qsa_start_playback(this); } - -void PlaybackWrapper::stop() -{ qsa_stop_playback(this); } - - -/***********/ -/* Capture */ -/***********/ - -struct CaptureWrapper final : public BackendBase { - CaptureWrapper(ALCdevice *device) noexcept : BackendBase{device} { } - ~CaptureWrapper() override; - - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; - void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - std::unique_ptr mExtraData; - - static constexpr inline const char *CurrentPrefix() noexcept { return "CaptureWrapper::"; } - DEF_NEWDEL(CaptureWrapper) -}; - -static ALCenum qsa_open_capture(CaptureWrapper *self, const ALCchar *deviceName) -{ - ALCdevice *device = self->mDevice; - int card, dev; - int format=-1; - int status; - - std::unique_ptr data{new qsa_data{}}; - - if(!deviceName) - deviceName = qsaDevice; - - if(strcmp(deviceName, qsaDevice) == 0) - status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_CAPTURE); - else - { - if(CaptureNameMap.empty()) - deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap); - - auto iter = std::find_if(CaptureNameMap.cbegin(), CaptureNameMap.cend(), - [deviceName](const DevMap &entry) -> bool - { return entry.name && strcmp(deviceName, entry.name) == 0; } - ); - if(iter == CaptureNameMap.cend()) - return ALC_INVALID_DEVICE; - - status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_CAPTURE); - } - - if(status < 0) - return ALC_INVALID_DEVICE; - - data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE); - if(data->audio_fd < 0) - { - snd_pcm_close(data->pcmHandle); - return ALC_INVALID_DEVICE; - } - - device->DeviceName = deviceName; - - switch (device->FmtType) - { - case DevFmtByte: - format=SND_PCM_SFMT_S8; - break; - case DevFmtUByte: - format=SND_PCM_SFMT_U8; - break; - case DevFmtShort: - format=SND_PCM_SFMT_S16_LE; - break; - case DevFmtUShort: - format=SND_PCM_SFMT_U16_LE; - break; - case DevFmtInt: - format=SND_PCM_SFMT_S32_LE; - break; - case DevFmtUInt: - format=SND_PCM_SFMT_U32_LE; - break; - case DevFmtFloat: - format=SND_PCM_SFMT_FLOAT_LE; - break; - } - - /* we actually don't want to block on reads */ - snd_pcm_nonblock_mode(data->pcmHandle, 1); - /* Disable mmap to control data transfer to the audio device */ - snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP); - - /* configure a sound channel */ - memset(&data->cparams, 0, sizeof(data->cparams)); - data->cparams.mode=SND_PCM_MODE_BLOCK; - data->cparams.channel=SND_PCM_CHANNEL_CAPTURE; - data->cparams.start_mode=SND_PCM_START_GO; - data->cparams.stop_mode=SND_PCM_STOP_STOP; - - data->cparams.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt(); - data->cparams.buf.block.frags_max=device->NumUpdates; - data->cparams.buf.block.frags_min=device->NumUpdates; - - data->cparams.format.interleave=1; - data->cparams.format.rate=device->Frequency; - data->cparams.format.voices=device->channelsFromFmt(); - data->cparams.format.format=format; - - if(snd_pcm_plugin_params(data->pcmHandle, &data->cparams) < 0) - { - snd_pcm_close(data->pcmHandle); - return ALC_INVALID_VALUE; - } - - self->mExtraData = std::move(data); - - return ALC_NO_ERROR; -} - -static void qsa_close_capture(CaptureWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - - if (data->pcmHandle!=nullptr) - snd_pcm_close(data->pcmHandle); - data->pcmHandle = nullptr; - - self->mExtraData = nullptr; -} - -static void qsa_start_capture(CaptureWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - int rstatus; - - if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0) - { - ERR("capture prepare failed: %s\n", snd_strerror(rstatus)); - return; - } - - memset(&data->csetup, 0, sizeof(data->csetup)); - data->csetup.channel=SND_PCM_CHANNEL_CAPTURE; - if ((rstatus=snd_pcm_plugin_setup(data->pcmHandle, &data->csetup))<0) - { - ERR("capture setup failed: %s\n", snd_strerror(rstatus)); - return; - } - - snd_pcm_capture_go(data->pcmHandle); -} - -static void qsa_stop_capture(CaptureWrapper *self) -{ - qsa_data *data = self->mExtraData.get(); - snd_pcm_capture_flush(data->pcmHandle); -} - -static ALCuint qsa_available_samples(CaptureWrapper *self) -{ - ALCdevice *device = self->mDevice; - qsa_data *data = self->mExtraData.get(); - snd_pcm_channel_status_t status; - ALint frame_size = device->frameSizeFromFmt(); - ALint free_size; - int rstatus; - - memset(&status, 0, sizeof (status)); - status.channel=SND_PCM_CHANNEL_CAPTURE; - snd_pcm_plugin_status(data->pcmHandle, &status); - if ((status.status==SND_PCM_STATUS_OVERRUN) || - (status.status==SND_PCM_STATUS_READY)) - { - if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0) - { - ERR("capture prepare failed: %s\n", snd_strerror(rstatus)); - aluHandleDisconnect(device, "Failed capture recovery: %s", snd_strerror(rstatus)); - return 0; - } - - snd_pcm_capture_go(data->pcmHandle); - return 0; - } - - free_size=data->csetup.buf.block.frag_size*data->csetup.buf.block.frags; - free_size-=status.free; - - return free_size/frame_size; -} - -static ALCenum qsa_capture_samples(CaptureWrapper *self, ALCvoid *buffer, ALCuint samples) -{ - ALCdevice *device = self->mDevice; - qsa_data *data = self->mExtraData.get(); - char* read_ptr; - snd_pcm_channel_status_t status; - int selectret; - int bytes_read; - ALint frame_size=device->frameSizeFromFmt(); - ALint len=samples*frame_size; - int rstatus; - - read_ptr = static_cast(buffer); - - while (len>0) - { - pollfd pollitem{}; - pollitem.fd = data->audio_fd; - pollitem.events = POLLOUT; - - /* Select also works like time slice to OS */ - bytes_read=0; - selectret = poll(&pollitem, 1, 2000); - switch (selectret) - { - case -1: - aluHandleDisconnect(device, "Failed to check capture samples"); - return ALC_INVALID_DEVICE; - case 0: - break; - default: - bytes_read=snd_pcm_plugin_read(data->pcmHandle, read_ptr, len); - break; - } - - if (bytes_read<=0) - { - if ((errno==EAGAIN) || (errno==EWOULDBLOCK)) - { - continue; - } - - memset(&status, 0, sizeof (status)); - status.channel=SND_PCM_CHANNEL_CAPTURE; - snd_pcm_plugin_status(data->pcmHandle, &status); - - /* we need to reinitialize the sound channel if we've overrun the buffer */ - if ((status.status==SND_PCM_STATUS_OVERRUN) || - (status.status==SND_PCM_STATUS_READY)) - { - if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0) - { - ERR("capture prepare failed: %s\n", snd_strerror(rstatus)); - aluHandleDisconnect(device, "Failed capture recovery: %s", - snd_strerror(rstatus)); - return ALC_INVALID_DEVICE; - } - snd_pcm_capture_go(data->pcmHandle); - } - } - else - { - read_ptr+=bytes_read; - len-=bytes_read; - } - } - - return ALC_NO_ERROR; -} - - -CaptureWrapper::~CaptureWrapper() -{ - if(mExtraData) - qsa_close_capture(this); -} - -ALCenum CaptureWrapper::open(const ALCchar *name) -{ return qsa_open_capture(this, name); } - -ALCboolean CaptureWrapper::start() -{ qsa_start_capture(this); return ALC_TRUE; } - -void CaptureWrapper::stop() -{ qsa_stop_capture(this); } - -ALCenum CaptureWrapper::captureSamples(void *buffer, ALCuint samples) -{ return qsa_capture_samples(this, buffer, samples); } - -ALCuint CaptureWrapper::availableSamples() -{ return qsa_available_samples(this); } - -} // namespace - - -bool QSABackendFactory::init() -{ return true; } - -bool QSABackendFactory::querySupport(BackendType type) -{ return (type == BackendType::Playback || type == BackendType::Capture); } - -void QSABackendFactory::probe(DevProbe type, std::string *outnames) -{ - auto add_device = [outnames](const DevMap &entry) -> void - { - const char *n = entry.name; - if(n && n[0]) - outnames->append(n, strlen(n)+1); - }; - - switch (type) - { - case DevProbe::Playback: - deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap); - std::for_each(DeviceNameMap.cbegin(), DeviceNameMap.cend(), add_device); - break; - case DevProbe::Capture: - deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap); - std::for_each(CaptureNameMap.cbegin(), CaptureNameMap.cend(), add_device); - break; - } -} - -BackendPtr QSABackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new PlaybackWrapper{device}}; - if(type == BackendType::Capture) - return BackendPtr{new CaptureWrapper{device}}; - return nullptr; -} - -BackendFactory &QSABackendFactory::getFactory() -{ - static QSABackendFactory factory{}; - return factory; -} diff --git a/modules/openal-soft/Alc/backends/qsa.h b/modules/openal-soft/Alc/backends/qsa.h deleted file mode 100644 index da548bb..0000000 --- a/modules/openal-soft/Alc/backends/qsa.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_QSA_H -#define BACKENDS_QSA_H - -#include "backends/base.h" - -struct QSABackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - void probe(DevProbe type, std::string *outnames) override; - - BackendPtr createBackend(ALCdevice *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_QSA_H */ diff --git a/modules/openal-soft/Alc/backends/sdl2.cpp b/modules/openal-soft/Alc/backends/sdl2.cpp index 51c927c..c072603 100644 --- a/modules/openal-soft/Alc/backends/sdl2.cpp +++ b/modules/openal-soft/Alc/backends/sdl2.cpp @@ -20,17 +20,19 @@ #include "config.h" -#include "backends/sdl2.h" - -#include -#include +#include "sdl2.h" +#include +#include +#include #include -#include "alMain.h" -#include "alu.h" -#include "threads.h" -#include "compat.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "core/device.h" +#include "core/logging.h" + +#include namespace { @@ -41,31 +43,29 @@ namespace { #define DEVNAME_PREFIX "" #endif -constexpr ALCchar defaultDeviceName[] = DEVNAME_PREFIX "Default Device"; +constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device"; struct Sdl2Backend final : public BackendBase { - Sdl2Backend(ALCdevice *device) noexcept : BackendBase{device} { } + Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { } ~Sdl2Backend() override; - static void audioCallbackC(void *ptr, Uint8 *stream, int len); - void audioCallback(Uint8 *stream, int len); + void audioCallback(Uint8 *stream, int len) noexcept; + static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept + { static_cast(ptr)->audioCallback(stream, len); } - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; - void lock() override; - void unlock() override; SDL_AudioDeviceID mDeviceID{0u}; - ALsizei mFrameSize{0}; + uint mFrameSize{0}; - ALuint mFrequency{0u}; + uint mFrequency{0u}; DevFmtChannels mFmtChans{}; DevFmtType mFmtType{}; - ALuint mUpdateSize{0u}; + uint mUpdateSize{0u}; - static constexpr inline const char *CurrentPrefix() noexcept { return "ALCsdl2Playback::"; } DEF_NEWDEL(Sdl2Backend) }; @@ -76,114 +76,108 @@ Sdl2Backend::~Sdl2Backend() mDeviceID = 0; } -void Sdl2Backend::audioCallbackC(void *ptr, Uint8 *stream, int len) -{ static_cast(ptr)->audioCallback(stream, len); } - -void Sdl2Backend::audioCallback(Uint8 *stream, int len) +void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept { - assert((len % mFrameSize) == 0); - aluMixData(mDevice, stream, len / mFrameSize); + const auto ulen = static_cast(len); + assert((ulen % mFrameSize) == 0); + mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt()); } -ALCenum Sdl2Backend::open(const ALCchar *name) +void Sdl2Backend::open(const char *name) { SDL_AudioSpec want{}, have{}; - want.freq = mDevice->Frequency; + + want.freq = static_cast(mDevice->Frequency); switch(mDevice->FmtType) { - case DevFmtUByte: want.format = AUDIO_U8; break; - case DevFmtByte: want.format = AUDIO_S8; break; - case DevFmtUShort: want.format = AUDIO_U16SYS; break; - case DevFmtShort: want.format = AUDIO_S16SYS; break; - case DevFmtUInt: /* fall-through */ - case DevFmtInt: want.format = AUDIO_S32SYS; break; - case DevFmtFloat: want.format = AUDIO_F32; break; + case DevFmtUByte: want.format = AUDIO_U8; break; + case DevFmtByte: want.format = AUDIO_S8; break; + case DevFmtUShort: want.format = AUDIO_U16SYS; break; + case DevFmtShort: want.format = AUDIO_S16SYS; break; + case DevFmtUInt: /* fall-through */ + case DevFmtInt: want.format = AUDIO_S32SYS; break; + case DevFmtFloat: want.format = AUDIO_F32; break; } want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2; - want.samples = mDevice->UpdateSize; + want.samples = static_cast(minu(mDevice->UpdateSize, 8192)); want.callback = &Sdl2Backend::audioCallbackC; want.userdata = this; /* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't * necessarily the first in the list. */ + SDL_AudioDeviceID devid; if(!name || strcmp(name, defaultDeviceName) == 0) - mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); + devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); else { const size_t prefix_len = strlen(DEVNAME_PREFIX); if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0) - mDeviceID = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); + devid = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have, + SDL_AUDIO_ALLOW_ANY_CHANGE); else - mDeviceID = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); + devid = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); } - if(mDeviceID == 0) - return ALC_INVALID_VALUE; - - mDevice->Frequency = have.freq; - if(have.channels == 1) - mDevice->FmtChans = DevFmtMono; - else if(have.channels == 2) - mDevice->FmtChans = DevFmtStereo; + 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 { - ERR("Got unhandled SDL channel count: %d\n", (int)have.channels); - return ALC_INVALID_VALUE; + SDL_CloseAudioDevice(devid); + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL channel count: %d", int{have.channels}}; } + + DevFmtType devtype{}; 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: - ERR("Got unsupported SDL format: 0x%04x\n", have.format); - return ALC_INVALID_VALUE; + case AUDIO_U8: devtype = DevFmtUByte; break; + case AUDIO_S8: devtype = DevFmtByte; break; + case AUDIO_U16SYS: devtype = DevFmtUShort; break; + case AUDIO_S16SYS: devtype = DevFmtShort; break; + 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}; } - mDevice->UpdateSize = have.samples; - mDevice->BufferSize = have.samples * 2; /* SDL always (tries to) use two periods. */ - mFrameSize = mDevice->frameSizeFromFmt(); - mFrequency = mDevice->Frequency; - mFmtChans = mDevice->FmtChans; - mFmtType = mDevice->FmtType; - mUpdateSize = mDevice->UpdateSize; + 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 ? name : defaultDeviceName; - return ALC_NO_ERROR; } -ALCboolean Sdl2Backend::reset() +bool Sdl2Backend::reset() { mDevice->Frequency = mFrequency; mDevice->FmtChans = mFmtChans; mDevice->FmtType = mFmtType; mDevice->UpdateSize = mUpdateSize; - mDevice->BufferSize = mUpdateSize * 2; - SetDefaultWFXChannelOrder(mDevice); - return ALC_TRUE; + mDevice->BufferSize = mUpdateSize * 2; /* SDL always (tries to) use two periods. */ + setDefaultWFXChannelOrder(); + return true; } -ALCboolean Sdl2Backend::start() -{ - SDL_PauseAudioDevice(mDeviceID, 0); - return ALC_TRUE; -} +void Sdl2Backend::start() +{ SDL_PauseAudioDevice(mDeviceID, 0); } void Sdl2Backend::stop() { SDL_PauseAudioDevice(mDeviceID, 1); } -void Sdl2Backend::lock() -{ SDL_LockAudioDevice(mDeviceID); } - -void Sdl2Backend::unlock() -{ SDL_UnlockAudioDevice(mDeviceID); } - } // namespace BackendFactory &SDL2BackendFactory::getFactory() @@ -198,25 +192,28 @@ bool SDL2BackendFactory::init() bool SDL2BackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -void SDL2BackendFactory::probe(DevProbe type, std::string *outnames) +std::string SDL2BackendFactory::probe(BackendType type) { - if(type != DevProbe::Playback) - return; + std::string outnames; + + if(type != BackendType::Playback) + return outnames; int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)}; /* Includes null char. */ - outnames->append(defaultDeviceName, sizeof(defaultDeviceName)); + outnames.append(defaultDeviceName, sizeof(defaultDeviceName)); for(int i{0};i < num_devices;++i) { std::string name{DEVNAME_PREFIX}; name += SDL_GetAudioDeviceName(i, SDL_FALSE); if(!name.empty()) - outnames->append(name.c_str(), name.length()+1); + outnames.append(name.c_str(), name.length()+1); } + return outnames; } -BackendPtr SDL2BackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr SDL2BackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new Sdl2Backend{device}}; diff --git a/modules/openal-soft/Alc/backends/sdl2.h b/modules/openal-soft/Alc/backends/sdl2.h index 041d47e..3bd8df8 100644 --- a/modules/openal-soft/Alc/backends/sdl2.h +++ b/modules/openal-soft/Alc/backends/sdl2.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_SDL2_H #define BACKENDS_SDL2_H -#include "backends/base.h" +#include "base.h" struct SDL2BackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/sndio.cpp b/modules/openal-soft/Alc/backends/sndio.cpp index 163dd2f..48387c6 100644 --- a/modules/openal-soft/Alc/backends/sndio.cpp +++ b/modules/openal-soft/Alc/backends/sndio.cpp @@ -20,8 +20,9 @@ #include "config.h" -#include "backends/sndio.h" +#include "sndio.h" +#include #include #include #include @@ -29,39 +30,46 @@ #include #include -#include "alMain.h" -#include "alu.h" +#include "alnumeric.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "ringbuffer.h" #include "threads.h" #include "vector.h" -#include "ringbuffer.h" #include namespace { -static const ALCchar sndio_device[] = "SndIO Default"; +static const char sndio_device[] = "SndIO Default"; + +struct SioPar : public sio_par { + SioPar() { sio_initpar(this); } + void clear() { sio_initpar(this); } +}; struct SndioPlayback final : public BackendBase { - SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~SndioPlayback() override; int mixerProc(); - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; sio_hdl *mSndHandle{nullptr}; + uint mFrameStep{}; - al::vector mBuffer; + al::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "SndioPlayback::"; } DEF_NEWDEL(SndioPlayback) }; @@ -74,32 +82,29 @@ SndioPlayback::~SndioPlayback() int SndioPlayback::mixerProc() { + const size_t frameStep{mFrameStep}; + const size_t frameSize{frameStep * mDevice->bytesFromFmt()}; + SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - const ALsizei frameSize{mDevice->frameSizeFromFmt()}; - - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { - auto WritePtr = static_cast(mBuffer.data()); - size_t len{mBuffer.size()}; + al::span buffer{mBuffer}; - lock(); - aluMixData(mDevice, WritePtr, len/frameSize); - unlock(); - while(len > 0 && !mKillNow.load(std::memory_order_acquire)) + mDevice->renderSamples(buffer.data(), static_cast(buffer.size() / frameSize), + frameStep); + while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire)) { - size_t wrote{sio_write(mSndHandle, WritePtr, len)}; + size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())}; if(wrote == 0) { ERR("sio_write failed\n"); - aluHandleDisconnect(mDevice, "Failed to write playback samples"); + mDevice->handleDisconnect("Failed to write playback samples"); break; } - - len -= wrote; - WritePtr += wrote; + buffer = buffer.subspan(wrote); } } @@ -107,130 +112,150 @@ int SndioPlayback::mixerProc() } -ALCenum SndioPlayback::open(const ALCchar *name) +void SndioPlayback::open(const char *name) { if(!name) name = sndio_device; else if(strcmp(name, sndio_device) != 0) - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; - mSndHandle = sio_open(nullptr, SIO_PLAY, 0); - if(mSndHandle == nullptr) - { - ERR("Could not open device\n"); - return ALC_INVALID_VALUE; - } + sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)}; + if(!sndHandle) + throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"}; + + if(mSndHandle) + sio_close(mSndHandle); + mSndHandle = sndHandle; mDevice->DeviceName = name; - return ALC_NO_ERROR; } -ALCboolean SndioPlayback::reset() +bool SndioPlayback::reset() { - sio_par par; - sio_initpar(&par); + SioPar par; - par.rate = mDevice->Frequency; - par.pchan = ((mDevice->FmtChans != DevFmtMono) ? 2 : 1); - - switch(mDevice->FmtType) + auto tryfmt = mDevice->FmtType; +retry_params: + switch(tryfmt) { - case DevFmtByte: - par.bits = 8; - par.sig = 1; - break; - case DevFmtUByte: - par.bits = 8; - par.sig = 0; - break; - case DevFmtFloat: - case DevFmtShort: - par.bits = 16; - par.sig = 1; - break; - case DevFmtUShort: - par.bits = 16; - par.sig = 0; - break; - case DevFmtInt: - par.bits = 32; - par.sig = 1; - break; - case DevFmtUInt: - par.bits = 32; - par.sig = 0; - break; + case DevFmtByte: + par.bits = 8; + par.sig = 1; + break; + case DevFmtUByte: + par.bits = 8; + par.sig = 0; + break; + case DevFmtShort: + par.bits = 16; + par.sig = 1; + break; + case DevFmtUShort: + par.bits = 16; + par.sig = 0; + break; + case DevFmtFloat: + case DevFmtInt: + par.bits = 32; + par.sig = 1; + break; + case DevFmtUInt: + par.bits = 32; + par.sig = 0; + break; } + par.bps = SIO_BPS(par.bits); par.le = SIO_LE_NATIVE; + par.msb = 1; + + par.rate = mDevice->Frequency; + par.pchan = mDevice->channelsFromFmt(); par.round = mDevice->UpdateSize; par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize; if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize; - if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) - { - ERR("Failed to set device parameters\n"); - return ALC_FALSE; + try { + if(!sio_setpar(mSndHandle, &par)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set device parameters"}; + + par.clear(); + if(!sio_getpar(mSndHandle, &par)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to get device parameters"}; + + if(par.bps > 1 && par.le != SIO_LE_NATIVE) + throw al::backend_exception{al::backend_error::DeviceError, + "%s-endian samples not supported", par.le ? "Little" : "Big"}; + if(par.bits < par.bps*8 && !par.msb) + throw al::backend_exception{al::backend_error::DeviceError, + "MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8}; + if(par.pchan < 1) + throw al::backend_exception{al::backend_error::DeviceError, + "No playback channels on device"}; } - - if(par.bits != par.bps*8) - { - ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8); - return ALC_FALSE; + catch(al::backend_exception &e) { + if(tryfmt == DevFmtShort) + throw; + par.clear(); + tryfmt = DevFmtShort; + goto retry_params; } - mDevice->Frequency = par.rate; - mDevice->FmtChans = ((par.pchan==1) ? DevFmtMono : DevFmtStereo); - - if(par.bits == 8 && par.sig == 1) - mDevice->FmtType = DevFmtByte; - else if(par.bits == 8 && par.sig == 0) - mDevice->FmtType = DevFmtUByte; - else if(par.bits == 16 && par.sig == 1) - mDevice->FmtType = DevFmtShort; - else if(par.bits == 16 && par.sig == 0) - mDevice->FmtType = DevFmtUShort; - else if(par.bits == 32 && par.sig == 1) - mDevice->FmtType = DevFmtInt; - else if(par.bits == 32 && par.sig == 0) - mDevice->FmtType = DevFmtUInt; + if(par.bps == 1) + mDevice->FmtType = (par.sig==1) ? DevFmtByte : DevFmtUByte; + else if(par.bps == 2) + mDevice->FmtType = (par.sig==1) ? DevFmtShort : DevFmtUShort; + else if(par.bps == 4) + 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}; + + mFrameStep = par.pchan; + if(par.pchan != mDevice->channelsFromFmt()) { - ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits); - return ALC_FALSE; + WARN("Got %u channel%s for %s\n", par.pchan, (par.pchan==1)?"":"s", + DevFmtChannelsString(mDevice->FmtChans)); + if(par.pchan < 2) mDevice->FmtChans = DevFmtMono; + else mDevice->FmtChans = DevFmtStereo; } + mDevice->Frequency = par.rate; - SetDefaultChannelOrder(mDevice); + setDefaultChannelOrder(); mDevice->UpdateSize = par.round; mDevice->BufferSize = par.bufsz + par.round; - mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); - std::fill(mBuffer.begin(), mBuffer.end(), 0); - - return ALC_TRUE; + mBuffer.resize(mDevice->UpdateSize * par.pchan*par.bps); + if(par.sig == 1) + std::fill(mBuffer.begin(), mBuffer.end(), al::byte{}); + else if(par.bits == 8) + std::fill_n(mBuffer.data(), mBuffer.size(), al::byte(0x80)); + else if(par.bits == 16) + std::fill_n(reinterpret_cast(mBuffer.data()), mBuffer.size()/2, 0x8000); + else if(par.bits == 32) + std::fill_n(reinterpret_cast(mBuffer.data()), mBuffer.size()/4, 0x80000000u); + + return true; } -ALCboolean SndioPlayback::start() +void SndioPlayback::start() { if(!sio_start(mSndHandle)) - { - ERR("Error starting playback\n"); - return ALC_FALSE; - } + throw al::backend_exception{al::backend_error::DeviceError, "Error starting playback"}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this}; - return ALC_TRUE; } catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); + sio_stop(mSndHandle); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - catch(...) { - } - sio_stop(mSndHandle); - return ALC_FALSE; } void SndioPlayback::stop() @@ -244,17 +269,22 @@ void SndioPlayback::stop() } +/* TODO: This could be improved by avoiding the ring buffer and record thread, + * counting the available samples with the sio_onmove callback and reading + * directly from the device. However, this depends on reasonable support for + * capture buffer sizes apps may request. + */ struct SndioCapture final : public BackendBase { - SndioCapture(ALCdevice *device) noexcept : BackendBase{device} { } + SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~SndioCapture() override; int recordProc(); - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; sio_hdl *mSndHandle{nullptr}; @@ -263,7 +293,6 @@ struct SndioCapture final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "SndioCapture::"; } DEF_NEWDEL(SndioCapture) }; @@ -279,167 +308,175 @@ int SndioCapture::recordProc() SetRTPriority(); althrd_setname(RECORD_THREAD_NAME); - const ALsizei frameSize{mDevice->frameSizeFromFmt()}; + const uint frameSize{mDevice->frameSizeFromFmt()}; - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + int nfds_pre{sio_nfds(mSndHandle)}; + if(nfds_pre <= 0) { - auto data = mRing->getWriteVector(); - size_t todo{data.first.len + data.second.len}; - if(todo == 0) + mDevice->handleDisconnect("Incorrect return value from sio_nfds(): %d", nfds_pre); + return 1; + } + + auto fds = std::make_unique(static_cast(nfds_pre)); + + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) + { + /* Wait until there's some samples to read. */ + const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)}; + if(nfds <= 0) { - static char junk[4096]; - sio_read(mSndHandle, junk, - minz(sizeof(junk)/frameSize, mDevice->UpdateSize)*frameSize); + mDevice->handleDisconnect("Failed to get polling fds: %d", nfds); + break; + } + int pollres{::poll(fds.get(), static_cast(nfds), 2000)}; + if(pollres < 0) + { + if(errno == EINTR) continue; + mDevice->handleDisconnect("Poll error: %s", strerror(errno)); + break; + } + if(pollres == 0) continue; + + const int revents{sio_revents(mSndHandle, fds.get())}; + if((revents&POLLHUP)) + { + mDevice->handleDisconnect("Got POLLHUP from poll events"); + break; } + if(!(revents&POLLIN)) + continue; - size_t total{0u}; - data.first.len *= frameSize; - data.second.len *= frameSize; - todo = minz(todo, mDevice->UpdateSize) * frameSize; - while(total < todo) + auto data = mRing->getWriteVector(); + al::span buffer{data.first.buf, data.first.len*frameSize}; + while(!buffer.empty()) { - if(!data.first.len) - data.first = data.second; + size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())}; + if(got == 0) break; - size_t got{sio_read(mSndHandle, data.first.buf, minz(todo-total, data.first.len))}; - if(!got) + mRing->writeAdvance(got / frameSize); + buffer = buffer.subspan(got); + if(buffer.empty()) { - aluHandleDisconnect(mDevice, "Failed to read capture samples"); - break; + data = mRing->getWriteVector(); + buffer = {data.first.buf, data.first.len*frameSize}; } - - data.first.buf += got; - data.first.len -= got; - total += got; } - mRing->writeAdvance(total / frameSize); + if(buffer.empty()) + { + /* Got samples to read, but no place to store it. Drop it. */ + static char junk[4096]; + sio_read(mSndHandle, junk, sizeof(junk) - (sizeof(junk)%frameSize)); + } } return 0; } -ALCenum SndioCapture::open(const ALCchar *name) +void SndioCapture::open(const char *name) { if(!name) name = sndio_device; else if(strcmp(name, sndio_device) != 0) - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; - mSndHandle = sio_open(nullptr, SIO_REC, 0); + mSndHandle = sio_open(nullptr, SIO_REC, true); if(mSndHandle == nullptr) - { - ERR("Could not open device\n"); - return ALC_INVALID_VALUE; - } - - sio_par par; - sio_initpar(&par); + throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"}; + SioPar par; switch(mDevice->FmtType) { - case DevFmtByte: - par.bps = 1; - par.sig = 1; - break; - case DevFmtUByte: - par.bps = 1; - par.sig = 0; - break; - case DevFmtShort: - par.bps = 2; - par.sig = 1; - break; - case DevFmtUShort: - par.bps = 2; - par.sig = 0; - break; - case DevFmtInt: - par.bps = 4; - par.sig = 1; - break; - case DevFmtUInt: - par.bps = 4; - par.sig = 0; - break; - case DevFmtFloat: - ERR("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType)); - return ALC_INVALID_VALUE; + case DevFmtByte: + par.bits = 8; + par.sig = 1; + break; + case DevFmtUByte: + par.bits = 8; + par.sig = 0; + break; + case DevFmtShort: + par.bits = 16; + par.sig = 1; + break; + case DevFmtUShort: + par.bits = 16; + par.sig = 0; + break; + case DevFmtInt: + par.bits = 32; + par.sig = 1; + break; + case DevFmtUInt: + par.bits = 32; + par.sig = 0; + break; + case DevFmtFloat: + throw al::backend_exception{al::backend_error::DeviceError, + "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } - par.bits = par.bps * 8; + par.bps = SIO_BPS(par.bits); par.le = SIO_LE_NATIVE; - par.msb = SIO_LE_NATIVE ? 0 : 1; + par.msb = 1; par.rchan = mDevice->channelsFromFmt(); par.rate = mDevice->Frequency; par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10); - par.round = minu(par.appbufsz, mDevice->Frequency/40); - - mDevice->UpdateSize = par.round; - mDevice->BufferSize = par.appbufsz; + par.round = minu(par.appbufsz/2, mDevice->Frequency/40); if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) - { - ERR("Failed to set device parameters\n"); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set device praameters"}; - if(par.bits != par.bps*8) - { - ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8); - return ALC_INVALID_VALUE; - } + if(par.bps > 1 && par.le != SIO_LE_NATIVE) + throw al::backend_exception{al::backend_error::DeviceError, + "%s-endian samples not supported", par.le ? "Little" : "Big"}; + if(par.bits < par.bps*8 && !par.msb) + throw al::backend_exception{al::backend_error::DeviceError, + "Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8}; - if(!((mDevice->FmtType == DevFmtByte && par.bits == 8 && par.sig != 0) || - (mDevice->FmtType == DevFmtUByte && par.bits == 8 && par.sig == 0) || - (mDevice->FmtType == DevFmtShort && par.bits == 16 && par.sig != 0) || - (mDevice->FmtType == DevFmtUShort && par.bits == 16 && par.sig == 0) || - (mDevice->FmtType == DevFmtInt && par.bits == 32 && par.sig != 0) || - (mDevice->FmtType == DevFmtUInt && par.bits == 32 && par.sig == 0)) || - mDevice->channelsFromFmt() != (ALsizei)par.rchan || - mDevice->Frequency != par.rate) + auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool { - ERR("Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead\n", + return (fmttype == DevFmtByte && p.bps == 1 && p.sig != 0) + || (fmttype == DevFmtUByte && p.bps == 1 && p.sig == 0) + || (fmttype == DevFmtShort && p.bps == 2 && p.sig != 0) + || (fmttype == DevFmtUShort && p.bps == 2 && p.sig == 0) + || (fmttype == DevFmtInt && p.bps == 4 && p.sig != 0) + || (fmttype == DevFmtUInt && p.bps == 4 && p.sig == 0); + }; + if(!match_fmt(mDevice->FmtType, par) || mDevice->channelsFromFmt() != par.rchan + || mDevice->Frequency != par.rate) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead", DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans), - mDevice->Frequency, par.sig?'s':'u', par.bits, par.rchan, par.rate); - return ALC_INVALID_VALUE; - } + mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate}; - mRing = CreateRingBuffer(mDevice->BufferSize, par.bps*par.rchan, false); - if(!mRing) - { - ERR("Failed to allocate %u-byte ringbuffer\n", mDevice->BufferSize*par.bps*par.rchan); - return ALC_OUT_OF_MEMORY; - } + mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false); + mDevice->BufferSize = static_cast(mRing->writeSpace()); + mDevice->UpdateSize = par.round; - SetDefaultChannelOrder(mDevice); + setDefaultChannelOrder(); mDevice->DeviceName = name; - return ALC_NO_ERROR; } -ALCboolean SndioCapture::start() +void SndioCapture::start() { if(!sio_start(mSndHandle)) - { - ERR("Error starting playback\n"); - return ALC_FALSE; - } + throw al::backend_exception{al::backend_error::DeviceError, "Error starting capture"}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this}; - return ALC_TRUE; } catch(std::exception& e) { - ERR("Could not create record thread: %s\n", e.what()); - } - catch(...) { + sio_stop(mSndHandle); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start capture thread: %s", e.what()}; } - sio_stop(mSndHandle); - return ALC_FALSE; } void SndioCapture::stop() @@ -452,14 +489,11 @@ void SndioCapture::stop() ERR("Error stopping device\n"); } -ALCenum SndioCapture::captureSamples(void *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} +void SndioCapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } -ALCuint SndioCapture::availableSamples() -{ return mRing->readSpace(); } +uint SndioCapture::availableSamples() +{ return static_cast(mRing->readSpace()); } } // namespace @@ -475,19 +509,21 @@ bool SndIOBackendFactory::init() bool SndIOBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -void SndIOBackendFactory::probe(DevProbe type, std::string *outnames) +std::string SndIOBackendFactory::probe(BackendType type) { + std::string outnames; switch(type) { - case DevProbe::Playback: - case DevProbe::Capture: - /* Includes null char. */ - outnames->append(sndio_device, sizeof(sndio_device)); - break; + case BackendType::Playback: + case BackendType::Capture: + /* Includes null char. */ + outnames.append(sndio_device, sizeof(sndio_device)); + break; } + return outnames; } -BackendPtr SndIOBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new SndioPlayback{device}}; diff --git a/modules/openal-soft/Alc/backends/sndio.h b/modules/openal-soft/Alc/backends/sndio.h index 1ed63d5..d943319 100644 --- a/modules/openal-soft/Alc/backends/sndio.h +++ b/modules/openal-soft/Alc/backends/sndio.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_SNDIO_H #define BACKENDS_SNDIO_H -#include "backends/base.h" +#include "base.h" struct SndIOBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/solaris.cpp b/modules/openal-soft/Alc/backends/solaris.cpp index 3e59a78..791609c 100644 --- a/modules/openal-soft/Alc/backends/solaris.cpp +++ b/modules/openal-soft/Alc/backends/solaris.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/solaris.h" +#include "solaris.h" #include #include @@ -34,46 +34,48 @@ #include #include #include +#include #include #include -#include "alMain.h" -#include "alu.h" -#include "alconfig.h" +#include "albyte.h" +#include "alc/alconfig.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" #include "threads.h" #include "vector.h" -#include "compat.h" #include namespace { -constexpr ALCchar solaris_device[] = "Solaris Default"; +constexpr char solaris_device[] = "Solaris Default"; -const char *solaris_driver = "/dev/audio"; +std::string solaris_driver{"/dev/audio"}; struct SolarisBackend final : public BackendBase { - SolarisBackend(ALCdevice *device) noexcept : BackendBase{device} { } + SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { } ~SolarisBackend() override; int mixerProc(); - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; int mFd{-1}; - al::vector mBuffer; + uint mFrameStep{}; + al::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "SolarisBackend::"; } DEF_NEWDEL(SolarisBackend) }; @@ -89,26 +91,23 @@ int SolarisBackend::mixerProc() SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - const int frame_size{mDevice->frameSizeFromFmt()}; + const size_t frame_step{mDevice->channelsFromFmt()}; + const uint frame_size{mDevice->frameSizeFromFmt()}; - lock(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { pollfd pollitem{}; pollitem.fd = mFd; pollitem.events = POLLOUT; - unlock(); int pret{poll(&pollitem, 1, 1000)}; - lock(); if(pret < 0) { if(errno == EINTR || errno == EAGAIN) continue; ERR("poll failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed to wait for playback buffer: %s", - strerror(errno)); + mDevice->handleDisconnect("Failed to wait for playback buffer: %s", strerror(errno)); break; } else if(pret == 0) @@ -117,9 +116,9 @@ int SolarisBackend::mixerProc() continue; } - ALubyte *write_ptr{mBuffer.data()}; + al::byte *write_ptr{mBuffer.data()}; size_t to_write{mBuffer.size()}; - aluMixData(mDevice, write_ptr, to_write/frame_size); + mDevice->renderSamples(write_ptr, static_cast(to_write/frame_size), frame_step); while(to_write > 0 && !mKillNow.load(std::memory_order_acquire)) { ssize_t wrote{write(mFd, write_ptr, to_write)}; @@ -128,124 +127,125 @@ int SolarisBackend::mixerProc() if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; ERR("write failed: %s\n", strerror(errno)); - aluHandleDisconnect(mDevice, "Failed to write playback samples: %s", - strerror(errno)); + mDevice->handleDisconnect("Failed to write playback samples: %s", strerror(errno)); break; } - to_write -= wrote; + to_write -= static_cast(wrote); write_ptr += wrote; } } - unlock(); return 0; } -ALCenum SolarisBackend::open(const ALCchar *name) +void SolarisBackend::open(const char *name) { if(!name) name = solaris_device; else if(strcmp(name, solaris_device) != 0) - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; - mFd = ::open(solaris_driver, O_WRONLY); - if(mFd == -1) - { - ERR("Could not open %s: %s\n", solaris_driver, strerror(errno)); - return ALC_INVALID_VALUE; - } + 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)}; + + if(mFd != -1) + ::close(mFd); + mFd = fd; mDevice->DeviceName = name; - return ALC_NO_ERROR; } -ALCboolean SolarisBackend::reset() +bool SolarisBackend::reset() { audio_info_t info; AUDIO_INITINFO(&info); info.play.sample_rate = mDevice->Frequency; - - if(mDevice->FmtChans != DevFmtMono) - mDevice->FmtChans = DevFmtStereo; - ALsizei numChannels{mDevice->channelsFromFmt()}; - info.play.channels = numChannels; - + info.play.channels = mDevice->channelsFromFmt(); switch(mDevice->FmtType) { - case DevFmtByte: - info.play.precision = 8; - info.play.encoding = AUDIO_ENCODING_LINEAR; - break; - case DevFmtUByte: - info.play.precision = 8; - info.play.encoding = AUDIO_ENCODING_LINEAR8; - break; - case DevFmtUShort: - case DevFmtInt: - case DevFmtUInt: - case DevFmtFloat: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - info.play.precision = 16; - info.play.encoding = AUDIO_ENCODING_LINEAR; - break; + case DevFmtByte: + info.play.precision = 8; + info.play.encoding = AUDIO_ENCODING_LINEAR; + break; + case DevFmtUByte: + info.play.precision = 8; + info.play.encoding = AUDIO_ENCODING_LINEAR8; + break; + case DevFmtUShort: + case DevFmtInt: + case DevFmtUInt: + case DevFmtFloat: + mDevice->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + info.play.precision = 16; + info.play.encoding = AUDIO_ENCODING_LINEAR; + break; } - - ALsizei frameSize{numChannels * mDevice->bytesFromFmt()}; - info.play.buffer_size = mDevice->BufferSize * frameSize; + info.play.buffer_size = mDevice->BufferSize * mDevice->frameSizeFromFmt(); if(ioctl(mFd, AUDIO_SETINFO, &info) < 0) { ERR("ioctl failed: %s\n", strerror(errno)); - return ALC_FALSE; + return false; } - if(mDevice->channelsFromFmt() != (ALsizei)info.play.channels) + if(mDevice->channelsFromFmt() != info.play.channels) { - ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(mDevice->FmtChans), - info.play.channels); - return ALC_FALSE; + if(info.play.channels >= 2) + mDevice->FmtChans = DevFmtStereo; + else if(info.play.channels == 1) + mDevice->FmtChans = DevFmtMono; + else + throw al::backend_exception{al::backend_error::DeviceError, + "Got %u device channels", info.play.channels}; } - if(!((info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && mDevice->FmtType == DevFmtUByte) || - (info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtByte) || - (info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtShort) || - (info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtInt))) + if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8) + mDevice->FmtType = DevFmtUByte; + else if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR) + mDevice->FmtType = DevFmtByte; + else if(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR) + mDevice->FmtType = DevFmtShort; + else if(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR) + mDevice->FmtType = DevFmtInt; + else { - ERR("Could not set %s samples, got %d (0x%x)\n", DevFmtTypeString(mDevice->FmtType), - info.play.precision, info.play.encoding); - return ALC_FALSE; + ERR("Got unhandled sample type: %d (0x%x)\n", 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 / frameSize; + mDevice->BufferSize = info.play.buffer_size / frame_size; + /* How to get the actual period size/count? */ mDevice->UpdateSize = mDevice->BufferSize / 2; - SetDefaultChannelOrder(mDevice); + setDefaultChannelOrder(); - mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); - std::fill(mBuffer.begin(), mBuffer.end(), 0); + mBuffer.resize(mDevice->UpdateSize * size_t{frame_size}); + std::fill(mBuffer.begin(), mBuffer.end(), al::byte{}); - return ALC_TRUE; + return true; } -ALCboolean SolarisBackend::start() +void SolarisBackend::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this}; - return ALC_TRUE; } catch(std::exception& e) { - ERR("Could not create playback thread: %s\n", e.what()); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - catch(...) { - } - return ALC_FALSE; } void SolarisBackend::stop() @@ -268,33 +268,34 @@ BackendFactory &SolarisBackendFactory::getFactory() bool SolarisBackendFactory::init() { - ConfigValueStr(nullptr, "solaris", "device", &solaris_driver); + if(auto devopt = ConfigValueStr(nullptr, "solaris", "device")) + solaris_driver = std::move(*devopt); return true; } bool SolarisBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -void SolarisBackendFactory::probe(DevProbe type, std::string *outnames) +std::string SolarisBackendFactory::probe(BackendType type) { + std::string outnames; switch(type) { - case DevProbe::Playback: - { -#ifdef HAVE_STAT - struct stat buf; - if(stat(solaris_driver, &buf) == 0) -#endif - outnames->append(solaris_device, sizeof(solaris_device)); - } - break; + case BackendType::Playback: + { + struct stat buf; + if(stat(solaris_driver.c_str(), &buf) == 0) + outnames.append(solaris_device, sizeof(solaris_device)); + } + break; - case DevProbe::Capture: - break; + case BackendType::Capture: + break; } + return outnames; } -BackendPtr SolarisBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new SolarisBackend{device}}; diff --git a/modules/openal-soft/Alc/backends/solaris.h b/modules/openal-soft/Alc/backends/solaris.h index 98b1059..5da6ad3 100644 --- a/modules/openal-soft/Alc/backends/solaris.h +++ b/modules/openal-soft/Alc/backends/solaris.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_SOLARIS_H #define BACKENDS_SOLARIS_H -#include "backends/base.h" +#include "base.h" struct SolarisBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/wasapi.cpp b/modules/openal-soft/Alc/backends/wasapi.cpp index e41116d..063fca9 100644 --- a/modules/openal-soft/Alc/backends/wasapi.cpp +++ b/modules/openal-soft/Alc/backends/wasapi.cpp @@ -20,7 +20,10 @@ #include "config.h" -#include "backends/wasapi.h" +#include "wasapi.h" + +#define WIN32_LEAN_AND_MEAN +#include #include #include @@ -40,22 +43,29 @@ #include #endif +#include +#include +#include +#include +#include #include +#include +#include #include -#include +#include #include #include -#include -#include -#include -#include -#include -#include "alMain.h" -#include "alu.h" +#include "albit.h" +#include "alnumeric.h" +#include "comptr.h" +#include "core/converter.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" #include "ringbuffer.h" -#include "compat.h" -#include "converter.h" +#include "strutils.h" +#include "threads.h" /* Some headers seem to define these as macros for __uuidof, which is annoying @@ -76,6 +86,15 @@ DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x namespace { +using std::chrono::milliseconds; +using std::chrono::seconds; + +using ReferenceTime = std::chrono::duration>; + +inline constexpr ReferenceTime operator "" _reftime(unsigned long long int n) noexcept +{ return ReferenceTime{static_cast(n)}; } + + #define MONO SPEAKER_FRONT_CENTER #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT) #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) @@ -83,20 +102,49 @@ namespace { #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) #define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) -#define X7DOT1_WIDE (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_FRONT_LEFT_OF_CENTER|SPEAKER_FRONT_RIGHT_OF_CENTER) -#define REFTIME_PER_SEC ((REFERENCE_TIME)10000000) +constexpr inline DWORD MaskFromTopBits(DWORD b) noexcept +{ + b |= b>>1; + b |= b>>2; + b |= b>>4; + b |= b>>8; + b |= b>>16; + return b; +} +constexpr DWORD MonoMask{MaskFromTopBits(MONO)}; +constexpr DWORD StereoMask{MaskFromTopBits(STEREO)}; +constexpr DWORD QuadMask{MaskFromTopBits(QUAD)}; +constexpr DWORD X51Mask{MaskFromTopBits(X5DOT1)}; +constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)}; +constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)}; +constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)}; -#define DEVNAME_HEAD "OpenAL Soft on " +constexpr char DevNameHead[] = "OpenAL Soft on "; +constexpr size_t DevNameHeadLen{al::size(DevNameHead) - 1}; -/* Scales the given value using 64-bit integer math, ceiling the result. */ -inline int64_t ScaleCeil(int64_t val, int64_t new_scale, int64_t old_scale) +/* Scales the given reftime value, rounding the result. */ +inline uint RefTime2Samples(const ReferenceTime &val, uint srate) { - return (val*new_scale + old_scale-1) / old_scale; + const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1}; + return static_cast(mini64(retval, std::numeric_limits::max())); } +class GuidPrinter { + char mMsg[64]; + +public: + GuidPrinter(const GUID &guid) + { + std::snprintf(mMsg, al::size(mMsg), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", + DWORD{guid.Data1}, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], + guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); + } + const char *c_str() const { return mMsg; } +}; + struct PropVariant { PROPVARIANT mProp; @@ -130,10 +178,9 @@ struct DevMap { bool checkName(const al::vector &list, const std::string &name) { - return std::find_if(list.cbegin(), list.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ) != list.cend(); + auto match_name = [&name](const DevMap &entry) -> bool + { return entry.name == name; }; + return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } al::vector PlaybackDevices; @@ -143,15 +190,16 @@ al::vector CaptureDevices; using NameGUIDPair = std::pair; NameGUIDPair get_device_name_and_guid(IMMDevice *device) { - std::string name{DEVNAME_HEAD}; - std::string guid; + static constexpr char UnknownName[]{"Unknown Device Name"}; + static constexpr char UnknownGuid[]{"Unknown Device GUID"}; + std::string name, guid; - IPropertyStore *ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, &ps); + ComPtr ps; + HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr()); if(FAILED(hr)) { WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return { name+"Unknown Device Name", "Unknown Device GUID" }; + return std::make_pair(UnknownName, UnknownGuid); } PropVariant pvprop; @@ -159,14 +207,14 @@ NameGUIDPair get_device_name_and_guid(IMMDevice *device) if(FAILED(hr)) { WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr); - name += "Unknown Device Name"; + name += UnknownName; } else if(pvprop->vt == VT_LPWSTR) name += wstr_to_utf8(pvprop->pwszVal); else { WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); - name += "Unknown Device Name"; + name += UnknownName; } pvprop.clear(); @@ -174,60 +222,61 @@ NameGUIDPair get_device_name_and_guid(IMMDevice *device) if(FAILED(hr)) { WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr); - guid = "Unknown Device GUID"; + guid = UnknownGuid; } else if(pvprop->vt == VT_LPWSTR) guid = wstr_to_utf8(pvprop->pwszVal); else { WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); - guid = "Unknown Device GUID"; + guid = UnknownGuid; } - ps->Release(); - - return {name, guid}; + return std::make_pair(std::move(name), std::move(guid)); } -void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor) +EndpointFormFactor get_device_formfactor(IMMDevice *device) { - IPropertyStore *ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, &ps); + ComPtr ps; + HRESULT hr{device->OpenPropertyStore(STGM_READ, ps.getPtr())}; if(FAILED(hr)) { WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return; + return UnknownFormFactor; } + EndpointFormFactor formfactor{UnknownFormFactor}; PropVariant pvform; - hr = ps->GetValue(reinterpret_cast(PKEY_AudioEndpoint_FormFactor), pvform.get()); + hr = ps->GetValue(PKEY_AudioEndpoint_FormFactor, pvform.get()); if(FAILED(hr)) WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr); else if(pvform->vt == VT_UI4) - *formfactor = static_cast(pvform->ulVal); - else if(pvform->vt == VT_EMPTY) - *formfactor = UnknownFormFactor; - else + formfactor = static_cast(pvform->ulVal); + else if(pvform->vt != VT_EMPTY) WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt); - - ps->Release(); + return formfactor; } void add_device(IMMDevice *device, const WCHAR *devid, al::vector &list) { - std::string basename, guidstr; - std::tie(basename, guidstr) = get_device_name_and_guid(device); + for(auto &entry : list) + { + if(entry.devid == devid) + return; + } + + auto name_guid = get_device_name_and_guid(device); int count{1}; - std::string newname{basename}; + std::string newname{name_guid.first}; while(checkName(list, newname)) { - newname = basename; + newname = name_guid.first; newname += " #"; newname += std::to_string(++count); } - list.emplace_back(std::move(newname), std::move(guidstr), devid); + list.emplace_back(std::move(newname), std::move(name_guid.second), devid); const DevMap &newentry = list.back(); TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(), @@ -238,7 +287,7 @@ WCHAR *get_device_id(IMMDevice *device) { WCHAR *devid; - HRESULT hr = device->GetId(&devid); + const HRESULT hr{device->GetId(&devid)}; if(FAILED(hr)) { ERR("Failed to get device id: %lx\n", hr); @@ -248,86 +297,160 @@ WCHAR *get_device_id(IMMDevice *device) return devid; } -HRESULT probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector &list) +void probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector &list) { - IMMDeviceCollection *coll; - HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, &coll)}; + al::vector{}.swap(list); + + ComPtr coll; + HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, coll.getPtr())}; if(FAILED(hr)) { ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr); - return hr; + return; } - IMMDevice *defdev{nullptr}; - WCHAR *defdevid{nullptr}; UINT count{0}; hr = coll->GetCount(&count); if(SUCCEEDED(hr) && count > 0) - { - list.clear(); list.reserve(count); - hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, &defdev); - } - if(SUCCEEDED(hr) && defdev != nullptr) + ComPtr device; + hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, device.getPtr()); + if(SUCCEEDED(hr)) { - defdevid = get_device_id(defdev); - if(defdevid) - add_device(defdev, defdevid, list); + if(WCHAR *devid{get_device_id(device.get())}) + { + add_device(device.get(), devid, list); + CoTaskMemFree(devid); + } + device = nullptr; } for(UINT i{0};i < count;++i) { - IMMDevice *device; - hr = coll->Item(i, &device); + hr = coll->Item(i, device.getPtr()); if(FAILED(hr)) continue; - WCHAR *devid{get_device_id(device)}; - if(devid) + if(WCHAR *devid{get_device_id(device.get())}) { - if(!defdevid || wcscmp(devid, defdevid) != 0) - add_device(device, devid, list); + add_device(device.get(), devid, list); CoTaskMemFree(devid); } - device->Release(); + device = nullptr; } +} - if(defdev) defdev->Release(); - if(defdevid) CoTaskMemFree(defdevid); - coll->Release(); - return S_OK; +bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) +{ + *out = WAVEFORMATEXTENSIBLE{}; + if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + { + *out = *CONTAINING_RECORD(in, const WAVEFORMATEXTENSIBLE, Format); + out->Format.cbSize = sizeof(*out) - sizeof(out->Format); + } + else if(in->wFormatTag == WAVE_FORMAT_PCM) + { + out->Format = *in; + out->Format.cbSize = 0; + out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample; + if(out->Format.nChannels == 1) + out->dwChannelMask = MONO; + else if(out->Format.nChannels == 2) + out->dwChannelMask = STEREO; + else + ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels); + out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) + { + out->Format = *in; + out->Format.cbSize = 0; + out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample; + if(out->Format.nChannels == 1) + out->dwChannelMask = MONO; + else if(out->Format.nChannels == 2) + out->dwChannelMask = STEREO; + else + ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels); + out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + } + else + { + ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag); + return false; + } + return true; +} + +void TraceFormat(const char *msg, const WAVEFORMATEX *format) +{ + constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)}; + if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size) + { + const WAVEFORMATEXTENSIBLE *fmtex{ + CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)}; + TRACE("%s:\n" + " FormatTag = 0x%04x\n" + " Channels = %d\n" + " SamplesPerSec = %lu\n" + " AvgBytesPerSec = %lu\n" + " BlockAlign = %d\n" + " BitsPerSample = %d\n" + " Size = %d\n" + " Samples = %d\n" + " ChannelMask = 0x%lx\n" + " SubFormat = %s\n", + msg, fmtex->Format.wFormatTag, fmtex->Format.nChannels, fmtex->Format.nSamplesPerSec, + fmtex->Format.nAvgBytesPerSec, fmtex->Format.nBlockAlign, fmtex->Format.wBitsPerSample, + fmtex->Format.cbSize, fmtex->Samples.wReserved, fmtex->dwChannelMask, + GuidPrinter{fmtex->SubFormat}.c_str()); + } + else + TRACE("%s:\n" + " FormatTag = 0x%04x\n" + " Channels = %d\n" + " SamplesPerSec = %lu\n" + " AvgBytesPerSec = %lu\n" + " BlockAlign = %d\n" + " BitsPerSample = %d\n" + " Size = %d\n", + msg, format->wFormatTag, format->nChannels, format->nSamplesPerSec, + format->nAvgBytesPerSec, format->nBlockAlign, format->wBitsPerSample, format->cbSize); } -enum class MsgType : unsigned int { +enum class MsgType { OpenDevice, + ReopenDevice, ResetDevice, StartDevice, StopDevice, CloseDevice, EnumeratePlayback, EnumerateCapture, - QuitThread, - Count + Count, + QuitThread = Count }; -constexpr char MessageStr[static_cast(MsgType::Count)][20]{ +constexpr char MessageStr[static_cast(MsgType::Count)][20]{ "Open Device", + "Reopen Device", "Reset Device", "Start Device", "Stop Device", "Close Device", "Enumerate Playback", - "Enumerate Capture", - "Quit" + "Enumerate Capture" }; /* Proxy interface used by the message handler. */ struct WasapiProxy { - virtual HRESULT openProxy() = 0; + virtual ~WasapiProxy() = default; + + virtual HRESULT openProxy(const char *name) = 0; virtual void closeProxy() = 0; virtual HRESULT resetProxy() = 0; @@ -337,18 +460,22 @@ struct WasapiProxy { struct Msg { MsgType mType; WasapiProxy *mProxy; + const char *mParam; std::promise mPromise; + + explicit operator bool() const noexcept { return mType != MsgType::QuitThread; } }; static std::deque mMsgQueue; static std::mutex mMsgQueueLock; static std::condition_variable mMsgQueueCond; - std::future pushMessage(MsgType type) + std::future pushMessage(MsgType type, const char *param=nullptr) { std::promise promise; std::future future{promise.get_future()}; - { std::lock_guard _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, this, std::move(promise)}); + { + std::lock_guard _{mMsgQueueLock}; + mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)}); } mMsgQueueCond.notify_one(); return future; @@ -358,26 +485,24 @@ struct WasapiProxy { { std::promise promise; std::future future{promise.get_future()}; - { std::lock_guard _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, nullptr, std::move(promise)}); + { + std::lock_guard _{mMsgQueueLock}; + mMsgQueue.emplace_back(Msg{type, nullptr, nullptr, std::move(promise)}); } mMsgQueueCond.notify_one(); return future; } - static bool popMessage(Msg &msg) + static Msg popMessage() { std::unique_lock lock{mMsgQueueLock}; - while(mMsgQueue.empty()) - mMsgQueueCond.wait(lock); - msg = std::move(mMsgQueue.front()); + mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();}); + Msg msg{std::move(mMsgQueue.front())}; mMsgQueue.pop_front(); - return msg.mType != MsgType::QuitThread; + return msg; } static int messageHandler(std::promise *promise); - - static constexpr inline const char *CurrentPrefix() noexcept { return "WasapiProxy::"; } }; std::deque WasapiProxy::mMsgQueue; std::mutex WasapiProxy::mMsgQueueLock; @@ -387,7 +512,7 @@ int WasapiProxy::messageHandler(std::promise *promise) { TRACE("Starting message thread\n"); - HRESULT cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + HRESULT cohr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; if(FAILED(cohr)) { WARN("Failed to initialize COM: 0x%08lx\n", cohr); @@ -405,9 +530,7 @@ int WasapiProxy::messageHandler(std::promise *promise) CoUninitialize(); return 0; } - auto Enumerator = static_cast(ptr); - Enumerator->Release(); - Enumerator = nullptr; + static_cast(ptr)->Release(); CoUninitialize(); TRACE("Message thread initialization complete\n"); @@ -415,13 +538,12 @@ int WasapiProxy::messageHandler(std::promise *promise) promise = nullptr; TRACE("Starting message loop\n"); - ALuint deviceCount{0}; - Msg msg; - while(popMessage(msg)) + uint deviceCount{0}; + while(Msg msg{popMessage()}) { - TRACE("Got message \"%s\" (0x%04x, this=%p)\n", - MessageStr[static_cast(msg.mType)], static_cast(msg.mType), - msg.mProxy); + TRACE("Got message \"%s\" (0x%04x, this=%p, param=%p)\n", + MessageStr[static_cast(msg.mType)], static_cast(msg.mType), + static_cast(msg.mProxy), static_cast(msg.mParam)); switch(msg.mType) { @@ -430,7 +552,7 @@ int WasapiProxy::messageHandler(std::promise *promise) if(++deviceCount == 1) hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if(SUCCEEDED(hr)) - hr = msg.mProxy->openProxy(); + hr = msg.mProxy->openProxy(msg.mParam); msg.mPromise.set_value(hr); if(FAILED(hr)) @@ -440,6 +562,11 @@ int WasapiProxy::messageHandler(std::promise *promise) } continue; + case MsgType::ReopenDevice: + hr = msg.mProxy->openProxy(msg.mParam); + msg.mPromise.set_value(hr); + continue; + case MsgType::ResetDevice: hr = msg.mProxy->resetProxy(); msg.mPromise.set_value(hr); @@ -469,32 +596,30 @@ int WasapiProxy::messageHandler(std::promise *promise) if(++deviceCount == 1) hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if(SUCCEEDED(hr)) - hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr); + hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, + IID_IMMDeviceEnumerator, &ptr); if(FAILED(hr)) msg.mPromise.set_value(hr); else { - Enumerator = static_cast(ptr); + ComPtr enumerator{static_cast(ptr)}; if(msg.mType == MsgType::EnumeratePlayback) - hr = probe_devices(Enumerator, eRender, PlaybackDevices); + probe_devices(enumerator.get(), eRender, PlaybackDevices); else if(msg.mType == MsgType::EnumerateCapture) - hr = probe_devices(Enumerator, eCapture, CaptureDevices); - msg.mPromise.set_value(hr); - - Enumerator->Release(); - Enumerator = nullptr; + probe_devices(enumerator.get(), eCapture, CaptureDevices); + msg.mPromise.set_value(S_OK); } if(--deviceCount == 0 && SUCCEEDED(cohr)) CoUninitialize(); continue; - default: - ERR("Unexpected message: %u\n", static_cast(msg.mType)); - msg.mPromise.set_value(E_FAIL); - 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"); @@ -503,43 +628,46 @@ int WasapiProxy::messageHandler(std::promise *promise) struct WasapiPlayback final : public BackendBase, WasapiProxy { - WasapiPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~WasapiPlayback() override; int mixerProc(); - ALCenum open(const ALCchar *name) override; - HRESULT openProxy() override; + void open(const char *name) override; + HRESULT openProxy(const char *name) override; void closeProxy() override; - ALCboolean reset() override; + bool reset() override; HRESULT resetProxy() override; - ALCboolean start() override; + void start() override; HRESULT startProxy() override; void stop() override; void stopProxy() override; ClockLatency getClockLatency() override; - std::wstring mDevId; - - IMMDevice *mMMDev{nullptr}; - IAudioClient *mClient{nullptr}; - IAudioRenderClient *mRender{nullptr}; + HRESULT mOpenStatus{E_FAIL}; + ComPtr mMMDev{nullptr}; + ComPtr mClient{nullptr}; + ComPtr mRender{nullptr}; HANDLE mNotifyEvent{nullptr}; + UINT32 mFrameStep{0u}; std::atomic mPadding{0u}; + std::mutex mMutex; + std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "WasapiPlayback::"; } DEF_NEWDEL(WasapiPlayback) }; WasapiPlayback::~WasapiPlayback() { - pushMessage(MsgType::CloseDevice).wait(); + if(SUCCEEDED(mOpenStatus)) + pushMessage(MsgType::CloseDevice).wait(); + mOpenStatus = E_FAIL; if(mNotifyEvent != nullptr) CloseHandle(mNotifyEvent); @@ -549,18 +677,18 @@ WasapiPlayback::~WasapiPlayback() FORCE_ALIGN int WasapiPlayback::mixerProc() { - HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; if(FAILED(hr)) { ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "COM init failed: 0x%08lx", hr); + mDevice->handleDisconnect("COM init failed: 0x%08lx", hr); return 1; } SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - const ALuint update_size{mDevice->UpdateSize}; + const uint update_size{mDevice->UpdateSize}; const UINT32 buffer_len{mDevice->BufferSize}; while(!mKillNow.load(std::memory_order_relaxed)) { @@ -569,12 +697,12 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() if(FAILED(hr)) { ERR("Failed to get padding: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failed to retrieve buffer padding: 0x%08lx", hr); + mDevice->handleDisconnect("Failed to retrieve buffer padding: 0x%08lx", hr); break; } mPadding.store(written, std::memory_order_relaxed); - ALuint len{buffer_len - written}; + uint len{buffer_len - written}; if(len < update_size) { DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; @@ -587,16 +715,17 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() hr = mRender->GetBuffer(len, &buffer); if(SUCCEEDED(hr)) { - lock(); - aluMixData(mDevice, buffer, len); - mPadding.store(written + len, std::memory_order_relaxed); - unlock(); + { + std::lock_guard _{mMutex}; + mDevice->renderSamples(buffer, len, mFrameStep); + mPadding.store(written + len, std::memory_order_relaxed); + } hr = mRender->ReleaseBuffer(len, 0); } if(FAILED(hr)) { ERR("Failed to buffer data: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "Failed to send playback samples: 0x%08lx", hr); + mDevice->handleDisconnect("Failed to send playback samples: 0x%08lx", hr); break; } } @@ -607,53 +736,18 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() } -bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) -{ - *out = WAVEFORMATEXTENSIBLE{}; - if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE) - { - *out = reinterpret_cast(*in); - out->Format.cbSize = sizeof(*out) - sizeof(out->Format); - } - else if(in->wFormatTag == WAVE_FORMAT_PCM) - { - out->Format = *in; - if(out->Format.nChannels == 1) - out->dwChannelMask = MONO; - else if(out->Format.nChannels == 2) - out->dwChannelMask = STEREO; - else - ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels); - out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - } - else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) - { - out->Format = *in; - if(out->Format.nChannels == 1) - out->dwChannelMask = MONO; - else if(out->Format.nChannels == 2) - out->dwChannelMask = STEREO; - else - ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels); - out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - } - else - { - ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag); - return false; - } - return true; -} - -ALCenum WasapiPlayback::open(const ALCchar *name) +void WasapiPlayback::open(const char *name) { HRESULT hr{S_OK}; - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(mNotifyEvent == nullptr) + if(!mNotifyEvent) { - ERR("Failed to create notify events: %lu\n", GetLastError()); - hr = E_FAIL; + mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if(mNotifyEvent == nullptr) + { + ERR("Failed to create notify events: %lu\n", GetLastError()); + hr = E_FAIL; + } } if(SUCCEEDED(hr)) @@ -661,114 +755,106 @@ ALCenum WasapiPlayback::open(const ALCchar *name) if(name) { if(PlaybackDevices.empty()) - pushMessage(MsgType::EnumeratePlayback).wait(); - - hr = E_FAIL; - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; } - ); - if(iter == PlaybackDevices.cend()) + pushMessage(MsgType::EnumeratePlayback); + if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) { - std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [&wname](const DevMap &entry) -> bool - { return entry.devid == wname; } - ); - } - if(iter == PlaybackDevices.cend()) - WARN("Failed to find device name matching \"%s\"\n", name); - else - { - mDevId = iter->devid; - mDevice->DeviceName = iter->name; - hr = S_OK; + name += DevNameHeadLen; + if(*name == '\0') + name = nullptr; } } - } - - if(SUCCEEDED(hr)) - hr = pushMessage(MsgType::OpenDevice).get(); - - if(FAILED(hr)) - { - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; - - mDevId.clear(); - ERR("Device init failed: 0x%08lx\n", hr); - return ALC_INVALID_VALUE; + if(SUCCEEDED(mOpenStatus)) + hr = pushMessage(MsgType::ReopenDevice, name).get(); + else + { + hr = pushMessage(MsgType::OpenDevice, name).get(); + mOpenStatus = hr; + } } - return ALC_NO_ERROR; + if(FAILED(hr)) + throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", + hr}; } -HRESULT WasapiPlayback::openProxy() +HRESULT WasapiPlayback::openProxy(const char *name) { - void *ptr; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr)}; - if(SUCCEEDED(hr)) + const wchar_t *devid{nullptr}; + if(name) { - auto Enumerator = static_cast(ptr); - if(mDevId.empty()) - hr = Enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &mMMDev); - else - hr = Enumerator->GetDevice(mDevId.c_str(), &mMMDev); - Enumerator->Release(); + auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + [name](const DevMap &entry) -> bool + { return entry.name == name || entry.endpoint_guid == name; }); + if(iter == PlaybackDevices.cend()) + { + const std::wstring wname{utf8_to_wstr(name)}; + iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + [&wname](const DevMap &entry) -> bool + { return entry.devid == wname; }); + } + if(iter == PlaybackDevices.cend()) + { + WARN("Failed to find device name matching \"%s\"\n", name); + return E_FAIL; + } + name = iter->name.c_str(); + devid = iter->devid.c_str(); } - if(SUCCEEDED(hr)) - hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); + + void *ptr; + ComPtr mmdev; + HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, + IID_IMMDeviceEnumerator, &ptr)}; if(SUCCEEDED(hr)) { - mClient = static_cast(ptr); - if(mDevice->DeviceName.empty()) - mDevice->DeviceName = get_device_name_and_guid(mMMDev).first; + ComPtr enumerator{static_cast(ptr)}; + if(!devid) + hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mmdev.getPtr()); + else + hr = enumerator->GetDevice(devid, mmdev.getPtr()); } - if(FAILED(hr)) { - if(mMMDev) - mMMDev->Release(); - mMMDev = nullptr; + WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + return hr; } + mClient = nullptr; + mMMDev = std::move(mmdev); + if(name) mDevice->DeviceName = std::string{DevNameHead} + name; + else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first; + return hr; } void WasapiPlayback::closeProxy() { - if(mClient) - mClient->Release(); mClient = nullptr; - - if(mMMDev) - mMMDev->Release(); mMMDev = nullptr; } -ALCboolean WasapiPlayback::reset() +bool WasapiPlayback::reset() { HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; - return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; + if(FAILED(hr)) + throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr}; + return true; } HRESULT WasapiPlayback::resetProxy() { - if(mClient) - mClient->Release(); mClient = nullptr; void *ptr; - HRESULT hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); + HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)}; if(FAILED(hr)) { ERR("Failed to reactivate audio client: 0x%08lx\n", hr); return hr; } - mClient = static_cast(ptr); + mClient = ComPtr{static_cast(ptr)}; WAVEFORMATEX *wfx; hr = mClient->GetMixFormat(&wfx); @@ -787,105 +873,103 @@ HRESULT WasapiPlayback::resetProxy() CoTaskMemFree(wfx); wfx = nullptr; - const REFERENCE_TIME per_time{mDevice->UpdateSize * REFTIME_PER_SEC / mDevice->Frequency}; - const REFERENCE_TIME buf_time{mDevice->BufferSize * REFTIME_PER_SEC / mDevice->Frequency}; + const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency}; + const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency}; - if(!(mDevice->Flags&DEVICE_FREQUENCY_REQUEST)) + if(!mDevice->Flags.test(FrequencyRequest)) mDevice->Frequency = OutputType.Format.nSamplesPerSec; - if(!(mDevice->Flags&DEVICE_CHANNELS_REQUEST)) + if(!mDevice->Flags.test(ChannelsRequest)) { - if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO) - mDevice->FmtChans = DevFmtMono; - else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO) - mDevice->FmtChans = DevFmtStereo; - else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1) - mDevice->FmtChans = DevFmtX51; - else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR) - mDevice->FmtChans = DevFmtX51Rear; - else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1) - mDevice->FmtChans = DevFmtX61; - else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE)) + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) mDevice->FmtChans = DevFmtX71; + else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) + mDevice->FmtChans = DevFmtX61; + else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 + || (chanmask&X51RearMask) == X5DOT1REAR)) + mDevice->FmtChans = DevFmtX51; + else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) + mDevice->FmtChans = DevFmtQuad; + else if(chancount >= 2 && (chanmask&StereoMask) == STEREO) + mDevice->FmtChans = DevFmtStereo; + else if(chancount >= 1 && (chanmask&MonoMask) == MONO) + mDevice->FmtChans = DevFmtMono; else - ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask); + ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask); } OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; switch(mDevice->FmtChans) { - case DevFmtMono: - OutputType.Format.nChannels = 1; - OutputType.dwChannelMask = MONO; - break; - case DevFmtAmbi3D: - mDevice->FmtChans = DevFmtStereo; - /*fall-through*/ - case DevFmtStereo: - OutputType.Format.nChannels = 2; - OutputType.dwChannelMask = STEREO; - break; - case DevFmtQuad: - OutputType.Format.nChannels = 4; - OutputType.dwChannelMask = QUAD; - break; - case DevFmtX51: - OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1; - break; - case DevFmtX51Rear: - OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1REAR; - break; - case DevFmtX61: - OutputType.Format.nChannels = 7; - OutputType.dwChannelMask = X6DOT1; - break; - case DevFmtX71: - OutputType.Format.nChannels = 8; - OutputType.dwChannelMask = X7DOT1; - break; + case DevFmtMono: + OutputType.Format.nChannels = 1; + OutputType.dwChannelMask = MONO; + break; + case DevFmtAmbi3D: + mDevice->FmtChans = DevFmtStereo; + /*fall-through*/ + case DevFmtStereo: + OutputType.Format.nChannels = 2; + OutputType.dwChannelMask = STEREO; + break; + case DevFmtQuad: + OutputType.Format.nChannels = 4; + OutputType.dwChannelMask = QUAD; + break; + case DevFmtX51: + OutputType.Format.nChannels = 6; + OutputType.dwChannelMask = X5DOT1; + break; + case DevFmtX61: + OutputType.Format.nChannels = 7; + OutputType.dwChannelMask = X6DOT1; + break; + case DevFmtX71: + OutputType.Format.nChannels = 8; + OutputType.dwChannelMask = X7DOT1; + break; } switch(mDevice->FmtType) { - case DevFmtByte: - mDevice->FmtType = DevFmtUByte; - /* fall-through */ - case DevFmtUByte: - OutputType.Format.wBitsPerSample = 8; - OutputType.Samples.wValidBitsPerSample = 8; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - OutputType.Format.wBitsPerSample = 16; - OutputType.Samples.wValidBitsPerSample = 16; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - /* fall-through */ - case DevFmtInt: - OutputType.Format.wBitsPerSample = 32; - OutputType.Samples.wValidBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtFloat: - OutputType.Format.wBitsPerSample = 32; - OutputType.Samples.wValidBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - break; + case DevFmtByte: + mDevice->FmtType = DevFmtUByte; + /* fall-through */ + case DevFmtUByte: + OutputType.Format.wBitsPerSample = 8; + OutputType.Samples.wValidBitsPerSample = 8; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtUShort: + mDevice->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + OutputType.Format.wBitsPerSample = 16; + OutputType.Samples.wValidBitsPerSample = 16; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtUInt: + mDevice->FmtType = DevFmtInt; + /* fall-through */ + case DevFmtInt: + OutputType.Format.wBitsPerSample = 32; + OutputType.Samples.wValidBitsPerSample = 32; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtFloat: + OutputType.Format.wBitsPerSample = 32; + OutputType.Samples.wValidBitsPerSample = 32; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + break; } OutputType.Format.nSamplesPerSec = mDevice->Frequency; - OutputType.Format.nBlockAlign = OutputType.Format.nChannels * - OutputType.Format.wBitsPerSample / 8; + OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels * + OutputType.Format.wBitsPerSample / 8); OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * - OutputType.Format.nBlockAlign; + OutputType.Format.nBlockAlign; + TraceFormat("Requesting playback format", &OutputType.Format); hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); if(FAILED(hr)) { @@ -900,6 +984,7 @@ HRESULT WasapiPlayback::resetProxy() if(wfx != nullptr) { + TraceFormat("Got playback format", wfx); if(!MakeExtensible(&OutputType, wfx)) { CoTaskMemFree(wfx); @@ -909,26 +994,62 @@ HRESULT WasapiPlayback::resetProxy() wfx = nullptr; mDevice->Frequency = OutputType.Format.nSamplesPerSec; - if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO) - mDevice->FmtChans = DevFmtMono; - else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO) - mDevice->FmtChans = DevFmtStereo; - else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1) - mDevice->FmtChans = DevFmtX51; - else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR) - mDevice->FmtChans = DevFmtX51Rear; - else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1) - mDevice->FmtChans = DevFmtX61; - else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE)) - mDevice->FmtChans = DevFmtX71; - else + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + /* Don't update the channel format if the requested format fits what's + * supported. + */ + bool chansok{false}; + if(mDevice->Flags.test(ChannelsRequest)) { - ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask); - mDevice->FmtChans = DevFmtStereo; - OutputType.Format.nChannels = 2; - OutputType.dwChannelMask = STEREO; + switch(mDevice->FmtChans) + { + case DevFmtMono: + chansok = (chancount >= 1 && (chanmask&MonoMask) == MONO); + break; + case DevFmtStereo: + chansok = (chancount >= 2 && (chanmask&StereoMask) == STEREO); + break; + case DevFmtQuad: + chansok = (chancount >= 4 && (chanmask&QuadMask) == QUAD); + break; + case DevFmtX51: + chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 + || (chanmask&X51RearMask) == X5DOT1REAR)); + break; + case DevFmtX61: + chansok = (chancount >= 7 && (chanmask&X61Mask) == X6DOT1); + break; + case DevFmtX71: + chansok = (chancount >= 8 && (chanmask&X71Mask) == X7DOT1); + break; + case DevFmtAmbi3D: + break; + } + } + if(!chansok) + { + if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) + mDevice->FmtChans = DevFmtX71; + else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) + mDevice->FmtChans = DevFmtX61; + else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 + || (chanmask&X51RearMask) == X5DOT1REAR)) + mDevice->FmtChans = DevFmtX51; + else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) + mDevice->FmtChans = DevFmtQuad; + else if(chancount >= 2 && (chanmask&StereoMask) == STEREO) + mDevice->FmtChans = DevFmtStereo; + else if(chancount >= 1 && (chanmask&MonoMask) == MONO) + mDevice->FmtChans = DevFmtMono; + else + { + ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, + OutputType.dwChannelMask); + mDevice->FmtChans = DevFmtStereo; + OutputType.Format.nChannels = 2; + OutputType.dwChannelMask = STEREO; + } } if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) @@ -952,7 +1073,7 @@ HRESULT WasapiPlayback::resetProxy() } else { - ERR("Unhandled format sub-type\n"); + ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str()); mDevice->FmtType = DevFmtShort; if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; @@ -961,25 +1082,24 @@ HRESULT WasapiPlayback::resetProxy() } OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; } + mFrameStep = OutputType.Format.nChannels; - EndpointFormFactor formfactor = UnknownFormFactor; - get_device_formfactor(mMMDev, &formfactor); - mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo && - (formfactor == Headphones || formfactor == Headset)); + const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())}; + mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset)); - SetDefaultWFXChannelOrder(mDevice); + setChannelOrderFromWFXMask(OutputType.dwChannelMask); - hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time, - 0, &OutputType.Format, nullptr); + hr = 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); return hr; } - UINT32 buffer_len, min_len; - REFERENCE_TIME min_per; - hr = mClient->GetDevicePeriod(&min_per, nullptr); + UINT32 buffer_len{}; + ReferenceTime min_per{}; + hr = mClient->GetDevicePeriod(&reinterpret_cast(min_per), nullptr); if(SUCCEEDED(hr)) hr = mClient->GetBufferSize(&buffer_len); if(FAILED(hr)) @@ -991,10 +1111,7 @@ HRESULT WasapiPlayback::resetProxy() /* Find the nearest multiple of the period size to the update size */ if(min_per < per_time) min_per *= maxi64((per_time + min_per/2) / min_per, 1); - min_len = (UINT32)ScaleCeil(min_per, mDevice->Frequency, REFTIME_PER_SEC); - min_len = minu(min_len, buffer_len/2); - - mDevice->UpdateSize = min_len; + mDevice->UpdateSize = minu(RefTime2Samples(min_per, mDevice->Frequency), buffer_len/2); mDevice->BufferSize = buffer_len; hr = mClient->SetEventHandle(mNotifyEvent); @@ -1008,17 +1125,19 @@ HRESULT WasapiPlayback::resetProxy() } -ALCboolean WasapiPlayback::start() +void WasapiPlayback::start() { - HRESULT hr{pushMessage(MsgType::StartDevice).get()}; - return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; + 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}; } HRESULT WasapiPlayback::startProxy() { ResetEvent(mNotifyEvent); - HRESULT hr = mClient->Start(); + HRESULT hr{mClient->Start()}; if(FAILED(hr)) { ERR("Failed to start audio client: 0x%08lx\n", hr); @@ -1029,13 +1148,12 @@ HRESULT WasapiPlayback::startProxy() hr = mClient->GetService(IID_IAudioRenderClient, &ptr); if(SUCCEEDED(hr)) { - mRender = static_cast(ptr); + mRender = ComPtr{static_cast(ptr)}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this}; } catch(...) { - mRender->Release(); mRender = nullptr; ERR("Failed to start thread\n"); hr = E_FAIL; @@ -1060,7 +1178,6 @@ void WasapiPlayback::stopProxy() mKillNow.store(true, std::memory_order_release); mThread.join(); - mRender->Release(); mRender = nullptr; mClient->Stop(); } @@ -1070,56 +1187,55 @@ ClockLatency WasapiPlayback::getClockLatency() { ClockLatency ret; - lock(); + std::lock_guard _{mMutex}; ret.ClockTime = GetDeviceClockTime(mDevice); ret.Latency = std::chrono::seconds{mPadding.load(std::memory_order_relaxed)}; ret.Latency /= mDevice->Frequency; - unlock(); return ret; } struct WasapiCapture final : public BackendBase, WasapiProxy { - WasapiCapture(ALCdevice *device) noexcept : BackendBase{device} { } + WasapiCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~WasapiCapture() override; int recordProc(); - ALCenum open(const ALCchar *name) override; - HRESULT openProxy() override; + void open(const char *name) override; + HRESULT openProxy(const char *name) override; void closeProxy() override; HRESULT resetProxy() override; - ALCboolean start() override; + void start() override; HRESULT startProxy() override; void stop() override; void stopProxy() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; - - std::wstring mDevId; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; - IMMDevice *mMMDev{nullptr}; - IAudioClient *mClient{nullptr}; - IAudioCaptureClient *mCapture{nullptr}; + HRESULT mOpenStatus{E_FAIL}; + ComPtr mMMDev{nullptr}; + ComPtr mClient{nullptr}; + ComPtr mCapture{nullptr}; HANDLE mNotifyEvent{nullptr}; - ChannelConverterPtr mChannelConv; + ChannelConverter mChannelConv{}; SampleConverterPtr mSampleConv; RingBufferPtr mRing; std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "WasapiCapture::"; } DEF_NEWDEL(WasapiCapture) }; WasapiCapture::~WasapiCapture() { - pushMessage(MsgType::CloseDevice).wait(); + if(SUCCEEDED(mOpenStatus)) + pushMessage(MsgType::CloseDevice).wait(); + mOpenStatus = E_FAIL; if(mNotifyEvent != nullptr) CloseHandle(mNotifyEvent); @@ -1129,11 +1245,11 @@ WasapiCapture::~WasapiCapture() FORCE_ALIGN int WasapiCapture::recordProc() { - HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; if(FAILED(hr)) { ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr); - aluHandleDisconnect(mDevice, "COM init failed: 0x%08lx", hr); + mDevice->handleDisconnect("COM init failed: 0x%08lx", hr); return 1; } @@ -1157,10 +1273,10 @@ FORCE_ALIGN int WasapiCapture::recordProc() ERR("Failed to get capture buffer: 0x%08lx\n", hr); else { - if(mChannelConv) + if(mChannelConv.is_active()) { samples.resize(numsamples*2); - mChannelConv->convert(rdata, samples.data(), numsamples); + mChannelConv.convert(rdata, samples.data(), numsamples); rdata = reinterpret_cast(samples.data()); } @@ -1169,11 +1285,11 @@ FORCE_ALIGN int WasapiCapture::recordProc() size_t dstframes; if(mSampleConv) { - const ALvoid *srcdata{rdata}; - auto srcframes = static_cast(numsamples); + const void *srcdata{rdata}; + uint srcframes{numsamples}; dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf, - static_cast(minz(data.first.len, INT_MAX))); + static_cast(minz(data.first.len, INT_MAX))); if(srcframes > 0 && dstframes == data.first.len && data.second.len > 0) { /* If some source samples remain, all of the first dest @@ -1181,14 +1297,14 @@ FORCE_ALIGN int WasapiCapture::recordProc() * dest block, do another run for the second block. */ dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf, - static_cast(minz(data.second.len, INT_MAX))); + static_cast(minz(data.second.len, INT_MAX))); } } else { - const auto framesize = static_cast(mDevice->frameSizeFromFmt()); - size_t len1 = minz(data.first.len, numsamples); - size_t len2 = minz(data.second.len, numsamples-len1); + const uint framesize{mDevice->frameSizeFromFmt()}; + size_t len1{minz(data.first.len, numsamples)}; + size_t len2{minz(data.second.len, numsamples-len1)}; memcpy(data.first.buf, rdata, len1*framesize); if(len2 > 0) @@ -1205,7 +1321,7 @@ FORCE_ALIGN int WasapiCapture::recordProc() if(FAILED(hr)) { - aluHandleDisconnect(mDevice, "Failed to capture samples: 0x%08lx", hr); + mDevice->handleDisconnect("Failed to capture samples: 0x%08lx", hr); break; } @@ -1219,7 +1335,7 @@ FORCE_ALIGN int WasapiCapture::recordProc() } -ALCenum WasapiCapture::open(const ALCchar *name) +void WasapiCapture::open(const char *name) { HRESULT hr{S_OK}; @@ -1235,106 +1351,87 @@ ALCenum WasapiCapture::open(const ALCchar *name) if(name) { if(CaptureDevices.empty()) - pushMessage(MsgType::EnumerateCapture).wait(); - - hr = E_FAIL; - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; } - ); - if(iter == CaptureDevices.cend()) - { - std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [&wname](const DevMap &entry) -> bool - { return entry.devid == wname; } - ); - } - if(iter == CaptureDevices.cend()) - WARN("Failed to find device name matching \"%s\"\n", name); - else + pushMessage(MsgType::EnumerateCapture); + if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) { - mDevId = iter->devid; - mDevice->DeviceName = iter->name; - hr = S_OK; + name += DevNameHeadLen; + if(*name == '\0') + name = nullptr; } } + hr = pushMessage(MsgType::OpenDevice, name).get(); } - - if(SUCCEEDED(hr)) - hr = pushMessage(MsgType::OpenDevice).get(); + mOpenStatus = hr; if(FAILED(hr)) - { - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; - - mDevId.clear(); - - ERR("Device init failed: 0x%08lx\n", hr); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", + hr}; hr = pushMessage(MsgType::ResetDevice).get(); if(FAILED(hr)) { if(hr == E_OUTOFMEMORY) - return ALC_OUT_OF_MEMORY; - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::OutOfMemory, "Out of memory"}; + throw al::backend_exception{al::backend_error::DeviceError, "Device reset failed"}; } - - return ALC_NO_ERROR; } -HRESULT WasapiCapture::openProxy() +HRESULT WasapiCapture::openProxy(const char *name) { + const wchar_t *devid{nullptr}; + if(name) + { + auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + [name](const DevMap &entry) -> bool + { return entry.name == name || entry.endpoint_guid == name; }); + if(iter == CaptureDevices.cend()) + { + const std::wstring wname{utf8_to_wstr(name)}; + iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + [&wname](const DevMap &entry) -> bool + { return entry.devid == wname; }); + } + if(iter == CaptureDevices.cend()) + { + WARN("Failed to find device name matching \"%s\"\n", name); + return E_FAIL; + } + name = iter->name.c_str(); + devid = iter->devid.c_str(); + } + void *ptr; HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr)}; if(SUCCEEDED(hr)) { - auto Enumerator = static_cast(ptr); - if(mDevId.empty()) - hr = Enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, &mMMDev); + ComPtr enumerator{static_cast(ptr)}; + if(!devid) + hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr()); else - hr = Enumerator->GetDevice(mDevId.c_str(), &mMMDev); - Enumerator->Release(); + hr = enumerator->GetDevice(devid, mMMDev.getPtr()); } - if(SUCCEEDED(hr)) - hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); - if(SUCCEEDED(hr)) - { - mClient = static_cast(ptr); - if(mDevice->DeviceName.empty()) - mDevice->DeviceName = get_device_name_and_guid(mMMDev).first; - } - if(FAILED(hr)) { - if(mMMDev) - mMMDev->Release(); - mMMDev = nullptr; + WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + return hr; } + mClient = nullptr; + if(name) mDevice->DeviceName = std::string{DevNameHead} + name; + else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first; + return hr; } void WasapiCapture::closeProxy() { - if(mClient) - mClient->Release(); mClient = nullptr; - - if(mMMDev) - mMMDev->Release(); mMMDev = nullptr; } HRESULT WasapiCapture::resetProxy() { - if(mClient) - mClient->Release(); mClient = nullptr; void *ptr; @@ -1344,82 +1441,79 @@ HRESULT WasapiCapture::resetProxy() ERR("Failed to reactivate audio client: 0x%08lx\n", hr); return hr; } - mClient = static_cast(ptr); + mClient = ComPtr{static_cast(ptr)}; // Make sure buffer is at least 100ms in size - REFERENCE_TIME buf_time{mDevice->BufferSize * REFTIME_PER_SEC / mDevice->Frequency}; - buf_time = maxu64(buf_time, REFTIME_PER_SEC/10); + ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency}; + buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}}); - WAVEFORMATEXTENSIBLE OutputType; - OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + WAVEFORMATEXTENSIBLE InputType{}; + InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; switch(mDevice->FmtChans) { - case DevFmtMono: - OutputType.Format.nChannels = 1; - OutputType.dwChannelMask = MONO; - break; - case DevFmtStereo: - OutputType.Format.nChannels = 2; - OutputType.dwChannelMask = STEREO; - break; - case DevFmtQuad: - OutputType.Format.nChannels = 4; - OutputType.dwChannelMask = QUAD; - break; - case DevFmtX51: - OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1; - break; - case DevFmtX51Rear: - OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1REAR; - break; - case DevFmtX61: - OutputType.Format.nChannels = 7; - OutputType.dwChannelMask = X6DOT1; - break; - case DevFmtX71: - OutputType.Format.nChannels = 8; - OutputType.dwChannelMask = X7DOT1; - break; + case DevFmtMono: + InputType.Format.nChannels = 1; + InputType.dwChannelMask = MONO; + break; + case DevFmtStereo: + InputType.Format.nChannels = 2; + InputType.dwChannelMask = STEREO; + break; + case DevFmtQuad: + InputType.Format.nChannels = 4; + InputType.dwChannelMask = QUAD; + break; + case DevFmtX51: + InputType.Format.nChannels = 6; + InputType.dwChannelMask = X5DOT1; + break; + case DevFmtX61: + InputType.Format.nChannels = 7; + InputType.dwChannelMask = X6DOT1; + break; + case DevFmtX71: + InputType.Format.nChannels = 8; + InputType.dwChannelMask = X7DOT1; + break; - case DevFmtAmbi3D: - return E_FAIL; + case DevFmtAmbi3D: + return E_FAIL; } switch(mDevice->FmtType) { - /* NOTE: Signedness doesn't matter, the converter will handle it. */ - case DevFmtByte: - case DevFmtUByte: - OutputType.Format.wBitsPerSample = 8; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtShort: - case DevFmtUShort: - OutputType.Format.wBitsPerSample = 16; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtInt: - case DevFmtUInt: - OutputType.Format.wBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtFloat: - OutputType.Format.wBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - break; + /* NOTE: Signedness doesn't matter, the converter will handle it. */ + case DevFmtByte: + case DevFmtUByte: + InputType.Format.wBitsPerSample = 8; + InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtShort: + case DevFmtUShort: + InputType.Format.wBitsPerSample = 16; + InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtInt: + case DevFmtUInt: + InputType.Format.wBitsPerSample = 32; + InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtFloat: + InputType.Format.wBitsPerSample = 32; + InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + break; } - OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; - OutputType.Format.nSamplesPerSec = mDevice->Frequency; + InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample; + InputType.Format.nSamplesPerSec = mDevice->Frequency; - OutputType.Format.nBlockAlign = OutputType.Format.nChannels * - OutputType.Format.wBitsPerSample / 8; - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * - OutputType.Format.nBlockAlign; - OutputType.Format.cbSize = sizeof(OutputType) - sizeof(OutputType.Format); + InputType.Format.nBlockAlign = static_cast(InputType.Format.nChannels * + InputType.Format.wBitsPerSample / 8); + InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec * + InputType.Format.nBlockAlign; + InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format); + TraceFormat("Requesting capture format", &InputType.Format); WAVEFORMATEX *wfx; - hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); + hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx); if(FAILED(hr)) { ERR("Failed to check format support: 0x%08lx\n", hr); @@ -1427,115 +1521,142 @@ HRESULT WasapiCapture::resetProxy() } mSampleConv = nullptr; - mChannelConv = nullptr; + mChannelConv = {}; if(wfx != nullptr) { - if(!(wfx->nChannels == OutputType.Format.nChannels || - (wfx->nChannels == 1 && OutputType.Format.nChannels == 2) || - (wfx->nChannels == 2 && OutputType.Format.nChannels == 1))) + TraceFormat("Got capture format", wfx); + if(!MakeExtensible(&InputType, wfx)) { - ERR("Failed to get matching format, wanted: %s %s %uhz, got: %d channel%s %d-bit %luhz\n", - DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, wfx->nChannels, (wfx->nChannels==1)?"":"s", wfx->wBitsPerSample, - wfx->nSamplesPerSec); CoTaskMemFree(wfx); return E_FAIL; } + CoTaskMemFree(wfx); + wfx = nullptr; - if(!MakeExtensible(&OutputType, wfx)) + auto validate_fmt = [](DeviceBase *device, uint32_t chancount, DWORD chanmask) noexcept + -> bool { - CoTaskMemFree(wfx); + switch(device->FmtChans) + { + /* If the device wants mono, we can handle any input. */ + case DevFmtMono: + return true; + /* If the device wants stereo, we can handle mono or stereo input. */ + case DevFmtStereo: + return (chancount == 2 && (chanmask == 0 || (chanmask&StereoMask) == STEREO)) + || (chancount == 1 && (chanmask&MonoMask) == MONO); + /* Otherwise, the device must match the input type. */ + case DevFmtQuad: + return (chancount == 4 && (chanmask == 0 || (chanmask&QuadMask) == QUAD)); + /* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */ + case DevFmtX51: + return (chancount == 6 && (chanmask == 0 || (chanmask&X51Mask) == X5DOT1 + || (chanmask&X51RearMask) == X5DOT1REAR)); + case DevFmtX61: + return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1)); + case DevFmtX71: + return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1)); + case DevFmtAmbi3D: + return (chanmask == 0 && chancount == device->channelsFromFmt()); + } + return false; + }; + 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", + DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), + mDevice->Frequency, InputType.dwChannelMask, InputType.Format.nChannels, + (InputType.Format.nChannels==1)?"":"s", InputType.Format.wBitsPerSample, + InputType.Format.nSamplesPerSec); return E_FAIL; } - CoTaskMemFree(wfx); - wfx = nullptr; } - DevFmtType srcType; - if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) + DevFmtType srcType{}; + if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) { - if(OutputType.Format.wBitsPerSample == 8) + if(InputType.Format.wBitsPerSample == 8) srcType = DevFmtUByte; - else if(OutputType.Format.wBitsPerSample == 16) + else if(InputType.Format.wBitsPerSample == 16) srcType = DevFmtShort; - else if(OutputType.Format.wBitsPerSample == 32) + else if(InputType.Format.wBitsPerSample == 32) srcType = DevFmtInt; else { - ERR("Unhandled integer bit depth: %d\n", OutputType.Format.wBitsPerSample); + ERR("Unhandled integer bit depth: %d\n", InputType.Format.wBitsPerSample); return E_FAIL; } } - else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) + else if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { - if(OutputType.Format.wBitsPerSample == 32) + if(InputType.Format.wBitsPerSample == 32) srcType = DevFmtFloat; else { - ERR("Unhandled float bit depth: %d\n", OutputType.Format.wBitsPerSample); + ERR("Unhandled float bit depth: %d\n", InputType.Format.wBitsPerSample); return E_FAIL; } } else { - ERR("Unhandled format sub-type\n"); + ERR("Unhandled format sub-type: %s\n", GuidPrinter{InputType.SubFormat}.c_str()); return E_FAIL; } - if(mDevice->FmtChans == DevFmtMono && OutputType.Format.nChannels == 2) + if(mDevice->FmtChans == DevFmtMono && InputType.Format.nChannels != 1) { - mChannelConv = CreateChannelConverter(srcType, DevFmtStereo, mDevice->FmtChans); - if(!mChannelConv) + uint chanmask{(1u<FmtChans}; + TRACE("Created %s multichannel-to-mono converter\n", DevFmtTypeString(srcType)); /* The channel converter always outputs float, so change the input type * for the resampler/type-converter. */ srcType = DevFmtFloat; } - else if(mDevice->FmtChans == DevFmtStereo && OutputType.Format.nChannels == 1) + else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1) { - mChannelConv = CreateChannelConverter(srcType, DevFmtMono, mDevice->FmtChans); - if(!mChannelConv) - { - ERR("Failed to create %s mono-to-stereo converter\n", DevFmtTypeString(srcType)); - return E_FAIL; - } + mChannelConv = ChannelConverter{srcType, 1, 0x1, mDevice->FmtChans}; TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType)); srcType = DevFmtFloat; } - if(mDevice->Frequency != OutputType.Format.nSamplesPerSec || mDevice->FmtType != srcType) + if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType) { mSampleConv = CreateSampleConverter(srcType, mDevice->FmtType, mDevice->channelsFromFmt(), - OutputType.Format.nSamplesPerSec, mDevice->Frequency, BSinc24Resampler); + InputType.Format.nSamplesPerSec, mDevice->Frequency, Resampler::FastBSinc24); if(!mSampleConv) { ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, DevFmtTypeString(srcType), OutputType.Format.nSamplesPerSec); + mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec); return E_FAIL; } TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n", - DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, DevFmtTypeString(srcType), OutputType.Format.nSamplesPerSec); + DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), + mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec); } - hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time, - 0, &OutputType.Format, nullptr); + hr = mClient->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); return hr; } - UINT32 buffer_len; - REFERENCE_TIME min_per; - hr = mClient->GetDevicePeriod(&min_per, nullptr); + UINT32 buffer_len{}; + ReferenceTime min_per{}; + hr = mClient->GetDevicePeriod(&reinterpret_cast(min_per), nullptr); if(SUCCEEDED(hr)) hr = mClient->GetBufferSize(&buffer_len); if(FAILED(hr)) @@ -1543,17 +1664,10 @@ HRESULT WasapiCapture::resetProxy() ERR("Failed to get buffer size: 0x%08lx\n", hr); return hr; } - mDevice->UpdateSize = static_cast(ScaleCeil(min_per, mDevice->Frequency, - REFTIME_PER_SEC)); + mDevice->UpdateSize = RefTime2Samples(min_per, mDevice->Frequency); mDevice->BufferSize = buffer_len; - buffer_len = maxu(mDevice->BufferSize, buffer_len); - mRing = CreateRingBuffer(buffer_len, mDevice->frameSizeFromFmt(), false); - if(!mRing) - { - ERR("Failed to allocate capture ring buffer\n"); - return E_OUTOFMEMORY; - } + mRing = RingBuffer::Create(buffer_len, mDevice->frameSizeFromFmt(), false); hr = mClient->SetEventHandle(mNotifyEvent); if(FAILED(hr)) @@ -1566,10 +1680,12 @@ HRESULT WasapiCapture::resetProxy() } -ALCboolean WasapiCapture::start() +void WasapiCapture::start() { - HRESULT hr{pushMessage(MsgType::StartDevice).get()}; - return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; + 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}; } HRESULT WasapiCapture::startProxy() @@ -1587,13 +1703,12 @@ HRESULT WasapiCapture::startProxy() hr = mClient->GetService(IID_IAudioCaptureClient, &ptr); if(SUCCEEDED(hr)) { - mCapture = static_cast(ptr); + mCapture = ComPtr{static_cast(ptr)}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this}; } catch(...) { - mCapture->Release(); mCapture = nullptr; ERR("Failed to start thread\n"); hr = E_FAIL; @@ -1621,21 +1736,17 @@ void WasapiCapture::stopProxy() mKillNow.store(true, std::memory_order_release); mThread.join(); - mCapture->Release(); mCapture = nullptr; mClient->Stop(); mClient->Reset(); } -ALCuint WasapiCapture::availableSamples() -{ return (ALCuint)mRing->readSpace(); } +void WasapiCapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } -ALCenum WasapiCapture::captureSamples(void *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} +uint WasapiCapture::availableSamples() +{ return static_cast(mRing->readSpace()); } } // namespace @@ -1655,39 +1766,39 @@ bool WasapiBackendFactory::init() catch(...) { } - return SUCCEEDED(InitResult) ? ALC_TRUE : ALC_FALSE; + return SUCCEEDED(InitResult); } bool WasapiBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -void WasapiBackendFactory::probe(DevProbe type, std::string *outnames) +std::string WasapiBackendFactory::probe(BackendType type) { - auto add_device = [outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames->append(entry.name.c_str(), entry.name.length()+1); - }; - HRESULT hr{}; + std::string outnames; switch(type) { - case DevProbe::Playback: - hr = WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).get(); - if(SUCCEEDED(hr)) - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); + case BackendType::Playback: + WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).wait(); + for(const DevMap &entry : PlaybackDevices) + { + /* +1 to also append the null char (to ensure a null-separated list + * and double-null terminated list). + */ + outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1); + } break; - case DevProbe::Capture: - hr = WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).get(); - if(SUCCEEDED(hr)) - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); + case BackendType::Capture: + WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).wait(); + for(const DevMap &entry : CaptureDevices) + outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1); break; } + + return outnames; } -BackendPtr WasapiBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr WasapiBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new WasapiPlayback{device}}; diff --git a/modules/openal-soft/Alc/backends/wasapi.h b/modules/openal-soft/Alc/backends/wasapi.h index 067dd25..bb2671e 100644 --- a/modules/openal-soft/Alc/backends/wasapi.h +++ b/modules/openal-soft/Alc/backends/wasapi.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_WASAPI_H #define BACKENDS_WASAPI_H -#include "backends/base.h" +#include "base.h" struct WasapiBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/wave.cpp b/modules/openal-soft/Alc/backends/wave.cpp index bf1736f..6360166 100644 --- a/modules/openal-soft/Alc/backends/wave.cpp +++ b/modules/openal-soft/Alc/backends/wave.cpp @@ -20,22 +20,31 @@ #include "config.h" -#include "backends/wave.h" +#include "wave.h" -#include -#include -#include +#include +#include #include - #include -#include -#include +#include +#include +#include +#include #include +#include -#include "alMain.h" -#include "alu.h" -#include "alconfig.h" -#include "compat.h" +#include "albit.h" +#include "albyte.h" +#include "alc/alconfig.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "opthelpers.h" +#include "strutils.h" +#include "threads.h" +#include "vector.h" namespace { @@ -44,61 +53,63 @@ using std::chrono::seconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; -constexpr ALCchar waveDevice[] = "Wave File Writer"; +using ubyte = unsigned char; +using ushort = unsigned short; + +constexpr char waveDevice[] = "Wave File Writer"; -constexpr ALubyte SUBTYPE_PCM[]{ +constexpr ubyte SUBTYPE_PCM[]{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }; -constexpr ALubyte SUBTYPE_FLOAT[]{ +constexpr ubyte SUBTYPE_FLOAT[]{ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }; -constexpr ALubyte SUBTYPE_BFORMAT_PCM[]{ +constexpr ubyte SUBTYPE_BFORMAT_PCM[]{ 0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00 }; -constexpr ALubyte SUBTYPE_BFORMAT_FLOAT[]{ +constexpr ubyte SUBTYPE_BFORMAT_FLOAT[]{ 0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00 }; -void fwrite16le(ALushort val, FILE *f) +void fwrite16le(ushort val, FILE *f) { - ALubyte data[2]{ static_cast(val&0xff), static_cast((val>>8)&0xff) }; + ubyte data[2]{ static_cast(val&0xff), static_cast((val>>8)&0xff) }; fwrite(data, 1, 2, f); } -void fwrite32le(ALuint val, FILE *f) +void fwrite32le(uint val, FILE *f) { - ALubyte data[4]{ static_cast(val&0xff), static_cast((val>>8)&0xff), - static_cast((val>>16)&0xff), static_cast((val>>24)&0xff) }; + ubyte data[4]{ static_cast(val&0xff), static_cast((val>>8)&0xff), + static_cast((val>>16)&0xff), static_cast((val>>24)&0xff) }; fwrite(data, 1, 4, f); } struct WaveBackend final : public BackendBase { - WaveBackend(ALCdevice *device) noexcept : BackendBase{device} { } + WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { } ~WaveBackend() override; int mixerProc(); - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; FILE *mFile{nullptr}; long mDataStart{-1}; - al::vector mBuffer; + al::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "WaveBackend::"; } DEF_NEWDEL(WaveBackend) }; @@ -115,12 +126,13 @@ int WaveBackend::mixerProc() althrd_setname(MIXER_THREAD_NAME); - const ALsizei frameSize{mDevice->frameSizeFromFmt()}; + const size_t frameStep{mDevice->channelsFromFmt()}; + const size_t frameSize{mDevice->frameSizeFromFmt()}; int64_t done{0}; auto start = std::chrono::steady_clock::now(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { auto now = std::chrono::steady_clock::now(); @@ -134,45 +146,35 @@ int WaveBackend::mixerProc() } while(avail-done >= mDevice->UpdateSize) { - lock(); - aluMixData(mDevice, mBuffer.data(), mDevice->UpdateSize); - unlock(); + mDevice->renderSamples(mBuffer.data(), mDevice->UpdateSize, frameStep); done += mDevice->UpdateSize; - if(!IS_LITTLE_ENDIAN) + if(al::endian::native != al::endian::little) { - const ALsizei bytesize{mDevice->bytesFromFmt()}; - ALsizei i; + const uint bytesize{mDevice->bytesFromFmt()}; if(bytesize == 2) { - ALushort *samples = reinterpret_cast(mBuffer.data()); - const auto len = static_cast(mBuffer.size() / 2); - for(i = 0;i < len;i++) - { - ALushort samp = samples[i]; - samples[i] = (samp>>8) | (samp<<8); - } + const size_t len{mBuffer.size() & ~size_t{1}}; + for(size_t i{0};i < len;i+=2) + std::swap(mBuffer[i], mBuffer[i+1]); } else if(bytesize == 4) { - ALuint *samples = reinterpret_cast(mBuffer.data()); - const auto len = static_cast(mBuffer.size() / 4); - for(i = 0;i < len;i++) + const size_t len{mBuffer.size() & ~size_t{3}}; + for(size_t i{0};i < len;i+=4) { - ALuint samp = samples[i]; - samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) | - ((samp<<8)&0x00ff0000) | (samp<<24); + std::swap(mBuffer[i ], mBuffer[i+3]); + std::swap(mBuffer[i+1], mBuffer[i+2]); } } } - size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)}; - (void)fs; - if(ferror(mFile)) + const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)}; + if(fs < mDevice->UpdateSize || ferror(mFile)) { ERR("Error writing to file\n"); - aluHandleDisconnect(mDevice, "Failed to write playback samples"); + mDevice->handleDisconnect("Failed to write playback samples"); break; } } @@ -185,47 +187,48 @@ int WaveBackend::mixerProc() if(done >= mDevice->Frequency) { seconds s{done/mDevice->Frequency}; + done %= mDevice->Frequency; start += s; - done -= mDevice->Frequency*s.count(); } } return 0; } -ALCenum WaveBackend::open(const ALCchar *name) +void WaveBackend::open(const char *name) { - const char *fname{GetConfigValue(nullptr, "wave", "file", "")}; - if(!fname[0]) return ALC_INVALID_VALUE; + auto fname = ConfigValueStr(nullptr, "wave", "file"); + if(!fname) throw al::backend_exception{al::backend_error::NoDevice, + "No wave output filename"}; if(!name) name = waveDevice; else if(strcmp(name, waveDevice) != 0) - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; + + /* There's only one "device", so if it's already open, we're done. */ + if(mFile) return; #ifdef _WIN32 { - std::wstring wname = utf8_to_wstr(fname); + std::wstring wname{utf8_to_wstr(fname->c_str())}; mFile = _wfopen(wname.c_str(), L"wb"); } #else - mFile = fopen(fname, "wb"); + mFile = fopen(fname->c_str(), "wb"); #endif if(!mFile) - { - ERR("Could not open file '%s': %s\n", fname, strerror(errno)); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s", + fname->c_str(), strerror(errno)}; mDevice->DeviceName = name; - - return ALC_NO_ERROR; } -ALCboolean WaveBackend::reset() +bool WaveBackend::reset() { - ALuint channels=0, bytes=0, chanmask=0; - int isbformat = 0; + uint channels{0}, bytes{0}, chanmask{0}; + bool isbformat{false}; size_t val; fseek(mFile, 0, SEEK_SET); @@ -239,38 +242,37 @@ ALCboolean WaveBackend::reset() switch(mDevice->FmtType) { - case DevFmtByte: - mDevice->FmtType = DevFmtUByte; - break; - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - break; - case DevFmtUByte: - case DevFmtShort: - case DevFmtInt: - case DevFmtFloat: - break; + case DevFmtByte: + mDevice->FmtType = DevFmtUByte; + break; + case DevFmtUShort: + mDevice->FmtType = DevFmtShort; + break; + case DevFmtUInt: + mDevice->FmtType = DevFmtInt; + break; + case DevFmtUByte: + case DevFmtShort: + case DevFmtInt: + case DevFmtFloat: + break; } switch(mDevice->FmtChans) { - case DevFmtMono: chanmask = 0x04; break; - case DevFmtStereo: chanmask = 0x01 | 0x02; break; - case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break; - case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break; - case DevFmtX51Rear: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020; break; - case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break; - case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; - case DevFmtAmbi3D: - /* .amb output requires FuMa */ - mDevice->mAmbiOrder = mini(mDevice->mAmbiOrder, 3); - mDevice->mAmbiLayout = AmbiLayout::FuMa; - mDevice->mAmbiScale = AmbiNorm::FuMa; - isbformat = 1; - chanmask = 0; - break; + case DevFmtMono: chanmask = 0x04; break; + case DevFmtStereo: chanmask = 0x01 | 0x02; break; + case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break; + case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break; + case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break; + case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; + case DevFmtAmbi3D: + /* .amb output requires FuMa */ + mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3); + mDevice->mAmbiLayout = DevAmbiLayout::FuMa; + mDevice->mAmbiScale = DevAmbiScaling::FuMa; + isbformat = true; + chanmask = 0; + break; } bytes = mDevice->bytesFromFmt(); channels = mDevice->channelsFromFmt(); @@ -288,25 +290,25 @@ ALCboolean WaveBackend::reset() // 16-bit val, format type id (extensible: 0xFFFE) fwrite16le(0xFFFE, mFile); // 16-bit val, channel count - fwrite16le(channels, mFile); + fwrite16le(static_cast(channels), mFile); // 32-bit val, frequency fwrite32le(mDevice->Frequency, mFile); // 32-bit val, bytes per second fwrite32le(mDevice->Frequency * channels * bytes, mFile); // 16-bit val, frame size - fwrite16le(channels * bytes, mFile); + fwrite16le(static_cast(channels * bytes), mFile); // 16-bit val, bits per sample - fwrite16le(bytes * 8, mFile); + fwrite16le(static_cast(bytes * 8), mFile); // 16-bit val, extra byte count fwrite16le(22, mFile); // 16-bit val, valid bits per sample - fwrite16le(bytes * 8, mFile); + fwrite16le(static_cast(bytes * 8), mFile); // 32-bit val, channel mask fwrite32le(chanmask, mFile); // 16 byte GUID, sub-type format val = fwrite((mDevice->FmtType == DevFmtFloat) ? - (isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) : - (isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile); + (isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) : + (isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile); (void)val; fputs("data", mFile); @@ -315,31 +317,30 @@ ALCboolean WaveBackend::reset() if(ferror(mFile)) { ERR("Error writing header: %s\n", strerror(errno)); - return ALC_FALSE; + return false; } mDataStart = ftell(mFile); - SetDefaultWFXChannelOrder(mDevice); + setDefaultWFXChannelOrder(); - const ALuint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize}; + const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize}; mBuffer.resize(bufsize); - return ALC_TRUE; + return true; } -ALCboolean WaveBackend::start() +void WaveBackend::start() { + if(mDataStart > 0 && fseek(mFile, 0, SEEK_END) != 0) + WARN("Failed to seek on output file\n"); try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this}; - return ALC_TRUE; } catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - catch(...) { - } - return ALC_FALSE; } void WaveBackend::stop() @@ -348,14 +349,17 @@ void WaveBackend::stop() return; mThread.join(); - long size{ftell(mFile)}; - if(size > 0) + if(mDataStart > 0) { - long dataLen{size - mDataStart}; - if(fseek(mFile, mDataStart-4, SEEK_SET) == 0) - fwrite32le(dataLen, mFile); // 'data' header len - if(fseek(mFile, 4, SEEK_SET) == 0) - fwrite32le(size-8, mFile); // 'WAVE' header len + long size{ftell(mFile)}; + if(size > 0) + { + long dataLen{size - mDataStart}; + if(fseek(mFile, 4, SEEK_SET) == 0) + fwrite32le(static_cast(size-8), mFile); // 'WAVE' header len + if(fseek(mFile, mDataStart-4, SEEK_SET) == 0) + fwrite32le(static_cast(dataLen), mFile); // 'data' header len + } } } @@ -368,20 +372,22 @@ bool WaveBackendFactory::init() bool WaveBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -void WaveBackendFactory::probe(DevProbe type, std::string *outnames) +std::string WaveBackendFactory::probe(BackendType type) { + std::string outnames; switch(type) { - case DevProbe::Playback: - /* Includes null char. */ - outnames->append(waveDevice, sizeof(waveDevice)); - break; - case DevProbe::Capture: - break; + case BackendType::Playback: + /* Includes null char. */ + outnames.append(waveDevice, sizeof(waveDevice)); + break; + case BackendType::Capture: + break; } + return outnames; } -BackendPtr WaveBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new WaveBackend{device}}; diff --git a/modules/openal-soft/Alc/backends/wave.h b/modules/openal-soft/Alc/backends/wave.h index b9b62d7..e768d33 100644 --- a/modules/openal-soft/Alc/backends/wave.h +++ b/modules/openal-soft/Alc/backends/wave.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_WAVE_H #define BACKENDS_WAVE_H -#include "backends/base.h" +#include "base.h" struct WaveBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/backends/winmm.cpp b/modules/openal-soft/Alc/backends/winmm.cpp index 12fa02c..0fdd8a0 100644 --- a/modules/openal-soft/Alc/backends/winmm.cpp +++ b/modules/openal-soft/Alc/backends/winmm.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/winmm.h" +#include "winmm.h" #include #include @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -37,11 +38,13 @@ #include #include -#include "alMain.h" -#include "alu.h" +#include "alnumeric.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" #include "ringbuffer.h" +#include "strutils.h" #include "threads.h" -#include "compat.h" #ifndef WAVE_FORMAT_IEEE_FLOAT #define WAVE_FORMAT_IEEE_FLOAT 0x0003 @@ -62,9 +65,9 @@ void ProbePlaybackDevices(void) { PlaybackDevices.clear(); - ALuint numdevs{waveOutGetNumDevs()}; + UINT numdevs{waveOutGetNumDevs()}; PlaybackDevices.reserve(numdevs); - for(ALuint i{0};i < numdevs;i++) + for(UINT i{0};i < numdevs;++i) { std::string dname; @@ -93,9 +96,9 @@ void ProbeCaptureDevices(void) { CaptureDevices.clear(); - ALuint numdevs{waveInGetNumDevs()}; + UINT numdevs{waveInGetNumDevs()}; CaptureDevices.reserve(numdevs); - for(ALuint i{0};i < numdevs;i++) + for(UINT i{0};i < numdevs;++i) { std::string dname; @@ -122,22 +125,23 @@ void ProbeCaptureDevices(void) struct WinMMPlayback final : public BackendBase { - WinMMPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~WinMMPlayback() override; - static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2); - void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2); + void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept; + static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept + { reinterpret_cast(instance)->waveOutProc(device, msg, param1, param2); } int mixerProc(); - ALCenum open(const ALCchar *name) override; - ALCboolean reset() override; - ALCboolean start() override; + void open(const char *name) override; + bool reset() override; + void start() override; void stop() override; - std::atomic mWritable{0u}; + std::atomic mWritable{0u}; al::semaphore mSem; - int mIdx{0}; + uint mIdx{0u}; std::array mWaveBuffer{}; HWAVEOUT mOutHdl{nullptr}; @@ -147,7 +151,6 @@ struct WinMMPlayback final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "WinMMPlayback::"; } DEF_NEWDEL(WinMMPlayback) }; @@ -161,17 +164,12 @@ WinMMPlayback::~WinMMPlayback() std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{}); } - -void CALLBACK WinMMPlayback::waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) -{ reinterpret_cast(instance)->waveOutProc(device, msg, param1, param2); } - /* WinMMPlayback::waveOutProc * * Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is * completed and returns to the application (for more data) */ -void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT UNUSED(device), UINT msg, - DWORD_PTR UNUSED(param1), DWORD_PTR UNUSED(param2)) +void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept { if(msg != WOM_DONE) return; mWritable.fetch_add(1, std::memory_order_acq_rel); @@ -183,37 +181,33 @@ FORCE_ALIGN int WinMMPlayback::mixerProc() SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - lock(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { - ALsizei todo = mWritable.load(std::memory_order_acquire); + uint todo{mWritable.load(std::memory_order_acquire)}; if(todo < 1) { - unlock(); mSem.wait(); - lock(); continue; } - int widx{mIdx}; + size_t widx{mIdx}; do { WAVEHDR &waveHdr = mWaveBuffer[widx]; - widx = (widx+1) % mWaveBuffer.size(); + if(++widx == mWaveBuffer.size()) widx = 0; - aluMixData(mDevice, waveHdr.lpData, mDevice->UpdateSize); + mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, mFormat.nChannels); mWritable.fetch_sub(1, std::memory_order_acq_rel); waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR)); } while(--todo); - mIdx = widx; + mIdx = static_cast(widx); } - unlock(); return 0; } -ALCenum WinMMPlayback::open(const ALCchar *name) +void WinMMPlayback::open(const char *name) { if(PlaybackDevices.empty()) ProbePlaybackDevices(); @@ -222,52 +216,60 @@ ALCenum WinMMPlayback::open(const ALCchar *name) auto iter = name ? std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) : PlaybackDevices.cbegin(); - if(iter == PlaybackDevices.cend()) return ALC_INVALID_VALUE; + if(iter == PlaybackDevices.cend()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; auto DeviceID = static_cast(std::distance(PlaybackDevices.cbegin(), iter)); + DevFmtType fmttype{mDevice->FmtType}; retry_open: - mFormat = WAVEFORMATEX{}; - if(mDevice->FmtType == DevFmtFloat) + WAVEFORMATEX format{}; + if(fmttype == DevFmtFloat) { - mFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; - mFormat.wBitsPerSample = 32; + format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + format.wBitsPerSample = 32; } else { - mFormat.wFormatTag = WAVE_FORMAT_PCM; - if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte) - mFormat.wBitsPerSample = 8; + format.wFormatTag = WAVE_FORMAT_PCM; + if(fmttype == DevFmtUByte || fmttype == DevFmtByte) + format.wBitsPerSample = 8; else - mFormat.wBitsPerSample = 16; + format.wBitsPerSample = 16; } - mFormat.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); - mFormat.nBlockAlign = mFormat.wBitsPerSample * mFormat.nChannels / 8; - mFormat.nSamplesPerSec = mDevice->Frequency; - mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign; - mFormat.cbSize = 0; - - MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &mFormat, (DWORD_PTR)&WinMMPlayback::waveOutProcC, + format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); + format.nBlockAlign = static_cast(format.wBitsPerSample * format.nChannels / 8); + format.nSamplesPerSec = mDevice->Frequency; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.cbSize = 0; + + HWAVEOUT outHandle{}; + MMRESULT res{waveOutOpen(&outHandle, DeviceID, &format, + reinterpret_cast(&WinMMPlayback::waveOutProcC), reinterpret_cast(this), CALLBACK_FUNCTION)}; if(res != MMSYSERR_NOERROR) { - if(mDevice->FmtType == DevFmtFloat) + if(fmttype == DevFmtFloat) { - mDevice->FmtType = DevFmtShort; + fmttype = DevFmtShort; goto retry_open; } - ERR("waveOutOpen failed: %u\n", res); - return ALC_INVALID_VALUE; + throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res}; } + if(mOutHdl) + waveOutClose(mOutHdl); + mOutHdl = outHandle; + mFormat = format; + mDevice->DeviceName = PlaybackDevices[DeviceID]; - return ALC_NO_ERROR; } -ALCboolean WinMMPlayback::reset() +bool WinMMPlayback::reset() { - mDevice->BufferSize = static_cast(uint64_t{mDevice->BufferSize} * + mDevice->BufferSize = static_cast(uint64_t{mDevice->BufferSize} * mFormat.nSamplesPerSec / mDevice->Frequency); - mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3; + mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3u; mDevice->UpdateSize = mDevice->BufferSize / 4; mDevice->Frequency = mFormat.nSamplesPerSec; @@ -278,7 +280,7 @@ ALCboolean WinMMPlayback::reset() else { ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample); - return ALC_FALSE; + return false; } } else if(mFormat.wFormatTag == WAVE_FORMAT_PCM) @@ -290,27 +292,34 @@ ALCboolean WinMMPlayback::reset() else { ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample); - return ALC_FALSE; + return false; } } else { ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag); - return ALC_FALSE; + return false; } - if(mFormat.nChannels == 2) + uint chanmask{}; + if(mFormat.nChannels >= 2) + { mDevice->FmtChans = DevFmtStereo; + chanmask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; + } else if(mFormat.nChannels == 1) + { mDevice->FmtChans = DevFmtMono; + chanmask = SPEAKER_FRONT_CENTER; + } else { ERR("Unhandled channel count: %d\n", mFormat.nChannels); - return ALC_FALSE; + return false; } - SetDefaultWFXChannelOrder(mDevice); + setChannelOrderFromWFXMask(chanmask); - ALuint BufferSize{mDevice->UpdateSize * mDevice->frameSizeFromFmt()}; + uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; al_free(mWaveBuffer[0].lpData); mWaveBuffer[0] = WAVEHDR{}; @@ -324,28 +333,23 @@ ALCboolean WinMMPlayback::reset() } mIdx = 0; - return ALC_TRUE; + return true; } -ALCboolean WinMMPlayback::start() +void WinMMPlayback::start() { try { - std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(), - [this](WAVEHDR &waveHdr) -> void - { waveOutPrepareHeader(mOutHdl, &waveHdr, static_cast(sizeof(WAVEHDR))); } - ); - mWritable.store(static_cast(mWaveBuffer.size()), std::memory_order_release); + for(auto &waveHdr : mWaveBuffer) + waveOutPrepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); + 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}; - return ALC_TRUE; } catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; } - return ALC_FALSE; } void WinMMPlayback::stop() @@ -356,32 +360,31 @@ void WinMMPlayback::stop() while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size()) mSem.wait(); - std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(), - [this](WAVEHDR &waveHdr) -> void - { waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); } - ); + for(auto &waveHdr : mWaveBuffer) + waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); mWritable.store(0, std::memory_order_release); } struct WinMMCapture final : public BackendBase { - WinMMCapture(ALCdevice *device) noexcept : BackendBase{device} { } + WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~WinMMCapture() override; - static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2); - void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2); + void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept; + static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept + { reinterpret_cast(instance)->waveInProc(device, msg, param1, param2); } int captureProc(); - ALCenum open(const ALCchar *name) override; - ALCboolean start() override; + void open(const char *name) override; + void start() override; void stop() override; - ALCenum captureSamples(void *buffer, ALCuint samples) override; - ALCuint availableSamples() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; - std::atomic mReadable{0u}; + std::atomic mReadable{0u}; al::semaphore mSem; - int mIdx{0}; + uint mIdx{0}; std::array mWaveBuffer{}; HWAVEIN mInHdl{nullptr}; @@ -393,7 +396,6 @@ struct WinMMCapture final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - static constexpr inline const char *CurrentPrefix() noexcept { return "WinMMCapture::"; } DEF_NEWDEL(WinMMCapture) }; @@ -408,16 +410,12 @@ WinMMCapture::~WinMMCapture() std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{}); } -void CALLBACK WinMMCapture::waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) -{ reinterpret_cast(instance)->waveInProc(device, msg, param1, param2); } - /* WinMMCapture::waveInProc * * Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is * completed and returns to the application (with more data). */ -void CALLBACK WinMMCapture::waveInProc(HWAVEIN UNUSED(device), UINT msg, - DWORD_PTR UNUSED(param1), DWORD_PTR UNUSED(param2)) +void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept { if(msg != WIM_DATA) return; mReadable.fetch_add(1, std::memory_order_acq_rel); @@ -428,20 +426,17 @@ int WinMMCapture::captureProc() { althrd_setname(RECORD_THREAD_NAME); - lock(); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { - ALuint todo{mReadable.load(std::memory_order_acquire)}; + uint todo{mReadable.load(std::memory_order_acquire)}; if(todo < 1) { - unlock(); mSem.wait(); - lock(); continue; } - int widx{mIdx}; + size_t widx{mIdx}; do { WAVEHDR &waveHdr = mWaveBuffer[widx]; widx = (widx+1) % mWaveBuffer.size(); @@ -450,15 +445,14 @@ int WinMMCapture::captureProc() mReadable.fetch_sub(1, std::memory_order_acq_rel); waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR)); } while(--todo); - mIdx = widx; + mIdx = static_cast(widx); } - unlock(); return 0; } -ALCenum WinMMCapture::open(const ALCchar *name) +void WinMMCapture::open(const char *name) { if(CaptureDevices.empty()) ProbeCaptureDevices(); @@ -467,55 +461,56 @@ ALCenum WinMMCapture::open(const ALCchar *name) auto iter = name ? std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) : CaptureDevices.cbegin(); - if(iter == CaptureDevices.cend()) return ALC_INVALID_VALUE; + if(iter == CaptureDevices.cend()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; auto DeviceID = static_cast(std::distance(CaptureDevices.cbegin(), iter)); switch(mDevice->FmtChans) { - case DevFmtMono: - case DevFmtStereo: - break; - - case DevFmtQuad: - case DevFmtX51: - case DevFmtX51Rear: - case DevFmtX61: - case DevFmtX71: - case DevFmtAmbi3D: - return ALC_INVALID_ENUM; + case DevFmtMono: + case DevFmtStereo: + break; + + case DevFmtQuad: + case DevFmtX51: + case DevFmtX61: + case DevFmtX71: + case DevFmtAmbi3D: + throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", + DevFmtChannelsString(mDevice->FmtChans)}; } switch(mDevice->FmtType) { - case DevFmtUByte: - case DevFmtShort: - case DevFmtInt: - case DevFmtFloat: - break; - - case DevFmtByte: - case DevFmtUShort: - case DevFmtUInt: - return ALC_INVALID_ENUM; + case DevFmtUByte: + case DevFmtShort: + case DevFmtInt: + case DevFmtFloat: + break; + + case DevFmtByte: + case DevFmtUShort: + case DevFmtUInt: + throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported", + DevFmtTypeString(mDevice->FmtType)}; } mFormat = WAVEFORMATEX{}; mFormat.wFormatTag = (mDevice->FmtType == DevFmtFloat) ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM; - mFormat.nChannels = mDevice->channelsFromFmt(); - mFormat.wBitsPerSample = mDevice->bytesFromFmt() * 8; - mFormat.nBlockAlign = mFormat.wBitsPerSample * mFormat.nChannels / 8; + 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.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign; mFormat.cbSize = 0; - MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat, (DWORD_PTR)&WinMMCapture::waveInProcC, + MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat, + reinterpret_cast(&WinMMCapture::waveInProcC), reinterpret_cast(this), CALLBACK_FUNCTION)}; if(res != MMSYSERR_NOERROR) - { - ERR("waveInOpen failed: %u\n", res); - return ALC_INVALID_VALUE; - } + throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: %u", res}; // Ensure each buffer is 50ms each DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u}; @@ -523,15 +518,14 @@ ALCenum WinMMCapture::open(const ALCchar *name) // Allocate circular memory buffer for the captured audio // Make sure circular buffer is at least 100ms in size - ALuint CapturedDataSize{mDevice->BufferSize}; - CapturedDataSize = static_cast(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size())); + uint CapturedDataSize{mDevice->BufferSize}; + CapturedDataSize = static_cast(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size())); - mRing = CreateRingBuffer(CapturedDataSize, mFormat.nBlockAlign, false); - if(!mRing) return ALC_INVALID_VALUE; + mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false); al_free(mWaveBuffer[0].lpData); mWaveBuffer[0] = WAVEHDR{}; - mWaveBuffer[0].lpData = static_cast(al_calloc(16, BufferSize*4)); + mWaveBuffer[0].lpData = static_cast(al_calloc(16, BufferSize * mWaveBuffer.size())); mWaveBuffer[0].dwBufferLength = BufferSize; for(size_t i{1};i < mWaveBuffer.size();++i) { @@ -541,10 +535,9 @@ ALCenum WinMMCapture::open(const ALCchar *name) } mDevice->DeviceName = CaptureDevices[DeviceID]; - return ALC_NO_ERROR; } -ALCboolean WinMMCapture::start() +void WinMMCapture::start() { try { for(size_t i{0};i < mWaveBuffer.size();++i) @@ -557,14 +550,11 @@ ALCboolean WinMMCapture::start() mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this}; waveInStart(mInHdl); - return ALC_TRUE; } catch(std::exception& e) { - ERR("Failed to start mixing thread: %s\n", e.what()); - } - catch(...) { + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start recording thread: %s", e.what()}; } - return ALC_FALSE; } void WinMMCapture::stop() @@ -586,14 +576,11 @@ void WinMMCapture::stop() mIdx = 0; } -ALCenum WinMMCapture::captureSamples(void *buffer, ALCuint samples) -{ - mRing->read(buffer, samples); - return ALC_NO_ERROR; -} +void WinMMCapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } -ALCuint WinMMCapture::availableSamples() -{ return (ALCuint)mRing->readSpace(); } +uint WinMMCapture::availableSamples() +{ return static_cast(mRing->readSpace()); } } // namespace @@ -604,31 +591,33 @@ bool WinMMBackendFactory::init() bool WinMMBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -void WinMMBackendFactory::probe(DevProbe type, std::string *outnames) +std::string WinMMBackendFactory::probe(BackendType type) { - auto add_device = [outnames](const std::string &dname) -> void + std::string outnames; + auto add_device = [&outnames](const std::string &dname) -> void { /* +1 to also append the null char (to ensure a null-separated list and * double-null terminated list). */ if(!dname.empty()) - outnames->append(dname.c_str(), dname.length()+1); + outnames.append(dname.c_str(), dname.length()+1); }; switch(type) { - case DevProbe::Playback: - ProbePlaybackDevices(); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; - - case DevProbe::Capture: - ProbeCaptureDevices(); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; + case BackendType::Playback: + ProbePlaybackDevices(); + std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); + break; + + case BackendType::Capture: + ProbeCaptureDevices(); + std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); + break; } + return outnames; } -BackendPtr WinMMBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr WinMMBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new WinMMPlayback{device}}; diff --git a/modules/openal-soft/Alc/backends/winmm.h b/modules/openal-soft/Alc/backends/winmm.h index e357ec1..45a706a 100644 --- a/modules/openal-soft/Alc/backends/winmm.h +++ b/modules/openal-soft/Alc/backends/winmm.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_WINMM_H #define BACKENDS_WINMM_H -#include "backends/base.h" +#include "base.h" struct WinMMBackendFactory final : public BackendFactory { public: @@ -9,9 +9,9 @@ public: bool querySupport(BackendType type) override; - void probe(DevProbe type, std::string *outnames) override; + std::string probe(BackendType type) override; - BackendPtr createBackend(ALCdevice *device, BackendType type) override; + BackendPtr createBackend(DeviceBase *device, BackendType type) override; static BackendFactory &getFactory(); }; diff --git a/modules/openal-soft/Alc/bformatdec.cpp b/modules/openal-soft/Alc/bformatdec.cpp deleted file mode 100644 index 563282a..0000000 --- a/modules/openal-soft/Alc/bformatdec.cpp +++ /dev/null @@ -1,201 +0,0 @@ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "bformatdec.h" -#include "ambdec.h" -#include "filters/splitter.h" -#include "alu.h" - -#include "threads.h" -#include "almalloc.h" - - -namespace { - -using namespace std::placeholders; - - -constexpr ALfloat Ambi3DDecoderHFScale[MAX_AMBI_ORDER+1] = { - 1.00000000e+00f, 1.00000000e+00f -}; -constexpr ALfloat Ambi3DDecoderHFScale2O[MAX_AMBI_ORDER+1] = { - 7.45355990e-01f, 1.00000000e+00f -}; -constexpr ALfloat Ambi3DDecoderHFScale3O[MAX_AMBI_ORDER+1] = { - 5.89792205e-01f, 8.79693856e-01f -}; - -inline auto GetDecoderHFScales(ALsizei order) noexcept -> const ALfloat(&)[MAX_AMBI_ORDER+1] -{ - if(order >= 3) return Ambi3DDecoderHFScale3O; - if(order == 2) return Ambi3DDecoderHFScale2O; - return Ambi3DDecoderHFScale; -} - -inline auto GetAmbiScales(AmbDecScale scaletype) noexcept -> const std::array& -{ - if(scaletype == AmbDecScale::FuMa) return AmbiScale::FromFuMa; - if(scaletype == AmbDecScale::SN3D) return AmbiScale::FromSN3D; - return AmbiScale::FromN3D; -} - -} // namespace - - -BFormatDec::BFormatDec(const AmbDecConf *conf, const bool allow_2band, const ALsizei inchans, - const ALuint srate, const ALsizei (&chanmap)[MAX_OUTPUT_CHANNELS]) -{ - mDualBand = allow_2band && (conf->FreqBands == 2); - if(!mDualBand) - mSamples.resize(2); - else - { - ASSUME(inchans > 0); - mSamples.resize(inchans * 2); - mSamplesHF = mSamples.data(); - mSamplesLF = mSamplesHF + inchans; - } - mNumChannels = inchans; - - mEnabled = std::accumulate(std::begin(chanmap), std::begin(chanmap)+conf->Speakers.size(), 0u, - [](ALuint mask, const ALsizei &chan) noexcept -> ALuint - { return mask | (1 << chan); } - ); - - const ALfloat xover_norm{conf->XOverFreq / static_cast(srate)}; - - const bool periphonic{(conf->ChanMask&AMBI_PERIPHONIC_MASK) != 0}; - const std::array &coeff_scale = GetAmbiScales(conf->CoeffScale); - const size_t coeff_count{periphonic ? MAX_AMBI_CHANNELS : MAX_AMBI2D_CHANNELS}; - - if(!mDualBand) - { - for(size_t i{0u};i < conf->Speakers.size();i++) - { - ALfloat (&mtx)[MAX_AMBI_CHANNELS] = mMatrix.Single[chanmap[i]]; - for(size_t j{0},k{0};j < coeff_count;j++) - { - const size_t l{periphonic ? j : AmbiIndex::From2D[j]}; - if(!(conf->ChanMask&(1u<HFMatrix[i][k] / coeff_scale[l] * - ((l>=9) ? conf->HFOrderGain[3] : - (l>=4) ? conf->HFOrderGain[2] : - (l>=1) ? conf->HFOrderGain[1] : conf->HFOrderGain[0]); - ++k; - } - } - } - else - { - mXOver[0].init(xover_norm); - std::fill(std::begin(mXOver)+1, std::end(mXOver), mXOver[0]); - - const float ratio{std::pow(10.0f, conf->XOverRatio / 40.0f)}; - for(size_t i{0u};i < conf->Speakers.size();i++) - { - ALfloat (&mtx)[sNumBands][MAX_AMBI_CHANNELS] = mMatrix.Dual[chanmap[i]]; - for(size_t j{0},k{0};j < coeff_count;j++) - { - const size_t l{periphonic ? j : AmbiIndex::From2D[j]}; - if(!(conf->ChanMask&(1u<HFMatrix[i][k] / coeff_scale[l] * - ((l>=9) ? conf->HFOrderGain[3] : - (l>=4) ? conf->HFOrderGain[2] : - (l>=1) ? conf->HFOrderGain[1] : conf->HFOrderGain[0]) * ratio; - mtx[sLFBand][j] = conf->LFMatrix[i][k] / coeff_scale[l] * - ((l>=9) ? conf->LFOrderGain[3] : - (l>=4) ? conf->LFOrderGain[2] : - (l>=1) ? conf->LFOrderGain[1] : conf->LFOrderGain[0]) / ratio; - ++k; - } - } - } -} - -BFormatDec::BFormatDec(const ALsizei inchans, const ALsizei chancount, - const ChannelDec (&chancoeffs)[MAX_OUTPUT_CHANNELS], - const ALsizei (&chanmap)[MAX_OUTPUT_CHANNELS]) -{ - mSamples.resize(2); - mNumChannels = inchans; - - ASSUME(chancount > 0); - mEnabled = std::accumulate(std::begin(chanmap), std::begin(chanmap)+chancount, 0u, - [](ALuint mask, const ALsizei &chan) noexcept -> ALuint - { return mask | (1 << chan); } - ); - - const ChannelDec *incoeffs{chancoeffs}; - auto set_coeffs = [this,inchans,&incoeffs](const ALsizei chanidx) noexcept -> void - { - ASSUME(chanidx >= 0); - ALfloat (&mtx)[MAX_AMBI_CHANNELS] = mMatrix.Single[chanidx]; - const ALfloat (&coeffs)[MAX_AMBI_CHANNELS] = *(incoeffs++); - - ASSUME(inchans > 0); - std::copy_n(std::begin(coeffs), inchans, std::begin(mtx)); - }; - std::for_each(chanmap, chanmap+chancount, set_coeffs); -} - - -void BFormatDec::process(ALfloat (*OutBuffer)[BUFFERSIZE], const ALsizei OutChannels, const ALfloat (*InSamples)[BUFFERSIZE], const ALsizei SamplesToDo) -{ - ASSUME(OutChannels > 0); - ASSUME(mNumChannels > 0); - - if(mDualBand) - { - for(ALsizei i{0};i < mNumChannels;i++) - mXOver[i].process(mSamplesHF[i].data(), mSamplesLF[i].data(), InSamples[i], - SamplesToDo); - - for(ALsizei chan{0};chan < OutChannels;chan++) - { - if(UNLIKELY(!(mEnabled&(1<(mSamplesHF[0]), - mNumChannels, 0, SamplesToDo); - MixRowSamples(OutBuffer[chan], mMatrix.Dual[chan][sLFBand], - &reinterpret_cast(mSamplesLF[0]), - mNumChannels, 0, SamplesToDo); - } - } - else - { - for(ALsizei chan{0};chan < OutChannels;chan++) - { - if(UNLIKELY(!(mEnabled&(1< BFormatDec::GetHFOrderScales(const ALsizei in_order, const ALsizei out_order) noexcept -{ - std::array ret{}; - - assert(out_order >= in_order); - ASSUME(out_order >= in_order); - - const ALfloat (&target)[MAX_AMBI_ORDER+1] = GetDecoderHFScales(out_order); - const ALfloat (&input)[MAX_AMBI_ORDER+1] = GetDecoderHFScales(in_order); - - for(ALsizei i{0};i < in_order+1;++i) - ret[i] = input[i] / target[i]; - - return ret; -} diff --git a/modules/openal-soft/Alc/bformatdec.h b/modules/openal-soft/Alc/bformatdec.h deleted file mode 100644 index d82f08a..0000000 --- a/modules/openal-soft/Alc/bformatdec.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef BFORMATDEC_H -#define BFORMATDEC_H - -#include "alMain.h" -#include "filters/splitter.h" -#include "ambidefs.h" -#include "almalloc.h" - - -struct AmbDecConf; - - -using ChannelDec = ALfloat[MAX_AMBI_CHANNELS]; - -class BFormatDec { - static constexpr size_t sHFBand{0}; - static constexpr size_t sLFBand{1}; - static constexpr size_t sNumBands{2}; - - ALuint mEnabled{0u}; /* Bitfield of enabled channels. */ - - union MatrixU { - ALfloat Dual[MAX_OUTPUT_CHANNELS][sNumBands][MAX_AMBI_CHANNELS]; - ALfloat Single[MAX_OUTPUT_CHANNELS][MAX_AMBI_CHANNELS]; - } mMatrix{}; - - /* NOTE: BandSplitter filters are unused with single-band decoding */ - BandSplitter mXOver[MAX_AMBI_CHANNELS]; - - al::vector, 16> mSamples; - /* These two alias into Samples */ - std::array *mSamplesHF{nullptr}; - std::array *mSamplesLF{nullptr}; - - ALsizei mNumChannels{0}; - bool mDualBand{false}; - -public: - BFormatDec(const AmbDecConf *conf, const bool allow_2band, const ALsizei inchans, - const ALuint srate, const ALsizei (&chanmap)[MAX_OUTPUT_CHANNELS]); - BFormatDec(const ALsizei inchans, const ALsizei chancount, - const ChannelDec (&chancoeffs)[MAX_OUTPUT_CHANNELS], - const ALsizei (&chanmap)[MAX_OUTPUT_CHANNELS]); - - /* Decodes the ambisonic input to the given output channels. */ - void process(ALfloat (*OutBuffer)[BUFFERSIZE], const ALsizei OutChannels, - const ALfloat (*InSamples)[BUFFERSIZE], const ALsizei SamplesToDo); - - /* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */ - static std::array GetHFOrderScales(const ALsizei in_order, - const ALsizei out_order) noexcept; - - DEF_NEWDEL(BFormatDec) -}; - -#endif /* BFORMATDEC_H */ diff --git a/modules/openal-soft/Alc/compat.h b/modules/openal-soft/Alc/compat.h deleted file mode 100644 index dc652bc..0000000 --- a/modules/openal-soft/Alc/compat.h +++ /dev/null @@ -1,236 +0,0 @@ -#ifndef AL_COMPAT_H -#define AL_COMPAT_H - -#ifdef __cplusplus - -#ifdef _WIN32 - -#define WIN32_LEAN_AND_MEAN -#include - -#include -#include -#include - -inline std::string wstr_to_utf8(const WCHAR *wstr) -{ - std::string ret; - - int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr); - if(len > 0) - { - ret.resize(len); - WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &ret[0], len, nullptr, nullptr); - ret.pop_back(); - } - - return ret; -} - -inline std::wstring utf8_to_wstr(const char *str) -{ - std::wstring ret; - - int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); - if(len > 0) - { - ret.resize(len); - MultiByteToWideChar(CP_UTF8, 0, str, -1, &ret[0], len); - ret.pop_back(); - } - - return ret; -} - - -namespace al { - -// Windows' std::ifstream fails with non-ANSI paths since the standard only -// specifies names using const char* (or std::string). MSVC has a non-standard -// extension using const wchar_t* (or std::wstring?) to handle Unicode paths, -// but not all Windows compilers support it. So we have to make our own istream -// that accepts UTF-8 paths and forwards to Unicode-aware I/O functions. -class filebuf final : public std::streambuf { - std::array mBuffer; - HANDLE mFile{INVALID_HANDLE_VALUE}; - - int_type underflow() override - { - if(mFile != INVALID_HANDLE_VALUE && gptr() == egptr()) - { - // Read in the next chunk of data, and set the pointers on success - DWORD got = 0; - if(ReadFile(mFile, mBuffer.data(), (DWORD)mBuffer.size(), &got, nullptr)) - setg(mBuffer.data(), mBuffer.data(), mBuffer.data()+got); - } - if(gptr() == egptr()) - return traits_type::eof(); - return traits_type::to_int_type(*gptr()); - } - - pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override - { - if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return traits_type::eof(); - - LARGE_INTEGER fpos; - switch(whence) - { - case std::ios_base::beg: - fpos.QuadPart = offset; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN)) - return traits_type::eof(); - break; - - case std::ios_base::cur: - // If the offset remains in the current buffer range, just - // update the pointer. - if((offset >= 0 && offset < off_type(egptr()-gptr())) || - (offset < 0 && -offset <= off_type(gptr()-eback()))) - { - // Get the current file offset to report the correct read - // offset. - fpos.QuadPart = 0; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT)) - return traits_type::eof(); - setg(eback(), gptr()+offset, egptr()); - return fpos.QuadPart - off_type(egptr()-gptr()); - } - // Need to offset for the file offset being at egptr() while - // the requested offset is relative to gptr(). - offset -= off_type(egptr()-gptr()); - fpos.QuadPart = offset; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT)) - return traits_type::eof(); - break; - - case std::ios_base::end: - fpos.QuadPart = offset; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_END)) - return traits_type::eof(); - break; - - default: - return traits_type::eof(); - } - setg(nullptr, nullptr, nullptr); - return fpos.QuadPart; - } - - pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override - { - // Simplified version of seekoff - if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return traits_type::eof(); - - LARGE_INTEGER fpos; - fpos.QuadPart = pos; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN)) - return traits_type::eof(); - - setg(nullptr, nullptr, nullptr); - return fpos.QuadPart; - } - -public: - bool open(const wchar_t *filename, std::ios_base::openmode mode) - { - if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return false; - HANDLE f{CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)}; - if(f == INVALID_HANDLE_VALUE) return false; - - if(mFile != INVALID_HANDLE_VALUE) - CloseHandle(mFile); - mFile = f; - - setg(nullptr, nullptr, nullptr); - return true; - } - bool open(const char *filename, std::ios_base::openmode mode) - { - std::wstring wname{utf8_to_wstr(filename)}; - return open(wname.c_str(), mode); - } - - bool is_open() const noexcept { return mFile != INVALID_HANDLE_VALUE; } - - filebuf() = default; - ~filebuf() override - { - if(mFile != INVALID_HANDLE_VALUE) - CloseHandle(mFile); - mFile = INVALID_HANDLE_VALUE; - } -}; - -// Inherit from std::istream to use our custom streambuf -class ifstream final : public std::istream { - filebuf mStreamBuf; - -public: - ifstream(const std::wstring &filename, std::ios_base::openmode mode = std::ios_base::in) - : ifstream(filename.c_str(), mode) { } - ifstream(const wchar_t *filename, std::ios_base::openmode mode = std::ios_base::in) - : std::istream{nullptr} - { - init(&mStreamBuf); - - // Set the failbit if the file failed to open. - if((mode&std::ios_base::out) || - !mStreamBuf.open(filename, mode|std::ios_base::in)) - clear(failbit); - } - - ifstream(const std::string &filename, std::ios_base::openmode mode = std::ios_base::in) - : ifstream(filename.c_str(), mode) { } - ifstream(const char *filename, std::ios_base::openmode mode = std::ios_base::in) - : std::istream{nullptr} - { - init(&mStreamBuf); - - // Set the failbit if the file failed to open. - if((mode&std::ios_base::out) || - !mStreamBuf.open(filename, mode|std::ios_base::in)) - clear(failbit); - } - - bool is_open() const noexcept { return mStreamBuf.is_open(); } -}; - -} // namespace al - -#define HAVE_DYNLOAD 1 - -#else /* _WIN32 */ - -#include - -namespace al { - -using filebuf = std::filebuf; -using ifstream = std::ifstream; - -} // namespace al - -#if defined(HAVE_DLFCN_H) -#define HAVE_DYNLOAD 1 -#endif - -#endif /* _WIN32 */ - -#include - -struct PathNamePair { std::string path, fname; }; -const PathNamePair &GetProcBinary(void); - -#ifdef HAVE_DYNLOAD -void *LoadLib(const char *name); -void CloseLib(void *handle); -void *GetSymbol(void *handle, const char *name); -#endif - -#endif /* __cplusplus */ - -#endif /* AL_COMPAT_H */ diff --git a/modules/openal-soft/Alc/context.cpp b/modules/openal-soft/Alc/context.cpp new file mode 100644 index 0000000..456c054 --- /dev/null +++ b/modules/openal-soft/Alc/context.cpp @@ -0,0 +1,1299 @@ + +#include "config.h" + +#include "context.h" + +#include +#include +#include +#include +#include +#include + +#include "AL/efx.h" + +#include "al/auxeffectslot.h" +#include "al/source.h" +#include "al/effect.h" +#include "al/event.h" +#include "al/listener.h" +#include "albit.h" +#include "alc/alu.h" +#include "core/async_event.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 "ringbuffer.h" +#include "vecmat.h" + +#ifdef ALSOFT_EAX +#include +#include + +#include "alstring.h" +#include "al/eax_exception.h" +#include "al/eax_globals.h" +#endif // ALSOFT_EAX + +namespace { + +using namespace std::placeholders; + +using voidp = void*; + +/* Default context extensions */ +constexpr ALchar alExtList[] = + "AL_EXT_ALAW " + "AL_EXT_BFORMAT " + "AL_EXT_DOUBLE " + "AL_EXT_EXPONENT_DISTANCE " + "AL_EXT_FLOAT32 " + "AL_EXT_IMA4 " + "AL_EXT_LINEAR_DISTANCE " + "AL_EXT_MCFORMATS " + "AL_EXT_MULAW " + "AL_EXT_MULAW_BFORMAT " + "AL_EXT_MULAW_MCFORMATS " + "AL_EXT_OFFSET " + "AL_EXT_source_distance_model " + "AL_EXT_SOURCE_RADIUS " + "AL_EXT_STEREO_ANGLES " + "AL_LOKI_quadriphonic " + "AL_SOFT_bformat_ex " + "AL_SOFTX_bformat_hoa " + "AL_SOFT_block_alignment " + "AL_SOFT_callback_buffer " + "AL_SOFTX_convolution_reverb " + "AL_SOFT_deferred_updates " + "AL_SOFT_direct_channels " + "AL_SOFT_direct_channels_remix " + "AL_SOFT_effect_target " + "AL_SOFT_events " + "AL_SOFT_gain_clamp_ex " + "AL_SOFTX_hold_on_disconnect " + "AL_SOFT_loop_points " + "AL_SOFTX_map_buffer " + "AL_SOFT_MSADPCM " + "AL_SOFT_source_latency " + "AL_SOFT_source_length " + "AL_SOFT_source_resampler " + "AL_SOFT_source_spatialize " + "AL_SOFT_UHJ"; + +} // namespace + + +std::atomic ALCcontext::sGlobalContext{nullptr}; + +thread_local ALCcontext *ALCcontext::sLocalContext{nullptr}; +ALCcontext::ThreadCtx::~ThreadCtx() +{ + if(ALCcontext *ctx{ALCcontext::sLocalContext}) + { + const bool result{ctx->releaseIfNoDelete()}; + ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx}, + result ? "" : ", leak detected"); + } +} +thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext; + +ALeffect ALCcontext::sDefaultEffect; + + +#ifdef __MINGW32__ +ALCcontext *ALCcontext::getThreadContext() noexcept +{ return sLocalContext; } +void ALCcontext::setThreadContext(ALCcontext *context) noexcept +{ sThreadContext.set(context); } +#endif + +ALCcontext::ALCcontext(al::intrusive_ptr device) + : ContextBase{device.get()}, mALDevice{std::move(device)} +{ +} + +ALCcontext::~ALCcontext() +{ + TRACE("Freeing context %p\n", voidp{this}); + + size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), size_t{0u}, + [](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"); + mSourceList.clear(); + mNumSources = 0; + +#ifdef ALSOFT_EAX + eax_uninitialize(); +#endif // ALSOFT_EAX + + mDefaultSlot = nullptr; + count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), size_t{0u}, + [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t + { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); + if(count > 0) + WARN("%zu AuxiliaryEffectSlot%s not deleted\n", count, (count==1)?"":"s"); + mEffectSlotList.clear(); + mNumEffectSlots = 0; +} + +void ALCcontext::init() +{ + if(sDefaultEffect.type != AL_EFFECT_NULL && mDevice->Type == DeviceType::Playback) + { + mDefaultSlot = std::make_unique(); + aluInitEffectPanning(&mDefaultSlot->mSlot, this); + } + + EffectSlotArray *auxslots; + if(!mDefaultSlot) + auxslots = EffectSlot::CreatePtrArray(0); + else + { + auxslots = EffectSlot::CreatePtrArray(1); + (*auxslots)[0] = &mDefaultSlot->mSlot; + mDefaultSlot->mState = SlotState::Playing; + } + mActiveAuxSlots.store(auxslots, std::memory_order_relaxed); + + allocVoiceChanges(); + { + VoiceChange *cur{mVoiceChangeTail}; + while(VoiceChange *next{cur->mNext.load(std::memory_order_relaxed)}) + cur = next; + mCurrentVoiceChange.store(cur, std::memory_order_relaxed); + } + + mExtensionList = alExtList; + +#ifdef ALSOFT_EAX + eax_initialize_extensions(); +#endif // ALSOFT_EAX + + 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.AirAbsorptionGainHF = mAirAbsorptionGainHF; + mParams.DopplerFactor = mDopplerFactor; + mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity; + mParams.SourceDistanceModel = mSourceDistanceModel; + mParams.mDistanceModel = mDistanceModel; + + + mAsyncEvents = RingBuffer::Create(511, sizeof(AsyncEvent), false); + StartEventThrd(this); + + + allocVoices(256); + mActiveVoiceCount.store(64, std::memory_order_relaxed); +} + +bool ALCcontext::deinit() +{ + if(sLocalContext == this) + { + WARN("%p released while current on thread\n", voidp{this}); + sThreadContext.set(nullptr); + release(); + } + + ALCcontext *origctx{this}; + if(sGlobalContext.compare_exchange_strong(origctx, nullptr)) + release(); + + bool ret{}; + /* First make sure this context exists in the device's list. */ + auto *oldarray = mDevice->mContexts.load(std::memory_order_acquire); + if(auto toremove = static_cast(std::count(oldarray->begin(), oldarray->end(), this))) + { + using ContextArray = al::FlexArray; + auto alloc_ctx_array = [](const size_t count) -> ContextArray* + { + if(count == 0) return &DeviceBase::sEmptyContextArray; + return ContextArray::Create(count).release(); + }; + auto *newarray = alloc_ctx_array(oldarray->size() - toremove); + + /* Copy the current/old context handles to the new array, excluding the + * given context. + */ + std::copy_if(oldarray->begin(), oldarray->end(), newarray->begin(), + std::bind(std::not_equal_to<>{}, _1, this)); + + /* Store the new context array in the device. Wait for any current mix + * to finish before deleting the old array. + */ + mDevice->mContexts.store(newarray); + if(oldarray != &DeviceBase::sEmptyContextArray) + { + mDevice->waitForMix(); + delete oldarray; + } + + ret = !newarray->empty(); + } + else + ret = !oldarray->empty(); + + StopEventThrd(this); + + return ret; +} + +void ALCcontext::applyAllUpdates() +{ + /* Tell the mixer to stop applying updates, then wait for any active + * updating to finish, before providing updates. + */ + mHoldUpdates.store(true, std::memory_order_release); + while((mUpdateCount.load(std::memory_order_acquire)&1) != 0) { + /* busy-wait */ + } + +#ifdef ALSOFT_EAX + eax_apply_deferred(); +#endif + if(std::exchange(mPropsDirty, false)) + UpdateContextProps(this); + UpdateAllEffectSlotProps(this); + UpdateAllSourceProps(this); + + /* Now with all updates declared, let the mixer continue applying them so + * they all happen at once. + */ + mHoldUpdates.store(false, std::memory_order_release); +} + +#ifdef ALSOFT_EAX +namespace { + +class ContextException : + public EaxException +{ +public: + explicit ContextException( + const char* message) + : + EaxException{"EAX_CONTEXT", message} + { + } +}; // ContextException + + +template +void ForEachSource(ALCcontext *context, F func) +{ + for(auto &sublist : context->mSourceList) + { + uint64_t usemask{~sublist.FreeMask}; + while(usemask) + { + const int idx{al::countr_zero(usemask)}; + usemask &= ~(1_u64 << idx); + + func(sublist.Sources[idx]); + } + } +} + +} // namespace + + +bool ALCcontext::eax_is_capable() const noexcept +{ + return eax_has_enough_aux_sends(); +} + +void ALCcontext::eax_uninitialize() noexcept +{ + if (!eax_is_initialized_) + { + return; + } + + eax_is_initialized_ = true; + eax_is_tried_ = false; + + eax_fx_slots_.uninitialize(); +} + +ALenum ALCcontext::eax_eax_set( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size) +{ + eax_initialize(); + + const auto eax_call = create_eax_call( + false, + property_set_id, + property_id, + property_source_id, + property_value, + property_value_size + ); + + eax_unlock_legacy_fx_slots(eax_call); + + switch (eax_call.get_property_set_id()) + { + case EaxEaxCallPropertySetId::context: + eax_set(eax_call); + break; + + case EaxEaxCallPropertySetId::fx_slot: + case EaxEaxCallPropertySetId::fx_slot_effect: + eax_dispatch_fx_slot(eax_call); + break; + + case EaxEaxCallPropertySetId::source: + eax_dispatch_source(eax_call); + break; + + default: + eax_fail("Unsupported property set id."); + } + + static constexpr auto deferred_flag = 0x80000000u; + if(!(property_id&deferred_flag) && !mDeferUpdates) + applyAllUpdates(); + + return AL_NO_ERROR; +} + +ALenum ALCcontext::eax_eax_get( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size) +{ + eax_initialize(); + + const auto eax_call = create_eax_call( + true, + property_set_id, + property_id, + property_source_id, + property_value, + property_value_size + ); + + eax_unlock_legacy_fx_slots(eax_call); + + switch (eax_call.get_property_set_id()) + { + case EaxEaxCallPropertySetId::context: + eax_get(eax_call); + break; + + case EaxEaxCallPropertySetId::fx_slot: + case EaxEaxCallPropertySetId::fx_slot_effect: + eax_dispatch_fx_slot(eax_call); + break; + + case EaxEaxCallPropertySetId::source: + eax_dispatch_source(eax_call); + break; + + default: + eax_fail("Unsupported property set id."); + } + + return AL_NO_ERROR; +} + +void ALCcontext::eax_update_filters() +{ + ForEachSource(this, std::mem_fn(&ALsource::eax_update_filters)); +} + +void ALCcontext::eax_commit_and_update_sources() +{ + std::unique_lock source_lock{mSourceLock}; + ForEachSource(this, std::mem_fn(&ALsource::eax_commit_and_update)); +} + +void ALCcontext::eax_set_last_error() noexcept +{ + eax_last_error_ = EAXERR_INVALID_OPERATION; +} + +[[noreturn]] +void ALCcontext::eax_fail( + const char* message) +{ + throw ContextException{message}; +} + +void ALCcontext::eax_initialize_extensions() +{ + if (!eax_g_is_enabled) + { + return; + } + + const auto string_max_capacity = + std::strlen(mExtensionList) + 1 + + std::strlen(eax1_ext_name) + 1 + + std::strlen(eax2_ext_name) + 1 + + std::strlen(eax3_ext_name) + 1 + + std::strlen(eax4_ext_name) + 1 + + std::strlen(eax5_ext_name) + 1 + + std::strlen(eax_x_ram_ext_name) + 1 + + 0; + + eax_extension_list_.reserve(string_max_capacity); + + if (eax_is_capable()) + { + eax_extension_list_ += eax1_ext_name; + eax_extension_list_ += ' '; + + eax_extension_list_ += eax2_ext_name; + eax_extension_list_ += ' '; + + eax_extension_list_ += eax3_ext_name; + eax_extension_list_ += ' '; + + eax_extension_list_ += eax4_ext_name; + eax_extension_list_ += ' '; + + eax_extension_list_ += eax5_ext_name; + eax_extension_list_ += ' '; + } + + eax_extension_list_ += eax_x_ram_ext_name; + eax_extension_list_ += ' '; + + eax_extension_list_ += mExtensionList; + mExtensionList = eax_extension_list_.c_str(); +} + +void ALCcontext::eax_initialize() +{ + if (eax_is_initialized_) + { + return; + } + + if (eax_is_tried_) + { + eax_fail("No EAX."); + } + + eax_is_tried_ = true; + + if (!eax_g_is_enabled) + { + eax_fail("EAX disabled by a configuration."); + } + + eax_ensure_compatibility(); + eax_set_defaults(); + eax_set_air_absorbtion_hf(); + eax_update_speaker_configuration(); + eax_initialize_fx_slots(); + eax_initialize_sources(); + + eax_is_initialized_ = true; +} + +bool ALCcontext::eax_has_no_default_effect_slot() const noexcept +{ + return mDefaultSlot == nullptr; +} + +void ALCcontext::eax_ensure_no_default_effect_slot() const +{ + if (!eax_has_no_default_effect_slot()) + { + eax_fail("There is a default effect slot in the context."); + } +} + +bool ALCcontext::eax_has_enough_aux_sends() const noexcept +{ + return mALDevice->NumAuxSends >= EAX_MAX_FXSLOTS; +} + +void ALCcontext::eax_ensure_enough_aux_sends() const +{ + if (!eax_has_enough_aux_sends()) + { + eax_fail("Not enough aux sends."); + } +} + +void ALCcontext::eax_ensure_compatibility() +{ + eax_ensure_enough_aux_sends(); +} + +unsigned long ALCcontext::eax_detect_speaker_configuration() const +{ +#define EAX_PREFIX "[EAX_DETECT_SPEAKER_CONFIG]" + + switch(mDevice->FmtChans) + { + case DevFmtMono: return SPEAKERS_2; + case DevFmtStereo: + /* Pretend 7.1 if using UHJ output, since they both provide full + * horizontal surround. + */ + if(mDevice->mUhjEncoder) + return SPEAKERS_7; + if(mDevice->Flags.test(DirectEar)) + return HEADPHONES; + return SPEAKERS_2; + case DevFmtQuad: return SPEAKERS_4; + case DevFmtX51: return SPEAKERS_5; + case DevFmtX61: return SPEAKERS_6; + case DevFmtX71: return SPEAKERS_7; + /* This could also be HEADPHONES, since headphones-based HRTF and Ambi3D + * provide full-sphere surround sound. Depends if apps are more likely to + * consider headphones or 7.1 for surround sound support. + */ + case DevFmtAmbi3D: return SPEAKERS_7; + } + ERR(EAX_PREFIX "Unexpected device channel format 0x%x.\n", mDevice->FmtChans); + return HEADPHONES; + +#undef EAX_PREFIX +} + +void ALCcontext::eax_update_speaker_configuration() +{ + eax_speaker_config_ = eax_detect_speaker_configuration(); +} + +void ALCcontext::eax_set_last_error_defaults() noexcept +{ + eax_last_error_ = EAX_OK; +} + +void ALCcontext::eax_set_session_defaults() noexcept +{ + eax_session_.ulEAXVersion = EAXCONTEXT_MINEAXSESSION; + eax_session_.ulMaxActiveSends = EAXCONTEXT_DEFAULTMAXACTIVESENDS; +} + +void ALCcontext::eax_set_context_defaults() noexcept +{ + eax_.context.guidPrimaryFXSlotID = EAXCONTEXT_DEFAULTPRIMARYFXSLOTID; + eax_.context.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR; + eax_.context.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF; + eax_.context.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE; +} + +void ALCcontext::eax_set_defaults() noexcept +{ + eax_set_last_error_defaults(); + eax_set_session_defaults(); + eax_set_context_defaults(); + + eax_d_ = eax_; +} + +void ALCcontext::eax_unlock_legacy_fx_slots(const EaxEaxCall& eax_call) noexcept +{ + if (eax_call.get_version() != 5 || eax_are_legacy_fx_slots_unlocked_) + return; + + eax_are_legacy_fx_slots_unlocked_ = true; + eax_fx_slots_.unlock_legacy(); +} + +void ALCcontext::eax_dispatch_fx_slot( + const EaxEaxCall& eax_call) +{ + const auto fx_slot_index = eax_call.get_fx_slot_index(); + if(!fx_slot_index.has_value()) + eax_fail("Invalid fx slot index."); + + auto& fx_slot = eax_get_fx_slot(*fx_slot_index); + if(fx_slot.eax_dispatch(eax_call)) + { + std::lock_guard source_lock{mSourceLock}; + eax_update_filters(); + } +} + +void ALCcontext::eax_dispatch_source( + const EaxEaxCall& eax_call) +{ + const auto source_id = eax_call.get_property_al_name(); + + std::lock_guard source_lock{mSourceLock}; + + const auto source = ALsource::eax_lookup_source(*this, source_id); + + if (!source) + { + eax_fail("Source not found."); + } + + source->eax_dispatch(eax_call); +} + +void ALCcontext::eax_get_primary_fx_slot_id( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_.context.guidPrimaryFXSlotID); +} + +void ALCcontext::eax_get_distance_factor( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_.context.flDistanceFactor); +} + +void ALCcontext::eax_get_air_absorption_hf( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_.context.flAirAbsorptionHF); +} + +void ALCcontext::eax_get_hf_reference( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_.context.flHFReference); +} + +void ALCcontext::eax_get_last_error( + const EaxEaxCall& eax_call) +{ + const auto eax_last_error = eax_last_error_; + eax_last_error_ = EAX_OK; + eax_call.set_value(eax_last_error); +} + +void ALCcontext::eax_get_speaker_config( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_speaker_config_); +} + +void ALCcontext::eax_get_session( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_session_); +} + +void ALCcontext::eax_get_macro_fx_factor( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_.context.flMacroFXFactor); +} + +void ALCcontext::eax_get_context_all( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_version()) + { + case 4: + eax_call.set_value(static_cast(eax_.context)); + break; + + case 5: + eax_call.set_value(static_cast(eax_.context)); + break; + + default: + eax_fail("Unsupported EAX version."); + } +} + +void ALCcontext::eax_get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXCONTEXT_NONE: + break; + + case EAXCONTEXT_ALLPARAMETERS: + eax_get_context_all(eax_call); + break; + + case EAXCONTEXT_PRIMARYFXSLOTID: + eax_get_primary_fx_slot_id(eax_call); + break; + + case EAXCONTEXT_DISTANCEFACTOR: + eax_get_distance_factor(eax_call); + break; + + case EAXCONTEXT_AIRABSORPTIONHF: + eax_get_air_absorption_hf(eax_call); + break; + + case EAXCONTEXT_HFREFERENCE: + eax_get_hf_reference(eax_call); + break; + + case EAXCONTEXT_LASTERROR: + eax_get_last_error(eax_call); + break; + + case EAXCONTEXT_SPEAKERCONFIG: + eax_get_speaker_config(eax_call); + break; + + case EAXCONTEXT_EAXSESSION: + eax_get_session(eax_call); + break; + + case EAXCONTEXT_MACROFXFACTOR: + eax_get_macro_fx_factor(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +void ALCcontext::eax_set_primary_fx_slot_id() +{ + eax_previous_primary_fx_slot_index_ = eax_primary_fx_slot_index_; + eax_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID; +} + +void ALCcontext::eax_set_distance_factor() +{ + mListener.mMetersPerUnit = eax_.context.flDistanceFactor; + mPropsDirty = true; +} + +void ALCcontext::eax_set_air_absorbtion_hf() +{ + mAirAbsorptionGainHF = level_mb_to_gain(eax_.context.flAirAbsorptionHF); + mPropsDirty = true; +} + +void ALCcontext::eax_set_hf_reference() +{ + // TODO +} + +void ALCcontext::eax_set_macro_fx_factor() +{ + // TODO +} + +void ALCcontext::eax_set_context() +{ + eax_set_primary_fx_slot_id(); + eax_set_distance_factor(); + eax_set_air_absorbtion_hf(); + eax_set_hf_reference(); +} + +void ALCcontext::eax_initialize_fx_slots() +{ + eax_fx_slots_.initialize(*this); + eax_previous_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID; + eax_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID; +} + +void ALCcontext::eax_initialize_sources() +{ + std::unique_lock source_lock{mSourceLock}; + auto init_source = [this](ALsource &source) noexcept + { source.eax_initialize(this); }; + ForEachSource(this, init_source); +} + +void ALCcontext::eax_update_sources() +{ + std::unique_lock source_lock{mSourceLock}; + auto update_source = [this](ALsource &source) + { source.eax_update(eax_context_shared_dirty_flags_); }; + ForEachSource(this, update_source); +} + +void ALCcontext::eax_validate_primary_fx_slot_id( + const GUID& primary_fx_slot_id) +{ + if (primary_fx_slot_id != EAX_NULL_GUID && + primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot0 && + primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot0 && + primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot1 && + primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot1 && + primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot2 && + primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot2 && + primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot3 && + primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot3) + { + eax_fail("Unsupported primary FX slot id."); + } +} + +void ALCcontext::eax_validate_distance_factor( + float distance_factor) +{ + eax_validate_range( + "Distance Factor", + distance_factor, + EAXCONTEXT_MINDISTANCEFACTOR, + EAXCONTEXT_MAXDISTANCEFACTOR); +} + +void ALCcontext::eax_validate_air_absorption_hf( + float air_absorption_hf) +{ + eax_validate_range( + "Air Absorption HF", + air_absorption_hf, + EAXCONTEXT_MINAIRABSORPTIONHF, + EAXCONTEXT_MAXAIRABSORPTIONHF); +} + +void ALCcontext::eax_validate_hf_reference( + float hf_reference) +{ + eax_validate_range( + "HF Reference", + hf_reference, + EAXCONTEXT_MINHFREFERENCE, + EAXCONTEXT_MAXHFREFERENCE); +} + +void ALCcontext::eax_validate_speaker_config( + unsigned long speaker_config) +{ + switch (speaker_config) + { + case HEADPHONES: + case SPEAKERS_2: + case SPEAKERS_4: + case SPEAKERS_5: + case SPEAKERS_6: + case SPEAKERS_7: + break; + + default: + eax_fail("Unsupported speaker configuration."); + } +} + +void ALCcontext::eax_validate_session_eax_version( + unsigned long eax_version) +{ + switch (eax_version) + { + case EAX_40: + case EAX_50: + break; + + default: + eax_fail("Unsupported session EAX version."); + } +} + +void ALCcontext::eax_validate_session_max_active_sends( + unsigned long max_active_sends) +{ + eax_validate_range( + "Max Active Sends", + max_active_sends, + EAXCONTEXT_MINMAXACTIVESENDS, + EAXCONTEXT_MAXMAXACTIVESENDS); +} + +void ALCcontext::eax_validate_session( + const EAXSESSIONPROPERTIES& eax_session) +{ + eax_validate_session_eax_version(eax_session.ulEAXVersion); + eax_validate_session_max_active_sends(eax_session.ulMaxActiveSends); +} + +void ALCcontext::eax_validate_macro_fx_factor( + float macro_fx_factor) +{ + eax_validate_range( + "Macro FX Factor", + macro_fx_factor, + EAXCONTEXT_MINMACROFXFACTOR, + EAXCONTEXT_MAXMACROFXFACTOR); +} + +void ALCcontext::eax_validate_context_all( + const EAX40CONTEXTPROPERTIES& context_all) +{ + eax_validate_primary_fx_slot_id(context_all.guidPrimaryFXSlotID); + eax_validate_distance_factor(context_all.flDistanceFactor); + eax_validate_air_absorption_hf(context_all.flAirAbsorptionHF); + eax_validate_hf_reference(context_all.flHFReference); +} + +void ALCcontext::eax_validate_context_all( + const EAX50CONTEXTPROPERTIES& context_all) +{ + eax_validate_context_all(static_cast(context_all)); + eax_validate_macro_fx_factor(context_all.flMacroFXFactor); +} + +void ALCcontext::eax_defer_primary_fx_slot_id( + const GUID& primary_fx_slot_id) +{ + eax_d_.context.guidPrimaryFXSlotID = primary_fx_slot_id; + + eax_context_dirty_flags_.guidPrimaryFXSlotID = + (eax_.context.guidPrimaryFXSlotID != eax_d_.context.guidPrimaryFXSlotID); +} + +void ALCcontext::eax_defer_distance_factor( + float distance_factor) +{ + eax_d_.context.flDistanceFactor = distance_factor; + + eax_context_dirty_flags_.flDistanceFactor = + (eax_.context.flDistanceFactor != eax_d_.context.flDistanceFactor); +} + +void ALCcontext::eax_defer_air_absorption_hf( + float air_absorption_hf) +{ + eax_d_.context.flAirAbsorptionHF = air_absorption_hf; + + eax_context_dirty_flags_.flAirAbsorptionHF = + (eax_.context.flAirAbsorptionHF != eax_d_.context.flAirAbsorptionHF); +} + +void ALCcontext::eax_defer_hf_reference( + float hf_reference) +{ + eax_d_.context.flHFReference = hf_reference; + + eax_context_dirty_flags_.flHFReference = + (eax_.context.flHFReference != eax_d_.context.flHFReference); +} + +void ALCcontext::eax_defer_macro_fx_factor( + float macro_fx_factor) +{ + eax_d_.context.flMacroFXFactor = macro_fx_factor; + + eax_context_dirty_flags_.flMacroFXFactor = + (eax_.context.flMacroFXFactor != eax_d_.context.flMacroFXFactor); +} + +void ALCcontext::eax_defer_context_all( + const EAX40CONTEXTPROPERTIES& context_all) +{ + eax_defer_primary_fx_slot_id(context_all.guidPrimaryFXSlotID); + eax_defer_distance_factor(context_all.flDistanceFactor); + eax_defer_air_absorption_hf(context_all.flAirAbsorptionHF); + eax_defer_hf_reference(context_all.flHFReference); +} + +void ALCcontext::eax_defer_context_all( + const EAX50CONTEXTPROPERTIES& context_all) +{ + eax_defer_context_all(static_cast(context_all)); + eax_defer_macro_fx_factor(context_all.flMacroFXFactor); +} + +void ALCcontext::eax_defer_context_all( + const EaxEaxCall& eax_call) +{ + switch(eax_call.get_version()) + { + case 4: + { + const auto& context_all = + eax_call.get_value(); + + eax_validate_context_all(context_all); + eax_defer_context_all(context_all); + } + break; + + case 5: + { + const auto& context_all = + eax_call.get_value(); + + eax_validate_context_all(context_all); + eax_defer_context_all(context_all); + } + break; + + default: + eax_fail("Unsupported EAX version."); + } +} + +void ALCcontext::eax_defer_primary_fx_slot_id( + const EaxEaxCall& eax_call) +{ + const auto& primary_fx_slot_id = + eax_call.get_value(); + + eax_validate_primary_fx_slot_id(primary_fx_slot_id); + eax_defer_primary_fx_slot_id(primary_fx_slot_id); +} + +void ALCcontext::eax_defer_distance_factor( + const EaxEaxCall& eax_call) +{ + const auto& distance_factor = + eax_call.get_value(); + + eax_validate_distance_factor(distance_factor); + eax_defer_distance_factor(distance_factor); +} + +void ALCcontext::eax_defer_air_absorption_hf( + const EaxEaxCall& eax_call) +{ + const auto& air_absorption_hf = + eax_call.get_value(); + + eax_validate_air_absorption_hf(air_absorption_hf); + eax_defer_air_absorption_hf(air_absorption_hf); +} + +void ALCcontext::eax_defer_hf_reference( + const EaxEaxCall& eax_call) +{ + const auto& hf_reference = + eax_call.get_value(); + + eax_validate_hf_reference(hf_reference); + eax_defer_hf_reference(hf_reference); +} + +void ALCcontext::eax_set_session( + const EaxEaxCall& eax_call) +{ + const auto& eax_session = + eax_call.get_value(); + + eax_validate_session(eax_session); + + eax_session_ = eax_session; +} + +void ALCcontext::eax_defer_macro_fx_factor( + const EaxEaxCall& eax_call) +{ + const auto& macro_fx_factor = + eax_call.get_value(); + + eax_validate_macro_fx_factor(macro_fx_factor); + eax_defer_macro_fx_factor(macro_fx_factor); +} + +void ALCcontext::eax_set( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXCONTEXT_NONE: + break; + + case EAXCONTEXT_ALLPARAMETERS: + eax_defer_context_all(eax_call); + break; + + case EAXCONTEXT_PRIMARYFXSLOTID: + eax_defer_primary_fx_slot_id(eax_call); + break; + + case EAXCONTEXT_DISTANCEFACTOR: + eax_defer_distance_factor(eax_call); + break; + + case EAXCONTEXT_AIRABSORPTIONHF: + eax_defer_air_absorption_hf(eax_call); + break; + + case EAXCONTEXT_HFREFERENCE: + eax_defer_hf_reference(eax_call); + break; + + case EAXCONTEXT_LASTERROR: + eax_fail("Last error is read-only."); + + case EAXCONTEXT_SPEAKERCONFIG: + eax_fail("Speaker configuration is read-only."); + + case EAXCONTEXT_EAXSESSION: + eax_set_session(eax_call); + break; + + case EAXCONTEXT_MACROFXFACTOR: + eax_defer_macro_fx_factor(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +void ALCcontext::eax_apply_deferred() +{ + if (eax_context_dirty_flags_ == ContextDirtyFlags{}) + { + return; + } + + eax_ = eax_d_; + + if (eax_context_dirty_flags_.guidPrimaryFXSlotID) + { + eax_context_shared_dirty_flags_.primary_fx_slot_id = true; + eax_set_primary_fx_slot_id(); + } + + if (eax_context_dirty_flags_.flDistanceFactor) + { + eax_set_distance_factor(); + } + + if (eax_context_dirty_flags_.flAirAbsorptionHF) + { + eax_set_air_absorbtion_hf(); + } + + if (eax_context_dirty_flags_.flHFReference) + { + eax_set_hf_reference(); + } + + if (eax_context_dirty_flags_.flMacroFXFactor) + { + eax_set_macro_fx_factor(); + } + + if (eax_context_shared_dirty_flags_ != EaxContextSharedDirtyFlags{}) + { + eax_update_sources(); + } + + eax_context_shared_dirty_flags_ = EaxContextSharedDirtyFlags{}; + eax_context_dirty_flags_ = ContextDirtyFlags{}; +} + + +namespace +{ + + +class EaxSetException : + public EaxException +{ +public: + explicit EaxSetException( + const char* message) + : + EaxException{"EAX_SET", message} + { + } +}; // EaxSetException + + +[[noreturn]] +void eax_fail_set( + const char* message) +{ + throw EaxSetException{message}; +} + + +class EaxGetException : + public EaxException +{ +public: + explicit EaxGetException( + const char* message) + : + EaxException{"EAX_GET", message} + { + } +}; // EaxGetException + + +[[noreturn]] +void eax_fail_get( + const char* message) +{ + throw EaxGetException{message}; +} + + +} // namespace + + +FORCE_ALIGN ALenum AL_APIENTRY EAXSet( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size) noexcept +try +{ + auto context = GetContextRef(); + + if (!context) + { + eax_fail_set("No current context."); + } + + std::lock_guard prop_lock{context->mPropLock}; + + return context->eax_eax_set( + property_set_id, + property_id, + property_source_id, + property_value, + property_value_size + ); +} +catch (...) +{ + eax_log_exception(__func__); + return AL_INVALID_OPERATION; +} + +FORCE_ALIGN ALenum AL_APIENTRY EAXGet( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size) noexcept +try +{ + auto context = GetContextRef(); + + if (!context) + { + eax_fail_get("No current context."); + } + + std::lock_guard prop_lock{context->mPropLock}; + + return context->eax_eax_get( + property_set_id, + property_id, + property_source_id, + property_value, + property_value_size + ); +} +catch (...) +{ + eax_log_exception(__func__); + return AL_INVALID_OPERATION; +} +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/Alc/context.h b/modules/openal-soft/Alc/context.h new file mode 100644 index 0000000..72b259e --- /dev/null +++ b/modules/openal-soft/Alc/context.h @@ -0,0 +1,504 @@ +#ifndef ALC_CONTEXT_H +#define ALC_CONTEXT_H + +#include +#include +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "al/listener.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "atomic.h" +#include "core/context.h" +#include "intrusive_ptr.h" +#include "vector.h" + +#ifdef ALSOFT_EAX +#include "al/eax_eax_call.h" +#include "al/eax_fx_slot_index.h" +#include "al/eax_fx_slots.h" +#include "al/eax_utils.h" + + +using EaxContextSharedDirtyFlagsValue = std::uint_least8_t; + +struct EaxContextSharedDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxContextSharedDirtyFlagsValue primary_fx_slot_id : 1; +}; // EaxContextSharedDirtyFlags + + +using ContextDirtyFlagsValue = std::uint_least8_t; + +struct ContextDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + ContextDirtyFlagsValue guidPrimaryFXSlotID : 1; + ContextDirtyFlagsValue flDistanceFactor : 1; + ContextDirtyFlagsValue flAirAbsorptionHF : 1; + ContextDirtyFlagsValue flHFReference : 1; + ContextDirtyFlagsValue flMacroFXFactor : 1; +}; // ContextDirtyFlags + + +struct EaxAlIsExtensionPresentResult +{ + ALboolean is_present; + bool is_return; +}; // EaxAlIsExtensionPresentResult +#endif // ALSOFT_EAX + +struct ALeffect; +struct ALeffectslot; +struct ALsource; + +using uint = unsigned int; + + +struct SourceSubList { + uint64_t FreeMask{~0_u64}; + ALsource *Sources{nullptr}; /* 64 */ + + SourceSubList() noexcept = default; + SourceSubList(const SourceSubList&) = delete; + SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources} + { rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; } + ~SourceSubList(); + + SourceSubList& operator=(const SourceSubList&) = delete; + SourceSubList& operator=(SourceSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; } +}; + +struct EffectSlotSubList { + uint64_t FreeMask{~0_u64}; + ALeffectslot *EffectSlots{nullptr}; /* 64 */ + + EffectSlotSubList() noexcept = default; + EffectSlotSubList(const EffectSlotSubList&) = delete; + EffectSlotSubList(EffectSlotSubList&& rhs) noexcept + : FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots} + { rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; } + ~EffectSlotSubList(); + + EffectSlotSubList& operator=(const EffectSlotSubList&) = delete; + EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; } +}; + +struct ALCcontext : public al::intrusive_ref, ContextBase { + const al::intrusive_ptr mALDevice; + + /* Wet buffers used by effect slots. */ + al::vector mWetBuffers; + + + bool mPropsDirty{true}; + bool mDeferUpdates{false}; + + std::mutex mPropLock; + + std::atomic mLastError{AL_NO_ERROR}; + + DistanceModel mDistanceModel{DistanceModel::Default}; + bool mSourceDistanceModel{false}; + + float mDopplerFactor{1.0f}; + float mDopplerVelocity{1.0f}; + float mSpeedOfSound{SpeedOfSoundMetersPerSec}; + float mAirAbsorptionGainHF{AirAbsorbGainHF}; + + std::mutex mEventCbLock; + ALEVENTPROCSOFT mEventCb{}; + void *mEventParam{nullptr}; + + ALlistener mListener{}; + + al::vector mSourceList; + ALuint mNumSources{0}; + std::mutex mSourceLock; + + al::vector mEffectSlotList; + ALuint mNumEffectSlots{0u}; + std::mutex mEffectSlotLock; + + /* Default effect slot */ + std::unique_ptr mDefaultSlot; + + const char *mExtensionList{nullptr}; + + + ALCcontext(al::intrusive_ptr device); + ALCcontext(const ALCcontext&) = delete; + ALCcontext& operator=(const ALCcontext&) = delete; + ~ALCcontext(); + + void init(); + /** + * Removes the context from its device and removes it from being current on + * the running thread or globally. Returns true if other contexts still + * exist on the device. + */ + bool deinit(); + + /** + * Defers/suspends updates for the given context's listener and sources. + * This does *NOT* stop mixing, but rather prevents certain property + * changes from taking effect. mPropLock must be held when called. + */ + void deferUpdates() noexcept { mDeferUpdates = true; } + + /** + * Resumes update processing after being deferred. mPropLock must be held + * when called. + */ + void processUpdates() + { + if(std::exchange(mDeferUpdates, false)) + applyAllUpdates(); + } + + /** + * Applies all pending updates for the context, listener, effect slots, and + * sources. + */ + void applyAllUpdates(); + +#ifdef __USE_MINGW_ANSI_STDIO + [[gnu::format(gnu_printf, 3, 4)]] +#else + [[gnu::format(printf, 3, 4)]] +#endif + void setError(ALenum errorCode, const char *msg, ...); + + /* Process-wide current context */ + static std::atomic sGlobalContext; + +private: + /* Thread-local current context. */ + static thread_local ALCcontext *sLocalContext; + + /* Thread-local context handling. This handles attempting to release the + * context which may have been left current when the thread is destroyed. + */ + class ThreadCtx { + public: + ~ThreadCtx(); + void set(ALCcontext *ctx) const noexcept { sLocalContext = ctx; } + }; + static thread_local ThreadCtx sThreadContext; + +public: + /* HACK: MinGW generates bad code when accessing an extern thread_local + * object. Add a wrapper function for it that only accesses it where it's + * defined. + */ +#ifdef __MINGW32__ + static ALCcontext *getThreadContext() noexcept; + static void setThreadContext(ALCcontext *context) noexcept; +#else + static ALCcontext *getThreadContext() noexcept { return sLocalContext; } + static void setThreadContext(ALCcontext *context) noexcept { sThreadContext.set(context); } +#endif + + /* Default effect that applies to sources that don't have an effect on send 0. */ + static ALeffect sDefaultEffect; + + DEF_NEWDEL(ALCcontext) + +#ifdef ALSOFT_EAX +public: + bool has_eax() const noexcept { return eax_is_initialized_; } + + bool eax_is_capable() const noexcept; + + + void eax_uninitialize() noexcept; + + + ALenum eax_eax_set( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size); + + ALenum eax_eax_get( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size); + + + void eax_update_filters(); + + void eax_commit_and_update_sources(); + + + void eax_set_last_error() noexcept; + + + EaxFxSlotIndex eax_get_previous_primary_fx_slot_index() const noexcept + { return eax_previous_primary_fx_slot_index_; } + EaxFxSlotIndex eax_get_primary_fx_slot_index() const noexcept + { return eax_primary_fx_slot_index_; } + + const ALeffectslot& eax_get_fx_slot(EaxFxSlotIndexValue fx_slot_index) const + { return eax_fx_slots_.get(fx_slot_index); } + ALeffectslot& eax_get_fx_slot(EaxFxSlotIndexValue fx_slot_index) + { return eax_fx_slots_.get(fx_slot_index); } + + void eax_commit_fx_slots() + { eax_fx_slots_.commit(); } + +private: + struct Eax + { + EAX50CONTEXTPROPERTIES context{}; + }; // Eax + + + bool eax_is_initialized_{}; + bool eax_is_tried_{}; + bool eax_are_legacy_fx_slots_unlocked_{}; + + long eax_last_error_{}; + unsigned long eax_speaker_config_{}; + + EaxFxSlotIndex eax_previous_primary_fx_slot_index_{}; + EaxFxSlotIndex eax_primary_fx_slot_index_{}; + EaxFxSlots eax_fx_slots_{}; + + EaxContextSharedDirtyFlags eax_context_shared_dirty_flags_{}; + + Eax eax_{}; + Eax eax_d_{}; + EAXSESSIONPROPERTIES eax_session_{}; + + ContextDirtyFlags eax_context_dirty_flags_{}; + + std::string eax_extension_list_{}; + + + [[noreturn]] + static void eax_fail( + const char* message); + + + void eax_initialize_extensions(); + + void eax_initialize(); + + + bool eax_has_no_default_effect_slot() const noexcept; + + void eax_ensure_no_default_effect_slot() const; + + bool eax_has_enough_aux_sends() const noexcept; + + void eax_ensure_enough_aux_sends() const; + + void eax_ensure_compatibility(); + + + unsigned long eax_detect_speaker_configuration() const; + void eax_update_speaker_configuration(); + + + void eax_set_last_error_defaults() noexcept; + + void eax_set_session_defaults() noexcept; + + void eax_set_context_defaults() noexcept; + + void eax_set_defaults() noexcept; + + void eax_initialize_sources(); + + + void eax_unlock_legacy_fx_slots(const EaxEaxCall& eax_call) noexcept; + + + void eax_dispatch_fx_slot( + const EaxEaxCall& eax_call); + + void eax_dispatch_source( + const EaxEaxCall& eax_call); + + + void eax_get_primary_fx_slot_id( + const EaxEaxCall& eax_call); + + void eax_get_distance_factor( + const EaxEaxCall& eax_call); + + void eax_get_air_absorption_hf( + const EaxEaxCall& eax_call); + + void eax_get_hf_reference( + const EaxEaxCall& eax_call); + + void eax_get_last_error( + const EaxEaxCall& eax_call); + + void eax_get_speaker_config( + const EaxEaxCall& eax_call); + + void eax_get_session( + const EaxEaxCall& eax_call); + + void eax_get_macro_fx_factor( + const EaxEaxCall& eax_call); + + void eax_get_context_all( + const EaxEaxCall& eax_call); + + void eax_get( + const EaxEaxCall& eax_call); + + + void eax_set_primary_fx_slot_id(); + + void eax_set_distance_factor(); + + void eax_set_air_absorbtion_hf(); + + void eax_set_hf_reference(); + + void eax_set_macro_fx_factor(); + + void eax_set_context(); + + void eax_initialize_fx_slots(); + + + void eax_update_sources(); + + + void eax_validate_primary_fx_slot_id( + const GUID& primary_fx_slot_id); + + void eax_validate_distance_factor( + float distance_factor); + + void eax_validate_air_absorption_hf( + float air_absorption_hf); + + void eax_validate_hf_reference( + float hf_reference); + + void eax_validate_speaker_config( + unsigned long speaker_config); + + void eax_validate_session_eax_version( + unsigned long eax_version); + + void eax_validate_session_max_active_sends( + unsigned long max_active_sends); + + void eax_validate_session( + const EAXSESSIONPROPERTIES& eax_session); + + void eax_validate_macro_fx_factor( + float macro_fx_factor); + + void eax_validate_context_all( + const EAX40CONTEXTPROPERTIES& context_all); + + void eax_validate_context_all( + const EAX50CONTEXTPROPERTIES& context_all); + + + void eax_defer_primary_fx_slot_id( + const GUID& primary_fx_slot_id); + + void eax_defer_distance_factor( + float distance_factor); + + void eax_defer_air_absorption_hf( + float air_absorption_hf); + + void eax_defer_hf_reference( + float hf_reference); + + void eax_defer_macro_fx_factor( + float macro_fx_factor); + + void eax_defer_context_all( + const EAX40CONTEXTPROPERTIES& context_all); + + void eax_defer_context_all( + const EAX50CONTEXTPROPERTIES& context_all); + + + void eax_defer_context_all( + const EaxEaxCall& eax_call); + + void eax_defer_primary_fx_slot_id( + const EaxEaxCall& eax_call); + + void eax_defer_distance_factor( + const EaxEaxCall& eax_call); + + void eax_defer_air_absorption_hf( + const EaxEaxCall& eax_call); + + void eax_defer_hf_reference( + const EaxEaxCall& eax_call); + + void eax_set_session( + const EaxEaxCall& eax_call); + + void eax_defer_macro_fx_factor( + const EaxEaxCall& eax_call); + + void eax_set( + const EaxEaxCall& eax_call); + + void eax_apply_deferred(); +#endif // ALSOFT_EAX +}; + +#define SETERR_RETURN(ctx, err, retval, ...) do { \ + (ctx)->setError((err), __VA_ARGS__); \ + return retval; \ +} while(0) + + +using ContextRef = al::intrusive_ptr; + +ContextRef GetContextRef(void); + +void UpdateContextProps(ALCcontext *context); + + +extern bool TrapALError; + + +#ifdef ALSOFT_EAX +ALenum AL_APIENTRY EAXSet( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size) noexcept; + +ALenum AL_APIENTRY EAXGet( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size) noexcept; +#endif // ALSOFT_EAX + +#endif /* ALC_CONTEXT_H */ diff --git a/modules/openal-soft/Alc/converter.cpp b/modules/openal-soft/Alc/converter.cpp deleted file mode 100644 index 5535fd4..0000000 --- a/modules/openal-soft/Alc/converter.cpp +++ /dev/null @@ -1,369 +0,0 @@ - -#include "config.h" - -#include "converter.h" - -#include - -#include "fpu_modes.h" -#include "mixer/defs.h" - - -namespace { - -/* Base template left undefined. Should be marked =delete, but Clang 3.8.1 - * chokes on that given the inline specializations. - */ -template -inline ALfloat LoadSample(typename DevFmtTypeTraits::Type val); - -template<> inline ALfloat LoadSample(DevFmtTypeTraits::Type val) -{ return val * (1.0f/128.0f); } -template<> inline ALfloat LoadSample(DevFmtTypeTraits::Type val) -{ return val * (1.0f/32768.0f); } -template<> inline ALfloat LoadSample(DevFmtTypeTraits::Type val) -{ return val * (1.0f/2147483648.0f); } -template<> inline ALfloat LoadSample(DevFmtTypeTraits::Type val) -{ return val; } - -template<> inline ALfloat LoadSample(DevFmtTypeTraits::Type val) -{ return LoadSample(val - 128); } -template<> inline ALfloat LoadSample(DevFmtTypeTraits::Type val) -{ return LoadSample(val - 32768); } -template<> inline ALfloat LoadSample(DevFmtTypeTraits::Type val) -{ return LoadSample(val - 2147483648u); } - - -template -inline void LoadSampleArray(ALfloat *RESTRICT dst, const void *src, size_t srcstep, ALsizei samples) -{ - using SampleType = typename DevFmtTypeTraits::Type; - - const SampleType *ssrc = static_cast(src); - for(ALsizei i{0};i < samples;i++) - dst[i] = LoadSample(ssrc[i*srcstep]); -} - -void LoadSamples(ALfloat *dst, const ALvoid *src, size_t srcstep, DevFmtType srctype, ALsizei samples) -{ -#define HANDLE_FMT(T) \ - case T: LoadSampleArray(dst, src, srcstep, samples); break - switch(srctype) - { - HANDLE_FMT(DevFmtByte); - HANDLE_FMT(DevFmtUByte); - HANDLE_FMT(DevFmtShort); - HANDLE_FMT(DevFmtUShort); - HANDLE_FMT(DevFmtInt); - HANDLE_FMT(DevFmtUInt); - HANDLE_FMT(DevFmtFloat); - } -#undef HANDLE_FMT -} - - -template -inline typename DevFmtTypeTraits::Type StoreSample(ALfloat); - -template<> inline ALfloat StoreSample(ALfloat val) -{ return val; } -template<> inline ALint StoreSample(ALfloat val) -{ return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); } -template<> inline ALshort StoreSample(ALfloat val) -{ return fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f)); } -template<> inline ALbyte StoreSample(ALfloat val) -{ return fastf2i(clampf(val*128.0f, -128.0f, 127.0f)); } - -/* Define unsigned output variations. */ -template<> inline ALuint StoreSample(ALfloat val) -{ return StoreSample(val) + 2147483648u; } -template<> inline ALushort StoreSample(ALfloat val) -{ return StoreSample(val) + 32768; } -template<> inline ALubyte StoreSample(ALfloat val) -{ return StoreSample(val) + 128; } - -template -inline void StoreSampleArray(void *dst, const ALfloat *RESTRICT src, size_t dststep, - ALsizei samples) -{ - using SampleType = typename DevFmtTypeTraits::Type; - - SampleType *sdst = static_cast(dst); - for(ALsizei i{0};i < samples;i++) - sdst[i*dststep] = StoreSample(src[i]); -} - - -void StoreSamples(ALvoid *dst, const ALfloat *src, size_t dststep, DevFmtType dsttype, ALsizei samples) -{ -#define HANDLE_FMT(T) \ - case T: StoreSampleArray(dst, src, dststep, samples); break - switch(dsttype) - { - HANDLE_FMT(DevFmtByte); - HANDLE_FMT(DevFmtUByte); - HANDLE_FMT(DevFmtShort); - HANDLE_FMT(DevFmtUShort); - HANDLE_FMT(DevFmtInt); - HANDLE_FMT(DevFmtUInt); - HANDLE_FMT(DevFmtFloat); - } -#undef HANDLE_FMT -} - - -template -void Mono2Stereo(ALfloat *RESTRICT dst, const void *src, ALsizei frames) -{ - using SampleType = typename DevFmtTypeTraits::Type; - - const SampleType *ssrc = static_cast(src); - for(ALsizei i{0};i < frames;i++) - dst[i*2 + 1] = dst[i*2 + 0] = LoadSample(ssrc[i]) * 0.707106781187f; -} - -template -void Stereo2Mono(ALfloat *RESTRICT dst, const void *src, ALsizei frames) -{ - using SampleType = typename DevFmtTypeTraits::Type; - - const SampleType *ssrc = static_cast(src); - for(ALsizei i{0};i < frames;i++) - dst[i] = (LoadSample(ssrc[i*2 + 0])+LoadSample(ssrc[i*2 + 1])) * - 0.707106781187f; -} - -} // namespace - -SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, ALsizei numchans, - ALsizei srcRate, ALsizei dstRate, Resampler resampler) -{ - if(numchans <= 0 || srcRate <= 0 || dstRate <= 0) - return nullptr; - - void *ptr{al_calloc(16, SampleConverter::Sizeof(numchans))}; - SampleConverterPtr converter{new (ptr) SampleConverter{static_cast(numchans)}}; - converter->mSrcType = srcType; - converter->mDstType = dstType; - converter->mSrcTypeSize = BytesFromDevFmt(srcType); - converter->mDstTypeSize = BytesFromDevFmt(dstType); - - converter->mSrcPrepCount = 0; - converter->mFracOffset = 0; - - /* Have to set the mixer FPU mode since that's what the resampler code expects. */ - FPUCtl mixer_mode{}; - auto step = static_cast( - mind(static_cast(srcRate)/dstRate*FRACTIONONE + 0.5, MAX_PITCH*FRACTIONONE)); - converter->mIncrement = maxi(step, 1); - if(converter->mIncrement == FRACTIONONE) - converter->mResample = Resample_; - else - { - if(resampler == BSinc24Resampler) - BsincPrepare(converter->mIncrement, &converter->mState.bsinc, &bsinc24); - else if(resampler == BSinc12Resampler) - BsincPrepare(converter->mIncrement, &converter->mState.bsinc, &bsinc12); - converter->mResample = SelectResampler(resampler); - } - - return converter; -} - -ALsizei SampleConverter::availableOut(ALsizei srcframes) const -{ - ALint prepcount{mSrcPrepCount}; - if(prepcount < 0) - { - /* Negative prepcount means we need to skip that many input samples. */ - if(-prepcount >= srcframes) - return 0; - srcframes += prepcount; - prepcount = 0; - } - - if(srcframes < 1) - { - /* No output samples if there's no input samples. */ - return 0; - } - - if(prepcount < MAX_RESAMPLE_PADDING*2 && - MAX_RESAMPLE_PADDING*2 - prepcount >= srcframes) - { - /* Not enough input samples to generate an output sample. */ - return 0; - } - - auto DataSize64 = static_cast(prepcount); - DataSize64 += srcframes; - DataSize64 -= MAX_RESAMPLE_PADDING*2; - DataSize64 <<= FRACTIONBITS; - DataSize64 -= mFracOffset; - - /* If we have a full prep, we can generate at least one sample. */ - return static_cast(clampu64((DataSize64 + mIncrement-1)/mIncrement, 1, BUFFERSIZE)); -} - -ALsizei SampleConverter::convert(const ALvoid **src, ALsizei *srcframes, ALvoid *dst, ALsizei dstframes) -{ - const ALsizei SrcFrameSize{static_cast(mChan.size()) * mSrcTypeSize}; - const ALsizei DstFrameSize{static_cast(mChan.size()) * mDstTypeSize}; - const ALsizei increment{mIncrement}; - auto SamplesIn = static_cast(*src); - ALsizei NumSrcSamples{*srcframes}; - - FPUCtl mixer_mode{}; - ALsizei pos{0}; - while(pos < dstframes && NumSrcSamples > 0) - { - ALint prepcount{mSrcPrepCount}; - if(prepcount < 0) - { - /* Negative prepcount means we need to skip that many input samples. */ - if(-prepcount >= NumSrcSamples) - { - mSrcPrepCount = prepcount + NumSrcSamples; - NumSrcSamples = 0; - break; - } - SamplesIn += SrcFrameSize*-prepcount; - NumSrcSamples += prepcount; - mSrcPrepCount = 0; - continue; - } - ALint toread{mini(NumSrcSamples, BUFFERSIZE - MAX_RESAMPLE_PADDING*2)}; - - if(prepcount < MAX_RESAMPLE_PADDING*2 && - MAX_RESAMPLE_PADDING*2 - prepcount >= toread) - { - /* Not enough input samples to generate an output sample. Store - * what we're given for later. - */ - for(size_t chan{0u};chan < mChan.size();chan++) - LoadSamples(&mChan[chan].PrevSamples[prepcount], SamplesIn + mSrcTypeSize*chan, - mChan.size(), mSrcType, toread); - - mSrcPrepCount = prepcount + toread; - NumSrcSamples = 0; - break; - } - - ALfloat *RESTRICT SrcData{mSrcSamples}; - ALfloat *RESTRICT DstData{mDstSamples}; - ALsizei DataPosFrac{mFracOffset}; - auto DataSize64 = static_cast(prepcount); - DataSize64 += toread; - DataSize64 -= MAX_RESAMPLE_PADDING*2; - DataSize64 <<= FRACTIONBITS; - DataSize64 -= DataPosFrac; - - /* If we have a full prep, we can generate at least one sample. */ - auto DstSize = static_cast( - clampu64((DataSize64 + increment-1)/increment, 1, BUFFERSIZE)); - DstSize = mini(DstSize, dstframes-pos); - - for(size_t chan{0u};chan < mChan.size();chan++) - { - const ALbyte *SrcSamples = SamplesIn + mSrcTypeSize*chan; - ALbyte *DstSamples = static_cast(dst) + mDstTypeSize*chan; - - /* Load the previous samples into the source data first, then the - * new samples from the input buffer. - */ - std::copy_n(mChan[chan].PrevSamples, prepcount, SrcData); - LoadSamples(SrcData + prepcount, SrcSamples, mChan.size(), mSrcType, toread); - - /* Store as many prep samples for next time as possible, given the - * number of output samples being generated. - */ - ALsizei SrcDataEnd{(DstSize*increment + DataPosFrac)>>FRACTIONBITS}; - if(SrcDataEnd >= prepcount+toread) - std::fill(std::begin(mChan[chan].PrevSamples), - std::end(mChan[chan].PrevSamples), 0.0f); - else - { - size_t len = mini(MAX_RESAMPLE_PADDING*2, prepcount+toread-SrcDataEnd); - std::copy_n(SrcData+SrcDataEnd, len, mChan[chan].PrevSamples); - std::fill(std::begin(mChan[chan].PrevSamples)+len, - std::end(mChan[chan].PrevSamples), 0.0f); - } - - /* Now resample, and store the result in the output buffer. */ - const ALfloat *ResampledData{mResample(&mState, SrcData+MAX_RESAMPLE_PADDING, - DataPosFrac, increment, DstData, DstSize)}; - - StoreSamples(DstSamples, ResampledData, mChan.size(), mDstType, DstSize); - } - - /* Update the number of prep samples still available, as well as the - * fractional offset. - */ - DataPosFrac += increment*DstSize; - mSrcPrepCount = mini(prepcount + toread - (DataPosFrac>>FRACTIONBITS), - MAX_RESAMPLE_PADDING*2); - mFracOffset = DataPosFrac & FRACTIONMASK; - - /* Update the src and dst pointers in case there's still more to do. */ - SamplesIn += SrcFrameSize*(DataPosFrac>>FRACTIONBITS); - NumSrcSamples -= mini(NumSrcSamples, (DataPosFrac>>FRACTIONBITS)); - - dst = static_cast(dst) + DstFrameSize*DstSize; - pos += DstSize; - } - - *src = SamplesIn; - *srcframes = NumSrcSamples; - - return pos; -} - - -ChannelConverterPtr CreateChannelConverter(DevFmtType srcType, DevFmtChannels srcChans, DevFmtChannels dstChans) -{ - if(srcChans != dstChans && !((srcChans == DevFmtMono && dstChans == DevFmtStereo) || - (srcChans == DevFmtStereo && dstChans == DevFmtMono))) - return nullptr; - - return ChannelConverterPtr{new ChannelConverter{srcType, srcChans, dstChans}}; -} - -void ChannelConverter::convert(const ALvoid *src, ALfloat *dst, ALsizei frames) const -{ - if(mSrcChans == mDstChans) - { - LoadSamples(dst, src, 1u, mSrcType, frames*ChannelsFromDevFmt(mSrcChans, 0)); - return; - } - - if(mSrcChans == DevFmtStereo && mDstChans == DevFmtMono) - { - switch(mSrcType) - { -#define HANDLE_FMT(T) case T: Stereo2Mono(dst, src, frames); break - HANDLE_FMT(DevFmtByte); - HANDLE_FMT(DevFmtUByte); - HANDLE_FMT(DevFmtShort); - HANDLE_FMT(DevFmtUShort); - HANDLE_FMT(DevFmtInt); - HANDLE_FMT(DevFmtUInt); - HANDLE_FMT(DevFmtFloat); -#undef HANDLE_FMT - } - } - else /*if(mSrcChans == DevFmtMono && mDstChans == DevFmtStereo)*/ - { - switch(mSrcType) - { -#define HANDLE_FMT(T) case T: Mono2Stereo(dst, src, frames); break - HANDLE_FMT(DevFmtByte); - HANDLE_FMT(DevFmtUByte); - HANDLE_FMT(DevFmtShort); - HANDLE_FMT(DevFmtUShort); - HANDLE_FMT(DevFmtInt); - HANDLE_FMT(DevFmtUInt); - HANDLE_FMT(DevFmtFloat); -#undef HANDLE_FMT - } - } -} diff --git a/modules/openal-soft/Alc/converter.h b/modules/openal-soft/Alc/converter.h deleted file mode 100644 index 04d9483..0000000 --- a/modules/openal-soft/Alc/converter.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef CONVERTER_H -#define CONVERTER_H - -#include - -#include "alMain.h" -#include "alu.h" -#include "almalloc.h" - -struct SampleConverter { - DevFmtType mSrcType{}; - DevFmtType mDstType{}; - ALsizei mSrcTypeSize{}; - ALsizei mDstTypeSize{}; - - ALint mSrcPrepCount{}; - - ALsizei mFracOffset{}; - ALsizei mIncrement{}; - InterpState mState{}; - ResamplerFunc mResample{}; - - alignas(16) ALfloat mSrcSamples[BUFFERSIZE]{}; - alignas(16) ALfloat mDstSamples[BUFFERSIZE]{}; - - struct ChanSamples { - alignas(16) ALfloat PrevSamples[MAX_RESAMPLE_PADDING*2]; - }; - al::FlexArray mChan; - - SampleConverter(size_t numchans) : mChan{numchans} { } - SampleConverter(const SampleConverter&) = delete; - SampleConverter& operator=(const SampleConverter&) = delete; - - ALsizei convert(const ALvoid **src, ALsizei *srcframes, ALvoid *dst, ALsizei dstframes); - ALsizei availableOut(ALsizei srcframes) const; - - static constexpr size_t Sizeof(size_t length) noexcept - { - return maxz(sizeof(SampleConverter), - al::FlexArray::Sizeof(length, offsetof(SampleConverter, mChan))); - } - - DEF_PLACE_NEWDEL() -}; -using SampleConverterPtr = std::unique_ptr; - -SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, ALsizei numchans, - ALsizei srcRate, ALsizei dstRate, Resampler resampler); - - -struct ChannelConverter { - DevFmtType mSrcType; - DevFmtChannels mSrcChans; - DevFmtChannels mDstChans; - - ChannelConverter(DevFmtType srctype, DevFmtChannels srcchans, DevFmtChannels dstchans) - : mSrcType(srctype), mSrcChans(srcchans), mDstChans(dstchans) - { } - - void convert(const ALvoid *src, ALfloat *dst, ALsizei frames) const; - - DEF_NEWDEL(ChannelConverter) -}; -using ChannelConverterPtr = std::unique_ptr; - -ChannelConverterPtr CreateChannelConverter(DevFmtType srcType, DevFmtChannels srcChans, - DevFmtChannels dstChans); - -#endif /* CONVERTER_H */ diff --git a/modules/openal-soft/Alc/cpu_caps.h b/modules/openal-soft/Alc/cpu_caps.h deleted file mode 100644 index 1d867f3..0000000 --- a/modules/openal-soft/Alc/cpu_caps.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef CPU_CAPS_H -#define CPU_CAPS_H - -#ifdef __cplusplus -extern "C" { -#endif - -extern int CPUCapFlags; -enum { - CPU_CAP_SSE = 1<<0, - CPU_CAP_SSE2 = 1<<1, - CPU_CAP_SSE3 = 1<<2, - CPU_CAP_SSE4_1 = 1<<3, - CPU_CAP_NEON = 1<<4, -}; - -void FillCPUCaps(int capfilter); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif /* CPU_CAPS_H */ diff --git a/modules/openal-soft/Alc/device.cpp b/modules/openal-soft/Alc/device.cpp new file mode 100644 index 0000000..e06c0d7 --- /dev/null +++ b/modules/openal-soft/Alc/device.cpp @@ -0,0 +1,90 @@ + +#include "config.h" + +#include "device.h" + +#include +#include + +#include "albit.h" +#include "alconfig.h" +#include "backends/base.h" +#include "core/bformatdec.h" +#include "core/bs2b.h" +#include "core/front_stablizer.h" +#include "core/hrtf.h" +#include "core/logging.h" +#include "core/mastering.h" +#include "core/uhjfilter.h" + + +namespace { + +using voidp = void*; + +} // namespace + + +ALCdevice::ALCdevice(DeviceType type) : DeviceBase{type} +{ } + +ALCdevice::~ALCdevice() +{ + TRACE("Freeing device %p\n", voidp{this}); + + Backend = nullptr; + + size_t count{std::accumulate(BufferList.cbegin(), BufferList.cend(), size_t{0u}, + [](size_t cur, const BufferSubList &sublist) noexcept -> size_t + { return cur + static_cast(al::popcount(~sublist.FreeMask)); })}; + if(count > 0) + WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s"); + + count = std::accumulate(EffectList.cbegin(), EffectList.cend(), size_t{0u}, + [](size_t cur, const EffectSubList &sublist) noexcept -> size_t + { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); + if(count > 0) + WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s"); + + count = std::accumulate(FilterList.cbegin(), FilterList.cend(), size_t{0u}, + [](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"); +} + +void ALCdevice::enumerateHrtfs() +{ + mHrtfList = EnumerateHrtf(configValue(nullptr, "hrtf-paths")); + if(auto defhrtfopt = configValue(nullptr, "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()); + else if(iter != mHrtfList.begin()) + std::rotate(mHrtfList.begin(), iter, iter+1); + } +} + +auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1 +{ + if(mContexts.load(std::memory_order_relaxed)->empty()) + return OutputMode1::Any; + + switch(FmtChans) + { + case DevFmtMono: return OutputMode1::Mono; + case DevFmtStereo: + if(mHrtf) + return OutputMode1::Hrtf; + else if(mUhjEncoder) + return OutputMode1::Uhj2; + return OutputMode1::StereoBasic; + case DevFmtQuad: return OutputMode1::Quad; + case DevFmtX51: return OutputMode1::X51; + case DevFmtX61: return OutputMode1::X61; + case DevFmtX71: return OutputMode1::X71; + case DevFmtAmbi3D: break; + } + return OutputMode1::Any; +} diff --git a/modules/openal-soft/Alc/device.h b/modules/openal-soft/Alc/device.h new file mode 100644 index 0000000..04931a5 --- /dev/null +++ b/modules/openal-soft/Alc/device.h @@ -0,0 +1,165 @@ +#ifndef ALC_DEVICE_H +#define ALC_DEVICE_H + +#include +#include +#include +#include +#include +#include + +#include "AL/alc.h" +#include "AL/alext.h" + +#include "alconfig.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "core/device.h" +#include "inprogext.h" +#include "intrusive_ptr.h" +#include "vector.h" + +#ifdef ALSOFT_EAX +#include "al/eax_x_ram.h" +#endif // ALSOFT_EAX + +struct ALbuffer; +struct ALeffect; +struct ALfilter; +struct BackendBase; + +using uint = unsigned int; + + +struct BufferSubList { + uint64_t FreeMask{~0_u64}; + ALbuffer *Buffers{nullptr}; /* 64 */ + + BufferSubList() noexcept = default; + BufferSubList(const BufferSubList&) = delete; + BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers} + { rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; } + ~BufferSubList(); + + BufferSubList& operator=(const BufferSubList&) = delete; + BufferSubList& operator=(BufferSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; } +}; + +struct EffectSubList { + uint64_t FreeMask{~0_u64}; + ALeffect *Effects{nullptr}; /* 64 */ + + EffectSubList() noexcept = default; + EffectSubList(const EffectSubList&) = delete; + EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects} + { rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; } + ~EffectSubList(); + + EffectSubList& operator=(const EffectSubList&) = delete; + EffectSubList& operator=(EffectSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; } +}; + +struct FilterSubList { + uint64_t FreeMask{~0_u64}; + ALfilter *Filters{nullptr}; /* 64 */ + + FilterSubList() noexcept = default; + FilterSubList(const FilterSubList&) = delete; + FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters} + { rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; } + ~FilterSubList(); + + FilterSubList& operator=(const FilterSubList&) = delete; + FilterSubList& operator=(FilterSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; } +}; + + +struct ALCdevice : public al::intrusive_ref, DeviceBase { + /* This lock protects the device state (format, update size, etc) from + * being from being changed in multiple threads, or being accessed while + * being changed. It's also used to serialize calls to the backend. + */ + std::mutex StateLock; + std::unique_ptr Backend; + + ALCuint NumMonoSources{}; + ALCuint NumStereoSources{}; + + // Maximum number of sources that can be created + uint SourcesMax{}; + // Maximum number of slots that can be created + uint AuxiliaryEffectSlotMax{}; + + std::string mHrtfName; + al::vector mHrtfList; + ALCenum mHrtfStatus{ALC_FALSE}; + + enum class OutputMode1 : ALCenum { + Any = ALC_ANY_SOFT, + Mono = ALC_MONO_SOFT, + Stereo = ALC_STEREO_SOFT, + StereoBasic = ALC_STEREO_BASIC_SOFT, + Uhj2 = ALC_STEREO_UHJ_SOFT, + Hrtf = ALC_STEREO_HRTF_SOFT, + Quad = ALC_QUAD_SOFT, + X51 = ALC_SURROUND_5_1_SOFT, + X61 = ALC_SURROUND_6_1_SOFT, + X71 = ALC_SURROUND_7_1_SOFT + }; + OutputMode1 getOutputMode1() const noexcept; + + using OutputMode = OutputMode1; + + std::atomic LastError{ALC_NO_ERROR}; + + // Map of Buffers for this device + std::mutex BufferLock; + al::vector BufferList; + + // Map of Effects for this device + std::mutex EffectLock; + al::vector EffectList; + + // Map of Filters for this device + std::mutex FilterLock; + al::vector FilterList; + +#ifdef ALSOFT_EAX + ALuint eax_x_ram_free_size{eax_x_ram_max_size}; +#endif // ALSOFT_EAX + + + ALCdevice(DeviceType type); + ~ALCdevice(); + + void enumerateHrtfs(); + + bool getConfigValueBool(const char *block, const char *key, bool def) + { return GetConfigValueBool(DeviceName.c_str(), block, key, def); } + + template + al::optional configValue(const char *block, const char *key) = delete; + + DEF_NEWDEL(ALCdevice) +}; + +template<> +inline al::optional ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueStr(DeviceName.c_str(), block, key); } +template<> +inline al::optional ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueInt(DeviceName.c_str(), block, key); } +template<> +inline al::optional ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueUInt(DeviceName.c_str(), block, key); } +template<> +inline al::optional ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueFloat(DeviceName.c_str(), block, key); } +template<> +inline al::optional ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueBool(DeviceName.c_str(), block, key); } + +#endif diff --git a/modules/openal-soft/Alc/effects/autowah.cpp b/modules/openal-soft/Alc/effects/autowah.cpp index dc03dee..50c4a5a 100644 --- a/modules/openal-soft/Alc/effects/autowah.cpp +++ b/modules/openal-soft/Alc/effects/autowah.cpp @@ -20,64 +20,75 @@ #include "config.h" -#include -#include - #include +#include +#include +#include +#include + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" -#include "alMain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" -#include "filters/biquad.h" -#include "vecmat.h" namespace { -#define MIN_FREQ 20.0f -#define MAX_FREQ 2500.0f -#define Q_FACTOR 5.0f +constexpr float GainScale{31621.0f}; +constexpr float MinFreq{20.0f}; +constexpr float MaxFreq{2500.0f}; +constexpr float QFactor{5.0f}; -struct ALautowahState final : public EffectState { +struct AutowahState final : public EffectState { /* Effect parameters */ - ALfloat mAttackRate; - ALfloat mReleaseRate; - ALfloat mResonanceGain; - ALfloat mPeakGain; - ALfloat mFreqMinNorm; - ALfloat mBandwidthNorm; - ALfloat mEnvDelay; + float mAttackRate; + float mReleaseRate; + float mResonanceGain; + float mPeakGain; + float mFreqMinNorm; + float mBandwidthNorm; + float mEnvDelay; /* Filter components derived from the envelope. */ struct { - ALfloat cos_w0; - ALfloat alpha; - } mEnv[BUFFERSIZE]; + float cos_w0; + float alpha; + } mEnv[BufferLineSize]; struct { /* Effect filters' history. */ struct { - ALfloat z1, z2; + float z1, z2; } Filter; /* Effect gains for each output channel */ - ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]; - ALfloat TargetGains[MAX_OUTPUT_CHANNELS]; - } mChans[MAX_AMBI_CHANNELS]; + float CurrentGains[MAX_OUTPUT_CHANNELS]; + float TargetGains[MAX_OUTPUT_CHANNELS]; + } mChans[MaxAmbiChannels]; /* Effects buffers */ - alignas(16) ALfloat mBufferOut[BUFFERSIZE]; + alignas(16) float mBufferOut[BufferLineSize]; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override; + void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) override; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) override; - DEF_NEWDEL(ALautowahState) + DEF_NEWDEL(AutowahState) }; -ALboolean ALautowahState::deviceUpdate(const ALCdevice *UNUSED(device)) +void AutowahState::deviceUpdate(const DeviceBase*, const Buffer&) { /* (Re-)initializing parameters and clear the buffers. */ @@ -101,65 +112,61 @@ ALboolean ALautowahState::deviceUpdate(const ALCdevice *UNUSED(device)) chan.Filter.z1 = 0.0f; chan.Filter.z2 = 0.0f; } - - return AL_TRUE; } -void ALautowahState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void AutowahState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->Device}; + const DeviceBase *device{context->mDevice}; + const auto frequency = static_cast(device->Frequency); - const ALfloat ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)}; + const float ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)}; - mAttackRate = expf(-1.0f / (props->Autowah.AttackTime*device->Frequency)); - mReleaseRate = expf(-1.0f / (ReleaseTime*device->Frequency)); + mAttackRate = std::exp(-1.0f / (props->Autowah.AttackTime*frequency)); + mReleaseRate = std::exp(-1.0f / (ReleaseTime*frequency)); /* 0-20dB Resonance Peak gain */ mResonanceGain = std::sqrt(std::log10(props->Autowah.Resonance)*10.0f / 3.0f); - mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain/AL_AUTOWAH_MAX_PEAK_GAIN); - mFreqMinNorm = MIN_FREQ / device->Frequency; - mBandwidthNorm = (MAX_FREQ-MIN_FREQ) / device->Frequency; - - mOutBuffer = target.Main->Buffer; - mOutChannels = target.Main->NumChannels; - for(ALsizei i{0};i < slot->Wet.NumChannels;++i) - { - auto coeffs = GetAmbiIdentityRow(i); - ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains); - } + mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain / GainScale); + mFreqMinNorm = MinFreq / frequency; + mBandwidthNorm = (MaxFreq-MinFreq) / frequency; + + mOutTarget = target.Main->Buffer; + auto set_gains = [slot,target](auto &chan, al::span coeffs) + { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); }; + SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains); } -void ALautowahState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) +void AutowahState::process(const size_t samplesToDo, + const al::span samplesIn, const al::span samplesOut) { - const ALfloat attack_rate = mAttackRate; - const ALfloat release_rate = mReleaseRate; - const ALfloat res_gain = mResonanceGain; - const ALfloat peak_gain = mPeakGain; - const ALfloat freq_min = mFreqMinNorm; - const ALfloat bandwidth = mBandwidthNorm; - ALfloat env_delay; - ALsizei c, i; - - env_delay = mEnvDelay; - for(i = 0;i < samplesToDo;i++) + const float attack_rate{mAttackRate}; + const float release_rate{mReleaseRate}; + const float res_gain{mResonanceGain}; + const float peak_gain{mPeakGain}; + const float freq_min{mFreqMinNorm}; + const float bandwidth{mBandwidthNorm}; + + float env_delay{mEnvDelay}; + for(size_t i{0u};i < samplesToDo;i++) { - ALfloat w0, sample, a; + float w0, sample, a; /* Envelope follower described on the book: Audio Effects, Theory, * Implementation and Application. */ sample = peak_gain * std::fabs(samplesIn[0][i]); a = (sample > env_delay) ? attack_rate : release_rate; - env_delay = lerp(sample, env_delay, a); + env_delay = lerpf(sample, env_delay, a); /* Calculate the cos and alpha components for this sample's filter. */ - w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * al::MathDefs::Tau(); - mEnv[i].cos_w0 = cosf(w0); - mEnv[i].alpha = sinf(w0)/(2.0f * Q_FACTOR); + w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * (al::numbers::pi_v*2.0f); + mEnv[i].cos_w0 = std::cos(w0); + mEnv[i].alpha = std::sin(w0)/(2.0f * QFactor); } mEnvDelay = env_delay; - ASSUME(numInput > 0); - for(c = 0;c < numInput;++c) + auto chandata = std::addressof(mChans[0]); + for(const auto &insamples : samplesIn) { /* This effectively inlines BiquadFilter_setParams for a peaking * filter and BiquadFilter_processC. The alpha and cosine components @@ -167,15 +174,15 @@ void ALautowahState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT sampl * envelope. Because the filter changes for each sample, the * coefficients are transient and don't need to be held. */ - ALfloat z1 = mChans[c].Filter.z1; - ALfloat z2 = mChans[c].Filter.z2; + float z1{chandata->Filter.z1}; + float z2{chandata->Filter.z2}; - for(i = 0;i < samplesToDo;i++) + for(size_t i{0u};i < samplesToDo;i++) { - const ALfloat alpha = mEnv[i].alpha; - const ALfloat cos_w0 = mEnv[i].cos_w0; - ALfloat input, output; - ALfloat a[3], b[3]; + const float alpha{mEnv[i].alpha}; + const float cos_w0{mEnv[i].cos_w0}; + float input, output; + float a[3], b[3]; b[0] = 1.0f + alpha*res_gain; b[1] = -2.0f * cos_w0; @@ -184,114 +191,28 @@ void ALautowahState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT sampl a[1] = -2.0f * cos_w0; a[2] = 1.0f - alpha/res_gain; - input = samplesIn[c][i]; + input = insamples[i]; output = input*(b[0]/a[0]) + z1; z1 = input*(b[1]/a[0]) - output*(a[1]/a[0]) + z2; z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]); mBufferOut[i] = output; } - mChans[c].Filter.z1 = z1; - mChans[c].Filter.z2 = z2; + chandata->Filter.z1 = z1; + chandata->Filter.z2 = z2; /* Now, mix the processed sound data to the output. */ - MixSamples(mBufferOut, numOutput, samplesOut, mChans[c].CurrentGains, - mChans[c].TargetGains, samplesToDo, 0, samplesToDo); + MixSamples({mBufferOut, samplesToDo}, samplesOut, chandata->CurrentGains, + chandata->TargetGains, samplesToDo, 0); + ++chandata; } } -void ALautowah_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_AUTOWAH_ATTACK_TIME: - if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah attack time out of range"); - props->Autowah.AttackTime = val; - break; - - case AL_AUTOWAH_RELEASE_TIME: - if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah release time out of range"); - props->Autowah.ReleaseTime = val; - break; - - case AL_AUTOWAH_RESONANCE: - if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah resonance out of range"); - props->Autowah.Resonance = val; - break; - - case AL_AUTOWAH_PEAK_GAIN: - if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah peak gain out of range"); - props->Autowah.PeakGain = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param); - } -} -void ALautowah_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ ALautowah_setParamf(props, context, param, vals[0]); } - -void ALautowah_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); } -void ALautowah_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); } - -void ALautowah_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_AUTOWAH_ATTACK_TIME: - *val = props->Autowah.AttackTime; - break; - - case AL_AUTOWAH_RELEASE_TIME: - *val = props->Autowah.ReleaseTime; - break; - - case AL_AUTOWAH_RESONANCE: - *val = props->Autowah.Resonance; - break; - - case AL_AUTOWAH_PEAK_GAIN: - *val = props->Autowah.PeakGain; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param); - } - -} -void ALautowah_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ ALautowah_getParamf(props, context, param, vals); } - -void ALautowah_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); } -void ALautowah_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); } - -DEFINE_ALEFFECT_VTABLE(ALautowah); - - struct AutowahStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ALautowahState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &ALautowah_vtable; } + al::intrusive_ptr create() override + { return al::intrusive_ptr{new AutowahState{}}; } }; -EffectProps AutowahStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME; - props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME; - props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE; - props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN; - return props; -} - } // namespace EffectStateFactory *AutowahStateFactory_getFactory() diff --git a/modules/openal-soft/Alc/effects/base.h b/modules/openal-soft/Alc/effects/base.h index ba1fbf9..9569585 100644 --- a/modules/openal-soft/Alc/effects/base.h +++ b/modules/openal-soft/Alc/effects/base.h @@ -1,170 +1,7 @@ #ifndef EFFECTS_BASE_H #define EFFECTS_BASE_H -#include "alMain.h" - -#include "almalloc.h" -#include "atomic.h" - - -struct ALeffectslot; - - -union EffectProps { - struct { - // Shared Reverb Properties - ALfloat Density; - ALfloat Diffusion; - ALfloat Gain; - ALfloat GainHF; - ALfloat DecayTime; - ALfloat DecayHFRatio; - ALfloat ReflectionsGain; - ALfloat ReflectionsDelay; - ALfloat LateReverbGain; - ALfloat LateReverbDelay; - ALfloat AirAbsorptionGainHF; - ALfloat RoomRolloffFactor; - ALboolean DecayHFLimit; - - // Additional EAX Reverb Properties - ALfloat GainLF; - ALfloat DecayLFRatio; - ALfloat ReflectionsPan[3]; - ALfloat LateReverbPan[3]; - ALfloat EchoTime; - ALfloat EchoDepth; - ALfloat ModulationTime; - ALfloat ModulationDepth; - ALfloat HFReference; - ALfloat LFReference; - } Reverb; - - struct { - ALfloat AttackTime; - ALfloat ReleaseTime; - ALfloat Resonance; - ALfloat PeakGain; - } Autowah; - - struct { - ALint Waveform; - ALint Phase; - ALfloat Rate; - ALfloat Depth; - ALfloat Feedback; - ALfloat Delay; - } Chorus; /* Also Flanger */ - - struct { - ALboolean OnOff; - } Compressor; - - struct { - ALfloat Edge; - ALfloat Gain; - ALfloat LowpassCutoff; - ALfloat EQCenter; - ALfloat EQBandwidth; - } Distortion; - - struct { - ALfloat Delay; - ALfloat LRDelay; - - ALfloat Damping; - ALfloat Feedback; - - ALfloat Spread; - } Echo; - - struct { - ALfloat LowCutoff; - ALfloat LowGain; - ALfloat Mid1Center; - ALfloat Mid1Gain; - ALfloat Mid1Width; - ALfloat Mid2Center; - ALfloat Mid2Gain; - ALfloat Mid2Width; - ALfloat HighCutoff; - ALfloat HighGain; - } Equalizer; - - struct { - ALfloat Frequency; - ALint LeftDirection; - ALint RightDirection; - } Fshifter; - - struct { - ALfloat Frequency; - ALfloat HighPassCutoff; - ALint Waveform; - } Modulator; - - struct { - ALint CoarseTune; - ALint FineTune; - } Pshifter; - - struct { - ALfloat Gain; - } Dedicated; -}; - - -struct EffectVtable { - void (*const setParami)(EffectProps *props, ALCcontext *context, ALenum param, ALint val); - void (*const setParamiv)(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals); - void (*const setParamf)(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val); - void (*const setParamfv)(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals); - - void (*const getParami)(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val); - void (*const getParamiv)(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals); - void (*const getParamf)(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val); - void (*const getParamfv)(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals); -}; - -#define DEFINE_ALEFFECT_VTABLE(T) \ -const EffectVtable T##_vtable = { \ - T##_setParami, T##_setParamiv, \ - T##_setParamf, T##_setParamfv, \ - T##_getParami, T##_getParamiv, \ - T##_getParamf, T##_getParamfv, \ -} - - -struct EffectTarget { - MixParams *Main; - RealMixParams *RealOut; -}; - -struct EffectState { - RefCount mRef{1u}; - - ALfloat (*mOutBuffer)[BUFFERSIZE]{nullptr}; - ALsizei mOutChannels{0}; - - - virtual ~EffectState() = default; - - virtual ALboolean deviceUpdate(const ALCdevice *device) = 0; - virtual void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) = 0; - virtual void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) = 0; - - void IncRef() noexcept; - void DecRef() noexcept; -}; - - -struct EffectStateFactory { - virtual ~EffectStateFactory() { } - - virtual EffectState *create() = 0; - virtual EffectProps getDefaultProps() const noexcept = 0; - virtual const EffectVtable *getEffectVtable() const noexcept = 0; -}; +#include "core/effects/base.h" EffectStateFactory *NullStateFactory_getFactory(void); @@ -180,8 +17,10 @@ EffectStateFactory *FlangerStateFactory_getFactory(void); EffectStateFactory *FshifterStateFactory_getFactory(void); EffectStateFactory *ModulatorStateFactory_getFactory(void); EffectStateFactory *PshifterStateFactory_getFactory(void); +EffectStateFactory* VmorpherStateFactory_getFactory(void); EffectStateFactory *DedicatedStateFactory_getFactory(void); +EffectStateFactory *ConvolutionStateFactory_getFactory(void); #endif /* EFFECTS_BASE_H */ diff --git a/modules/openal-soft/Alc/effects/chorus.cpp b/modules/openal-soft/Alc/effects/chorus.cpp index 74b46a1..99a2a68 100644 --- a/modules/openal-soft/Alc/effects/chorus.cpp +++ b/modules/openal-soft/Alc/effects/chorus.cpp @@ -20,96 +20,77 @@ #include "config.h" -#include - -#include #include - -#include "alMain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" -#include "filters/biquad.h" +#include +#include +#include +#include + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "core/resampler_limits.h" +#include "intrusive_ptr.h" +#include "opthelpers.h" #include "vector.h" namespace { -static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch"); -static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch"); - -enum class WaveForm { - Sinusoid, - Triangle -}; - -void GetTriangleDelays(ALint *delays, ALsizei offset, ALsizei lfo_range, ALfloat lfo_scale, - ALfloat depth, ALsizei delay, ALsizei todo) -{ - std::generate_n(delays, todo, - [&offset,lfo_range,lfo_scale,depth,delay]() -> ALint - { - offset = (offset+1)%lfo_range; - return fastf2i((1.0f - std::abs(2.0f - lfo_scale*offset)) * depth) + delay; - } - ); -} +using uint = unsigned int; -void GetSinusoidDelays(ALint *delays, ALsizei offset, ALsizei lfo_range, ALfloat lfo_scale, - ALfloat depth, ALsizei delay, ALsizei todo) -{ - std::generate_n(delays, todo, - [&offset,lfo_range,lfo_scale,depth,delay]() -> ALint - { - offset = (offset+1)%lfo_range; - return fastf2i(std::sin(lfo_scale*offset) * depth) + delay; - } - ); -} +#define MAX_UPDATE_SAMPLES 256 struct ChorusState final : public EffectState { - al::vector mSampleBuffer; - ALsizei mOffset{0}; + al::vector mSampleBuffer; + uint mOffset{0}; - ALsizei mLfoOffset{0}; - ALsizei mLfoRange{1}; - ALfloat mLfoScale{0.0f}; - ALint mLfoDisp{0}; + uint mLfoOffset{0}; + uint mLfoRange{1}; + float mLfoScale{0.0f}; + uint mLfoDisp{0}; /* Gains for left and right sides */ struct { - ALfloat Current[MAX_OUTPUT_CHANNELS]{}; - ALfloat Target[MAX_OUTPUT_CHANNELS]{}; + float Current[MAX_OUTPUT_CHANNELS]{}; + float Target[MAX_OUTPUT_CHANNELS]{}; } mGains[2]; /* effect parameters */ - WaveForm mWaveform{}; - ALint mDelay{0}; - ALfloat mDepth{0.0f}; - ALfloat mFeedback{0.0f}; + ChorusWaveform mWaveform{}; + int mDelay{0}; + float mDepth{0.0f}; + float mFeedback{0.0f}; + void getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo); + void getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo); - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override; + void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) override; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) override; DEF_NEWDEL(ChorusState) }; -ALboolean ChorusState::deviceUpdate(const ALCdevice *Device) +void ChorusState::deviceUpdate(const DeviceBase *Device, const Buffer&) { - const ALfloat max_delay = maxf(AL_CHORUS_MAX_DELAY, AL_FLANGER_MAX_DELAY); - size_t maxlen; - - maxlen = NextPowerOf2(float2int(max_delay*2.0f*Device->Frequency) + 1u); - if(maxlen <= 0) return AL_FALSE; + constexpr float max_delay{maxf(ChorusMaxDelay, FlangerMaxDelay)}; + const auto frequency = static_cast(Device->Frequency); + const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)}; if(maxlen != mSampleBuffer.size()) - { - mSampleBuffer.resize(maxlen); - mSampleBuffer.shrink_to_fit(); - } + al::vector(maxlen).swap(mSampleBuffer); std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f); for(auto &e : mGains) @@ -117,45 +98,36 @@ ALboolean ChorusState::deviceUpdate(const ALCdevice *Device) std::fill(std::begin(e.Current), std::end(e.Current), 0.0f); std::fill(std::begin(e.Target), std::end(e.Target), 0.0f); } - - return AL_TRUE; } -void ChorusState::update(const ALCcontext *Context, const ALeffectslot *Slot, const EffectProps *props, const EffectTarget target) +void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot, + const EffectProps *props, const EffectTarget target) { - static constexpr ALsizei mindelay = MAX_RESAMPLE_PADDING << FRACTIONBITS; - - switch(props->Chorus.Waveform) - { - case AL_CHORUS_WAVEFORM_TRIANGLE: - mWaveform = WaveForm::Triangle; - break; - case AL_CHORUS_WAVEFORM_SINUSOID: - mWaveform = WaveForm::Sinusoid; - break; - } + constexpr int mindelay{(MaxResamplerPadding>>1) << MixerFracBits}; /* The LFO depth is scaled to be relative to the sample delay. Clamp the * delay and depth to allow enough padding for resampling. */ - const ALCdevice *device{Context->Device}; - const auto frequency = static_cast(device->Frequency); - mDelay = maxi(float2int(props->Chorus.Delay*frequency*FRACTIONONE + 0.5f), mindelay); - mDepth = minf(props->Chorus.Depth * mDelay, static_cast(mDelay - mindelay)); + const DeviceBase *device{Context->mDevice}; + const auto frequency = static_cast(device->Frequency); + + mWaveform = props->Chorus.Waveform; + + mDelay = maxi(float2int(props->Chorus.Delay*frequency*MixerFracOne + 0.5f), mindelay); + mDepth = minf(props->Chorus.Depth * static_cast(mDelay), + static_cast(mDelay - mindelay)); mFeedback = props->Chorus.Feedback; /* Gains for left and right sides */ - ALfloat coeffs[2][MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f, coeffs[0]); - CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f, coeffs[1]); + const auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f); + const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f); - mOutBuffer = target.Main->Buffer; - mOutChannels = target.Main->NumChannels; - ComputePanGains(target.Main, coeffs[0], Slot->Params.Gain, mGains[0].Target); - ComputePanGains(target.Main, coeffs[1], Slot->Params.Gain, mGains[1].Target); + mOutTarget = target.Main->Buffer; + ComputePanGains(target.Main, lcoeffs.data(), Slot->Gain, mGains[0].Target); + ComputePanGains(target.Main, rcoeffs.data(), Slot->Gain, mGains[1].Target); - ALfloat rate{props->Chorus.Rate}; + float rate{props->Chorus.Rate}; if(!(rate > 0.0f)) { mLfoOffset = 0; @@ -168,344 +140,144 @@ void ChorusState::update(const ALCcontext *Context, const ALeffectslot *Slot, co /* Calculate LFO coefficient (number of samples per cycle). Limit the * max range to avoid overflow when calculating the displacement. */ - ALsizei lfo_range = float2int(minf(frequency/rate + 0.5f, static_cast(INT_MAX/360 - 180))); + uint lfo_range{float2uint(minf(frequency/rate + 0.5f, float{INT_MAX/360 - 180}))}; - mLfoOffset = float2int(static_cast(mLfoOffset)/mLfoRange*lfo_range + 0.5f) % lfo_range; + mLfoOffset = mLfoOffset * lfo_range / mLfoRange; mLfoRange = lfo_range; switch(mWaveform) { - case WaveForm::Triangle: - mLfoScale = 4.0f / mLfoRange; - break; - case WaveForm::Sinusoid: - mLfoScale = al::MathDefs::Tau() / mLfoRange; - break; + case ChorusWaveform::Triangle: + mLfoScale = 4.0f / static_cast(mLfoRange); + break; + case ChorusWaveform::Sinusoid: + mLfoScale = al::numbers::pi_v*2.0f / static_cast(mLfoRange); + break; } /* Calculate lfo phase displacement */ - ALint phase{props->Chorus.Phase}; + int phase{props->Chorus.Phase}; if(phase < 0) phase = 360 + phase; - mLfoDisp = (mLfoRange*phase + 180) / 360; + mLfoDisp = (mLfoRange*static_cast(phase) + 180) / 360; } } -void ChorusState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei /*numInput*/, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) -{ - const auto bufmask = static_cast(mSampleBuffer.size()-1); - const ALfloat feedback{mFeedback}; - const ALsizei avgdelay{(mDelay + (FRACTIONONE>>1)) >> FRACTIONBITS}; - ALfloat *RESTRICT delaybuf{mSampleBuffer.data()}; - ALsizei offset{mOffset}; - ALsizei i, c; - ALsizei base; - - for(base = 0;base < samplesToDo;) - { - const ALsizei todo = mini(256, samplesToDo-base); - ALint moddelays[2][256]; - alignas(16) ALfloat temps[2][256]; - - if(mWaveform == WaveForm::Sinusoid) - { - GetSinusoidDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay, - todo); - GetSinusoidDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale, - mDepth, mDelay, todo); - } - else /*if(mWaveform == WaveForm::Triangle)*/ - { - GetTriangleDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay, - todo); - GetTriangleDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale, - mDepth, mDelay, todo); - } - mLfoOffset = (mLfoOffset+todo) % mLfoRange; - for(i = 0;i < todo;i++) - { - // Feed the buffer's input first (necessary for delays < 1). - delaybuf[offset&bufmask] = samplesIn[0][base+i]; - - // Tap for the left output. - ALint delay{offset - (moddelays[0][i]>>FRACTIONBITS)}; - ALfloat mu{(moddelays[0][i]&FRACTIONMASK) * (1.0f/FRACTIONONE)}; - temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], - delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], - mu); - - // Tap for the right output. - delay = offset - (moddelays[1][i]>>FRACTIONBITS); - mu = (moddelays[1][i]&FRACTIONMASK) * (1.0f/FRACTIONONE); - temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], - delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], - mu); - - // Accumulate feedback from the average delay of the taps. - delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback; - offset++; - } - - for(c = 0;c < 2;c++) - MixSamples(temps[c], numOutput, samplesOut, mGains[c].Current, mGains[c].Target, - samplesToDo-base, base, todo); - - base += todo; - } - - mOffset = offset; -} - - -void Chorus_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) +void ChorusState::getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo) { - switch(param) - { - case AL_CHORUS_WAVEFORM: - if(!(val >= AL_CHORUS_MIN_WAVEFORM && val <= AL_CHORUS_MAX_WAVEFORM)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid chorus waveform"); - props->Chorus.Waveform = val; - break; + const uint lfo_range{mLfoRange}; + const float lfo_scale{mLfoScale}; + const float depth{mDepth}; + const int delay{mDelay}; - case AL_CHORUS_PHASE: - if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus phase out of range"); - props->Chorus.Phase = val; - break; + ASSUME(lfo_range > 0); + ASSUME(todo > 0); - default: - alSetError(context, AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param); - } -} -void Chorus_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Chorus_setParami(props, context, param, vals[0]); } -void Chorus_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) + uint offset{mLfoOffset}; + auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> uint { - case AL_CHORUS_RATE: - if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus rate out of range"); - props->Chorus.Rate = val; - break; - - case AL_CHORUS_DEPTH: - if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus depth out of range"); - props->Chorus.Depth = val; - break; - - case AL_CHORUS_FEEDBACK: - if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus feedback out of range"); - props->Chorus.Feedback = val; - break; + offset = (offset+1)%lfo_range; + const float offset_norm{static_cast(offset) * lfo_scale}; + return static_cast(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay); + }; + std::generate_n(delays[0], todo, gen_lfo); - case AL_CHORUS_DELAY: - if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus delay out of range"); - props->Chorus.Delay = val; - break; + offset = (mLfoOffset+mLfoDisp) % lfo_range; + std::generate_n(delays[1], todo, gen_lfo); - default: - alSetError(context, AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param); - } + mLfoOffset = static_cast(mLfoOffset+todo) % lfo_range; } -void Chorus_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Chorus_setParamf(props, context, param, vals[0]); } -void Chorus_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) +void ChorusState::getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo) { - switch(param) - { - case AL_CHORUS_WAVEFORM: - *val = props->Chorus.Waveform; - break; + const uint lfo_range{mLfoRange}; + const float lfo_scale{mLfoScale}; + const float depth{mDepth}; + const int delay{mDelay}; - case AL_CHORUS_PHASE: - *val = props->Chorus.Phase; - break; + ASSUME(lfo_range > 0); + ASSUME(todo > 0); - default: - alSetError(context, AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param); - } -} -void Chorus_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Chorus_getParami(props, context, param, vals); } -void Chorus_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) + uint offset{mLfoOffset}; + auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> uint { - case AL_CHORUS_RATE: - *val = props->Chorus.Rate; - break; - - case AL_CHORUS_DEPTH: - *val = props->Chorus.Depth; - break; + offset = (offset+1)%lfo_range; + const float offset_norm{static_cast(offset) * lfo_scale}; + return static_cast(fastf2i(std::sin(offset_norm)*depth) + delay); + }; + std::generate_n(delays[0], todo, gen_lfo); - case AL_CHORUS_FEEDBACK: - *val = props->Chorus.Feedback; - break; + offset = (mLfoOffset+mLfoDisp) % lfo_range; + std::generate_n(delays[1], todo, gen_lfo); - case AL_CHORUS_DELAY: - *val = props->Chorus.Delay; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param); - } + mLfoOffset = static_cast(mLfoOffset+todo) % lfo_range; } -void Chorus_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Chorus_getParamf(props, context, param, vals); } -DEFINE_ALEFFECT_VTABLE(Chorus); - - -struct ChorusStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ChorusState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Chorus_vtable; } -}; - -EffectProps ChorusStateFactory::getDefaultProps() const noexcept +void ChorusState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - EffectProps props{}; - props.Chorus.Waveform = AL_CHORUS_DEFAULT_WAVEFORM; - props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE; - props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE; - props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH; - props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK; - props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY; - return props; -} - + const size_t bufmask{mSampleBuffer.size()-1}; + const float feedback{mFeedback}; + const uint avgdelay{(static_cast(mDelay) + (MixerFracOne>>1)) >> MixerFracBits}; + float *RESTRICT delaybuf{mSampleBuffer.data()}; + uint offset{mOffset}; -void Flanger_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) + for(size_t base{0u};base < samplesToDo;) { - case AL_FLANGER_WAVEFORM: - if(!(val >= AL_FLANGER_MIN_WAVEFORM && val <= AL_FLANGER_MAX_WAVEFORM)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid flanger waveform"); - props->Chorus.Waveform = val; - break; + const size_t todo{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)}; - case AL_FLANGER_PHASE: - if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger phase out of range"); - props->Chorus.Phase = val; - break; + uint moddelays[2][MAX_UPDATE_SAMPLES]; + if(mWaveform == ChorusWaveform::Sinusoid) + getSinusoidDelays(moddelays, todo); + else /*if(mWaveform == ChorusWaveform::Triangle)*/ + getTriangleDelays(moddelays, todo); - default: - alSetError(context, AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param); - } -} -void Flanger_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Flanger_setParami(props, context, param, vals[0]); } -void Flanger_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_FLANGER_RATE: - if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger rate out of range"); - props->Chorus.Rate = val; - break; - - case AL_FLANGER_DEPTH: - if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger depth out of range"); - props->Chorus.Depth = val; - break; - - case AL_FLANGER_FEEDBACK: - if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger feedback out of range"); - props->Chorus.Feedback = val; - break; + alignas(16) float temps[2][MAX_UPDATE_SAMPLES]; + for(size_t i{0u};i < todo;++i) + { + // Feed the buffer's input first (necessary for delays < 1). + delaybuf[offset&bufmask] = samplesIn[0][base+i]; - case AL_FLANGER_DELAY: - if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger delay out of range"); - props->Chorus.Delay = val; - break; + // Tap for the left output. + uint delay{offset - (moddelays[0][i]>>MixerFracBits)}; + float mu{static_cast(moddelays[0][i]&MixerFracMask) * (1.0f/MixerFracOne)}; + temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], + delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu); - default: - alSetError(context, AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param); - } -} -void Flanger_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Flanger_setParamf(props, context, param, vals[0]); } + // Tap for the right output. + delay = offset - (moddelays[1][i]>>MixerFracBits); + mu = static_cast(moddelays[1][i]&MixerFracMask) * (1.0f/MixerFracOne); + temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], + delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu); -void Flanger_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_FLANGER_WAVEFORM: - *val = props->Chorus.Waveform; - break; + // Accumulate feedback from the average delay of the taps. + delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback; + ++offset; + } - case AL_FLANGER_PHASE: - *val = props->Chorus.Phase; - break; + for(size_t c{0};c < 2;++c) + MixSamples({temps[c], todo}, samplesOut, mGains[c].Current, mGains[c].Target, + samplesToDo-base, base); - default: - alSetError(context, AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param); + base += todo; } -} -void Flanger_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Flanger_getParami(props, context, param, vals); } -void Flanger_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_FLANGER_RATE: - *val = props->Chorus.Rate; - break; - - case AL_FLANGER_DEPTH: - *val = props->Chorus.Depth; - break; - - case AL_FLANGER_FEEDBACK: - *val = props->Chorus.Feedback; - break; - - case AL_FLANGER_DELAY: - *val = props->Chorus.Delay; - break; - default: - alSetError(context, AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param); - } + mOffset = offset; } -void Flanger_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Flanger_getParamf(props, context, param, vals); } -DEFINE_ALEFFECT_VTABLE(Flanger); + +struct ChorusStateFactory final : public EffectStateFactory { + al::intrusive_ptr create() override + { return al::intrusive_ptr{new ChorusState{}}; } +}; /* Flanger is basically a chorus with a really short delay. They can both use * the same processing functions, so piggyback flanger on the chorus functions. */ struct FlangerStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ChorusState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Flanger_vtable; } + al::intrusive_ptr create() override + { return al::intrusive_ptr{new ChorusState{}}; } }; -EffectProps FlangerStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Chorus.Waveform = AL_FLANGER_DEFAULT_WAVEFORM; - props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE; - props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE; - props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH; - props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK; - props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY; - return props; -} - } // namespace EffectStateFactory *ChorusStateFactory_getFactory() diff --git a/modules/openal-soft/Alc/effects/compressor.cpp b/modules/openal-soft/Alc/effects/compressor.cpp index 2a441b0..366f227 100644 --- a/modules/openal-soft/Alc/effects/compressor.cpp +++ b/modules/openal-soft/Alc/effects/compressor.cpp @@ -1,33 +1,56 @@ /** - * OpenAL cross platform audio library + * This file is part of the OpenAL Soft cross platform audio library + * * Copyright (C) 2013 by Anis A. Hireche - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * 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 + * * Neither the name of Spherical-Harmonic-Transform nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" +#include #include +#include +#include + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" -#include "alMain.h" -#include "alcontext.h" -#include "alu.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "vecmat.h" +struct ContextBase; namespace { @@ -41,72 +64,68 @@ namespace { struct CompressorState final : public EffectState { /* Effect gains for each channel */ - ALfloat mGain[MAX_AMBI_CHANNELS][MAX_OUTPUT_CHANNELS]{}; + float mGain[MaxAmbiChannels][MAX_OUTPUT_CHANNELS]{}; /* Effect parameters */ - ALboolean mEnabled{AL_TRUE}; - ALfloat mAttackMult{1.0f}; - ALfloat mReleaseMult{1.0f}; - ALfloat mEnvFollower{1.0f}; + bool mEnabled{true}; + float mAttackMult{1.0f}; + float mReleaseMult{1.0f}; + float mEnvFollower{1.0f}; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override; + void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) override; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) override; DEF_NEWDEL(CompressorState) }; -ALboolean CompressorState::deviceUpdate(const ALCdevice *device) +void CompressorState::deviceUpdate(const DeviceBase *device, const Buffer&) { /* Number of samples to do a full attack and release (non-integer sample * counts are okay). */ - const ALfloat attackCount = static_cast(device->Frequency) * ATTACK_TIME; - const ALfloat releaseCount = static_cast(device->Frequency) * RELEASE_TIME; + const float attackCount{static_cast(device->Frequency) * ATTACK_TIME}; + const float releaseCount{static_cast(device->Frequency) * RELEASE_TIME}; /* Calculate per-sample multipliers to attack and release at the desired * rates. */ mAttackMult = std::pow(AMP_ENVELOPE_MAX/AMP_ENVELOPE_MIN, 1.0f/attackCount); mReleaseMult = std::pow(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount); - - return AL_TRUE; } -void CompressorState::update(const ALCcontext* UNUSED(context), const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void CompressorState::update(const ContextBase*, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { mEnabled = props->Compressor.OnOff; - mOutBuffer = target.Main->Buffer; - mOutChannels = target.Main->NumChannels; - for(ALsizei i{0};i < slot->Wet.NumChannels;++i) - { - auto coeffs = GetAmbiIdentityRow(i); - ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mGain[i]); - } + mOutTarget = target.Main->Buffer; + auto set_gains = [slot,target](auto &gains, al::span coeffs) + { ComputePanGains(target.Main, coeffs.data(), slot->Gain, gains); }; + SetAmbiPanIdentity(std::begin(mGain), slot->Wet.Buffer.size(), set_gains); } -void CompressorState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) +void CompressorState::process(const size_t samplesToDo, + const al::span samplesIn, const al::span samplesOut) { - ALsizei i, j, k; - ALsizei base; - - for(base = 0;base < samplesToDo;) + for(size_t base{0u};base < samplesToDo;) { - ALfloat gains[256]; - ALsizei td = mini(256, samplesToDo-base); - ALfloat env = mEnvFollower; + float gains[256]; + const size_t td{minz(256, samplesToDo-base)}; /* Generate the per-sample gains from the signal envelope. */ + float env{mEnvFollower}; if(mEnabled) { - for(i = 0;i < td;++i) + for(size_t i{0u};i < td;++i) { /* Clamp the absolute amplitude to the defined envelope limits, * then attack or release the envelope to reach it. */ - const ALfloat amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN, + const float amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN, AMP_ENVELOPE_MAX)}; if(amplitude > env) env = minf(env*mAttackMult, amplitude); @@ -125,9 +144,9 @@ void CompressorState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samp * ensure smooth gain changes when the compressor is turned on and * off. */ - for(i = 0;i < td;++i) + for(size_t i{0u};i < td;++i) { - const ALfloat amplitude{1.0f}; + const float amplitude{1.0f}; if(amplitude > env) env = minf(env*mAttackMult, amplitude); else if(amplitude < env) @@ -139,18 +158,18 @@ void CompressorState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samp mEnvFollower = env; /* Now compress the signal amplitude to output. */ - ASSUME(numInput > 0); - for(j = 0;j < numInput;j++) + auto changains = std::addressof(mGain[0]); + for(const auto &input : samplesIn) { - ASSUME(numOutput > 0); - for(k = 0;k < numOutput;k++) + const float *outgains{*(changains++)}; + for(FloatBufferLine &output : samplesOut) { - const ALfloat gain{mGain[j][k]}; - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) + const float gain{*(outgains++)}; + if(!(std::fabs(gain) > GainSilenceThreshold)) continue; - for(i = 0;i < td;i++) - samplesOut[k][base+i] += samplesIn[j][base+i] * gains[i] * gain; + for(size_t i{0u};i < td;i++) + output[base+i] += input[base+i] * gains[i] * gain; } } @@ -159,64 +178,11 @@ void CompressorState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samp } -void Compressor_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_COMPRESSOR_ONOFF: - if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Compressor state out of range"); - props->Compressor.OnOff = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", - param); - } -} -void Compressor_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Compressor_setParami(props, context, param, vals[0]); } -void Compressor_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat) -{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); } -void Compressor_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); } - -void Compressor_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_COMPRESSOR_ONOFF: - *val = props->Compressor.OnOff; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", - param); - } -} -void Compressor_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Compressor_getParami(props, context, param, vals); } -void Compressor_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); } -void Compressor_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); } - -DEFINE_ALEFFECT_VTABLE(Compressor); - - struct CompressorStateFactory final : public EffectStateFactory { - EffectState *create() override { return new CompressorState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Compressor_vtable; } + al::intrusive_ptr create() override + { return al::intrusive_ptr{new CompressorState{}}; } }; -EffectProps CompressorStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF; - return props; -} - } // namespace EffectStateFactory *CompressorStateFactory_getFactory() diff --git a/modules/openal-soft/Alc/effects/convolution.cpp b/modules/openal-soft/Alc/effects/convolution.cpp new file mode 100644 index 0000000..196238f --- /dev/null +++ b/modules/openal-soft/Alc/effects/convolution.cpp @@ -0,0 +1,612 @@ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SSE_INTRINSICS +#include +#elif defined(HAVE_NEON) +#include +#endif + +#include "albyte.h" +#include "alcomplex.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "base.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/buffer_storage.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/filters/splitter.h" +#include "core/fmt_traits.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" +#include "polyphase_resampler.h" +#include "vector.h" + + +namespace { + +/* Convolution reverb is implemented using a segmented overlap-add method. The + * impulse response is broken up into multiple segments of 128 samples, and + * each segment has an FFT applied with a 256-sample buffer (the latter half + * left silent) to get its frequency-domain response. The resulting response + * has its positive/non-mirrored frequencies saved (129 bins) in each segment. + * + * Input samples are similarly broken up into 128-sample segments, with an FFT + * applied to each new incoming segment to get its 129 bins. A history of FFT'd + * input segments is maintained, equal to the length of the impulse response. + * + * To apply the reverberation, each impulse response segment is convolved with + * its paired input segment (using complex multiplies, far cheaper than FIRs), + * accumulating into a 256-bin FFT buffer. The input history is then shifted to + * align with later impulse response segments for next time. + * + * An inverse FFT is then applied to the accumulated FFT buffer to get a 256- + * sample time-domain response for output, which is split in two halves. The + * first half is the 128-sample output, and the second half is a 128-sample + * (really, 127) delayed extension, which gets added to the output next time. + * Convolving two time-domain responses of lengths N and M results in a time- + * domain signal of length N+M-1, and this holds true regardless of the + * convolution being applied in the frequency domain, so these "overflow" + * samples need to be accounted for. + * + * To avoid a delay with gathering enough input samples to apply an FFT with, + * the first segment is applied directly in the time-domain as the samples come + * in. Once enough have been retrieved, the FFT is applied on the input and + * it's paired with the remaining (FFT'd) filter segments for processing. + */ + + +void LoadSamples(double *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype, + const size_t samples) noexcept +{ +#define HANDLE_FMT(T) case T: al::LoadSampleArray(dst, src, srcstep, samples); break + switch(srctype) + { + HANDLE_FMT(FmtUByte); + HANDLE_FMT(FmtShort); + HANDLE_FMT(FmtFloat); + HANDLE_FMT(FmtDouble); + HANDLE_FMT(FmtMulaw); + HANDLE_FMT(FmtAlaw); + } +#undef HANDLE_FMT +} + + +inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept +{ + switch(scaletype) + { + case AmbiScaling::FuMa: return AmbiScale::FromFuMa(); + case AmbiScaling::SN3D: return AmbiScale::FromSN3D(); + case AmbiScaling::UHJ: return AmbiScale::FromUHJ(); + case AmbiScaling::N3D: break; + } + return AmbiScale::FromN3D(); +} + +inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept +{ + if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa(); + return AmbiIndex::FromACN(); +} + +inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept +{ + if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D(); + return AmbiIndex::FromACN2D(); +} + + +struct ChanMap { + Channel channel; + float angle; + float elevation; +}; + +constexpr float Deg2Rad(float x) noexcept +{ return static_cast(al::numbers::pi / 180.0 * x); } + + +using complex_d = std::complex; + +constexpr size_t ConvolveUpdateSize{256}; +constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2}; + + +void apply_fir(al::span dst, const float *RESTRICT src, const float *RESTRICT filter) +{ +#ifdef HAVE_SSE_INTRINSICS + for(float &output : dst) + { + __m128 r4{_mm_setzero_ps()}; + for(size_t j{0};j < ConvolveUpdateSamples;j+=4) + { + const __m128 coeffs{_mm_load_ps(&filter[j])}; + const __m128 s{_mm_loadu_ps(&src[j])}; + + r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); + } + r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + output = _mm_cvtss_f32(r4); + + ++src; + } + +#elif defined(HAVE_NEON) + + for(float &output : dst) + { + float32x4_t r4{vdupq_n_f32(0.0f)}; + for(size_t j{0};j < ConvolveUpdateSamples;j+=4) + r4 = vmlaq_f32(r4, vld1q_f32(&src[j]), vld1q_f32(&filter[j])); + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + output = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + + ++src; + } + +#else + + for(float &output : dst) + { + float ret{0.0f}; + for(size_t j{0};j < ConvolveUpdateSamples;++j) + ret += src[j] * filter[j]; + output = ret; + ++src; + } +#endif +} + +struct ConvolutionState final : public EffectState { + FmtChannels mChannels{}; + AmbiLayout mAmbiLayout{}; + AmbiScaling mAmbiScaling{}; + uint mAmbiOrder{}; + + size_t mFifoPos{0}; + std::array mInput{}; + al::vector,16> mFilter; + al::vector,16> mOutput; + + alignas(16) std::array mFftBuffer{}; + + size_t mCurrentSegment{0}; + size_t mNumConvolveSegs{0}; + + struct ChannelData { + alignas(16) FloatBufferLine mBuffer{}; + float mHfScale{}; + BandSplitter mFilter{}; + float Current[MAX_OUTPUT_CHANNELS]{}; + float Target[MAX_OUTPUT_CHANNELS]{}; + }; + using ChannelDataArray = al::FlexArray; + std::unique_ptr mChans; + std::unique_ptr mComplexData; + + + ConvolutionState() = default; + ~ConvolutionState() override = default; + + void NormalMix(const al::span samplesOut, const size_t samplesToDo); + void UpsampleMix(const al::span samplesOut, const size_t samplesToDo); + void (ConvolutionState::*mMix)(const al::span,const size_t) + {&ConvolutionState::NormalMix}; + + void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) override; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) override; + + DEF_NEWDEL(ConvolutionState) +}; + +void ConvolutionState::NormalMix(const al::span samplesOut, + const size_t samplesToDo) +{ + for(auto &chan : *mChans) + MixSamples({chan.mBuffer.data(), samplesToDo}, samplesOut, chan.Current, chan.Target, + samplesToDo, 0); +} + +void ConvolutionState::UpsampleMix(const al::span samplesOut, + const size_t samplesToDo) +{ + for(auto &chan : *mChans) + { + const al::span src{chan.mBuffer.data(), samplesToDo}; + chan.mFilter.processHfScale(src, chan.mHfScale); + MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0); + } +} + + +void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buffer) +{ + constexpr uint MaxConvolveAmbiOrder{1u}; + + mFifoPos = 0; + mInput.fill(0.0f); + decltype(mFilter){}.swap(mFilter); + decltype(mOutput){}.swap(mOutput); + mFftBuffer.fill(complex_d{}); + + mCurrentSegment = 0; + mNumConvolveSegs = 0; + + mChans = nullptr; + mComplexData = nullptr; + + /* An empty buffer doesn't need a convolution filter. */ + if(!buffer.storage || buffer.storage->mSampleLen < 1) return; + + constexpr size_t m{ConvolveUpdateSize/2 + 1}; + auto bytesPerSample = BytesFromFmt(buffer.storage->mType); + auto realChannels = ChannelsFromFmt(buffer.storage->mChannels, buffer.storage->mAmbiOrder); + auto numChannels = ChannelsFromFmt(buffer.storage->mChannels, + minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder)); + + mChans = ChannelDataArray::Create(numChannels); + + /* The impulse response needs to have the same sample rate as the input and + * output. The bsinc24 resampler is decent, but there is high-frequency + * attenation that some people may be able to pick up on. Since this is + * called very infrequently, go ahead and use the polyphase resampler. + */ + PPhaseResampler resampler; + if(device->Frequency != buffer.storage->mSampleRate) + resampler.init(buffer.storage->mSampleRate, device->Frequency); + const auto resampledCount = static_cast( + (uint64_t{buffer.storage->mSampleLen}*device->Frequency+(buffer.storage->mSampleRate-1)) / + buffer.storage->mSampleRate); + + const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; + for(auto &e : *mChans) + e.mFilter = splitter; + + mFilter.resize(numChannels, {}); + mOutput.resize(numChannels, {}); + + /* Calculate the number of segments needed to hold the impulse response and + * the input history (rounded up), and allocate them. Exclude one segment + * which gets applied as a time-domain FIR filter. Make sure at least one + * segment is allocated to simplify handling. + */ + mNumConvolveSegs = (resampledCount+(ConvolveUpdateSamples-1)) / ConvolveUpdateSamples; + mNumConvolveSegs = maxz(mNumConvolveSegs, 2) - 1; + + const size_t complex_length{mNumConvolveSegs * m * (numChannels+1)}; + mComplexData = std::make_unique(complex_length); + std::fill_n(mComplexData.get(), complex_length, complex_d{}); + + mChannels = buffer.storage->mChannels; + mAmbiLayout = buffer.storage->mAmbiLayout; + mAmbiScaling = buffer.storage->mAmbiScaling; + mAmbiOrder = minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder); + + auto srcsamples = std::make_unique(maxz(buffer.storage->mSampleLen, resampledCount)); + complex_d *filteriter = mComplexData.get() + mNumConvolveSegs*m; + for(size_t c{0};c < numChannels;++c) + { + /* Load the samples from the buffer, and resample to match the device. */ + LoadSamples(srcsamples.get(), buffer.samples.data() + bytesPerSample*c, realChannels, + buffer.storage->mType, buffer.storage->mSampleLen); + if(device->Frequency != buffer.storage->mSampleRate) + resampler.process(buffer.storage->mSampleLen, srcsamples.get(), resampledCount, + srcsamples.get()); + + /* Store the first segment's samples in reverse in the time-domain, to + * apply as a FIR filter. + */ + const size_t first_size{minz(resampledCount, ConvolveUpdateSamples)}; + std::transform(srcsamples.get(), srcsamples.get()+first_size, mFilter[c].rbegin(), + [](const double d) noexcept -> float { return static_cast(d); }); + + size_t done{first_size}; + for(size_t s{0};s < mNumConvolveSegs;++s) + { + const size_t todo{minz(resampledCount-done, ConvolveUpdateSamples)}; + + auto iter = std::copy_n(&srcsamples[done], todo, mFftBuffer.begin()); + done += todo; + std::fill(iter, mFftBuffer.end(), complex_d{}); + + forward_fft(mFftBuffer); + filteriter = std::copy_n(mFftBuffer.cbegin(), m, filteriter); + } + } +} + + +void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps* /*props*/, const EffectTarget target) +{ + /* NOTE: Stereo and Rear are slightly different from normal mixing (as + * defined in alu.cpp). These are 45 degrees from center, rather than the + * 30 degrees used there. + * + * TODO: LFE is not mixed to output. This will require each buffer channel + * to have its own output target since the main mixing buffer won't have an + * LFE channel (due to being B-Format). + */ + static constexpr ChanMap MonoMap[1]{ + { FrontCenter, 0.0f, 0.0f } + }, StereoMap[2]{ + { FrontLeft, Deg2Rad(-45.0f), Deg2Rad(0.0f) }, + { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) } + }, RearMap[2]{ + { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) }, + { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) } + }, QuadMap[4]{ + { FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) }, + { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) }, + { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) }, + { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) } + }, X51Map[6]{ + { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) }, + { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, + { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, + { LFE, 0.0f, 0.0f }, + { SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) }, + { SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) } + }, X61Map[7]{ + { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) }, + { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, + { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, + { LFE, 0.0f, 0.0f }, + { BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) }, + { SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) }, + { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) } + }, X71Map[8]{ + { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) }, + { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, + { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, + { LFE, 0.0f, 0.0f }, + { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) }, + { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) }, + { SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) }, + { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) } + }; + + if(mNumConvolveSegs < 1) + return; + + mMix = &ConvolutionState::NormalMix; + + for(auto &chan : *mChans) + std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f); + const float gain{slot->Gain}; + /* TODO: UHJ should be decoded to B-Format and processed that way, since + * there's no telling if it can ever do a direct-out mix (even if the + * device is outputing UHJ, the effect slot can feed another effect that's + * not UHJ). + * + * Not that UHJ should really ever be used for convolution, but it's a + * valid format regardless. + */ + if((mChannels == FmtUHJ2 || mChannels == FmtUHJ3 || mChannels == FmtUHJ4) && target.RealOut + && target.RealOut->ChannelIndex[FrontLeft] != INVALID_CHANNEL_INDEX + && target.RealOut->ChannelIndex[FrontRight] != INVALID_CHANNEL_INDEX) + { + mOutTarget = target.RealOut->Buffer; + const uint lidx = target.RealOut->ChannelIndex[FrontLeft]; + const uint ridx = target.RealOut->ChannelIndex[FrontRight]; + (*mChans)[0].Target[lidx] = gain; + (*mChans)[1].Target[ridx] = gain; + } + else if(IsBFormat(mChannels)) + { + DeviceBase *device{context->mDevice}; + if(device->mAmbiOrder > mAmbiOrder) + { + mMix = &ConvolutionState::UpsampleMix; + const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder); + (*mChans)[0].mHfScale = scales[0]; + for(size_t i{1};i < mChans->size();++i) + (*mChans)[i].mHfScale = scales[1]; + } + mOutTarget = target.Main->Buffer; + + auto&& scales = GetAmbiScales(mAmbiScaling); + const uint8_t *index_map{(mChannels == FmtBFormat2D) ? + GetAmbi2DLayout(mAmbiLayout).data() : + GetAmbiLayout(mAmbiLayout).data()}; + + std::array coeffs{}; + for(size_t c{0u};c < mChans->size();++c) + { + const size_t acn{index_map[c]}; + coeffs[acn] = scales[acn]; + ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[c].Target); + coeffs[acn] = 0.0f; + } + } + else + { + DeviceBase *device{context->mDevice}; + al::span chanmap{}; + switch(mChannels) + { + case FmtMono: chanmap = MonoMap; break; + case FmtSuperStereo: + case FmtStereo: chanmap = StereoMap; break; + case FmtRear: chanmap = RearMap; break; + case FmtQuad: chanmap = QuadMap; break; + case FmtX51: chanmap = X51Map; break; + case FmtX61: chanmap = X61Map; break; + case FmtX71: chanmap = X71Map; break; + case FmtBFormat2D: + case FmtBFormat3D: + case FmtUHJ2: + case FmtUHJ3: + case FmtUHJ4: + break; + } + + mOutTarget = target.Main->Buffer; + if(device->mRenderMode == RenderMode::Pairwise) + { + auto ScaleAzimuthFront = [](float azimuth, float scale) -> float + { + constexpr float half_pi{al::numbers::pi_v*0.5f}; + const float abs_azi{std::fabs(azimuth)}; + if(!(abs_azi >= half_pi)) + return std::copysign(minf(abs_azi*scale, half_pi), azimuth); + return azimuth; + }; + + for(size_t i{0};i < chanmap.size();++i) + { + if(chanmap[i].channel == LFE) continue; + const auto coeffs = CalcAngleCoeffs(ScaleAzimuthFront(chanmap[i].angle, 2.0f), + chanmap[i].elevation, 0.0f); + ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target); + } + } + else for(size_t i{0};i < chanmap.size();++i) + { + if(chanmap[i].channel == LFE) continue; + const auto coeffs = CalcAngleCoeffs(chanmap[i].angle, chanmap[i].elevation, 0.0f); + ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target); + } + } +} + +void ConvolutionState::process(const size_t samplesToDo, + const al::span samplesIn, const al::span samplesOut) +{ + if(mNumConvolveSegs < 1) + return; + + constexpr size_t m{ConvolveUpdateSize/2 + 1}; + size_t curseg{mCurrentSegment}; + auto &chans = *mChans; + + for(size_t base{0u};base < samplesToDo;) + { + const size_t todo{minz(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)}; + + std::copy_n(samplesIn[0].begin() + base, todo, + mInput.begin()+ConvolveUpdateSamples+mFifoPos); + + /* Apply the FIR for the newly retrieved input samples, and combine it + * with the inverse FFT'd output samples. + */ + for(size_t c{0};c < chans.size();++c) + { + auto buf_iter = chans[c].mBuffer.begin() + base; + apply_fir({std::addressof(*buf_iter), todo}, mInput.data()+1 + mFifoPos, + mFilter[c].data()); + + auto fifo_iter = mOutput[c].begin() + mFifoPos; + std::transform(fifo_iter, fifo_iter+todo, buf_iter, buf_iter, std::plus<>{}); + } + + mFifoPos += todo; + base += todo; + + /* Check whether the input buffer is filled with new samples. */ + if(mFifoPos < ConvolveUpdateSamples) break; + mFifoPos = 0; + + /* Move the newest input to the front for the next iteration's history. */ + std::copy(mInput.cbegin()+ConvolveUpdateSamples, mInput.cend(), mInput.begin()); + + /* Calculate the frequency domain response and add the relevant + * frequency bins to the FFT history. + */ + auto fftiter = std::copy_n(mInput.cbegin(), ConvolveUpdateSamples, mFftBuffer.begin()); + std::fill(fftiter, mFftBuffer.end(), complex_d{}); + forward_fft(mFftBuffer); + + std::copy_n(mFftBuffer.cbegin(), m, &mComplexData[curseg*m]); + + const complex_d *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m}; + for(size_t c{0};c < chans.size();++c) + { + std::fill_n(mFftBuffer.begin(), m, complex_d{}); + + /* Convolve each input segment with its IR filter counterpart + * (aligned in time). + */ + const complex_d *RESTRICT input{&mComplexData[curseg*m]}; + for(size_t s{curseg};s < mNumConvolveSegs;++s) + { + for(size_t i{0};i < m;++i,++input,++filter) + mFftBuffer[i] += *input * *filter; + } + input = mComplexData.get(); + for(size_t s{0};s < curseg;++s) + { + for(size_t i{0};i < m;++i,++input,++filter) + mFftBuffer[i] += *input * *filter; + } + + /* Reconstruct the mirrored/negative frequencies to do a proper + * inverse FFT. + */ + for(size_t i{m};i < ConvolveUpdateSize;++i) + mFftBuffer[i] = std::conj(mFftBuffer[ConvolveUpdateSize-i]); + + /* Apply iFFT to get the 256 (really 255) samples for output. The + * 128 output samples are combined with the last output's 127 + * second-half samples (and this output's second half is + * subsequently saved for next time). + */ + inverse_fft(mFftBuffer); + + /* The iFFT'd response is scaled up by the number of bins, so apply + * the inverse to normalize the output. + */ + for(size_t i{0};i < ConvolveUpdateSamples;++i) + mOutput[c][i] = + static_cast(mFftBuffer[i].real() * (1.0/double{ConvolveUpdateSize})) + + mOutput[c][ConvolveUpdateSamples+i]; + for(size_t i{0};i < ConvolveUpdateSamples;++i) + mOutput[c][ConvolveUpdateSamples+i] = + static_cast(mFftBuffer[ConvolveUpdateSamples+i].real() * + (1.0/double{ConvolveUpdateSize})); + } + + /* Shift the input history. */ + curseg = curseg ? (curseg-1) : (mNumConvolveSegs-1); + } + mCurrentSegment = curseg; + + /* Finally, mix to the output. */ + (this->*mMix)(samplesOut, samplesToDo); +} + + +struct ConvolutionStateFactory final : public EffectStateFactory { + al::intrusive_ptr create() override + { return al::intrusive_ptr{new ConvolutionState{}}; } +}; + +} // namespace + +EffectStateFactory *ConvolutionStateFactory_getFactory() +{ + static ConvolutionStateFactory ConvolutionFactory{}; + return &ConvolutionFactory; +} diff --git a/modules/openal-soft/Alc/effects/dedicated.cpp b/modules/openal-soft/Alc/effects/dedicated.cpp index 14d0c3c..671eb5e 100644 --- a/modules/openal-soft/Alc/effects/dedicated.cpp +++ b/modules/openal-soft/Alc/effects/dedicated.cpp @@ -20,139 +20,97 @@ #include "config.h" -#include -#include #include +#include +#include +#include + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" -#include "alMain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" +struct ContextBase; namespace { +using uint = unsigned int; + struct DedicatedState final : public EffectState { - ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS]; - ALfloat mTargetGains[MAX_OUTPUT_CHANNELS]; + float mCurrentGains[MAX_OUTPUT_CHANNELS]; + float mTargetGains[MAX_OUTPUT_CHANNELS]; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override; + void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) override; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) override; DEF_NEWDEL(DedicatedState) }; -ALboolean DedicatedState::deviceUpdate(const ALCdevice *UNUSED(device)) +void DedicatedState::deviceUpdate(const DeviceBase*, const Buffer&) { std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f); - return AL_TRUE; } -void DedicatedState::update(const ALCcontext* UNUSED(context), const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void DedicatedState::update(const ContextBase*, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f); - const ALfloat Gain{slot->Params.Gain * props->Dedicated.Gain}; + const float Gain{slot->Gain * props->Dedicated.Gain}; - if(slot->Params.EffectType == AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT) + if(slot->EffectType == EffectSlotType::DedicatedLFE) { - const int idx{!target.RealOut ? -1 : GetChannelIdxByName(*target.RealOut, LFE)}; - if(idx != -1) + const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX : + GetChannelIdxByName(*target.RealOut, LFE)}; + if(idx != INVALID_CHANNEL_INDEX) { - mOutBuffer = target.RealOut->Buffer; - mOutChannels = target.RealOut->NumChannels; + mOutTarget = target.RealOut->Buffer; mTargetGains[idx] = Gain; } } - else if(slot->Params.EffectType == AL_EFFECT_DEDICATED_DIALOGUE) + else if(slot->EffectType == EffectSlotType::DedicatedDialog) { /* Dialog goes to the front-center speaker if it exists, otherwise it * plays from the front-center location. */ - const int idx{!target.RealOut ? -1 : GetChannelIdxByName(*target.RealOut, FrontCenter)}; - if(idx != -1) + const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX : + GetChannelIdxByName(*target.RealOut, FrontCenter)}; + if(idx != INVALID_CHANNEL_INDEX) { - mOutBuffer = target.RealOut->Buffer; - mOutChannels = target.RealOut->NumChannels; + mOutTarget = target.RealOut->Buffer; mTargetGains[idx] = Gain; } else { - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs); + const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f); - mOutBuffer = target.Main->Buffer; - mOutChannels = target.Main->NumChannels; - ComputePanGains(target.Main, coeffs, Gain, mTargetGains); + mOutTarget = target.Main->Buffer; + ComputePanGains(target.Main, coeffs.data(), Gain, mTargetGains); } } } -void DedicatedState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei /*numInput*/, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) +void DedicatedState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - MixSamples(samplesIn[0], numOutput, samplesOut, mCurrentGains, mTargetGains, samplesToDo, 0, - samplesToDo); + MixSamples({samplesIn[0].data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains, + samplesToDo, 0); } -void Dedicated_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); } -void Dedicated_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); } -void Dedicated_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_DEDICATED_GAIN: - if(!(val >= 0.0f && std::isfinite(val))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Dedicated gain out of range"); - props->Dedicated.Gain = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param); - } -} -void Dedicated_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Dedicated_setParamf(props, context, param, vals[0]); } - -void Dedicated_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); } -void Dedicated_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); } -void Dedicated_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_DEDICATED_GAIN: - *val = props->Dedicated.Gain; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param); - } -} -void Dedicated_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Dedicated_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Dedicated); - - struct DedicatedStateFactory final : public EffectStateFactory { - EffectState *create() override { return new DedicatedState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Dedicated_vtable; } + al::intrusive_ptr create() override + { return al::intrusive_ptr{new DedicatedState{}}; } }; -EffectProps DedicatedStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Dedicated.Gain = 1.0f; - return props; -} - } // namespace EffectStateFactory *DedicatedStateFactory_getFactory() diff --git a/modules/openal-soft/Alc/effects/distortion.cpp b/modules/openal-soft/Alc/effects/distortion.cpp index d2bcd01..74cffd4 100644 --- a/modules/openal-soft/Alc/effects/distortion.cpp +++ b/modules/openal-soft/Alc/effects/distortion.cpp @@ -20,143 +20,143 @@ #include "config.h" -#include +#include +#include #include - -#include - -#include "alMain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" -#include "filters/biquad.h" +#include + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/filters/biquad.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" namespace { struct DistortionState final : public EffectState { /* Effect gains for each channel */ - ALfloat mGain[MAX_OUTPUT_CHANNELS]{}; + float mGain[MAX_OUTPUT_CHANNELS]{}; /* Effect parameters */ BiquadFilter mLowpass; BiquadFilter mBandpass; - ALfloat mAttenuation{}; - ALfloat mEdgeCoeff{}; + float mAttenuation{}; + float mEdgeCoeff{}; - ALfloat mBuffer[2][BUFFERSIZE]{}; + float mBuffer[2][BufferLineSize]{}; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override; + void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) override; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) override; DEF_NEWDEL(DistortionState) }; -ALboolean DistortionState::deviceUpdate(const ALCdevice *UNUSED(device)) +void DistortionState::deviceUpdate(const DeviceBase*, const Buffer&) { mLowpass.clear(); mBandpass.clear(); - return AL_TRUE; } -void DistortionState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void DistortionState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->Device}; + const DeviceBase *device{context->mDevice}; /* Store waveshaper edge settings. */ - const ALfloat edge{ - minf(std::sin(al::MathDefs::Pi()*0.5f * props->Distortion.Edge), 0.99f)}; + const float edge{minf(std::sin(al::numbers::pi_v*0.5f * props->Distortion.Edge), + 0.99f)}; mEdgeCoeff = 2.0f * edge / (1.0f-edge); - ALfloat cutoff{props->Distortion.LowpassCutoff}; + float cutoff{props->Distortion.LowpassCutoff}; /* Bandwidth value is constant in octaves. */ - ALfloat bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)}; - /* Multiply sampling frequency by the amount of oversampling done during + float bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)}; + /* Divide normalized frequency by the amount of oversampling done during * processing. */ - auto frequency = static_cast(device->Frequency); - mLowpass.setParams(BiquadType::LowPass, 1.0f, cutoff / (frequency*4.0f), - calc_rcpQ_from_bandwidth(cutoff / (frequency*4.0f), bandwidth) - ); + auto frequency = static_cast(device->Frequency); + mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth); cutoff = props->Distortion.EQCenter; /* Convert bandwidth in Hz to octaves. */ bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f); - mBandpass.setParams(BiquadType::BandPass, 1.0f, cutoff / (frequency*4.0f), - calc_rcpQ_from_bandwidth(cutoff / (frequency*4.0f), bandwidth) - ); + mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth); - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs); + const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f); - mOutBuffer = target.Main->Buffer; - mOutChannels = target.Main->NumChannels; - ComputePanGains(target.Main, coeffs, slot->Params.Gain*props->Distortion.Gain, mGain); + mOutTarget = target.Main->Buffer; + ComputePanGains(target.Main, coeffs.data(), slot->Gain*props->Distortion.Gain, mGain); } -void DistortionState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei /*numInput*/, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) +void DistortionState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - ALfloat (*RESTRICT buffer)[BUFFERSIZE] = mBuffer; - const ALfloat fc = mEdgeCoeff; - ALsizei base; - ALsizei i, k; - - for(base = 0;base < samplesToDo;) + const float fc{mEdgeCoeff}; + for(size_t base{0u};base < samplesToDo;) { /* Perform 4x oversampling to avoid aliasing. Oversampling greatly * improves distortion quality and allows to implement lowpass and * bandpass filters using high frequencies, at which classic IIR * filters became unstable. */ - ALsizei todo{mini(BUFFERSIZE, (samplesToDo-base) * 4)}; + size_t todo{minz(BufferLineSize, (samplesToDo-base) * 4)}; /* Fill oversample buffer using zero stuffing. Multiply the sample by * the amount of oversampling to maintain the signal's power. */ - for(i = 0;i < todo;i++) - buffer[0][i] = !(i&3) ? samplesIn[0][(i>>2)+base] * 4.0f : 0.0f; + for(size_t i{0u};i < todo;i++) + mBuffer[0][i] = !(i&3) ? samplesIn[0][(i>>2)+base] * 4.0f : 0.0f; /* First step, do lowpass filtering of original signal. Additionally * perform buffer interpolation and lowpass cutoff for oversampling * (which is fortunately first step of distortion). So combine three * operations into the one. */ - mLowpass.process(buffer[1], buffer[0], todo); + mLowpass.process({mBuffer[0], todo}, mBuffer[1]); /* Second step, do distortion using waveshaper function to emulate * signal processing during tube overdriving. Three steps of * waveshaping are intended to modify waveform without boost/clipping/ * attenuation process. */ - for(i = 0;i < todo;i++) + auto proc_sample = [fc](float smp) -> float { - ALfloat smp = buffer[1][i]; - - smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp)); - smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp)) * -1.0f; - smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp)); - - buffer[0][i] = smp; - } + smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)); + smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)) * -1.0f; + smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)); + return smp; + }; + std::transform(std::begin(mBuffer[1]), std::begin(mBuffer[1])+todo, std::begin(mBuffer[0]), + proc_sample); /* Third step, do bandpass filtering of distorted signal. */ - mBandpass.process(buffer[1], buffer[0], todo); + mBandpass.process({mBuffer[0], todo}, mBuffer[1]); todo >>= 2; - for(k = 0;k < numOutput;k++) + const float *outgains{mGain}; + for(FloatBufferLine &output : samplesOut) { /* Fourth step, final, do attenuation and perform decimation, * storing only one sample out of four. */ - const ALfloat gain{mGain[k]}; - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) + const float gain{*(outgains++)}; + if(!(std::fabs(gain) > GainSilenceThreshold)) continue; - for(i = 0;i < todo;i++) - samplesOut[k][base+i] += gain * buffer[1][i*4]; + for(size_t i{0u};i < todo;i++) + output[base+i] += gain * mBuffer[1][i*4]; } base += todo; @@ -164,108 +164,11 @@ void DistortionState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samp } -void Distortion_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); } -void Distortion_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); } -void Distortion_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_DISTORTION_EDGE: - if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion edge out of range"); - props->Distortion.Edge = val; - break; - - case AL_DISTORTION_GAIN: - if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion gain out of range"); - props->Distortion.Gain = val; - break; - - case AL_DISTORTION_LOWPASS_CUTOFF: - if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion low-pass cutoff out of range"); - props->Distortion.LowpassCutoff = val; - break; - - case AL_DISTORTION_EQCENTER: - if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ center out of range"); - props->Distortion.EQCenter = val; - break; - - case AL_DISTORTION_EQBANDWIDTH: - if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ bandwidth out of range"); - props->Distortion.EQBandwidth = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", - param); - } -} -void Distortion_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Distortion_setParamf(props, context, param, vals[0]); } - -void Distortion_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); } -void Distortion_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); } -void Distortion_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_DISTORTION_EDGE: - *val = props->Distortion.Edge; - break; - - case AL_DISTORTION_GAIN: - *val = props->Distortion.Gain; - break; - - case AL_DISTORTION_LOWPASS_CUTOFF: - *val = props->Distortion.LowpassCutoff; - break; - - case AL_DISTORTION_EQCENTER: - *val = props->Distortion.EQCenter; - break; - - case AL_DISTORTION_EQBANDWIDTH: - *val = props->Distortion.EQBandwidth; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", - param); - } -} -void Distortion_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Distortion_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Distortion); - - struct DistortionStateFactory final : public EffectStateFactory { - EffectState *create() override { return new DistortionState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Distortion_vtable; } + al::intrusive_ptr create() override + { return al::intrusive_ptr{new DistortionState{}}; } }; -EffectProps DistortionStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE; - props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN; - props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF; - props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER; - props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH; - return props; -} - } // namespace EffectStateFactory *DistortionStateFactory_getFactory() diff --git a/modules/openal-soft/Alc/effects/echo.cpp b/modules/openal-soft/Alc/effects/echo.cpp index 9cd6fb8..5d00371 100644 --- a/modules/openal-soft/Alc/effects/echo.cpp +++ b/modules/openal-soft/Alc/effects/echo.cpp @@ -20,67 +20,74 @@ #include "config.h" -#include -#include - #include - -#include "alMain.h" -#include "alcontext.h" -#include "alFilter.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" -#include "filters/biquad.h" +#include +#include +#include +#include + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/filters/biquad.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" +#include "opthelpers.h" #include "vector.h" namespace { +using uint = unsigned int; + +constexpr float LowpassFreqRef{5000.0f}; + struct EchoState final : public EffectState { - al::vector mSampleBuffer; + al::vector mSampleBuffer; // The echo is two tap. The delay is the number of samples from before the // current offset struct { - ALsizei delay{0}; + size_t delay{0u}; } mTap[2]; - ALsizei mOffset{0}; + size_t mOffset{0u}; /* The panning gains for the two taps */ struct { - ALfloat Current[MAX_OUTPUT_CHANNELS]{}; - ALfloat Target[MAX_OUTPUT_CHANNELS]{}; + float Current[MAX_OUTPUT_CHANNELS]{}; + float Target[MAX_OUTPUT_CHANNELS]{}; } mGains[2]; BiquadFilter mFilter; - ALfloat mFeedGain{0.0f}; + float mFeedGain{0.0f}; - alignas(16) ALfloat mTempBuffer[2][BUFFERSIZE]; + alignas(16) float mTempBuffer[2][BufferLineSize]; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override; + void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) override; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) override; DEF_NEWDEL(EchoState) }; -ALboolean EchoState::deviceUpdate(const ALCdevice *Device) +void EchoState::deviceUpdate(const DeviceBase *Device, const Buffer&) { - ALuint maxlen; + const auto frequency = static_cast(Device->Frequency); // Use the next power of 2 for the buffer length, so the tap offsets can be // wrapped using a mask instead of a modulo - maxlen = float2int(AL_ECHO_MAX_DELAY*Device->Frequency + 0.5f) + - float2int(AL_ECHO_MAX_LRDELAY*Device->Frequency + 0.5f); - maxlen = NextPowerOf2(maxlen); - if(maxlen <= 0) return AL_FALSE; - + const uint maxlen{NextPowerOf2(float2uint(EchoMaxDelay*frequency + 0.5f) + + float2uint(EchoMaxLRDelay*frequency + 0.5f))}; if(maxlen != mSampleBuffer.size()) - { - mSampleBuffer.resize(maxlen); - mSampleBuffer.shrink_to_fit(); - } + al::vector(maxlen).swap(mSampleBuffer); std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f); for(auto &e : mGains) @@ -88,57 +95,53 @@ ALboolean EchoState::deviceUpdate(const ALCdevice *Device) std::fill(std::begin(e.Current), std::end(e.Current), 0.0f); std::fill(std::begin(e.Target), std::end(e.Target), 0.0f); } - - return AL_TRUE; } -void EchoState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void EchoState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const ALCdevice *device = context->Device; - const auto frequency = static_cast(device->Frequency); + const DeviceBase *device{context->mDevice}; + const auto frequency = static_cast(device->Frequency); - mTap[0].delay = maxi(float2int(props->Echo.Delay*frequency + 0.5f), 1); - mTap[1].delay = float2int(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay; + mTap[0].delay = maxu(float2uint(props->Echo.Delay*frequency + 0.5f), 1); + mTap[1].delay = float2uint(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay; - const ALfloat gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */ - mFilter.setParams(BiquadType::HighShelf, gainhf, LOWPASSFREQREF/frequency, - calc_rcpQ_from_slope(gainhf, 1.0f)); + const float gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */ + mFilter.setParamsFromSlope(BiquadType::HighShelf, LowpassFreqRef/frequency, gainhf, 1.0f); mFeedGain = props->Echo.Feedback; /* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */ - const ALfloat angle{std::asin(props->Echo.Spread)}; + const float angle{std::asin(props->Echo.Spread)}; - ALfloat coeffs[2][MAX_AMBI_CHANNELS]; - CalcAngleCoeffs(-angle, 0.0f, 0.0f, coeffs[0]); - CalcAngleCoeffs( angle, 0.0f, 0.0f, coeffs[1]); + const auto coeffs0 = CalcAngleCoeffs(-angle, 0.0f, 0.0f); + const auto coeffs1 = CalcAngleCoeffs( angle, 0.0f, 0.0f); - mOutBuffer = target.Main->Buffer; - mOutChannels = target.Main->NumChannels; - ComputePanGains(target.Main, coeffs[0], slot->Params.Gain, mGains[0].Target); - ComputePanGains(target.Main, coeffs[1], slot->Params.Gain, mGains[1].Target); + mOutTarget = target.Main->Buffer; + ComputePanGains(target.Main, coeffs0.data(), slot->Gain, mGains[0].Target); + ComputePanGains(target.Main, coeffs1.data(), slot->Gain, mGains[1].Target); } -void EchoState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei /*numInput*/, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) +void EchoState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - const auto mask = static_cast(mSampleBuffer.size()-1); - ALfloat *RESTRICT delaybuf{mSampleBuffer.data()}; - ALsizei offset{mOffset}; - ALsizei tap1{offset - mTap[0].delay}; - ALsizei tap2{offset - mTap[1].delay}; - ALfloat z1, z2; + const size_t mask{mSampleBuffer.size()-1}; + float *RESTRICT delaybuf{mSampleBuffer.data()}; + size_t offset{mOffset}; + size_t tap1{offset - mTap[0].delay}; + size_t tap2{offset - mTap[1].delay}; + float z1, z2; ASSUME(samplesToDo > 0); - ASSUME(mask > 0); + const BiquadFilter filter{mFilter}; std::tie(z1, z2) = mFilter.getComponents(); - for(ALsizei i{0};i < samplesToDo;) + for(size_t i{0u};i < samplesToDo;) { offset &= mask; tap1 &= mask; tap2 &= mask; - ALsizei td{mini(mask+1 - maxi(offset, maxi(tap1, tap2)), samplesToDo-i)}; + size_t td{minz(mask+1 - maxz(offset, maxz(tap1, tap2)), samplesToDo-i)}; do { /* Feed the delay buffer's input first. */ delaybuf[offset] = samplesIn[0][i]; @@ -151,118 +154,23 @@ void EchoState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn) const float feedb{mTempBuffer[1][i++]}; /* Add feedback to the delay buffer with damping and attenuation. */ - delaybuf[offset++] += mFilter.processOne(feedb, z1, z2) * mFeedGain; + delaybuf[offset++] += filter.processOne(feedb, z1, z2) * mFeedGain; } while(--td); } mFilter.setComponents(z1, z2); mOffset = offset; - for(ALsizei c{0};c < 2;c++) - MixSamples(mTempBuffer[c], numOutput, samplesOut, mGains[c].Current, mGains[c].Target, - samplesToDo, 0, samplesToDo); + for(size_t c{0};c < 2;c++) + MixSamples({mTempBuffer[c], samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target, + samplesToDo, 0); } -void Echo_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); } -void Echo_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); } -void Echo_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_ECHO_DELAY: - if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo delay out of range"); - props->Echo.Delay = val; - break; - - case AL_ECHO_LRDELAY: - if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo LR delay out of range"); - props->Echo.LRDelay = val; - break; - - case AL_ECHO_DAMPING: - if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo damping out of range"); - props->Echo.Damping = val; - break; - - case AL_ECHO_FEEDBACK: - if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo feedback out of range"); - props->Echo.Feedback = val; - break; - - case AL_ECHO_SPREAD: - if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo spread out of range"); - props->Echo.Spread = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param); - } -} -void Echo_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Echo_setParamf(props, context, param, vals[0]); } - -void Echo_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); } -void Echo_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); } -void Echo_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_ECHO_DELAY: - *val = props->Echo.Delay; - break; - - case AL_ECHO_LRDELAY: - *val = props->Echo.LRDelay; - break; - - case AL_ECHO_DAMPING: - *val = props->Echo.Damping; - break; - - case AL_ECHO_FEEDBACK: - *val = props->Echo.Feedback; - break; - - case AL_ECHO_SPREAD: - *val = props->Echo.Spread; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param); - } -} -void Echo_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Echo_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Echo); - - struct EchoStateFactory final : public EffectStateFactory { - EffectState *create() override { return new EchoState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Echo_vtable; } + al::intrusive_ptr create() override + { return al::intrusive_ptr{new EchoState{}}; } }; -EffectProps EchoStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Echo.Delay = AL_ECHO_DEFAULT_DELAY; - props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY; - props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING; - props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK; - props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD; - return props; -} - } // namespace EffectStateFactory *EchoStateFactory_getFactory() diff --git a/modules/openal-soft/Alc/effects/equalizer.cpp b/modules/openal-soft/Alc/effects/equalizer.cpp index e152494..67ad67b 100644 --- a/modules/openal-soft/Alc/effects/equalizer.cpp +++ b/modules/openal-soft/Alc/effects/equalizer.cpp @@ -20,19 +20,25 @@ #include "config.h" -#include -#include - #include +#include +#include #include - -#include "alMain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" -#include "filters/biquad.h" -#include "vecmat.h" +#include +#include + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/filters/biquad.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" namespace { @@ -85,63 +91,64 @@ struct EqualizerState final : public EffectState { BiquadFilter filter[4]; /* Effect gains for each channel */ - ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{}; - ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{}; - } mChans[MAX_AMBI_CHANNELS]; + float CurrentGains[MAX_OUTPUT_CHANNELS]{}; + float TargetGains[MAX_OUTPUT_CHANNELS]{}; + } mChans[MaxAmbiChannels]; - ALfloat mSampleBuffer[BUFFERSIZE]{}; + FloatBufferLine mSampleBuffer{}; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override; + void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) override; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) override; DEF_NEWDEL(EqualizerState) }; -ALboolean EqualizerState::deviceUpdate(const ALCdevice *UNUSED(device)) +void EqualizerState::deviceUpdate(const DeviceBase*, const Buffer&) { for(auto &e : mChans) { - std::for_each(std::begin(e.filter), std::end(e.filter), - std::mem_fn(&BiquadFilter::clear)); + std::for_each(std::begin(e.filter), std::end(e.filter), std::mem_fn(&BiquadFilter::clear)); std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); } - return AL_TRUE; } -void EqualizerState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void EqualizerState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const ALCdevice *device = context->Device; - auto frequency = static_cast(device->Frequency); - ALfloat gain, f0norm; + const DeviceBase *device{context->mDevice}; + auto frequency = static_cast(device->Frequency); + float gain, f0norm; /* Calculate coefficients for the each type of filter. Note that the shelf - * filters' gain is for the reference frequency, which is the centerpoint - * of the transition band. + * and peaking filters' gain is for the centerpoint of the transition band, + * while the effect property gains are for the shelf/peak itself. So the + * property gains need their dB halved (sqrt of linear gain) for the + * shelf/peak to reach the provided gain. */ - gain = maxf(sqrtf(props->Equalizer.LowGain), 0.0625f); /* Limit -24dB */ - f0norm = props->Equalizer.LowCutoff/frequency; - mChans[0].filter[0].setParams(BiquadType::LowShelf, gain, f0norm, - calc_rcpQ_from_slope(gain, 0.75f)); + gain = std::sqrt(props->Equalizer.LowGain); + f0norm = props->Equalizer.LowCutoff / frequency; + mChans[0].filter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f); - gain = maxf(props->Equalizer.Mid1Gain, 0.0625f); - f0norm = props->Equalizer.Mid1Center/frequency; - mChans[0].filter[1].setParams(BiquadType::Peaking, gain, f0norm, - calc_rcpQ_from_bandwidth(f0norm, props->Equalizer.Mid1Width)); + gain = std::sqrt(props->Equalizer.Mid1Gain); + f0norm = props->Equalizer.Mid1Center / frequency; + mChans[0].filter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, + props->Equalizer.Mid1Width); - gain = maxf(props->Equalizer.Mid2Gain, 0.0625f); - f0norm = props->Equalizer.Mid2Center/frequency; - mChans[0].filter[2].setParams(BiquadType::Peaking, gain, f0norm, - calc_rcpQ_from_bandwidth(f0norm, props->Equalizer.Mid2Width)); + gain = std::sqrt(props->Equalizer.Mid2Gain); + f0norm = props->Equalizer.Mid2Center / frequency; + mChans[0].filter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, + props->Equalizer.Mid2Width); - gain = maxf(sqrtf(props->Equalizer.HighGain), 0.0625f); - f0norm = props->Equalizer.HighCutoff/frequency; - mChans[0].filter[3].setParams(BiquadType::HighShelf, gain, f0norm, - calc_rcpQ_from_slope(gain, 0.75f)); + gain = std::sqrt(props->Equalizer.HighGain); + f0norm = props->Equalizer.HighCutoff / frequency; + mChans[0].filter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f); /* Copy the filter coefficients for the other input channels. */ - for(ALsizei i{1};i < slot->Wet.NumChannels;++i) + for(size_t i{1u};i < slot->Wet.Buffer.size();++i) { mChans[i].filter[0].copyParamsFrom(mChans[0].filter[0]); mChans[i].filter[1].copyParamsFrom(mChans[0].filter[1]); @@ -149,186 +156,33 @@ void EqualizerState::update(const ALCcontext *context, const ALeffectslot *slot, mChans[i].filter[3].copyParamsFrom(mChans[0].filter[3]); } - mOutBuffer = target.Main->Buffer; - mOutChannels = target.Main->NumChannels; - for(ALsizei i{0};i < slot->Wet.NumChannels;++i) - { - auto coeffs = GetAmbiIdentityRow(i); - ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains); - } + mOutTarget = target.Main->Buffer; + auto set_gains = [slot,target](auto &chan, al::span coeffs) + { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); }; + SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains); } -void EqualizerState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) +void EqualizerState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - ASSUME(numInput > 0); - for(ALsizei c{0};c < numInput;c++) + const al::span buffer{mSampleBuffer.data(), samplesToDo}; + auto chan = std::addressof(mChans[0]); + for(const auto &input : samplesIn) { - mChans[c].filter[0].process(mSampleBuffer, samplesIn[c], samplesToDo); - mChans[c].filter[1].process(mSampleBuffer, mSampleBuffer, samplesToDo); - mChans[c].filter[2].process(mSampleBuffer, mSampleBuffer, samplesToDo); - mChans[c].filter[3].process(mSampleBuffer, mSampleBuffer, samplesToDo); + const al::span inbuf{input.data(), samplesToDo}; + DualBiquad{chan->filter[0], chan->filter[1]}.process(inbuf, buffer.begin()); + DualBiquad{chan->filter[2], chan->filter[3]}.process(buffer, buffer.begin()); - MixSamples(mSampleBuffer, numOutput, samplesOut, mChans[c].CurrentGains, - mChans[c].TargetGains, samplesToDo, 0, samplesToDo); + MixSamples(buffer, samplesOut, chan->CurrentGains, chan->TargetGains, samplesToDo, 0u); + ++chan; } } -void Equalizer_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint) -{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); } -void Equalizer_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); } -void Equalizer_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_EQUALIZER_LOW_GAIN: - if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band gain out of range"); - props->Equalizer.LowGain = val; - break; - - case AL_EQUALIZER_LOW_CUTOFF: - if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band cutoff out of range"); - props->Equalizer.LowCutoff = val; - break; - - case AL_EQUALIZER_MID1_GAIN: - if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band gain out of range"); - props->Equalizer.Mid1Gain = val; - break; - - case AL_EQUALIZER_MID1_CENTER: - if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band center out of range"); - props->Equalizer.Mid1Center = val; - break; - - case AL_EQUALIZER_MID1_WIDTH: - if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band width out of range"); - props->Equalizer.Mid1Width = val; - break; - - case AL_EQUALIZER_MID2_GAIN: - if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band gain out of range"); - props->Equalizer.Mid2Gain = val; - break; - - case AL_EQUALIZER_MID2_CENTER: - if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band center out of range"); - props->Equalizer.Mid2Center = val; - break; - - case AL_EQUALIZER_MID2_WIDTH: - if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band width out of range"); - props->Equalizer.Mid2Width = val; - break; - - case AL_EQUALIZER_HIGH_GAIN: - if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band gain out of range"); - props->Equalizer.HighGain = val; - break; - - case AL_EQUALIZER_HIGH_CUTOFF: - if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band cutoff out of range"); - props->Equalizer.HighCutoff = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param); - } -} -void Equalizer_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Equalizer_setParamf(props, context, param, vals[0]); } - -void Equalizer_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); } -void Equalizer_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); } -void Equalizer_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_EQUALIZER_LOW_GAIN: - *val = props->Equalizer.LowGain; - break; - - case AL_EQUALIZER_LOW_CUTOFF: - *val = props->Equalizer.LowCutoff; - break; - - case AL_EQUALIZER_MID1_GAIN: - *val = props->Equalizer.Mid1Gain; - break; - - case AL_EQUALIZER_MID1_CENTER: - *val = props->Equalizer.Mid1Center; - break; - - case AL_EQUALIZER_MID1_WIDTH: - *val = props->Equalizer.Mid1Width; - break; - - case AL_EQUALIZER_MID2_GAIN: - *val = props->Equalizer.Mid2Gain; - break; - - case AL_EQUALIZER_MID2_CENTER: - *val = props->Equalizer.Mid2Center; - break; - - case AL_EQUALIZER_MID2_WIDTH: - *val = props->Equalizer.Mid2Width; - break; - - case AL_EQUALIZER_HIGH_GAIN: - *val = props->Equalizer.HighGain; - break; - - case AL_EQUALIZER_HIGH_CUTOFF: - *val = props->Equalizer.HighCutoff; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param); - } -} -void Equalizer_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Equalizer_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Equalizer); - - struct EqualizerStateFactory final : public EffectStateFactory { - EffectState *create() override { return new EqualizerState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Equalizer_vtable; } + al::intrusive_ptr create() override + { return al::intrusive_ptr{new EqualizerState{}}; } }; -EffectProps EqualizerStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF; - props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN; - props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER; - props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN; - props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH; - props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER; - props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN; - props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH; - props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF; - props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN; - return props; -} - } // namespace EffectStateFactory *EqualizerStateFactory_getFactory() diff --git a/modules/openal-soft/Alc/effects/fshifter.cpp b/modules/openal-soft/Alc/effects/fshifter.cpp index e3dc6f1..def745c 100644 --- a/modules/openal-soft/Alc/effects/fshifter.cpp +++ b/modules/openal-soft/Alc/effects/fshifter.cpp @@ -20,22 +20,32 @@ #include "config.h" -#include -#include +#include #include +#include #include -#include - -#include "alMain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" +#include +#include +#include "alc/effects/base.h" #include "alcomplex.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" + namespace { +using uint = unsigned int; using complex_d = std::complex; #define HIL_SIZE 1024 @@ -45,254 +55,189 @@ using complex_d = std::complex; #define FIFO_LATENCY (HIL_STEP * (OVERSAMP-1)) /* Define a Hann window, used to filter the HIL input and output. */ -/* Making this constexpr seems to require C++14. */ -std::array InitHannWindow() +std::array InitHannWindow() { - std::array ret; + std::array ret; /* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */ - for(ALsizei i{0};i < HIL_SIZE>>1;i++) + for(size_t i{0};i < HIL_SIZE>>1;i++) { - ALdouble val = std::sin(al::MathDefs::Pi() * i / ALdouble{HIL_SIZE-1}); + constexpr double scale{al::numbers::pi / double{HIL_SIZE}}; + const double val{std::sin(static_cast(i+1) * scale)}; ret[i] = ret[HIL_SIZE-1-i] = val * val; } return ret; } -alignas(16) const std::array HannWindow = InitHannWindow(); +alignas(16) const std::array HannWindow = InitHannWindow(); struct FshifterState final : public EffectState { /* Effect parameters */ - ALsizei mCount{}; - ALsizei mPhaseStep{}; - ALsizei mPhase{}; - ALdouble mLdSign{}; - - /*Effects buffers*/ - ALfloat mInFIFO[HIL_SIZE]{}; - complex_d mOutFIFO[HIL_SIZE]{}; + size_t mCount{}; + size_t mPos{}; + uint mPhaseStep[2]{}; + uint mPhase[2]{}; + double mSign[2]{}; + + /* Effects buffers */ + double mInFIFO[HIL_SIZE]{}; + complex_d mOutFIFO[HIL_STEP]{}; complex_d mOutputAccum[HIL_SIZE]{}; complex_d mAnalytic[HIL_SIZE]{}; - complex_d mOutdata[BUFFERSIZE]{}; + complex_d mOutdata[BufferLineSize]{}; - alignas(16) ALfloat mBufferOut[BUFFERSIZE]{}; + alignas(16) float mBufferOut[BufferLineSize]{}; /* Effect gains for each output channel */ - ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS]{}; - ALfloat mTargetGains[MAX_OUTPUT_CHANNELS]{}; + struct { + float Current[MAX_OUTPUT_CHANNELS]{}; + float Target[MAX_OUTPUT_CHANNELS]{}; + } mGains[2]; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override; + void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) override; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) override; DEF_NEWDEL(FshifterState) }; -ALboolean FshifterState::deviceUpdate(const ALCdevice *UNUSED(device)) +void FshifterState::deviceUpdate(const DeviceBase*, const Buffer&) { /* (Re-)initializing parameters and clear the buffers. */ - mCount = FIFO_LATENCY; - mPhaseStep = 0; - mPhase = 0; - mLdSign = 1.0; + mCount = 0; + mPos = FIFO_LATENCY; - std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0f); + std::fill(std::begin(mPhaseStep), std::end(mPhaseStep), 0u); + std::fill(std::begin(mPhase), std::end(mPhase), 0u); + std::fill(std::begin(mSign), std::end(mSign), 1.0); + std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0); std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), complex_d{}); std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), complex_d{}); std::fill(std::begin(mAnalytic), std::end(mAnalytic), complex_d{}); - std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f); - std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f); - - return AL_TRUE; + for(auto &gain : mGains) + { + std::fill(std::begin(gain.Current), std::end(gain.Current), 0.0f); + std::fill(std::begin(gain.Target), std::end(gain.Target), 0.0f); + } } -void FshifterState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void FshifterState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->Device}; + const DeviceBase *device{context->mDevice}; - ALfloat step{props->Fshifter.Frequency / static_cast(device->Frequency)}; - mPhaseStep = fastf2i(minf(step, 0.5f) * FRACTIONONE); + const float step{props->Fshifter.Frequency / static_cast(device->Frequency)}; + mPhaseStep[0] = mPhaseStep[1] = fastf2u(minf(step, 1.0f) * MixerFracOne); switch(props->Fshifter.LeftDirection) { - case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN: - mLdSign = -1.0; - break; - - case AL_FREQUENCY_SHIFTER_DIRECTION_UP: - mLdSign = 1.0; - break; - - case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: - mPhase = 0; - mPhaseStep = 0; - break; + case FShifterDirection::Down: + mSign[0] = -1.0; + break; + case FShifterDirection::Up: + mSign[0] = 1.0; + break; + case FShifterDirection::Off: + mPhase[0] = 0; + mPhaseStep[0] = 0; + break; + } + + switch(props->Fshifter.RightDirection) + { + case FShifterDirection::Down: + mSign[1] = -1.0; + break; + case FShifterDirection::Up: + mSign[1] = 1.0; + break; + case FShifterDirection::Off: + mPhase[1] = 0; + mPhaseStep[1] = 0; + break; } - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs); + const auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f); + const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f); - mOutBuffer = target.Main->Buffer; - mOutChannels = target.Main->NumChannels; - ComputePanGains(target.Main, coeffs, slot->Params.Gain, mTargetGains); + mOutTarget = target.Main->Buffer; + ComputePanGains(target.Main, lcoeffs.data(), slot->Gain, mGains[0].Target); + ComputePanGains(target.Main, rcoeffs.data(), slot->Gain, mGains[1].Target); } -void FshifterState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei /*numInput*/, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) +void FshifterState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - static constexpr complex_d complex_zero{0.0, 0.0}; - ALfloat *RESTRICT BufferOut = mBufferOut; - ALsizei j, k, base; - - for(base = 0;base < samplesToDo;) + for(size_t base{0u};base < samplesToDo;) { - const ALsizei todo{mini(HIL_SIZE-mCount, samplesToDo-base)}; - - ASSUME(todo > 0); + size_t todo{minz(HIL_STEP-mCount, samplesToDo-base)}; /* Fill FIFO buffer with samples data */ - k = mCount; - for(j = 0;j < todo;j++,k++) - { - mInFIFO[k] = samplesIn[0][base+j]; - mOutdata[base+j] = mOutFIFO[k-FIFO_LATENCY]; - } - mCount += todo; - base += todo; + const size_t pos{mPos}; + size_t count{mCount}; + do { + mInFIFO[pos+count] = samplesIn[0][base]; + mOutdata[base] = mOutFIFO[count]; + ++base; ++count; + } while(--todo); + mCount = count; /* Check whether FIFO buffer is filled */ - if(mCount < HIL_SIZE) continue; - mCount = FIFO_LATENCY; + if(mCount < HIL_STEP) break; + mCount = 0; + mPos = (mPos+HIL_STEP) & (HIL_SIZE-1); /* Real signal windowing and store in Analytic buffer */ - for(k = 0;k < HIL_SIZE;k++) - { - mAnalytic[k].real(mInFIFO[k] * HannWindow[k]); - mAnalytic[k].imag(0.0); - } + for(size_t src{mPos}, k{0u};src < HIL_SIZE;++src,++k) + mAnalytic[k] = mInFIFO[src]*HannWindow[k]; + for(size_t src{0u}, k{HIL_SIZE-mPos};src < mPos;++src,++k) + mAnalytic[k] = mInFIFO[src]*HannWindow[k]; /* Processing signal by Discrete Hilbert Transform (analytical signal). */ - complex_hilbert(mAnalytic, HIL_SIZE); + complex_hilbert(mAnalytic); /* Windowing and add to output accumulator */ - for(k = 0;k < HIL_SIZE;k++) - mOutputAccum[k] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k]; - - /* Shift accumulator, input & output FIFO */ - for(k = 0;k < HIL_STEP;k++) mOutFIFO[k] = mOutputAccum[k]; - for(j = 0;k < HIL_SIZE;k++,j++) mOutputAccum[j] = mOutputAccum[k]; - for(;j < HIL_SIZE;j++) mOutputAccum[j] = complex_zero; - for(k = 0;k < FIFO_LATENCY;k++) - mInFIFO[k] = mInFIFO[k+HIL_STEP]; + for(size_t dst{mPos}, k{0u};dst < HIL_SIZE;++dst,++k) + mOutputAccum[dst] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k]; + for(size_t dst{0u}, k{HIL_SIZE-mPos};dst < mPos;++dst,++k) + mOutputAccum[dst] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k]; + + /* Copy out the accumulated result, then clear for the next iteration. */ + std::copy_n(mOutputAccum + mPos, HIL_STEP, mOutFIFO); + std::fill_n(mOutputAccum + mPos, HIL_STEP, complex_d{}); } /* Process frequency shifter using the analytic signal obtained. */ - for(k = 0;k < samplesToDo;k++) - { - double phase = mPhase * ((1.0/FRACTIONONE) * al::MathDefs::Tau()); - BufferOut[k] = static_cast(mOutdata[k].real()*std::cos(phase) + - mOutdata[k].imag()*std::sin(phase)*mLdSign); - - mPhase += mPhaseStep; - mPhase &= FRACTIONMASK; - } - - /* Now, mix the processed sound data to the output. */ - MixSamples(BufferOut, numOutput, samplesOut, mCurrentGains, mTargetGains, - maxi(samplesToDo, 512), 0, samplesToDo); -} - - -void Fshifter_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_FREQUENCY_SHIFTER_FREQUENCY: - if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter frequency out of range"); - props->Fshifter.Frequency = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", param); - } -} -void Fshifter_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Fshifter_setParamf(props, context, param, vals[0]); } - -void Fshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: - if(!(val >= AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter left direction out of range"); - props->Fshifter.LeftDirection = val; - break; - - case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: - if(!(val >= AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter right direction out of range"); - props->Fshifter.RightDirection = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x", param); - } -} -void Fshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Fshifter_setParami(props, context, param, vals[0]); } - -void Fshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) + float *RESTRICT BufferOut{mBufferOut}; + for(int c{0};c < 2;++c) { - case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: - *val = props->Fshifter.LeftDirection; - break; - case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: - *val = props->Fshifter.RightDirection; - break; - default: - alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x", param); - } -} -void Fshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Fshifter_getParami(props, context, param, vals); } + const uint phase_step{mPhaseStep[c]}; + uint phase_idx{mPhase[c]}; + for(size_t k{0};k < samplesToDo;++k) + { + const double phase{phase_idx * (al::numbers::pi*2.0 / MixerFracOne)}; + BufferOut[k] = static_cast(mOutdata[k].real()*std::cos(phase) + + mOutdata[k].imag()*std::sin(phase)*mSign[c]); -void Fshifter_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_FREQUENCY_SHIFTER_FREQUENCY: - *val = props->Fshifter.Frequency; - break; + phase_idx += phase_step; + phase_idx &= MixerFracMask; + } + mPhase[c] = phase_idx; - default: - alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", param); + /* Now, mix the processed sound data to the output. */ + MixSamples({BufferOut, samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target, + maxz(samplesToDo, 512), 0); } } -void Fshifter_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Fshifter_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Fshifter); struct FshifterStateFactory final : public EffectStateFactory { - EffectState *create() override { return new FshifterState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Fshifter_vtable; } + al::intrusive_ptr create() override + { return al::intrusive_ptr{new FshifterState{}}; } }; -EffectProps FshifterStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY; - props.Fshifter.LeftDirection = AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION; - props.Fshifter.RightDirection = AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION; - return props; -} - } // namespace EffectStateFactory *FshifterStateFactory_getFactory() diff --git a/modules/openal-soft/Alc/effects/modulator.cpp b/modules/openal-soft/Alc/effects/modulator.cpp index 0ddd051..84561f5 100644 --- a/modules/openal-soft/Alc/effects/modulator.cpp +++ b/modules/openal-soft/Alc/effects/modulator.cpp @@ -20,55 +20,55 @@ #include "config.h" -#include -#include - -#include #include - -#include "alMain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" -#include "filters/biquad.h" -#include "vecmat.h" +#include +#include +#include + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/filters/biquad.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" namespace { +using uint = unsigned int; + #define MAX_UPDATE_SAMPLES 128 #define WAVEFORM_FRACBITS 24 #define WAVEFORM_FRACONE (1<(index) * - (al::MathDefs::Tau() / ALfloat{WAVEFORM_FRACONE})); + constexpr float scale{al::numbers::pi_v*2.0f / WAVEFORM_FRACONE}; + return std::sin(static_cast(index) * scale); } -inline ALfloat Saw(ALsizei index) -{ - return static_cast(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f; -} +inline float Saw(uint index) +{ return static_cast(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f; } -inline ALfloat Square(ALsizei index) -{ - return static_cast(((index>>(WAVEFORM_FRACBITS-2))&2) - 1); -} +inline float Square(uint index) +{ return static_cast(static_cast((index>>(WAVEFORM_FRACBITS-2))&2) - 1); } -inline ALfloat One(ALsizei UNUSED(index)) -{ - return 1.0f; -} +inline float One(uint) { return 1.0f; } -template -void Modulate(ALfloat *RESTRICT dst, ALsizei index, const ALsizei step, ALsizei todo) +template +void Modulate(float *RESTRICT dst, uint index, const uint step, size_t todo) { - ALsizei i; - for(i = 0;i < todo;i++) + for(size_t i{0u};i < todo;i++) { index += step; index &= WAVEFORM_FRACMASK; @@ -78,95 +78,90 @@ void Modulate(ALfloat *RESTRICT dst, ALsizei index, const ALsizei step, ALsizei struct ModulatorState final : public EffectState { - void (*mGetSamples)(ALfloat*RESTRICT, ALsizei, const ALsizei, ALsizei){}; + void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){}; - ALsizei mIndex{0}; - ALsizei mStep{1}; + uint mIndex{0}; + uint mStep{1}; struct { BiquadFilter Filter; - ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{}; - ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{}; - } mChans[MAX_AMBI_CHANNELS]; + float CurrentGains[MAX_OUTPUT_CHANNELS]{}; + float TargetGains[MAX_OUTPUT_CHANNELS]{}; + } mChans[MaxAmbiChannels]; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override; + void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) override; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) override; DEF_NEWDEL(ModulatorState) }; -ALboolean ModulatorState::deviceUpdate(const ALCdevice *UNUSED(device)) +void ModulatorState::deviceUpdate(const DeviceBase*, const Buffer&) { for(auto &e : mChans) { e.Filter.clear(); std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); } - return AL_TRUE; } -void ModulatorState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void ModulatorState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->Device}; + const DeviceBase *device{context->mDevice}; - const float step{props->Modulator.Frequency / static_cast(device->Frequency)}; - mStep = fastf2i(clampf(step*WAVEFORM_FRACONE, 0.0f, ALfloat{WAVEFORM_FRACONE-1})); + const float step{props->Modulator.Frequency / static_cast(device->Frequency)}; + mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1})); if(mStep == 0) mGetSamples = Modulate; - else if(props->Modulator.Waveform == AL_RING_MODULATOR_SINUSOID) + else if(props->Modulator.Waveform == ModulatorWaveform::Sinusoid) mGetSamples = Modulate; - else if(props->Modulator.Waveform == AL_RING_MODULATOR_SAWTOOTH) + else if(props->Modulator.Waveform == ModulatorWaveform::Sawtooth) mGetSamples = Modulate; - else /*if(Slot->Params.EffectProps.Modulator.Waveform == AL_RING_MODULATOR_SQUARE)*/ + else /*if(props->Modulator.Waveform == ModulatorWaveform::Square)*/ mGetSamples = Modulate; - ALfloat f0norm{props->Modulator.HighPassCutoff / static_cast(device->Frequency)}; + float f0norm{props->Modulator.HighPassCutoff / static_cast(device->Frequency)}; f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f); /* Bandwidth value is constant in octaves. */ - mChans[0].Filter.setParams(BiquadType::HighPass, 1.0f, f0norm, - calc_rcpQ_from_bandwidth(f0norm, 0.75f)); - for(ALsizei i{1};i < slot->Wet.NumChannels;++i) + mChans[0].Filter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f); + for(size_t i{1u};i < slot->Wet.Buffer.size();++i) mChans[i].Filter.copyParamsFrom(mChans[0].Filter); - mOutBuffer = target.Main->Buffer; - mOutChannels = target.Main->NumChannels; - for(ALsizei i{0};i < slot->Wet.NumChannels;++i) - { - auto coeffs = GetAmbiIdentityRow(i); - ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains); - } + mOutTarget = target.Main->Buffer; + auto set_gains = [slot,target](auto &chan, al::span coeffs) + { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); }; + SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains); } -void ModulatorState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) +void ModulatorState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - const ALsizei step = mStep; - ALsizei base; - - for(base = 0;base < samplesToDo;) + for(size_t base{0u};base < samplesToDo;) { - alignas(16) ALfloat modsamples[MAX_UPDATE_SAMPLES]; - ALsizei td = mini(MAX_UPDATE_SAMPLES, samplesToDo-base); - ALsizei c, i; + alignas(16) float modsamples[MAX_UPDATE_SAMPLES]; + const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)}; - mGetSamples(modsamples, mIndex, step, td); - mIndex += (step*td) & WAVEFORM_FRACMASK; + mGetSamples(modsamples, mIndex, mStep, td); + mIndex += static_cast(mStep * td); mIndex &= WAVEFORM_FRACMASK; - ASSUME(numInput > 0); - for(c = 0;c < numInput;c++) + auto chandata = std::begin(mChans); + for(const auto &input : samplesIn) { - alignas(16) ALfloat temps[MAX_UPDATE_SAMPLES]; + alignas(16) float temps[MAX_UPDATE_SAMPLES]; - mChans[c].Filter.process(temps, &samplesIn[c][base], td); - for(i = 0;i < td;i++) + chandata->Filter.process({&input[base], td}, temps); + for(size_t i{0u};i < td;i++) temps[i] *= modsamples[i]; - MixSamples(temps, numOutput, samplesOut, mChans[c].CurrentGains, - mChans[c].TargetGains, samplesToDo-base, base, td); + MixSamples({temps, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains, + samplesToDo-base, base); + ++chandata; } base += td; @@ -174,106 +169,11 @@ void ModulatorState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT sampl } -void Modulator_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator frequency out of range"); - props->Modulator.Frequency = val; - break; - - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator high-pass cutoff out of range"); - props->Modulator.HighPassCutoff = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param); - } -} -void Modulator_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ Modulator_setParamf(props, context, param, vals[0]); } -void Modulator_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - Modulator_setParamf(props, context, param, static_cast(val)); - break; - - case AL_RING_MODULATOR_WAVEFORM: - if(!(val >= AL_RING_MODULATOR_MIN_WAVEFORM && val <= AL_RING_MODULATOR_MAX_WAVEFORM)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid modulator waveform"); - props->Modulator.Waveform = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param); - } -} -void Modulator_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Modulator_setParami(props, context, param, vals[0]); } - -void Modulator_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - *val = static_cast(props->Modulator.Frequency); - break; - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - *val = static_cast(props->Modulator.HighPassCutoff); - break; - case AL_RING_MODULATOR_WAVEFORM: - *val = props->Modulator.Waveform; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param); - } -} -void Modulator_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Modulator_getParami(props, context, param, vals); } -void Modulator_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - *val = props->Modulator.Frequency; - break; - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - *val = props->Modulator.HighPassCutoff; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param); - } -} -void Modulator_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ Modulator_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(Modulator); - - struct ModulatorStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ModulatorState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Modulator_vtable; } + al::intrusive_ptr create() override + { return al::intrusive_ptr{new ModulatorState{}}; } }; -EffectProps ModulatorStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY; - props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF; - props.Modulator.Waveform = AL_RING_MODULATOR_DEFAULT_WAVEFORM; - return props; -} - } // namespace EffectStateFactory *ModulatorStateFactory_getFactory() diff --git a/modules/openal-soft/Alc/effects/null.cpp b/modules/openal-soft/Alc/effects/null.cpp index 5a2e4d7..cda1420 100644 --- a/modules/openal-soft/Alc/effects/null.cpp +++ b/modules/openal-soft/Alc/effects/null.cpp @@ -1,14 +1,17 @@ + #include "config.h" -#include +#include -#include "AL/al.h" -#include "AL/alc.h" +#include "almalloc.h" +#include "alspan.h" +#include "base.h" +#include "core/bufferline.h" +#include "intrusive_ptr.h" -#include "alMain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" +struct ContextBase; +struct DeviceBase; +struct EffectSlot; namespace { @@ -17,9 +20,11 @@ struct NullState final : public EffectState { NullState(); ~NullState() override; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override; + void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) override; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) override; DEF_NEWDEL(NullState) }; @@ -34,20 +39,20 @@ NullState::NullState() = default; */ NullState::~NullState() = default; -/* This updates the device-dependant effect state. This is called on +/* This updates the device-dependant effect state. This is called on state * initialization and any time the device parameters (e.g. playback frequency, * format) have been changed. Will always be followed by a call to the update * method, if successful. */ -ALboolean NullState::deviceUpdate(const ALCdevice* UNUSED(device)) +void NullState::deviceUpdate(const DeviceBase* /*device*/, const Buffer& /*buffer*/) { - return AL_TRUE; } -/* This updates the effect state. This is called any time the effect is - * (re)loaded into a slot. +/* This updates the effect state with new properties. This is called any time + * the effect is (re)loaded into a slot. */ -void NullState::update(const ALCcontext* UNUSED(context), const ALeffectslot* UNUSED(slot), const EffectProps* UNUSED(props), const EffectTarget UNUSED(target)) +void NullState::update(const ContextBase* /*context*/, const EffectSlot* /*slot*/, + const EffectProps* /*props*/, const EffectTarget /*target*/) { } @@ -55,102 +60,20 @@ void NullState::update(const ALCcontext* UNUSED(context), const ALeffectslot* UN * input to the output buffer. The result should be added to the output buffer, * not replace it. */ -void NullState::process(ALsizei /*samplesToDo*/, const ALfloat (*RESTRICT /*samplesIn*/)[BUFFERSIZE], const ALsizei /*numInput*/, ALfloat (*RESTRICT /*samplesOut*/)[BUFFERSIZE], const ALsizei /*numOutput*/) -{ -} - - -void NullEffect_setParami(EffectProps *UNUSED(props), ALCcontext *context, ALenum param, ALint UNUSED(val)) -{ - switch(param) - { - default: - alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param); - } -} -void NullEffect_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ - switch(param) - { - default: - NullEffect_setParami(props, context, param, vals[0]); - } -} -void NullEffect_setParamf(EffectProps *UNUSED(props), ALCcontext *context, ALenum param, ALfloat UNUSED(val)) -{ - switch(param) - { - default: - alSetError(context, AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param); - } -} -void NullEffect_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ - switch(param) - { - default: - NullEffect_setParamf(props, context, param, vals[0]); - } -} - -void NullEffect_getParami(const EffectProps *UNUSED(props), ALCcontext *context, ALenum param, ALint* UNUSED(val)) -{ - switch(param) - { - default: - alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param); - } -} -void NullEffect_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ - switch(param) - { - default: - NullEffect_getParami(props, context, param, vals); - } -} -void NullEffect_getParamf(const EffectProps *UNUSED(props), ALCcontext *context, ALenum param, ALfloat* UNUSED(val)) -{ - switch(param) - { - default: - alSetError(context, AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param); - } -} -void NullEffect_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) +void NullState::process(const size_t/*samplesToDo*/, + const al::span /*samplesIn*/, + const al::span /*samplesOut*/) { - switch(param) - { - default: - NullEffect_getParamf(props, context, param, vals); - } } -DEFINE_ALEFFECT_VTABLE(NullEffect); - struct NullStateFactory final : public EffectStateFactory { - EffectState *create() override; - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override; + al::intrusive_ptr create() override; }; /* Creates EffectState objects of the appropriate type. */ -EffectState *NullStateFactory::create() -{ return new NullState{}; } - -/* Returns an ALeffectProps initialized with this effect type's default - * property values. - */ -EffectProps NullStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - return props; -} - -/* Returns a pointer to this effect type's global set/get vtable. */ -const EffectVtable *NullStateFactory::getEffectVtable() const noexcept -{ return &NullEffect_vtable; } +al::intrusive_ptr NullStateFactory::create() +{ return al::intrusive_ptr{new NullState{}}; } } // namespace diff --git a/modules/openal-soft/Alc/effects/pshifter.cpp b/modules/openal-soft/Alc/effects/pshifter.cpp index 1d85fb9..aa20c66 100644 --- a/modules/openal-soft/Alc/effects/pshifter.cpp +++ b/modules/openal-soft/Alc/effects/pshifter.cpp @@ -20,27 +20,33 @@ #include "config.h" -#ifdef HAVE_SSE_INTRINSICS -#include -#endif - -#include -#include +#include #include +#include #include -#include - -#include "alMain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alu.h" +#include +#include +#include "alc/effects/base.h" #include "alcomplex.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" + +struct ContextBase; namespace { +using uint = unsigned int; using complex_d = std::complex; #define STFT_SIZE 1024 @@ -50,353 +56,215 @@ using complex_d = std::complex; #define STFT_STEP (STFT_SIZE / OVERSAMP) #define FIFO_LATENCY (STFT_STEP * (OVERSAMP-1)) -inline int double2int(double d) -{ -#if defined(HAVE_SSE_INTRINSICS) - return _mm_cvttsd_si32(_mm_set_sd(d)); - -#elif ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \ - !defined(__SSE2_MATH__)) || (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2) - - int sign, shift; - int64_t mant; - union { - double d; - int64_t i64; - } conv; - - conv.d = d; - sign = (conv.i64>>63) | 1; - shift = ((conv.i64>>52)&0x7ff) - (1023+52); - - /* Over/underflow */ - if(UNLIKELY(shift >= 63 || shift < -52)) - return 0; - - mant = (conv.i64&0xfffffffffffff_i64) | 0x10000000000000_i64; - if(LIKELY(shift < 0)) - return (int)(mant >> -shift) * sign; - return (int)(mant << shift) * sign; - -#else - - return static_cast(d); -#endif -} - /* Define a Hann window, used to filter the STFT input and output. */ -/* Making this constexpr seems to require C++14. */ -std::array InitHannWindow() +std::array InitHannWindow() { - std::array ret; - /* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */ - for(ALsizei i{0};i < STFT_SIZE>>1;i++) + std::array ret; + /* Create lookup table of the Hann window for the desired size, i.e. STFT_SIZE */ + for(size_t i{0};i < STFT_SIZE>>1;i++) { - ALdouble val = std::sin(al::MathDefs::Pi() * i / ALdouble{STFT_SIZE-1}); + constexpr double scale{al::numbers::pi / double{STFT_SIZE}}; + const double val{std::sin(static_cast(i+1) * scale)}; ret[i] = ret[STFT_SIZE-1-i] = val * val; } return ret; } -alignas(16) const std::array HannWindow = InitHannWindow(); - +alignas(16) const std::array HannWindow = InitHannWindow(); -struct ALphasor { - ALdouble Amplitude; - ALdouble Phase; -}; -struct ALfrequencyDomain { - ALdouble Amplitude; - ALdouble Frequency; +struct FrequencyBin { + double Amplitude; + double FreqBin; }; -/* Converts complex to ALphasor */ -inline ALphasor rect2polar(const complex_d &number) -{ - ALphasor polar; - polar.Amplitude = std::abs(number); - polar.Phase = std::arg(number); - return polar; -} - -/* Converts ALphasor to complex */ -inline complex_d polar2rect(const ALphasor &number) -{ return std::polar(number.Amplitude, number.Phase); } - - struct PshifterState final : public EffectState { /* Effect parameters */ - ALsizei mCount; - ALsizei mPitchShiftI; - ALfloat mPitchShift; - ALfloat mFreqPerBin; + size_t mCount; + size_t mPos; + uint mPitchShiftI; + double mPitchShift; /* Effects buffers */ - ALfloat mInFIFO[STFT_SIZE]; - ALfloat mOutFIFO[STFT_STEP]; - ALdouble mLastPhase[STFT_HALF_SIZE+1]; - ALdouble mSumPhase[STFT_HALF_SIZE+1]; - ALdouble mOutputAccum[STFT_SIZE]; + std::array mFIFO; + std::array mLastPhase; + std::array mSumPhase; + std::array mOutputAccum; - complex_d mFFTbuffer[STFT_SIZE]; + std::array mFftBuffer; - ALfrequencyDomain mAnalysis_buffer[STFT_HALF_SIZE+1]; - ALfrequencyDomain mSyntesis_buffer[STFT_HALF_SIZE+1]; + std::array mAnalysisBuffer; + std::array mSynthesisBuffer; - alignas(16) ALfloat mBufferOut[BUFFERSIZE]; + alignas(16) FloatBufferLine mBufferOut; /* Effect gains for each output channel */ - ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS]; - ALfloat mTargetGains[MAX_OUTPUT_CHANNELS]; + float mCurrentGains[MAX_OUTPUT_CHANNELS]; + float mTargetGains[MAX_OUTPUT_CHANNELS]; - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override; + void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) override; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) override; DEF_NEWDEL(PshifterState) }; -ALboolean PshifterState::deviceUpdate(const ALCdevice *device) +void PshifterState::deviceUpdate(const DeviceBase*, const Buffer&) { /* (Re-)initializing parameters and clear the buffers. */ - mCount = FIFO_LATENCY; - mPitchShiftI = FRACTIONONE; - mPitchShift = 1.0f; - mFreqPerBin = device->Frequency / static_cast(STFT_SIZE); - - std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0f); - std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), 0.0f); - std::fill(std::begin(mLastPhase), std::end(mLastPhase), 0.0); - std::fill(std::begin(mSumPhase), std::end(mSumPhase), 0.0); - std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), 0.0); - std::fill(std::begin(mFFTbuffer), std::end(mFFTbuffer), complex_d{}); - std::fill(std::begin(mAnalysis_buffer), std::end(mAnalysis_buffer), ALfrequencyDomain{}); - std::fill(std::begin(mSyntesis_buffer), std::end(mSyntesis_buffer), ALfrequencyDomain{}); + mCount = 0; + mPos = FIFO_LATENCY; + mPitchShiftI = MixerFracOne; + mPitchShift = 1.0; + + std::fill(mFIFO.begin(), mFIFO.end(), 0.0); + std::fill(mLastPhase.begin(), mLastPhase.end(), 0.0); + std::fill(mSumPhase.begin(), mSumPhase.end(), 0.0); + std::fill(mOutputAccum.begin(), mOutputAccum.end(), 0.0); + std::fill(mFftBuffer.begin(), mFftBuffer.end(), complex_d{}); + std::fill(mAnalysisBuffer.begin(), mAnalysisBuffer.end(), FrequencyBin{}); + std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{}); std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f); std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f); - - return AL_TRUE; } -void PshifterState::update(const ALCcontext* UNUSED(context), const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) +void PshifterState::update(const ContextBase*, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) { - const float pitch{std::pow(2.0f, - static_cast(props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune) / 1200.0f - )}; - mPitchShiftI = fastf2i(pitch*FRACTIONONE); - mPitchShift = mPitchShiftI * (1.0f/FRACTIONONE); - - ALfloat coeffs[MAX_AMBI_CHANNELS]; - CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs); - - mOutBuffer = target.Main->Buffer; - mOutChannels = target.Main->NumChannels; - ComputePanGains(target.Main, coeffs, slot->Params.Gain, mTargetGains); + const int tune{props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune}; + const float pitch{std::pow(2.0f, static_cast(tune) / 1200.0f)}; + mPitchShiftI = fastf2u(pitch*MixerFracOne); + mPitchShift = mPitchShiftI * double{1.0/MixerFracOne}; + + const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f); + + mOutTarget = target.Main->Buffer; + ComputePanGains(target.Main, coeffs.data(), slot->Gain, mTargetGains); } -void PshifterState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei /*numInput*/, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) +void PshifterState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { /* Pitch shifter engine based on the work of Stephan Bernsee. * http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/ */ - static constexpr ALdouble expected{al::MathDefs::Tau() / OVERSAMP}; - const ALdouble freq_per_bin{mFreqPerBin}; - ALfloat *RESTRICT bufferOut{mBufferOut}; - ALsizei count{mCount}; + /* Cycle offset per update expected of each frequency bin (bin 0 is none, + * bin 1 is x1, bin 2 is x2, etc). + */ + constexpr double expected_cycles{al::numbers::pi*2.0 / OVERSAMP}; - for(ALsizei i{0};i < samplesToDo;) + for(size_t base{0u};base < samplesToDo;) { - do { - /* Fill FIFO buffer with samples data */ - mInFIFO[count] = samplesIn[0][i]; - bufferOut[i] = mOutFIFO[count - FIFO_LATENCY]; + const size_t todo{minz(STFT_STEP-mCount, samplesToDo-base)}; - count++; - } while(++i < samplesToDo && count < STFT_SIZE); + /* Retrieve the output samples from the FIFO and fill in the new input + * samples. + */ + auto fifo_iter = mFIFO.begin()+mPos + mCount; + std::transform(fifo_iter, fifo_iter+todo, mBufferOut.begin()+base, + [](double d) noexcept -> float { return static_cast(d); }); - /* Check whether FIFO buffer is filled */ - if(count < STFT_SIZE) break; - count = FIFO_LATENCY; + std::copy_n(samplesIn[0].begin()+base, todo, fifo_iter); + mCount += todo; + base += todo; - /* Real signal windowing and store in FFTbuffer */ - for(ALsizei k{0};k < STFT_SIZE;k++) - { - mFFTbuffer[k].real(mInFIFO[k] * HannWindow[k]); - mFFTbuffer[k].imag(0.0); - } + /* Check whether FIFO buffer is filled with new samples. */ + if(mCount < STFT_STEP) break; + mCount = 0; + mPos = (mPos+STFT_STEP) & (mFIFO.size()-1); - /* ANALYSIS */ - /* Apply FFT to FFTbuffer data */ - complex_fft(mFFTbuffer, STFT_SIZE, -1.0); + /* Time-domain signal windowing, store in FftBuffer, and apply a + * forward FFT to get the frequency-domain signal. + */ + for(size_t src{mPos}, k{0u};src < STFT_SIZE;++src,++k) + mFftBuffer[k] = mFIFO[src] * HannWindow[k]; + for(size_t src{0u}, k{STFT_SIZE-mPos};src < mPos;++src,++k) + mFftBuffer[k] = mFIFO[src] * HannWindow[k]; + forward_fft(mFftBuffer); /* Analyze the obtained data. Since the real FFT is symmetric, only * STFT_HALF_SIZE+1 samples are needed. */ - for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++) + for(size_t k{0u};k < STFT_HALF_SIZE+1;k++) { - /* Compute amplitude and phase */ - ALphasor component{rect2polar(mFFTbuffer[k])}; + const double amplitude{std::abs(mFftBuffer[k])}; + const double phase{std::arg(mFftBuffer[k])}; /* Compute phase difference and subtract expected phase difference */ - double tmp{(component.Phase - mLastPhase[k]) - k*expected}; + double tmp{(phase - mLastPhase[k]) - static_cast(k)*expected_cycles}; /* Map delta phase into +/- Pi interval */ - int qpd{double2int(tmp / al::MathDefs::Pi())}; - tmp -= al::MathDefs::Pi() * (qpd + (qpd%2)); + int qpd{double2int(tmp / al::numbers::pi)}; + tmp -= al::numbers::pi * (qpd + (qpd%2)); /* Get deviation from bin frequency from the +/- Pi interval */ - tmp /= expected; + tmp /= expected_cycles; - /* Compute the k-th partials' true frequency, twice the amplitude - * for maintain the gain (because half of bins are used) and store - * amplitude and true frequency in analysis buffer. + /* Compute the k-th partials' true frequency and store the + * amplitude and frequency bin in the analysis buffer. */ - mAnalysis_buffer[k].Amplitude = 2.0 * component.Amplitude; - mAnalysis_buffer[k].Frequency = (k + tmp) * freq_per_bin; - - /* Store actual phase[k] for the calculations in the next frame*/ - mLastPhase[k] = component.Phase; - } + mAnalysisBuffer[k].Amplitude = amplitude; + mAnalysisBuffer[k].FreqBin = static_cast(k) + tmp; - /* PROCESSING */ - /* pitch shifting */ - for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++) - { - mSyntesis_buffer[k].Amplitude = 0.0; - mSyntesis_buffer[k].Frequency = 0.0; + /* Store the actual phase[k] for the next frame. */ + mLastPhase[k] = phase; } - for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++) + /* Shift the frequency bins according to the pitch adjustment, + * accumulating the amplitudes of overlapping frequency bins. + */ + std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{}); + const size_t bin_count{minz(STFT_HALF_SIZE+1, + (((STFT_HALF_SIZE+1)<>1) - 1)/mPitchShiftI + 1)}; + for(size_t k{0u};k < bin_count;k++) { - ALsizei j{(k*mPitchShiftI) >> FRACTIONBITS}; - if(j >= STFT_HALF_SIZE+1) break; - - mSyntesis_buffer[j].Amplitude += mAnalysis_buffer[k].Amplitude; - mSyntesis_buffer[j].Frequency = mAnalysis_buffer[k].Frequency * mPitchShift; + const size_t j{(k*mPitchShiftI + (MixerFracOne>>1)) >> MixerFracBits}; + mSynthesisBuffer[j].Amplitude += mAnalysisBuffer[k].Amplitude; + mSynthesisBuffer[j].FreqBin = mAnalysisBuffer[k].FreqBin * mPitchShift; } - /* SYNTHESIS */ - /* Synthesis the processing data */ - for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++) + /* Reconstruct the frequency-domain signal from the adjusted frequency + * bins. + */ + for(size_t k{0u};k < STFT_HALF_SIZE+1;k++) { - ALphasor component; - ALdouble tmp; - - /* Compute bin deviation from scaled freq */ - tmp = mSyntesis_buffer[k].Frequency/freq_per_bin - k; - /* Calculate actual delta phase and accumulate it to get bin phase */ - mSumPhase[k] += (k + tmp) * expected; + mSumPhase[k] += mSynthesisBuffer[k].FreqBin * expected_cycles; - component.Amplitude = mSyntesis_buffer[k].Amplitude; - component.Phase = mSumPhase[k]; - - /* Compute phasor component to cartesian complex number and storage it into FFTbuffer*/ - mFFTbuffer[k] = polar2rect(component); + mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Amplitude, mSumPhase[k]); } - /* zero negative frequencies for recontruct a real signal */ - for(ALsizei k{STFT_HALF_SIZE+1};k < STFT_SIZE;k++) - mFFTbuffer[k] = complex_d{}; - - /* Apply iFFT to buffer data */ - complex_fft(mFFTbuffer, STFT_SIZE, 1.0); - - /* Windowing and add to output */ - for(ALsizei k{0};k < STFT_SIZE;k++) - mOutputAccum[k] += HannWindow[k] * mFFTbuffer[k].real() / - (0.5 * STFT_HALF_SIZE * OVERSAMP); - - /* Shift accumulator, input & output FIFO */ - ALsizei j, k; - for(k = 0;k < STFT_STEP;k++) mOutFIFO[k] = static_cast(mOutputAccum[k]); - for(j = 0;k < STFT_SIZE;k++,j++) mOutputAccum[j] = mOutputAccum[k]; - for(;j < STFT_SIZE;j++) mOutputAccum[j] = 0.0; - for(k = 0;k < FIFO_LATENCY;k++) - mInFIFO[k] = mInFIFO[k+STFT_STEP]; - } - mCount = count; - - /* Now, mix the processed sound data to the output. */ - MixSamples(bufferOut, numOutput, samplesOut, mCurrentGains, mTargetGains, - maxi(samplesToDo, 512), 0, samplesToDo); -} - + for(size_t k{STFT_HALF_SIZE+1};k < STFT_SIZE;++k) + mFftBuffer[k] = std::conj(mFftBuffer[STFT_SIZE-k]); -void Pshifter_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat) -{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); } -void Pshifter_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", param); } - -void Pshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_PITCH_SHIFTER_COARSE_TUNE: - if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter coarse tune out of range"); - props->Pshifter.CoarseTune = val; - break; - - case AL_PITCH_SHIFTER_FINE_TUNE: - if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE)) - SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter fine tune out of range"); - props->Pshifter.FineTune = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", param); + /* Apply an inverse FFT to get the time-domain siganl, and accumulate + * for the output with windowing. + */ + inverse_fft(mFftBuffer); + for(size_t dst{mPos}, k{0u};dst < STFT_SIZE;++dst,++k) + mOutputAccum[dst] += HannWindow[k]*mFftBuffer[k].real() * (4.0/OVERSAMP/STFT_SIZE); + for(size_t dst{0u}, k{STFT_SIZE-mPos};dst < mPos;++dst,++k) + mOutputAccum[dst] += HannWindow[k]*mFftBuffer[k].real() * (4.0/OVERSAMP/STFT_SIZE); + + /* Copy out the accumulated result, then clear for the next iteration. */ + std::copy_n(mOutputAccum.begin() + mPos, STFT_STEP, mFIFO.begin() + mPos); + std::fill_n(mOutputAccum.begin() + mPos, STFT_STEP, 0.0); } -} -void Pshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ Pshifter_setParami(props, context, param, vals[0]); } -void Pshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_PITCH_SHIFTER_COARSE_TUNE: - *val = props->Pshifter.CoarseTune; - break; - case AL_PITCH_SHIFTER_FINE_TUNE: - *val = props->Pshifter.FineTune; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", param); - } + /* Now, mix the processed sound data to the output. */ + MixSamples({mBufferOut.data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains, + maxz(samplesToDo, 512), 0); } -void Pshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ Pshifter_getParami(props, context, param, vals); } - -void Pshifter_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); } -void Pshifter_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*) -{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", param); } - -DEFINE_ALEFFECT_VTABLE(Pshifter); struct PshifterStateFactory final : public EffectStateFactory { - EffectState *create() override; - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &Pshifter_vtable; } + al::intrusive_ptr create() override + { return al::intrusive_ptr{new PshifterState{}}; } }; -EffectState *PshifterStateFactory::create() -{ return new PshifterState{}; } - -EffectProps PshifterStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE; - props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE; - return props; -} - } // namespace EffectStateFactory *PshifterStateFactory_getFactory() diff --git a/modules/openal-soft/Alc/effects/reverb.cpp b/modules/openal-soft/Alc/effects/reverb.cpp index 6b159b0..81c6f86 100644 --- a/modules/openal-soft/Alc/effects/reverb.cpp +++ b/modules/openal-soft/Alc/effects/reverb.cpp @@ -20,48 +20,73 @@ #include "config.h" -#include -#include -#include - -#include -#include #include +#include +#include #include - -#include "alMain.h" -#include "alcontext.h" -#include "alu.h" -#include "alAuxEffectSlot.h" -#include "alListener.h" -#include "alError.h" -#include "bformatdec.h" -#include "filters/biquad.h" -#include "vector.h" +#include +#include +#include + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/filters/biquad.h" +#include "core/filters/splitter.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" +#include "opthelpers.h" #include "vecmat.h" +#include "vector.h" /* This is a user config option for modifying the overall output of the reverb * effect. */ -ALfloat ReverbBoost = 1.0f; +float ReverbBoost = 1.0f; namespace { +using uint = unsigned int; + +constexpr float MaxModulationTime{4.0f}; +constexpr float DefaultModulationTime{0.25f}; + +#define MOD_FRACBITS 24 +#define MOD_FRACONE (1<(1.0/al::numbers::sqrt2); +alignas(16) constexpr float LateA2B[NUM_LINES][NUM_LINES]{ + { 0.5f, 0.5f, 0.5f, 0.5f }, + { InvSqrt2, -InvSqrt2, 0.0f, 0.0f }, + { 0.0f, 0.0f, InvSqrt2, -InvSqrt2 }, + { 0.5f, 0.5f, -0.5f, -0.5f } +}; /* The all-pass and delay lines have a variable length dependent on the * effect's density parameter, which helps alter the perceived environment @@ -104,7 +135,7 @@ constexpr ALfloat FadeStep{1.0f / FADE_SAMPLES}; * The density scale below will result in a max line multiplier of 50, for an * effective size range of 5m to 50m. */ -constexpr ALfloat DENSITY_SCALE{125000.0f}; +constexpr float DENSITY_SCALE{125000.0f}; /* All delay line lengths are specified in seconds. * @@ -150,7 +181,7 @@ constexpr ALfloat DENSITY_SCALE{125000.0f}; * * Assuming an average of 1m, we get the following taps: */ -constexpr std::array EARLY_TAP_LENGTHS{{ +constexpr std::array EARLY_TAP_LENGTHS{{ 0.0000000e+0f, 2.0213520e-4f, 4.2531060e-4f, 6.7171600e-4f }}; @@ -160,7 +191,7 @@ constexpr std::array EARLY_TAP_LENGTHS{{ * * Where a is the approximate maximum all-pass cycle limit (20). */ -constexpr std::array EARLY_ALLPASS_LENGTHS{{ +constexpr std::array EARLY_ALLPASS_LENGTHS{{ 9.7096800e-5f, 1.0720356e-4f, 1.1836234e-4f, 1.3068260e-4f }}; @@ -186,7 +217,7 @@ constexpr std::array EARLY_ALLPASS_LENGTHS{{ * * Using an average dimension of 1m, we get: */ -constexpr std::array EARLY_LINE_LENGTHS{{ +constexpr std::array EARLY_LINE_LENGTHS{{ 5.9850400e-4f, 1.0913150e-3f, 1.5376658e-3f, 1.9419362e-3f }}; @@ -194,7 +225,7 @@ constexpr std::array EARLY_LINE_LENGTHS{{ * * A_i = (5 / 3) L_i / r_1 */ -constexpr std::array LATE_ALLPASS_LENGTHS{{ +constexpr std::array LATE_ALLPASS_LENGTHS{{ 1.6182800e-4f, 2.0389060e-4f, 2.8159360e-4f, 3.2365600e-4f }}; @@ -213,26 +244,54 @@ constexpr std::array LATE_ALLPASS_LENGTHS{{ * * For our 1m average room, we get: */ -constexpr std::array LATE_LINE_LENGTHS{{ +constexpr std::array LATE_LINE_LENGTHS{{ 1.9419362e-3f, 2.4466860e-3f, 3.3791220e-3f, 3.8838720e-3f }}; +using ReverbUpdateLine = std::array; + struct DelayLineI { /* The delay lines use interleaved samples, with the lengths being powers * of 2 to allow the use of bit-masking instead of a modulus for wrapping. */ - ALsizei Mask{0}; - ALfloat (*Line)[NUM_LINES]{nullptr}; + size_t Mask{0u}; + union { + uintptr_t LineOffset{0u}; + std::array *Line; + }; + + /* Given the allocated sample buffer, this function updates each delay line + * offset. + */ + void realizeLineOffset(std::array *sampleBuffer) noexcept + { Line = sampleBuffer + LineOffset; } + /* Calculate the length of a delay line and store its mask and offset. */ + uint calcLineLength(const float length, const uintptr_t offset, const float frequency, + const uint extra) + { + /* All line lengths are powers of 2, calculated from their lengths in + * seconds, rounded up. + */ + uint samples{float2uint(std::ceil(length*frequency))}; + samples = NextPowerOf2(samples + extra); + + /* All lines share a single sample buffer. */ + Mask = samples - 1; + LineOffset = offset; + + /* Return the sample count for accumulation. */ + return samples; + } - void write(ALsizei offset, const ALsizei c, const ALfloat *RESTRICT in, const ALsizei count) const noexcept + void write(size_t offset, const size_t c, const float *RESTRICT in, const size_t count) const noexcept { ASSUME(count > 0); - for(ALsizei i{0};i < count;) + for(size_t i{0u};i < count;) { offset &= Mask; - ALsizei td{mini(Mask+1 - offset, count - i)}; + size_t td{minz(Mask+1 - offset, count - i)}; do { Line[offset++][c] = in[i++]; } while(--td); @@ -242,31 +301,29 @@ struct DelayLineI { struct VecAllpass { DelayLineI Delay; - ALfloat Coeff{0.0f}; - ALsizei Offset[NUM_LINES][2]{}; - - void processFaded(ALfloat (*RESTRICT samples)[BUFFERSIZE], ALsizei offset, - const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fade, const ALsizei todo); - void processUnfaded(ALfloat (*RESTRICT samples)[BUFFERSIZE], ALsizei offset, - const ALfloat xCoeff, const ALfloat yCoeff, const ALsizei todo); + float Coeff{0.0f}; + size_t Offset[NUM_LINES][2]{}; + + void processFaded(const al::span samples, size_t offset, + const float xCoeff, const float yCoeff, float fadeCount, const float fadeStep, + const size_t todo); + void processUnfaded(const al::span samples, size_t offset, + const float xCoeff, const float yCoeff, const size_t todo); }; struct T60Filter { /* Two filters are used to adjust the signal. One to control the low * frequencies, and one to control the high frequencies. */ - ALfloat MidGain[2]{0.0f, 0.0f}; + float MidGain[2]{0.0f, 0.0f}; BiquadFilter HFFilter, LFFilter; - void calcCoeffs(const ALfloat length, const ALfloat lfDecayTime, const ALfloat mfDecayTime, - const ALfloat hfDecayTime, const ALfloat lf0norm, const ALfloat hf0norm); + void calcCoeffs(const float length, const float lfDecayTime, const float mfDecayTime, + const float hfDecayTime, const float lf0norm, const float hf0norm); /* Applies the two T60 damping filter sections. */ - void process(ALfloat *samples, const ALsizei todo) - { - HFFilter.process(samples, samples, todo); - LFFilter.process(samples, samples, todo); - } + void process(const al::span samples) + { DualBiquad{HFFilter, LFFilter}.process(samples, samples.data()); } }; struct EarlyReflections { @@ -279,59 +336,81 @@ struct EarlyReflections { * reflections. */ DelayLineI Delay; - ALsizei Offset[NUM_LINES][2]{}; - ALfloat Coeff[NUM_LINES][2]{}; + size_t Offset[NUM_LINES][2]{}; + float Coeff[NUM_LINES][2]{}; /* The gain for each output channel based on 3D panning. */ - ALfloat CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; - ALfloat PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; + float CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; + float PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; - void updateLines(const ALfloat density, const ALfloat diffusion, const ALfloat decayTime, - const ALfloat frequency); + void updateLines(const float density_mult, const float diffusion, const float decayTime, + const float frequency); +}; + + +struct Modulation { + /* The vibrato time is tracked with an index over a (MOD_FRACONE) + * normalized range. + */ + uint Index, Step; + + /* The depth of frequency change, in samples. */ + float Depth[2]; + + float ModDelays[MAX_UPDATE_SAMPLES]; + + void updateModulator(float modTime, float modDepth, float frequency); + + void calcDelays(size_t todo); + void calcFadedDelays(size_t todo, float fadeCount, float fadeStep); }; struct LateReverb { /* A recursive delay line is used fill in the reverb tail. */ DelayLineI Delay; - ALsizei Offset[NUM_LINES][2]{}; + size_t Offset[NUM_LINES][2]{}; /* Attenuation to compensate for the modal density and decay rate of the * late lines. */ - ALfloat DensityGain[2]{0.0f, 0.0f}; + float DensityGain[2]{0.0f, 0.0f}; /* T60 decay filters are used to simulate absorption. */ T60Filter T60[NUM_LINES]; + Modulation Mod; + /* A Gerzon vector all-pass filter is used to simulate diffusion. */ VecAllpass VecAp; /* The gain for each output channel based on 3D panning. */ - ALfloat CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; - ALfloat PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; + float CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; + float PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; - void updateLines(const ALfloat density, const ALfloat diffusion, const ALfloat lfDecayTime, - const ALfloat mfDecayTime, const ALfloat hfDecayTime, const ALfloat lf0norm, - const ALfloat hf0norm, const ALfloat frequency); + void updateLines(const float density_mult, const float diffusion, const float lfDecayTime, + const float mfDecayTime, const float hfDecayTime, const float lf0norm, + const float hf0norm, const float frequency); }; struct ReverbState final : public EffectState { /* All delay lines are allocated as a single buffer to reduce memory * fragmentation and management code. */ - al::vector mSampleBuffer; + al::vector,16> mSampleBuffer; struct { /* Calculated parameters which indicate if cross-fading is needed after * an update. */ - ALfloat Density{AL_EAXREVERB_DEFAULT_DENSITY}; - ALfloat Diffusion{AL_EAXREVERB_DEFAULT_DIFFUSION}; - ALfloat DecayTime{AL_EAXREVERB_DEFAULT_DECAY_TIME}; - ALfloat HFDecayTime{AL_EAXREVERB_DEFAULT_DECAY_HFRATIO * AL_EAXREVERB_DEFAULT_DECAY_TIME}; - ALfloat LFDecayTime{AL_EAXREVERB_DEFAULT_DECAY_LFRATIO * AL_EAXREVERB_DEFAULT_DECAY_TIME}; - ALfloat HFReference{AL_EAXREVERB_DEFAULT_HFREFERENCE}; - ALfloat LFReference{AL_EAXREVERB_DEFAULT_LFREFERENCE}; + float Density{1.0f}; + float Diffusion{1.0f}; + float DecayTime{1.49f}; + float HFDecayTime{0.83f * 1.49f}; + float LFDecayTime{1.0f * 1.49f}; + float ModulationTime{0.25f}; + float ModulationDepth{0.0f}; + float HFReference{5000.0f}; + float LFReference{250.0f}; } mParams; /* Master effect filters */ @@ -344,109 +423,138 @@ struct ReverbState final : public EffectState { DelayLineI mDelay; /* Tap points for early reflection delay. */ - ALsizei mEarlyDelayTap[NUM_LINES][2]{}; - ALfloat mEarlyDelayCoeff[NUM_LINES][2]{}; + size_t mEarlyDelayTap[NUM_LINES][2]{}; + float mEarlyDelayCoeff[NUM_LINES][2]{}; /* Tap points for late reverb feed and delay. */ - ALsizei mLateFeedTap{}; - ALsizei mLateDelayTap[NUM_LINES][2]{}; + size_t mLateFeedTap{}; + size_t mLateDelayTap[NUM_LINES][2]{}; /* Coefficients for the all-pass and line scattering matrices. */ - ALfloat mMixX{0.0f}; - ALfloat mMixY{0.0f}; + float mMixX{0.0f}; + float mMixY{0.0f}; EarlyReflections mEarly; LateReverb mLate; - /* Indicates the cross-fade point for delay line reads [0,FADE_SAMPLES]. */ - ALsizei mFadeCount{0}; + bool mDoFading{}; /* Maximum number of samples to process at once. */ - ALsizei mMaxUpdate[2]{BUFFERSIZE, BUFFERSIZE}; + size_t mMaxUpdate[2]{MAX_UPDATE_SAMPLES, MAX_UPDATE_SAMPLES}; /* The current write offset for all delay lines. */ - ALsizei mOffset{0}; + size_t mOffset{}; /* Temporary storage used when processing. */ - alignas(16) ALfloat mTempSamples[NUM_LINES][BUFFERSIZE]{}; - alignas(16) ALfloat mEarlyBuffer[NUM_LINES][BUFFERSIZE]{}; - alignas(16) ALfloat mLateBuffer[NUM_LINES][BUFFERSIZE]{}; + union { + alignas(16) FloatBufferLine mTempLine{}; + alignas(16) std::array mTempSamples; + }; + alignas(16) std::array mEarlySamples{}; + alignas(16) std::array mLateSamples{}; - using MixOutT = void (ReverbState::*)(const ALsizei numOutput, - ALfloat (*samplesOut)[BUFFERSIZE], const ALsizei todo); + using MixOutT = void (ReverbState::*)(const al::span samplesOut, + const size_t counter, const size_t offset, const size_t todo); MixOutT mMixOut{&ReverbState::MixOutPlain}; - std::array mOrderScales{}; + std::array mOrderScales{}; std::array,2> mAmbiSplitter; - void MixOutPlain(const ALsizei numOutput, ALfloat (*samplesOut)[BUFFERSIZE], - const ALsizei todo) + static void DoMixRow(const al::span OutBuffer, const al::span Gains, + const float *InSamples, const size_t InStride) + { + std::fill(OutBuffer.begin(), OutBuffer.end(), 0.0f); + for(const float gain : Gains) + { + const float *RESTRICT input{al::assume_aligned<16>(InSamples)}; + InSamples += InStride; + + if(!(std::fabs(gain) > GainSilenceThreshold)) + continue; + + for(float &sample : OutBuffer) + { + sample += *input * gain; + ++input; + } + } + } + + + void MixOutPlain(const al::span samplesOut, const size_t counter, + const size_t offset, const size_t todo) { ASSUME(todo > 0); /* Convert back to B-Format, and mix the results to output. */ - for(ALsizei c{0};c < NUM_LINES;c++) + const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), todo}; + for(size_t c{0u};c < NUM_LINES;c++) { - std::fill_n(std::begin(mTempSamples[0]), todo, 0.0f); - MixRowSamples(mTempSamples[0], A2B[c], mEarlyBuffer, NUM_LINES, 0, todo); - MixSamples(mTempSamples[0], numOutput, samplesOut, mEarly.CurrentGain[c], - mEarly.PanGain[c], todo, 0, todo); + DoMixRow(tmpspan, EarlyA2B[c], mEarlySamples[0].data(), mEarlySamples[0].size()); + MixSamples(tmpspan, samplesOut, mEarly.CurrentGain[c], mEarly.PanGain[c], counter, + offset); } - - for(ALsizei c{0};c < NUM_LINES;c++) + for(size_t c{0u};c < NUM_LINES;c++) { - std::fill_n(std::begin(mTempSamples[0]), todo, 0.0f); - MixRowSamples(mTempSamples[0], A2B[c], mLateBuffer, NUM_LINES, 0, todo); - MixSamples(mTempSamples[0], numOutput, samplesOut, mLate.CurrentGain[c], - mLate.PanGain[c], todo, 0, todo); + DoMixRow(tmpspan, LateA2B[c], mLateSamples[0].data(), mLateSamples[0].size()); + MixSamples(tmpspan, samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], counter, + offset); } } - void MixOutAmbiUp(const ALsizei numOutput, ALfloat (*samplesOut)[BUFFERSIZE], - const ALsizei todo) + void MixOutAmbiUp(const al::span samplesOut, const size_t counter, + const size_t offset, const size_t todo) { ASSUME(todo > 0); - for(ALsizei c{0};c < NUM_LINES;c++) + const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), todo}; + for(size_t c{0u};c < NUM_LINES;c++) { - std::fill_n(std::begin(mTempSamples[0]), todo, 0.0f); - MixRowSamples(mTempSamples[0], A2B[c], mEarlyBuffer, NUM_LINES, 0, todo); + DoMixRow(tmpspan, EarlyA2B[c], mEarlySamples[0].data(), mEarlySamples[0].size()); /* Apply scaling to the B-Format's HF response to "upsample" it to * higher-order output. */ - const ALfloat hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; - mAmbiSplitter[0][c].applyHfScale(mTempSamples[0], hfscale, todo); + const float hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; + mAmbiSplitter[0][c].processHfScale(tmpspan, hfscale); - MixSamples(mTempSamples[0], numOutput, samplesOut, mEarly.CurrentGain[c], - mEarly.PanGain[c], todo, 0, todo); + MixSamples(tmpspan, samplesOut, mEarly.CurrentGain[c], mEarly.PanGain[c], counter, + offset); } - - for(ALsizei c{0};c < NUM_LINES;c++) + for(size_t c{0u};c < NUM_LINES;c++) { - std::fill_n(std::begin(mTempSamples[0]), todo, 0.0f); - MixRowSamples(mTempSamples[0], A2B[c], mLateBuffer, NUM_LINES, 0, todo); + DoMixRow(tmpspan, LateA2B[c], mLateSamples[0].data(), mLateSamples[0].size()); - const ALfloat hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; - mAmbiSplitter[1][c].applyHfScale(mTempSamples[0], hfscale, todo); + const float hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; + mAmbiSplitter[1][c].processHfScale(tmpspan, hfscale); - MixSamples(mTempSamples[0], numOutput, samplesOut, mLate.CurrentGain[c], - mLate.PanGain[c], todo, 0, todo); + MixSamples(tmpspan, samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], counter, + offset); } } - bool allocLines(const ALfloat frequency); + void allocLines(const float frequency); - void updateDelayLine(const ALfloat earlyDelay, const ALfloat lateDelay, const ALfloat density, - const ALfloat decayTime, const ALfloat frequency); - void update3DPanning(const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan, - const ALfloat earlyGain, const ALfloat lateGain, const EffectTarget &target); + void updateDelayLine(const float earlyDelay, const float lateDelay, const float density_mult, + const float decayTime, const float frequency); + void update3DPanning(const float *ReflectionsPan, const float *LateReverbPan, + const float earlyGain, const float lateGain, const EffectTarget &target); - ALboolean deviceUpdate(const ALCdevice *device) override; - void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; - void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override; + void earlyUnfaded(const size_t offset, const size_t todo); + void earlyFaded(const size_t offset, const size_t todo, const float fade, + const float fadeStep); + + void lateUnfaded(const size_t offset, const size_t todo); + void lateFaded(const size_t offset, const size_t todo, const float fade, + const float fadeStep); + + void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) override; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) override; DEF_NEWDEL(ReverbState) }; @@ -455,117 +563,86 @@ struct ReverbState final : public EffectState { * Device Update * **************************************/ -inline ALfloat CalcDelayLengthMult(ALfloat density) +inline float CalcDelayLengthMult(float density) { return maxf(5.0f, std::cbrt(density*DENSITY_SCALE)); } -/* Given the allocated sample buffer, this function updates each delay line - * offset. - */ -inline ALvoid RealizeLineOffset(ALfloat *sampleBuffer, DelayLineI *Delay) -{ - union { - ALfloat *f; - ALfloat (*f4)[NUM_LINES]; - } u; - u.f = &sampleBuffer[reinterpret_cast(Delay->Line) * NUM_LINES]; - Delay->Line = u.f4; -} - -/* Calculate the length of a delay line and store its mask and offset. */ -ALuint CalcLineLength(const ALfloat length, const ptrdiff_t offset, const ALfloat frequency, - const ALuint extra, DelayLineI *Delay) -{ - /* All line lengths are powers of 2, calculated from their lengths in - * seconds, rounded up. - */ - auto samples = static_cast(float2int(std::ceil(length*frequency))); - samples = NextPowerOf2(samples + extra); - - /* All lines share a single sample buffer. */ - Delay->Mask = samples - 1; - Delay->Line = reinterpret_cast(offset); - - /* Return the sample count for accumulation. */ - return samples; -} - /* Calculates the delay line metrics and allocates the shared sample buffer - * for all lines given the sample rate (frequency). If an allocation failure - * occurs, it returns AL_FALSE. + * for all lines given the sample rate (frequency). */ -bool ReverbState::allocLines(const ALfloat frequency) +void ReverbState::allocLines(const float frequency) { /* All delay line lengths are calculated to accomodate the full range of * lengths given their respective paramters. */ - ALuint totalSamples{0u}; + size_t totalSamples{0u}; /* Multiplier for the maximum density value, i.e. density=1, which is * actually the least density... */ - ALfloat multiplier{CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY)}; + const float multiplier{CalcDelayLengthMult(1.0f)}; /* The main delay length includes the maximum early reflection delay, the * largest early tap width, the maximum late reverb delay, and the * largest late tap width. Finally, it must also be extended by the - * update size (BUFFERSIZE) for block processing. + * update size (BufferLineSize) for block processing. */ - ALfloat length{AL_EAXREVERB_MAX_REFLECTIONS_DELAY + EARLY_TAP_LENGTHS.back()*multiplier + - AL_EAXREVERB_MAX_LATE_REVERB_DELAY + - (LATE_LINE_LENGTHS.back() - LATE_LINE_LENGTHS.front())*0.25f*multiplier}; - totalSamples += CalcLineLength(length, totalSamples, frequency, BUFFERSIZE, &mDelay); + constexpr float LateLineDiffAvg{(LATE_LINE_LENGTHS.back()-LATE_LINE_LENGTHS.front()) / + float{NUM_LINES}}; + float length{ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier + + ReverbMaxLateReverbDelay + LateLineDiffAvg*multiplier}; + totalSamples += mDelay.calcLineLength(length, totalSamples, frequency, BufferLineSize); /* The early vector all-pass line. */ length = EARLY_ALLPASS_LENGTHS.back() * multiplier; - totalSamples += CalcLineLength(length, totalSamples, frequency, 0, &mEarly.VecAp.Delay); + totalSamples += mEarly.VecAp.Delay.calcLineLength(length, totalSamples, frequency, 0); /* The early reflection line. */ length = EARLY_LINE_LENGTHS.back() * multiplier; - totalSamples += CalcLineLength(length, totalSamples, frequency, 0, &mEarly.Delay); + totalSamples += mEarly.Delay.calcLineLength(length, totalSamples, frequency, 0); /* The late vector all-pass line. */ length = LATE_ALLPASS_LENGTHS.back() * multiplier; - totalSamples += CalcLineLength(length, totalSamples, frequency, 0, &mLate.VecAp.Delay); + totalSamples += mLate.VecAp.Delay.calcLineLength(length, totalSamples, frequency, 0); + + /* The modulator's line length is calculated from the maximum modulation + * time and depth coefficient, and halfed for the low-to-high frequency + * swing. + */ + constexpr float max_mod_delay{MaxModulationTime*MODULATION_DEPTH_COEFF / 2.0f}; /* The late delay lines are calculated from the largest maximum density - * line length. + * line length, and the maximum modulation delay. An additional sample is + * added to keep it stable when there is no modulation. */ - length = LATE_LINE_LENGTHS.back() * multiplier; - totalSamples += CalcLineLength(length, totalSamples, frequency, 0, &mLate.Delay); + length = LATE_LINE_LENGTHS.back()*multiplier + max_mod_delay; + totalSamples += mLate.Delay.calcLineLength(length, totalSamples, frequency, 1); - totalSamples *= NUM_LINES; if(totalSamples != mSampleBuffer.size()) - { - mSampleBuffer.resize(totalSamples); - mSampleBuffer.shrink_to_fit(); - } + decltype(mSampleBuffer)(totalSamples).swap(mSampleBuffer); /* Clear the sample buffer. */ - std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f); + std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), decltype(mSampleBuffer)::value_type{}); /* Update all delays to reflect the new sample buffer. */ - RealizeLineOffset(mSampleBuffer.data(), &mDelay); - RealizeLineOffset(mSampleBuffer.data(), &mEarly.VecAp.Delay); - RealizeLineOffset(mSampleBuffer.data(), &mEarly.Delay); - RealizeLineOffset(mSampleBuffer.data(), &mLate.VecAp.Delay); - RealizeLineOffset(mSampleBuffer.data(), &mLate.Delay); - - return true; + mDelay.realizeLineOffset(mSampleBuffer.data()); + mEarly.VecAp.Delay.realizeLineOffset(mSampleBuffer.data()); + mEarly.Delay.realizeLineOffset(mSampleBuffer.data()); + mLate.VecAp.Delay.realizeLineOffset(mSampleBuffer.data()); + mLate.Delay.realizeLineOffset(mSampleBuffer.data()); } -ALboolean ReverbState::deviceUpdate(const ALCdevice *device) +void ReverbState::deviceUpdate(const DeviceBase *device, const Buffer&) { - const auto frequency = static_cast(device->Frequency); + const auto frequency = static_cast(device->Frequency); /* Allocate the delay lines. */ - if(!allocLines(frequency)) - return AL_FALSE; + allocLines(frequency); - const ALfloat multiplier{CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY)}; + const float multiplier{CalcDelayLengthMult(1.0f)}; /* The late feed taps are set a fixed position past the latest delay tap. */ - mLateFeedTap = float2int( - (AL_EAXREVERB_MAX_REFLECTIONS_DELAY + EARLY_TAP_LENGTHS.back()*multiplier) * frequency); + mLateFeedTap = float2uint((ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier) * + frequency); /* Clear filters and gain coefficients since the delay lines were all just * cleared (if not reallocated). @@ -591,6 +668,10 @@ ALboolean ReverbState::deviceUpdate(const ALCdevice *device) t60.LFFilter.clear(); } + mLate.Mod.Index = 0; + mLate.Mod.Step = 1; + std::fill(std::begin(mLate.Mod.Depth), std::end(mLate.Mod.Depth), 0.0f); + for(auto &gains : mEarly.CurrentGain) std::fill(std::begin(gains), std::end(gains), 0.0f); for(auto &gains : mEarly.PanGain) @@ -600,26 +681,24 @@ ALboolean ReverbState::deviceUpdate(const ALCdevice *device) for(auto &gains : mLate.PanGain) std::fill(std::begin(gains), std::end(gains), 0.0f); - /* Reset counters and offset base. */ - mFadeCount = 0; - std::fill(std::begin(mMaxUpdate), std::end(mMaxUpdate), BUFFERSIZE); + /* Reset fading and offset base. */ + mDoFading = true; + std::fill(std::begin(mMaxUpdate), std::end(mMaxUpdate), MAX_UPDATE_SAMPLES); mOffset = 0; if(device->mAmbiOrder > 1) { mMixOut = &ReverbState::MixOutAmbiUp; - mOrderScales = BFormatDec::GetHFOrderScales(1, device->mAmbiOrder); + mOrderScales = AmbiScale::GetHFOrderScales(1, device->mAmbiOrder); } else { mMixOut = &ReverbState::MixOutPlain; mOrderScales.fill(1.0f); } - mAmbiSplitter[0][0].init(400.0f / frequency); + mAmbiSplitter[0][0].init(device->mXOverFreq / frequency); std::fill(mAmbiSplitter[0].begin()+1, mAmbiSplitter[0].end(), mAmbiSplitter[0][0]); std::fill(mAmbiSplitter[1].begin(), mAmbiSplitter[1].end(), mAmbiSplitter[0][0]); - - return AL_TRUE; } /************************************** @@ -629,19 +708,22 @@ ALboolean ReverbState::deviceUpdate(const ALCdevice *device) /* Calculate a decay coefficient given the length of each cycle and the time * until the decay reaches -60 dB. */ -inline ALfloat CalcDecayCoeff(const ALfloat length, const ALfloat decayTime) -{ return std::pow(REVERB_DECAY_GAIN, length/decayTime); } +inline float CalcDecayCoeff(const float length, const float decayTime) +{ return std::pow(ReverbDecayGain, length/decayTime); } /* Calculate a decay length from a coefficient and the time until the decay * reaches -60 dB. */ -inline ALfloat CalcDecayLength(const ALfloat coeff, const ALfloat decayTime) -{ return std::log10(coeff) * decayTime / std::log10(REVERB_DECAY_GAIN); } +inline float CalcDecayLength(const float coeff, const float decayTime) +{ + constexpr float log10_decaygain{-3.0f/*std::log10(ReverbDecayGain)*/}; + return std::log10(coeff) * decayTime / log10_decaygain; +} /* Calculate an attenuation to be applied to the input of any echo models to * compensate for modal density and decay time. */ -inline ALfloat CalcDensityGain(const ALfloat a) +inline float CalcDensityGain(const float a) { /* The energy of a signal can be obtained by finding the area under the * squared signal. This takes the form of Sum(x_n^2), where x is the @@ -660,11 +742,11 @@ inline ALfloat CalcDensityGain(const ALfloat a) } /* Calculate the scattering matrix coefficients given a diffusion factor. */ -inline ALvoid CalcMatrixCoeffs(const ALfloat diffusion, ALfloat *x, ALfloat *y) +inline void CalcMatrixCoeffs(const float diffusion, float *x, float *y) { /* The matrix is of order 4, so n is sqrt(4 - 1). */ - ALfloat n{std::sqrt(3.0f)}; - ALfloat t{diffusion * std::atan(n)}; + constexpr float n{al::numbers::sqrt3_v}; + const float t{diffusion * std::atan(n)}; /* Calculate the first mixing matrix coefficient. */ *x = std::cos(t); @@ -675,18 +757,18 @@ inline ALvoid CalcMatrixCoeffs(const ALfloat diffusion, ALfloat *x, ALfloat *y) /* Calculate the limited HF ratio for use with the late reverb low-pass * filters. */ -ALfloat CalcLimitedHfRatio(const ALfloat hfRatio, const ALfloat airAbsorptionGainHF, - const ALfloat decayTime, const ALfloat SpeedOfSound) +float CalcLimitedHfRatio(const float hfRatio, const float airAbsorptionGainHF, + const float decayTime) { /* Find the attenuation due to air absorption in dB (converting delay * time to meters using the speed of sound). Then reversing the decay * equation, solve for HF ratio. The delay length is cancelled out of * the equation, so it can be calculated once for all lines. */ - ALfloat limitRatio{1.0f / (CalcDecayLength(airAbsorptionGainHF, decayTime) * SpeedOfSound)}; + float limitRatio{1.0f / SpeedOfSoundMetersPerSec / + CalcDecayLength(airAbsorptionGainHF, decayTime)}; - /* Using the limit calculated above, apply the upper bound to the HF ratio. - */ + /* Using the limit calculated above, apply the upper bound to the HF ratio. */ return minf(limitRatio, hfRatio); } @@ -695,62 +777,91 @@ ALfloat CalcLimitedHfRatio(const ALfloat hfRatio, const ALfloat airAbsorptionGai * of specified length, using a combination of two shelf filter sections given * decay times for each band split at two reference frequencies. */ -void T60Filter::calcCoeffs(const ALfloat length, const ALfloat lfDecayTime, - const ALfloat mfDecayTime, const ALfloat hfDecayTime, const ALfloat lf0norm, - const ALfloat hf0norm) +void T60Filter::calcCoeffs(const float length, const float lfDecayTime, + const float mfDecayTime, const float hfDecayTime, const float lf0norm, + const float hf0norm) { - const ALfloat lfGain{CalcDecayCoeff(length, lfDecayTime)}; - const ALfloat mfGain{CalcDecayCoeff(length, mfDecayTime)}; - const ALfloat hfGain{CalcDecayCoeff(length, hfDecayTime)}; + const float mfGain{CalcDecayCoeff(length, mfDecayTime)}; + const float lfGain{CalcDecayCoeff(length, lfDecayTime) / mfGain}; + const float hfGain{CalcDecayCoeff(length, hfDecayTime) / mfGain}; MidGain[1] = mfGain; - LFFilter.setParams(BiquadType::LowShelf, lfGain/mfGain, lf0norm, - calc_rcpQ_from_slope(lfGain/mfGain, 1.0f)); - HFFilter.setParams(BiquadType::HighShelf, hfGain/mfGain, hf0norm, - calc_rcpQ_from_slope(hfGain/mfGain, 1.0f)); + LFFilter.setParamsFromSlope(BiquadType::LowShelf, lf0norm, lfGain, 1.0f); + HFFilter.setParamsFromSlope(BiquadType::HighShelf, hf0norm, hfGain, 1.0f); } /* Update the early reflection line lengths and gain coefficients. */ -void EarlyReflections::updateLines(const ALfloat density, const ALfloat diffusion, - const ALfloat decayTime, const ALfloat frequency) +void EarlyReflections::updateLines(const float density_mult, const float diffusion, + const float decayTime, const float frequency) { - const ALfloat multiplier{CalcDelayLengthMult(density)}; - /* Calculate the all-pass feed-back/forward coefficient. */ - VecAp.Coeff = std::sqrt(0.5f) * std::pow(diffusion, 2.0f); + VecAp.Coeff = diffusion*diffusion * InvSqrt2; - for(ALsizei i{0};i < NUM_LINES;i++) + for(size_t i{0u};i < NUM_LINES;i++) { - /* Calculate the length (in seconds) of each all-pass line. */ - ALfloat length{EARLY_ALLPASS_LENGTHS[i] * multiplier}; + /* Calculate the delay length of each all-pass line. */ + float length{EARLY_ALLPASS_LENGTHS[i] * density_mult}; + VecAp.Offset[i][1] = float2uint(length * frequency); - /* Calculate the delay offset for each all-pass line. */ - VecAp.Offset[i][1] = float2int(length * frequency); - - /* Calculate the length (in seconds) of each delay line. */ - length = EARLY_LINE_LENGTHS[i] * multiplier; - - /* Calculate the delay offset for each delay line. */ - Offset[i][1] = float2int(length * frequency); + /* Calculate the delay length of each delay line. */ + length = EARLY_LINE_LENGTHS[i] * density_mult; + Offset[i][1] = float2uint(length * frequency); /* Calculate the gain (coefficient) for each line. */ Coeff[i][1] = CalcDecayCoeff(length, decayTime); } } +/* Update the EAX modulation step and depth. Keep in mind that this kind of + * vibrato is additive and not multiplicative as one may expect. The downswing + * will sound stronger than the upswing. + */ +void Modulation::updateModulator(float modTime, float modDepth, float frequency) +{ + /* Modulation is calculated in two parts. + * + * The modulation time effects the sinus rate, altering the speed of + * frequency changes. An index is incremented for each sample with an + * appropriate step size to generate an LFO, which will vary the feedback + * delay over time. + */ + Step = maxu(fastf2u(MOD_FRACONE / (frequency * modTime)), 1); + + /* The modulation depth effects the amount of frequency change over the + * range of the sinus. It needs to be scaled by the modulation time so that + * a given depth produces a consistent change in frequency over all ranges + * of time. Since the depth is applied to a sinus value, it needs to be + * halved once for the sinus range and again for the sinus swing in time + * (half of it is spent decreasing the frequency, half is spent increasing + * it). + */ + if(modTime >= DefaultModulationTime) + { + /* To cancel the effects of a long period modulation on the late + * reverberation, the amount of pitch should be varied (decreased) + * according to the modulation time. The natural form is varying + * inversely, in fact resulting in an invariant. + */ + Depth[1] = MODULATION_DEPTH_COEFF / 4.0f * DefaultModulationTime * modDepth * frequency; + } + else + Depth[1] = MODULATION_DEPTH_COEFF / 4.0f * modTime * modDepth * frequency; +} + /* Update the late reverb line lengths and T60 coefficients. */ -void LateReverb::updateLines(const ALfloat density, const ALfloat diffusion, - const ALfloat lfDecayTime, const ALfloat mfDecayTime, const ALfloat hfDecayTime, - const ALfloat lf0norm, const ALfloat hf0norm, const ALfloat frequency) +void LateReverb::updateLines(const float density_mult, const float diffusion, + const float lfDecayTime, const float mfDecayTime, const float hfDecayTime, + const float lf0norm, const float hf0norm, const float frequency) { /* Scaling factor to convert the normalized reference frequencies from * representing 0...freq to 0...max_reference. */ - const ALfloat norm_weight_factor{frequency / AL_EAXREVERB_MAX_HFREFERENCE}; + constexpr float MaxHFReference{20000.0f}; + const float norm_weight_factor{frequency / MaxHFReference}; - const ALfloat late_allpass_avg{ + const float late_allpass_avg{ std::accumulate(LATE_ALLPASS_LENGTHS.begin(), LATE_ALLPASS_LENGTHS.end(), 0.0f) / - static_cast(LATE_ALLPASS_LENGTHS.size())}; + float{NUM_LINES}}; /* To compensate for changes in modal density and decay time of the late * reverb signal, the input is attenuated based on the maximal energy of @@ -760,46 +871,39 @@ void LateReverb::updateLines(const ALfloat density, const ALfloat diffusion, * The average length of the delay lines is used to calculate the * attenuation coefficient. */ - const ALfloat multiplier{CalcDelayLengthMult(density)}; - ALfloat length{std::accumulate(LATE_LINE_LENGTHS.begin(), LATE_LINE_LENGTHS.end(), 0.0f) / - static_cast(LATE_LINE_LENGTHS.size()) * multiplier}; - length += late_allpass_avg * multiplier; + float length{std::accumulate(LATE_LINE_LENGTHS.begin(), LATE_LINE_LENGTHS.end(), 0.0f) / + float{NUM_LINES} + late_allpass_avg}; + length *= density_mult; /* The density gain calculation uses an average decay time weighted by * approximate bandwidth. This attempts to compensate for losses of energy * that reduce decay time due to scattering into highly attenuated bands. */ - const ALfloat bandWeights[3]{ - lf0norm*norm_weight_factor, - hf0norm*norm_weight_factor - lf0norm*norm_weight_factor, - 1.0f - hf0norm*norm_weight_factor}; - DensityGain[1] = CalcDensityGain( - CalcDecayCoeff(length, - bandWeights[0]*lfDecayTime + bandWeights[1]*mfDecayTime + bandWeights[2]*hfDecayTime - ) - ); + const float decayTimeWeighted{ + lf0norm*norm_weight_factor*lfDecayTime + + (hf0norm - lf0norm)*norm_weight_factor*mfDecayTime + + (1.0f - hf0norm*norm_weight_factor)*hfDecayTime}; + DensityGain[1] = CalcDensityGain(CalcDecayCoeff(length, decayTimeWeighted)); /* Calculate the all-pass feed-back/forward coefficient. */ - VecAp.Coeff = std::sqrt(0.5f) * std::pow(diffusion, 2.0f); + VecAp.Coeff = diffusion*diffusion * InvSqrt2; - for(ALsizei i{0};i < NUM_LINES;i++) + for(size_t i{0u};i < NUM_LINES;i++) { - /* Calculate the length (in seconds) of each all-pass line. */ - length = LATE_ALLPASS_LENGTHS[i] * multiplier; - - /* Calculate the delay offset for each all-pass line. */ - VecAp.Offset[i][1] = float2int(length * frequency); - - /* Calculate the length (in seconds) of each delay line. */ - length = LATE_LINE_LENGTHS[i] * multiplier; + /* Calculate the delay length of each all-pass line. */ + length = LATE_ALLPASS_LENGTHS[i] * density_mult; + VecAp.Offset[i][1] = float2uint(length * frequency); - /* Calculate the delay offset for each delay line. */ - Offset[i][1] = float2int(length*frequency + 0.5f); + /* Calculate the delay length of each feedback delay line. */ + length = LATE_LINE_LENGTHS[i] * density_mult; + Offset[i][1] = float2uint(length*frequency + 0.5f); /* Approximate the absorption that the vector all-pass would exhibit * given the current diffusion so we don't have to process a full T60 - * filter for each of its four lines. + * filter for each of its four lines. Also include the average + * modulation delay (depth is half the max delay in samples). */ - length += lerp(LATE_ALLPASS_LENGTHS[i], late_allpass_avg, diffusion) * multiplier; + length += lerpf(LATE_ALLPASS_LENGTHS[i], late_allpass_avg, diffusion)*density_mult + + Mod.Depth[1]/frequency; /* Calculate the T60 damping coefficients for each line. */ T60[i].calcCoeffs(length, lfDecayTime, mfDecayTime, hfDecayTime, lf0norm, hf0norm); @@ -808,11 +912,9 @@ void LateReverb::updateLines(const ALfloat density, const ALfloat diffusion, /* Update the offsets for the main effect delay line. */ -void ReverbState::updateDelayLine(const ALfloat earlyDelay, const ALfloat lateDelay, - const ALfloat density, const ALfloat decayTime, const ALfloat frequency) +void ReverbState::updateDelayLine(const float earlyDelay, const float lateDelay, + const float density_mult, const float decayTime, const float frequency) { - const ALfloat multiplier{CalcDelayLengthMult(density)}; - /* Early reflection taps are decorrelated by means of an average room * reflection approximation described above the definition of the taps. * This approximation is linear and so the above density multiplier can @@ -823,16 +925,15 @@ void ReverbState::updateDelayLine(const ALfloat earlyDelay, const ALfloat lateDe * delay path and offsets that would continue the propagation naturally * into the late lines. */ - for(ALsizei i{0};i < NUM_LINES;i++) + for(size_t i{0u};i < NUM_LINES;i++) { - ALfloat length{earlyDelay + EARLY_TAP_LENGTHS[i]*multiplier}; - mEarlyDelayTap[i][1] = float2int(length * frequency); - - length = EARLY_TAP_LENGTHS[i]*multiplier; + float length{EARLY_TAP_LENGTHS[i]*density_mult}; + mEarlyDelayTap[i][1] = float2uint((earlyDelay+length) * frequency); mEarlyDelayCoeff[i][1] = CalcDecayCoeff(length, decayTime); - length = lateDelay + (LATE_LINE_LENGTHS[i] - LATE_LINE_LENGTHS.front())*0.25f*multiplier; - mLateDelayTap[i][1] = mLateFeedTap + float2int(length * frequency); + length = (LATE_LINE_LENGTHS[i] - LATE_LINE_LENGTHS.front())/float{NUM_LINES}*density_mult + + lateDelay; + mLateDelayTap[i][1] = mLateFeedTap + float2uint(length * frequency); } } @@ -841,7 +942,7 @@ void ReverbState::updateDelayLine(const ALfloat earlyDelay, const ALfloat lateDe * focal strength. This function results in a B-Format transformation matrix * that spatially focuses the signal in the desired direction. */ -alu::Matrix GetTransformFromVector(const ALfloat *vec) +alu::Matrix GetTransformFromVector(const float *vec) { /* Normalize the panning vector according to the N3D scale, which has an * extra sqrt(3) term on the directional components. Converting from OpenAL @@ -850,13 +951,13 @@ alu::Matrix GetTransformFromVector(const ALfloat *vec) * rest of OpenAL which use right-handed. This is fixed by negating Z, * which cancels out with the B-Format Z negation. */ - ALfloat norm[3]; - ALfloat mag{std::sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2])}; + float norm[3]; + float mag{std::sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2])}; if(mag > 1.0f) { - norm[0] = vec[0] / mag * -al::MathDefs::Sqrt3(); - norm[1] = vec[1] / mag * al::MathDefs::Sqrt3(); - norm[2] = vec[2] / mag * al::MathDefs::Sqrt3(); + norm[0] = vec[0] / mag * -al::numbers::sqrt3_v; + norm[1] = vec[1] / mag * al::numbers::sqrt3_v; + norm[2] = vec[2] / mag * al::numbers::sqrt3_v; mag = 1.0f; } else @@ -865,9 +966,9 @@ alu::Matrix GetTransformFromVector(const ALfloat *vec) * term. There's no need to renormalize the magnitude since it would * just be reapplied in the matrix. */ - norm[0] = vec[0] * -al::MathDefs::Sqrt3(); - norm[1] = vec[1] * al::MathDefs::Sqrt3(); - norm[2] = vec[2] * al::MathDefs::Sqrt3(); + norm[0] = vec[0] * -al::numbers::sqrt3_v; + norm[1] = vec[1] * al::numbers::sqrt3_v; + norm[2] = vec[2] * al::numbers::sqrt3_v; } return alu::Matrix{ @@ -879,61 +980,56 @@ alu::Matrix GetTransformFromVector(const ALfloat *vec) } /* Update the early and late 3D panning gains. */ -void ReverbState::update3DPanning(const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan, - const ALfloat earlyGain, const ALfloat lateGain, const EffectTarget &target) +void ReverbState::update3DPanning(const float *ReflectionsPan, const float *LateReverbPan, + const float earlyGain, const float lateGain, const EffectTarget &target) { /* Create matrices that transform a B-Format signal according to the * panning vectors. */ const alu::Matrix earlymat{GetTransformFromVector(ReflectionsPan)}; const alu::Matrix latemat{GetTransformFromVector(LateReverbPan)}; - mOutBuffer = target.Main->Buffer; - mOutChannels = target.Main->NumChannels; - for(ALsizei i{0};i < NUM_LINES;i++) + + mOutTarget = target.Main->Buffer; + for(size_t i{0u};i < NUM_LINES;i++) { - const ALfloat coeffs[MAX_AMBI_CHANNELS]{earlymat[0][i], earlymat[1][i], earlymat[2][i], + const float coeffs[MaxAmbiChannels]{earlymat[0][i], earlymat[1][i], earlymat[2][i], earlymat[3][i]}; ComputePanGains(target.Main, coeffs, earlyGain, mEarly.PanGain[i]); } - for(ALsizei i{0};i < NUM_LINES;i++) + for(size_t i{0u};i < NUM_LINES;i++) { - const ALfloat coeffs[MAX_AMBI_CHANNELS]{latemat[0][i], latemat[1][i], latemat[2][i], + const float coeffs[MaxAmbiChannels]{latemat[0][i], latemat[1][i], latemat[2][i], latemat[3][i]}; ComputePanGains(target.Main, coeffs, lateGain, mLate.PanGain[i]); } } -void ReverbState::update(const ALCcontext *Context, const ALeffectslot *Slot, const EffectProps *props, const EffectTarget target) +void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, + const EffectProps *props, const EffectTarget target) { - const ALCdevice *Device{Context->Device}; - const ALlistener &Listener = Context->Listener; - const auto frequency = static_cast(Device->Frequency); + const DeviceBase *Device{Context->mDevice}; + const auto frequency = static_cast(Device->Frequency); /* Calculate the master filters */ - ALfloat hf0norm{minf(props->Reverb.HFReference / frequency, 0.49f)}; - /* Restrict the filter gains from going below -60dB to keep the filter from - * killing most of the signal. - */ - ALfloat gainhf{maxf(props->Reverb.GainHF, 0.001f)}; - mFilter[0].Lp.setParams(BiquadType::HighShelf, gainhf, hf0norm, - calc_rcpQ_from_slope(gainhf, 1.0f)); - ALfloat lf0norm{minf(props->Reverb.LFReference / frequency, 0.49f)}; - ALfloat gainlf{maxf(props->Reverb.GainLF, 0.001f)}; - mFilter[0].Hp.setParams(BiquadType::LowShelf, gainlf, lf0norm, - calc_rcpQ_from_slope(gainlf, 1.0f)); - for(ALsizei i{1};i < NUM_LINES;i++) + float hf0norm{minf(props->Reverb.HFReference/frequency, 0.49f)}; + mFilter[0].Lp.setParamsFromSlope(BiquadType::HighShelf, hf0norm, props->Reverb.GainHF, 1.0f); + float lf0norm{minf(props->Reverb.LFReference/frequency, 0.49f)}; + mFilter[0].Hp.setParamsFromSlope(BiquadType::LowShelf, lf0norm, props->Reverb.GainLF, 1.0f); + for(size_t i{1u};i < NUM_LINES;i++) { mFilter[i].Lp.copyParamsFrom(mFilter[0].Lp); mFilter[i].Hp.copyParamsFrom(mFilter[0].Hp); } + /* The density-based room size (delay length) multiplier. */ + const float density_mult{CalcDelayLengthMult(props->Reverb.Density)}; + /* Update the main effect delay and associated taps. */ updateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay, - props->Reverb.Density, props->Reverb.DecayTime, frequency); + density_mult, props->Reverb.DecayTime, frequency); /* Update the early lines. */ - mEarly.updateLines(props->Reverb.Density, props->Reverb.Diffusion, props->Reverb.DecayTime, - frequency); + mEarly.updateLines(density_mult, props->Reverb.Diffusion, props->Reverb.DecayTime, frequency); /* Get the mixing matrix coefficients. */ CalcMatrixCoeffs(props->Reverb.Diffusion, &mMixX, &mMixY); @@ -941,55 +1037,65 @@ void ReverbState::update(const ALCcontext *Context, const ALeffectslot *Slot, co /* If the HF limit parameter is flagged, calculate an appropriate limit * based on the air absorption parameter. */ - ALfloat hfRatio{props->Reverb.DecayHFRatio}; + float hfRatio{props->Reverb.DecayHFRatio}; if(props->Reverb.DecayHFLimit && props->Reverb.AirAbsorptionGainHF < 1.0f) hfRatio = CalcLimitedHfRatio(hfRatio, props->Reverb.AirAbsorptionGainHF, - props->Reverb.DecayTime, Listener.Params.ReverbSpeedOfSound - ); + props->Reverb.DecayTime); /* Calculate the LF/HF decay times. */ - const ALfloat lfDecayTime{clampf(props->Reverb.DecayTime * props->Reverb.DecayLFRatio, - AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME)}; - const ALfloat hfDecayTime{clampf(props->Reverb.DecayTime * hfRatio, - AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME)}; + constexpr float MinDecayTime{0.1f}, MaxDecayTime{20.0f}; + const float lfDecayTime{clampf(props->Reverb.DecayTime*props->Reverb.DecayLFRatio, + MinDecayTime, MaxDecayTime)}; + const float hfDecayTime{clampf(props->Reverb.DecayTime*hfRatio, MinDecayTime, MaxDecayTime)}; + + /* Update the modulator rate and depth. */ + mLate.Mod.updateModulator(props->Reverb.ModulationTime, props->Reverb.ModulationDepth, + frequency); /* Update the late lines. */ - mLate.updateLines(props->Reverb.Density, props->Reverb.Diffusion, lfDecayTime, + mLate.updateLines(density_mult, props->Reverb.Diffusion, lfDecayTime, props->Reverb.DecayTime, hfDecayTime, lf0norm, hf0norm, frequency); /* Update early and late 3D panning. */ - const ALfloat gain{props->Reverb.Gain * Slot->Params.Gain * ReverbBoost}; + const float gain{props->Reverb.Gain * Slot->Gain * ReverbBoost}; update3DPanning(props->Reverb.ReflectionsPan, props->Reverb.LateReverbPan, props->Reverb.ReflectionsGain*gain, props->Reverb.LateReverbGain*gain, target); /* Calculate the max update size from the smallest relevant delay. */ - mMaxUpdate[1] = mini(BUFFERSIZE, mini(mEarly.Offset[0][1], mLate.Offset[0][1])); + mMaxUpdate[1] = minz(MAX_UPDATE_SAMPLES, minz(mEarly.Offset[0][1], mLate.Offset[0][1])); /* Determine if delay-line cross-fading is required. Density is essentially * a master control for the feedback delays, so changes the offsets of many * delay lines. */ - if(mParams.Density != props->Reverb.Density || + mDoFading |= (mParams.Density != props->Reverb.Density || /* Diffusion and decay times influences the decay rate (gain) of the * late reverb T60 filter. */ - mParams.Diffusion != props->Reverb.Diffusion || - mParams.DecayTime != props->Reverb.DecayTime || - mParams.HFDecayTime != hfDecayTime || - mParams.LFDecayTime != lfDecayTime || - /* HF/LF References control the weighting used to calculate the density - * gain. - */ - mParams.HFReference != props->Reverb.HFReference || - mParams.LFReference != props->Reverb.LFReference) - mFadeCount = 0; - mParams.Density = props->Reverb.Density; - mParams.Diffusion = props->Reverb.Diffusion; - mParams.DecayTime = props->Reverb.DecayTime; - mParams.HFDecayTime = hfDecayTime; - mParams.LFDecayTime = lfDecayTime; - mParams.HFReference = props->Reverb.HFReference; - mParams.LFReference = props->Reverb.LFReference; + mParams.Diffusion != props->Reverb.Diffusion || + mParams.DecayTime != props->Reverb.DecayTime || + mParams.HFDecayTime != hfDecayTime || + mParams.LFDecayTime != lfDecayTime || + /* Modulation time and depth both require fading the modulation delay. */ + mParams.ModulationTime != props->Reverb.ModulationTime || + mParams.ModulationDepth != props->Reverb.ModulationDepth || + /* HF/LF References control the weighting used to calculate the density + * gain. + */ + mParams.HFReference != props->Reverb.HFReference || + mParams.LFReference != props->Reverb.LFReference); + if(mDoFading) + { + mParams.Density = props->Reverb.Density; + mParams.Diffusion = props->Reverb.Diffusion; + mParams.DecayTime = props->Reverb.DecayTime; + mParams.HFDecayTime = hfDecayTime; + mParams.LFDecayTime = lfDecayTime; + mParams.ModulationTime = props->Reverb.ModulationTime; + mParams.ModulationDepth = props->Reverb.ModulationDepth; + mParams.HFReference = props->Reverb.HFReference; + mParams.LFReference = props->Reverb.LFReference; + } } @@ -1035,36 +1141,34 @@ void ReverbState::update(const ALCcontext *Context, const ALeffectslot *Slot, co * Where D is a diagonal matrix (of x), and S is a triangular matrix (of y) * whose combination of signs are being iterated. */ -inline void VectorPartialScatter(ALfloat *RESTRICT out, const ALfloat *RESTRICT in, - const ALfloat xCoeff, const ALfloat yCoeff) +inline auto VectorPartialScatter(const std::array &RESTRICT in, + const float xCoeff, const float yCoeff) -> std::array { - out[0] = xCoeff*in[0] + yCoeff*( in[1] + -in[2] + in[3]); - out[1] = xCoeff*in[1] + yCoeff*(-in[0] + in[2] + in[3]); - out[2] = xCoeff*in[2] + yCoeff*( in[0] + -in[1] + in[3]); - out[3] = xCoeff*in[3] + yCoeff*(-in[0] + -in[1] + -in[2] ); + return std::array{{ + xCoeff*in[0] + yCoeff*( in[1] + -in[2] + in[3]), + xCoeff*in[1] + yCoeff*(-in[0] + in[2] + in[3]), + xCoeff*in[2] + yCoeff*( in[0] + -in[1] + in[3]), + xCoeff*in[3] + yCoeff*(-in[0] + -in[1] + -in[2] ) + }}; } /* Utilizes the above, but reverses the input channels. */ -inline void VectorScatterRevDelayIn(const DelayLineI *Delay, ALint offset, - const ALfloat xCoeff, const ALfloat yCoeff, const ALsizei base, - const ALfloat (*RESTRICT in)[BUFFERSIZE], const ALsizei count) +void VectorScatterRevDelayIn(const DelayLineI delay, size_t offset, const float xCoeff, + const float yCoeff, const al::span in, const size_t count) { - const DelayLineI delay{*Delay}; - - ASSUME(base >= 0); ASSUME(count > 0); - for(ALsizei i{0};i < count;) + for(size_t i{0u};i < count;) { offset &= delay.Mask; - ALsizei td{mini(delay.Mask+1 - offset, count-i)}; + size_t td{minz(delay.Mask+1 - offset, count-i)}; do { - ALfloat f[NUM_LINES]; - for(ALsizei j{0};j < NUM_LINES;j++) - f[NUM_LINES-1-j] = in[j][base+i]; + std::array f; + for(size_t j{0u};j < NUM_LINES;j++) + f[NUM_LINES-1-j] = in[j][i]; ++i; - VectorPartialScatter(delay.Line[offset++], f, xCoeff, yCoeff); + delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff); } while(--td); } } @@ -1079,91 +1183,93 @@ inline void VectorScatterRevDelayIn(const DelayLineI *Delay, ALint offset, * Two static specializations are used for transitional (cross-faded) delay * line processing and non-transitional processing. */ -void VecAllpass::processUnfaded(ALfloat (*RESTRICT samples)[BUFFERSIZE], ALsizei offset, - const ALfloat xCoeff, const ALfloat yCoeff, const ALsizei todo) +void VecAllpass::processUnfaded(const al::span samples, size_t offset, + const float xCoeff, const float yCoeff, const size_t todo) { const DelayLineI delay{Delay}; - const ALfloat feedCoeff{Coeff}; + const float feedCoeff{Coeff}; ASSUME(todo > 0); - ALsizei vap_offset[NUM_LINES]; - for(ALsizei j{0};j < NUM_LINES;j++) + size_t vap_offset[NUM_LINES]; + for(size_t j{0u};j < NUM_LINES;j++) vap_offset[j] = offset - Offset[j][0]; - for(ALsizei i{0};i < todo;) + for(size_t i{0u};i < todo;) { - for(ALsizei j{0};j < NUM_LINES;j++) + for(size_t j{0u};j < NUM_LINES;j++) vap_offset[j] &= delay.Mask; offset &= delay.Mask; - ALsizei maxoff{offset}; - for(ALsizei j{0};j < NUM_LINES;j++) - maxoff = maxi(maxoff, vap_offset[j]); - ALsizei td{mini(delay.Mask+1 - maxoff, todo - i)}; + size_t maxoff{offset}; + for(size_t j{0u};j < NUM_LINES;j++) + maxoff = maxz(maxoff, vap_offset[j]); + size_t td{minz(delay.Mask+1 - maxoff, todo - i)}; do { - ALfloat f[NUM_LINES]; - for(ALsizei j{0};j < NUM_LINES;j++) + std::array f; + for(size_t j{0u};j < NUM_LINES;j++) { - const ALfloat input{samples[j][i]}; - const ALfloat out{delay.Line[vap_offset[j]++][j] - feedCoeff*input}; + const float input{samples[j][i]}; + const float out{delay.Line[vap_offset[j]++][j] - feedCoeff*input}; f[j] = input + feedCoeff*out; samples[j][i] = out; } ++i; - VectorPartialScatter(delay.Line[offset++], f, xCoeff, yCoeff); + delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff); } while(--td); } } -void VecAllpass::processFaded(ALfloat (*RESTRICT samples)[BUFFERSIZE], ALsizei offset, - const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fade, const ALsizei todo) +void VecAllpass::processFaded(const al::span samples, size_t offset, + const float xCoeff, const float yCoeff, float fadeCount, const float fadeStep, + const size_t todo) { const DelayLineI delay{Delay}; - const ALfloat feedCoeff{Coeff}; + const float feedCoeff{Coeff}; ASSUME(todo > 0); - fade *= 1.0f/FADE_SAMPLES; - ALsizei vap_offset[NUM_LINES][2]; - for(ALsizei j{0};j < NUM_LINES;j++) + size_t vap_offset[NUM_LINES][2]; + for(size_t j{0u};j < NUM_LINES;j++) { vap_offset[j][0] = offset - Offset[j][0]; vap_offset[j][1] = offset - Offset[j][1]; } - for(ALsizei i{0};i < todo;) + for(size_t i{0u};i < todo;) { - for(ALsizei j{0};j < NUM_LINES;j++) + for(size_t j{0u};j < NUM_LINES;j++) { vap_offset[j][0] &= delay.Mask; vap_offset[j][1] &= delay.Mask; } offset &= delay.Mask; - ALsizei maxoff{offset}; - for(ALsizei j{0};j < NUM_LINES;j++) - maxoff = maxi(maxoff, maxi(vap_offset[j][0], vap_offset[j][1])); - ALsizei td{mini(delay.Mask+1 - maxoff, todo - i)}; + size_t maxoff{offset}; + for(size_t j{0u};j < NUM_LINES;j++) + maxoff = maxz(maxoff, maxz(vap_offset[j][0], vap_offset[j][1])); + size_t td{minz(delay.Mask+1 - maxoff, todo - i)}; do { - fade += FadeStep; - ALfloat f[NUM_LINES]; - for(ALsizei j{0};j < NUM_LINES;j++) + fadeCount += 1.0f; + const float fade{fadeCount * fadeStep}; + + std::array f; + for(size_t j{0u};j < NUM_LINES;j++) f[j] = delay.Line[vap_offset[j][0]++][j]*(1.0f-fade) + delay.Line[vap_offset[j][1]++][j]*fade; - for(ALsizei j{0};j < NUM_LINES;j++) + for(size_t j{0u};j < NUM_LINES;j++) { - const ALfloat input{samples[j][i]}; - const ALfloat out{f[j] - feedCoeff*input}; + const float input{samples[j][i]}; + const float out{f[j] - feedCoeff*input}; f[j] = input + feedCoeff*out; samples[j][i] = out; } ++i; - VectorPartialScatter(delay.Line[offset++], f, xCoeff, yCoeff); + delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff); } while(--td); } } @@ -1187,30 +1293,28 @@ void VecAllpass::processFaded(ALfloat (*RESTRICT samples)[BUFFERSIZE], ALsizei o * Two static specializations are used for transitional (cross-faded) delay * line processing and non-transitional processing. */ -void EarlyReflection_Unfaded(ReverbState *State, const ALsizei offset, const ALsizei todo, - const ALsizei base, ALfloat (*RESTRICT out)[BUFFERSIZE]) +void ReverbState::earlyUnfaded(const size_t offset, const size_t todo) { - ALfloat (*RESTRICT temps)[BUFFERSIZE]{State->mTempSamples}; - const DelayLineI early_delay{State->mEarly.Delay}; - const DelayLineI main_delay{State->mDelay}; - const ALfloat mixX{State->mMixX}; - const ALfloat mixY{State->mMixY}; + const DelayLineI early_delay{mEarly.Delay}; + const DelayLineI main_delay{mDelay}; + const float mixX{mMixX}; + const float mixY{mMixY}; ASSUME(todo > 0); /* First, load decorrelated samples from the main delay line as the primary * reflections. */ - for(ALsizei j{0};j < NUM_LINES;j++) + for(size_t j{0u};j < NUM_LINES;j++) { - ALsizei early_delay_tap{offset - State->mEarlyDelayTap[j][0]}; - const ALfloat coeff{State->mEarlyDelayCoeff[j][0]}; - for(ALsizei i{0};i < todo;) + size_t early_delay_tap{offset - mEarlyDelayTap[j][0]}; + const float coeff{mEarlyDelayCoeff[j][0]}; + for(size_t i{0u};i < todo;) { early_delay_tap &= main_delay.Mask; - ALsizei td{mini(main_delay.Mask+1 - early_delay_tap, todo - i)}; + size_t td{minz(main_delay.Mask+1 - early_delay_tap, todo - i)}; do { - temps[j][i++] = main_delay.Line[early_delay_tap++][j] * coeff; + mTempSamples[j][i++] = main_delay.Line[early_delay_tap++][j] * coeff; } while(--td); } } @@ -1218,114 +1322,147 @@ void EarlyReflection_Unfaded(ReverbState *State, const ALsizei offset, const ALs /* Apply a vector all-pass, to help color the initial reflections based on * the diffusion strength. */ - State->mEarly.VecAp.processUnfaded(temps, offset, mixX, mixY, todo); + mEarly.VecAp.processUnfaded(mTempSamples, offset, mixX, mixY, todo); /* Apply a delay and bounce to generate secondary reflections, combine with * the primary reflections and write out the result for mixing. */ - for(ALsizei j{0};j < NUM_LINES;j++) + for(size_t j{0u};j < NUM_LINES;j++) { - ALint feedb_tap{offset - State->mEarly.Offset[j][0]}; - const ALfloat feedb_coeff{State->mEarly.Coeff[j][0]}; + size_t feedb_tap{offset - mEarly.Offset[j][0]}; + const float feedb_coeff{mEarly.Coeff[j][0]}; + float *out{mEarlySamples[j].data()}; - ASSUME(base >= 0); - for(ALsizei i{0};i < todo;) + for(size_t i{0u};i < todo;) { feedb_tap &= early_delay.Mask; - ALsizei td{mini(early_delay.Mask+1 - feedb_tap, todo - i)}; + size_t td{minz(early_delay.Mask+1 - feedb_tap, todo - i)}; do { - out[j][base+i] = temps[j][i] + early_delay.Line[feedb_tap++][j]*feedb_coeff; + out[i] = mTempSamples[j][i] + early_delay.Line[feedb_tap++][j]*feedb_coeff; ++i; } while(--td); } } - for(ALsizei j{0};j < NUM_LINES;j++) - early_delay.write(offset, NUM_LINES-1-j, temps[j], todo); + for(size_t j{0u};j < NUM_LINES;j++) + early_delay.write(offset, NUM_LINES-1-j, mTempSamples[j].data(), todo); /* Also write the result back to the main delay line for the late reverb * stage to pick up at the appropriate time, appplying a scatter and * bounce to improve the initial diffusion in the late reverb. */ - const ALsizei late_feed_tap{offset - State->mLateFeedTap}; - VectorScatterRevDelayIn(&main_delay, late_feed_tap, mixX, mixY, base, out, todo); + const size_t late_feed_tap{offset - mLateFeedTap}; + VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, mEarlySamples, todo); } -void EarlyReflection_Faded(ReverbState *State, const ALsizei offset, const ALsizei todo, - const ALfloat fade, const ALsizei base, ALfloat (*RESTRICT out)[BUFFERSIZE]) +void ReverbState::earlyFaded(const size_t offset, const size_t todo, const float fade, + const float fadeStep) { - ALfloat (*RESTRICT temps)[BUFFERSIZE]{State->mTempSamples}; - const DelayLineI early_delay{State->mEarly.Delay}; - const DelayLineI main_delay{State->mDelay}; - const ALfloat mixX{State->mMixX}; - const ALfloat mixY{State->mMixY}; + const DelayLineI early_delay{mEarly.Delay}; + const DelayLineI main_delay{mDelay}; + const float mixX{mMixX}; + const float mixY{mMixY}; ASSUME(todo > 0); - for(ALsizei j{0};j < NUM_LINES;j++) + for(size_t j{0u};j < NUM_LINES;j++) { - ALsizei early_delay_tap0{offset - State->mEarlyDelayTap[j][0]}; - ALsizei early_delay_tap1{offset - State->mEarlyDelayTap[j][1]}; - const ALfloat oldCoeff{State->mEarlyDelayCoeff[j][0]}; - const ALfloat oldCoeffStep{-oldCoeff / FADE_SAMPLES}; - const ALfloat newCoeffStep{State->mEarlyDelayCoeff[j][1] / FADE_SAMPLES}; - ALfloat fadeCount{fade}; - - for(ALsizei i{0};i < todo;) + size_t early_delay_tap0{offset - mEarlyDelayTap[j][0]}; + size_t early_delay_tap1{offset - mEarlyDelayTap[j][1]}; + const float oldCoeff{mEarlyDelayCoeff[j][0]}; + const float oldCoeffStep{-oldCoeff * fadeStep}; + const float newCoeffStep{mEarlyDelayCoeff[j][1] * fadeStep}; + float fadeCount{fade}; + + for(size_t i{0u};i < todo;) { early_delay_tap0 &= main_delay.Mask; early_delay_tap1 &= main_delay.Mask; - ALsizei td{mini(main_delay.Mask+1 - maxi(early_delay_tap0, early_delay_tap1), todo-i)}; + size_t td{minz(main_delay.Mask+1 - maxz(early_delay_tap0, early_delay_tap1), todo-i)}; do { fadeCount += 1.0f; - const ALfloat fade0{oldCoeff + oldCoeffStep*fadeCount}; - const ALfloat fade1{newCoeffStep*fadeCount}; - temps[j][i++] = + const float fade0{oldCoeff + oldCoeffStep*fadeCount}; + const float fade1{newCoeffStep*fadeCount}; + mTempSamples[j][i++] = main_delay.Line[early_delay_tap0++][j]*fade0 + main_delay.Line[early_delay_tap1++][j]*fade1; } while(--td); } } - State->mEarly.VecAp.processFaded(temps, offset, mixX, mixY, fade, todo); + mEarly.VecAp.processFaded(mTempSamples, offset, mixX, mixY, fade, fadeStep, todo); - for(ALsizei j{0};j < NUM_LINES;j++) + for(size_t j{0u};j < NUM_LINES;j++) { - ALint feedb_tap0{offset - State->mEarly.Offset[j][0]}; - ALint feedb_tap1{offset - State->mEarly.Offset[j][1]}; - const ALfloat feedb_oldCoeff{State->mEarly.Coeff[j][0]}; - const ALfloat feedb_oldCoeffStep{-feedb_oldCoeff / FADE_SAMPLES}; - const ALfloat feedb_newCoeffStep{State->mEarly.Coeff[j][1] / FADE_SAMPLES}; - ALfloat fadeCount{fade}; - - ASSUME(base >= 0); - for(ALsizei i{0};i < todo;) + size_t feedb_tap0{offset - mEarly.Offset[j][0]}; + size_t feedb_tap1{offset - mEarly.Offset[j][1]}; + const float feedb_oldCoeff{mEarly.Coeff[j][0]}; + const float feedb_oldCoeffStep{-feedb_oldCoeff * fadeStep}; + const float feedb_newCoeffStep{mEarly.Coeff[j][1] * fadeStep}; + float *out{mEarlySamples[j].data()}; + float fadeCount{fade}; + + for(size_t i{0u};i < todo;) { feedb_tap0 &= early_delay.Mask; feedb_tap1 &= early_delay.Mask; - ALsizei td{mini(early_delay.Mask+1 - maxi(feedb_tap0, feedb_tap1), todo - i)}; + size_t td{minz(early_delay.Mask+1 - maxz(feedb_tap0, feedb_tap1), todo - i)}; do { fadeCount += 1.0f; - const ALfloat fade0{feedb_oldCoeff + feedb_oldCoeffStep*fadeCount}; - const ALfloat fade1{feedb_newCoeffStep*fadeCount}; - out[j][base+i] = temps[j][i] + + const float fade0{feedb_oldCoeff + feedb_oldCoeffStep*fadeCount}; + const float fade1{feedb_newCoeffStep*fadeCount}; + out[i] = mTempSamples[j][i] + early_delay.Line[feedb_tap0++][j]*fade0 + early_delay.Line[feedb_tap1++][j]*fade1; ++i; } while(--td); } } - for(ALsizei j{0};j < NUM_LINES;j++) - early_delay.write(offset, NUM_LINES-1-j, temps[j], todo); + for(size_t j{0u};j < NUM_LINES;j++) + early_delay.write(offset, NUM_LINES-1-j, mTempSamples[j].data(), todo); + + const size_t late_feed_tap{offset - mLateFeedTap}; + VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, mEarlySamples, todo); +} + + +void Modulation::calcDelays(size_t todo) +{ + constexpr float inv_scale{MOD_FRACONE / al::numbers::pi_v / 2.0f}; + uint idx{Index}; + const uint step{Step}; + const float depth{Depth[0]}; + for(size_t i{0};i < todo;++i) + { + idx += step; + const float lfo{std::sin(static_cast(idx&MOD_FRACMASK) / inv_scale)}; + ModDelays[i] = (lfo+1.0f) * depth; + } + Index = idx; +} - const ALsizei late_feed_tap{offset - State->mLateFeedTap}; - VectorScatterRevDelayIn(&main_delay, late_feed_tap, mixX, mixY, base, out, todo); +void Modulation::calcFadedDelays(size_t todo, float fadeCount, float fadeStep) +{ + constexpr float inv_scale{MOD_FRACONE / al::numbers::pi_v / 2.0f}; + uint idx{Index}; + const uint step{Step}; + const float depth{Depth[0]}; + const float depthStep{(Depth[1]-depth) * fadeStep}; + for(size_t i{0};i < todo;++i) + { + fadeCount += 1.0f; + idx += step; + const float lfo{std::sin(static_cast(idx&MOD_FRACMASK) / inv_scale)}; + ModDelays[i] = (lfo+1.0f) * (depth + depthStep*fadeCount); + } + Index = idx; } + /* This generates the reverb tail using a modified feed-back delay network * (FDN). * - * Results from the early reflections are mixed with the output from the late - * delay lines. + * Results from the early reflections are mixed with the output from the + * modulated late delay lines. * * The late response is then completed by T60 and all-pass filtering the mix. * @@ -1335,755 +1472,242 @@ void EarlyReflection_Faded(ReverbState *State, const ALsizei offset, const ALsiz * Two variations are made, one for for transitional (cross-faded) delay line * processing and one for non-transitional processing. */ -void LateReverb_Unfaded(ReverbState *State, const ALsizei offset, const ALsizei todo, - const ALsizei base, ALfloat (*RESTRICT out)[BUFFERSIZE]) +void ReverbState::lateUnfaded(const size_t offset, const size_t todo) { - ALfloat (*RESTRICT temps)[BUFFERSIZE]{State->mTempSamples}; - const DelayLineI late_delay{State->mLate.Delay}; - const DelayLineI main_delay{State->mDelay}; - const ALfloat mixX{State->mMixX}; - const ALfloat mixY{State->mMixY}; + const DelayLineI late_delay{mLate.Delay}; + const DelayLineI main_delay{mDelay}; + const float mixX{mMixX}; + const float mixY{mMixY}; ASSUME(todo > 0); - /* First, load decorrelated samples from the main and feedback delay lines. + /* First, calculate the modulated delays for the late feedback. */ + mLate.Mod.calcDelays(todo); + + /* Next, load decorrelated samples from the main and feedback delay lines. * Filter the signal to apply its frequency-dependent decay. */ - for(ALsizei j{0};j < NUM_LINES;j++) + for(size_t j{0u};j < NUM_LINES;j++) { - ALsizei late_delay_tap{offset - State->mLateDelayTap[j][0]}; - ALsizei late_feedb_tap{offset - State->mLate.Offset[j][0]}; - const ALfloat midGain{State->mLate.T60[j].MidGain[0]}; - const ALfloat densityGain{State->mLate.DensityGain[0] * midGain}; - for(ALsizei i{0};i < todo;) + size_t late_delay_tap{offset - mLateDelayTap[j][0]}; + size_t late_feedb_tap{offset - mLate.Offset[j][0]}; + const float midGain{mLate.T60[j].MidGain[0]}; + const float densityGain{mLate.DensityGain[0] * midGain}; + + for(size_t i{0u};i < todo;) { late_delay_tap &= main_delay.Mask; - late_feedb_tap &= late_delay.Mask; - ALsizei td{mini( - mini(main_delay.Mask+1 - late_delay_tap, late_delay.Mask+1 - late_feedb_tap), - todo - i)}; + size_t td{minz(todo - i, main_delay.Mask+1 - late_delay_tap)}; do { - temps[j][i++] = - main_delay.Line[late_delay_tap++][j]*densityGain + - late_delay.Line[late_feedb_tap++][j]*midGain; + /* Calculate the read offset and fraction between it and the + * next sample. + */ + const float fdelay{mLate.Mod.ModDelays[i]}; + const size_t delay{float2uint(fdelay)}; + const float frac{fdelay - static_cast(delay)}; + + /* Feed the delay line with the late feedback sample, and get + * the two samples crossed by the delayed offset. + */ + const float out0{late_delay.Line[(late_feedb_tap-delay) & late_delay.Mask][j]}; + const float out1{late_delay.Line[(late_feedb_tap-delay-1) & late_delay.Mask][j]}; + ++late_feedb_tap; + + /* The output is obtained by linearly interpolating the two + * samples that were acquired above, and combined with the main + * delay tap. + */ + mTempSamples[j][i] = lerpf(out0, out1, frac)*midGain + + main_delay.Line[late_delay_tap++][j]*densityGain; + ++i; } while(--td); } - State->mLate.T60[j].process(temps[j], todo); + mLate.T60[j].process({mTempSamples[j].data(), todo}); } /* Apply a vector all-pass to improve micro-surface diffusion, and write * out the results for mixing. */ - State->mLate.VecAp.processUnfaded(temps, offset, mixX, mixY, todo); - - for(ALsizei j{0};j < NUM_LINES;j++) - std::copy_n(temps[j], todo, out[j]+base); + mLate.VecAp.processUnfaded(mTempSamples, offset, mixX, mixY, todo); + for(size_t j{0u};j < NUM_LINES;j++) + std::copy_n(mTempSamples[j].begin(), todo, mLateSamples[j].begin()); /* Finally, scatter and bounce the results to refeed the feedback buffer. */ - VectorScatterRevDelayIn(&late_delay, offset, mixX, mixY, base, out, todo); + VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, mTempSamples, todo); } -void LateReverb_Faded(ReverbState *State, const ALsizei offset, const ALsizei todo, - const ALfloat fade, const ALsizei base, ALfloat (*RESTRICT out)[BUFFERSIZE]) +void ReverbState::lateFaded(const size_t offset, const size_t todo, const float fade, + const float fadeStep) { - ALfloat (*RESTRICT temps)[BUFFERSIZE]{State->mTempSamples}; - const DelayLineI late_delay{State->mLate.Delay}; - const DelayLineI main_delay{State->mDelay}; - const ALfloat mixX{State->mMixX}; - const ALfloat mixY{State->mMixY}; + const DelayLineI late_delay{mLate.Delay}; + const DelayLineI main_delay{mDelay}; + const float mixX{mMixX}; + const float mixY{mMixY}; ASSUME(todo > 0); - for(ALsizei j{0};j < NUM_LINES;j++) + mLate.Mod.calcFadedDelays(todo, fade, fadeStep); + + for(size_t j{0u};j < NUM_LINES;j++) { - const ALfloat oldMidGain{State->mLate.T60[j].MidGain[0]}; - const ALfloat midGain{State->mLate.T60[j].MidGain[1]}; - const ALfloat oldMidStep{-oldMidGain / FADE_SAMPLES}; - const ALfloat midStep{midGain / FADE_SAMPLES}; - const ALfloat oldDensityGain{State->mLate.DensityGain[0] * oldMidGain}; - const ALfloat densityGain{State->mLate.DensityGain[1] * midGain}; - const ALfloat oldDensityStep{-oldDensityGain / FADE_SAMPLES}; - const ALfloat densityStep{densityGain / FADE_SAMPLES}; - ALsizei late_delay_tap0{offset - State->mLateDelayTap[j][0]}; - ALsizei late_delay_tap1{offset - State->mLateDelayTap[j][1]}; - ALsizei late_feedb_tap0{offset - State->mLate.Offset[j][0]}; - ALsizei late_feedb_tap1{offset - State->mLate.Offset[j][1]}; - ALfloat fadeCount{fade}; - - for(ALsizei i{0};i < todo;) + const float oldMidGain{mLate.T60[j].MidGain[0]}; + const float midGain{mLate.T60[j].MidGain[1]}; + const float oldMidStep{-oldMidGain * fadeStep}; + const float midStep{midGain * fadeStep}; + const float oldDensityGain{mLate.DensityGain[0] * oldMidGain}; + const float densityGain{mLate.DensityGain[1] * midGain}; + const float oldDensityStep{-oldDensityGain * fadeStep}; + const float densityStep{densityGain * fadeStep}; + size_t late_delay_tap0{offset - mLateDelayTap[j][0]}; + size_t late_delay_tap1{offset - mLateDelayTap[j][1]}; + size_t late_feedb_tap0{offset - mLate.Offset[j][0]}; + size_t late_feedb_tap1{offset - mLate.Offset[j][1]}; + float fadeCount{fade}; + + for(size_t i{0u};i < todo;) { late_delay_tap0 &= main_delay.Mask; late_delay_tap1 &= main_delay.Mask; - late_feedb_tap0 &= late_delay.Mask; - late_feedb_tap1 &= late_delay.Mask; - ALsizei td{mini( - mini(main_delay.Mask+1 - maxi(late_delay_tap0, late_delay_tap1), - late_delay.Mask+1 - maxi(late_feedb_tap0, late_feedb_tap1)), - todo - i)}; + size_t td{minz(todo - i, main_delay.Mask+1 - maxz(late_delay_tap0, late_delay_tap1))}; do { fadeCount += 1.0f; - const ALfloat fade0{oldDensityGain + oldDensityStep*fadeCount}; - const ALfloat fade1{densityStep*fadeCount}; - const ALfloat gfade0{oldMidGain + oldMidStep*fadeCount}; - const ALfloat gfade1{midStep*fadeCount}; - temps[j][i++] = + + const float fdelay{mLate.Mod.ModDelays[i]}; + const size_t delay{float2uint(fdelay)}; + const float frac{fdelay - static_cast(delay)}; + + const float out00{late_delay.Line[(late_feedb_tap0-delay) & late_delay.Mask][j]}; + const float out01{late_delay.Line[(late_feedb_tap0-delay-1) & late_delay.Mask][j]}; + ++late_feedb_tap0; + const float out10{late_delay.Line[(late_feedb_tap1-delay) & late_delay.Mask][j]}; + const float out11{late_delay.Line[(late_feedb_tap1-delay-1) & late_delay.Mask][j]}; + ++late_feedb_tap1; + + const float fade0{oldDensityGain + oldDensityStep*fadeCount}; + const float fade1{densityStep*fadeCount}; + const float gfade0{oldMidGain + oldMidStep*fadeCount}; + const float gfade1{midStep*fadeCount}; + mTempSamples[j][i] = lerpf(out00, out01, frac)*gfade0 + + lerpf(out10, out11, frac)*gfade1 + main_delay.Line[late_delay_tap0++][j]*fade0 + - main_delay.Line[late_delay_tap1++][j]*fade1 + - late_delay.Line[late_feedb_tap0++][j]*gfade0 + - late_delay.Line[late_feedb_tap1++][j]*gfade1; + main_delay.Line[late_delay_tap1++][j]*fade1; + ++i; } while(--td); } - State->mLate.T60[j].process(temps[j], todo); + mLate.T60[j].process({mTempSamples[j].data(), todo}); } - State->mLate.VecAp.processFaded(temps, offset, mixX, mixY, fade, todo); - - for(ALsizei j{0};j < NUM_LINES;j++) - std::copy_n(temps[j], todo, out[j]+base); + mLate.VecAp.processFaded(mTempSamples, offset, mixX, mixY, fade, fadeStep, todo); + for(size_t j{0u};j < NUM_LINES;j++) + std::copy_n(mTempSamples[j].begin(), todo, mLateSamples[j].begin()); - VectorScatterRevDelayIn(&late_delay, offset, mixX, mixY, base, out, todo); + VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, mTempSamples, todo); } -void ReverbState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) +void ReverbState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - ALsizei fadeCount{mFadeCount}; + size_t offset{mOffset}; ASSUME(samplesToDo > 0); /* Convert B-Format to A-Format for processing. */ - ALfloat (&afmt)[NUM_LINES][BUFFERSIZE] = mTempSamples; - for(ALsizei c{0};c < NUM_LINES;c++) + const size_t numInput{minz(samplesIn.size(), NUM_LINES)}; + const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo}; + for(size_t c{0u};c < NUM_LINES;c++) { - std::fill_n(std::begin(afmt[c]), samplesToDo, 0.0f); - MixRowSamples(afmt[c], B2A[c], samplesIn, numInput, 0, samplesToDo); - - /* Band-pass the incoming samples. */ - mFilter[c].Lp.process(afmt[c], afmt[c], samplesToDo); - mFilter[c].Hp.process(afmt[c], afmt[c], samplesToDo); - } - - /* Process reverb for these samples. */ - for(ALsizei base{0};base < samplesToDo;) - { - ALsizei todo{samplesToDo - base}; - /* If cross-fading, don't do more samples than there are to fade. */ - if(FADE_SAMPLES-fadeCount > 0) - { - todo = mini(todo, FADE_SAMPLES-fadeCount); - todo = mini(todo, mMaxUpdate[0]); - } - todo = mini(todo, mMaxUpdate[1]); - ASSUME(todo > 0 && todo <= BUFFERSIZE); - - const ALsizei offset{mOffset + base}; - ASSUME(offset >= 0); - - /* Feed the initial delay line. */ - for(ALsizei c{0};c < NUM_LINES;c++) - mDelay.write(offset, c, afmt[c]+base, todo); - - /* Process the samples for reverb. */ - if(UNLIKELY(fadeCount < FADE_SAMPLES)) + std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); + for(size_t i{0};i < numInput;++i) { - auto fade = static_cast(fadeCount); + const float gain{B2A[c][i]}; + const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())}; - /* Generate early reflections and late reverb. */ - EarlyReflection_Faded(this, offset, todo, fade, base, mEarlyBuffer); - - LateReverb_Faded(this, offset, todo, fade, base, mLateBuffer); - - /* Step fading forward. */ - fadeCount += todo; - if(fadeCount >= FADE_SAMPLES) + for(float &sample : tmpspan) { - /* Update the cross-fading delay line taps. */ - fadeCount = FADE_SAMPLES; - for(ALsizei c{0};c < NUM_LINES;c++) - { - mEarlyDelayTap[c][0] = mEarlyDelayTap[c][1]; - mEarlyDelayCoeff[c][0] = mEarlyDelayCoeff[c][1]; - mEarly.VecAp.Offset[c][0] = mEarly.VecAp.Offset[c][1]; - mEarly.Offset[c][0] = mEarly.Offset[c][1]; - mEarly.Coeff[c][0] = mEarly.Coeff[c][1]; - mLateDelayTap[c][0] = mLateDelayTap[c][1]; - mLate.VecAp.Offset[c][0] = mLate.VecAp.Offset[c][1]; - mLate.Offset[c][0] = mLate.Offset[c][1]; - mLate.T60[c].MidGain[0] = mLate.T60[c].MidGain[1]; - } - mLate.DensityGain[0] = mLate.DensityGain[1]; - mMaxUpdate[0] = mMaxUpdate[1]; + sample += *input * gain; + ++input; } } - else - { - /* Generate early reflections and late reverb. */ - EarlyReflection_Unfaded(this, offset, todo, base, mEarlyBuffer); - LateReverb_Unfaded(this, offset, todo, base, mLateBuffer); - } - - base += todo; + /* Band-pass the incoming samples and feed the initial delay line. */ + DualBiquad{mFilter[c].Lp, mFilter[c].Hp}.process(tmpspan, tmpspan.data()); + mDelay.write(offset, c, tmpspan.cbegin(), samplesToDo); } - mOffset = (mOffset+samplesToDo) & 0x3fffffff; - mFadeCount = fadeCount; - - /* Finally, mix early reflections and late reverb. */ - (this->*mMixOut)(numOutput, samplesOut, samplesToDo); -} - -void EAXReverb_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_EAXREVERB_DECAY_HFLIMIT: - if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay hflimit out of range"); - props->Reverb.DecayHFLimit = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", - param); - } -} -void EAXReverb_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ EAXReverb_setParami(props, context, param, vals[0]); } -void EAXReverb_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_EAXREVERB_DENSITY: - if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb density out of range"); - props->Reverb.Density = val; - break; - - case AL_EAXREVERB_DIFFUSION: - if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb diffusion out of range"); - props->Reverb.Diffusion = val; - break; - - case AL_EAXREVERB_GAIN: - if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gain out of range"); - props->Reverb.Gain = val; - break; - - case AL_EAXREVERB_GAINHF: - if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gainhf out of range"); - props->Reverb.GainHF = val; - break; - - case AL_EAXREVERB_GAINLF: - if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gainlf out of range"); - props->Reverb.GainLF = val; - break; - - case AL_EAXREVERB_DECAY_TIME: - if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay time out of range"); - props->Reverb.DecayTime = val; - break; - - case AL_EAXREVERB_DECAY_HFRATIO: - if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay hfratio out of range"); - props->Reverb.DecayHFRatio = val; - break; - - case AL_EAXREVERB_DECAY_LFRATIO: - if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay lfratio out of range"); - props->Reverb.DecayLFRatio = val; - break; - - case AL_EAXREVERB_REFLECTIONS_GAIN: - if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections gain out of range"); - props->Reverb.ReflectionsGain = val; - break; - - case AL_EAXREVERB_REFLECTIONS_DELAY: - if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections delay out of range"); - props->Reverb.ReflectionsDelay = val; - break; - - case AL_EAXREVERB_LATE_REVERB_GAIN: - if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb gain out of range"); - props->Reverb.LateReverbGain = val; - break; - - case AL_EAXREVERB_LATE_REVERB_DELAY: - if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb delay out of range"); - props->Reverb.LateReverbDelay = val; - break; - - case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: - if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb air absorption gainhf out of range"); - props->Reverb.AirAbsorptionGainHF = val; - break; - - case AL_EAXREVERB_ECHO_TIME: - if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb echo time out of range"); - props->Reverb.EchoTime = val; - break; - - case AL_EAXREVERB_ECHO_DEPTH: - if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb echo depth out of range"); - props->Reverb.EchoDepth = val; - break; - - case AL_EAXREVERB_MODULATION_TIME: - if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb modulation time out of range"); - props->Reverb.ModulationTime = val; - break; - - case AL_EAXREVERB_MODULATION_DEPTH: - if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb modulation depth out of range"); - props->Reverb.ModulationDepth = val; - break; - - case AL_EAXREVERB_HFREFERENCE: - if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb hfreference out of range"); - props->Reverb.HFReference = val; - break; - - case AL_EAXREVERB_LFREFERENCE: - if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb lfreference out of range"); - props->Reverb.LFReference = val; - break; - - case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: - if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb room rolloff factor out of range"); - props->Reverb.RoomRolloffFactor = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", - param); - } -} -void EAXReverb_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ - switch(param) + /* Process reverb for these samples. */ + if LIKELY(!mDoFading) { - case AL_EAXREVERB_REFLECTIONS_PAN: - if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2]))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections pan out of range"); - props->Reverb.ReflectionsPan[0] = vals[0]; - props->Reverb.ReflectionsPan[1] = vals[1]; - props->Reverb.ReflectionsPan[2] = vals[2]; - break; - case AL_EAXREVERB_LATE_REVERB_PAN: - if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2]))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb pan out of range"); - props->Reverb.LateReverbPan[0] = vals[0]; - props->Reverb.LateReverbPan[1] = vals[1]; - props->Reverb.LateReverbPan[2] = vals[2]; - break; - - default: - EAXReverb_setParamf(props, context, param, vals[0]); - break; - } -} + for(size_t base{0};base < samplesToDo;) + { + /* Calculate the number of samples we can do this iteration. */ + size_t todo{minz(samplesToDo - base, mMaxUpdate[0])}; + /* Some mixers require maintaining a 4-sample alignment, so ensure + * that if it's not the last iteration. + */ + if(base+todo < samplesToDo) todo &= ~size_t{3}; + ASSUME(todo > 0); -void EAXReverb_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_EAXREVERB_DECAY_HFLIMIT: - *val = props->Reverb.DecayHFLimit; - break; + /* Generate non-faded early reflections and late reverb. */ + earlyUnfaded(offset, todo); + lateUnfaded(offset, todo); - default: - alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", - param); + /* Finally, mix early reflections and late reverb. */ + (this->*mMixOut)(samplesOut, samplesToDo-base, base, todo); + + offset += todo; + base += todo; + } } -} -void EAXReverb_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ EAXReverb_getParami(props, context, param, vals); } -void EAXReverb_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) + else { - case AL_EAXREVERB_DENSITY: - *val = props->Reverb.Density; - break; - - case AL_EAXREVERB_DIFFUSION: - *val = props->Reverb.Diffusion; - break; - - case AL_EAXREVERB_GAIN: - *val = props->Reverb.Gain; - break; - - case AL_EAXREVERB_GAINHF: - *val = props->Reverb.GainHF; - break; - - case AL_EAXREVERB_GAINLF: - *val = props->Reverb.GainLF; - break; - - case AL_EAXREVERB_DECAY_TIME: - *val = props->Reverb.DecayTime; - break; - - case AL_EAXREVERB_DECAY_HFRATIO: - *val = props->Reverb.DecayHFRatio; - break; - - case AL_EAXREVERB_DECAY_LFRATIO: - *val = props->Reverb.DecayLFRatio; - break; - - case AL_EAXREVERB_REFLECTIONS_GAIN: - *val = props->Reverb.ReflectionsGain; - break; - - case AL_EAXREVERB_REFLECTIONS_DELAY: - *val = props->Reverb.ReflectionsDelay; - break; - - case AL_EAXREVERB_LATE_REVERB_GAIN: - *val = props->Reverb.LateReverbGain; - break; - - case AL_EAXREVERB_LATE_REVERB_DELAY: - *val = props->Reverb.LateReverbDelay; - break; - - case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: - *val = props->Reverb.AirAbsorptionGainHF; - break; - - case AL_EAXREVERB_ECHO_TIME: - *val = props->Reverb.EchoTime; - break; - - case AL_EAXREVERB_ECHO_DEPTH: - *val = props->Reverb.EchoDepth; - break; - - case AL_EAXREVERB_MODULATION_TIME: - *val = props->Reverb.ModulationTime; - break; - - case AL_EAXREVERB_MODULATION_DEPTH: - *val = props->Reverb.ModulationDepth; - break; + const float fadeStep{1.0f / static_cast(samplesToDo)}; + for(size_t base{0};base < samplesToDo;) + { + size_t todo{minz(samplesToDo - base, minz(mMaxUpdate[0], mMaxUpdate[1]))}; + if(base+todo < samplesToDo) todo &= ~size_t{3}; + ASSUME(todo > 0); - case AL_EAXREVERB_HFREFERENCE: - *val = props->Reverb.HFReference; - break; + /* Generate cross-faded early reflections and late reverb. */ + auto fadeCount = static_cast(base); + earlyFaded(offset, todo, fadeCount, fadeStep); + lateFaded(offset, todo, fadeCount, fadeStep); - case AL_EAXREVERB_LFREFERENCE: - *val = props->Reverb.LFReference; - break; + (this->*mMixOut)(samplesOut, samplesToDo-base, base, todo); - case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: - *val = props->Reverb.RoomRolloffFactor; - break; + offset += todo; + base += todo; + } - default: - alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", - param); - } -} -void EAXReverb_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ - switch(param) - { - case AL_EAXREVERB_REFLECTIONS_PAN: - vals[0] = props->Reverb.ReflectionsPan[0]; - vals[1] = props->Reverb.ReflectionsPan[1]; - vals[2] = props->Reverb.ReflectionsPan[2]; - break; - case AL_EAXREVERB_LATE_REVERB_PAN: - vals[0] = props->Reverb.LateReverbPan[0]; - vals[1] = props->Reverb.LateReverbPan[1]; - vals[2] = props->Reverb.LateReverbPan[2]; - break; - - default: - EAXReverb_getParamf(props, context, param, vals); - break; + /* Update the cross-fading delay line taps. */ + for(size_t c{0u};c < NUM_LINES;c++) + { + mEarlyDelayTap[c][0] = mEarlyDelayTap[c][1]; + mEarlyDelayCoeff[c][0] = mEarlyDelayCoeff[c][1]; + mLateDelayTap[c][0] = mLateDelayTap[c][1]; + mEarly.VecAp.Offset[c][0] = mEarly.VecAp.Offset[c][1]; + mEarly.Offset[c][0] = mEarly.Offset[c][1]; + mEarly.Coeff[c][0] = mEarly.Coeff[c][1]; + mLate.Offset[c][0] = mLate.Offset[c][1]; + mLate.T60[c].MidGain[0] = mLate.T60[c].MidGain[1]; + mLate.VecAp.Offset[c][0] = mLate.VecAp.Offset[c][1]; + } + mLate.DensityGain[0] = mLate.DensityGain[1]; + mLate.Mod.Depth[0] = mLate.Mod.Depth[1]; + mMaxUpdate[0] = mMaxUpdate[1]; + mDoFading = false; } + mOffset = offset; } -DEFINE_ALEFFECT_VTABLE(EAXReverb); - struct ReverbStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ReverbState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &EAXReverb_vtable; } + al::intrusive_ptr create() override + { return al::intrusive_ptr{new ReverbState{}}; } }; -EffectProps ReverbStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Reverb.Density = AL_EAXREVERB_DEFAULT_DENSITY; - props.Reverb.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION; - props.Reverb.Gain = AL_EAXREVERB_DEFAULT_GAIN; - props.Reverb.GainHF = AL_EAXREVERB_DEFAULT_GAINHF; - props.Reverb.GainLF = AL_EAXREVERB_DEFAULT_GAINLF; - props.Reverb.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME; - props.Reverb.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO; - props.Reverb.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO; - props.Reverb.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN; - props.Reverb.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY; - props.Reverb.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN; - props.Reverb.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY; - props.Reverb.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME; - props.Reverb.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH; - props.Reverb.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME; - props.Reverb.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH; - props.Reverb.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF; - props.Reverb.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE; - props.Reverb.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE; - props.Reverb.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; - props.Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT; - return props; -} - - -void StdReverb_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val) -{ - switch(param) - { - case AL_REVERB_DECAY_HFLIMIT: - if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay hflimit out of range"); - props->Reverb.DecayHFLimit = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param); - } -} -void StdReverb_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals) -{ StdReverb_setParami(props, context, param, vals[0]); } -void StdReverb_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_REVERB_DENSITY: - if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb density out of range"); - props->Reverb.Density = val; - break; - - case AL_REVERB_DIFFUSION: - if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb diffusion out of range"); - props->Reverb.Diffusion = val; - break; - - case AL_REVERB_GAIN: - if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb gain out of range"); - props->Reverb.Gain = val; - break; - - case AL_REVERB_GAINHF: - if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb gainhf out of range"); - props->Reverb.GainHF = val; - break; - - case AL_REVERB_DECAY_TIME: - if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay time out of range"); - props->Reverb.DecayTime = val; - break; - - case AL_REVERB_DECAY_HFRATIO: - if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay hfratio out of range"); - props->Reverb.DecayHFRatio = val; - break; - - case AL_REVERB_REFLECTIONS_GAIN: - if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb reflections gain out of range"); - props->Reverb.ReflectionsGain = val; - break; - - case AL_REVERB_REFLECTIONS_DELAY: - if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb reflections delay out of range"); - props->Reverb.ReflectionsDelay = val; - break; - - case AL_REVERB_LATE_REVERB_GAIN: - if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb late reverb gain out of range"); - props->Reverb.LateReverbGain = val; - break; - - case AL_REVERB_LATE_REVERB_DELAY: - if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb late reverb delay out of range"); - props->Reverb.LateReverbDelay = val; - break; - - case AL_REVERB_AIR_ABSORPTION_GAINHF: - if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb air absorption gainhf out of range"); - props->Reverb.AirAbsorptionGainHF = val; - break; - - case AL_REVERB_ROOM_ROLLOFF_FACTOR: - if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb room rolloff factor out of range"); - props->Reverb.RoomRolloffFactor = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param); - } -} -void StdReverb_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals) -{ StdReverb_setParamf(props, context, param, vals[0]); } - -void StdReverb_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val) -{ - switch(param) - { - case AL_REVERB_DECAY_HFLIMIT: - *val = props->Reverb.DecayHFLimit; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param); - } -} -void StdReverb_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals) -{ StdReverb_getParami(props, context, param, vals); } -void StdReverb_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_REVERB_DENSITY: - *val = props->Reverb.Density; - break; - - case AL_REVERB_DIFFUSION: - *val = props->Reverb.Diffusion; - break; - - case AL_REVERB_GAIN: - *val = props->Reverb.Gain; - break; - - case AL_REVERB_GAINHF: - *val = props->Reverb.GainHF; - break; - - case AL_REVERB_DECAY_TIME: - *val = props->Reverb.DecayTime; - break; - - case AL_REVERB_DECAY_HFRATIO: - *val = props->Reverb.DecayHFRatio; - break; - - case AL_REVERB_REFLECTIONS_GAIN: - *val = props->Reverb.ReflectionsGain; - break; - - case AL_REVERB_REFLECTIONS_DELAY: - *val = props->Reverb.ReflectionsDelay; - break; - - case AL_REVERB_LATE_REVERB_GAIN: - *val = props->Reverb.LateReverbGain; - break; - - case AL_REVERB_LATE_REVERB_DELAY: - *val = props->Reverb.LateReverbDelay; - break; - - case AL_REVERB_AIR_ABSORPTION_GAINHF: - *val = props->Reverb.AirAbsorptionGainHF; - break; - - case AL_REVERB_ROOM_ROLLOFF_FACTOR: - *val = props->Reverb.RoomRolloffFactor; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param); - } -} -void StdReverb_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals) -{ StdReverb_getParamf(props, context, param, vals); } - -DEFINE_ALEFFECT_VTABLE(StdReverb); - - struct StdReverbStateFactory final : public EffectStateFactory { - EffectState *create() override { return new ReverbState{}; } - EffectProps getDefaultProps() const noexcept override; - const EffectVtable *getEffectVtable() const noexcept override { return &StdReverb_vtable; } + al::intrusive_ptr create() override + { return al::intrusive_ptr{new ReverbState{}}; } }; -EffectProps StdReverbStateFactory::getDefaultProps() const noexcept -{ - EffectProps props{}; - props.Reverb.Density = AL_REVERB_DEFAULT_DENSITY; - props.Reverb.Diffusion = AL_REVERB_DEFAULT_DIFFUSION; - props.Reverb.Gain = AL_REVERB_DEFAULT_GAIN; - props.Reverb.GainHF = AL_REVERB_DEFAULT_GAINHF; - props.Reverb.GainLF = 1.0f; - props.Reverb.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME; - props.Reverb.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO; - props.Reverb.DecayLFRatio = 1.0f; - props.Reverb.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN; - props.Reverb.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY; - props.Reverb.ReflectionsPan[0] = 0.0f; - props.Reverb.ReflectionsPan[1] = 0.0f; - props.Reverb.ReflectionsPan[2] = 0.0f; - props.Reverb.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN; - props.Reverb.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY; - props.Reverb.LateReverbPan[0] = 0.0f; - props.Reverb.LateReverbPan[1] = 0.0f; - props.Reverb.LateReverbPan[2] = 0.0f; - props.Reverb.EchoTime = 0.25f; - props.Reverb.EchoDepth = 0.0f; - props.Reverb.ModulationTime = 0.25f; - props.Reverb.ModulationDepth = 0.0f; - props.Reverb.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF; - props.Reverb.HFReference = 5000.0f; - props.Reverb.LFReference = 250.0f; - props.Reverb.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; - props.Reverb.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT; - return props; -} - } // namespace EffectStateFactory *ReverbStateFactory_getFactory() diff --git a/modules/openal-soft/Alc/effects/vmorpher.cpp b/modules/openal-soft/Alc/effects/vmorpher.cpp new file mode 100644 index 0000000..edc50eb --- /dev/null +++ b/modules/openal-soft/Alc/effects/vmorpher.cpp @@ -0,0 +1,337 @@ +/** + * This file is part of the OpenAL Soft cross platform audio library + * + * Copyright (C) 2019 by Anis A. Hireche + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Spherical-Harmonic-Transform nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" + + +namespace { + +using uint = unsigned int; + +#define MAX_UPDATE_SAMPLES 256 +#define NUM_FORMANTS 4 +#define NUM_FILTERS 2 +#define Q_FACTOR 5.0f + +#define VOWEL_A_INDEX 0 +#define VOWEL_B_INDEX 1 + +#define WAVEFORM_FRACBITS 24 +#define WAVEFORM_FRACONE (1<*2.0f / WAVEFORM_FRACONE}; + return std::sin(static_cast(index) * scale)*0.5f + 0.5f; +} + +inline float Saw(uint index) +{ return static_cast(index) / float{WAVEFORM_FRACONE}; } + +inline float Triangle(uint index) +{ return std::fabs(static_cast(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f); } + +inline float Half(uint) { return 0.5f; } + +template +void Oscillate(float *RESTRICT dst, uint index, const uint step, size_t todo) +{ + for(size_t i{0u};i < todo;i++) + { + index += step; + index &= WAVEFORM_FRACMASK; + dst[i] = func(index); + } +} + +struct FormantFilter +{ + float mCoeff{0.0f}; + float mGain{1.0f}; + float mS1{0.0f}; + float mS2{0.0f}; + + FormantFilter() = default; + FormantFilter(float f0norm, float gain) + : mCoeff{std::tan(al::numbers::pi_v * f0norm)}, mGain{gain} + { } + + inline void process(const float *samplesIn, float *samplesOut, const size_t numInput) + { + /* A state variable filter from a topology-preserving transform. + * Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg + */ + const float g{mCoeff}; + const float gain{mGain}; + const float h{1.0f / (1.0f + (g/Q_FACTOR) + (g*g))}; + float s1{mS1}; + float s2{mS2}; + + for(size_t i{0u};i < numInput;i++) + { + const float H{(samplesIn[i] - (1.0f/Q_FACTOR + g)*s1 - s2)*h}; + const float B{g*H + s1}; + const float L{g*B + s2}; + + s1 = g*H + B; + s2 = g*B + L; + + // Apply peak and accumulate samples. + samplesOut[i] += B * gain; + } + mS1 = s1; + mS2 = s2; + } + + inline void clear() + { + mS1 = 0.0f; + mS2 = 0.0f; + } +}; + + +struct VmorpherState final : public EffectState { + struct { + /* Effect parameters */ + FormantFilter Formants[NUM_FILTERS][NUM_FORMANTS]; + + /* Effect gains for each channel */ + float CurrentGains[MAX_OUTPUT_CHANNELS]{}; + float TargetGains[MAX_OUTPUT_CHANNELS]{}; + } mChans[MaxAmbiChannels]; + + void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){}; + + uint mIndex{0}; + uint mStep{1}; + + /* Effects buffers */ + alignas(16) float mSampleBufferA[MAX_UPDATE_SAMPLES]{}; + alignas(16) float mSampleBufferB[MAX_UPDATE_SAMPLES]{}; + alignas(16) float mLfo[MAX_UPDATE_SAMPLES]{}; + + void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) override; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) override; + + static std::array getFiltersByPhoneme(VMorpherPhenome phoneme, + float frequency, float pitch); + + DEF_NEWDEL(VmorpherState) +}; + +std::array VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme, + float frequency, float pitch) +{ + /* Using soprano formant set of values to + * better match mid-range frequency space. + * + * See: https://www.classes.cs.uchicago.edu/archive/1999/spring/CS295/Computing_Resources/Csound/CsManual3.48b1.HTML/Appendices/table3.html + */ + switch(phoneme) + { + case VMorpherPhenome::A: + return {{ + {( 800 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ + {(1150 * pitch) / frequency, 0.501187f}, /* std::pow(10.0f, -6 / 20.0f); */ + {(2900 * pitch) / frequency, 0.025118f}, /* std::pow(10.0f, -32 / 20.0f); */ + {(3900 * pitch) / frequency, 0.100000f} /* std::pow(10.0f, -20 / 20.0f); */ + }}; + case VMorpherPhenome::E: + return {{ + {( 350 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ + {(2000 * pitch) / frequency, 0.100000f}, /* std::pow(10.0f, -20 / 20.0f); */ + {(2800 * pitch) / frequency, 0.177827f}, /* std::pow(10.0f, -15 / 20.0f); */ + {(3600 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */ + }}; + case VMorpherPhenome::I: + return {{ + {( 270 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ + {(2140 * pitch) / frequency, 0.251188f}, /* std::pow(10.0f, -12 / 20.0f); */ + {(2950 * pitch) / frequency, 0.050118f}, /* std::pow(10.0f, -26 / 20.0f); */ + {(3900 * pitch) / frequency, 0.050118f} /* std::pow(10.0f, -26 / 20.0f); */ + }}; + case VMorpherPhenome::O: + return {{ + {( 450 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ + {( 800 * pitch) / frequency, 0.281838f}, /* std::pow(10.0f, -11 / 20.0f); */ + {(2830 * pitch) / frequency, 0.079432f}, /* std::pow(10.0f, -22 / 20.0f); */ + {(3800 * pitch) / frequency, 0.079432f} /* std::pow(10.0f, -22 / 20.0f); */ + }}; + case VMorpherPhenome::U: + return {{ + {( 325 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ + {( 700 * pitch) / frequency, 0.158489f}, /* std::pow(10.0f, -16 / 20.0f); */ + {(2700 * pitch) / frequency, 0.017782f}, /* std::pow(10.0f, -35 / 20.0f); */ + {(3800 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */ + }}; + default: + break; + } + return {}; +} + + +void VmorpherState::deviceUpdate(const DeviceBase*, const Buffer&) +{ + for(auto &e : mChans) + { + std::for_each(std::begin(e.Formants[VOWEL_A_INDEX]), std::end(e.Formants[VOWEL_A_INDEX]), + std::mem_fn(&FormantFilter::clear)); + std::for_each(std::begin(e.Formants[VOWEL_B_INDEX]), std::end(e.Formants[VOWEL_B_INDEX]), + std::mem_fn(&FormantFilter::clear)); + std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); + } +} + +void VmorpherState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) +{ + const DeviceBase *device{context->mDevice}; + const float frequency{static_cast(device->Frequency)}; + const float step{props->Vmorpher.Rate / frequency}; + mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1})); + + if(mStep == 0) + mGetSamples = Oscillate; + else if(props->Vmorpher.Waveform == VMorpherWaveform::Sinusoid) + mGetSamples = Oscillate; + else if(props->Vmorpher.Waveform == VMorpherWaveform::Triangle) + mGetSamples = Oscillate; + else /*if(props->Vmorpher.Waveform == VMorpherWaveform::Sawtooth)*/ + mGetSamples = Oscillate; + + const float pitchA{std::pow(2.0f, + static_cast(props->Vmorpher.PhonemeACoarseTuning) / 12.0f)}; + const float pitchB{std::pow(2.0f, + static_cast(props->Vmorpher.PhonemeBCoarseTuning) / 12.0f)}; + + auto vowelA = getFiltersByPhoneme(props->Vmorpher.PhonemeA, frequency, pitchA); + auto vowelB = getFiltersByPhoneme(props->Vmorpher.PhonemeB, frequency, pitchB); + + /* Copy the filter coefficients to the input channels. */ + for(size_t i{0u};i < slot->Wet.Buffer.size();++i) + { + std::copy(vowelA.begin(), vowelA.end(), std::begin(mChans[i].Formants[VOWEL_A_INDEX])); + std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].Formants[VOWEL_B_INDEX])); + } + + mOutTarget = target.Main->Buffer; + auto set_gains = [slot,target](auto &chan, al::span coeffs) + { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); }; + SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains); +} + +void VmorpherState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) +{ + /* Following the EFX specification for a conformant implementation which describes + * the effect as a pair of 4-band formant filters blended together using an LFO. + */ + for(size_t base{0u};base < samplesToDo;) + { + const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)}; + + mGetSamples(mLfo, mIndex, mStep, td); + mIndex += static_cast(mStep * td); + mIndex &= WAVEFORM_FRACMASK; + + auto chandata = std::begin(mChans); + for(const auto &input : samplesIn) + { + auto& vowelA = chandata->Formants[VOWEL_A_INDEX]; + auto& vowelB = chandata->Formants[VOWEL_B_INDEX]; + + /* Process first vowel. */ + std::fill_n(std::begin(mSampleBufferA), td, 0.0f); + vowelA[0].process(&input[base], mSampleBufferA, td); + vowelA[1].process(&input[base], mSampleBufferA, td); + vowelA[2].process(&input[base], mSampleBufferA, td); + vowelA[3].process(&input[base], mSampleBufferA, td); + + /* Process second vowel. */ + std::fill_n(std::begin(mSampleBufferB), td, 0.0f); + vowelB[0].process(&input[base], mSampleBufferB, td); + vowelB[1].process(&input[base], mSampleBufferB, td); + vowelB[2].process(&input[base], mSampleBufferB, td); + vowelB[3].process(&input[base], mSampleBufferB, td); + + alignas(16) float blended[MAX_UPDATE_SAMPLES]; + for(size_t i{0u};i < td;i++) + blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]); + + /* Now, mix the processed sound data to the output. */ + MixSamples({blended, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains, + samplesToDo-base, base); + ++chandata; + } + + base += td; + } +} + + +struct VmorpherStateFactory final : public EffectStateFactory { + al::intrusive_ptr create() override + { return al::intrusive_ptr{new VmorpherState{}}; } +}; + +} // namespace + +EffectStateFactory *VmorpherStateFactory_getFactory() +{ + static VmorpherStateFactory VmorpherFactory{}; + return &VmorpherFactory; +} diff --git a/modules/openal-soft/Alc/filters/biquad.h b/modules/openal-soft/Alc/filters/biquad.h deleted file mode 100644 index 57edd78..0000000 --- a/modules/openal-soft/Alc/filters/biquad.h +++ /dev/null @@ -1,137 +0,0 @@ -#ifndef FILTERS_BIQUAD_H -#define FILTERS_BIQUAD_H - -#include -#include - -#include "AL/al.h" -#include "math_defs.h" - - -/* Filters implementation is based on the "Cookbook formulae for audio - * EQ biquad filter coefficients" by Robert Bristow-Johnson - * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt - */ -/* Implementation note: For the shelf filters, the specified gain is for the - * reference frequency, which is the centerpoint of the transition band. This - * better matches EFX filter design. To set the gain for the shelf itself, use - * the square root of the desired linear gain (or halve the dB gain). - */ - -enum class BiquadType { - /** EFX-style low-pass filter, specifying a gain and reference frequency. */ - HighShelf, - /** EFX-style high-pass filter, specifying a gain and reference frequency. */ - LowShelf, - /** Peaking filter, specifying a gain and reference frequency. */ - Peaking, - - /** Low-pass cut-off filter, specifying a cut-off frequency. */ - LowPass, - /** High-pass cut-off filter, specifying a cut-off frequency. */ - HighPass, - /** Band-pass filter, specifying a center frequency. */ - BandPass, -}; - -template -class BiquadFilterR { - /* Last two delayed components for direct form II. */ - Real z1{0.0f}, z2{0.0f}; - /* Transfer function coefficients "b" (numerator) */ - Real b0{1.0f}, b1{0.0f}, b2{0.0f}; - /* Transfer function coefficients "a" (denominator; a0 is pre-applied). */ - Real a1{0.0f}, a2{0.0f}; - -public: - void clear() noexcept { z1 = z2 = 0.0f; } - - /** - * Sets the filter state for the specified filter type and its parameters. - * - * \param type The type of filter to apply. - * \param gain The gain for the reference frequency response. Only used by - * the Shelf and Peaking filter types. - * \param f0norm The reference frequency normal (ref_freq / sample_rate). - * This is the center point for the Shelf, Peaking, and - * BandPass filter types, or the cutoff frequency for the - * LowPass and HighPass filter types. - * \param rcpQ The reciprocal of the Q coefficient for the filter's - * transition band. Can be generated from calc_rcpQ_from_slope - * or calc_rcpQ_from_bandwidth as needed. - */ - void setParams(BiquadType type, Real gain, Real f0norm, Real rcpQ); - - void copyParamsFrom(const BiquadFilterR &other) - { - b0 = other.b0; - b1 = other.b1; - b2 = other.b2; - a1 = other.a1; - a2 = other.a2; - } - - - void process(Real *dst, const Real *src, int numsamples); - - void passthru(int numsamples) noexcept - { - if(LIKELY(numsamples >= 2)) - { - z1 = 0.0f; - z2 = 0.0f; - } - else if(numsamples == 1) - { - z1 = z2; - z2 = 0.0f; - } - } - - /* Rather hacky. It's just here to support "manual" processing. */ - std::pair getComponents() const noexcept - { return {z1, z2}; } - void setComponents(Real z1_, Real z2_) noexcept - { z1 = z1_; z2 = z2_; } - Real processOne(const Real in, Real &z1_, Real &z2_) const noexcept - { - Real out{in*b0 + z1_}; - z1_ = in*b1 - out*a1 + z2_; - z2_ = in*b2 - out*a2; - return out; - } -}; - -using BiquadFilter = BiquadFilterR; - -/** - * Calculates the rcpQ (i.e. 1/Q) coefficient for shelving filters, using the - * reference gain and shelf slope parameter. - * \param gain 0 < gain - * \param slope 0 < slope <= 1 - */ -inline float calc_rcpQ_from_slope(float gain, float slope) -{ return std::sqrt((gain + 1.0f/gain)*(1.0f/slope - 1.0f) + 2.0f); } - -inline double calc_rcpQ_from_slope(double gain, double slope) -{ return std::sqrt((gain + 1.0/gain)*(1.0/slope - 1.0) + 2.0); } - -/** - * Calculates the rcpQ (i.e. 1/Q) coefficient for filters, using the normalized - * reference frequency and bandwidth. - * \param f0norm 0 < f0norm < 0.5. - * \param bandwidth 0 < bandwidth - */ -inline float calc_rcpQ_from_bandwidth(float f0norm, float bandwidth) -{ - const float w0{al::MathDefs::Tau() * f0norm}; - return 2.0f*std::sinh(std::log(2.0f)/2.0f*bandwidth*w0/std::sin(w0)); -} - -inline double calc_rcpQ_from_bandwidth(double f0norm, double bandwidth) -{ - const double w0{al::MathDefs::Tau() * f0norm}; - return 2.0*std::sinh(std::log(2.0)/2.0*bandwidth*w0/std::sin(w0)); -} - -#endif /* FILTERS_BIQUAD_H */ diff --git a/modules/openal-soft/Alc/filters/splitter.cpp b/modules/openal-soft/Alc/filters/splitter.cpp deleted file mode 100644 index 8b1a4db..0000000 --- a/modules/openal-soft/Alc/filters/splitter.cpp +++ /dev/null @@ -1,132 +0,0 @@ - -#include "config.h" - -#include "splitter.h" - -#include -#include -#include - -#include "math_defs.h" - -template -void BandSplitterR::init(Real f0norm) -{ - const Real w{f0norm * al::MathDefs::Tau()}; - const Real cw{std::cos(w)}; - if(cw > std::numeric_limits::epsilon()) - coeff = (std::sin(w) - 1.0f) / cw; - else - coeff = cw * -0.5f; - - lp_z1 = 0.0f; - lp_z2 = 0.0f; - ap_z1 = 0.0f; -} - -template -void BandSplitterR::process(Real *hpout, Real *lpout, const Real *input, const int count) -{ - ASSUME(count > 0); - - const Real ap_coeff{this->coeff}; - const Real lp_coeff{this->coeff*0.5f + 0.5f}; - Real lp_z1{this->lp_z1}; - Real lp_z2{this->lp_z2}; - Real ap_z1{this->ap_z1}; - auto proc_sample = [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpout](const Real in) noexcept -> Real - { - /* Low-pass sample processing. */ - Real d{(in - lp_z1) * lp_coeff}; - Real lp_y{lp_z1 + d}; - lp_z1 = lp_y + d; - - d = (lp_y - lp_z2) * lp_coeff; - lp_y = lp_z2 + d; - lp_z2 = lp_y + d; - - *(lpout++) = lp_y; - - /* All-pass sample processing. */ - Real ap_y{in*ap_coeff + ap_z1}; - ap_z1 = in - ap_y*ap_coeff; - - /* High-pass generated from removing low-passed output. */ - return ap_y - lp_y; - }; - std::transform(input, input+count, hpout, proc_sample); - this->lp_z1 = lp_z1; - this->lp_z2 = lp_z2; - this->ap_z1 = ap_z1; -} - -template -void BandSplitterR::applyHfScale(Real *samples, const Real hfscale, const int count) -{ - ASSUME(count > 0); - - const Real ap_coeff{this->coeff}; - const Real lp_coeff{this->coeff*0.5f + 0.5f}; - Real lp_z1{this->lp_z1}; - Real lp_z2{this->lp_z2}; - Real ap_z1{this->ap_z1}; - auto proc_sample = [hfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real - { - /* Low-pass sample processing. */ - Real d{(in - lp_z1) * lp_coeff}; - Real lp_y{lp_z1 + d}; - lp_z1 = lp_y + d; - - d = (lp_y - lp_z2) * lp_coeff; - lp_y = lp_z2 + d; - lp_z2 = lp_y + d; - - /* All-pass sample processing. */ - Real ap_y{in*ap_coeff + ap_z1}; - ap_z1 = in - ap_y*ap_coeff; - - /* High-pass generated from removing low-passed output. */ - return (ap_y-lp_y)*hfscale + lp_y; - }; - std::transform(samples, samples+count, samples, proc_sample); - this->lp_z1 = lp_z1; - this->lp_z2 = lp_z2; - this->ap_z1 = ap_z1; -} - -template class BandSplitterR; -template class BandSplitterR; - - -template -void SplitterAllpassR::init(Real f0norm) -{ - const Real w{f0norm * al::MathDefs::Tau()}; - const Real cw{std::cos(w)}; - if(cw > std::numeric_limits::epsilon()) - coeff = (std::sin(w) - 1.0f) / cw; - else - coeff = cw * -0.5f; - - z1 = 0.0f; -} - -template -void SplitterAllpassR::process(Real *samples, int count) -{ - ASSUME(count > 0); - - const Real coeff{this->coeff}; - Real z1{this->z1}; - auto proc_sample = [coeff,&z1](const Real in) noexcept -> Real - { - const Real out{in*coeff + z1}; - z1 = in - out*coeff; - return out; - }; - std::transform(samples, samples+count, samples, proc_sample); - this->z1 = z1; -} - -template class SplitterAllpassR; -template class SplitterAllpassR; diff --git a/modules/openal-soft/Alc/filters/splitter.h b/modules/openal-soft/Alc/filters/splitter.h deleted file mode 100644 index cd5fe86..0000000 --- a/modules/openal-soft/Alc/filters/splitter.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef FILTER_SPLITTER_H -#define FILTER_SPLITTER_H - -#include "alMain.h" -#include "almalloc.h" - - -/* Band splitter. Splits a signal into two phase-matching frequency bands. */ -template -class BandSplitterR { - Real coeff{0.0f}; - Real lp_z1{0.0f}; - Real lp_z2{0.0f}; - Real ap_z1{0.0f}; - -public: - BandSplitterR() = default; - BandSplitterR(const BandSplitterR&) = default; - BandSplitterR(Real f0norm) { init(f0norm); } - - void init(Real f0norm); - void clear() noexcept { lp_z1 = lp_z2 = ap_z1 = 0.0f; } - void process(Real *hpout, Real *lpout, const Real *input, const int count); - void applyHfScale(Real *samples, const Real hfscale, const int count); -}; -using BandSplitter = BandSplitterR; - -/* The all-pass portion of the band splitter. Applies the same phase shift - * without splitting the signal. - */ -template -class SplitterAllpassR { - Real coeff{0.0f}; - Real z1{0.0f}; - -public: - SplitterAllpassR() = default; - SplitterAllpassR(const SplitterAllpassR&) = default; - SplitterAllpassR(Real f0norm) { init(f0norm); } - - void init(Real f0norm); - void clear() noexcept { z1 = 0.0f; } - void process(Real *samples, int count); -}; -using SplitterAllpass = SplitterAllpassR; - - -struct FrontStablizer { - static constexpr size_t DelayLength{256u}; - - alignas(16) float DelayBuf[MAX_OUTPUT_CHANNELS][DelayLength]; - - SplitterAllpass APFilter; - BandSplitter LFilter, RFilter; - alignas(16) float LSplit[2][BUFFERSIZE]; - alignas(16) float RSplit[2][BUFFERSIZE]; - - alignas(16) float TempBuf[BUFFERSIZE + DelayLength]; - - DEF_NEWDEL(FrontStablizer) -}; - -#endif /* FILTER_SPLITTER_H */ diff --git a/modules/openal-soft/Alc/fpu_modes.h b/modules/openal-soft/Alc/fpu_modes.h deleted file mode 100644 index 5465e9c..0000000 --- a/modules/openal-soft/Alc/fpu_modes.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef FPU_MODES_H -#define FPU_MODES_H - -class FPUCtl { -#if defined(HAVE_SSE_INTRINSICS) || (defined(__GNUC__) && defined(HAVE_SSE)) - unsigned int sse_state{}; -#endif - bool in_mode{}; - -public: - FPUCtl(); - /* HACK: 32-bit targets for GCC seem to have a problem here with certain - * noexcept methods (which destructors are) causing an internal compiler - * error. No idea why it's these methods specifically, but this is needed - * to get it to compile. - */ - ~FPUCtl() noexcept(false) { leave(); } - - FPUCtl(const FPUCtl&) = delete; - FPUCtl& operator=(const FPUCtl&) = delete; - - void leave(); -}; - -#endif /* FPU_MODES_H */ diff --git a/modules/openal-soft/Alc/helpers.cpp b/modules/openal-soft/Alc/helpers.cpp deleted file mode 100644 index 1a62263..0000000 --- a/modules/openal-soft/Alc/helpers.cpp +++ /dev/null @@ -1,740 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2011 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#ifdef _WIN32 -#ifdef __MINGW32__ -#define _WIN32_IE 0x501 -#else -#define _WIN32_IE 0x400 -#endif -#endif - -#include "config.h" - -#include -#include -#include -#include -#include -#ifdef HAVE_MALLOC_H -#include -#endif -#ifdef HAVE_DIRENT_H -#include -#endif -#ifdef HAVE_PROC_PIDPATH -#include -#endif - -#ifdef __FreeBSD__ -#include -#include -#endif - -#ifndef AL_NO_UID_DEFS -#if defined(HAVE_GUIDDEF_H) || defined(HAVE_INITGUID_H) -#define INITGUID -#include -#ifdef HAVE_GUIDDEF_H -#include -#else -#include -#endif - -DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71); -DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71); - -DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf,0x08, 0x00,0xa0,0xc9,0x25,0xcd,0x16); - -DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e,0x3d, 0xc4,0x57,0x92,0x91,0x69,0x2e); -DEFINE_GUID(IID_IMMDeviceEnumerator, 0xa95664d2, 0x9614, 0x4f35, 0xa7,0x46, 0xde,0x8d,0xb6,0x36,0x17,0xe6); -DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1,0x78, 0xc2,0xf5,0x68,0xa7,0x03,0xb2); -DEFINE_GUID(IID_IAudioRenderClient, 0xf294acfc, 0x3146, 0x4483, 0xa7,0xbf, 0xad,0xdc,0xa7,0xc2,0x60,0xe2); -DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4,0xde, 0x18,0x5c,0x39,0x5c,0xd3,0x17); - -#ifdef HAVE_WASAPI -#include -#include -#include -DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14); -DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0); -DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 ); -#endif -#endif -#endif /* AL_NO_UID_DEFS */ - -#ifdef HAVE_DLFCN_H -#include -#endif -#ifdef HAVE_INTRIN_H -#include -#endif -#ifdef HAVE_CPUID_H -#include -#endif -#ifdef HAVE_SSE_INTRINSICS -#include -#endif -#ifdef HAVE_SYS_SYSCONF_H -#include -#endif -#ifdef HAVE_FLOAT_H -#include -#endif -#ifdef HAVE_IEEEFP_H -#include -#endif - -#ifndef _WIN32 -#include -#include -#include -#include -#include -#elif defined(_WIN32_IE) -#include -#endif - -#include -#include -#include -#include - -#include "alMain.h" -#include "alu.h" -#include "cpu_caps.h" -#include "fpu_modes.h" -#include "vector.h" -#include "compat.h" -#include "threads.h" - - -#if defined(HAVE_GCC_GET_CPUID) && (defined(__i386__) || defined(__x86_64__) || \ - defined(_M_IX86) || defined(_M_X64)) -using reg_type = unsigned int; -static inline void get_cpuid(int f, reg_type *regs) -{ __get_cpuid(f, ®s[0], ®s[1], ®s[2], ®s[3]); } -#define CAN_GET_CPUID -#elif defined(HAVE_CPUID_INTRINSIC) && (defined(__i386__) || defined(__x86_64__) || \ - defined(_M_IX86) || defined(_M_X64)) -using reg_type = int; -static inline void get_cpuid(int f, reg_type *regs) -{ (__cpuid)(regs, f); } -#define CAN_GET_CPUID -#endif - -int CPUCapFlags = 0; - -void FillCPUCaps(int capfilter) -{ - int caps = 0; - -/* FIXME: We really should get this for all available CPUs in case different - * CPUs have different caps (is that possible on one machine?). */ -#ifdef CAN_GET_CPUID - union { - reg_type regs[4]; - char str[sizeof(reg_type[4])]; - } cpuinf[3] = {{ { 0, 0, 0, 0 } }}; - - get_cpuid(0, cpuinf[0].regs); - if(cpuinf[0].regs[0] == 0) - ERR("Failed to get CPUID\n"); - else - { - unsigned int maxfunc = cpuinf[0].regs[0]; - unsigned int maxextfunc; - - get_cpuid(0x80000000, cpuinf[0].regs); - maxextfunc = cpuinf[0].regs[0]; - - TRACE("Detected max CPUID function: 0x%x (ext. 0x%x)\n", maxfunc, maxextfunc); - - TRACE("Vendor ID: \"%.4s%.4s%.4s\"\n", cpuinf[0].str+4, cpuinf[0].str+12, cpuinf[0].str+8); - if(maxextfunc >= 0x80000004) - { - get_cpuid(0x80000002, cpuinf[0].regs); - get_cpuid(0x80000003, cpuinf[1].regs); - get_cpuid(0x80000004, cpuinf[2].regs); - TRACE("Name: \"%.16s%.16s%.16s\"\n", cpuinf[0].str, cpuinf[1].str, cpuinf[2].str); - } - - if(maxfunc >= 1) - { - get_cpuid(1, cpuinf[0].regs); - if((cpuinf[0].regs[3]&(1<<25))) - caps |= CPU_CAP_SSE; - if((caps&CPU_CAP_SSE) && (cpuinf[0].regs[3]&(1<<26))) - caps |= CPU_CAP_SSE2; - if((caps&CPU_CAP_SSE2) && (cpuinf[0].regs[2]&(1<<0))) - caps |= CPU_CAP_SSE3; - if((caps&CPU_CAP_SSE3) && (cpuinf[0].regs[2]&(1<<19))) - caps |= CPU_CAP_SSE4_1; - } - } -#else - /* Assume support for whatever's supported if we can't check for it */ -#if defined(HAVE_SSE4_1) -#warning "Assuming SSE 4.1 run-time support!" - caps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1; -#elif defined(HAVE_SSE3) -#warning "Assuming SSE 3 run-time support!" - caps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3; -#elif defined(HAVE_SSE2) -#warning "Assuming SSE 2 run-time support!" - caps |= CPU_CAP_SSE | CPU_CAP_SSE2; -#elif defined(HAVE_SSE) -#warning "Assuming SSE run-time support!" - caps |= CPU_CAP_SSE; -#endif -#endif -#ifdef HAVE_NEON - FILE *file = fopen("/proc/cpuinfo", "rt"); - if(!file) - ERR("Failed to open /proc/cpuinfo, cannot check for NEON support\n"); - else - { - std::string features; - char buf[256]; - - while(fgets(buf, sizeof(buf), file) != nullptr) - { - if(strncmp(buf, "Features\t:", 10) != 0) - continue; - - features = buf+10; - while(features.back() != '\n') - { - if(fgets(buf, sizeof(buf), file) == nullptr) - break; - features += buf; - } - break; - } - fclose(file); - file = nullptr; - - if(!features.empty()) - { - const char *str = features.c_str(); - while(isspace(str[0])) ++str; - - TRACE("Got features string:%s\n", str); - while((str=strstr(str, "neon")) != nullptr) - { - if(isspace(*(str-1)) && (str[4] == 0 || isspace(str[4]))) - { - caps |= CPU_CAP_NEON; - break; - } - ++str; - } - } - } -#endif - - TRACE("Extensions:%s%s%s%s%s%s\n", - ((capfilter&CPU_CAP_SSE) ? ((caps&CPU_CAP_SSE) ? " +SSE" : " -SSE") : ""), - ((capfilter&CPU_CAP_SSE2) ? ((caps&CPU_CAP_SSE2) ? " +SSE2" : " -SSE2") : ""), - ((capfilter&CPU_CAP_SSE3) ? ((caps&CPU_CAP_SSE3) ? " +SSE3" : " -SSE3") : ""), - ((capfilter&CPU_CAP_SSE4_1) ? ((caps&CPU_CAP_SSE4_1) ? " +SSE4.1" : " -SSE4.1") : ""), - ((capfilter&CPU_CAP_NEON) ? ((caps&CPU_CAP_NEON) ? " +NEON" : " -NEON") : ""), - ((!capfilter) ? " -none-" : "") - ); - CPUCapFlags = caps & capfilter; -} - - -FPUCtl::FPUCtl() -{ -#if defined(HAVE_SSE_INTRINSICS) - this->sse_state = _mm_getcsr(); - unsigned int sseState = this->sse_state; - sseState |= 0x8000; /* set flush-to-zero */ - sseState |= 0x0040; /* set denormals-are-zero */ - _mm_setcsr(sseState); - -#elif defined(__GNUC__) && defined(HAVE_SSE) - - if((CPUCapFlags&CPU_CAP_SSE)) - { - __asm__ __volatile__("stmxcsr %0" : "=m" (*&this->sse_state)); - unsigned int sseState = this->sse_state; - sseState |= 0x8000; /* set flush-to-zero */ - if((CPUCapFlags&CPU_CAP_SSE2)) - sseState |= 0x0040; /* set denormals-are-zero */ - __asm__ __volatile__("ldmxcsr %0" : : "m" (*&sseState)); - } -#endif - - this->in_mode = true; -} - -void FPUCtl::leave() -{ - if(!this->in_mode) return; - -#if defined(HAVE_SSE_INTRINSICS) - _mm_setcsr(this->sse_state); - -#elif defined(__GNUC__) && defined(HAVE_SSE) - - if((CPUCapFlags&CPU_CAP_SSE)) - __asm__ __volatile__("ldmxcsr %0" : : "m" (*&this->sse_state)); -#endif - this->in_mode = false; -} - - -#ifdef _WIN32 - -const PathNamePair &GetProcBinary() -{ - static PathNamePair ret; - if(!ret.fname.empty() || !ret.path.empty()) - return ret; - - al::vector fullpath(256); - DWORD len; - while((len=GetModuleFileNameW(nullptr, fullpath.data(), static_cast(fullpath.size()))) == fullpath.size()) - fullpath.resize(fullpath.size() << 1); - if(len == 0) - { - ERR("Failed to get process name: error %lu\n", GetLastError()); - return ret; - } - - fullpath.resize(len); - if(fullpath.back() != 0) - fullpath.push_back(0); - - auto sep = std::find(fullpath.rbegin()+1, fullpath.rend(), '\\'); - sep = std::find(fullpath.rbegin()+1, sep, '/'); - if(sep != fullpath.rend()) - { - *sep = 0; - ret.fname = wstr_to_utf8(&*sep + 1); - ret.path = wstr_to_utf8(fullpath.data()); - } - else - ret.fname = wstr_to_utf8(fullpath.data()); - - TRACE("Got: %s, %s\n", ret.path.c_str(), ret.fname.c_str()); - return ret; -} - - -void *LoadLib(const char *name) -{ - std::wstring wname{utf8_to_wstr(name)}; - return LoadLibraryW(wname.c_str()); -} -void CloseLib(void *handle) -{ FreeLibrary(static_cast(handle)); } -void *GetSymbol(void *handle, const char *name) -{ - void *ret{reinterpret_cast(GetProcAddress(static_cast(handle), name))}; - if(!ret) ERR("Failed to load %s\n", name); - return ret; -} - - -void al_print(const char *type, const char *prefix, const char *func, const char *fmt, ...) -{ - al::vector dynmsg; - char stcmsg[256]; - char *str{stcmsg}; - - va_list args, args2; - va_start(args, fmt); - va_copy(args2, args); - int msglen{std::vsnprintf(str, sizeof(stcmsg), fmt, args)}; - if(UNLIKELY(msglen >= 0 && static_cast(msglen) >= sizeof(stcmsg))) - { - dynmsg.resize(static_cast(msglen) + 1u); - str = dynmsg.data(); - msglen = std::vsnprintf(str, dynmsg.size(), fmt, args2); - } - va_end(args2); - va_end(args); - - std::wstring wstr{utf8_to_wstr(str)}; - fprintf(gLogFile, "AL lib: %s %s%s: %ls", type, prefix, func, wstr.c_str()); - fflush(gLogFile); -} - - -static inline int is_slash(int c) -{ return (c == '\\' || c == '/'); } - -static void DirectorySearch(const char *path, const char *ext, al::vector *const results) -{ - std::string pathstr{path}; - pathstr += "\\*"; - pathstr += ext; - TRACE("Searching %s\n", pathstr.c_str()); - - std::wstring wpath{utf8_to_wstr(pathstr.c_str())}; - WIN32_FIND_DATAW fdata; - HANDLE hdl{FindFirstFileW(wpath.c_str(), &fdata)}; - if(hdl != INVALID_HANDLE_VALUE) - { - size_t base = results->size(); - do { - results->emplace_back(); - std::string &str = results->back(); - str = path; - str += '\\'; - str += wstr_to_utf8(fdata.cFileName); - TRACE("Got result %s\n", str.c_str()); - } while(FindNextFileW(hdl, &fdata)); - FindClose(hdl); - - std::sort(results->begin()+base, results->end()); - } -} - -al::vector SearchDataFiles(const char *ext, const char *subdir) -{ - static std::mutex search_lock; - std::lock_guard _{search_lock}; - - /* If the path is absolute, use it directly. */ - al::vector results; - if(isalpha(subdir[0]) && subdir[1] == ':' && is_slash(subdir[2])) - { - std::string path{subdir}; - std::replace(path.begin(), path.end(), '/', '\\'); - DirectorySearch(path.c_str(), ext, &results); - return results; - } - if(subdir[0] == '\\' && subdir[1] == '\\' && subdir[2] == '?' && subdir[3] == '\\') - { - DirectorySearch(subdir, ext, &results); - return results; - } - - std::string path; - - /* Search the app-local directory. */ - WCHAR *cwdbuf{_wgetenv(L"ALSOFT_LOCAL_PATH")}; - if(cwdbuf && *cwdbuf != '\0') - { - path = wstr_to_utf8(cwdbuf); - if(is_slash(path.back())) - path.pop_back(); - } - else if(!(cwdbuf=_wgetcwd(nullptr, 0))) - path = "."; - else - { - path = wstr_to_utf8(cwdbuf); - if(is_slash(path.back())) - path.pop_back(); - free(cwdbuf); - } - std::replace(path.begin(), path.end(), '/', '\\'); - DirectorySearch(path.c_str(), ext, &results); - - /* Search the local and global data dirs. */ - static constexpr int ids[2]{ CSIDL_APPDATA, CSIDL_COMMON_APPDATA }; - for(int id : ids) - { - WCHAR buffer[MAX_PATH]; - if(SHGetSpecialFolderPathW(nullptr, buffer, id, FALSE) == FALSE) - continue; - - path = wstr_to_utf8(buffer); - if(!is_slash(path.back())) - path += '\\'; - path += subdir; - std::replace(path.begin(), path.end(), '/', '\\'); - - DirectorySearch(path.c_str(), ext, &results); - } - - return results; -} - -void SetRTPriority(void) -{ - bool failed = false; - if(RTPrioLevel > 0) - failed = !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); - if(failed) ERR("Failed to set priority level for thread\n"); -} - -#else - -const PathNamePair &GetProcBinary() -{ - static PathNamePair ret; - if(!ret.fname.empty() || !ret.path.empty()) - return ret; - - al::vector pathname; -#ifdef __FreeBSD__ - size_t pathlen; - int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; - if(sysctl(mib, 4, nullptr, &pathlen, nullptr, 0) == -1) - WARN("Failed to sysctl kern.proc.pathname: %s\n", strerror(errno)); - else - { - pathname.resize(pathlen + 1); - sysctl(mib, 4, pathname.data(), &pathlen, nullptr, 0); - pathname.resize(pathlen); - } -#endif -#ifdef HAVE_PROC_PIDPATH - if(pathname.empty()) - { - char procpath[PROC_PIDPATHINFO_MAXSIZE]{}; - const pid_t pid{getpid()}; - if(proc_pidpath(pid, procpath, sizeof(procpath)) < 1) - ERR("proc_pidpath(%d, ...) failed: %s\n", pid, strerror(errno)); - else - pathname.insert(pathname.end(), procpath, procpath+strlen(procpath)); - } -#endif - if(pathname.empty()) - { - pathname.resize(256); - - const char *selfname{"/proc/self/exe"}; - ssize_t len{readlink(selfname, pathname.data(), pathname.size())}; - if(len == -1 && errno == ENOENT) - { - selfname = "/proc/self/file"; - len = readlink(selfname, pathname.data(), pathname.size()); - } - if(len == -1 && errno == ENOENT) - { - selfname = "/proc/curproc/exe"; - len = readlink(selfname, pathname.data(), pathname.size()); - } - if(len == -1 && errno == ENOENT) - { - selfname = "/proc/curproc/file"; - len = readlink(selfname, pathname.data(), pathname.size()); - } - - while(len > 0 && static_cast(len) == pathname.size()) - { - pathname.resize(pathname.size() << 1); - len = readlink(selfname, pathname.data(), pathname.size()); - } - if(len <= 0) - { - WARN("Failed to readlink %s: %s\n", selfname, strerror(errno)); - return ret; - } - - pathname.resize(len); - } - while(!pathname.empty() && pathname.back() == 0) - pathname.pop_back(); - - auto sep = std::find(pathname.crbegin(), pathname.crend(), '/'); - if(sep != pathname.crend()) - { - ret.path = std::string(pathname.cbegin(), sep.base()-1); - ret.fname = std::string(sep.base(), pathname.cend()); - } - else - ret.fname = std::string(pathname.cbegin(), pathname.cend()); - - TRACE("Got: %s, %s\n", ret.path.c_str(), ret.fname.c_str()); - return ret; -} - - -#ifdef HAVE_DLFCN_H - -void *LoadLib(const char *name) -{ - dlerror(); - void *handle{dlopen(name, RTLD_NOW)}; - const char *err{dlerror()}; - if(err) handle = nullptr; - return handle; -} -void CloseLib(void *handle) -{ dlclose(handle); } -void *GetSymbol(void *handle, const char *name) -{ - dlerror(); - void *sym{dlsym(handle, name)}; - const char *err{dlerror()}; - if(err) - { - WARN("Failed to load %s: %s\n", name, err); - sym = nullptr; - } - return sym; -} - -#endif /* HAVE_DLFCN_H */ - -void al_print(const char *type, const char *prefix, const char *func, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - fprintf(gLogFile, "AL lib: %s %s%s: ", type, prefix, func); - vfprintf(gLogFile, fmt, ap); - va_end(ap); - - fflush(gLogFile); -} - - -static void DirectorySearch(const char *path, const char *ext, al::vector *const results) -{ - TRACE("Searching %s for *%s\n", path, ext); - DIR *dir{opendir(path)}; - if(dir != nullptr) - { - const size_t extlen = strlen(ext); - size_t base = results->size(); - - struct dirent *dirent; - while((dirent=readdir(dir)) != nullptr) - { - if(strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) - continue; - - size_t len{strlen(dirent->d_name)}; - if(len <= extlen) continue; - if(strcasecmp(dirent->d_name+len-extlen, ext) != 0) - continue; - - results->emplace_back(); - std::string &str = results->back(); - str = path; - if(str.back() != '/') - str.push_back('/'); - str += dirent->d_name; - TRACE("Got result %s\n", str.c_str()); - } - closedir(dir); - - std::sort(results->begin()+base, results->end()); - } -} - -al::vector SearchDataFiles(const char *ext, const char *subdir) -{ - static std::mutex search_lock; - std::lock_guard _{search_lock}; - - al::vector results; - if(subdir[0] == '/') - { - DirectorySearch(subdir, ext, &results); - return results; - } - - /* Search the app-local directory. */ - const char *str{getenv("ALSOFT_LOCAL_PATH")}; - if(str && *str != '\0') - DirectorySearch(str, ext, &results); - else - { - al::vector cwdbuf(256); - while(!getcwd(cwdbuf.data(), cwdbuf.size())) - { - if(errno != ERANGE) - { - cwdbuf.clear(); - break; - } - cwdbuf.resize(cwdbuf.size() << 1); - } - if(cwdbuf.empty()) - DirectorySearch(".", ext, &results); - else - { - DirectorySearch(cwdbuf.data(), ext, &results); - cwdbuf.clear(); - } - } - - // Search local data dir - if((str=getenv("XDG_DATA_HOME")) != nullptr && str[0] != '\0') - { - std::string path{str}; - if(path.back() != '/') - path += '/'; - path += subdir; - DirectorySearch(path.c_str(), ext, &results); - } - else if((str=getenv("HOME")) != nullptr && str[0] != '\0') - { - std::string path{str}; - if(path.back() == '/') - path.pop_back(); - path += "/.local/share/"; - path += subdir; - DirectorySearch(path.c_str(), ext, &results); - } - - // Search global data dirs - if((str=getenv("XDG_DATA_DIRS")) == nullptr || str[0] == '\0') - str = "/usr/local/share/:/usr/share/"; - - const char *next{str}; - while((str=next) != nullptr && str[0] != '\0') - { - next = strchr(str, ':'); - - std::string path = (next ? std::string(str, next++) : std::string(str)); - if(path.empty()) continue; - - if(path.back() != '/') - path += '/'; - path += subdir; - - DirectorySearch(path.c_str(), ext, &results); - } - - return results; -} - -void SetRTPriority() -{ - bool failed = false; -#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) - if(RTPrioLevel > 0) - { - struct sched_param param; - /* Use the minimum real-time priority possible for now (on Linux this - * should be 1 for SCHED_RR) */ - param.sched_priority = sched_get_priority_min(SCHED_RR); - failed = !!pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); - } -#else - /* Real-time priority not available */ - failed = (RTPrioLevel>0); -#endif - if(failed) - ERR("Failed to set priority level for thread\n"); -} - -#endif diff --git a/modules/openal-soft/Alc/hrtf.cpp b/modules/openal-soft/Alc/hrtf.cpp deleted file mode 100644 index 92d7b47..0000000 --- a/modules/openal-soft/Alc/hrtf.cpp +++ /dev/null @@ -1,1394 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2011 by Chris Robinson - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "AL/al.h" -#include "AL/alc.h" -#include "alMain.h" -#include "alSource.h" -#include "alu.h" -#include "hrtf.h" -#include "alconfig.h" -#include "filters/splitter.h" - -#include "compat.h" -#include "almalloc.h" -#include "alspan.h" - - -struct HrtfHandle { - std::unique_ptr entry; - al::FlexArray filename; - - HrtfHandle(size_t fname_len) : filename{fname_len} { } - HrtfHandle(const HrtfHandle&) = delete; - HrtfHandle& operator=(const HrtfHandle&) = delete; - - static std::unique_ptr Create(size_t fname_len); - static constexpr size_t Sizeof(size_t length) noexcept - { - return maxz(sizeof(HrtfHandle), - al::FlexArray::Sizeof(length, offsetof(HrtfHandle, filename))); - } - - DEF_PLACE_NEWDEL() -}; - -std::unique_ptr HrtfHandle::Create(size_t fname_len) -{ - void *ptr{al_calloc(DEF_ALIGN, HrtfHandle::Sizeof(fname_len))}; - return std::unique_ptr{new (ptr) HrtfHandle{fname_len}}; -} - -namespace { - -using namespace std::placeholders; - -using HrtfHandlePtr = std::unique_ptr; - -/* Data set limits must be the same as or more flexible than those defined in - * the makemhr utility. - */ -#define MIN_IR_SIZE (8) -#define MAX_IR_SIZE (512) -#define MOD_IR_SIZE (2) - -#define MIN_FD_COUNT (1) -#define MAX_FD_COUNT (16) - -#define MIN_FD_DISTANCE (0.05f) -#define MAX_FD_DISTANCE (2.5f) - -#define MIN_EV_COUNT (5) -#define MAX_EV_COUNT (128) - -#define MIN_AZ_COUNT (1) -#define MAX_AZ_COUNT (128) - -#define MAX_HRIR_DELAY (HRTF_HISTORY_LENGTH-1) - -constexpr ALchar magicMarker00[8]{'M','i','n','P','H','R','0','0'}; -constexpr ALchar magicMarker01[8]{'M','i','n','P','H','R','0','1'}; -constexpr ALchar magicMarker02[8]{'M','i','n','P','H','R','0','2'}; - -/* First value for pass-through coefficients (remaining are 0), used for omni- - * directional sounds. */ -constexpr ALfloat PassthruCoeff{0.707106781187f/*sqrt(0.5)*/}; - -std::mutex LoadedHrtfLock; -al::vector LoadedHrtfs; - - -class databuf final : public std::streambuf { - int_type underflow() override - { return traits_type::eof(); } - - pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override - { - if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return traits_type::eof(); - - char_type *cur; - switch(whence) - { - case std::ios_base::beg: - if(offset < 0 || offset > egptr()-eback()) - return traits_type::eof(); - cur = eback() + offset; - break; - - case std::ios_base::cur: - if((offset >= 0 && offset > egptr()-gptr()) || - (offset < 0 && -offset > gptr()-eback())) - return traits_type::eof(); - cur = gptr() + offset; - break; - - case std::ios_base::end: - if(offset > 0 || -offset > egptr()-eback()) - return traits_type::eof(); - cur = egptr() + offset; - break; - - default: - return traits_type::eof(); - } - - setg(eback(), cur, egptr()); - return cur - eback(); - } - - pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override - { - // Simplified version of seekoff - if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return traits_type::eof(); - - if(pos < 0 || pos > egptr()-eback()) - return traits_type::eof(); - - setg(eback(), eback() + static_cast(pos), egptr()); - return pos; - } - -public: - databuf(const char_type *start, const char_type *end) noexcept - { - setg(const_cast(start), const_cast(start), - const_cast(end)); - } -}; - -class idstream final : public std::istream { - databuf mStreamBuf; - -public: - idstream(const char *start, const char *end) - : std::istream{nullptr}, mStreamBuf{start, end} - { init(&mStreamBuf); } -}; - - -struct IdxBlend { ALsizei idx; ALfloat blend; }; -/* Calculate the elevation index given the polar elevation in radians. This - * will return an index between 0 and (evcount - 1). - */ -IdxBlend CalcEvIndex(ALsizei evcount, ALfloat ev) -{ - ev = (al::MathDefs::Pi()*0.5f + ev) * (evcount-1) / al::MathDefs::Pi(); - ALsizei idx{float2int(ev)}; - - return IdxBlend{mini(idx, evcount-1), ev-idx}; -} - -/* Calculate the azimuth index given the polar azimuth in radians. This will - * return an index between 0 and (azcount - 1). - */ -IdxBlend CalcAzIndex(ALsizei azcount, ALfloat az) -{ - az = (al::MathDefs::Tau()+az) * azcount / al::MathDefs::Tau(); - ALsizei idx{float2int(az)}; - - return IdxBlend{idx%azcount, az-idx}; -} - -} // namespace - - -/* Calculates static HRIR coefficients and delays for the given polar elevation - * and azimuth in radians. The coefficients are normalized. - */ -void GetHrtfCoeffs(const HrtfEntry *Hrtf, ALfloat elevation, ALfloat azimuth, ALfloat distance, - ALfloat spread, HrirArray &coeffs, ALsizei (&delays)[2]) -{ - const ALfloat dirfact{1.0f - (spread / al::MathDefs::Tau())}; - - const auto *field = Hrtf->field; - const auto *field_end = field + Hrtf->fdCount-1; - ALsizei ebase{0}; - while(distance < field->distance && field != field_end) - { - ebase += field->evCount; - ++field; - } - - /* Claculate the elevation indinces. */ - const auto elev0 = CalcEvIndex(field->evCount, elevation); - const ALsizei elev1_idx{mini(elev0.idx+1, field->evCount-1)}; - const ALsizei ir0offset{Hrtf->elev[ebase + elev0.idx].irOffset}; - const ALsizei ir1offset{Hrtf->elev[ebase + elev1_idx].irOffset}; - - /* Calculate azimuth indices. */ - const auto az0 = CalcAzIndex(Hrtf->elev[ebase + elev0.idx].azCount, azimuth); - const auto az1 = CalcAzIndex(Hrtf->elev[ebase + elev1_idx].azCount, azimuth); - - /* Calculate the HRIR indices to blend. */ - ALsizei idx[4]{ - ir0offset + az0.idx, - ir0offset + ((az0.idx+1) % Hrtf->elev[ebase + elev0.idx].azCount), - ir1offset + az1.idx, - ir1offset + ((az1.idx+1) % Hrtf->elev[ebase + elev1_idx].azCount) - }; - - /* Calculate bilinear blending weights, attenuated according to the - * directional panning factor. - */ - const ALfloat blend[4]{ - (1.0f-elev0.blend) * (1.0f-az0.blend) * dirfact, - (1.0f-elev0.blend) * ( az0.blend) * dirfact, - ( elev0.blend) * (1.0f-az1.blend) * dirfact, - ( elev0.blend) * ( az1.blend) * dirfact - }; - - /* Calculate the blended HRIR delays. */ - delays[0] = fastf2i( - Hrtf->delays[idx[0]][0]*blend[0] + Hrtf->delays[idx[1]][0]*blend[1] + - Hrtf->delays[idx[2]][0]*blend[2] + Hrtf->delays[idx[3]][0]*blend[3] - ); - delays[1] = fastf2i( - Hrtf->delays[idx[0]][1]*blend[0] + Hrtf->delays[idx[1]][1]*blend[1] + - Hrtf->delays[idx[2]][1]*blend[2] + Hrtf->delays[idx[3]][1]*blend[3] - ); - - const ALsizei irSize{Hrtf->irSize}; - ASSUME(irSize >= MIN_IR_SIZE); - - /* Calculate the sample offsets for the HRIR indices. */ - idx[0] *= irSize; - idx[1] *= irSize; - idx[2] *= irSize; - idx[3] *= irSize; - - /* Calculate the blended HRIR coefficients. */ - ALfloat *coeffout{al::assume_aligned<16>(&coeffs[0][0])}; - coeffout[0] = PassthruCoeff * (1.0f-dirfact); - coeffout[1] = PassthruCoeff * (1.0f-dirfact); - std::fill(coeffout+2, coeffout + irSize*2, 0.0f); - for(ALsizei c{0};c < 4;c++) - { - const ALfloat *srccoeffs{al::assume_aligned<16>(Hrtf->coeffs[idx[c]])}; - const ALfloat mult{blend[c]}; - auto blend_coeffs = [mult](const ALfloat src, const ALfloat coeff) noexcept -> ALfloat - { return src*mult + coeff; }; - std::transform(srccoeffs, srccoeffs + irSize*2, coeffout, - coeffout, blend_coeffs); - } -} - - -std::unique_ptr DirectHrtfState::Create(size_t num_chans) -{ - void *ptr{al_calloc(16, DirectHrtfState::Sizeof(num_chans))}; - return std::unique_ptr{new (ptr) DirectHrtfState{num_chans}}; -} - -void BuildBFormatHrtf(const HrtfEntry *Hrtf, DirectHrtfState *state, const ALsizei NumChannels, const AngularPoint *AmbiPoints, const ALfloat (*RESTRICT AmbiMatrix)[MAX_AMBI_CHANNELS], const size_t AmbiCount, const ALfloat *RESTRICT AmbiOrderHFGain) -{ - static constexpr int OrderFromChan[MAX_AMBI_CHANNELS]{ - 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3, - }; - /* Set this to true for dual-band HRTF processing. May require better - * calculation of the new IR length to deal with the head and tail - * generated by the HF scaling. - */ - static constexpr bool DualBand{true}; - - ASSUME(NumChannels > 0); - ASSUME(AmbiCount > 0); - - auto &field = Hrtf->field[0]; - ALsizei min_delay{HRTF_HISTORY_LENGTH}; - ALsizei max_delay{0}; - auto idx = al::vector(AmbiCount); - auto calc_idxs = [Hrtf,&field,&max_delay,&min_delay](const AngularPoint &pt) noexcept -> ALsizei - { - /* Calculate elevation index. */ - const auto evidx = clampi( - static_cast((90.0f+pt.Elev)*(field.evCount-1)/180.0f + 0.5f), - 0, field.evCount-1); - - const ALsizei azcount{Hrtf->elev[evidx].azCount}; - const ALsizei iroffset{Hrtf->elev[evidx].irOffset}; - - /* Calculate azimuth index for this elevation. */ - const auto azidx = static_cast((360.0f+pt.Azim)*azcount/360.0f + 0.5f) % azcount; - - /* Calculate the index for the impulse response. */ - ALsizei idx{iroffset + azidx}; - - min_delay = mini(min_delay, mini(Hrtf->delays[idx][0], Hrtf->delays[idx][1])); - max_delay = maxi(max_delay, maxi(Hrtf->delays[idx][0], Hrtf->delays[idx][1])); - - return idx; - }; - std::transform(AmbiPoints, AmbiPoints+AmbiCount, idx.begin(), calc_idxs); - - /* For dual-band processing, add a 12-sample delay to compensate for the HF - * scale on the minimum-phase response. - */ - static constexpr ALsizei base_delay{DualBand ? 12 : 0}; - const ALdouble xover_norm{400.0 / Hrtf->sampleRate}; - BandSplitterR splitter{xover_norm}; - SplitterAllpassR allpass{xover_norm}; - - auto tmpres = al::vector>(NumChannels); - auto tmpfilt = al::vector>(3); - for(size_t c{0u};c < AmbiCount;++c) - { - const ALfloat (*fir)[2]{&Hrtf->coeffs[idx[c] * Hrtf->irSize]}; - const ALsizei ldelay{Hrtf->delays[idx[c]][0] - min_delay + base_delay}; - const ALsizei rdelay{Hrtf->delays[idx[c]][1] - min_delay + base_delay}; - - if(!DualBand) - { - /* For single-band decoding, apply the HF scale to the response. */ - for(ALsizei i{0};i < NumChannels;++i) - { - const ALdouble mult{ALdouble{AmbiOrderHFGain[OrderFromChan[i]]} * - AmbiMatrix[c][i]}; - const ALsizei numirs{mini(Hrtf->irSize, HRIR_LENGTH-maxi(ldelay, rdelay))}; - ALsizei lidx{ldelay}, ridx{rdelay}; - for(ALsizei j{0};j < numirs;++j) - { - tmpres[i][lidx++][0] += fir[j][0] * mult; - tmpres[i][ridx++][1] += fir[j][1] * mult; - } - } - continue; - } - - /* For dual-band processing, the HRIR needs to be split into low and - * high frequency responses. The band-splitter alone creates frequency- - * dependent phase-shifts, which is not ideal. To counteract it, - * combine it with a backwards phase-shift. - */ - - /* Load the (left) HRIR backwards, into a temp buffer with padding. */ - std::fill(tmpfilt[2].begin(), tmpfilt[2].end(), 0.0); - std::transform(fir, fir+Hrtf->irSize, tmpfilt[2].rbegin() + HRIR_LENGTH*3, - [](const ALfloat (&ir)[2]) noexcept -> ALdouble { return ir[0]; }); - - /* Apply the all-pass on the reversed signal and reverse the resulting - * sample array. This produces the forward response with a backwards - * phase-shift (+n degrees becomes -n degrees). - */ - allpass.clear(); - allpass.process(tmpfilt[2].data(), static_cast(tmpfilt[2].size())); - std::reverse(tmpfilt[2].begin(), tmpfilt[2].end()); - - /* Now apply the band-splitter. This applies the normal phase-shift, - * which cancels out with the backwards phase-shift to get the original - * phase on the split signal. - */ - splitter.clear(); - splitter.process(tmpfilt[0].data(), tmpfilt[1].data(), tmpfilt[2].data(), - static_cast(tmpfilt[2].size())); - - /* Apply left ear response with delay and HF scale. */ - for(ALsizei i{0};i < NumChannels;++i) - { - const ALdouble mult{AmbiMatrix[c][i]}; - const ALdouble hfgain{AmbiOrderHFGain[OrderFromChan[i]]}; - ALsizei j{HRIR_LENGTH*3 - ldelay}; - for(ALsizei lidx{0};lidx < HRIR_LENGTH;++lidx,++j) - tmpres[i][lidx][0] += (tmpfilt[0][j]*hfgain + tmpfilt[1][j]) * mult; - } - - /* Now run the same process on the right HRIR. */ - std::fill(tmpfilt[2].begin(), tmpfilt[2].end(), 0.0); - std::transform(fir, fir+Hrtf->irSize, tmpfilt[2].rbegin() + HRIR_LENGTH*3, - [](const ALfloat (&ir)[2]) noexcept -> ALdouble { return ir[1]; }); - - allpass.clear(); - allpass.process(tmpfilt[2].data(), static_cast(tmpfilt[2].size())); - std::reverse(tmpfilt[2].begin(), tmpfilt[2].end()); - - splitter.clear(); - splitter.process(tmpfilt[0].data(), tmpfilt[1].data(), tmpfilt[2].data(), - static_cast(tmpfilt[2].size())); - - for(ALsizei i{0};i < NumChannels;++i) - { - const ALdouble mult{AmbiMatrix[c][i]}; - const ALdouble hfgain{AmbiOrderHFGain[OrderFromChan[i]]}; - ALsizei j{HRIR_LENGTH*3 - rdelay}; - for(ALsizei ridx{0};ridx < HRIR_LENGTH;++ridx,++j) - tmpres[i][ridx][1] += (tmpfilt[0][j]*hfgain + tmpfilt[1][j]) * mult; - } - } - tmpfilt.clear(); - idx.clear(); - - for(ALsizei i{0};i < NumChannels;++i) - { - auto copy_arr = [](const std::array &in) noexcept -> std::array - { return std::array{{static_cast(in[0]), static_cast(in[1])}}; }; - std::transform(tmpres[i].begin(), tmpres[i].end(), state->Chan[i].Coeffs.begin(), - copy_arr); - } - tmpres.clear(); - - ALsizei max_length{HRIR_LENGTH}; - /* Increase the IR size by 24 samples with dual-band processing to account - * for the head and tail from the HF response scale. - */ - const ALsizei irsize{DualBand ? mini(Hrtf->irSize + base_delay*2, max_length) : Hrtf->irSize}; - max_length = mini(max_delay-min_delay + irsize, max_length); - - /* Round up to the next IR size multiple. */ - max_length += MOD_IR_SIZE-1; - max_length -= max_length%MOD_IR_SIZE; - - TRACE("Skipped delay: %d, max delay: %d, new FIR length: %d\n", - min_delay, max_delay-min_delay, max_length); - state->IrSize = max_length; -} - - -namespace { - -std::unique_ptr CreateHrtfStore(ALuint rate, ALsizei irSize, const ALsizei fdCount, - const ALubyte *evCount, const ALfloat *distance, const ALushort *azCount, - const ALushort *irOffset, ALsizei irCount, const ALfloat (*coeffs)[2], - const ALubyte (*delays)[2], const char *filename) -{ - std::unique_ptr Hrtf; - - ALsizei evTotal{std::accumulate(evCount, evCount+fdCount, 0)}; - size_t total{sizeof(HrtfEntry)}; - total = RoundUp(total, alignof(HrtfEntry::Field)); /* Align for field infos */ - total += sizeof(HrtfEntry::Field)*fdCount; - total = RoundUp(total, alignof(HrtfEntry::Elevation)); /* Align for elevation infos */ - total += sizeof(Hrtf->elev[0])*evTotal; - total = RoundUp(total, 16); /* Align for coefficients using SIMD */ - total += sizeof(Hrtf->coeffs[0])*irSize*irCount; - total += sizeof(Hrtf->delays[0])*irCount; - - Hrtf.reset(new (al_calloc(16, total)) HrtfEntry{}); - if(!Hrtf) - ERR("Out of memory allocating storage for %s.\n", filename); - else - { - InitRef(&Hrtf->ref, 1u); - Hrtf->sampleRate = rate; - Hrtf->irSize = irSize; - Hrtf->fdCount = fdCount; - - /* Set up pointers to storage following the main HRTF struct. */ - char *base = reinterpret_cast(Hrtf.get()); - uintptr_t offset = sizeof(HrtfEntry); - - offset = RoundUp(offset, alignof(HrtfEntry::Field)); /* Align for field infos */ - auto field_ = reinterpret_cast(base + offset); - offset += sizeof(field_[0])*fdCount; - - offset = RoundUp(offset, alignof(HrtfEntry::Elevation)); /* Align for elevation infos */ - auto elev_ = reinterpret_cast(base + offset); - offset += sizeof(elev_[0])*evTotal; - - offset = RoundUp(offset, 16); /* Align for coefficients using SIMD */ - auto coeffs_ = reinterpret_cast(base + offset); - offset += sizeof(coeffs_[0])*irSize*irCount; - - auto delays_ = reinterpret_cast(base + offset); - offset += sizeof(delays_[0])*irCount; - - assert(offset == total); - - /* Copy input data to storage. */ - for(ALsizei i{0};i < fdCount;i++) - { - field_[i].distance = distance[i]; - field_[i].evCount = evCount[i]; - } - for(ALsizei i{0};i < evTotal;i++) - { - elev_[i].azCount = azCount[i]; - elev_[i].irOffset = irOffset[i]; - } - for(ALsizei i{0};i < irSize*irCount;i++) - { - coeffs_[i][0] = coeffs[i][0]; - coeffs_[i][1] = coeffs[i][1]; - } - for(ALsizei i{0};i < irCount;i++) - { - delays_[i][0] = delays[i][0]; - delays_[i][1] = delays[i][1]; - } - - /* Finally, assign the storage pointers. */ - Hrtf->field = field_; - Hrtf->elev = elev_; - Hrtf->coeffs = coeffs_; - Hrtf->delays = delays_; - } - - return Hrtf; -} - -ALubyte GetLE_ALubyte(std::istream &data) -{ - return static_cast(data.get()); -} - -ALshort GetLE_ALshort(std::istream &data) -{ - int ret = data.get(); - ret |= data.get() << 8; - return static_cast((ret^32768) - 32768); -} - -ALushort GetLE_ALushort(std::istream &data) -{ - int ret = data.get(); - ret |= data.get() << 8; - return static_cast(ret); -} - -ALint GetLE_ALint24(std::istream &data) -{ - int ret = data.get(); - ret |= data.get() << 8; - ret |= data.get() << 16; - return (ret^8388608) - 8388608; -} - -ALuint GetLE_ALuint(std::istream &data) -{ - int ret = data.get(); - ret |= data.get() << 8; - ret |= data.get() << 16; - ret |= data.get() << 24; - return ret; -} - -std::unique_ptr LoadHrtf00(std::istream &data, const char *filename) -{ - ALuint rate{GetLE_ALuint(data)}; - ALushort irCount{GetLE_ALushort(data)}; - ALushort irSize{GetLE_ALushort(data)}; - ALubyte evCount{GetLE_ALubyte(data)}; - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - ALboolean failed{AL_FALSE}; - if(irSize < MIN_IR_SIZE || irSize > MAX_IR_SIZE || (irSize%MOD_IR_SIZE)) - { - ERR("Unsupported HRIR size: irSize=%d (%d to %d by %d)\n", - irSize, MIN_IR_SIZE, MAX_IR_SIZE, MOD_IR_SIZE); - failed = AL_TRUE; - } - if(evCount < MIN_EV_COUNT || evCount > MAX_EV_COUNT) - { - ERR("Unsupported elevation count: evCount=%d (%d to %d)\n", - evCount, MIN_EV_COUNT, MAX_EV_COUNT); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - al::vector evOffset(evCount); - for(auto &val : evOffset) - val = GetLE_ALushort(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(ALsizei i{1};i < evCount;i++) - { - if(evOffset[i] <= evOffset[i-1]) - { - ERR("Invalid evOffset: evOffset[%d]=%d (last=%d)\n", - i, evOffset[i], evOffset[i-1]); - failed = AL_TRUE; - } - } - if(irCount <= evOffset.back()) - { - ERR("Invalid evOffset: evOffset[%zu]=%d (irCount=%d)\n", - evOffset.size()-1, evOffset.back(), irCount); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - al::vector azCount(evCount); - for(ALsizei i{1};i < evCount;i++) - { - azCount[i-1] = evOffset[i] - evOffset[i-1]; - if(azCount[i-1] < MIN_AZ_COUNT || azCount[i-1] > MAX_AZ_COUNT) - { - ERR("Unsupported azimuth count: azCount[%d]=%d (%d to %d)\n", - i-1, azCount[i-1], MIN_AZ_COUNT, MAX_AZ_COUNT); - failed = AL_TRUE; - } - } - azCount.back() = irCount - evOffset.back(); - if(azCount.back() < MIN_AZ_COUNT || azCount.back() > MAX_AZ_COUNT) - { - ERR("Unsupported azimuth count: azCount[%zu]=%d (%d to %d)\n", - azCount.size()-1, azCount.back(), MIN_AZ_COUNT, MAX_AZ_COUNT); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - al::vector> coeffs(irSize*irCount); - al::vector> delays(irCount); - for(auto &val : coeffs) - val[0] = GetLE_ALshort(data) / 32768.0f; - for(auto &val : delays) - val[0] = GetLE_ALubyte(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(ALsizei i{0};i < irCount;i++) - { - if(delays[i][0] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%d]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - } - if(failed) - return nullptr; - - /* Mirror the left ear responses to the right ear. */ - for(ALsizei i{0};i < evCount;i++) - { - ALushort evoffset = evOffset[i]; - ALubyte azcount = azCount[i]; - for(ALsizei j{0};j < azcount;j++) - { - ALsizei lidx = evoffset + j; - ALsizei ridx = evoffset + ((azcount-j) % azcount); - - for(ALsizei k{0};k < irSize;k++) - coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0]; - delays[ridx][1] = delays[lidx][0]; - } - } - - static constexpr ALfloat distance{0.0f}; - return CreateHrtfStore(rate, irSize, 1, &evCount, &distance, azCount.data(), evOffset.data(), - irCount, &reinterpret_cast(coeffs[0]), - &reinterpret_cast(delays[0]), filename); -} - -std::unique_ptr LoadHrtf01(std::istream &data, const char *filename) -{ - ALuint rate{GetLE_ALuint(data)}; - ALushort irSize{GetLE_ALubyte(data)}; - ALubyte evCount{GetLE_ALubyte(data)}; - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - ALboolean failed{AL_FALSE}; - if(irSize < MIN_IR_SIZE || irSize > MAX_IR_SIZE || (irSize%MOD_IR_SIZE)) - { - ERR("Unsupported HRIR size: irSize=%d (%d to %d by %d)\n", - irSize, MIN_IR_SIZE, MAX_IR_SIZE, MOD_IR_SIZE); - failed = AL_TRUE; - } - if(evCount < MIN_EV_COUNT || evCount > MAX_EV_COUNT) - { - ERR("Unsupported elevation count: evCount=%d (%d to %d)\n", - evCount, MIN_EV_COUNT, MAX_EV_COUNT); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - al::vector azCount(evCount); - std::generate(azCount.begin(), azCount.end(), std::bind(GetLE_ALubyte, std::ref(data))); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(ALsizei i{0};i < evCount;++i) - { - if(azCount[i] < MIN_AZ_COUNT || azCount[i] > MAX_AZ_COUNT) - { - ERR("Unsupported azimuth count: azCount[%d]=%d (%d to %d)\n", - i, azCount[i], MIN_AZ_COUNT, MAX_AZ_COUNT); - failed = AL_TRUE; - } - } - if(failed) - return nullptr; - - al::vector evOffset(evCount); - evOffset[0] = 0; - ALushort irCount{azCount[0]}; - for(ALsizei i{1};i < evCount;i++) - { - evOffset[i] = evOffset[i-1] + azCount[i-1]; - irCount += azCount[i]; - } - - al::vector> coeffs(irSize*irCount); - al::vector> delays(irCount); - for(auto &val : coeffs) - val[0] = GetLE_ALshort(data) / 32768.0f; - for(auto &val : delays) - val[0] = GetLE_ALubyte(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(ALsizei i{0};i < irCount;i++) - { - if(delays[i][0] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%d]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - } - if(failed) - return nullptr; - - /* Mirror the left ear responses to the right ear. */ - for(ALsizei i{0};i < evCount;i++) - { - ALushort evoffset = evOffset[i]; - ALubyte azcount = azCount[i]; - for(ALsizei j{0};j < azcount;j++) - { - ALsizei lidx = evoffset + j; - ALsizei ridx = evoffset + ((azcount-j) % azcount); - - for(ALsizei k{0};k < irSize;k++) - coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0]; - delays[ridx][1] = delays[lidx][0]; - } - } - - static constexpr ALfloat distance{0.0f}; - return CreateHrtfStore(rate, irSize, 1, &evCount, &distance, azCount.data(), evOffset.data(), - irCount, &reinterpret_cast(coeffs[0]), - &reinterpret_cast(delays[0]), filename); -} - -#define SAMPLETYPE_S16 0 -#define SAMPLETYPE_S24 1 - -#define CHANTYPE_LEFTONLY 0 -#define CHANTYPE_LEFTRIGHT 1 - -std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) -{ - ALuint rate{GetLE_ALuint(data)}; - ALubyte sampleType{GetLE_ALubyte(data)}; - ALubyte channelType{GetLE_ALubyte(data)}; - ALushort irSize{GetLE_ALubyte(data)}; - ALubyte fdCount{GetLE_ALubyte(data)}; - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - ALboolean failed{AL_FALSE}; - if(sampleType > SAMPLETYPE_S24) - { - ERR("Unsupported sample type: %d\n", sampleType); - failed = AL_TRUE; - } - if(channelType > CHANTYPE_LEFTRIGHT) - { - ERR("Unsupported channel type: %d\n", channelType); - failed = AL_TRUE; - } - - if(irSize < MIN_IR_SIZE || irSize > MAX_IR_SIZE || (irSize%MOD_IR_SIZE)) - { - ERR("Unsupported HRIR size: irSize=%d (%d to %d by %d)\n", - irSize, MIN_IR_SIZE, MAX_IR_SIZE, MOD_IR_SIZE); - failed = AL_TRUE; - } - if(fdCount < 1 || fdCount > MAX_FD_COUNT) - { - ERR("Multiple field-depths not supported: fdCount=%d (%d to %d)\n", - fdCount, MIN_FD_COUNT, MAX_FD_COUNT); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - al::vector distance(fdCount); - al::vector evCount(fdCount); - al::vector azCount; - for(ALsizei f{0};f < fdCount;f++) - { - distance[f] = GetLE_ALushort(data) / 1000.0f; - evCount[f] = GetLE_ALubyte(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - if(distance[f] < MIN_FD_DISTANCE || distance[f] > MAX_FD_DISTANCE) - { - ERR("Unsupported field distance[%d]=%f (%f to %f meters)\n", f, - distance[f], MIN_FD_DISTANCE, MAX_FD_DISTANCE); - failed = AL_TRUE; - } - if(f > 0 && distance[f] <= distance[f-1]) - { - ERR("Field distance[%d] is not after previous (%f > %f)\n", f, distance[f], - distance[f-1]); - failed = AL_TRUE; - } - if(evCount[f] < MIN_EV_COUNT || evCount[f] > MAX_EV_COUNT) - { - ERR("Unsupported elevation count: evCount[%d]=%d (%d to %d)\n", f, - evCount[f], MIN_EV_COUNT, MAX_EV_COUNT); - failed = AL_TRUE; - } - if(failed) - return nullptr; - - size_t ebase{azCount.size()}; - azCount.resize(ebase + evCount[f]); - std::generate(azCount.begin()+ebase, azCount.end(), - std::bind(GetLE_ALubyte, std::ref(data))); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - for(ALsizei e{0};e < evCount[f];e++) - { - if(azCount[ebase+e] < MIN_AZ_COUNT || azCount[ebase+e] > MAX_AZ_COUNT) - { - ERR("Unsupported azimuth count: azCount[%d][%d]=%d (%d to %d)\n", f, e, - azCount[ebase+e], MIN_AZ_COUNT, MAX_AZ_COUNT); - failed = AL_TRUE; - } - } - if(failed) - return nullptr; - } - - al::vector evOffset(azCount.size()); - evOffset[0] = 0; - std::partial_sum(azCount.cbegin(), azCount.cend()-1, evOffset.begin()+1); - const ALsizei irTotal{evOffset.back() + azCount.back()}; - - al::vector> coeffs(irSize*irTotal); - al::vector> delays(irTotal); - if(channelType == CHANTYPE_LEFTONLY) - { - if(sampleType == SAMPLETYPE_S16) - { - for(auto &val : coeffs) - val[0] = GetLE_ALshort(data) / 32768.0f; - } - else if(sampleType == SAMPLETYPE_S24) - { - for(auto &val : coeffs) - val[0] = GetLE_ALint24(data) / 8388608.0f; - } - for(auto &val : delays) - val[0] = GetLE_ALubyte(data); - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - for(ALsizei i{0};i < irTotal;++i) - { - if(delays[i][0] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%d][0]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - } - } - else if(channelType == CHANTYPE_LEFTRIGHT) - { - if(sampleType == SAMPLETYPE_S16) - { - for(auto &val : coeffs) - { - val[0] = GetLE_ALshort(data) / 32768.0f; - val[1] = GetLE_ALshort(data) / 32768.0f; - } - } - else if(sampleType == SAMPLETYPE_S24) - { - for(auto &val : coeffs) - { - val[0] = GetLE_ALint24(data) / 8388608.0f; - val[1] = GetLE_ALint24(data) / 8388608.0f; - } - } - for(auto &val : delays) - { - val[0] = GetLE_ALubyte(data); - val[1] = GetLE_ALubyte(data); - } - if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } - - for(ALsizei i{0};i < irTotal;++i) - { - if(delays[i][0] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%d][0]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - if(delays[i][1] > MAX_HRIR_DELAY) - { - ERR("Invalid delays[%d][1]: %d (%d)\n", i, delays[i][1], MAX_HRIR_DELAY); - failed = AL_TRUE; - } - } - } - if(failed) - return nullptr; - - if(channelType == CHANTYPE_LEFTONLY) - { - /* Mirror the left ear responses to the right ear. */ - ALsizei ebase{0}; - for(ALsizei f{0};f < fdCount;f++) - { - for(ALsizei e{0};e < evCount[f];e++) - { - ALushort evoffset = evOffset[ebase+e]; - ALubyte azcount = azCount[ebase+e]; - for(ALsizei a{0};a < azcount;a++) - { - ALsizei lidx = evoffset + a; - ALsizei ridx = evoffset + ((azcount-a) % azcount); - - for(ALsizei k{0};k < irSize;k++) - coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0]; - delays[ridx][1] = delays[lidx][0]; - } - } - ebase += evCount[f]; - } - } - - if(fdCount > 1) - { - auto distance_ = al::vector(distance.size()); - auto evCount_ = al::vector(evCount.size()); - auto azCount_ = al::vector(azCount.size()); - auto evOffset_ = al::vector(evOffset.size()); - auto coeffs_ = al::vector(coeffs.size()); - auto delays_ = al::vector>(delays.size()); - - /* Simple reverse for the per-field elements. */ - std::reverse_copy(distance.cbegin(), distance.cend(), distance_.begin()); - std::reverse_copy(evCount.cbegin(), evCount.cend(), evCount_.begin()); - - /* Each field has a group of elevations, which each have an azimuth - * count. Reverse the order of the groups, keeping the relative order - * of per-group azimuth counts. - */ - auto azcnt_end = azCount_.end(); - auto copy_azs = [&azCount,&azcnt_end](const size_t ebase, const ALubyte num_evs) -> size_t - { - auto azcnt_src = azCount.begin()+ebase; - azcnt_end = std::copy_backward(azcnt_src, azcnt_src+num_evs, azcnt_end); - return ebase + num_evs; - }; - std::accumulate(evCount.cbegin(), evCount.cend(), 0u, copy_azs); - assert(azCount_.begin() == azcnt_end); - - /* Reestablish the IR offset for each elevation index, given the new - * ordering of elevations. - */ - evOffset_[0] = 0; - std::partial_sum(azCount_.cbegin(), azCount_.cend()-1, evOffset_.begin()+1); - - /* Reverse the order of each field's group of IRs. */ - auto coeffs_end = coeffs_.end(); - auto delays_end = delays_.end(); - auto copy_irs = [irSize,&azCount,&coeffs,&delays,&coeffs_end,&delays_end](const size_t ebase, const ALubyte num_evs) -> size_t - { - const ALsizei abase{std::accumulate(azCount.cbegin(), azCount.cbegin()+ebase, 0)}; - const ALsizei num_azs{std::accumulate(azCount.cbegin()+ebase, - azCount.cbegin() + (ebase+num_evs), 0)}; - - coeffs_end = std::copy_backward(coeffs.cbegin() + abase*irSize, - coeffs.cbegin() + (abase+num_azs)*irSize, coeffs_end); - delays_end = std::copy_backward(delays.cbegin() + abase, - delays.cbegin() + (abase+num_azs), delays_end); - - return ebase + num_evs; - }; - std::accumulate(evCount.cbegin(), evCount.cend(), 0u, copy_irs); - assert(coeffs_.begin() == coeffs_end); - assert(delays_.begin() == delays_end); - - distance = std::move(distance_); - evCount = std::move(evCount_); - azCount = std::move(azCount_); - evOffset = std::move(evOffset_); - coeffs = std::move(coeffs_); - delays = std::move(delays_); - } - - return CreateHrtfStore(rate, irSize, fdCount, evCount.data(), distance.data(), azCount.data(), - evOffset.data(), irTotal, &reinterpret_cast(coeffs[0]), - &reinterpret_cast(delays[0]), filename); -} - - -bool checkName(al::vector &list, const std::string &name) -{ - return std::find_if(list.cbegin(), list.cend(), - [&name](const EnumeratedHrtf &entry) - { return name == entry.name; } - ) != list.cend(); -} - -void AddFileEntry(al::vector &list, const std::string &filename) -{ - /* Check if this file has already been loaded globally. */ - auto loaded_entry = LoadedHrtfs.begin(); - for(;loaded_entry != LoadedHrtfs.end();++loaded_entry) - { - if(filename != (*loaded_entry)->filename.data()) - continue; - - /* Check if this entry has already been added to the list. */ - auto iter = std::find_if(list.cbegin(), list.cend(), - [loaded_entry](const EnumeratedHrtf &entry) -> bool - { return loaded_entry->get() == entry.hrtf; } - ); - if(iter != list.cend()) - { - TRACE("Skipping duplicate file entry %s\n", filename.c_str()); - return; - } - - break; - } - - if(loaded_entry == LoadedHrtfs.end()) - { - TRACE("Got new file \"%s\"\n", filename.c_str()); - - LoadedHrtfs.emplace_back(HrtfHandle::Create(filename.length()+1)); - loaded_entry = LoadedHrtfs.end()-1; - strcpy((*loaded_entry)->filename.data(), filename.c_str()); - } - - /* TODO: Get a human-readable name from the HRTF data (possibly coming in a - * format update). */ - size_t namepos = filename.find_last_of('/')+1; - if(!namepos) namepos = filename.find_last_of('\\')+1; - - size_t extpos{filename.find_last_of('.')}; - if(extpos <= namepos) extpos = std::string::npos; - - const std::string basename{(extpos == std::string::npos) ? - filename.substr(namepos) : filename.substr(namepos, extpos-namepos)}; - std::string newname{basename}; - int count{1}; - while(checkName(list, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - list.emplace_back(EnumeratedHrtf{newname, loaded_entry->get()}); - const EnumeratedHrtf &entry = list.back(); - - TRACE("Adding file entry \"%s\"\n", entry.name.c_str()); -} - -/* Unfortunate that we have to duplicate AddFileEntry to take a memory buffer - * for input instead of opening the given filename. - */ -void AddBuiltInEntry(al::vector &list, const std::string &filename, ALuint residx) -{ - auto loaded_entry = LoadedHrtfs.begin(); - for(;loaded_entry != LoadedHrtfs.end();++loaded_entry) - { - if(filename != (*loaded_entry)->filename.data()) - continue; - - /* Check if this entry has already been added to the list. */ - auto iter = std::find_if(list.cbegin(), list.cend(), - [loaded_entry](const EnumeratedHrtf &entry) -> bool - { return loaded_entry->get() == entry.hrtf; } - ); - if(iter != list.cend()) - { - TRACE("Skipping duplicate file entry %s\n", filename.c_str()); - return; - } - - break; - } - - if(loaded_entry == LoadedHrtfs.end()) - { - TRACE("Got new file \"%s\"\n", filename.c_str()); - - LoadedHrtfs.emplace_back(HrtfHandle::Create(filename.length()+32)); - loaded_entry = LoadedHrtfs.end()-1; - snprintf((*loaded_entry)->filename.data(), (*loaded_entry)->filename.size(), "!%u_%s", - residx, filename.c_str()); - } - - /* TODO: Get a human-readable name from the HRTF data (possibly coming in a - * format update). */ - - std::string newname{filename}; - int count{1}; - while(checkName(list, newname)) - { - newname = filename; - newname += " #"; - newname += std::to_string(++count); - } - list.emplace_back(EnumeratedHrtf{newname, loaded_entry->get()}); - const EnumeratedHrtf &entry = list.back(); - - TRACE("Adding built-in entry \"%s\"\n", entry.name.c_str()); -} - - -#define IDR_DEFAULT_44100_MHR 1 -#define IDR_DEFAULT_48000_MHR 2 - -using ResData = al::span; -#ifndef ALSOFT_EMBED_HRTF_DATA - -ResData GetResource(int UNUSED(name)) -{ return ResData{}; } - -#else - -#include "default-44100.mhr.h" -#include "default-48000.mhr.h" - -ResData GetResource(int name) -{ - if(name == IDR_DEFAULT_44100_MHR) - return {reinterpret_cast(hrtf_default_44100), sizeof(hrtf_default_44100)}; - if(name == IDR_DEFAULT_48000_MHR) - return {reinterpret_cast(hrtf_default_48000), sizeof(hrtf_default_48000)}; - return ResData{}; -} -#endif - -} // namespace - - -al::vector EnumerateHrtf(const char *devname) -{ - al::vector list; - - bool usedefaults{true}; - const char *pathlist{""}; - if(ConfigValueStr(devname, nullptr, "hrtf-paths", &pathlist)) - { - while(pathlist && *pathlist) - { - const char *next, *end; - - while(isspace(*pathlist) || *pathlist == ',') - pathlist++; - if(*pathlist == '\0') - continue; - - next = strchr(pathlist, ','); - if(next) - end = next++; - else - { - end = pathlist + strlen(pathlist); - usedefaults = false; - } - - while(end != pathlist && isspace(*(end-1))) - --end; - if(end != pathlist) - { - const std::string pname{pathlist, end}; - for(const auto &fname : SearchDataFiles(".mhr", pname.c_str())) - AddFileEntry(list, fname); - } - - pathlist = next; - } - } - else if(ConfigValueExists(devname, nullptr, "hrtf_tables")) - ERR("The hrtf_tables option is deprecated, please use hrtf-paths instead.\n"); - - if(usedefaults) - { - for(const auto &fname : SearchDataFiles(".mhr", "openal/hrtf")) - AddFileEntry(list, fname); - - if(!GetResource(IDR_DEFAULT_44100_MHR).empty()) - AddBuiltInEntry(list, "Built-In 44100hz", IDR_DEFAULT_44100_MHR); - - if(!GetResource(IDR_DEFAULT_48000_MHR).empty()) - AddBuiltInEntry(list, "Built-In 48000hz", IDR_DEFAULT_48000_MHR); - } - - const char *defaulthrtf{""}; - if(!list.empty() && ConfigValueStr(devname, nullptr, "default-hrtf", &defaulthrtf)) - { - auto iter = std::find_if(list.begin(), list.end(), - [defaulthrtf](const EnumeratedHrtf &entry) -> bool - { return entry.name == defaulthrtf; } - ); - if(iter == list.end()) - WARN("Failed to find default HRTF \"%s\"\n", defaulthrtf); - else if(iter != list.begin()) - { - EnumeratedHrtf entry{*iter}; - list.erase(iter); - list.insert(list.begin(), entry); - } - } - - return list; -} - -HrtfEntry *GetLoadedHrtf(HrtfHandle *handle) -{ - std::lock_guard _{LoadedHrtfLock}; - - if(handle->entry) - { - HrtfEntry *hrtf{handle->entry.get()}; - hrtf->IncRef(); - return hrtf; - } - - std::unique_ptr stream; - const char *name{""}; - ALuint residx{}; - char ch{}; - if(sscanf(handle->filename.data(), "!%u%c", &residx, &ch) == 2 && ch == '_') - { - name = strchr(handle->filename.data(), ch)+1; - - TRACE("Loading %s...\n", name); - ResData res{GetResource(residx)}; - if(res.empty()) - { - ERR("Could not get resource %u, %s\n", residx, name); - return nullptr; - } - stream = al::make_unique(res.begin(), res.end()); - } - else - { - name = handle->filename.data(); - - TRACE("Loading %s...\n", handle->filename.data()); - auto fstr = al::make_unique(handle->filename.data(), std::ios::binary); - if(!fstr->is_open()) - { - ERR("Could not open %s\n", handle->filename.data()); - return nullptr; - } - stream = std::move(fstr); - } - - std::unique_ptr hrtf; - char magic[sizeof(magicMarker02)]; - stream->read(magic, sizeof(magic)); - if(stream->gcount() < static_cast(sizeof(magicMarker02))) - ERR("%s data is too short (%zu bytes)\n", name, stream->gcount()); - else if(memcmp(magic, magicMarker02, sizeof(magicMarker02)) == 0) - { - TRACE("Detected data set format v2\n"); - hrtf = LoadHrtf02(*stream, name); - } - else if(memcmp(magic, magicMarker01, sizeof(magicMarker01)) == 0) - { - TRACE("Detected data set format v1\n"); - hrtf = LoadHrtf01(*stream, name); - } - else if(memcmp(magic, magicMarker00, sizeof(magicMarker00)) == 0) - { - TRACE("Detected data set format v0\n"); - hrtf = LoadHrtf00(*stream, name); - } - else - ERR("Invalid header in %s: \"%.8s\"\n", name, magic); - stream.reset(); - - if(!hrtf) - { - ERR("Failed to load %s\n", name); - return nullptr; - } - - TRACE("Loaded HRTF support for format: %s %uhz\n", - DevFmtChannelsString(DevFmtStereo), hrtf->sampleRate); - handle->entry = std::move(hrtf); - - return handle->entry.get(); -} - - -void HrtfEntry::IncRef() -{ - auto ref = IncrementRef(&this->ref); - TRACEREF("%p increasing refcount to %u\n", this, ref); -} - -void HrtfEntry::DecRef() -{ - auto ref = DecrementRef(&this->ref); - TRACEREF("%p decreasing refcount to %u\n", this, ref); - if(ref == 0) - { - std::lock_guard _{LoadedHrtfLock}; - - /* Need to double-check that it's still unused, as another device - * could've reacquired this HRTF after its reference went to 0 and - * before the lock was taken. - */ - auto iter = std::find_if(LoadedHrtfs.begin(), LoadedHrtfs.end(), - [this](const HrtfHandlePtr &entry) noexcept -> bool - { return this == entry->entry.get(); } - ); - if(iter != LoadedHrtfs.end() && ReadRef(&this->ref) == 0) - { - (*iter)->entry = nullptr; - TRACE("Unloaded unused HRTF %s\n", (*iter)->filename.data()); - } - } -} diff --git a/modules/openal-soft/Alc/hrtf.h b/modules/openal-soft/Alc/hrtf.h deleted file mode 100644 index 0283de1..0000000 --- a/modules/openal-soft/Alc/hrtf.h +++ /dev/null @@ -1,121 +0,0 @@ -#ifndef ALC_HRTF_H -#define ALC_HRTF_H - -#include -#include -#include - -#include "AL/al.h" -#include "AL/alc.h" - -#include "vector.h" -#include "almalloc.h" - - -#define HRTF_HISTORY_BITS (6) -#define HRTF_HISTORY_LENGTH (1<; - -template -using HrirArray = std::array,HRIR_LENGTH>; - -struct HrtfState { - alignas(16) std::array History; - alignas(16) HrirArray Values; -}; - -struct HrtfParams { - alignas(16) HrirArray Coeffs; - ALsizei Delay[2]; - ALfloat Gain; -}; - -struct DirectHrtfState { - /* HRTF filter state for dry buffer content */ - ALsizei IrSize{0}; - struct ChanData { - alignas(16) HrirArray Values; - alignas(16) HrirArray Coeffs; - }; - al::FlexArray Chan; - - DirectHrtfState(size_t numchans) : Chan{numchans} { } - DirectHrtfState(const DirectHrtfState&) = delete; - DirectHrtfState& operator=(const DirectHrtfState&) = delete; - - static std::unique_ptr Create(size_t num_chans); - static constexpr size_t Sizeof(size_t numchans) noexcept - { return al::FlexArray::Sizeof(numchans, offsetof(DirectHrtfState, Chan)); } - - DEF_PLACE_NEWDEL() -}; - -struct AngularPoint { - ALfloat Elev; - ALfloat Azim; -}; - - -al::vector EnumerateHrtf(const char *devname); -HrtfEntry *GetLoadedHrtf(HrtfHandle *handle); - -void GetHrtfCoeffs(const HrtfEntry *Hrtf, ALfloat elevation, ALfloat azimuth, ALfloat distance, - ALfloat spread, HrirArray &coeffs, ALsizei (&delays)[2]); - -/** - * Produces HRTF filter coefficients for decoding B-Format, given a set of - * virtual speaker positions, a matching decoding matrix, and per-order high- - * frequency gains for the decoder. The calculated impulse responses are - * ordered and scaled according to the matrix input. Note the specified virtual - * positions should be in degrees, not radians! - */ -void BuildBFormatHrtf(const HrtfEntry *Hrtf, DirectHrtfState *state, const ALsizei NumChannels, const AngularPoint *AmbiPoints, const ALfloat (*RESTRICT AmbiMatrix)[MAX_AMBI_CHANNELS], const size_t AmbiCount, const ALfloat *RESTRICT AmbiOrderHFGain); - -#endif /* ALC_HRTF_H */ diff --git a/modules/openal-soft/Alc/inprogext.h b/modules/openal-soft/Alc/inprogext.h index 15881b5..9af80f1 100644 --- a/modules/openal-soft/Alc/inprogext.h +++ b/modules/openal-soft/Alc/inprogext.h @@ -9,25 +9,6 @@ extern "C" { #endif -#ifndef ALC_SOFT_loopback_bformat -#define ALC_SOFT_loopback_bformat 1 -#define ALC_AMBISONIC_LAYOUT_SOFT 0x1997 -#define ALC_AMBISONIC_SCALING_SOFT 0x1998 -#define ALC_AMBISONIC_ORDER_SOFT 0x1999 -#define ALC_MAX_AMBISONIC_ORDER_SOFT 0x199B - -#define ALC_BFORMAT3D_SOFT 0x1508 - -/* Ambisonic layouts */ -#define ALC_FUMA_SOFT 0x0000 -#define ALC_ACN_SOFT 0x0001 - -/* Ambisonic scalings (normalization) */ -/*#define ALC_FUMA_SOFT*/ -#define ALC_SN3D_SOFT 0x0001 -#define ALC_N3D_SOFT 0x0002 -#endif - #ifndef AL_SOFT_map_buffer #define AL_SOFT_map_buffer 1 typedef unsigned int ALbitfieldSOFT; @@ -47,44 +28,44 @@ AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, A #endif #endif -#ifndef AL_SOFT_events -#define AL_SOFT_events 1 -#define AL_EVENT_CALLBACK_FUNCTION_SOFT 0x1220 -#define AL_EVENT_CALLBACK_USER_PARAM_SOFT 0x1221 -#define AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT 0x1222 -#define AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT 0x1223 -#define AL_EVENT_TYPE_ERROR_SOFT 0x1224 -#define AL_EVENT_TYPE_PERFORMANCE_SOFT 0x1225 -#define AL_EVENT_TYPE_DEPRECATED_SOFT 0x1226 -#define AL_EVENT_TYPE_DISCONNECTED_SOFT 0x1227 -typedef void (AL_APIENTRY*ALEVENTPROCSOFT)(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message, - void *userParam); -typedef void (AL_APIENTRY*LPALEVENTCONTROLSOFT)(ALsizei count, const ALenum *types, ALboolean enable); -typedef void (AL_APIENTRY*LPALEVENTCALLBACKSOFT)(ALEVENTPROCSOFT callback, void *userParam); -typedef void* (AL_APIENTRY*LPALGETPOINTERSOFT)(ALenum pname); -typedef void (AL_APIENTRY*LPALGETPOINTERVSOFT)(ALenum pname, void **values); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable); -AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam); -AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname); -AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values); -#endif +#ifndef AL_SOFT_bformat_hoa +#define AL_SOFT_bformat_hoa +#define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D #endif -#ifndef AL_SOFT_buffer_layers -#define AL_SOFT_buffer_layers -typedef void (AL_APIENTRY*LPALSOURCEQUEUEBUFFERLAYERSSOFT)(ALuint src, ALsizei nb, const ALuint *buffers); +#ifndef AL_SOFT_convolution_reverb +#define AL_SOFT_convolution_reverb +#define AL_EFFECT_CONVOLUTION_REVERB_SOFT 0xA000 +#define AL_EFFECTSLOT_STATE_SOFT 0x199D +typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYSOFT)(ALuint slotid); +typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYVSOFT)(ALsizei n, const ALuint *slotids); +typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPSOFT)(ALuint slotid); +typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPVSOFT)(ALsizei n, const ALuint *slotids); #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb, const ALuint *buffers); +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid); +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids); +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid); +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids); #endif #endif -#ifndef AL_SOFT_effect_chain -#define AL_SOFT_effect_chain -#define AL_EFFECTSLOT_TARGET_SOFT 0xf000 +#ifndef AL_SOFT_hold_on_disconnect +#define AL_SOFT_hold_on_disconnect +#define AL_STOP_SOURCES_ON_DISCONNECT_SOFT 0x19AB #endif + +/* Non-standard export. Not part of any extension. */ +AL_API const ALchar* AL_APIENTRY alsoft_get_version(void); + + +/* Functions from abandoned extenions. Only here for binary compatibility. */ +AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb, + const ALuint *buffers); + +AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname); +AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/modules/openal-soft/Alc/logging.h b/modules/openal-soft/Alc/logging.h deleted file mode 100644 index e709bbf..0000000 --- a/modules/openal-soft/Alc/logging.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef LOGGING_H -#define LOGGING_H - -#include - -#include "opthelpers.h" - - -#ifdef __GNUC__ -#define DECL_FORMAT(x, y, z) __attribute__((format(x, (y), (z)))) -#else -#define DECL_FORMAT(x, y, z) -#endif - - -extern FILE *gLogFile; - -constexpr inline const char *CurrentPrefix() noexcept { return ""; } -#if defined(__GNUC__) && !defined(_WIN32) -#define AL_PRINT(T, MSG, ...) fprintf(gLogFile, "AL lib: %s %s%s: " MSG, T, CurrentPrefix(), __FUNCTION__ , ## __VA_ARGS__) -#else -void al_print(const char *type, const char *prefix, const char *func, const char *fmt, ...) DECL_FORMAT(printf, 4,5); -#define AL_PRINT(T, ...) al_print((T), CurrentPrefix(), __FUNCTION__, __VA_ARGS__) -#endif - -#ifdef __ANDROID__ -#include -#define LOG_ANDROID(T, MSG, ...) __android_log_print(T, "openal", "AL lib: %s%s: " MSG, CurrentPrefix(), __FUNCTION__ , ## __VA_ARGS__) -#else -#define LOG_ANDROID(T, MSG, ...) ((void)0) -#endif - -enum LogLevel { - NoLog, - LogError, - LogWarning, - LogTrace, - LogRef -}; -extern LogLevel gLogLevel; - -#define TRACEREF(...) do { \ - if(UNLIKELY(gLogLevel >= LogRef)) \ - AL_PRINT("(--)", __VA_ARGS__); \ -} while(0) - -#define TRACE(...) do { \ - if(UNLIKELY(gLogLevel >= LogTrace)) \ - AL_PRINT("(II)", __VA_ARGS__); \ - LOG_ANDROID(ANDROID_LOG_DEBUG, __VA_ARGS__); \ -} while(0) - -#define WARN(...) do { \ - if(UNLIKELY(gLogLevel >= LogWarning)) \ - AL_PRINT("(WW)", __VA_ARGS__); \ - LOG_ANDROID(ANDROID_LOG_WARN, __VA_ARGS__); \ -} while(0) - -#define ERR(...) do { \ - if(UNLIKELY(gLogLevel >= LogError)) \ - AL_PRINT("(EE)", __VA_ARGS__); \ - LOG_ANDROID(ANDROID_LOG_ERROR, __VA_ARGS__); \ -} while(0) - -#endif /* LOGGING_H */ diff --git a/modules/openal-soft/Alc/mastering.h b/modules/openal-soft/Alc/mastering.h deleted file mode 100644 index a9411bd..0000000 --- a/modules/openal-soft/Alc/mastering.h +++ /dev/null @@ -1,107 +0,0 @@ -#ifndef MASTERING_H -#define MASTERING_H - -#include - -#include "AL/al.h" - -#include "almalloc.h" -/* For BUFFERSIZE. */ -#include "alMain.h" - - -struct SlidingHold; - -/* General topology and basic automation was based on the following paper: - * - * D. Giannoulis, M. Massberg and J. D. Reiss, - * "Parameter Automation in a Dynamic Range Compressor," - * Journal of the Audio Engineering Society, v61 (10), Oct. 2013 - * - * Available (along with supplemental reading) at: - * - * http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/ - */ -struct Compressor { - ALsizei mNumChans{0}; - ALuint mSampleRate{0u}; - - struct { - bool Knee : 1; - bool Attack : 1; - bool Release : 1; - bool PostGain : 1; - bool Declip : 1; - } mAuto{}; - - ALsizei mLookAhead{0}; - - ALfloat mPreGain{0.0f}; - ALfloat mPostGain{0.0f}; - - ALfloat mThreshold{0.0f}; - ALfloat mSlope{0.0f}; - ALfloat mKnee{0.0f}; - - ALfloat mAttack{0.0f}; - ALfloat mRelease{0.0f}; - - alignas(16) ALfloat mSideChain[2*BUFFERSIZE]{}; - alignas(16) ALfloat mCrestFactor[BUFFERSIZE]{}; - - SlidingHold *mHold{nullptr}; - ALfloat (*mDelay)[BUFFERSIZE]{nullptr}; - ALsizei mDelayIndex{0}; - - ALfloat mCrestCoeff{0.0f}; - ALfloat mGainEstimate{0.0f}; - ALfloat mAdaptCoeff{0.0f}; - - ALfloat mLastPeakSq{0.0f}; - ALfloat mLastRmsSq{0.0f}; - ALfloat mLastRelease{0.0f}; - ALfloat mLastAttack{0.0f}; - ALfloat mLastGainDev{0.0f}; - - - ~Compressor(); - void process(const ALsizei SamplesToDo, ALfloat (*OutBuffer)[BUFFERSIZE]); - ALsizei getLookAhead() const noexcept { return mLookAhead; } - - DEF_PLACE_NEWDEL() -}; - -/* The compressor is initialized with the following settings: - * - * NumChans - Number of channels to process. - * SampleRate - Sample rate to process. - * AutoKnee - Whether to automate the knee width parameter. - * AutoAttack - Whether to automate the attack time parameter. - * AutoRelease - Whether to automate the release time parameter. - * AutoPostGain - Whether to automate the make-up (post) gain parameter. - * AutoDeclip - Whether to automate clipping reduction. Ignored when - * not automating make-up gain. - * LookAheadTime - Look-ahead time (in seconds). - * HoldTime - Peak hold-time (in seconds). - * PreGainDb - Gain applied before detection (in dB). - * PostGainDb - Make-up gain applied after compression (in dB). - * ThresholdDb - Triggering threshold (in dB). - * Ratio - Compression ratio (x:1). Set to INFINIFTY for true - * limiting. Ignored when automating knee width. - * KneeDb - Knee width (in dB). Ignored when automating knee - * width. - * AttackTimeMin - Attack time (in seconds). Acts as a maximum when - * automating attack time. - * ReleaseTimeMin - Release time (in seconds). Acts as a maximum when - * automating release time. - */ -std::unique_ptr CompressorInit(const ALsizei NumChans, const ALuint SampleRate, - const ALboolean AutoKnee, const ALboolean AutoAttack, - const ALboolean AutoRelease, const ALboolean AutoPostGain, - const ALboolean AutoDeclip, const ALfloat LookAheadTime, - const ALfloat HoldTime, const ALfloat PreGainDb, - const ALfloat PostGainDb, const ALfloat ThresholdDb, - const ALfloat Ratio, const ALfloat KneeDb, - const ALfloat AttackTime, const ALfloat ReleaseTime); - -#endif /* MASTERING_H */ diff --git a/modules/openal-soft/Alc/mixer/defs.h b/modules/openal-soft/Alc/mixer/defs.h deleted file mode 100644 index cd30183..0000000 --- a/modules/openal-soft/Alc/mixer/defs.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef MIXER_DEFS_H -#define MIXER_DEFS_H - -#include "AL/alc.h" -#include "AL/al.h" -#include "alMain.h" -#include "alu.h" - - -struct MixGains; -struct MixHrtfParams; -struct HrtfState; -struct DirectHrtfState; - - -struct CTag { }; -struct SSETag { }; -struct SSE2Tag { }; -struct SSE3Tag { }; -struct SSE4Tag { }; -struct NEONTag { }; - -struct CopyTag { }; -struct PointTag { }; -struct LerpTag { }; -struct CubicTag { }; -struct BSincTag { }; - -template -const ALfloat *Resample_(const InterpState *state, const ALfloat *RESTRICT src, ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen); - -template -void Mix_(const ALfloat *data, const ALsizei OutChans, ALfloat (*OutBuffer)[BUFFERSIZE], ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, const ALsizei BufferSize); -template -void MixRow_(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*data)[BUFFERSIZE], const ALsizei InChans, const ALsizei InPos, const ALsizei BufferSize); - -template -void MixHrtf_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, MixHrtfParams *hrtfparams, const ALsizei BufferSize); -template -void MixHrtfBlend_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize); -template -void MixDirectHrtf_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State, const ALsizei NumChans, const ALsizei BufferSize); - -/* Vectorized resampler helpers */ -inline void InitiatePositionArrays(ALsizei frac, ALint increment, ALsizei *RESTRICT frac_arr, ALsizei *RESTRICT pos_arr, ALsizei size) -{ - pos_arr[0] = 0; - frac_arr[0] = frac; - for(ALsizei i{1};i < size;i++) - { - ALint frac_tmp = frac_arr[i-1] + increment; - pos_arr[i] = pos_arr[i-1] + (frac_tmp>>FRACTIONBITS); - frac_arr[i] = frac_tmp&FRACTIONMASK; - } -} - -#endif /* MIXER_DEFS_H */ diff --git a/modules/openal-soft/Alc/mixer/hrtfbase.h b/modules/openal-soft/Alc/mixer/hrtfbase.h deleted file mode 100644 index 162d728..0000000 --- a/modules/openal-soft/Alc/mixer/hrtfbase.h +++ /dev/null @@ -1,132 +0,0 @@ -#ifndef MIXER_HRTFBASE_H -#define MIXER_HRTFBASE_H - -#include - -#include "alu.h" -#include "../hrtf.h" -#include "opthelpers.h" - - -using ApplyCoeffsT = void(ALsizei Offset, float2 *RESTRICT Values, const ALsizei irSize, - const HrirArray &Coeffs, const ALfloat left, const ALfloat right); - -template -inline void MixHrtfBase(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data, - float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - MixHrtfParams *hrtfparams, const ALsizei BufferSize) -{ - ASSUME(OutPos >= 0); - ASSUME(IrSize >= 4); - ASSUME(BufferSize > 0); - - const auto &Coeffs = *hrtfparams->Coeffs; - const ALfloat gainstep{hrtfparams->GainStep}; - const ALfloat gain{hrtfparams->Gain}; - ALfloat stepcount{0.0f}; - - ALsizei Delay[2]{ - HRTF_HISTORY_LENGTH - hrtfparams->Delay[0], - HRTF_HISTORY_LENGTH - hrtfparams->Delay[1] }; - ASSUME(Delay[0] >= 0 && Delay[1] >= 0); - - for(ALsizei i{0};i < BufferSize;++i) - { - const ALfloat g{gain + gainstep*stepcount}; - const ALfloat left{data[Delay[0]++] * g}; - const ALfloat right{data[Delay[1]++] * g}; - ApplyCoeffs(i, AccumSamples+i, IrSize, Coeffs, left, right); - - stepcount += 1.0f; - } - for(ALsizei i{0};i < BufferSize;++i) - LeftOut[OutPos+i] += AccumSamples[i][0]; - for(ALsizei i{0};i < BufferSize;++i) - RightOut[OutPos+i] += AccumSamples[i][1]; - - hrtfparams->Gain = gain + gainstep*stepcount; -} - -template -inline void MixHrtfBlendBase(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, - const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize) -{ - const auto &OldCoeffs = oldparams->Coeffs; - const ALfloat oldGain{oldparams->Gain}; - const ALfloat oldGainStep{-oldGain / static_cast(BufferSize)}; - const auto &NewCoeffs = *newparams->Coeffs; - const ALfloat newGainStep{newparams->GainStep}; - ALfloat stepcount{0.0f}; - - ASSUME(OutPos >= 0); - ASSUME(IrSize >= 4); - ASSUME(BufferSize > 0); - - ALsizei OldDelay[2]{ - HRTF_HISTORY_LENGTH - oldparams->Delay[0], - HRTF_HISTORY_LENGTH - oldparams->Delay[1] }; - ASSUME(OldDelay[0] >= 0 && OldDelay[1] >= 0); - ALsizei NewDelay[2]{ - HRTF_HISTORY_LENGTH - newparams->Delay[0], - HRTF_HISTORY_LENGTH - newparams->Delay[1] }; - ASSUME(NewDelay[0] >= 0 && NewDelay[1] >= 0); - - for(ALsizei i{0};i < BufferSize;++i) - { - ALfloat g{oldGain + oldGainStep*stepcount}; - ALfloat left{data[OldDelay[0]++] * g}; - ALfloat right{data[OldDelay[1]++] * g}; - ApplyCoeffs(i, AccumSamples+i, IrSize, OldCoeffs, left, right); - - g = newGainStep*stepcount; - left = data[NewDelay[0]++] * g; - right = data[NewDelay[1]++] * g; - ApplyCoeffs(i, AccumSamples+i, IrSize, NewCoeffs, left, right); - - stepcount += 1.0f; - } - for(ALsizei i{0};i < BufferSize;++i) - LeftOut[OutPos+i] += AccumSamples[i][0]; - for(ALsizei i{0};i < BufferSize;++i) - RightOut[OutPos+i] += AccumSamples[i][1]; - - newparams->Gain = newGainStep*stepcount; -} - -template -inline void MixDirectHrtfBase(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, - const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State, - const ALsizei NumChans, const ALsizei BufferSize) -{ - ASSUME(NumChans > 0); - ASSUME(BufferSize > 0); - - const ALsizei IrSize{State->IrSize}; - ASSUME(IrSize >= 4); - - for(ALsizei c{0};c < NumChans;++c) - { - const ALfloat (&input)[BUFFERSIZE] = data[c]; - const auto &Coeffs = State->Chan[c].Coeffs; - - auto accum_iter = std::copy_n(State->Chan[c].Values.begin(), - State->Chan[c].Values.size(), AccumSamples); - std::fill_n(accum_iter, BufferSize, float2{}); - - for(ALsizei i{0};i < BufferSize;++i) - { - const ALfloat insample{input[i]}; - ApplyCoeffs(i, AccumSamples+i, IrSize, Coeffs, insample, insample); - } - for(ALsizei i{0};i < BufferSize;++i) - LeftOut[i] += AccumSamples[i][0]; - for(ALsizei i{0};i < BufferSize;++i) - RightOut[i] += AccumSamples[i][1]; - - std::copy_n(AccumSamples + BufferSize, State->Chan[c].Values.size(), - State->Chan[c].Values.begin()); - } -} - -#endif /* MIXER_HRTFBASE_H */ diff --git a/modules/openal-soft/Alc/mixer/mixer_c.cpp b/modules/openal-soft/Alc/mixer/mixer_c.cpp deleted file mode 100644 index 1c22115..0000000 --- a/modules/openal-soft/Alc/mixer/mixer_c.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include "config.h" - -#include - -#include - -#include "alMain.h" -#include "alu.h" -#include "alSource.h" -#include "alAuxEffectSlot.h" -#include "defs.h" -#include "hrtfbase.h" - - -static inline ALfloat do_point(const InterpState&, const ALfloat *RESTRICT vals, const ALsizei) noexcept -{ return vals[0]; } -static inline ALfloat do_lerp(const InterpState&, const ALfloat *RESTRICT vals, const ALsizei frac) noexcept -{ return lerp(vals[0], vals[1], frac * (1.0f/FRACTIONONE)); } -static inline ALfloat do_cubic(const InterpState&, const ALfloat *RESTRICT vals, const ALsizei frac) noexcept -{ return cubic(vals[0], vals[1], vals[2], vals[3], frac * (1.0f/FRACTIONONE)); } -static inline ALfloat do_bsinc(const InterpState &istate, const ALfloat *RESTRICT vals, const ALsizei frac) noexcept -{ - ASSUME(istate.bsinc.m > 0); - - // Calculate the phase index and factor. -#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS) - const ALsizei pi{frac >> FRAC_PHASE_BITDIFF}; - const ALfloat pf{(frac & ((1< -static const ALfloat *DoResample(const InterpState *state, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, - ALsizei numsamples) -{ - ASSUME(numsamples > 0); - ASSUME(increment > 0); - ASSUME(frac >= 0); - - const InterpState istate{*state}; - auto proc_sample = [&src,&frac,istate,increment]() -> ALfloat - { - const ALfloat ret{Sampler(istate, src, frac)}; - - frac += increment; - src += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - - return ret; - }; - std::generate_n(dst, numsamples, proc_sample); - - return dst; -} - - -template<> -const ALfloat *Resample_(const InterpState* UNUSED(state), - const ALfloat *RESTRICT src, ALsizei UNUSED(frac), ALint UNUSED(increment), - ALfloat *RESTRICT dst, ALsizei dstlen) -{ - ASSUME(dstlen > 0); -#if defined(HAVE_SSE) || defined(HAVE_NEON) - /* Avoid copying the source data if it's aligned like the destination. */ - if((reinterpret_cast(src)&15) == (reinterpret_cast(dst)&15)) - return src; -#endif - std::copy_n(src, dstlen, dst); - return dst; -} - -template<> -const ALfloat *Resample_(const InterpState *state, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ return DoResample(state, src, frac, increment, dst, dstlen); } - -template<> -const ALfloat *Resample_(const InterpState *state, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ return DoResample(state, src, frac, increment, dst, dstlen); } - -template<> -const ALfloat *Resample_(const InterpState *state, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ return DoResample(state, src-1, frac, increment, dst, dstlen); } - -template<> -const ALfloat *Resample_(const InterpState *state, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ return DoResample(state, src-state->bsinc.l, frac, increment, dst, dstlen); } - - -static inline void ApplyCoeffs(ALsizei /*Offset*/, float2 *RESTRICT Values, const ALsizei IrSize, - const HrirArray &Coeffs, const ALfloat left, const ALfloat right) -{ - ASSUME(IrSize >= 2); - for(ALsizei c{0};c < IrSize;++c) - { - Values[c][0] += Coeffs[c][0] * left; - Values[c][1] += Coeffs[c][1] * right; - } -} - -template<> -void MixHrtf_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data, - float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - MixHrtfParams *hrtfparams, const ALsizei BufferSize) -{ - MixHrtfBase(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, hrtfparams, - BufferSize); -} - -template<> -void MixHrtfBlend_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, - const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize) -{ - MixHrtfBlendBase(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, oldparams, - newparams, BufferSize); -} - -template<> -void MixDirectHrtf_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, - const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State, - const ALsizei NumChans, const ALsizei BufferSize) -{ - MixDirectHrtfBase(LeftOut, RightOut, data, AccumSamples, State, NumChans, - BufferSize); -} - - -template<> -void Mix_(const ALfloat *data, const ALsizei OutChans, ALfloat (*OutBuffer)[BUFFERSIZE], - ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, - const ALsizei BufferSize) -{ - ASSUME(OutChans > 0); - ASSUME(BufferSize > 0); - - const ALfloat delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - for(ALsizei c{0};c < OutChans;c++) - { - ALfloat *RESTRICT dst{&OutBuffer[c][OutPos]}; - ALsizei pos{0}; - ALfloat gain{CurrentGains[c]}; - - const ALfloat diff{TargetGains[c] - gain}; - if(std::fabs(diff) > std::numeric_limits::epsilon()) - { - ALsizei minsize{mini(BufferSize, Counter)}; - const ALfloat step{diff * delta}; - ALfloat step_count{0.0f}; - for(;pos < minsize;pos++) - { - dst[pos] += data[pos] * (gain + step*step_count); - step_count += 1.0f; - } - if(pos == Counter) - gain = TargetGains[c]; - else - gain += step*step_count; - CurrentGains[c] = gain; - } - - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - for(;pos < BufferSize;pos++) - dst[pos] += data[pos]*gain; - } -} - -/* Basically the inverse of the above. Rather than one input going to multiple - * outputs (each with its own gain), it's multiple inputs (each with its own - * gain) going to one output. This applies one row (vs one column) of a matrix - * transform. And as the matrices are more or less static once set up, no - * stepping is necessary. - */ -template<> -void MixRow_(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*data)[BUFFERSIZE], - const ALsizei InChans, const ALsizei InPos, const ALsizei BufferSize) -{ - ASSUME(InChans > 0); - ASSUME(BufferSize > 0); - - for(ALsizei c{0};c < InChans;c++) - { - const ALfloat *RESTRICT src{&data[c][InPos]}; - const ALfloat gain{Gains[c]}; - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - - for(ALsizei i{0};i < BufferSize;i++) - OutBuffer[i] += src[i] * gain; - } -} diff --git a/modules/openal-soft/Alc/mixer/mixer_neon.cpp b/modules/openal-soft/Alc/mixer/mixer_neon.cpp deleted file mode 100644 index cdd9629..0000000 --- a/modules/openal-soft/Alc/mixer/mixer_neon.cpp +++ /dev/null @@ -1,309 +0,0 @@ -#include "config.h" - -#include - -#include - -#include "AL/al.h" -#include "AL/alc.h" -#include "alMain.h" -#include "alu.h" -#include "hrtf.h" -#include "defs.h" -#include "hrtfbase.h" - - - -template<> -const ALfloat *Resample_(const InterpState* UNUSED(state), - const ALfloat *RESTRICT src, ALsizei frac, ALint increment, - ALfloat *RESTRICT dst, ALsizei dstlen) -{ - const int32x4_t increment4 = vdupq_n_s32(increment*4); - const float32x4_t fracOne4 = vdupq_n_f32(1.0f/FRACTIONONE); - const int32x4_t fracMask4 = vdupq_n_s32(FRACTIONMASK); - alignas(16) ALsizei pos_[4], frac_[4]; - int32x4_t pos4, frac4; - ALsizei todo, pos, i; - - ASSUME(frac >= 0); - ASSUME(increment > 0); - ASSUME(dstlen > 0); - - InitiatePositionArrays(frac, increment, frac_, pos_, 4); - frac4 = vld1q_s32(frac_); - pos4 = vld1q_s32(pos_); - - todo = dstlen & ~3; - for(i = 0;i < todo;i += 4) - { - const int pos0 = vgetq_lane_s32(pos4, 0); - const int pos1 = vgetq_lane_s32(pos4, 1); - const int pos2 = vgetq_lane_s32(pos4, 2); - const int pos3 = vgetq_lane_s32(pos4, 3); - const float32x4_t val1 = (float32x4_t){src[pos0], src[pos1], src[pos2], src[pos3]}; - const float32x4_t val2 = (float32x4_t){src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1]}; - - /* val1 + (val2-val1)*mu */ - const float32x4_t r0 = vsubq_f32(val2, val1); - const float32x4_t mu = vmulq_f32(vcvtq_f32_s32(frac4), fracOne4); - const float32x4_t out = vmlaq_f32(val1, mu, r0); - - vst1q_f32(&dst[i], out); - - frac4 = vaddq_s32(frac4, increment4); - pos4 = vaddq_s32(pos4, vshrq_n_s32(frac4, FRACTIONBITS)); - frac4 = vandq_s32(frac4, fracMask4); - } - - /* NOTE: These four elements represent the position *after* the last four - * samples, so the lowest element is the next position to resample. - */ - pos = vgetq_lane_s32(pos4, 0); - frac = vgetq_lane_s32(frac4, 0); - - for(;i < dstlen;++i) - { - dst[i] = lerp(src[pos], src[pos+1], frac * (1.0f/FRACTIONONE)); - - frac += increment; - pos += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } - return dst; -} - -template<> -const ALfloat *Resample_(const InterpState *state, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ - const ALfloat *const filter = state->bsinc.filter; - const float32x4_t sf4 = vdupq_n_f32(state->bsinc.sf); - const ALsizei m = state->bsinc.m; - const float32x4_t *fil, *scd, *phd, *spd; - ALsizei pi, i, j, offset; - float32x4_t r4; - ALfloat pf; - - ASSUME(m > 0); - ASSUME(dstlen > 0); - ASSUME(increment > 0); - ASSUME(frac >= 0); - - src -= state->bsinc.l; - for(i = 0;i < dstlen;i++) - { - // Calculate the phase index and factor. -#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS) - pi = frac >> FRAC_PHASE_BITDIFF; - pf = (frac & ((1<> 2; - const float32x4_t pf4 = vdupq_n_f32(pf); - - ASSUME(count > 0); - - for(j = 0;j < count;j++) - { - /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ - const float32x4_t f4 = vmlaq_f32( - vmlaq_f32(fil[j], sf4, scd[j]), - pf4, vmlaq_f32(phd[j], sf4, spd[j]) - ); - /* r += f*src */ - r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j*4])); - } - } - r4 = vaddq_f32(r4, vcombine_f32(vrev64_f32(vget_high_f32(r4)), - vrev64_f32(vget_low_f32(r4)))); - dst[i] = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); - - frac += increment; - src += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } - return dst; -} - - -static inline void ApplyCoeffs(ALsizei /*Offset*/, float2 *RESTRICT Values, const ALsizei IrSize, - const HrirArray &Coeffs, const ALfloat left, const ALfloat right) -{ - ASSUME(IrSize >= 2); - - float32x4_t leftright4; - { - float32x2_t leftright2 = vdup_n_f32(0.0); - leftright2 = vset_lane_f32(left, leftright2, 0); - leftright2 = vset_lane_f32(right, leftright2, 1); - leftright4 = vcombine_f32(leftright2, leftright2); - } - - for(ALsizei c{0};c < IrSize;c += 2) - { - float32x4_t vals = vcombine_f32(vld1_f32((float32_t*)&Values[c ][0]), - vld1_f32((float32_t*)&Values[c+1][0])); - float32x4_t coefs = vld1q_f32((float32_t*)&Coeffs[c][0]); - - vals = vmlaq_f32(vals, coefs, leftright4); - - vst1_f32((float32_t*)&Values[c ][0], vget_low_f32(vals)); - vst1_f32((float32_t*)&Values[c+1][0], vget_high_f32(vals)); - } -} - -template<> -void MixHrtf_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data, - float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - MixHrtfParams *hrtfparams, const ALsizei BufferSize) -{ - MixHrtfBase(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, hrtfparams, - BufferSize); -} - -template<> -void MixHrtfBlend_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, - const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize) -{ - MixHrtfBlendBase(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, oldparams, - newparams, BufferSize); -} - -template<> -void MixDirectHrtf_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, - const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State, - const ALsizei NumChans, const ALsizei BufferSize) -{ - MixDirectHrtfBase(LeftOut, RightOut, data, AccumSamples, State, NumChans, - BufferSize); -} - - -template<> -void Mix_(const ALfloat *data, const ALsizei OutChans, ALfloat (*OutBuffer)[BUFFERSIZE], - ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, - const ALsizei BufferSize) -{ - ASSUME(OutChans > 0); - ASSUME(BufferSize > 0); - - const ALfloat delta{(Counter > 0) ? 1.0f/(ALfloat)Counter : 0.0f}; - for(ALsizei c{0};c < OutChans;c++) - { - ALfloat *RESTRICT dst{al::assume_aligned<16>(&OutBuffer[c][OutPos])}; - ALsizei pos{0}; - ALfloat gain{CurrentGains[c]}; - const ALfloat diff{TargetGains[c] - gain}; - - if(std::fabs(diff) > std::numeric_limits::epsilon()) - { - ALsizei minsize{mini(BufferSize, Counter)}; - const ALfloat step{diff * delta}; - ALfloat step_count{0.0f}; - /* Mix with applying gain steps in aligned multiples of 4. */ - if(LIKELY(minsize > 3)) - { - const float32x4_t four4{vdupq_n_f32(4.0f)}; - const float32x4_t step4{vdupq_n_f32(step)}; - const float32x4_t gain4{vdupq_n_f32(gain)}; - float32x4_t step_count4{vsetq_lane_f32(0.0f, - vsetq_lane_f32(1.0f, - vsetq_lane_f32(2.0f, - vsetq_lane_f32(3.0f, vdupq_n_f32(0.0f), 3), - 2), 1), 0 - )}; - ALsizei todo{minsize >> 2}; - - do { - const float32x4_t val4 = vld1q_f32(&data[pos]); - float32x4_t dry4 = vld1q_f32(&dst[pos]); - dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4)); - step_count4 = vaddq_f32(step_count4, four4); - vst1q_f32(&dst[pos], dry4); - pos += 4; - } while(--todo); - /* NOTE: step_count4 now represents the next four counts after - * the last four mixed samples, so the lowest element - * represents the next step count to apply. - */ - step_count = vgetq_lane_f32(step_count4, 0); - } - /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ - for(;pos < minsize;pos++) - { - dst[pos] += data[pos]*(gain + step*step_count); - step_count += 1.0f; - } - if(pos == Counter) - gain = TargetGains[c]; - else - gain += step*step_count; - CurrentGains[c] = gain; - - /* Mix until pos is aligned with 4 or the mix is done. */ - minsize = mini(BufferSize, (pos+3)&~3); - for(;pos < minsize;pos++) - dst[pos] += data[pos]*gain; - } - - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - if(LIKELY(BufferSize-pos > 3)) - { - ALsizei todo{(BufferSize-pos) >> 2}; - const float32x4_t gain4 = vdupq_n_f32(gain); - do { - const float32x4_t val4 = vld1q_f32(&data[pos]); - float32x4_t dry4 = vld1q_f32(&dst[pos]); - dry4 = vmlaq_f32(dry4, val4, gain4); - vst1q_f32(&dst[pos], dry4); - pos += 4; - } while(--todo); - } - for(;pos < BufferSize;pos++) - dst[pos] += data[pos]*gain; - } -} - -template<> -void MixRow_(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*data)[BUFFERSIZE], - const ALsizei InChans, const ALsizei InPos, const ALsizei BufferSize) -{ - ASSUME(InChans > 0); - ASSUME(BufferSize > 0); - - for(ALsizei c{0};c < InChans;c++) - { - const ALfloat *RESTRICT src{al::assume_aligned<16>(&data[c][InPos])}; - ALsizei pos{0}; - const ALfloat gain{Gains[c]}; - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - - if(LIKELY(BufferSize > 3)) - { - ALsizei todo{BufferSize >> 2}; - float32x4_t gain4{vdupq_n_f32(gain)}; - do { - const float32x4_t val4 = vld1q_f32(&src[pos]); - float32x4_t dry4 = vld1q_f32(&OutBuffer[pos]); - dry4 = vmlaq_f32(dry4, val4, gain4); - vst1q_f32(&OutBuffer[pos], dry4); - pos += 4; - } while(--todo); - } - for(;pos < BufferSize;pos++) - OutBuffer[pos] += src[pos]*gain; - } -} diff --git a/modules/openal-soft/Alc/mixer/mixer_sse.cpp b/modules/openal-soft/Alc/mixer/mixer_sse.cpp deleted file mode 100644 index 629fa42..0000000 --- a/modules/openal-soft/Alc/mixer/mixer_sse.cpp +++ /dev/null @@ -1,263 +0,0 @@ -#include "config.h" - -#include - -#include - -#include "AL/al.h" -#include "AL/alc.h" -#include "alMain.h" -#include "alu.h" - -#include "alSource.h" -#include "alAuxEffectSlot.h" -#include "defs.h" -#include "hrtfbase.h" - - -template<> -const ALfloat *Resample_(const InterpState *state, const ALfloat *RESTRICT src, - ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen) -{ - const ALfloat *const filter{state->bsinc.filter}; - const __m128 sf4{_mm_set1_ps(state->bsinc.sf)}; - const ALsizei m{state->bsinc.m}; - - ASSUME(m > 0); - ASSUME(dstlen > 0); - ASSUME(increment > 0); - ASSUME(frac >= 0); - - src -= state->bsinc.l; - for(ALsizei i{0};i < dstlen;i++) - { - // Calculate the phase index and factor. -#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS) - const ALsizei pi{frac >> FRAC_PHASE_BITDIFF}; - const ALfloat pf{(frac & ((1<(filter + offset)}; offset += m; - const __m128 *scd{reinterpret_cast(filter + offset)}; offset += m; - const __m128 *phd{reinterpret_cast(filter + offset)}; offset += m; - const __m128 *spd{reinterpret_cast(filter + offset)}; - - // Apply the scale and phase interpolated filter. - __m128 r4{_mm_setzero_ps()}; - { - const ALsizei count{m >> 2}; - const __m128 pf4{_mm_set1_ps(pf)}; - - ASSUME(count > 0); - -#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z)) - for(ALsizei j{0};j < count;j++) - { - /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ - const __m128 f4 = MLA4( - MLA4(fil[j], sf4, scd[j]), - pf4, MLA4(phd[j], sf4, spd[j]) - ); - /* r += f*src */ - r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j*4])); - } -#undef MLA4 - } - r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); - r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - dst[i] = _mm_cvtss_f32(r4); - - frac += increment; - src += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } - return dst; -} - - -static inline void ApplyCoeffs(ALsizei Offset, float2 *RESTRICT Values, const ALsizei IrSize, - const HrirArray &Coeffs, const ALfloat left, const ALfloat right) -{ - const __m128 lrlr{_mm_setr_ps(left, right, left, right)}; - - ASSUME(IrSize >= 2); - - if((Offset&1)) - { - __m128 imp0, imp1; - __m128 coeffs{_mm_load_ps(&Coeffs[0][0])}; - __m128 vals{_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(&Values[0][0]))}; - imp0 = _mm_mul_ps(lrlr, coeffs); - vals = _mm_add_ps(imp0, vals); - _mm_storel_pi(reinterpret_cast<__m64*>(&Values[0][0]), vals); - ALsizei i{1}; - for(;i < IrSize-1;i += 2) - { - coeffs = _mm_load_ps(&Coeffs[i+1][0]); - vals = _mm_load_ps(&Values[i][0]); - imp1 = _mm_mul_ps(lrlr, coeffs); - imp0 = _mm_shuffle_ps(imp0, imp1, _MM_SHUFFLE(1, 0, 3, 2)); - vals = _mm_add_ps(imp0, vals); - _mm_store_ps(&Values[i][0], vals); - imp0 = imp1; - } - vals = _mm_loadl_pi(vals, reinterpret_cast<__m64*>(&Values[i][0])); - imp0 = _mm_movehl_ps(imp0, imp0); - vals = _mm_add_ps(imp0, vals); - _mm_storel_pi(reinterpret_cast<__m64*>(&Values[i][0]), vals); - } - else - { - for(ALsizei i{0};i < IrSize;i += 2) - { - __m128 coeffs{_mm_load_ps(&Coeffs[i][0])}; - __m128 vals{_mm_load_ps(&Values[i][0])}; - vals = _mm_add_ps(vals, _mm_mul_ps(lrlr, coeffs)); - _mm_store_ps(&Values[i][0], vals); - } - } -} - -template<> -void MixHrtf_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data, - float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - MixHrtfParams *hrtfparams, const ALsizei BufferSize) -{ - MixHrtfBase(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, hrtfparams, - BufferSize); -} - -template<> -void MixHrtfBlend_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, - const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize) -{ - MixHrtfBlendBase(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, oldparams, - newparams, BufferSize); -} - -template<> -void MixDirectHrtf_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, - const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State, - const ALsizei NumChans, const ALsizei BufferSize) -{ - MixDirectHrtfBase(LeftOut, RightOut, data, AccumSamples, State, NumChans, - BufferSize); -} - - -template<> -void Mix_(const ALfloat *data, const ALsizei OutChans, ALfloat (*OutBuffer)[BUFFERSIZE], - ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, - const ALsizei BufferSize) -{ - ASSUME(OutChans > 0); - ASSUME(BufferSize > 0); - - const ALfloat delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - for(ALsizei c{0};c < OutChans;c++) - { - ALfloat *RESTRICT dst{al::assume_aligned<16>(&OutBuffer[c][OutPos])}; - ALsizei pos{0}; - ALfloat gain{CurrentGains[c]}; - const ALfloat diff{TargetGains[c] - gain}; - - if(std::fabs(diff) > std::numeric_limits::epsilon()) - { - ALsizei minsize{mini(BufferSize, Counter)}; - const ALfloat step{diff * delta}; - ALfloat step_count{0.0f}; - /* Mix with applying gain steps in aligned multiples of 4. */ - if(LIKELY(minsize > 3)) - { - const __m128 four4{_mm_set1_ps(4.0f)}; - const __m128 step4{_mm_set1_ps(step)}; - const __m128 gain4{_mm_set1_ps(gain)}; - __m128 step_count4{_mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f)}; - ALsizei todo{minsize >> 2}; - do { - const __m128 val4{_mm_load_ps(&data[pos])}; - __m128 dry4{_mm_load_ps(&dst[pos])}; -#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z)) - /* dry += val * (gain + step*step_count) */ - dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4)); -#undef MLA4 - _mm_store_ps(&dst[pos], dry4); - step_count4 = _mm_add_ps(step_count4, four4); - pos += 4; - } while(--todo); - /* NOTE: step_count4 now represents the next four counts after - * the last four mixed samples, so the lowest element - * represents the next step count to apply. - */ - step_count = _mm_cvtss_f32(step_count4); - } - /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ - for(;pos < minsize;pos++) - { - dst[pos] += data[pos]*(gain + step*step_count); - step_count += 1.0f; - } - if(pos == Counter) - gain = TargetGains[c]; - else - gain += step*step_count; - CurrentGains[c] = gain; - - /* Mix until pos is aligned with 4 or the mix is done. */ - minsize = mini(BufferSize, (pos+3)&~3); - for(;pos < minsize;pos++) - dst[pos] += data[pos]*gain; - } - - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - if(LIKELY(BufferSize-pos > 3)) - { - ALsizei todo{(BufferSize-pos) >> 2}; - const __m128 gain4{_mm_set1_ps(gain)}; - do { - const __m128 val4{_mm_load_ps(&data[pos])}; - __m128 dry4{_mm_load_ps(&dst[pos])}; - dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4)); - _mm_store_ps(&dst[pos], dry4); - pos += 4; - } while(--todo); - } - for(;pos < BufferSize;pos++) - dst[pos] += data[pos]*gain; - } -} - -template<> -void MixRow_(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*data)[BUFFERSIZE], - const ALsizei InChans, const ALsizei InPos, const ALsizei BufferSize) -{ - ASSUME(InChans > 0); - ASSUME(BufferSize > 0); - - for(ALsizei c{0};c < InChans;c++) - { - const ALfloat *RESTRICT src{al::assume_aligned<16>(&data[c][InPos])}; - const ALfloat gain{Gains[c]}; - if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - - ALsizei pos{0}; - if(LIKELY(BufferSize > 3)) - { - ALsizei todo{BufferSize >> 2}; - const __m128 gain4 = _mm_set1_ps(gain); - do { - const __m128 val4{_mm_load_ps(&src[pos])}; - __m128 dry4{_mm_load_ps(&OutBuffer[pos])}; - dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4)); - _mm_store_ps(&OutBuffer[pos], dry4); - pos += 4; - } while(--todo); - } - for(;pos < BufferSize;pos++) - OutBuffer[pos] += src[pos]*gain; - } -} diff --git a/modules/openal-soft/Alc/mixer/mixer_sse2.cpp b/modules/openal-soft/Alc/mixer/mixer_sse2.cpp deleted file mode 100644 index 2b594d9..0000000 --- a/modules/openal-soft/Alc/mixer/mixer_sse2.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2014 by Timothy Arceri . - * 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 -#include - -#include "alu.h" -#include "defs.h" - - -template<> -const ALfloat *Resample_(const InterpState* UNUSED(state), - const ALfloat *RESTRICT src, ALsizei frac, ALint increment, - ALfloat *RESTRICT dst, ALsizei dstlen) -{ - const __m128i increment4{_mm_set1_epi32(increment*4)}; - const __m128 fracOne4{_mm_set1_ps(1.0f/FRACTIONONE)}; - const __m128i fracMask4{_mm_set1_epi32(FRACTIONMASK)}; - - ASSUME(frac > 0); - ASSUME(increment > 0); - ASSUME(dstlen >= 0); - - alignas(16) ALsizei pos_[4], frac_[4]; - InitiatePositionArrays(frac, increment, frac_, pos_, 4); - __m128i frac4{_mm_setr_epi32(frac_[0], frac_[1], frac_[2], frac_[3])}; - __m128i pos4{_mm_setr_epi32(pos_[0], pos_[1], pos_[2], pos_[3])}; - - const ALsizei todo{dstlen & ~3}; - for(ALsizei i{0};i < todo;i += 4) - { - const int pos0{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(0, 0, 0, 0)))}; - const int pos1{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(1, 1, 1, 1)))}; - const int pos2{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(2, 2, 2, 2)))}; - const int pos3{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(3, 3, 3, 3)))}; - const __m128 val1{_mm_setr_ps(src[pos0 ], src[pos1 ], src[pos2 ], src[pos3 ])}; - const __m128 val2{_mm_setr_ps(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])}; - - /* val1 + (val2-val1)*mu */ - const __m128 r0{_mm_sub_ps(val2, val1)}; - const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)}; - const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))}; - - _mm_store_ps(&dst[i], out); - - frac4 = _mm_add_epi32(frac4, increment4); - pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, FRACTIONBITS)); - frac4 = _mm_and_si128(frac4, fracMask4); - } - - /* NOTE: These four elements represent the position *after* the last four - * samples, so the lowest element is the next position to resample. - */ - ALsizei pos{_mm_cvtsi128_si32(pos4)}; - frac = _mm_cvtsi128_si32(frac4); - - for(ALsizei i{todo};i < dstlen;++i) - { - dst[i] = lerp(src[pos], src[pos+1], frac * (1.0f/FRACTIONONE)); - - frac += increment; - pos += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; - } - return dst; -} diff --git a/modules/openal-soft/Alc/mixvoice.cpp b/modules/openal-soft/Alc/mixvoice.cpp deleted file mode 100644 index f539f11..0000000 --- a/modules/openal-soft/Alc/mixvoice.cpp +++ /dev/null @@ -1,878 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include -#include -#include -#include -#include - -#include -#include - -#include "AL/al.h" -#include "AL/alc.h" - -#include "alMain.h" -#include "alcontext.h" -#include "alSource.h" -#include "alBuffer.h" -#include "alListener.h" -#include "alAuxEffectSlot.h" -#include "sample_cvt.h" -#include "alu.h" -#include "alconfig.h" -#include "ringbuffer.h" - -#include "cpu_caps.h" -#include "mixer/defs.h" - - -static_assert((INT_MAX>>FRACTIONBITS)/MAX_PITCH > BUFFERSIZE, - "MAX_PITCH and/or BUFFERSIZE are too large for FRACTIONBITS!"); - -/* BSinc24 requires up to 23 extra samples before the current position, and 24 after. */ -static_assert(MAX_RESAMPLE_PADDING >= 24, "MAX_RESAMPLE_PADDING must be at least 24!"); - - -Resampler ResamplerDefault = LinearResampler; - -MixerFunc MixSamples = Mix_; -RowMixerFunc MixRowSamples = MixRow_; -static HrtfMixerFunc MixHrtfSamples = MixHrtf_; -static HrtfMixerBlendFunc MixHrtfBlendSamples = MixHrtfBlend_; - -static MixerFunc SelectMixer() -{ -#ifdef HAVE_NEON - if((CPUCapFlags&CPU_CAP_NEON)) - return Mix_; -#endif -#ifdef HAVE_SSE - if((CPUCapFlags&CPU_CAP_SSE)) - return Mix_; -#endif - return Mix_; -} - -static RowMixerFunc SelectRowMixer() -{ -#ifdef HAVE_NEON - if((CPUCapFlags&CPU_CAP_NEON)) - return MixRow_; -#endif -#ifdef HAVE_SSE - if((CPUCapFlags&CPU_CAP_SSE)) - return MixRow_; -#endif - return MixRow_; -} - -static inline HrtfMixerFunc SelectHrtfMixer() -{ -#ifdef HAVE_NEON - if((CPUCapFlags&CPU_CAP_NEON)) - return MixHrtf_; -#endif -#ifdef HAVE_SSE - if((CPUCapFlags&CPU_CAP_SSE)) - return MixHrtf_; -#endif - return MixHrtf_; -} - -static inline HrtfMixerBlendFunc SelectHrtfBlendMixer() -{ -#ifdef HAVE_NEON - if((CPUCapFlags&CPU_CAP_NEON)) - return MixHrtfBlend_; -#endif -#ifdef HAVE_SSE - if((CPUCapFlags&CPU_CAP_SSE)) - return MixHrtfBlend_; -#endif - return MixHrtfBlend_; -} - -ResamplerFunc SelectResampler(Resampler resampler) -{ - switch(resampler) - { - case PointResampler: - return Resample_; - case LinearResampler: -#ifdef HAVE_NEON - if((CPUCapFlags&CPU_CAP_NEON)) - return Resample_; -#endif -#ifdef HAVE_SSE4_1 - if((CPUCapFlags&CPU_CAP_SSE4_1)) - return Resample_; -#endif -#ifdef HAVE_SSE2 - if((CPUCapFlags&CPU_CAP_SSE2)) - return Resample_; -#endif - return Resample_; - case FIR4Resampler: - return Resample_; - case BSinc12Resampler: - case BSinc24Resampler: -#ifdef HAVE_NEON - if((CPUCapFlags&CPU_CAP_NEON)) - return Resample_; -#endif -#ifdef HAVE_SSE - if((CPUCapFlags&CPU_CAP_SSE)) - return Resample_; -#endif - return Resample_; - } - - return Resample_; -} - - -void aluInitMixer() -{ - const char *str; - - if(ConfigValueStr(nullptr, nullptr, "resampler", &str)) - { - if(strcasecmp(str, "point") == 0 || strcasecmp(str, "none") == 0) - ResamplerDefault = PointResampler; - else if(strcasecmp(str, "linear") == 0) - ResamplerDefault = LinearResampler; - else if(strcasecmp(str, "cubic") == 0) - ResamplerDefault = FIR4Resampler; - else if(strcasecmp(str, "bsinc12") == 0) - ResamplerDefault = BSinc12Resampler; - else if(strcasecmp(str, "bsinc24") == 0) - ResamplerDefault = BSinc24Resampler; - else if(strcasecmp(str, "bsinc") == 0) - { - WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", str); - ResamplerDefault = BSinc12Resampler; - } - else if(strcasecmp(str, "sinc4") == 0 || strcasecmp(str, "sinc8") == 0) - { - WARN("Resampler option \"%s\" is deprecated, using cubic\n", str); - ResamplerDefault = FIR4Resampler; - } - else - { - char *end; - long n = strtol(str, &end, 0); - if(*end == '\0' && (n == PointResampler || n == LinearResampler || n == FIR4Resampler)) - ResamplerDefault = static_cast(n); - else - WARN("Invalid resampler: %s\n", str); - } - } - - MixHrtfBlendSamples = SelectHrtfBlendMixer(); - MixHrtfSamples = SelectHrtfMixer(); - MixSamples = SelectMixer(); - MixRowSamples = SelectRowMixer(); -} - - -namespace { - -void SendSourceStoppedEvent(ALCcontext *context, ALuint id) -{ - ALbitfieldSOFT enabledevt{context->EnabledEvts.load(std::memory_order_acquire)}; - if(!(enabledevt&EventType_SourceStateChange)) return; - - RingBuffer *ring{context->AsyncEvents.get()}; - auto evt_vec = ring->getWriteVector(); - if(evt_vec.first.len < 1) return; - - AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}}; - evt->u.srcstate.id = id; - evt->u.srcstate.state = AL_STOPPED; - - ring->writeAdvance(1); - context->EventSem.post(); -} - - -const ALfloat *DoFilters(BiquadFilter *lpfilter, BiquadFilter *hpfilter, - ALfloat *RESTRICT dst, const ALfloat *RESTRICT src, ALsizei numsamples, int type) -{ - switch(type) - { - case AF_None: - lpfilter->passthru(numsamples); - hpfilter->passthru(numsamples); - break; - - case AF_LowPass: - lpfilter->process(dst, src, numsamples); - hpfilter->passthru(numsamples); - return dst; - case AF_HighPass: - lpfilter->passthru(numsamples); - hpfilter->process(dst, src, numsamples); - return dst; - - case AF_BandPass: - for(ALsizei i{0};i < numsamples;) - { - ALfloat temp[256]; - ALsizei todo = mini(256, numsamples-i); - - lpfilter->process(temp, src+i, todo); - hpfilter->process(dst+i, temp, todo); - i += todo; - } - return dst; - } - return src; -} - - -/* Base template left undefined. Should be marked =delete, but Clang 3.8.1 - * chokes on that given the inline specializations. - */ -template -inline ALfloat LoadSample(typename FmtTypeTraits::Type val); - -template<> inline ALfloat LoadSample(FmtTypeTraits::Type val) -{ return (val-128) * (1.0f/128.0f); } -template<> inline ALfloat LoadSample(FmtTypeTraits::Type val) -{ return val * (1.0f/32768.0f); } -template<> inline ALfloat LoadSample(FmtTypeTraits::Type val) -{ return val; } -template<> inline ALfloat LoadSample(FmtTypeTraits::Type val) -{ return static_cast(val); } -template<> inline ALfloat LoadSample(FmtTypeTraits::Type val) -{ return muLawDecompressionTable[val] * (1.0f/32768.0f); } -template<> inline ALfloat LoadSample(FmtTypeTraits::Type val) -{ return aLawDecompressionTable[val] * (1.0f/32768.0f); } - -template -inline void LoadSampleArray(ALfloat *RESTRICT dst, const void *src, ALint srcstep, - const ptrdiff_t samples) -{ - using SampleType = typename FmtTypeTraits::Type; - - const SampleType *ssrc = static_cast(src); - for(ALsizei i{0};i < samples;i++) - dst[i] += LoadSample(ssrc[i*srcstep]); -} - -void LoadSamples(ALfloat *RESTRICT dst, const ALvoid *RESTRICT src, ALint srcstep, FmtType srctype, - const ptrdiff_t samples) -{ -#define HANDLE_FMT(T) case T: LoadSampleArray(dst, src, srcstep, samples); break - switch(srctype) - { - HANDLE_FMT(FmtUByte); - HANDLE_FMT(FmtShort); - HANDLE_FMT(FmtFloat); - HANDLE_FMT(FmtDouble); - HANDLE_FMT(FmtMulaw); - HANDLE_FMT(FmtAlaw); - } -#undef HANDLE_FMT -} - -ALfloat *LoadBufferStatic(ALbufferlistitem *BufferListItem, ALbufferlistitem *&BufferLoopItem, - const ALsizei NumChannels, const ALsizei SampleSize, const ALsizei chan, ALsizei DataPosInt, - ALfloat *SrcData, const ALfloat *const SrcDataEnd) -{ - /* TODO: For static sources, loop points are taken from the first buffer - * (should be adjusted by any buffer offset, to possibly be added later). - */ - const ALbuffer *Buffer0{BufferListItem->buffers[0]}; - const ALsizei LoopStart{Buffer0->LoopStart}; - const ALsizei LoopEnd{Buffer0->LoopEnd}; - ASSUME(LoopStart >= 0); - ASSUME(LoopEnd > LoopStart); - - /* If current pos is beyond the loop range, do not loop */ - if(!BufferLoopItem || DataPosInt >= LoopEnd) - { - const ptrdiff_t SizeToDo{SrcDataEnd - SrcData}; - ASSUME(SizeToDo > 0); - - BufferLoopItem = nullptr; - - auto load_buffer = [DataPosInt,SrcData,NumChannels,SampleSize,chan,SizeToDo](ptrdiff_t CompLen, const ALbuffer *buffer) -> ptrdiff_t - { - if(DataPosInt >= buffer->SampleLen) - return CompLen; - - /* Load what's left to play from the buffer */ - const ptrdiff_t DataSize{std::min(SizeToDo, buffer->SampleLen-DataPosInt)}; - CompLen = std::max(CompLen, DataSize); - - const ALbyte *Data{buffer->mData.data()}; - Data += (DataPosInt*NumChannels + chan)*SampleSize; - - LoadSamples(SrcData, Data, NumChannels, buffer->mFmtType, DataSize); - return CompLen; - }; - /* It's impossible to have a buffer list item with no entries. */ - ASSUME(BufferListItem->num_buffers > 0); - auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers; - SrcData += std::accumulate(BufferListItem->buffers, buffers_end, ptrdiff_t{0}, - load_buffer); - } - else - { - const ptrdiff_t SizeToDo{std::min(SrcDataEnd-SrcData, LoopEnd-DataPosInt)}; - ASSUME(SizeToDo > 0); - - auto load_buffer = [DataPosInt,SrcData,NumChannels,SampleSize,chan,SizeToDo](ptrdiff_t CompLen, const ALbuffer *buffer) -> ptrdiff_t - { - if(DataPosInt >= buffer->SampleLen) - return CompLen; - - /* Load what's left of this loop iteration */ - const ptrdiff_t DataSize{std::min(SizeToDo, buffer->SampleLen-DataPosInt)}; - CompLen = std::max(CompLen, DataSize); - - const ALbyte *Data{buffer->mData.data()}; - Data += (DataPosInt*NumChannels + chan)*SampleSize; - - LoadSamples(SrcData, Data, NumChannels, buffer->mFmtType, DataSize); - return CompLen; - }; - ASSUME(BufferListItem->num_buffers > 0); - auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers; - SrcData += std::accumulate(BufferListItem->buffers, buffers_end, ptrdiff_t{0}, - load_buffer); - - const auto LoopSize = static_cast(LoopEnd - LoopStart); - while(SrcData != SrcDataEnd) - { - const ptrdiff_t SizeToDo{std::min(SrcDataEnd-SrcData, LoopSize)}; - ASSUME(SizeToDo > 0); - - auto load_buffer_loop = [LoopStart,SrcData,NumChannels,SampleSize,chan,SizeToDo](ptrdiff_t CompLen, const ALbuffer *buffer) -> ptrdiff_t - { - if(LoopStart >= buffer->SampleLen) - return CompLen; - - const ptrdiff_t DataSize{std::min(SizeToDo, - buffer->SampleLen-LoopStart)}; - CompLen = std::max(CompLen, DataSize); - - const ALbyte *Data{buffer->mData.data()}; - Data += (LoopStart*NumChannels + chan)*SampleSize; - - LoadSamples(SrcData, Data, NumChannels, buffer->mFmtType, DataSize); - return CompLen; - }; - SrcData += std::accumulate(BufferListItem->buffers, buffers_end, ptrdiff_t{0}, - load_buffer_loop); - } - } - return SrcData; -} - -ALfloat *LoadBufferQueue(ALbufferlistitem *BufferListItem, ALbufferlistitem *BufferLoopItem, - const ALsizei NumChannels, const ALsizei SampleSize, const ALsizei chan, ALsizei DataPosInt, - ALfloat *SrcData, const ALfloat *const SrcDataEnd) -{ - /* Crawl the buffer queue to fill in the temp buffer */ - while(BufferListItem && SrcData != SrcDataEnd) - { - if(DataPosInt >= BufferListItem->max_samples) - { - DataPosInt -= BufferListItem->max_samples; - BufferListItem = BufferListItem->next.load(std::memory_order_acquire); - if(!BufferListItem) BufferListItem = BufferLoopItem; - continue; - } - - const ptrdiff_t SizeToDo{SrcDataEnd - SrcData}; - ASSUME(SizeToDo > 0); - auto load_buffer = [DataPosInt,SrcData,NumChannels,SampleSize,chan,SizeToDo](ptrdiff_t CompLen, const ALbuffer *buffer) -> ptrdiff_t - { - if(!buffer) return CompLen; - if(DataPosInt >= buffer->SampleLen) - return CompLen; - - const ptrdiff_t DataSize{std::min(SizeToDo, buffer->SampleLen-DataPosInt)}; - CompLen = std::max(CompLen, DataSize); - - const ALbyte *Data{buffer->mData.data()}; - Data += (DataPosInt*NumChannels + chan)*SampleSize; - - LoadSamples(SrcData, Data, NumChannels, buffer->mFmtType, DataSize); - return CompLen; - }; - ASSUME(BufferListItem->num_buffers > 0); - auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers; - SrcData += std::accumulate(BufferListItem->buffers, buffers_end, ptrdiff_t{0u}, - load_buffer); - - if(SrcData == SrcDataEnd) - break; - DataPosInt = 0; - BufferListItem = BufferListItem->next.load(std::memory_order_acquire); - if(!BufferListItem) BufferListItem = BufferLoopItem; - } - - return SrcData; -} - -} // namespace - -void MixVoice(ALvoice *voice, ALvoice::State vstate, const ALuint SourceID, ALCcontext *Context, const ALsizei SamplesToDo) -{ - static constexpr ALfloat SilentTarget[MAX_OUTPUT_CHANNELS]{}; - - ASSUME(SamplesToDo > 0); - - /* Get voice info */ - const bool isstatic{(voice->mFlags&VOICE_IS_STATIC) != 0}; - ALsizei DataPosInt{static_cast(voice->mPosition.load(std::memory_order_relaxed))}; - ALsizei DataPosFrac{voice->mPositionFrac.load(std::memory_order_relaxed)}; - ALbufferlistitem *BufferListItem{voice->mCurrentBuffer.load(std::memory_order_relaxed)}; - ALbufferlistitem *BufferLoopItem{voice->mLoopBuffer.load(std::memory_order_relaxed)}; - const ALsizei NumChannels{voice->mNumChannels}; - const ALsizei SampleSize{voice->mSampleSize}; - const ALint increment{voice->mStep}; - - ASSUME(DataPosInt >= 0); - ASSUME(DataPosFrac >= 0); - ASSUME(NumChannels > 0); - ASSUME(SampleSize > 0); - ASSUME(increment > 0); - - ALCdevice *Device{Context->Device}; - const ALsizei IrSize{Device->mHrtf ? Device->mHrtf->irSize : 0}; - - ASSUME(IrSize >= 0); - - ResamplerFunc Resample{(increment == FRACTIONONE && DataPosFrac == 0) ? - Resample_ : voice->mResampler}; - - ALsizei Counter{(voice->mFlags&VOICE_IS_FADING) ? SamplesToDo : 0}; - if(!Counter) - { - /* No fading, just overwrite the old/current params. */ - for(ALsizei chan{0};chan < NumChannels;chan++) - { - DirectParams &parms = voice->mDirect.Params[chan]; - if(!(voice->mFlags&VOICE_HAS_HRTF)) - std::copy(std::begin(parms.Gains.Target), std::end(parms.Gains.Target), - std::begin(parms.Gains.Current)); - else - parms.Hrtf.Old = parms.Hrtf.Target; - auto set_current = [chan](ALvoice::SendData &send) -> void - { - if(!send.Buffer) - return; - - SendParams &parms = send.Params[chan]; - std::copy(std::begin(parms.Gains.Target), std::end(parms.Gains.Target), - std::begin(parms.Gains.Current)); - }; - std::for_each(voice->mSend.begin(), voice->mSend.end(), set_current); - } - } - else if((voice->mFlags&VOICE_HAS_HRTF)) - { - for(ALsizei chan{0};chan < NumChannels;chan++) - { - DirectParams &parms = voice->mDirect.Params[chan]; - if(!(parms.Hrtf.Old.Gain > GAIN_SILENCE_THRESHOLD)) - { - /* The old HRTF params are silent, so overwrite the old - * coefficients with the new, and reset the old gain to 0. The - * future mix will then fade from silence. - */ - parms.Hrtf.Old = parms.Hrtf.Target; - parms.Hrtf.Old.Gain = 0.0f; - } - } - } - - ALsizei buffers_done{0}; - ALsizei OutPos{0}; - do { - /* Figure out how many buffer samples will be needed */ - ALsizei DstBufferSize{SamplesToDo - OutPos}; - - /* Calculate the last written dst sample pos. */ - int64_t DataSize64{DstBufferSize - 1}; - /* Calculate the last read src sample pos. */ - DataSize64 = (DataSize64*increment + DataPosFrac) >> FRACTIONBITS; - /* +1 to get the src sample count, include padding. */ - DataSize64 += 1 + MAX_RESAMPLE_PADDING*2; - - auto SrcBufferSize = static_cast( - mini64(DataSize64, BUFFERSIZE + MAX_RESAMPLE_PADDING*2 + 1)); - if(SrcBufferSize > BUFFERSIZE + MAX_RESAMPLE_PADDING*2) - { - SrcBufferSize = BUFFERSIZE + MAX_RESAMPLE_PADDING*2; - /* If the source buffer got saturated, we can't fill the desired - * dst size. Figure out how many samples we can actually mix from - * this. - */ - DataSize64 = SrcBufferSize - MAX_RESAMPLE_PADDING*2; - DataSize64 = ((DataSize64<(mini64(DataSize64, DstBufferSize)); - - /* Some mixers like having a multiple of 4, so try to give that - * unless this is the last update. - */ - if(DstBufferSize < SamplesToDo-OutPos) - DstBufferSize &= ~3; - } - - for(ALsizei chan{0};chan < NumChannels;chan++) - { - auto &SrcData = Device->SourceData; - - /* Load the previous samples into the source data first, and clear the rest. */ - auto srciter = std::copy_n(voice->mResampleData[chan].mPrevSamples.begin(), - MAX_RESAMPLE_PADDING, std::begin(SrcData)); - std::fill(srciter, std::end(SrcData), 0.0f); - - auto srcdata_end = std::begin(SrcData) + SrcBufferSize; - if(UNLIKELY(!BufferListItem)) - srciter = std::copy( - voice->mResampleData[chan].mPrevSamples.begin()+MAX_RESAMPLE_PADDING, - voice->mResampleData[chan].mPrevSamples.end(), srciter); - else if(isstatic) - srciter = LoadBufferStatic(BufferListItem, BufferLoopItem, NumChannels, - SampleSize, chan, DataPosInt, srciter, srcdata_end); - else - srciter = LoadBufferQueue(BufferListItem, BufferLoopItem, NumChannels, - SampleSize, chan, DataPosInt, srciter, srcdata_end); - - if(UNLIKELY(srciter != srcdata_end)) - { - /* If the source buffer wasn't filled, copy the last sample for - * the remaining buffer. Ideally it should have ended with - * silence, but if not the gain fading should help avoid clicks - * from sudden amplitude changes. - */ - const ALfloat sample{*(srciter-1)}; - std::fill(srciter, srcdata_end, sample); - } - - /* Store the last source samples used for next time. */ - std::copy_n(&SrcData[(increment*DstBufferSize + DataPosFrac)>>FRACTIONBITS], - voice->mResampleData[chan].mPrevSamples.size(), - voice->mResampleData[chan].mPrevSamples.begin()); - - /* Resample, then apply ambisonic upsampling as needed. */ - const ALfloat *ResampledData{Resample(&voice->mResampleState, - &SrcData[MAX_RESAMPLE_PADDING], DataPosFrac, increment, - Device->ResampledData, DstBufferSize)}; - if((voice->mFlags&VOICE_IS_AMBISONIC)) - { - const ALfloat hfscale{voice->mResampleData[chan].mAmbiScale}; - /* Beware the evil const_cast. It's safe since it's pointing to - * either SrcData or Device->ResampledData (both non-const), - * but the resample method takes its input as const float* and - * may return it without copying to output, making it currently - * unavoidable. - */ - voice->mResampleData[chan].mAmbiSplitter.applyHfScale( - const_cast(ResampledData), hfscale, DstBufferSize); - } - - /* Now filter and mix to the appropriate outputs. */ - { - DirectParams &parms = voice->mDirect.Params[chan]; - const ALfloat *samples{DoFilters(&parms.LowPass, &parms.HighPass, - Device->FilteredData, ResampledData, DstBufferSize, - voice->mDirect.FilterType)}; - - if((voice->mFlags&VOICE_HAS_HRTF)) - { - const int OutLIdx{GetChannelIdxByName(Device->RealOut, FrontLeft)}; - const int OutRIdx{GetChannelIdxByName(Device->RealOut, FrontRight)}; - ASSUME(OutLIdx >= 0 && OutRIdx >= 0); - - auto &HrtfSamples = Device->HrtfSourceData; - auto &AccumSamples = Device->HrtfAccumData; - const ALfloat TargetGain{UNLIKELY(vstate == ALvoice::Stopping) ? 0.0f : - parms.Hrtf.Target.Gain}; - ALsizei fademix{0}; - - /* Copy the HRTF history and new input samples into a temp - * buffer. - */ - auto src_iter = std::copy(parms.Hrtf.State.History.begin(), - parms.Hrtf.State.History.end(), std::begin(HrtfSamples)); - std::copy_n(samples, DstBufferSize, src_iter); - /* Copy the last used samples back into the history buffer - * for later. - */ - std::copy_n(std::begin(HrtfSamples) + DstBufferSize, - parms.Hrtf.State.History.size(), parms.Hrtf.State.History.begin()); - - /* Copy the current filtered values being accumulated into - * the temp buffer. - */ - auto accum_iter = std::copy_n(parms.Hrtf.State.Values.begin(), - parms.Hrtf.State.Values.size(), std::begin(AccumSamples)); - - /* Clear the accumulation buffer that will start getting - * filled in. - */ - std::fill_n(accum_iter, DstBufferSize, float2{}); - - /* If fading, the old gain is not silence, and this is the - * first mixing pass, fade between the IRs. - */ - if(Counter && (parms.Hrtf.Old.Gain > GAIN_SILENCE_THRESHOLD) && OutPos == 0) - { - fademix = mini(DstBufferSize, 128); - - ALfloat gain{TargetGain}; - - /* The new coefficients need to fade in completely - * since they're replacing the old ones. To keep the - * gain fading consistent, interpolate between the old - * and new target gains given how much of the fade time - * this mix handles. - */ - if(LIKELY(Counter > fademix)) - { - const ALfloat a{static_cast(fademix) / - static_cast(Counter)}; - gain = lerp(parms.Hrtf.Old.Gain, TargetGain, a); - } - MixHrtfParams hrtfparams; - hrtfparams.Coeffs = &parms.Hrtf.Target.Coeffs; - hrtfparams.Delay[0] = parms.Hrtf.Target.Delay[0]; - hrtfparams.Delay[1] = parms.Hrtf.Target.Delay[1]; - hrtfparams.Gain = 0.0f; - hrtfparams.GainStep = gain / static_cast(fademix); - - MixHrtfBlendSamples( - voice->mDirect.Buffer[OutLIdx], voice->mDirect.Buffer[OutRIdx], - HrtfSamples, AccumSamples, OutPos, IrSize, &parms.Hrtf.Old, - &hrtfparams, fademix); - /* Update the old parameters with the result. */ - parms.Hrtf.Old = parms.Hrtf.Target; - if(fademix < Counter) - parms.Hrtf.Old.Gain = hrtfparams.Gain; - else - parms.Hrtf.Old.Gain = TargetGain; - } - - if(LIKELY(fademix < DstBufferSize)) - { - const ALsizei todo{DstBufferSize - fademix}; - ALfloat gain{TargetGain}; - - /* Interpolate the target gain if the gain fading lasts - * longer than this mix. - */ - if(Counter > DstBufferSize) - { - const ALfloat a{static_cast(todo) / - static_cast(Counter-fademix)}; - gain = lerp(parms.Hrtf.Old.Gain, TargetGain, a); - } - - MixHrtfParams hrtfparams; - hrtfparams.Coeffs = &parms.Hrtf.Target.Coeffs; - hrtfparams.Delay[0] = parms.Hrtf.Target.Delay[0]; - hrtfparams.Delay[1] = parms.Hrtf.Target.Delay[1]; - hrtfparams.Gain = parms.Hrtf.Old.Gain; - hrtfparams.GainStep = (gain - parms.Hrtf.Old.Gain) / - static_cast(todo); - MixHrtfSamples( - voice->mDirect.Buffer[OutLIdx], voice->mDirect.Buffer[OutRIdx], - HrtfSamples+fademix, AccumSamples+fademix, OutPos+fademix, IrSize, - &hrtfparams, todo); - /* Store the interpolated gain or the final target gain - * depending if the fade is done. - */ - if(DstBufferSize < Counter) - parms.Hrtf.Old.Gain = gain; - else - parms.Hrtf.Old.Gain = TargetGain; - } - - /* Copy the new in-progress accumulation values back for - * the next mix. - */ - std::copy_n(std::begin(AccumSamples) + DstBufferSize, - parms.Hrtf.State.Values.size(), parms.Hrtf.State.Values.begin()); - } - else if((voice->mFlags&VOICE_HAS_NFC)) - { - const ALfloat *TargetGains{UNLIKELY(vstate == ALvoice::Stopping) ? - SilentTarget : parms.Gains.Target}; - - MixSamples(samples, voice->mDirect.ChannelsPerOrder[0], - voice->mDirect.Buffer, parms.Gains.Current, TargetGains, Counter, - OutPos, DstBufferSize); - - ALfloat (&nfcsamples)[BUFFERSIZE] = Device->NfcSampleData; - ALsizei chanoffset{voice->mDirect.ChannelsPerOrder[0]}; - using FilterProc = void (NfcFilter::*)(float*,const float*,int); - auto apply_nfc = [voice,&parms,samples,TargetGains,DstBufferSize,Counter,OutPos,&chanoffset,&nfcsamples](FilterProc process, ALsizei order) -> void - { - if(voice->mDirect.ChannelsPerOrder[order] < 1) - return; - (parms.NFCtrlFilter.*process)(nfcsamples, samples, DstBufferSize); - MixSamples(nfcsamples, voice->mDirect.ChannelsPerOrder[order], - voice->mDirect.Buffer+chanoffset, parms.Gains.Current+chanoffset, - TargetGains+chanoffset, Counter, OutPos, DstBufferSize); - chanoffset += voice->mDirect.ChannelsPerOrder[order]; - }; - apply_nfc(&NfcFilter::process1, 1); - apply_nfc(&NfcFilter::process2, 2); - apply_nfc(&NfcFilter::process3, 3); - } - else - { - const ALfloat *TargetGains{UNLIKELY(vstate == ALvoice::Stopping) ? - SilentTarget : parms.Gains.Target}; - MixSamples(samples, voice->mDirect.Channels, voice->mDirect.Buffer, - parms.Gains.Current, TargetGains, Counter, OutPos, DstBufferSize); - } - } - - ALfloat (&FilterBuf)[BUFFERSIZE] = Device->FilteredData; - auto mix_send = [vstate,Counter,OutPos,DstBufferSize,chan,ResampledData,&FilterBuf](ALvoice::SendData &send) -> void - { - if(!send.Buffer) - return; - - SendParams &parms = send.Params[chan]; - const ALfloat *samples{DoFilters(&parms.LowPass, &parms.HighPass, - FilterBuf, ResampledData, DstBufferSize, send.FilterType)}; - - const ALfloat *TargetGains{UNLIKELY(vstate==ALvoice::Stopping) ? SilentTarget : - parms.Gains.Target}; - MixSamples(samples, send.Channels, send.Buffer, parms.Gains.Current, - TargetGains, Counter, OutPos, DstBufferSize); - }; - std::for_each(voice->mSend.begin(), voice->mSend.end(), mix_send); - } - /* Update positions */ - DataPosFrac += increment*DstBufferSize; - DataPosInt += DataPosFrac>>FRACTIONBITS; - DataPosFrac &= FRACTIONMASK; - - OutPos += DstBufferSize; - Counter = maxi(DstBufferSize, Counter) - DstBufferSize; - - if(UNLIKELY(!BufferListItem)) - { - /* Do nothing extra when there's no buffers. */ - } - else if(isstatic) - { - if(BufferLoopItem) - { - /* Handle looping static source */ - const ALbuffer *Buffer{BufferListItem->buffers[0]}; - const ALsizei LoopStart{Buffer->LoopStart}; - const ALsizei LoopEnd{Buffer->LoopEnd}; - if(DataPosInt >= LoopEnd) - { - assert(LoopEnd > LoopStart); - DataPosInt = ((DataPosInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart; - } - } - else - { - /* Handle non-looping static source */ - if(DataPosInt >= BufferListItem->max_samples) - { - if(LIKELY(vstate == ALvoice::Playing)) - vstate = ALvoice::Stopped; - BufferListItem = nullptr; - break; - } - } - } - else while(1) - { - /* Handle streaming source */ - if(BufferListItem->max_samples > DataPosInt) - break; - - DataPosInt -= BufferListItem->max_samples; - - buffers_done += BufferListItem->num_buffers; - BufferListItem = BufferListItem->next.load(std::memory_order_relaxed); - if(!BufferListItem && !(BufferListItem=BufferLoopItem)) - { - if(LIKELY(vstate == ALvoice::Playing)) - vstate = ALvoice::Stopped; - break; - } - } - } while(OutPos < SamplesToDo); - - voice->mFlags |= VOICE_IS_FADING; - - /* Don't update positions and buffers if we were stopping. */ - if(UNLIKELY(vstate == ALvoice::Stopping)) - { - voice->mPlayState.store(ALvoice::Stopped, std::memory_order_release); - return; - } - - /* Update voice info */ - voice->mPosition.store(DataPosInt, std::memory_order_relaxed); - voice->mPositionFrac.store(DataPosFrac, std::memory_order_relaxed); - voice->mCurrentBuffer.store(BufferListItem, std::memory_order_relaxed); - if(vstate == ALvoice::Stopped) - { - voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); - voice->mSourceID.store(0u, std::memory_order_relaxed); - } - std::atomic_thread_fence(std::memory_order_release); - - /* Send any events now, after the position/buffer info was updated. */ - ALbitfieldSOFT enabledevt{Context->EnabledEvts.load(std::memory_order_acquire)}; - if(buffers_done > 0 && (enabledevt&EventType_BufferCompleted)) - { - RingBuffer *ring{Context->AsyncEvents.get()}; - auto evt_vec = ring->getWriteVector(); - if(evt_vec.first.len > 0) - { - AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_BufferCompleted}}; - evt->u.bufcomp.id = SourceID; - evt->u.bufcomp.count = buffers_done; - ring->writeAdvance(1); - Context->EventSem.post(); - } - } - - if(vstate == ALvoice::Stopped) - { - /* If the voice just ended, set it to Stopping so the next render - * ensures any residual noise fades to 0 amplitude. - */ - voice->mPlayState.store(ALvoice::Stopping, std::memory_order_release); - SendSourceStoppedEvent(Context, SourceID); - } -} diff --git a/modules/openal-soft/Alc/panning.cpp b/modules/openal-soft/Alc/panning.cpp index a209c6c..00bf566 100644 --- a/modules/openal-soft/Alc/panning.cpp +++ b/modules/openal-soft/Alc/panning.cpp @@ -20,38 +20,45 @@ #include "config.h" +#include +#include +#include #include -#include +#include #include -#include -#include - -#include -#include -#include -#include #include +#include +#include +#include +#include +#include -#include "alMain.h" -#include "alAuxEffectSlot.h" -#include "alu.h" -#include "alconfig.h" -#include "ambdec.h" -#include "bformatdec.h" -#include "filters/splitter.h" -#include "uhjfilter.h" -#include "bs2b.h" +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" +#include "al/auxeffectslot.h" +#include "albit.h" +#include "alconfig.h" +#include "alc/context.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "aloptional.h" #include "alspan.h" - - -constexpr std::array AmbiScale::FromN3D; -constexpr std::array AmbiScale::FromSN3D; -constexpr std::array AmbiScale::FromFuMa; -constexpr std::array AmbiIndex::FromFuMa; -constexpr std::array AmbiIndex::FromACN; -constexpr std::array AmbiIndex::From2D; -constexpr std::array AmbiIndex::From3D; +#include "alstring.h" +#include "alu.h" +#include "core/ambdec.h" +#include "core/ambidefs.h" +#include "core/bformatdec.h" +#include "core/bs2b.h" +#include "core/devformat.h" +#include "core/front_stablizer.h" +#include "core/hrtf.h" +#include "core/logging.h" +#include "core/uhjfilter.h" +#include "device.h" +#include "opthelpers.h" namespace { @@ -74,31 +81,13 @@ inline const char *GetLabelFromChannel(Channel channel) case SideLeft: return "side-left"; case SideRight: return "side-right"; - case UpperFrontLeft: return "upper-front-left"; - case UpperFrontRight: return "upper-front-right"; - case UpperBackLeft: return "upper-back-left"; - case UpperBackRight: return "upper-back-right"; - case LowerFrontLeft: return "lower-front-left"; - case LowerFrontRight: return "lower-front-right"; - case LowerBackLeft: return "lower-back-left"; - case LowerBackRight: return "lower-back-right"; - - case Aux0: return "aux-0"; - case Aux1: return "aux-1"; - case Aux2: return "aux-2"; - case Aux3: return "aux-3"; - case Aux4: return "aux-4"; - case Aux5: return "aux-5"; - case Aux6: return "aux-6"; - case Aux7: return "aux-7"; - case Aux8: return "aux-8"; - case Aux9: return "aux-9"; - case Aux10: return "aux-10"; - case Aux11: return "aux-11"; - case Aux12: return "aux-12"; - case Aux13: return "aux-13"; - case Aux14: return "aux-14"; - case Aux15: return "aux-15"; + case 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 MaxChannels: break; } @@ -106,166 +95,158 @@ inline const char *GetLabelFromChannel(Channel channel) } -struct ChannelMap { - Channel ChanName; - ALfloat Config[MAX_AMBI2D_CHANNELS]; -}; +std::unique_ptr CreateStablizer(const size_t outchans, const uint srate) +{ + auto stablizer = FrontStablizer::Create(outchans); + for(auto &buf : stablizer->DelayBuf) + std::fill(buf.begin(), buf.end(), 0.0f); + + /* Initialize band-splitting filter for the mid signal, with a crossover at + * 5khz (could be higher). + */ + stablizer->MidFilter.init(5000.0f / static_cast(srate)); + + return stablizer; +} -bool MakeSpeakerMap(ALCdevice *device, const AmbDecConf *conf, ALsizei (&speakermap)[MAX_OUTPUT_CHANNELS]) +void AllocChannels(ALCdevice *device, const size_t main_chans, const size_t real_chans) { - auto map_spkr = [device](const AmbDecConf::SpeakerConf &speaker) -> ALsizei - { - /* NOTE: AmbDec does not define any standard speaker names, however - * for this to work we have to by able to find the output channel - * the speaker definition corresponds to. Therefore, OpenAL Soft - * requires these channel labels to be recognized: - * - * LF = Front left - * RF = Front right - * LS = Side left - * RS = Side right - * LB = Back left - * RB = Back right - * CE = Front center - * CB = Back center - * - * Additionally, surround51 will acknowledge back speakers for side - * channels, and surround51rear will acknowledge side speakers for - * back channels, to avoid issues with an ambdec expecting 5.1 to - * use the side channels when the device is configured for back, - * and vice-versa. - */ - Channel ch{}; - if(speaker.Name == "LF") - ch = FrontLeft; - else if(speaker.Name == "RF") - ch = FrontRight; - else if(speaker.Name == "CE") - ch = FrontCenter; - else if(speaker.Name == "LS") - { - if(device->FmtChans == DevFmtX51Rear) - ch = BackLeft; - else - ch = SideLeft; - } - else if(speaker.Name == "RS") - { - if(device->FmtChans == DevFmtX51Rear) - ch = BackRight; - else - ch = SideRight; - } - else if(speaker.Name == "LB") - { - if(device->FmtChans == DevFmtX51) - ch = SideLeft; - else - ch = BackLeft; - } - else if(speaker.Name == "RB") - { - if(device->FmtChans == DevFmtX51) - ch = SideRight; - else - ch = BackRight; - } - else if(speaker.Name == "CB") - ch = BackCenter; - else - { - const char *name{speaker.Name.c_str()}; - unsigned int n; - char c; + TRACE("Channel config, Main: %zu, Real: %zu\n", main_chans, real_chans); - if(sscanf(name, "AUX%u%c", &n, &c) == 1 && n < 16) - ch = static_cast(Aux0+n); - else - { - ERR("AmbDec speaker label \"%s\" not recognized\n", name); - return -1; - } - } - const int chidx{GetChannelIdxByName(device->RealOut, ch)}; - if(chidx == -1) - ERR("Failed to lookup AmbDec speaker label %s\n", speaker.Name.c_str()); - return chidx; - }; - std::transform(conf->Speakers.begin(), conf->Speakers.end(), std::begin(speakermap), map_spkr); - /* Return success if no invalid entries are found. */ - auto speakermap_end = std::begin(speakermap) + conf->Speakers.size(); - return std::find(std::begin(speakermap), speakermap_end, -1) == speakermap_end; + /* 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, + num_chans*sizeof(device->MixBuffer[0])); + device->MixBuffer.resize(num_chans); + al::span buffer{device->MixBuffer}; + + device->Dry.Buffer = buffer.first(main_chans); + buffer = buffer.subspan(main_chans); + if(real_chans != 0) + { + device->RealOut.Buffer = buffer.first(real_chans); + buffer = buffer.subspan(real_chans); + } + else + device->RealOut.Buffer = device->Dry.Buffer; } -constexpr ChannelMap MonoCfg[1] = { - { FrontCenter, { 1.0f } }, -}, StereoCfg[2] = { - { FrontLeft, { 5.00000000e-1f, 2.88675135e-1f, 5.52305643e-2f } }, - { FrontRight, { 5.00000000e-1f, -2.88675135e-1f, 5.52305643e-2f } }, -}, QuadCfg[4] = { - { BackLeft, { 3.53553391e-1f, 2.04124145e-1f, -2.04124145e-1f } }, - { FrontLeft, { 3.53553391e-1f, 2.04124145e-1f, 2.04124145e-1f } }, - { FrontRight, { 3.53553391e-1f, -2.04124145e-1f, 2.04124145e-1f } }, - { BackRight, { 3.53553391e-1f, -2.04124145e-1f, -2.04124145e-1f } }, -}, X51SideCfg[4] = { - { SideLeft, { 3.33000782e-1f, 1.89084803e-1f, -2.00042375e-1f, -2.12307769e-2f, -1.14579885e-2f } }, - { FrontLeft, { 1.88542860e-1f, 1.27709292e-1f, 1.66295695e-1f, 7.30571517e-2f, 2.10901184e-2f } }, - { FrontRight, { 1.88542860e-1f, -1.27709292e-1f, 1.66295695e-1f, -7.30571517e-2f, 2.10901184e-2f } }, - { SideRight, { 3.33000782e-1f, -1.89084803e-1f, -2.00042375e-1f, 2.12307769e-2f, -1.14579885e-2f } }, -}, X51RearCfg[4] = { - { BackLeft, { 3.33000782e-1f, 1.89084803e-1f, -2.00042375e-1f, -2.12307769e-2f, -1.14579885e-2f } }, - { FrontLeft, { 1.88542860e-1f, 1.27709292e-1f, 1.66295695e-1f, 7.30571517e-2f, 2.10901184e-2f } }, - { FrontRight, { 1.88542860e-1f, -1.27709292e-1f, 1.66295695e-1f, -7.30571517e-2f, 2.10901184e-2f } }, - { BackRight, { 3.33000782e-1f, -1.89084803e-1f, -2.00042375e-1f, 2.12307769e-2f, -1.14579885e-2f } }, -}, X61Cfg[6] = { - { SideLeft, { 2.04460341e-1f, 2.17177926e-1f, -4.39996780e-2f, -2.60790269e-2f, -6.87239792e-2f } }, - { FrontLeft, { 1.58923161e-1f, 9.21772680e-2f, 1.59658796e-1f, 6.66278083e-2f, 3.84686854e-2f } }, - { FrontRight, { 1.58923161e-1f, -9.21772680e-2f, 1.59658796e-1f, -6.66278083e-2f, 3.84686854e-2f } }, - { SideRight, { 2.04460341e-1f, -2.17177926e-1f, -4.39996780e-2f, 2.60790269e-2f, -6.87239792e-2f } }, - { BackCenter, { 2.50001688e-1f, 0.00000000e+0f, -2.50000094e-1f, 0.00000000e+0f, 6.05133395e-2f } }, -}, X71Cfg[6] = { - { BackLeft, { 2.04124145e-1f, 1.08880247e-1f, -1.88586120e-1f, -1.29099444e-1f, 7.45355993e-2f, 3.73460789e-2f, 0.00000000e+0f } }, - { SideLeft, { 2.04124145e-1f, 2.17760495e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.49071198e-1f, -3.73460789e-2f, 0.00000000e+0f } }, - { FrontLeft, { 2.04124145e-1f, 1.08880247e-1f, 1.88586120e-1f, 1.29099444e-1f, 7.45355993e-2f, 3.73460789e-2f, 0.00000000e+0f } }, - { FrontRight, { 2.04124145e-1f, -1.08880247e-1f, 1.88586120e-1f, -1.29099444e-1f, 7.45355993e-2f, -3.73460789e-2f, 0.00000000e+0f } }, - { SideRight, { 2.04124145e-1f, -2.17760495e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.49071198e-1f, 3.73460789e-2f, 0.00000000e+0f } }, - { BackRight, { 2.04124145e-1f, -1.08880247e-1f, -1.88586120e-1f, 1.29099444e-1f, 7.45355993e-2f, -3.73460789e-2f, 0.00000000e+0f } }, +using ChannelCoeffs = std::array; +enum DecoderMode : bool { + SingleBand = false, + DualBand = true +}; + +template +struct DecoderConfig; + +template +struct DecoderConfig { + uint8_t mOrder{}; + bool mIs3D{}; + std::array mChannels{}; + DevAmbiScaling mScaling{}; + std::array mOrderGain{}; + std::array mCoeffs{}; +}; + +template +struct DecoderConfig { + uint8_t mOrder{}; + bool mIs3D{}; + std::array mChannels{}; + DevAmbiScaling mScaling{}; + std::array mOrderGain{}; + std::array mCoeffs{}; + std::array mOrderGainLF{}; + std::array mCoeffsLF{}; +}; + +template<> +struct DecoderConfig { + uint8_t mOrder{}; + bool mIs3D{}; + al::span mChannels; + DevAmbiScaling mScaling{}; + al::span mOrderGain; + al::span mCoeffs; + al::span mOrderGainLF; + al::span mCoeffsLF; + + template + DecoderConfig& operator=(const DecoderConfig &rhs) noexcept + { + mOrder = rhs.mOrder; + mIs3D = rhs.mIs3D; + mChannels = rhs.mChannels; + mScaling = rhs.mScaling; + mOrderGain = rhs.mOrderGain; + mCoeffs = rhs.mCoeffs; + mOrderGainLF = {}; + mCoeffsLF = {}; + return *this; + } + + template + DecoderConfig& operator=(const DecoderConfig &rhs) noexcept + { + mOrder = rhs.mOrder; + mIs3D = rhs.mIs3D; + mChannels = rhs.mChannels; + mScaling = rhs.mScaling; + mOrderGain = rhs.mOrderGain; + mCoeffs = rhs.mCoeffs; + mOrderGainLF = rhs.mOrderGainLF; + mCoeffsLF = rhs.mCoeffsLF; + return *this; + } }; +using DecoderView = DecoderConfig; -void InitNearFieldCtrl(ALCdevice *device, ALfloat ctrl_dist, ALsizei order, const ALsizei *RESTRICT chans_per_order) + +void InitNearFieldCtrl(ALCdevice *device, float ctrl_dist, uint order, bool is3d) { + static const uint chans_per_order2d[MaxAmbiOrder+1]{ 1, 2, 2, 2 }; + static const uint chans_per_order3d[MaxAmbiOrder+1]{ 1, 3, 5, 7 }; + /* NFC is only used when AvgSpeakerDist is greater than 0. */ - const char *devname{device->DeviceName.c_str()}; - if(!GetConfigValueBool(devname, "decoder", "nfc", 0) || !(ctrl_dist > 0.0f)) + if(!device->getConfigValueBool("decoder", "nfc", 0) || !(ctrl_dist > 0.0f)) return; - device->AvgSpeakerDist = minf(ctrl_dist, 10.0f); + device->AvgSpeakerDist = clampf(ctrl_dist, 0.1f, 10.0f); TRACE("Using near-field reference distance: %.2f meters\n", device->AvgSpeakerDist); - auto iter = std::copy(chans_per_order, chans_per_order+order+1, + const float w1{SpeedOfSoundMetersPerSec / + (device->AvgSpeakerDist * static_cast(device->Frequency))}; + device->mNFCtrlFilter.init(w1); + + auto iter = std::copy_n(is3d ? chans_per_order3d : chans_per_order2d, order+1u, std::begin(device->NumChannelsPerOrder)); - std::fill(iter, std::end(device->NumChannelsPerOrder), 0); + std::fill(iter, std::end(device->NumChannelsPerOrder), 0u); } -void InitDistanceComp(ALCdevice *device, const AmbDecConf *conf, const ALsizei (&speakermap)[MAX_OUTPUT_CHANNELS]) +void InitDistanceComp(ALCdevice *device, const al::span channels, + const al::span dists) { - const ALfloat maxdist{ - std::accumulate(conf->Speakers.begin(), conf->Speakers.end(), float{0.0f}, - std::bind(maxf, _1, std::bind(std::mem_fn(&AmbDecConf::SpeakerConf::Distance), _2)) - ) - }; + const float maxdist{std::accumulate(std::begin(dists), std::end(dists), 0.0f, maxf)}; - const char *devname{device->DeviceName.c_str()}; - if(!GetConfigValueBool(devname, "decoder", "distance-comp", 1) || !(maxdist > 0.0f)) + if(!device->getConfigValueBool("decoder", "distance-comp", 1) || !(maxdist > 0.0f)) return; - auto srate = static_cast(device->Frequency); + const auto distSampleScale = static_cast(device->Frequency) / SpeedOfSoundMetersPerSec; + std::vector ChanDelay; + ChanDelay.reserve(device->RealOut.Buffer.size()); size_t total{0u}; - for(size_t i{0u};i < conf->Speakers.size();i++) + for(size_t chidx{0};chidx < channels.size();++chidx) { - const AmbDecConf::SpeakerConf &speaker = conf->Speakers[i]; - const ALsizei chan{speakermap[i]}; + const Channel ch{channels[chidx]}; + const uint idx{device->RealOut.ChannelIndex[ch]}; + if(idx == INVALID_CHANNEL_INDEX) + continue; + + const float distance{dists[chidx]}; /* Distance compensation only delays in steps of the sample rate. This * is a bit less accurate since the delay time falls to the nearest @@ -273,709 +254,866 @@ void InitDistanceComp(ALCdevice *device, const AmbDecConf *conf, const ALsizei ( * phase offsets. This means at 48khz, for instance, the distance delay * will be in steps of about 7 millimeters. */ - const ALfloat delay{ - std::floor((maxdist - speaker.Distance)/SPEEDOFSOUNDMETRESPERSEC*srate + 0.5f) - }; - if(delay >= static_cast(MAX_DELAY_LENGTH)) - ERR("Delay for speaker \"%s\" exceeds buffer length (%f >= %d)\n", - speaker.Name.c_str(), delay, MAX_DELAY_LENGTH); - - device->ChannelDelay[chan].Length = static_cast(clampf( - delay, 0.0f, static_cast(MAX_DELAY_LENGTH-1) - )); - device->ChannelDelay[chan].Gain = speaker.Distance / maxdist; - TRACE("Channel %u \"%s\" distance compensation: %d samples, %f gain\n", chan, - speaker.Name.c_str(), device->ChannelDelay[chan].Length, - device->ChannelDelay[chan].Gain - ); + float delay{std::floor((maxdist - distance)*distSampleScale + 0.5f)}; + if(delay > float{MAX_DELAY_LENGTH-1}) + { + ERR("Delay for channel %u (%s) exceeds buffer length (%f > %d)\n", idx, + GetLabelFromChannel(ch), delay, MAX_DELAY_LENGTH-1); + delay = float{MAX_DELAY_LENGTH-1}; + } + + ChanDelay.resize(maxz(ChanDelay.size(), idx+1)); + ChanDelay[idx].Length = static_cast(delay); + ChanDelay[idx].Gain = distance / maxdist; + TRACE("Channel %s distance comp: %u samples, %f gain\n", GetLabelFromChannel(ch), + ChanDelay[idx].Length, ChanDelay[idx].Gain); /* Round up to the next 4th sample, so each channel buffer starts * 16-byte aligned. */ - total += RoundUp(device->ChannelDelay[chan].Length, 4); + total += RoundUp(ChanDelay[idx].Length, 4); } if(total > 0) { - device->ChannelDelay.resize(total); - device->ChannelDelay[0].Buffer = device->ChannelDelay.data(); - auto set_bufptr = [](const DistanceComp::DistData &last, const DistanceComp::DistData &cur) -> DistanceComp::DistData + auto chandelays = DistanceComp::Create(total); + + ChanDelay[0].Buffer = chandelays->mSamples.data(); + auto set_bufptr = [](const DistanceComp::ChanData &last, const DistanceComp::ChanData &cur) + -> DistanceComp::ChanData { - DistanceComp::DistData ret{cur}; + DistanceComp::ChanData ret{cur}; ret.Buffer = last.Buffer + RoundUp(last.Length, 4); return ret; }; - std::partial_sum(device->ChannelDelay.begin(), device->ChannelDelay.end(), - device->ChannelDelay.begin(), set_bufptr); + std::partial_sum(ChanDelay.begin(), ChanDelay.end(), chandelays->mChannels.begin(), + set_bufptr); + device->ChannelDelays = std::move(chandelays); } } -auto GetAmbiScales(AmbiNorm scaletype) noexcept -> const std::array& +inline auto& GetAmbiScales(DevAmbiScaling scaletype) noexcept { - if(scaletype == AmbiNorm::FuMa) return AmbiScale::FromFuMa; - if(scaletype == AmbiNorm::SN3D) return AmbiScale::FromSN3D; - return AmbiScale::FromN3D; + if(scaletype == DevAmbiScaling::FuMa) return AmbiScale::FromFuMa(); + if(scaletype == DevAmbiScaling::SN3D) return AmbiScale::FromSN3D(); + return AmbiScale::FromN3D(); } -auto GetAmbiLayout(AmbiLayout layouttype) noexcept -> const std::array& +inline auto& GetAmbiLayout(DevAmbiLayout layouttype) noexcept { - if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa; - return AmbiIndex::FromACN; + if(layouttype == DevAmbiLayout::FuMa) return AmbiIndex::FromFuMa(); + return AmbiIndex::FromACN(); } -void InitPanning(ALCdevice *device) +DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, + DecoderConfig &decoder) { - al::span chanmap; - ALsizei coeffcount{}; - - switch(device->FmtChans) - { - case DevFmtMono: - chanmap = MonoCfg; - coeffcount = 1; - break; - - case DevFmtStereo: - chanmap = StereoCfg; - coeffcount = 3; - break; + DecoderView ret{}; - case DevFmtQuad: - chanmap = QuadCfg; - coeffcount = 3; - break; + decoder.mOrder = (conf->ChanMask > Ambi2OrderMask) ? uint8_t{3} : + (conf->ChanMask > Ambi1OrderMask) ? uint8_t{2} : uint8_t{1}; + decoder.mIs3D = (conf->ChanMask&AmbiPeriphonicMask) != 0; - case DevFmtX51: - chanmap = X51SideCfg; - coeffcount = 5; - break; + switch(conf->CoeffScale) + { + case AmbDecScale::N3D: decoder.mScaling = DevAmbiScaling::N3D; break; + case AmbDecScale::SN3D: decoder.mScaling = DevAmbiScaling::SN3D; break; + case AmbDecScale::FuMa: decoder.mScaling = DevAmbiScaling::FuMa; break; + } - case DevFmtX51Rear: - chanmap = X51RearCfg; - coeffcount = 5; - break; + std::copy_n(std::begin(conf->HFOrderGain), + std::min(al::size(conf->HFOrderGain), al::size(decoder.mOrderGain)), + std::begin(decoder.mOrderGain)); + std::copy_n(std::begin(conf->LFOrderGain), + std::min(al::size(conf->LFOrderGain), al::size(decoder.mOrderGainLF)), + std::begin(decoder.mOrderGainLF)); - case DevFmtX61: - chanmap = X61Cfg; - coeffcount = 5; - break; + std::array idx_map{}; + if(decoder.mIs3D) + { + uint flags{conf->ChanMask}; + auto elem = idx_map.begin(); + while(flags) + { + int acn{al::countr_zero(flags)}; + flags &= ~(1u<(acn); + ++elem; + } + } + else + { + uint flags{conf->ChanMask}; + auto elem = idx_map.begin(); + while(flags) + { + int acn{al::countr_zero(flags)}; + flags &= ~(1u<(al::popcount(conf->ChanMask)); + const auto hfmatrix = conf->HFMatrix; + const auto lfmatrix = conf->LFMatrix; - if(device->FmtChans == DevFmtAmbi3D) + uint chan_count{0}; + using const_speaker_span = al::span; + for(auto &speaker : const_speaker_span{conf->Speakers.get(), conf->NumSpeakers}) { - const char *devname{device->DeviceName.c_str()}; - const std::array &acnmap = GetAmbiLayout(device->mAmbiLayout); - const std::array &n3dscale = GetAmbiScales(device->mAmbiScale); - - /* For DevFmtAmbi3D, the ambisonic order is already set. */ - const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)}; - std::transform(acnmap.begin(), acnmap.begin()+count, std::begin(device->Dry.AmbiMap), - [&n3dscale](const ALsizei &acn) noexcept -> BFChannelConfig - { return BFChannelConfig{1.0f/n3dscale[acn], acn}; } - ); - device->Dry.NumChannels = static_cast(count); - - ALfloat nfc_delay{0.0f}; - if(ConfigValueFloat(devname, "decoder", "nfc-ref-delay", &nfc_delay) && nfc_delay > 0.0f) + /* NOTE: AmbDec does not define any standard speaker names, however + * for this to work we have to by able to find the output channel + * the speaker definition corresponds to. Therefore, OpenAL Soft + * requires these channel labels to be recognized: + * + * LF = Front left + * RF = Front right + * LS = Side left + * RS = Side right + * LB = Back left + * RB = Back right + * CE = Front center + * CB = Back center + * + * Additionally, surround51 will acknowledge back speakers for side + * channels, to avoid issues with an ambdec expecting 5.1 to use the + * back channels. + */ + Channel ch{}; + if(speaker.Name == "LF") + ch = FrontLeft; + else if(speaker.Name == "RF") + ch = FrontRight; + else if(speaker.Name == "CE") + ch = FrontCenter; + else if(speaker.Name == "LS") + ch = SideLeft; + else if(speaker.Name == "RS") + ch = SideRight; + else if(speaker.Name == "LB") + ch = (device->FmtChans == DevFmtX51) ? SideLeft : BackLeft; + else if(speaker.Name == "RB") + ch = (device->FmtChans == DevFmtX51) ? SideRight : BackRight; + else if(speaker.Name == "CB") + ch = BackCenter; + else { - static constexpr ALsizei chans_per_order[MAX_AMBI_ORDER+1]{ 1, 3, 5, 7 }; - nfc_delay = clampf(nfc_delay, 0.001f, 1000.0f); - InitNearFieldCtrl(device, nfc_delay * SPEEDOFSOUNDMETRESPERSEC, - device->mAmbiOrder, chans_per_order); + ERR("AmbDec speaker label \"%s\" not recognized\n", speaker.Name.c_str()); + continue; } - device->RealOut.NumChannels = 0; - } - else - { - ChannelDec chancoeffs[MAX_OUTPUT_CHANNELS]{}; - ALsizei idxmap[MAX_OUTPUT_CHANNELS]{}; - for(size_t i{0u};i < chanmap.size();++i) + decoder.mChannels[chan_count] = ch; + for(size_t src{0};src < num_coeffs;++src) + { + const size_t dst{idx_map[src]}; + decoder.mCoeffs[chan_count][dst] = hfmatrix[chan_count][src]; + } + if(conf->FreqBands > 1) { - const ALint idx{GetChannelIdxByName(device->RealOut, chanmap[i].ChanName)}; - if(idx < 0) + for(size_t src{0};src < num_coeffs;++src) { - ERR("Failed to find %s channel in device\n", - GetLabelFromChannel(chanmap[i].ChanName)); - continue; + const size_t dst{idx_map[src]}; + decoder.mCoeffsLF[chan_count][dst] = lfmatrix[chan_count][src]; } - idxmap[i] = idx; - std::copy_n(chanmap[i].Config, coeffcount, chancoeffs[i]); } + ++chan_count; + } - /* For non-DevFmtAmbi3D, set the ambisonic order given the mixing - * channel count. Built-in speaker decoders are always 2D, so just - * reverse that calculation. - */ - device->mAmbiOrder = (coeffcount-1) / 2; - - std::transform(AmbiIndex::From2D.begin(), AmbiIndex::From2D.begin()+coeffcount, - std::begin(device->Dry.AmbiMap), - [](const ALsizei &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); - device->Dry.NumChannels = coeffcount; - - TRACE("Enabling %s-order%s ambisonic decoder\n", - (coeffcount > 5) ? "third" : - (coeffcount > 3) ? "second" : "first", - "" - ); - device->AmbiDecoder = al::make_unique(coeffcount, - static_cast(chanmap.size()), chancoeffs, idxmap); - - device->RealOut.NumChannels = device->channelsFromFmt(); + if(chan_count > 0) + { + ret.mOrder = decoder.mOrder; + ret.mIs3D = decoder.mIs3D; + ret.mScaling = decoder.mScaling; + ret.mChannels = {decoder.mChannels.data(), chan_count}; + ret.mOrderGain = decoder.mOrderGain; + ret.mCoeffs = {decoder.mCoeffs.data(), chan_count}; + if(conf->FreqBands > 1) + { + ret.mOrderGainLF = decoder.mOrderGainLF; + ret.mCoeffsLF = {decoder.mCoeffsLF.data(), chan_count}; + } } + return ret; } -void InitCustomPanning(ALCdevice *device, bool hqdec, const AmbDecConf *conf, const ALsizei (&speakermap)[MAX_OUTPUT_CHANNELS]) +constexpr DecoderConfig MonoConfig{ + 0, false, {{FrontCenter}}, + DevAmbiScaling::N3D, + {{1.0f}}, + {{ {{1.0f}} }} +}; +constexpr DecoderConfig StereoConfig{ + 1, false, {{FrontLeft, FrontRight}}, + DevAmbiScaling::N3D, + {{1.0f, 1.0f}}, + {{ + {{5.00000000e-1f, 2.88675135e-1f, 5.52305643e-2f}}, + {{5.00000000e-1f, -2.88675135e-1f, 5.52305643e-2f}}, + }} +}; +constexpr DecoderConfig QuadConfig{ + 2, false, {{BackLeft, FrontLeft, FrontRight, BackRight}}, + DevAmbiScaling::N3D, + /*HF*/{{1.15470054e+0f, 1.00000000e+0f, 5.77350269e-1f}}, + {{ + {{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f, -1.29099445e-1f, 0.00000000e+0f}}, + {{2.50000000e-1f, 2.04124145e-1f, 2.04124145e-1f, 1.29099445e-1f, 0.00000000e+0f}}, + {{2.50000000e-1f, -2.04124145e-1f, 2.04124145e-1f, -1.29099445e-1f, 0.00000000e+0f}}, + {{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f, 1.29099445e-1f, 0.00000000e+0f}}, + }}, + /*LF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f, -1.29099445e-1f, 0.00000000e+0f}}, + {{2.50000000e-1f, 2.04124145e-1f, 2.04124145e-1f, 1.29099445e-1f, 0.00000000e+0f}}, + {{2.50000000e-1f, -2.04124145e-1f, 2.04124145e-1f, -1.29099445e-1f, 0.00000000e+0f}}, + {{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f, 1.29099445e-1f, 0.00000000e+0f}}, + }} +}; +constexpr DecoderConfig X51Config{ + 2, false, {{SideLeft, FrontLeft, FrontCenter, FrontRight, SideRight}}, + DevAmbiScaling::FuMa, + /*HF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{5.67316000e-1f, 4.22920000e-1f, -3.15495000e-1f, -6.34490000e-2f, -2.92380000e-2f}}, + {{3.68584000e-1f, 2.72349000e-1f, 3.21616000e-1f, 1.92645000e-1f, 4.82600000e-2f}}, + {{1.83579000e-1f, 0.00000000e+0f, 1.99588000e-1f, 0.00000000e+0f, 9.62820000e-2f}}, + {{3.68584000e-1f, -2.72349000e-1f, 3.21616000e-1f, -1.92645000e-1f, 4.82600000e-2f}}, + {{5.67316000e-1f, -4.22920000e-1f, -3.15495000e-1f, 6.34490000e-2f, -2.92380000e-2f}}, + }}, + /*LF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{4.90109850e-1f, 3.77305010e-1f, -3.73106990e-1f, -1.25914530e-1f, 1.45133000e-2f}}, + {{1.49085730e-1f, 3.03561680e-1f, 1.53290060e-1f, 2.45112480e-1f, -1.50753130e-1f}}, + {{1.37654920e-1f, 0.00000000e+0f, 4.49417940e-1f, 0.00000000e+0f, 2.57844070e-1f}}, + {{1.49085730e-1f, -3.03561680e-1f, 1.53290060e-1f, -2.45112480e-1f, -1.50753130e-1f}}, + {{4.90109850e-1f, -3.77305010e-1f, -3.73106990e-1f, 1.25914530e-1f, 1.45133000e-2f}}, + }} +}; +constexpr DecoderConfig X61Config{ + 2, false, {{SideLeft, FrontLeft, FrontRight, SideRight, BackCenter}}, + DevAmbiScaling::N3D, + {{1.0f, 1.0f, 1.0f}}, + {{ + {{2.04460341e-1f, 2.17177926e-1f, -4.39996780e-2f, -2.60790269e-2f, -6.87239792e-2f}}, + {{1.58923161e-1f, 9.21772680e-2f, 1.59658796e-1f, 6.66278083e-2f, 3.84686854e-2f}}, + {{1.58923161e-1f, -9.21772680e-2f, 1.59658796e-1f, -6.66278083e-2f, 3.84686854e-2f}}, + {{2.04460341e-1f, -2.17177926e-1f, -4.39996780e-2f, 2.60790269e-2f, -6.87239792e-2f}}, + {{2.50001688e-1f, 0.00000000e+0f, -2.50000094e-1f, 0.00000000e+0f, 6.05133395e-2f}}, + }} +}; +constexpr DecoderConfig X71Config{ + 3, false, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight}}, + DevAmbiScaling::N3D, + /*HF*/{{1.22474487e+0f, 1.13151672e+0f, 8.66025404e-1f, 4.68689571e-1f}}, + {{ + {{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f, 7.96819073e-2f, 0.00000000e+0f}}, + {{1.66666667e-1f, 1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f, -7.96819073e-2f, 0.00000000e+0f}}, + {{1.66666667e-1f, 9.62250449e-2f, 1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f, 7.96819073e-2f, 0.00000000e+0f}}, + {{1.66666667e-1f, -9.62250449e-2f, 1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f, -7.96819073e-2f, 0.00000000e+0f}}, + {{1.66666667e-1f, -1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f, 7.96819073e-2f, 0.00000000e+0f}}, + {{1.66666667e-1f, -9.62250449e-2f, -1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f, -7.96819073e-2f, 0.00000000e+0f}}, + }}, + /*LF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f, 7.96819073e-2f, 0.00000000e+0f}}, + {{1.66666667e-1f, 1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f, -7.96819073e-2f, 0.00000000e+0f}}, + {{1.66666667e-1f, 9.62250449e-2f, 1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f, 7.96819073e-2f, 0.00000000e+0f}}, + {{1.66666667e-1f, -9.62250449e-2f, 1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f, -7.96819073e-2f, 0.00000000e+0f}}, + {{1.66666667e-1f, -1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f, 7.96819073e-2f, 0.00000000e+0f}}, + {{1.66666667e-1f, -9.62250449e-2f, -1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f, -7.96819073e-2f, 0.00000000e+0f}}, + }} +}; + +void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=false, + DecoderView decoder={}) { - static constexpr ALsizei chans_per_order2d[MAX_AMBI_ORDER+1] = { 1, 2, 2, 2 }; - static constexpr ALsizei chans_per_order3d[MAX_AMBI_ORDER+1] = { 1, 3, 5, 7 }; + if(!decoder.mOrder) + { + switch(device->FmtChans) + { + case DevFmtMono: decoder = MonoConfig; break; + case DevFmtStereo: decoder = StereoConfig; break; + case DevFmtQuad: decoder = QuadConfig; break; + case DevFmtX51: decoder = X51Config; break; + case DevFmtX61: decoder = X61Config; break; + case DevFmtX71: decoder = X71Config; break; + case DevFmtAmbi3D: + auto&& acnmap = GetAmbiLayout(device->mAmbiLayout); + auto&& n3dscale = GetAmbiScales(device->mAmbiScale); + + /* For DevFmtAmbi3D, the ambisonic order is already set. */ + const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)}; + std::transform(acnmap.begin(), acnmap.begin()+count, std::begin(device->Dry.AmbiMap), + [&n3dscale](const uint8_t &acn) noexcept -> BFChannelConfig + { return BFChannelConfig{1.0f/n3dscale[acn], acn}; }); + AllocChannels(device, count, 0); + + float nfc_delay{device->configValue("decoder", "nfc-ref-delay").value_or(0.0f)}; + if(nfc_delay > 0.0f) + InitNearFieldCtrl(device, nfc_delay * SpeedOfSoundMetersPerSec, device->mAmbiOrder, + true); + return; + } + } - if(!hqdec && conf->FreqBands != 1) - ERR("Basic renderer uses the high-frequency matrix as single-band (xover_freq = %.0fhz)\n", - conf->XOverFreq); + const bool dual_band{hqdec && !decoder.mCoeffsLF.empty()}; + al::vector chancoeffs, chancoeffslf; + for(size_t i{0u};i < decoder.mChannels.size();++i) + { + const uint idx{GetChannelIdxByName(device->RealOut, decoder.mChannels[i])}; + if(idx == INVALID_CHANNEL_INDEX) + { + ERR("Failed to find %s channel in device\n", + GetLabelFromChannel(decoder.mChannels[i])); + continue; + } - ALsizei order{(conf->ChanMask > AMBI_2ORDER_MASK) ? 3 : - (conf->ChanMask > AMBI_1ORDER_MASK) ? 2 : 1}; - device->mAmbiOrder = order; + chancoeffs.resize(maxz(chancoeffs.size(), idx+1u), ChannelDec{}); + al::span coeffs{chancoeffs[idx]}; + size_t ambichan{0}; + for(uint o{0};o < decoder.mOrder+1u;++o) + { + const float order_gain{decoder.mOrderGain[o]}; + const size_t order_max{decoder.mIs3D ? AmbiChannelsFromOrder(o) : + Ambi2DChannelsFromOrder(o)}; + for(;ambichan < order_max;++ambichan) + coeffs[ambichan] = decoder.mCoeffs[i][ambichan] * order_gain; + } + if(!dual_band) + continue; - ALsizei count; - if((conf->ChanMask&AMBI_PERIPHONIC_MASK)) - { - count = static_cast(AmbiChannelsFromOrder(order)); - std::transform(AmbiIndex::From3D.begin(), AmbiIndex::From3D.begin()+count, - std::begin(device->Dry.AmbiMap), - [](const ALsizei &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); + chancoeffslf.resize(maxz(chancoeffslf.size(), idx+1u), ChannelDec{}); + coeffs = chancoeffslf[idx]; + ambichan = 0; + for(uint o{0};o < decoder.mOrder+1u;++o) + { + const float order_gain{decoder.mOrderGainLF[o]}; + const size_t order_max{decoder.mIs3D ? AmbiChannelsFromOrder(o) : + Ambi2DChannelsFromOrder(o)}; + for(;ambichan < order_max;++ambichan) + coeffs[ambichan] = decoder.mCoeffsLF[i][ambichan] * order_gain; + } } - else + + /* For non-DevFmtAmbi3D, set the ambisonic order. */ + device->mAmbiOrder = decoder.mOrder; + + const size_t ambicount{decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder) : + Ambi2DChannelsFromOrder(decoder.mOrder)}; + const al::span acnmap{decoder.mIs3D ? AmbiIndex::FromACN().data() : + AmbiIndex::FromACN2D().data(), ambicount}; + auto&& coeffscale = GetAmbiScales(decoder.mScaling); + std::transform(acnmap.begin(), acnmap.end(), std::begin(device->Dry.AmbiMap), + [&coeffscale](const uint8_t &acn) noexcept + { return BFChannelConfig{1.0f/coeffscale[acn], acn}; }); + AllocChannels(device, ambicount, device->channelsFromFmt()); + + std::unique_ptr stablizer; + if(stablize) { - count = static_cast(Ambi2DChannelsFromOrder(order)); - std::transform(AmbiIndex::From2D.begin(), AmbiIndex::From2D.begin()+count, - std::begin(device->Dry.AmbiMap), - [](const ALsizei &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); + /* Only enable the stablizer if the decoder does not output to the + * front-center channel. + */ + const auto cidx = device->RealOut.ChannelIndex[FrontCenter]; + bool hasfc{false}; + if(cidx < chancoeffs.size()) + { + for(const auto &coeff : chancoeffs[cidx]) + hasfc |= coeff != 0.0f; + } + if(!hasfc && cidx < chancoeffslf.size()) + { + for(const auto &coeff : chancoeffslf[cidx]) + hasfc |= coeff != 0.0f; + } + if(!hasfc) + { + stablizer = CreateStablizer(device->channelsFromFmt(), device->Frequency); + TRACE("Front stablizer enabled\n"); + } } - device->Dry.NumChannels = count; TRACE("Enabling %s-band %s-order%s ambisonic decoder\n", - (!hqdec || conf->FreqBands == 1) ? "single" : "dual", - (conf->ChanMask > AMBI_2ORDER_MASK) ? "third" : - (conf->ChanMask > AMBI_1ORDER_MASK) ? "second" : "first", - (conf->ChanMask&AMBI_PERIPHONIC_MASK) ? " periphonic" : "" - ); - device->AmbiDecoder = al::make_unique(conf, hqdec, count, device->Frequency, - speakermap); - - device->RealOut.NumChannels = device->channelsFromFmt(); - - auto accum_spkr_dist = std::bind(std::plus{}, _1, - std::bind(std::mem_fn(&AmbDecConf::SpeakerConf::Distance), _2)); - const ALfloat avg_dist{ - std::accumulate(conf->Speakers.begin(), conf->Speakers.end(), float{0.0f}, - accum_spkr_dist) / static_cast(conf->Speakers.size()) - }; - InitNearFieldCtrl(device, avg_dist, order, - (conf->ChanMask&AMBI_PERIPHONIC_MASK) ? chans_per_order3d : chans_per_order2d); - - InitDistanceComp(device, conf, speakermap); + !dual_band ? "single" : "dual", + (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)); } void InitHrtfPanning(ALCdevice *device) { - /* NOTE: In degrees, and azimuth goes clockwise. */ - static constexpr AngularPoint AmbiPoints[]{ - { 35.264390f, -45.000000f }, - { 35.264390f, 45.000000f }, - { 35.264390f, 135.000000f }, - { 35.264390f, -135.000000f }, - { -35.264390f, -45.000000f }, - { -35.264390f, 45.000000f }, - { -35.264390f, 135.000000f }, - { -35.264390f, -135.000000f }, - { 0.000000f, -20.905157f }, - { 0.000000f, 20.905157f }, - { 0.000000f, 159.094843f }, - { 0.000000f, -159.094843f }, - { 20.905157f, -90.000000f }, - { -20.905157f, -90.000000f }, - { -20.905157f, 90.000000f }, - { 20.905157f, 90.000000f }, - { 69.094843f, 0.000000f }, - { -69.094843f, 0.000000f }, - { -69.094843f, 180.000000f }, - { 69.094843f, 180.000000f }, + constexpr float Deg180{al::numbers::pi_v}; + constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/}; + constexpr float Deg_45{Deg_90 / 2.0f /* 45 degrees*/}; + constexpr float Deg135{Deg_45 * 3.0f /*135 degrees*/}; + constexpr float Deg_35{6.154797087e-01f /* 35~ 36 degrees*/}; + constexpr float Deg_69{1.205932499e+00f /* 69~ 70 degrees*/}; + constexpr float Deg111{1.935660155e+00f /*110~111 degrees*/}; + constexpr float Deg_21{3.648638281e-01f /* 20~ 21 degrees*/}; + static const AngularPoint AmbiPoints1O[]{ + { EvRadians{ Deg_35}, AzRadians{-Deg_45} }, + { EvRadians{ Deg_35}, AzRadians{-Deg135} }, + { EvRadians{ Deg_35}, AzRadians{ Deg_45} }, + { EvRadians{ Deg_35}, AzRadians{ Deg135} }, + { EvRadians{-Deg_35}, AzRadians{-Deg_45} }, + { EvRadians{-Deg_35}, AzRadians{-Deg135} }, + { EvRadians{-Deg_35}, AzRadians{ Deg_45} }, + { EvRadians{-Deg_35}, AzRadians{ Deg135} }, + }, AmbiPoints2O[]{ + { EvRadians{ 0.0f}, AzRadians{ 0.0f} }, + { EvRadians{ 0.0f}, AzRadians{ Deg180} }, + { EvRadians{ 0.0f}, AzRadians{-Deg_90} }, + { EvRadians{ 0.0f}, AzRadians{ Deg_90} }, + { EvRadians{ Deg_90}, AzRadians{ 0.0f} }, + { EvRadians{-Deg_90}, AzRadians{ 0.0f} }, + { EvRadians{ Deg_35}, AzRadians{-Deg_45} }, + { EvRadians{ Deg_35}, AzRadians{-Deg135} }, + { EvRadians{ Deg_35}, AzRadians{ Deg_45} }, + { EvRadians{ Deg_35}, AzRadians{ Deg135} }, + { EvRadians{-Deg_35}, AzRadians{-Deg_45} }, + { EvRadians{-Deg_35}, AzRadians{-Deg135} }, + { EvRadians{-Deg_35}, AzRadians{ Deg_45} }, + { EvRadians{-Deg_35}, AzRadians{ Deg135} }, + }, AmbiPoints3O[]{ + { EvRadians{ Deg_69}, AzRadians{-Deg_90} }, + { EvRadians{ Deg_69}, AzRadians{ Deg_90} }, + { EvRadians{-Deg_69}, AzRadians{-Deg_90} }, + { EvRadians{-Deg_69}, AzRadians{ Deg_90} }, + { EvRadians{ 0.0f}, AzRadians{-Deg_69} }, + { EvRadians{ 0.0f}, AzRadians{-Deg111} }, + { EvRadians{ 0.0f}, AzRadians{ Deg_69} }, + { EvRadians{ 0.0f}, AzRadians{ Deg111} }, + { EvRadians{ Deg_21}, AzRadians{ 0.0f} }, + { EvRadians{ Deg_21}, AzRadians{ Deg180} }, + { EvRadians{-Deg_21}, AzRadians{ 0.0f} }, + { EvRadians{-Deg_21}, AzRadians{ Deg180} }, + { EvRadians{ Deg_35}, AzRadians{-Deg_45} }, + { EvRadians{ Deg_35}, AzRadians{-Deg135} }, + { EvRadians{ Deg_35}, AzRadians{ Deg_45} }, + { EvRadians{ Deg_35}, AzRadians{ Deg135} }, + { EvRadians{-Deg_35}, AzRadians{-Deg_45} }, + { EvRadians{-Deg_35}, AzRadians{-Deg135} }, + { EvRadians{-Deg_35}, AzRadians{ Deg_45} }, + { EvRadians{-Deg_35}, AzRadians{ Deg135} }, }; - static constexpr ALfloat AmbiMatrix[][MAX_AMBI_CHANNELS]{ - { 5.00000000e-02f, 5.00000000e-02f, 5.00000000e-02f, 5.00000000e-02f, 6.45497224e-02f, 6.45497224e-02f, 0.00000000e+00f, 6.45497224e-02f, 0.00000000e+00f, 1.48264644e-02f, 6.33865691e-02f, 1.01126676e-01f, -7.36485380e-02f, -1.09260065e-02f, 7.08683387e-02f, -1.01622099e-01f }, - { 5.00000000e-02f, -5.00000000e-02f, 5.00000000e-02f, 5.00000000e-02f, -6.45497224e-02f, -6.45497224e-02f, 0.00000000e+00f, 6.45497224e-02f, 0.00000000e+00f, -1.48264644e-02f, -6.33865691e-02f, -1.01126676e-01f, -7.36485380e-02f, -1.09260065e-02f, 7.08683387e-02f, -1.01622099e-01f }, - { 5.00000000e-02f, -5.00000000e-02f, 5.00000000e-02f, -5.00000000e-02f, 6.45497224e-02f, -6.45497224e-02f, 0.00000000e+00f, -6.45497224e-02f, 0.00000000e+00f, -1.48264644e-02f, 6.33865691e-02f, -1.01126676e-01f, -7.36485380e-02f, 1.09260065e-02f, 7.08683387e-02f, 1.01622099e-01f }, - { 5.00000000e-02f, 5.00000000e-02f, 5.00000000e-02f, -5.00000000e-02f, -6.45497224e-02f, 6.45497224e-02f, 0.00000000e+00f, -6.45497224e-02f, 0.00000000e+00f, 1.48264644e-02f, -6.33865691e-02f, 1.01126676e-01f, -7.36485380e-02f, 1.09260065e-02f, 7.08683387e-02f, 1.01622099e-01f }, - { 5.00000000e-02f, 5.00000000e-02f, -5.00000000e-02f, 5.00000000e-02f, 6.45497224e-02f, -6.45497224e-02f, 0.00000000e+00f, -6.45497224e-02f, 0.00000000e+00f, 1.48264644e-02f, -6.33865691e-02f, 1.01126676e-01f, 7.36485380e-02f, -1.09260065e-02f, -7.08683387e-02f, -1.01622099e-01f }, - { 5.00000000e-02f, -5.00000000e-02f, -5.00000000e-02f, 5.00000000e-02f, -6.45497224e-02f, 6.45497224e-02f, 0.00000000e+00f, -6.45497224e-02f, 0.00000000e+00f, -1.48264644e-02f, 6.33865691e-02f, -1.01126676e-01f, 7.36485380e-02f, -1.09260065e-02f, -7.08683387e-02f, -1.01622099e-01f }, - { 5.00000000e-02f, -5.00000000e-02f, -5.00000000e-02f, -5.00000000e-02f, 6.45497224e-02f, 6.45497224e-02f, 0.00000000e+00f, 6.45497224e-02f, 0.00000000e+00f, -1.48264644e-02f, -6.33865691e-02f, -1.01126676e-01f, 7.36485380e-02f, 1.09260065e-02f, -7.08683387e-02f, 1.01622099e-01f }, - { 5.00000000e-02f, 5.00000000e-02f, -5.00000000e-02f, -5.00000000e-02f, -6.45497224e-02f, -6.45497224e-02f, 0.00000000e+00f, 6.45497224e-02f, 0.00000000e+00f, 1.48264644e-02f, 6.33865691e-02f, 1.01126676e-01f, 7.36485380e-02f, 1.09260065e-02f, -7.08683387e-02f, 1.01622099e-01f }, - { 5.00000000e-02f, 3.09016994e-02f, 0.00000000e+00f, 8.09016994e-02f, 6.45497224e-02f, 0.00000000e+00f, -5.59016994e-02f, 0.00000000e+00f, 7.21687836e-02f, 7.76323754e-02f, 0.00000000e+00f, -1.49775925e-01f, 0.00000000e+00f, -2.95083663e-02f, 0.00000000e+00f, 7.76323754e-02f }, - { 5.00000000e-02f, -3.09016994e-02f, 0.00000000e+00f, 8.09016994e-02f, -6.45497224e-02f, 0.00000000e+00f, -5.59016994e-02f, 0.00000000e+00f, 7.21687836e-02f, -7.76323754e-02f, 0.00000000e+00f, 1.49775925e-01f, 0.00000000e+00f, -2.95083663e-02f, 0.00000000e+00f, 7.76323754e-02f }, - { 5.00000000e-02f, -3.09016994e-02f, 0.00000000e+00f, -8.09016994e-02f, 6.45497224e-02f, 0.00000000e+00f, -5.59016994e-02f, 0.00000000e+00f, 7.21687836e-02f, -7.76323754e-02f, 0.00000000e+00f, 1.49775925e-01f, 0.00000000e+00f, 2.95083663e-02f, 0.00000000e+00f, -7.76323754e-02f }, - { 5.00000000e-02f, 3.09016994e-02f, 0.00000000e+00f, -8.09016994e-02f, -6.45497224e-02f, 0.00000000e+00f, -5.59016994e-02f, 0.00000000e+00f, 7.21687836e-02f, 7.76323754e-02f, 0.00000000e+00f, -1.49775925e-01f, 0.00000000e+00f, 2.95083663e-02f, 0.00000000e+00f, -7.76323754e-02f }, - { 5.00000000e-02f, 8.09016994e-02f, 3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, 6.45497224e-02f, -3.45491503e-02f, 0.00000000e+00f, -8.44966837e-02f, -4.79794466e-02f, 0.00000000e+00f, -6.77901327e-02f, 3.03448665e-02f, 0.00000000e+00f, -1.65948192e-01f, 0.00000000e+00f }, - { 5.00000000e-02f, 8.09016994e-02f, -3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, -6.45497224e-02f, -3.45491503e-02f, 0.00000000e+00f, -8.44966837e-02f, -4.79794466e-02f, 0.00000000e+00f, -6.77901327e-02f, -3.03448665e-02f, 0.00000000e+00f, 1.65948192e-01f, 0.00000000e+00f }, - { 5.00000000e-02f, -8.09016994e-02f, -3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, 6.45497224e-02f, -3.45491503e-02f, 0.00000000e+00f, -8.44966837e-02f, 4.79794466e-02f, 0.00000000e+00f, 6.77901327e-02f, -3.03448665e-02f, 0.00000000e+00f, 1.65948192e-01f, 0.00000000e+00f }, - { 5.00000000e-02f, -8.09016994e-02f, 3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, -6.45497224e-02f, -3.45491503e-02f, 0.00000000e+00f, -8.44966837e-02f, 4.79794466e-02f, 0.00000000e+00f, 6.77901327e-02f, 3.03448665e-02f, 0.00000000e+00f, -1.65948192e-01f, 0.00000000e+00f }, - { 5.00000000e-02f, 0.00000000e+00f, 8.09016994e-02f, 3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, 9.04508497e-02f, 6.45497224e-02f, 1.23279000e-02f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f, 7.94438918e-02f, 1.12611206e-01f, -2.42115150e-02f, 1.25611822e-01f }, - { 5.00000000e-02f, 0.00000000e+00f, -8.09016994e-02f, 3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, 9.04508497e-02f, -6.45497224e-02f, 1.23279000e-02f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f, -7.94438918e-02f, 1.12611206e-01f, 2.42115150e-02f, 1.25611822e-01f }, - { 5.00000000e-02f, 0.00000000e+00f, -8.09016994e-02f, -3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, 9.04508497e-02f, 6.45497224e-02f, 1.23279000e-02f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f, -7.94438918e-02f, -1.12611206e-01f, 2.42115150e-02f, -1.25611822e-01f }, - { 5.00000000e-02f, 0.00000000e+00f, 8.09016994e-02f, -3.09016994e-02f, 0.00000000e+00f, 0.00000000e+00f, 9.04508497e-02f, -6.45497224e-02f, 1.23279000e-02f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f, 7.94438918e-02f, -1.12611206e-01f, -2.42115150e-02f, -1.25611822e-01f } + static const float AmbiMatrix1O[][MaxAmbiChannels]{ + { 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f }, + { 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f }, + { 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f }, + { 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f }, + { 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f }, + { 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f }, + { 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f }, + { 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f }, + }, AmbiMatrix2O[][MaxAmbiChannels]{ + { 7.142857143e-02f, 0.000000000e+00f, 0.000000000e+00f, 1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, 1.290994449e-01f, }, + { 7.142857143e-02f, 0.000000000e+00f, 0.000000000e+00f, -1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, 1.290994449e-01f, }, + { 7.142857143e-02f, 1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, -1.290994449e-01f, }, + { 7.142857143e-02f, -1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, -1.290994449e-01f, }, + { 7.142857143e-02f, 0.000000000e+00f, 1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 1.490711985e-01f, 0.000000000e+00f, 0.000000000e+00f, }, + { 7.142857143e-02f, 0.000000000e+00f, -1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 1.490711985e-01f, 0.000000000e+00f, 0.000000000e+00f, }, + { 7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, 9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, }, + { 7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, -9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, }, + { 7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, -9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, }, + { 7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, 9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, }, + { 7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, 9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, }, + { 7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, -9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, }, + { 7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, -9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, }, + { 7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, 9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, }, + }, AmbiMatrix3O[][MaxAmbiChannels]{ + { 5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }, + { 5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }, + { 5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }, + { 5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }, + { 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }, + { 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }, + { 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }, + { 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }, + { 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f, }, + { 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f, }, + { 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f, }, + { 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f, }, + { 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }, + { 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }, + { 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }, + { 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }, + { 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }, + { 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }, + { 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }, + { 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }, }; - static constexpr ALfloat AmbiOrderHFGainFOA[MAX_AMBI_ORDER+1]{ - 3.16227766e+00f, 1.82574186e+00f - }, AmbiOrderHFGainHOA[MAX_AMBI_ORDER+1]{ - 2.35702260e+00f, 1.82574186e+00f, 9.42809042e-01f - /*1.86508671e+00f, 1.60609389e+00f, 1.14205530e+00f, 5.68379553e-01f*/ + static const float AmbiOrderHFGain1O[MaxAmbiOrder+1]{ + /*ENRGY*/ 2.000000000e+00f, 1.154700538e+00f + }, AmbiOrderHFGain2O[MaxAmbiOrder+1]{ + /*ENRGY 2.357022604e+00f, 1.825741858e+00f, 9.428090416e-01f*/ + /*AMP 1.000000000e+00f, 7.745966692e-01f, 4.000000000e-01f*/ + /*RMS*/ 9.128709292e-01f, 7.071067812e-01f, 3.651483717e-01f + }, AmbiOrderHFGain3O[MaxAmbiOrder+1]{ + /*ENRGY 1.865086714e+00f, 1.606093894e+00f, 1.142055301e+00f, 5.683795528e-01f*/ + /*AMP 1.000000000e+00f, 8.611363116e-01f, 6.123336207e-01f, 3.047469850e-01f*/ + /*RMS*/ 8.340921354e-01f, 7.182670250e-01f, 5.107426573e-01f, 2.541870634e-01f }; - static constexpr ALsizei ChansPerOrder[MAX_AMBI_ORDER+1]{ 1, 3, 5, 7 }; - const ALfloat *AmbiOrderHFGain{AmbiOrderHFGainFOA}; - static_assert(al::size(AmbiPoints) == al::size(AmbiMatrix), "Ambisonic HRTF mismatch"); + static_assert(al::size(AmbiPoints1O) == al::size(AmbiMatrix1O), "First-Order Ambisonic HRTF mismatch"); + static_assert(al::size(AmbiPoints2O) == al::size(AmbiMatrix2O), "Second-Order Ambisonic HRTF mismatch"); + static_assert(al::size(AmbiPoints3O) == al::size(AmbiMatrix3O), "Third-Order Ambisonic HRTF mismatch"); + + /* A 700hz crossover frequency provides tighter sound imaging at the sweet + * spot with ambisonic decoding, as the distance between the ears is closer + * to half this frequency wavelength, which is the optimal point where the + * response should change between optimizing phase vs volume. Normally this + * tighter imaging is at the cost of a smaller sweet spot, but since the + * listener is fixed in the center of the HRTF responses for the decoder, + * we don't have to worry about ever being out of the sweet spot. + * + * A better option here may be to have the head radius as part of the HRTF + * data set and calculate the optimal crossover frequency from that. + */ + device->mXOverFreq = 700.0f; /* Don't bother with HOA when using full HRTF rendering. Nothing needs it, * and it eases the CPU/memory load. */ - ALsizei ambi_order{1}; - if(device->mRenderMode != HrtfRender) + device->mRenderMode = RenderMode::Hrtf; + uint ambi_order{1}; + if(auto modeopt = device->configValue(nullptr, "hrtf-mode")) + { + struct HrtfModeEntry { + char name[8]; + RenderMode mode; + uint order; + }; + static const HrtfModeEntry hrtf_modes[]{ + { "full", RenderMode::Hrtf, 1 }, + { "ambi1", RenderMode::Normal, 1 }, + { "ambi2", RenderMode::Normal, 2 }, + { "ambi3", RenderMode::Normal, 3 }, + }; + + const char *mode{modeopt->c_str()}; + if(al::strcasecmp(mode, "basic") == 0) + { + ERR("HRTF mode \"%s\" deprecated, substituting \"%s\"\n", mode, "ambi2"); + mode = "ambi2"; + } + + auto match_entry = [mode](const HrtfModeEntry &entry) -> bool + { return al::strcasecmp(mode, entry.name) == 0; }; + auto iter = std::find_if(std::begin(hrtf_modes), std::end(hrtf_modes), match_entry); + if(iter == std::end(hrtf_modes)) + ERR("Unexpected hrtf-mode: %s\n", mode); + else + { + device->mRenderMode = iter->mode; + ambi_order = iter->order; + } + } + TRACE("%u%s order %sHRTF rendering enabled, using \"%s\"\n", ambi_order, + (((ambi_order%100)/10) == 1) ? "th" : + ((ambi_order%10) == 1) ? "st" : + ((ambi_order%10) == 2) ? "nd" : + ((ambi_order%10) == 3) ? "rd" : "th", + (device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "", + device->mHrtfName.c_str()); + + al::span AmbiPoints{AmbiPoints1O}; + const float (*AmbiMatrix)[MaxAmbiChannels]{AmbiMatrix1O}; + al::span AmbiOrderHFGain{AmbiOrderHFGain1O}; + if(ambi_order >= 3) + { + AmbiPoints = AmbiPoints3O; + AmbiMatrix = AmbiMatrix3O; + AmbiOrderHFGain = AmbiOrderHFGain3O; + } + else if(ambi_order == 2) { - ambi_order = 2; - AmbiOrderHFGain = AmbiOrderHFGainHOA; + AmbiPoints = AmbiPoints2O; + AmbiMatrix = AmbiMatrix2O; + AmbiOrderHFGain = AmbiOrderHFGain2O; } device->mAmbiOrder = ambi_order; const size_t count{AmbiChannelsFromOrder(ambi_order)}; - device->mHrtfState = DirectHrtfState::Create(count); - - std::transform(AmbiIndex::From3D.begin(), AmbiIndex::From3D.begin()+count, + std::transform(AmbiIndex::FromACN().begin(), AmbiIndex::FromACN().begin()+count, std::begin(device->Dry.AmbiMap), - [](const ALsizei &index) noexcept { return BFChannelConfig{1.0f, index}; } + [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; } ); - device->Dry.NumChannels = static_cast(count); - - device->RealOut.NumChannels = device->channelsFromFmt(); + AllocChannels(device, count, device->channelsFromFmt()); - BuildBFormatHrtf(device->mHrtf, device->mHrtfState.get(), device->Dry.NumChannels, AmbiPoints, - AmbiMatrix, al::size(AmbiPoints), AmbiOrderHFGain); + HrtfStore *Hrtf{device->mHrtf.get()}; + auto hrtfstate = DirectHrtfState::Create(count); + hrtfstate->build(Hrtf, device->mIrSize, AmbiPoints, AmbiMatrix, device->mXOverFreq, + AmbiOrderHFGain); + device->mHrtfState = std::move(hrtfstate); - HrtfEntry *Hrtf{device->mHrtf}; - InitNearFieldCtrl(device, Hrtf->field[0].distance, ambi_order, ChansPerOrder); + InitNearFieldCtrl(device, Hrtf->field[0].distance, ambi_order, true); } void InitUhjPanning(ALCdevice *device) { /* UHJ is always 2D first-order. */ - static constexpr size_t count{Ambi2DChannelsFromOrder(1)}; + constexpr size_t count{Ambi2DChannelsFromOrder(1)}; device->mAmbiOrder = 1; - auto acnmap_end = AmbiIndex::FromFuMa.begin() + count; - std::transform(AmbiIndex::FromFuMa.begin(), acnmap_end, std::begin(device->Dry.AmbiMap), - [](const ALsizei &acn) noexcept -> BFChannelConfig - { return BFChannelConfig{1.0f/AmbiScale::FromFuMa[acn], acn}; } - ); - device->Dry.NumChannels = count; - - device->RealOut.NumChannels = device->channelsFromFmt(); + auto acnmap_begin = AmbiIndex::FromFuMa().begin(); + std::transform(acnmap_begin, acnmap_begin + count, std::begin(device->Dry.AmbiMap), + [](const uint8_t &acn) noexcept -> BFChannelConfig + { return BFChannelConfig{1.0f/AmbiScale::FromUHJ()[acn], acn}; }); + AllocChannels(device, count, device->channelsFromFmt()); } } // namespace - -void CalcAmbiCoeffs(const ALfloat y, const ALfloat z, const ALfloat x, const ALfloat spread, - ALfloat (&coeffs)[MAX_AMBI_CHANNELS]) -{ - /* Zeroth-order */ - coeffs[0] = 1.0f; /* ACN 0 = 1 */ - /* First-order */ - coeffs[1] = 1.732050808f * y; /* ACN 1 = sqrt(3) * Y */ - coeffs[2] = 1.732050808f * z; /* ACN 2 = sqrt(3) * Z */ - coeffs[3] = 1.732050808f * x; /* ACN 3 = sqrt(3) * X */ - /* Second-order */ - coeffs[4] = 3.872983346f * x * y; /* ACN 4 = sqrt(15) * X * Y */ - coeffs[5] = 3.872983346f * y * z; /* ACN 5 = sqrt(15) * Y * Z */ - coeffs[6] = 1.118033989f * (z*z*3.0f - 1.0f); /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */ - coeffs[7] = 3.872983346f * x * z; /* ACN 7 = sqrt(15) * X * Z */ - coeffs[8] = 1.936491673f * (x*x - y*y); /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */ - /* Third-order */ - coeffs[9] = 2.091650066f * y * (x*x*3.0f - y*y); /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */ - coeffs[10] = 10.246950766f * z * x * y; /* ACN 10 = sqrt(105) * Z * X * Y */ - coeffs[11] = 1.620185175f * y * (z*z*5.0f - 1.0f); /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */ - coeffs[12] = 1.322875656f * z * (z*z*5.0f - 3.0f); /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */ - coeffs[13] = 1.620185175f * x * (z*z*5.0f - 1.0f); /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */ - coeffs[14] = 5.123475383f * z * (x*x - y*y); /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */ - coeffs[15] = 2.091650066f * x * (x*x - y*y*3.0f); /* ACN 15 = sqrt(35/8) * X * (X*X - 3*Y*Y) */ - /* Fourth-order */ - /* ACN 16 = sqrt(35)*3/2 * X * Y * (X*X - Y*Y) */ - /* ACN 17 = sqrt(35/2)*3/2 * (3*X*X - Y*Y) * Y * Z */ - /* ACN 18 = sqrt(5)*3/2 * X * Y * (7*Z*Z - 1) */ - /* ACN 19 = sqrt(5/2)*3/2 * Y * Z * (7*Z*Z - 3) */ - /* ACN 20 = 3/8 * (35*Z*Z*Z*Z - 30*Z*Z + 3) */ - /* ACN 21 = sqrt(5/2)*3/2 * X * Z * (7*Z*Z - 3) */ - /* ACN 22 = sqrt(5)*3/4 * (X*X - Y*Y) * (7*Z*Z - 1) */ - /* ACN 23 = sqrt(35/2)*3/2 * (X*X - 3*Y*Y) * X * Z */ - /* ACN 24 = sqrt(35)*3/8 * (X*X*X*X - 6*X*X*Y*Y + Y*Y*Y*Y) */ - - if(spread > 0.0f) - { - /* Implement the spread by using a spherical source that subtends the - * angle spread. See: - * http://www.ppsloan.org/publications/StupidSH36.pdf - Appendix A3 - * - * When adjusted for N3D normalization instead of SN3D, these - * calculations are: - * - * ZH0 = -sqrt(pi) * (-1+ca); - * ZH1 = 0.5*sqrt(pi) * sa*sa; - * ZH2 = -0.5*sqrt(pi) * ca*(-1+ca)*(ca+1); - * ZH3 = -0.125*sqrt(pi) * (-1+ca)*(ca+1)*(5*ca*ca - 1); - * ZH4 = -0.125*sqrt(pi) * ca*(-1+ca)*(ca+1)*(7*ca*ca - 3); - * ZH5 = -0.0625*sqrt(pi) * (-1+ca)*(ca+1)*(21*ca*ca*ca*ca - 14*ca*ca + 1); - * - * The gain of the source is compensated for size, so that the - * loudness doesn't depend on the spread. Thus: - * - * ZH0 = 1.0f; - * ZH1 = 0.5f * (ca+1.0f); - * ZH2 = 0.5f * (ca+1.0f)*ca; - * ZH3 = 0.125f * (ca+1.0f)*(5.0f*ca*ca - 1.0f); - * ZH4 = 0.125f * (ca+1.0f)*(7.0f*ca*ca - 3.0f)*ca; - * ZH5 = 0.0625f * (ca+1.0f)*(21.0f*ca*ca*ca*ca - 14.0f*ca*ca + 1.0f); - */ - ALfloat ca = std::cos(spread * 0.5f); - /* Increase the source volume by up to +3dB for a full spread. */ - ALfloat scale = std::sqrt(1.0f + spread/al::MathDefs::Tau()); - - ALfloat ZH0_norm = scale; - ALfloat ZH1_norm = 0.5f * (ca+1.f) * scale; - ALfloat ZH2_norm = 0.5f * (ca+1.f)*ca * scale; - ALfloat ZH3_norm = 0.125f * (ca+1.f)*(5.f*ca*ca-1.f) * scale; - - /* Zeroth-order */ - coeffs[0] *= ZH0_norm; - /* First-order */ - coeffs[1] *= ZH1_norm; - coeffs[2] *= ZH1_norm; - coeffs[3] *= ZH1_norm; - /* Second-order */ - coeffs[4] *= ZH2_norm; - coeffs[5] *= ZH2_norm; - coeffs[6] *= ZH2_norm; - coeffs[7] *= ZH2_norm; - coeffs[8] *= ZH2_norm; - /* Third-order */ - coeffs[9] *= ZH3_norm; - coeffs[10] *= ZH3_norm; - coeffs[11] *= ZH3_norm; - coeffs[12] *= ZH3_norm; - coeffs[13] *= ZH3_norm; - coeffs[14] *= ZH3_norm; - coeffs[15] *= ZH3_norm; - } -} - -void ComputePanGains(const MixParams *mix, const ALfloat *RESTRICT coeffs, ALfloat ingain, ALfloat (&gains)[MAX_OUTPUT_CHANNELS]) -{ - auto ambimap = mix->AmbiMap.cbegin(); - const ALsizei numchans{mix->NumChannels}; - - ASSUME(numchans > 0); - auto iter = std::transform(ambimap, ambimap+numchans, std::begin(gains), - [coeffs,ingain](const BFChannelConfig &chanmap) noexcept -> ALfloat - { - ASSUME(chanmap.Index >= 0); - return chanmap.Scale * coeffs[chanmap.Index] * ingain; - } - ); - std::fill(iter, std::end(gains), 0.0f); -} - - -void aluInitRenderer(ALCdevice *device, ALint hrtf_id, HrtfRequestMode hrtf_appreq, HrtfRequestMode hrtf_userreq) +void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional stereomode) { /* Hold the HRTF the device last used, in case it's used again. */ - HrtfEntry *old_hrtf{device->mHrtf}; + HrtfStorePtr old_hrtf{std::move(device->mHrtf)}; device->mHrtfState = nullptr; device->mHrtf = nullptr; - device->HrtfName.clear(); - device->mRenderMode = NormalRender; - - device->Dry.AmbiMap.fill(BFChannelConfig{}); - device->Dry.NumChannels = 0; - std::fill(std::begin(device->NumChannelsPerOrder), std::end(device->NumChannelsPerOrder), 0); - - device->AvgSpeakerDist = 0.0f; - device->ChannelDelay.clear(); - - device->AmbiDecoder = nullptr; - device->Stablizer = nullptr; + device->mIrSize = 0; + device->mHrtfName.clear(); + device->mXOverFreq = 400.0f; + device->mRenderMode = RenderMode::Normal; if(device->FmtChans != DevFmtStereo) { - if(old_hrtf) - old_hrtf->DecRef(); old_hrtf = nullptr; - if(hrtf_appreq == Hrtf_Enable) - device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; + if(stereomode && *stereomode == StereoEncoding::Hrtf) + device->mHrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; const char *layout{nullptr}; switch(device->FmtChans) { - case DevFmtQuad: layout = "quad"; break; - case DevFmtX51: /* fall-through */ - case DevFmtX51Rear: layout = "surround51"; break; - case DevFmtX61: layout = "surround61"; break; - case DevFmtX71: layout = "surround71"; break; - /* Mono, Stereo, and Ambisonics output don't use custom decoders. */ - case DevFmtMono: - case DevFmtStereo: - case DevFmtAmbi3D: - break; + case DevFmtQuad: layout = "quad"; break; + case DevFmtX51: layout = "surround51"; break; + case DevFmtX61: layout = "surround61"; break; + case DevFmtX71: layout = "surround71"; break; + /* Mono, Stereo, and Ambisonics output don't use custom decoders. */ + case DevFmtMono: + case DevFmtStereo: + case DevFmtAmbi3D: + break; } - const char *devname{device->DeviceName.c_str()}; - ALsizei speakermap[MAX_OUTPUT_CHANNELS]; - AmbDecConf *pconf{nullptr}; - AmbDecConf conf{}; - if(layout) + std::unique_ptr> decoder_store; + DecoderView decoder{}; + float speakerdists[MaxChannels]{}; + auto load_config = [device,&decoder_store,&decoder,&speakerdists](const char *config) { - const char *fname; - if(ConfigValueStr(devname, "decoder", layout, &fname)) + AmbDecConf conf{}; + if(auto err = conf.load(config)) { - if(!conf.load(fname)) - ERR("Failed to load layout file %s\n", fname); - else if(conf.Speakers.size() > MAX_OUTPUT_CHANNELS) - ERR("Unsupported speaker count %zu (max %d)\n", conf.Speakers.size(), - MAX_OUTPUT_CHANNELS); - else if(conf.ChanMask > AMBI_3ORDER_MASK) - ERR("Unsupported channel mask 0x%04x (max 0x%x)\n", conf.ChanMask, - AMBI_3ORDER_MASK); - else if(MakeSpeakerMap(device, &conf, speakermap)) - pconf = &conf; + ERR("Failed to load layout file %s\n", config); + ERR(" %s\n", err->c_str()); } - } + else if(conf.NumSpeakers > MAX_OUTPUT_CHANNELS) + ERR("Unsupported decoder speaker count %zu (max %d)\n", conf.NumSpeakers, + MAX_OUTPUT_CHANNELS); + else if(conf.ChanMask > Ambi3OrderMask) + ERR("Unsupported decoder channel mask 0x%04x (max 0x%x)\n", conf.ChanMask, + Ambi3OrderMask); + else + { + device->mXOverFreq = clampf(conf.XOverFreq, 100.0f, 1000.0f); - if(!pconf) - InitPanning(device); - else + decoder_store = std::make_unique>(); + decoder = MakeDecoderView(device, &conf, *decoder_store); + for(size_t i{0};i < decoder.mChannels.size();++i) + speakerdists[i] = conf.Speakers[i].Distance; + } + }; + if(layout) { - int hqdec{GetConfigValueBool(devname, "decoder", "hq-mode", 0)}; - InitCustomPanning(device, !!hqdec, pconf, speakermap); + if(auto decopt = device->configValue("decoder", layout)) + load_config(decopt->c_str()); } /* Enable the stablizer only for formats that have front-left, front- * right, and front-center outputs. */ - switch(device->FmtChans) + const bool stablize{device->RealOut.ChannelIndex[FrontCenter] != INVALID_CHANNEL_INDEX + && device->RealOut.ChannelIndex[FrontLeft] != INVALID_CHANNEL_INDEX + && device->RealOut.ChannelIndex[FrontRight] != INVALID_CHANNEL_INDEX + && device->getConfigValueBool(nullptr, "front-stablizer", 0) != 0}; + const bool hqdec{device->getConfigValueBool("decoder", "hq-mode", 1) != 0}; + InitPanning(device, hqdec, stablize, decoder); + if(decoder.mOrder > 0) { - case DevFmtX51: - case DevFmtX51Rear: - case DevFmtX61: - case DevFmtX71: - if(GetConfigValueBool(devname, nullptr, "front-stablizer", 0)) + float accum_dist{0.0f}, spkr_count{0.0f}; + for(auto dist : speakerdists) { - auto stablizer = al::make_unique(); - /* Initialize band-splitting filters for the front-left and - * front-right channels, with a crossover at 5khz (could be - * higher). - */ - const ALfloat scale{static_cast(5000.0 / device->Frequency)}; - - stablizer->LFilter.init(scale); - stablizer->RFilter = stablizer->LFilter; - - /* Initialize an all-pass filter for the phase corrector. */ - stablizer->APFilter.init(scale); - - device->Stablizer = std::move(stablizer); - /* NOTE: Don't know why this has to be "copied" into a local - * static constexpr variable to avoid a reference on - * FrontStablizer::DelayLength... - */ - static constexpr size_t StablizerDelay{FrontStablizer::DelayLength}; - device->FixedLatency += nanoseconds{seconds{StablizerDelay}} / device->Frequency; + if(dist > 0.0f) + { + accum_dist += dist; + spkr_count += 1.0f; + } + } + if(spkr_count > 0) + { + InitNearFieldCtrl(device, accum_dist / spkr_count, decoder.mOrder, decoder.mIs3D); + InitDistanceComp(device, decoder.mChannels, speakerdists); } - break; - case DevFmtMono: - case DevFmtStereo: - case DevFmtQuad: - case DevFmtAmbi3D: - break; } - TRACE("Front stablizer %s\n", device->Stablizer ? "enabled" : "disabled"); - - return; - } - - device->AmbiDecoder = nullptr; - - bool headphones{device->IsHeadphones != AL_FALSE}; - if(device->Type != Loopback) - { - const char *mode; - if(ConfigValueStr(device->DeviceName.c_str(), nullptr, "stereo-mode", &mode)) + if(auto *ambidec{device->AmbiDecoder.get()}) { - if(strcasecmp(mode, "headphones") == 0) - headphones = true; - else if(strcasecmp(mode, "speakers") == 0) - headphones = false; - else if(strcasecmp(mode, "auto") != 0) - ERR("Unexpected stereo-mode: %s\n", mode); + device->PostProcess = ambidec->hasStablizer() ? &ALCdevice::ProcessAmbiDecStablized + : &ALCdevice::ProcessAmbiDec; } + return; } - if(hrtf_userreq == Hrtf_Default) - { - bool usehrtf = (headphones && hrtf_appreq != Hrtf_Disable) || - (hrtf_appreq == Hrtf_Enable); - if(!usehrtf) goto no_hrtf; - device->HrtfStatus = ALC_HRTF_ENABLED_SOFT; - if(headphones && hrtf_appreq != Hrtf_Disable) - device->HrtfStatus = ALC_HRTF_HEADPHONES_DETECTED_SOFT; - } - else + /* If HRTF is explicitly requested, or if there's no explicit request and + * the device is headphones, try to enable it. + */ + if(stereomode.value_or(StereoEncoding::Default) == StereoEncoding::Hrtf + || (!stereomode && device->Flags.test(DirectEar))) { - if(hrtf_userreq != Hrtf_Enable) + if(device->mHrtfList.empty()) + device->enumerateHrtfs(); + + if(hrtf_id >= 0 && static_cast(hrtf_id) < device->mHrtfList.size()) { - if(hrtf_appreq == Hrtf_Enable) - device->HrtfStatus = ALC_HRTF_DENIED_SOFT; - goto no_hrtf; + const std::string &hrtfname = device->mHrtfList[static_cast(hrtf_id)]; + if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) + { + device->mHrtf = std::move(hrtf); + device->mHrtfName = hrtfname; + } } - device->HrtfStatus = ALC_HRTF_REQUIRED_SOFT; - } - - if(device->HrtfList.empty()) - device->HrtfList = EnumerateHrtf(device->DeviceName.c_str()); - if(hrtf_id >= 0 && static_cast(hrtf_id) < device->HrtfList.size()) - { - const EnumeratedHrtf &entry = device->HrtfList[hrtf_id]; - HrtfEntry *hrtf{GetLoadedHrtf(entry.hrtf)}; - if(hrtf && hrtf->sampleRate == device->Frequency) + if(!device->mHrtf) { - device->mHrtf = hrtf; - device->HrtfName = entry.name; + for(const auto &hrtfname : device->mHrtfList) + { + if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) + { + device->mHrtf = std::move(hrtf); + device->mHrtfName = hrtfname; + break; + } + } } - else if(hrtf) - hrtf->DecRef(); - } - if(!device->mHrtf) - { - auto find_hrtf = [device](const EnumeratedHrtf &entry) -> bool + if(device->mHrtf) { - HrtfEntry *hrtf{GetLoadedHrtf(entry.hrtf)}; - if(!hrtf) return false; - if(hrtf->sampleRate != device->Frequency) + old_hrtf = nullptr; + + HrtfStore *hrtf{device->mHrtf.get()}; + device->mIrSize = hrtf->irSize; + if(auto hrtfsizeopt = device->configValue(nullptr, "hrtf-size")) { - hrtf->DecRef(); - return false; + if(*hrtfsizeopt > 0 && *hrtfsizeopt < device->mIrSize) + device->mIrSize = maxu(*hrtfsizeopt, MinIrLength); } - device->mHrtf = hrtf; - device->HrtfName = entry.name; - return true; - }; - std::find_if(device->HrtfList.cbegin(), device->HrtfList.cend(), find_hrtf); - } - if(device->mHrtf) - { - if(old_hrtf) - old_hrtf->DecRef(); - old_hrtf = nullptr; - - device->mRenderMode = HrtfRender; - const char *mode; - if(ConfigValueStr(device->DeviceName.c_str(), nullptr, "hrtf-mode", &mode)) - { - if(strcasecmp(mode, "full") == 0) - device->mRenderMode = HrtfRender; - else if(strcasecmp(mode, "basic") == 0) - device->mRenderMode = NormalRender; - else - ERR("Unexpected hrtf-mode: %s\n", mode); + InitHrtfPanning(device); + device->PostProcess = &ALCdevice::ProcessHrtf; + device->mHrtfStatus = ALC_HRTF_ENABLED_SOFT; + return; } - - TRACE("%s HRTF rendering enabled, using \"%s\"\n", - ((device->mRenderMode == HrtfRender) ? "Full" : "Basic"), device->HrtfName.c_str() - ); - InitHrtfPanning(device); - return; } - device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; - -no_hrtf: - if(old_hrtf) - old_hrtf->DecRef(); old_hrtf = nullptr; - device->mRenderMode = StereoPair; - - int bs2blevel{((headphones && hrtf_appreq != Hrtf_Disable) || - (hrtf_appreq == Hrtf_Enable)) ? 5 : 0}; - if(device->Type != Loopback) - ConfigValueInt(device->DeviceName.c_str(), nullptr, "cf_level", &bs2blevel); - if(bs2blevel > 0 && bs2blevel <= 6) + if(stereomode.value_or(StereoEncoding::Default) == StereoEncoding::Uhj) { - device->Bs2b = al::make_unique(); - bs2b_set_params(device->Bs2b.get(), bs2blevel, device->Frequency); - TRACE("BS2B enabled\n"); - InitPanning(device); + device->mUhjEncoder = std::make_unique(); + TRACE("UHJ enabled\n"); + InitUhjPanning(device); + device->PostProcess = &ALCdevice::ProcessUhj; return; } - const char *mode; - if(ConfigValueStr(device->DeviceName.c_str(), nullptr, "stereo-encoding", &mode)) + device->mRenderMode = RenderMode::Pairwise; + if(device->Type != DeviceType::Loopback) { - if(strcasecmp(mode, "uhj") == 0) - device->mRenderMode = NormalRender; - else if(strcasecmp(mode, "panpot") != 0) - ERR("Unexpected stereo-encoding: %s\n", mode); - } - if(device->mRenderMode == NormalRender) - { - device->Uhj_Encoder = al::make_unique(); - TRACE("UHJ enabled\n"); - InitUhjPanning(device); - return; + if(auto cflevopt = device->configValue(nullptr, "cf_level")) + { + if(*cflevopt > 0 && *cflevopt <= 6) + { + device->Bs2b = std::make_unique(); + bs2b_set_params(device->Bs2b.get(), *cflevopt, + static_cast(device->Frequency)); + TRACE("BS2B enabled\n"); + InitPanning(device); + device->PostProcess = &ALCdevice::ProcessBs2b; + return; + } + } } TRACE("Stereo rendering\n"); InitPanning(device); + device->PostProcess = &ALCdevice::ProcessAmbiDec; } -void aluInitEffectPanning(ALeffectslot *slot, ALCdevice *device) +void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context) { + DeviceBase *device{context->mDevice}; const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)}; - slot->MixBuffer.resize(count); - slot->MixBuffer.shrink_to_fit(); - auto acnmap_end = AmbiIndex::From3D.begin() + count; - auto iter = std::transform(AmbiIndex::From3D.begin(), acnmap_end, slot->Wet.AmbiMap.begin(), - [](const ALsizei &acn) noexcept -> BFChannelConfig - { return BFChannelConfig{1.0f, acn}; } - ); + auto wetbuffer_iter = context->mWetBuffers.end(); + if(slot->mWetBuffer) + { + /* If the effect slot already has a wet buffer attached, allocate a new + * one in its place. + */ + wetbuffer_iter = context->mWetBuffers.begin(); + for(;wetbuffer_iter != context->mWetBuffers.end();++wetbuffer_iter) + { + if(wetbuffer_iter->get() == slot->mWetBuffer) + { + slot->mWetBuffer = nullptr; + slot->Wet.Buffer = {}; + + *wetbuffer_iter = WetBufferPtr{new(FamCount(count)) WetBuffer{count}}; + + break; + } + } + } + if(wetbuffer_iter == context->mWetBuffers.end()) + { + /* Otherwise, search for an unused wet buffer. */ + wetbuffer_iter = context->mWetBuffers.begin(); + for(;wetbuffer_iter != context->mWetBuffers.end();++wetbuffer_iter) + { + if(!(*wetbuffer_iter)->mInUse) + break; + } + if(wetbuffer_iter == context->mWetBuffers.end()) + { + /* Otherwise, allocate a new one to use. */ + context->mWetBuffers.emplace_back(WetBufferPtr{new(FamCount(count)) WetBuffer{count}}); + wetbuffer_iter = context->mWetBuffers.end()-1; + } + } + WetBuffer *wetbuffer{slot->mWetBuffer = wetbuffer_iter->get()}; + wetbuffer->mInUse = true; + + auto acnmap_begin = AmbiIndex::FromACN().begin(); + auto iter = std::transform(acnmap_begin, acnmap_begin + count, slot->Wet.AmbiMap.begin(), + [](const uint8_t &acn) noexcept -> BFChannelConfig + { return BFChannelConfig{1.0f, acn}; }); std::fill(iter, slot->Wet.AmbiMap.end(), BFChannelConfig{}); - slot->Wet.Buffer = &reinterpret_cast(slot->MixBuffer[0]); - slot->Wet.NumChannels = static_cast(count); + slot->Wet.Buffer = wetbuffer->mBuffer; } diff --git a/modules/openal-soft/Alc/uhjfilter.cpp b/modules/openal-soft/Alc/uhjfilter.cpp deleted file mode 100644 index 64d5f76..0000000 --- a/modules/openal-soft/Alc/uhjfilter.cpp +++ /dev/null @@ -1,126 +0,0 @@ - -#include "config.h" - -#include "uhjfilter.h" - -#include - -#include "alu.h" - -namespace { - -/* This is the maximum number of samples processed for each inner loop - * iteration. */ -#define MAX_UPDATE_SAMPLES 128 - - -constexpr ALfloat Filter1CoeffSqr[4] = { - 0.479400865589f, 0.876218493539f, 0.976597589508f, 0.997499255936f -}; -constexpr ALfloat Filter2CoeffSqr[4] = { - 0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156685f -}; - -void allpass_process(AllPassState *state, ALfloat *dst, const ALfloat *src, const ALfloat aa, ALsizei todo) -{ - ALfloat z1{state->z[0]}; - ALfloat z2{state->z[1]}; - auto proc_sample = [aa,&z1,&z2](ALfloat input) noexcept -> ALfloat - { - ALfloat output = input*aa + z1; - z1 = z2; z2 = output*aa - input; - return output; - }; - std::transform(src, src+todo, dst, proc_sample); - state->z[0] = z1; - state->z[1] = z2; -} - -} // namespace - - -/* NOTE: There seems to be a bit of an inconsistency in how this encoding is - * supposed to work. Some references, such as - * - * http://members.tripod.com/martin_leese/Ambisonic/UHJ_file_format.html - * - * specify a pre-scaling of sqrt(2) on the W channel input, while other - * references, such as - * - * https://en.wikipedia.org/wiki/Ambisonic_UHJ_format#Encoding.5B1.5D - * and - * https://wiki.xiph.org/Ambisonics#UHJ_format - * - * do not. The sqrt(2) scaling is in line with B-Format decoder coefficients - * which include such a scaling for the W channel input, however the original - * source for this equation is a 1985 paper by Michael Gerzon, which does not - * apparently include the scaling. Applying the extra scaling creates a louder - * result with a narrower stereo image compared to not scaling, and I don't - * know which is the intended result. - */ - -void Uhj2Encoder::encode(ALfloat *LeftOut, ALfloat *RightOut, ALfloat (*InSamples)[BUFFERSIZE], const ALsizei SamplesToDo) -{ - alignas(16) ALfloat D[MAX_UPDATE_SAMPLES], S[MAX_UPDATE_SAMPLES]; - alignas(16) ALfloat temp[MAX_UPDATE_SAMPLES]; - - ASSUME(SamplesToDo > 0); - - for(ALsizei base{0};base < SamplesToDo;) - { - ALsizei todo = mini(SamplesToDo - base, MAX_UPDATE_SAMPLES); - ASSUME(todo > 0); - - /* D = 0.6554516*Y */ - const ALfloat *RESTRICT input{al::assume_aligned<16>(InSamples[2]+base)}; - for(ALsizei i{0};i < todo;i++) - temp[i] = 0.6554516f*input[i]; - allpass_process(&mFilter1_Y[0], temp, temp, Filter1CoeffSqr[0], todo); - allpass_process(&mFilter1_Y[1], temp, temp, Filter1CoeffSqr[1], todo); - allpass_process(&mFilter1_Y[2], temp, temp, Filter1CoeffSqr[2], todo); - allpass_process(&mFilter1_Y[3], temp, temp, Filter1CoeffSqr[3], todo); - /* NOTE: Filter1 requires a 1 sample delay for the final output, so - * take the last processed sample from the previous run as the first - * output sample. - */ - D[0] = mLastY; - for(ALsizei i{1};i < todo;i++) - D[i] = temp[i-1]; - mLastY = temp[todo-1]; - - /* D += j(-0.3420201*W + 0.5098604*X) */ - const ALfloat *RESTRICT input0{al::assume_aligned<16>(InSamples[0]+base)}; - const ALfloat *RESTRICT input1{al::assume_aligned<16>(InSamples[1]+base)}; - for(ALsizei i{0};i < todo;i++) - temp[i] = -0.3420201f*input0[i] + 0.5098604f*input1[i]; - allpass_process(&mFilter2_WX[0], temp, temp, Filter2CoeffSqr[0], todo); - allpass_process(&mFilter2_WX[1], temp, temp, Filter2CoeffSqr[1], todo); - allpass_process(&mFilter2_WX[2], temp, temp, Filter2CoeffSqr[2], todo); - allpass_process(&mFilter2_WX[3], temp, temp, Filter2CoeffSqr[3], todo); - for(ALsizei i{0};i < todo;i++) - D[i] += temp[i]; - - /* S = 0.9396926*W + 0.1855740*X */ - for(ALsizei i{0};i < todo;i++) - temp[i] = 0.9396926f*input0[i] + 0.1855740f*input1[i]; - allpass_process(&mFilter1_WX[0], temp, temp, Filter1CoeffSqr[0], todo); - allpass_process(&mFilter1_WX[1], temp, temp, Filter1CoeffSqr[1], todo); - allpass_process(&mFilter1_WX[2], temp, temp, Filter1CoeffSqr[2], todo); - allpass_process(&mFilter1_WX[3], temp, temp, Filter1CoeffSqr[3], todo); - S[0] = mLastWX; - for(ALsizei i{1};i < todo;i++) - S[i] = temp[i-1]; - mLastWX = temp[todo-1]; - - /* Left = (S + D)/2.0 */ - ALfloat *RESTRICT left = al::assume_aligned<16>(LeftOut+base); - for(ALsizei i{0};i < todo;i++) - left[i] += (S[i] + D[i]) * 0.5f; - /* Right = (S - D)/2.0 */ - ALfloat *RESTRICT right = al::assume_aligned<16>(RightOut+base); - for(ALsizei i{0};i < todo;i++) - right[i] += (S[i] - D[i]) * 0.5f; - - base += todo; - } -} diff --git a/modules/openal-soft/Alc/uhjfilter.h b/modules/openal-soft/Alc/uhjfilter.h deleted file mode 100644 index 1351491..0000000 --- a/modules/openal-soft/Alc/uhjfilter.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef UHJFILTER_H -#define UHJFILTER_H - -#include "AL/al.h" - -#include "alMain.h" -#include "almalloc.h" - - -struct AllPassState { - ALfloat z[2]{0.0f, 0.0f}; -}; - -/* Encoding 2-channel UHJ from B-Format is done as: - * - * S = 0.9396926*W + 0.1855740*X - * D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y - * - * Left = (S + D)/2.0 - * Right = (S - D)/2.0 - * - * where j is a wide-band +90 degree phase shift. - * - * The phase shift is done using a Hilbert transform, described here: - * https://web.archive.org/web/20060708031958/http://www.biochem.oulu.fi/~oniemita/dsp/hilbert/ - * It works using 2 sets of 4 chained filters. The first filter chain produces - * a phase shift of varying magnitude over a wide range of frequencies, while - * the second filter chain produces a phase shift 90 degrees ahead of the - * first over the same range. - * - * Combining these two stages requires the use of three filter chains. S- - * channel output uses a Filter1 chain on the W and X channel mix, while the D- - * channel output uses a Filter1 chain on the Y channel plus a Filter2 chain on - * the W and X channel mix. This results in the W and X input mix on the D- - * channel output having the required +90 degree phase shift relative to the - * other inputs. - */ - -struct Uhj2Encoder { - AllPassState mFilter1_Y[4]; - AllPassState mFilter2_WX[4]; - AllPassState mFilter1_WX[4]; - ALfloat mLastY{0.0f}, mLastWX{0.0f}; - - /* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input - * signal. The input must use FuMa channel ordering and scaling. - */ - void encode(ALfloat *LeftOut, ALfloat *RightOut, ALfloat (*InSamples)[BUFFERSIZE], const ALsizei SamplesToDo); - - DEF_NEWDEL(Uhj2Encoder) -}; - -#endif /* UHJFILTER_H */ diff --git a/modules/openal-soft/BSD-3Clause b/modules/openal-soft/BSD-3Clause new file mode 100644 index 0000000..b1c2dbd --- /dev/null +++ b/modules/openal-soft/BSD-3Clause @@ -0,0 +1,31 @@ +Portions of this software are licensed under the BSD 3-Clause license. + +Copyright (c) 2015, Archontis Politis +Copyright (c) 2019, Anis A. Hireche +Copyright (c) 2019, Christopher Robinson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of Spherical-Harmonic-Transform nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/modules/openal-soft/CMakeLists.txt b/modules/openal-soft/CMakeLists.txt index cb9e934..2edb30a 100644 --- a/modules/openal-soft/CMakeLists.txt +++ b/modules/openal-soft/CMakeLists.txt @@ -1,60 +1,101 @@ # CMake build file list for OpenAL -CMAKE_MINIMUM_REQUIRED(VERSION 3.0.2) - -PROJECT(OpenAL) - -IF(COMMAND CMAKE_POLICY) - CMAKE_POLICY(SET CMP0003 NEW) - CMAKE_POLICY(SET CMP0005 NEW) - IF(POLICY CMP0020) - CMAKE_POLICY(SET CMP0020 NEW) - ENDIF(POLICY CMP0020) - IF(POLICY CMP0042) - CMAKE_POLICY(SET CMP0042 NEW) - ENDIF(POLICY CMP0042) - IF(POLICY CMP0054) - CMAKE_POLICY(SET CMP0054 NEW) - ENDIF(POLICY CMP0054) - IF(POLICY CMP0075) - CMAKE_POLICY(SET CMP0075 NEW) - ENDIF(POLICY CMP0075) -ENDIF(COMMAND CMAKE_POLICY) - -SET(CMAKE_MODULE_PATH "${OpenAL_SOURCE_DIR}/cmake") - -INCLUDE(CheckFunctionExists) -INCLUDE(CheckLibraryExists) -INCLUDE(CheckSharedFunctionExists) -INCLUDE(CheckIncludeFile) -INCLUDE(CheckIncludeFiles) -INCLUDE(CheckSymbolExists) -INCLUDE(CheckCCompilerFlag) -INCLUDE(CheckCXXCompilerFlag) -INCLUDE(CheckCSourceCompiles) -INCLUDE(CheckCXXSourceCompiles) -INCLUDE(CheckTypeSize) +cmake_minimum_required(VERSION 3.0.2) + +if(APPLE) + # The workaround for try_compile failing with code signing + # since cmake-3.18.2, not required + set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES + ${CMAKE_TRY_COMPILE_PLATFORM_VARIABLES} + "CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED" + "CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED") + set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED NO) + set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO) +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + # Fix compile failure with armv7 deployment target >= 11.0, xcode clang + # will report: + # error: invalid iOS deployment version '--target=armv7-apple-ios13.6', + # iOS 10 is the maximum deployment target for 32-bit targets + # If CMAKE_OSX_DEPLOYMENT_TARGET is not defined, cmake will choose latest + # deployment target + if("${CMAKE_OSX_ARCHITECTURES}" MATCHES ".*armv7.*") + if(NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET + OR NOT CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS "11.0") + message(STATUS "Forcing iOS deployment target to 10.0 for armv7") + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.0" CACHE STRING "Minimum OS X deployment version" + FORCE) + endif() + endif() +endif() + +project(OpenAL) + +if(COMMAND CMAKE_POLICY) + cmake_policy(SET CMP0003 NEW) + cmake_policy(SET CMP0005 NEW) + if(POLICY CMP0020) + cmake_policy(SET CMP0020 NEW) + endif(POLICY CMP0020) + if(POLICY CMP0042) + cmake_policy(SET CMP0042 NEW) + endif(POLICY CMP0042) + if(POLICY CMP0054) + cmake_policy(SET CMP0054 NEW) + endif(POLICY CMP0054) + if(POLICY CMP0075) + cmake_policy(SET CMP0075 NEW) + endif(POLICY CMP0075) +endif(COMMAND CMAKE_POLICY) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING + "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." + FORCE) +endif() +if(NOT CMAKE_DEBUG_POSTFIX) + set(CMAKE_DEBUG_POSTFIX "" CACHE STRING + "Library postfix for debug builds. Normally left blank." + FORCE) +endif() + +set(CMAKE_MODULE_PATH "${OpenAL_SOURCE_DIR}/cmake") + +include(CheckFunctionExists) +include(CheckLibraryExists) +include(CheckIncludeFile) +include(CheckIncludeFiles) +include(CheckSymbolExists) +include(CheckCCompilerFlag) +include(CheckCXXCompilerFlag) +include(CheckCSourceCompiles) +include(CheckCXXSourceCompiles) include(CheckStructHasMember) -include(CheckFileOffsetBits) +include(CMakePackageConfigHelpers) include(GNUInstallDirs) -SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS TRUE) +find_package(PkgConfig) + +option(ALSOFT_DLOPEN "Check for the dlopen API for loading optional libs" ON) -OPTION(ALSOFT_DLOPEN "Check for the dlopen API for loading optional libs" ON) +option(ALSOFT_WERROR "Treat compile warnings as errors" OFF) -OPTION(ALSOFT_WERROR "Treat compile warnings as errors" OFF) +option(ALSOFT_UTILS "Build utility programs" ON) +option(ALSOFT_NO_CONFIG_UTIL "Disable building the alsoft-config utility" OFF) -OPTION(ALSOFT_UTILS "Build and install utility programs" ON) -OPTION(ALSOFT_NO_CONFIG_UTIL "Disable building the alsoft-config utility" OFF) +option(ALSOFT_EXAMPLES "Build example programs" ON) -OPTION(ALSOFT_EXAMPLES "Build and install example programs" ON) -OPTION(ALSOFT_TESTS "Build and install test programs" ON) +option(ALSOFT_INSTALL "Install main library" ON) +option(ALSOFT_INSTALL_CONFIG "Install alsoft.conf sample configuration file" ON) +option(ALSOFT_INSTALL_HRTF_DATA "Install HRTF data files" ON) +option(ALSOFT_INSTALL_AMBDEC_PRESETS "Install AmbDec preset files" ON) +option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...)" ON) +option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" ON) +option(ALSOFT_UPDATE_BUILD_VERSION "Update git build version info" ON) -OPTION(ALSOFT_CONFIG "Install alsoft.conf sample configuration file" ON) -OPTION(ALSOFT_HRTF_DEFS "Install HRTF definition files" ON) -OPTION(ALSOFT_AMBDEC_PRESETS "Install AmbDec preset files" ON) -OPTION(ALSOFT_INSTALL "Install headers and libraries" ON) +option(ALSOFT_EAX "Enable legacy EAX extensions" ${WIN32}) if(DEFINED SHARE_INSTALL_DIR) message(WARNING "SHARE_INSTALL_DIR is deprecated. Use the variables provided by the GNUInstallDirs module instead") @@ -64,208 +105,177 @@ endif() if(DEFINED LIB_SUFFIX) message(WARNING "LIB_SUFFIX is deprecated. Use the variables provided by the GNUInstallDirs module instead") endif() +if(DEFINED ALSOFT_CONFIG) + message(WARNING "ALSOFT_CONFIG is deprecated. Use ALSOFT_INSTALL_CONFIG instead") +endif() +if(DEFINED ALSOFT_HRTF_DEFS) + message(WARNING "ALSOFT_HRTF_DEFS is deprecated. Use ALSOFT_INSTALL_HRTF_DATA instead") +endif() +if(DEFINED ALSOFT_AMBDEC_PRESETS) + message(WARNING "ALSOFT_AMBDEC_PRESETS is deprecated. Use ALSOFT_INSTALL_AMBDEC_PRESETS instead") +endif() -SET(CPP_DEFS ) # C pre-process, not C++ -SET(INC_PATHS ) -SET(C_FLAGS ) -SET(LINKER_FLAGS ) -SET(EXTRA_LIBS ) - -IF(WIN32) - SET(CPP_DEFS ${CPP_DEFS} _WIN32 _WIN32_WINNT=0x0502) - - OPTION(ALSOFT_BUILD_ROUTER "Build the router (EXPERIMENTAL; creates OpenAL32.dll and soft_oal.dll)" OFF) +set(CPP_DEFS ) # C pre-processor, not C++ +set(INC_PATHS ) +set(C_FLAGS ) +set(LINKER_FLAGS ) +set(EXTRA_LIBS ) - # This option is mainly for static linking OpenAL Soft into another project - # that already defines the IDs. It is up to that project to ensure all - # required IDs are defined. - OPTION(ALSOFT_NO_UID_DEFS "Do not define GUIDs, IIDs, CLSIDs, or PropertyKeys" OFF) +if(WIN32) + set(CPP_DEFS ${CPP_DEFS} _WIN32 NOMINMAX) + if(MINGW) + set(CPP_DEFS ${CPP_DEFS} __USE_MINGW_ANSI_STDIO) + endif() - IF(MINGW) - OPTION(ALSOFT_BUILD_IMPORT_LIB "Build an import .lib using dlltool (requires sed)" ON) - IF(NOT DLLTOOL) - IF(HOST) - SET(DLLTOOL "${HOST}-dlltool") - ELSE() - SET(DLLTOOL "dlltool") - ENDIF() - ENDIF() - ENDIF() -ENDIF() + option(ALSOFT_BUILD_ROUTER "Build the router (EXPERIMENTAL; creates OpenAL32.dll and soft_oal.dll)" OFF) + if(MINGW) + option(ALSOFT_BUILD_IMPORT_LIB "Build an import .lib using dlltool (requires sed)" ON) + endif() +elseif(APPLE) + option(ALSOFT_OSX_FRAMEWORK "Build as macOS framework" OFF) +endif() # QNX's gcc do not uses /usr/include and /usr/lib pathes by default -IF ("${CMAKE_C_PLATFORM_ID}" STREQUAL "QNX") - SET(INC_PATHS ${INC_PATHS} /usr/include) - SET(LINKER_FLAGS ${LINKER_FLAGS} -L/usr/lib) -ENDIF() - -IF(NOT LIBTYPE) - SET(LIBTYPE SHARED) -ENDIF() - -SET(LIB_MAJOR_VERSION "1") -SET(LIB_MINOR_VERSION "19") -SET(LIB_REVISION "1") -SET(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}") +if("${CMAKE_C_PLATFORM_ID}" STREQUAL "QNX") + set(INC_PATHS ${INC_PATHS} /usr/include) + set(LINKER_FLAGS ${LINKER_FLAGS} -L/usr/lib) +endif() -SET(EXPORT_DECL "") -SET(ALIGN_DECL "") +# When the library is built for static linking, apps should define +# AL_LIBTYPE_STATIC when including the AL headers. +if(NOT LIBTYPE) + set(LIBTYPE SHARED) +endif() +set(LIB_MAJOR_VERSION "1") +set(LIB_MINOR_VERSION "22") +set(LIB_REVISION "0") +set(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}") +set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0) -CHECK_TYPE_SIZE("long" SIZEOF_LONG) -CHECK_TYPE_SIZE("long long" SIZEOF_LONG_LONG) +set(EXPORT_DECL "") -CHECK_C_COMPILER_FLAG(-std=c11 HAVE_STD_C11) -IF(HAVE_STD_C11) - SET(CMAKE_C_FLAGS "-std=c11 ${CMAKE_C_FLAGS}") -ELSE() - CHECK_C_COMPILER_FLAG(-std=c99 HAVE_STD_C99) - IF(HAVE_STD_C99) - SET(CMAKE_C_FLAGS "-std=c99 ${CMAKE_C_FLAGS}") - ENDIF() -ENDIF() +# Require C++14 +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) -CHECK_CXX_COMPILER_FLAG(-std=c++11 HAVE_STD_CXX11) -IF(HAVE_STD_CXX11) - SET(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}") -ENDIF() +# Prefer C11, but support C99 and C90 too. +set(CMAKE_C_STANDARD 11) if(NOT WIN32) # Check if _POSIX_C_SOURCE and _XOPEN_SOURCE needs to be set for POSIX functions - CHECK_SYMBOL_EXISTS(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN_DEFAULT) - IF(NOT HAVE_POSIX_MEMALIGN_DEFAULT) - SET(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=600") - CHECK_SYMBOL_EXISTS(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN_POSIX) - IF(NOT HAVE_POSIX_MEMALIGN_POSIX) - SET(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS}) - ELSE() - SET(CPP_DEFS ${CPP_DEFS} _POSIX_C_SOURCE=200112L _XOPEN_SOURCE=600) - ENDIF() - ENDIF() - UNSET(OLD_REQUIRED_FLAGS) -ENDIF() - -# Set defines for large file support. Don't set this for Android targets. See: -# https://android-developers.googleblog.com/2017/09/introducing-android-native-development.html -IF(NOT ANDROID) - CHECK_FILE_OFFSET_BITS() - IF(_FILE_OFFSET_BITS) - SET(CPP_DEFS ${CPP_DEFS} "_FILE_OFFSET_BITS=${_FILE_OFFSET_BITS}") - SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -D_FILE_OFFSET_BITS=${_FILE_OFFSET_BITS}") - ENDIF() - SET(CPP_DEFS ${CPP_DEFS} _LARGEFILE_SOURCE _LARGE_FILES) - SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -D_LARGEFILE_SOURCE -D_LARGE_FILES") -ENDIF() + check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN_DEFAULT) + if(NOT HAVE_POSIX_MEMALIGN_DEFAULT) + set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=600") + check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN_POSIX) + if(NOT HAVE_POSIX_MEMALIGN_POSIX) + set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS}) + else() + set(CPP_DEFS ${CPP_DEFS} _POSIX_C_SOURCE=200112L _XOPEN_SOURCE=600) + endif() + endif() + unset(OLD_REQUIRED_FLAGS) +endif() # C99 has restrict, but C++ does not, so we can only utilize __restrict. -SET(RESTRICT_DECL ) -CHECK_C_SOURCE_COMPILES("int *__restrict foo; - int main() {return 0;}" HAVE___RESTRICT) -IF(HAVE___RESTRICT) -# SET(RESTRICT_DECL "__restrict") -ENDIF() - -# Some systems may need libatomic for C11 atomic functions to work -SET(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) -SET(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES} atomic) -CHECK_C_SOURCE_COMPILES("#include -int _Atomic foo = ATOMIC_VAR_INIT(0); -int main() -{ - return atomic_fetch_add(&foo, 2); -}" +check_cxx_source_compiles("int *__restrict foo; +int main() { return 0; }" HAVE___RESTRICT) +if(HAVE___RESTRICT) + set(CPP_DEFS ${CPP_DEFS} RESTRICT=__restrict) +else() + set(CPP_DEFS ${CPP_DEFS} "RESTRICT=") +endif() + +# Some systems may need libatomic for atomic functions to work +set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) +set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES} atomic) +check_cxx_source_compiles("#include +std::atomic foo{0}; +int main() { return foo.fetch_add(2); }" HAVE_LIBATOMIC) -IF(NOT HAVE_LIBATOMIC) - SET(CMAKE_REQUIRED_LIBRARIES "${OLD_REQUIRED_LIBRARIES}") -ELSE() - SET(EXTRA_LIBS atomic ${EXTRA_LIBS}) -ENDIF() -UNSET(OLD_REQUIRED_LIBRARIES) - -# Include liblog for Android logging -CHECK_LIBRARY_EXISTS(log __android_log_print "" HAVE_LIBLOG) -IF(HAVE_LIBLOG) - SET(EXTRA_LIBS log ${EXTRA_LIBS}) - SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} log) -ENDIF() - -IF(NOT CMAKE_BUILD_TYPE) - SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING - "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." - FORCE) -ENDIF() -IF(NOT CMAKE_DEBUG_POSTFIX) - SET(CMAKE_DEBUG_POSTFIX "" CACHE STRING - "Library postfix for debug builds. Normally left blank." - FORCE) -ENDIF() - -IF(MSVC) - SET(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE NOMINMAX) - SET(C_FLAGS ${C_FLAGS} /wd4065 /wd4098 /wd4200) - - IF(NOT DXSDK_DIR) - STRING(REGEX REPLACE "\\\\" "/" DXSDK_DIR "$ENV{DXSDK_DIR}") - ELSE() - STRING(REGEX REPLACE "\\\\" "/" DXSDK_DIR "${DXSDK_DIR}") - ENDIF() - 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 +if(NOT HAVE_LIBATOMIC) + set(CMAKE_REQUIRED_LIBRARIES "${OLD_REQUIRED_LIBRARIES}") +else() + set(EXTRA_LIBS atomic ${EXTRA_LIBS}) +endif() +unset(OLD_REQUIRED_LIBRARIES) + +if(ANDROID) + # Include liblog for Android logging + check_library_exists(log __android_log_print "" HAVE_LIBLOG) + if(HAVE_LIBLOG) + set(EXTRA_LIBS log ${EXTRA_LIBS}) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} log) + endif() +endif() + +if(MSVC) + set(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS) + 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) + # Remove /W3, which is added by default, since we set /W4. Some build + # generators with MSVC complain about both /W3 and /W4 being specified. + foreach(flag_var CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + if(${flag_var} MATCHES "/W3") + string(REGEX REPLACE "/W3" "" ${flag_var} "${${flag_var}}") + endif() + endforeach() + + if(NOT DXSDK_DIR) + string(REGEX REPLACE "\\\\" "/" DXSDK_DIR "$ENV{DXSDK_DIR}") + else() + string(REGEX REPLACE "\\\\" "/" DXSDK_DIR "${DXSDK_DIR}") + endif() + 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) - 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 -Wall) - CHECK_C_COMPILER_FLAG(-Wextra HAVE_W_EXTRA) - IF(HAVE_W_EXTRA) - SET(C_FLAGS ${C_FLAGS} -Wextra) - ENDIF() - - IF(ALSOFT_WERROR) - SET(C_FLAGS ${C_FLAGS} -Werror) - ENDIF() + 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 + $<$:-Wold-style-cast -Wnon-virtual-dtor -Woverloaded-virtual>) + + if(ALSOFT_WERROR) + set(C_FLAGS ${C_FLAGS} -Werror) + 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() - - CHECK_C_COMPILER_FLAG(-fno-math-errno HAVE_FNO_MATH_ERRNO) - IF(HAVE_FNO_MATH_ERRNO) - SET(C_FLAGS ${C_FLAGS} -fno-math-errno) - ENDIF() + 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() - CHECK_C_SOURCE_COMPILES("int foo() __attribute__((destructor)); - int main() {return 0;}" HAVE_GCC_DESTRUCTOR) + check_c_compiler_flag(-fno-math-errno HAVE_FNO_MATH_ERRNO) + if(HAVE_FNO_MATH_ERRNO) + set(C_FLAGS ${C_FLAGS} -fno-math-errno) + endif() option(ALSOFT_STATIC_LIBGCC "Force -static-libgcc for static GCC runtimes" OFF) if(ALSOFT_STATIC_LIBGCC) set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} -static-libgcc) - check_c_source_compiles( -"#include -int main() -{ - return 0; -}" - HAVE_STATIC_LIBGCC_SWITCH - ) + check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBGCC_SWITCH) set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES}) unset(OLD_REQUIRED_LIBRARIES) @@ -279,14 +289,7 @@ int main() if(ALSOFT_STATIC_STDCXX) set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-Wl,--push-state,-Bstatic,-lstdc++,--pop-state") - check_cxx_source_compiles( -"#include -int main() -{ - return 0; -}" - HAVE_STATIC_LIBSTDCXX_SWITCH - ) + check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBSTDCXX_SWITCH) set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES}) unset(OLD_REQUIRED_LIBRARIES) @@ -301,14 +304,7 @@ int main() if(ALSOFT_STATIC_WINPTHREAD) set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-Wl,--push-state,-Bstatic,-lwinpthread,--pop-state") - check_cxx_source_compiles( -"#include -int main() -{ - return 0; -}" - HAVE_STATIC_LIBWINPTHREAD_SWITCH - ) + check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBWINPTHREAD_SWITCH) set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES}) unset(OLD_REQUIRED_LIBRARIES) @@ -318,1164 +314,1242 @@ int main() set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,--push-state,-Bstatic,-lwinpthread,--pop-state") endif() endif() -ENDIF() +endif() # Set visibility/export options if available -IF(WIN32) - SET(EXPORT_DECL "__declspec(dllexport)") - IF(NOT MINGW) - SET(ALIGN_DECL "__declspec(align(x))") - ELSE() - SET(ALIGN_DECL "__declspec(aligned(x))") - ENDIF() -ELSE() - SET(OLD_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") +if(WIN32) + if(NOT LIBTYPE STREQUAL "STATIC") + set(EXPORT_DECL "__declspec(dllexport)") + endif() +else() + set(OLD_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") # Yes GCC, really don't accept visibility modes you don't support - SET(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Wattributes -Werror") + set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Wattributes -Werror") - CHECK_C_SOURCE_COMPILES("int foo() __attribute__((visibility(\"protected\"))); + check_c_source_compiles("int foo() __attribute__((visibility(\"protected\"))); int main() {return 0;}" HAVE_GCC_PROTECTED_VISIBILITY) - IF(HAVE_GCC_PROTECTED_VISIBILITY) - SET(EXPORT_DECL "__attribute__((visibility(\"protected\")))") - ELSE() - CHECK_C_SOURCE_COMPILES("int foo() __attribute__((visibility(\"default\"))); + if(HAVE_GCC_PROTECTED_VISIBILITY) + if(NOT LIBTYPE STREQUAL "STATIC") + set(EXPORT_DECL "__attribute__((visibility(\"protected\")))") + endif() + else() + check_c_source_compiles("int foo() __attribute__((visibility(\"default\"))); int main() {return 0;}" HAVE_GCC_DEFAULT_VISIBILITY) - IF(HAVE_GCC_DEFAULT_VISIBILITY) - SET(EXPORT_DECL "__attribute__((visibility(\"default\")))") - ENDIF() - ENDIF() - - IF(HAVE_GCC_PROTECTED_VISIBILITY OR HAVE_GCC_DEFAULT_VISIBILITY) - CHECK_C_COMPILER_FLAG(-fvisibility=hidden HAVE_VISIBILITY_HIDDEN_SWITCH) - IF(HAVE_VISIBILITY_HIDDEN_SWITCH) - SET(C_FLAGS ${C_FLAGS} -fvisibility=hidden) - ENDIF() - ENDIF() - - CHECK_C_SOURCE_COMPILES("int foo __attribute__((aligned(16))); - int main() {return 0;}" HAVE_ATTRIBUTE_ALIGNED) - IF(HAVE_ATTRIBUTE_ALIGNED) - SET(ALIGN_DECL "__attribute__((aligned(x)))") - ENDIF() - - SET(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS}") -ENDIF() - -SET(SSE2_SWITCH "") -SET(SSE3_SWITCH "") -SET(SSE4_1_SWITCH "") -SET(FPU_NEON_SWITCH "") - -CHECK_C_COMPILER_FLAG(-msse2 HAVE_MSSE2_SWITCH) -IF(HAVE_MSSE2_SWITCH) - SET(SSE2_SWITCH "-msse2") -ENDIF() -CHECK_C_COMPILER_FLAG(-msse3 HAVE_MSSE3_SWITCH) -IF(HAVE_MSSE3_SWITCH) - SET(SSE3_SWITCH "-msse3") -ENDIF() -CHECK_C_COMPILER_FLAG(-msse4.1 HAVE_MSSE4_1_SWITCH) -IF(HAVE_MSSE4_1_SWITCH) - SET(SSE4_1_SWITCH "-msse4.1") -ENDIF() -CHECK_C_COMPILER_FLAG(-mfpu=neon HAVE_MFPU_NEON_SWITCH) -IF(HAVE_MFPU_NEON_SWITCH) - SET(FPU_NEON_SWITCH "-mfpu=neon") -ENDIF() - -CHECK_INCLUDE_FILE(xmmintrin.h HAVE_XMMINTRIN_H "${SSE2_SWITCH}") -CHECK_INCLUDE_FILE(emmintrin.h HAVE_EMMINTRIN_H "${SSE2_SWITCH}") -CHECK_INCLUDE_FILE(pmmintrin.h HAVE_PMMINTRIN_H "${SSE3_SWITCH}") -CHECK_INCLUDE_FILE(smmintrin.h HAVE_SMMINTRIN_H "${SSE4_1_SWITCH}") -CHECK_INCLUDE_FILE(arm_neon.h HAVE_ARM_NEON_H "${FPU_NEON_SWITCH}") - -SET(SSE_FLAGS ) -SET(FPMATH_SET "0") -IF(CMAKE_SIZEOF_VOID_P MATCHES "4") - IF(SSE2_SWITCH OR MSVC) - OPTION(ALSOFT_ENABLE_SSE2_CODEGEN "Enable SSE2 code generation instead of x87 for 32-bit targets." TRUE) - ENDIF() - - IF(ALSOFT_ENABLE_SSE2_CODEGEN) - IF(SSE2_SWITCH) - CHECK_C_COMPILER_FLAG("${SSE2_SWITCH} -mfpmath=sse" HAVE_MFPMATH_SSE_2) - IF(HAVE_MFPMATH_SSE_2) - SET(SSE_FLAGS ${SSE_FLAGS} ${SSE2_SWITCH} -mfpmath=sse) - SET(C_FLAGS ${C_FLAGS} ${SSE_FLAGS}) - SET(FPMATH_SET 2) - ENDIF() - ELSEIF(MSVC) - CHECK_C_COMPILER_FLAG("/arch:SSE2" HAVE_ARCH_SSE2) - IF(HAVE_ARCH_SSE2) - SET(SSE_FLAGS ${SSE_FLAGS} "/arch:SSE2") - SET(C_FLAGS ${C_FLAGS} ${SSE_FLAGS}) - SET(FPMATH_SET 2) - ENDIF() - ENDIF() - ENDIF() -ENDIF() - -IF(HAVE_EMMINTRIN_H) - SET(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - FOREACH(flag_var ${SSE_FLAGS}) - SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${flag_var}") - ENDFOREACH() - - CHECK_C_SOURCE_COMPILES("#include - int main() {_mm_pause(); return 0;}" HAVE_SSE_INTRINSICS) - - SET(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS}) -ENDIF() - - -CHECK_C_SOURCE_COMPILES("int foo(const char *str, ...) __attribute__((format(printf, 1, 2))); - int main() {return 0;}" HAVE_GCC_FORMAT) - -CHECK_INCLUDE_FILE(malloc.h HAVE_MALLOC_H) -CHECK_INCLUDE_FILE(dirent.h HAVE_DIRENT_H) -CHECK_INCLUDE_FILE(strings.h HAVE_STRINGS_H) -CHECK_INCLUDE_FILE(cpuid.h HAVE_CPUID_H) -CHECK_INCLUDE_FILE(intrin.h HAVE_INTRIN_H) -CHECK_INCLUDE_FILE(sys/sysconf.h HAVE_SYS_SYSCONF_H) -CHECK_INCLUDE_FILE(fenv.h HAVE_FENV_H) -CHECK_INCLUDE_FILE(float.h HAVE_FLOAT_H) -CHECK_INCLUDE_FILE(ieeefp.h HAVE_IEEEFP_H) -CHECK_INCLUDE_FILE(guiddef.h HAVE_GUIDDEF_H) -IF(NOT HAVE_GUIDDEF_H) - CHECK_INCLUDE_FILE(initguid.h HAVE_INITGUID_H) -ENDIF() - -# Some systems need libm for some of the following math functions to work -SET(MATH_LIB ) -CHECK_LIBRARY_EXISTS(m pow "" HAVE_LIBM) -IF(HAVE_LIBM) - SET(MATH_LIB ${MATH_LIB} m) - SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} m) -ENDIF() + if(HAVE_GCC_DEFAULT_VISIBILITY) + if(NOT LIBTYPE STREQUAL "STATIC") + set(EXPORT_DECL "__attribute__((visibility(\"default\")))") + endif() + endif() + endif() -# Check for the dlopen API (for dynamicly loading backend libs) -IF(ALSOFT_DLOPEN) - CHECK_LIBRARY_EXISTS(dl dlopen "" HAVE_LIBDL) - IF(HAVE_LIBDL) - SET(EXTRA_LIBS dl ${EXTRA_LIBS}) - SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} dl) - ENDIF() + if(HAVE_GCC_PROTECTED_VISIBILITY OR HAVE_GCC_DEFAULT_VISIBILITY) + check_c_compiler_flag(-fvisibility=hidden HAVE_VISIBILITY_HIDDEN_SWITCH) + if(HAVE_VISIBILITY_HIDDEN_SWITCH) + set(C_FLAGS ${C_FLAGS} -fvisibility=hidden) + endif() + endif() + + set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS}") +endif() + + +set(SSE2_SWITCH "") + +if(NOT MSVC) + set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) + # Yes GCC, really don't accept command line options you don't support + set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Werror") + check_c_compiler_flag(-msse2 HAVE_MSSE2_SWITCH) + if(HAVE_MSSE2_SWITCH) + set(SSE2_SWITCH "-msse2") + endif() + set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS}) + unset(OLD_REQUIRED_FLAGS) +endif() + +check_include_file(xmmintrin.h HAVE_XMMINTRIN_H) +check_include_file(emmintrin.h HAVE_EMMINTRIN_H) +check_include_file(pmmintrin.h HAVE_PMMINTRIN_H) +check_include_file(smmintrin.h HAVE_SMMINTRIN_H) +check_include_file(arm_neon.h HAVE_ARM_NEON_H) + +set(HAVE_SSE 0) +set(HAVE_SSE2 0) +set(HAVE_SSE3 0) +set(HAVE_SSE4_1 0) +set(HAVE_NEON 0) + +# Check for SSE support +option(ALSOFT_REQUIRE_SSE "Require SSE support" OFF) +if(HAVE_XMMINTRIN_H) + option(ALSOFT_CPUEXT_SSE "Enable SSE support" ON) + if(ALSOFT_CPUEXT_SSE) + set(HAVE_SSE 1) + endif() +endif() +if(ALSOFT_REQUIRE_SSE AND NOT HAVE_SSE) + message(FATAL_ERROR "Failed to enabled required SSE CPU extensions") +endif() + +option(ALSOFT_REQUIRE_SSE2 "Require SSE2 support" OFF) +if(HAVE_EMMINTRIN_H) + option(ALSOFT_CPUEXT_SSE2 "Enable SSE2 support" ON) + if(HAVE_SSE AND ALSOFT_CPUEXT_SSE2) + set(HAVE_SSE2 1) + endif() +endif() +if(ALSOFT_REQUIRE_SSE2 AND NOT HAVE_SSE2) + message(FATAL_ERROR "Failed to enable required SSE2 CPU extensions") +endif() + +option(ALSOFT_REQUIRE_SSE3 "Require SSE3 support" OFF) +if(HAVE_PMMINTRIN_H) + option(ALSOFT_CPUEXT_SSE3 "Enable SSE3 support" ON) + if(HAVE_SSE2 AND ALSOFT_CPUEXT_SSE3) + set(HAVE_SSE3 1) + endif() +endif() +if(ALSOFT_REQUIRE_SSE3 AND NOT HAVE_SSE3) + message(FATAL_ERROR "Failed to enable required SSE3 CPU extensions") +endif() + +option(ALSOFT_REQUIRE_SSE4_1 "Require SSE4.1 support" OFF) +if(HAVE_SMMINTRIN_H) + option(ALSOFT_CPUEXT_SSE4_1 "Enable SSE4.1 support" ON) + if(HAVE_SSE3 AND ALSOFT_CPUEXT_SSE4_1) + set(HAVE_SSE4_1 1) + endif() +endif() +if(ALSOFT_REQUIRE_SSE4_1 AND NOT HAVE_SSE4_1) + message(FATAL_ERROR "Failed to enable required SSE4.1 CPU extensions") +endif() + +# Check for ARM Neon support +option(ALSOFT_REQUIRE_NEON "Require ARM NEON support" OFF) +if(HAVE_ARM_NEON_H) + option(ALSOFT_CPUEXT_NEON "Enable ARM NEON support" ON) + if(ALSOFT_CPUEXT_NEON) + check_c_source_compiles("#include + int main() + { + int32x4_t ret4 = vdupq_n_s32(0); + return vgetq_lane_s32(ret4, 0); + }" HAVE_NEON_INTRINSICS) + if(HAVE_NEON_INTRINSICS) + set(HAVE_NEON 1) + endif() + endif() +endif() +if(ALSOFT_REQUIRE_NEON AND NOT HAVE_NEON) + message(FATAL_ERROR "Failed to enabled required ARM NEON CPU extensions") +endif() + + +set(SSE_FLAGS ) +set(FPMATH_SET "0") +if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2) + option(ALSOFT_ENABLE_SSE2_CODEGEN "Enable SSE2 code generation instead of x87 for 32-bit targets." TRUE) + if(ALSOFT_ENABLE_SSE2_CODEGEN) + if(MSVC) + check_c_compiler_flag("/arch:SSE2" HAVE_ARCH_SSE2) + if(HAVE_ARCH_SSE2) + set(SSE_FLAGS ${SSE_FLAGS} "/arch:SSE2") + set(C_FLAGS ${C_FLAGS} ${SSE_FLAGS}) + set(FPMATH_SET 2) + endif() + elseif(SSE2_SWITCH) + check_c_compiler_flag("${SSE2_SWITCH} -mfpmath=sse" HAVE_MFPMATH_SSE_2) + if(HAVE_MFPMATH_SSE_2) + set(SSE_FLAGS ${SSE_FLAGS} ${SSE2_SWITCH} -mfpmath=sse) + set(C_FLAGS ${C_FLAGS} ${SSE_FLAGS}) + set(FPMATH_SET 2) + endif() + # SSE depends on a 16-byte aligned stack, and by default, GCC + # assumes the stack is suitably aligned. Older Linux code or other + # OSs don't guarantee this on 32-bit, so externally-callable + # functions need to ensure an aligned stack. + set(EXPORT_DECL "${EXPORT_DECL} __attribute__((force_align_arg_pointer))") + endif() + endif() +endif() + +if(HAVE_SSE2) + set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) + foreach(flag_var ${SSE_FLAGS}) + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${flag_var}") + endforeach() + + check_c_source_compiles("#include + int main() {_mm_pause(); return 0;}" HAVE_SSE_INTRINSICS) + + set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS}) +endif() - CHECK_INCLUDE_FILE(dlfcn.h HAVE_DLFCN_H) -ENDIF() + +check_include_file(malloc.h HAVE_MALLOC_H) +check_include_file(cpuid.h HAVE_CPUID_H) +check_include_file(intrin.h HAVE_INTRIN_H) +check_include_file(guiddef.h HAVE_GUIDDEF_H) +if(NOT HAVE_GUIDDEF_H) + check_include_file(initguid.h HAVE_INITGUID_H) +endif() + +# Some systems need libm for some math functions to work +set(MATH_LIB ) +check_library_exists(m pow "" HAVE_LIBM) +if(HAVE_LIBM) + set(MATH_LIB ${MATH_LIB} m) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} m) +endif() + +# Some systems need to link with -lrt for clock_gettime as used by the common +# eaxmple functions. +set(RT_LIB ) +check_library_exists(rt clock_gettime "" HAVE_LIBRT) +if(HAVE_LIBRT) + set(RT_LIB rt) +endif() + +# Check for the dlopen API (for dynamicly loading backend libs) +if(ALSOFT_DLOPEN) + check_include_file(dlfcn.h HAVE_DLFCN_H) + check_library_exists(dl dlopen "" HAVE_LIBDL) + if(HAVE_LIBDL) + set(EXTRA_LIBS dl ${EXTRA_LIBS}) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} dl) + endif() +endif() # Check for a cpuid intrinsic -IF(HAVE_CPUID_H) - CHECK_C_SOURCE_COMPILES("#include +if(HAVE_CPUID_H) + check_c_source_compiles("#include int main() { unsigned int eax, ebx, ecx, edx; return __get_cpuid(0, &eax, &ebx, &ecx, &edx); }" HAVE_GCC_GET_CPUID) -ENDIF() -IF(HAVE_INTRIN_H) - CHECK_C_SOURCE_COMPILES("#include +endif() +if(HAVE_INTRIN_H) + check_c_source_compiles("#include int main() { int regs[4]; __cpuid(regs, 0); return regs[0]; }" HAVE_CPUID_INTRINSIC) - CHECK_C_SOURCE_COMPILES("#include - int main() - { - unsigned long idx = 0; - _BitScanForward64(&idx, 1); - return idx; - }" HAVE_BITSCANFORWARD64_INTRINSIC) - CHECK_C_SOURCE_COMPILES("#include - int main() - { - unsigned long idx = 0; - _BitScanForward(&idx, 1); - return idx; - }" HAVE_BITSCANFORWARD_INTRINSIC) -ENDIF() - -CHECK_SYMBOL_EXISTS(sysconf unistd.h HAVE_SYSCONF) -CHECK_SYMBOL_EXISTS(aligned_alloc stdlib.h HAVE_ALIGNED_ALLOC) -CHECK_SYMBOL_EXISTS(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN) -CHECK_SYMBOL_EXISTS(_aligned_malloc malloc.h HAVE__ALIGNED_MALLOC) -CHECK_SYMBOL_EXISTS(proc_pidpath libproc.h HAVE_PROC_PIDPATH) - -CHECK_FUNCTION_EXISTS(stat HAVE_STAT) -CHECK_FUNCTION_EXISTS(strcasecmp HAVE_STRCASECMP) -IF(NOT HAVE_STRCASECMP) - CHECK_FUNCTION_EXISTS(_stricmp HAVE__STRICMP) - IF(NOT HAVE__STRICMP) - MESSAGE(FATAL_ERROR "No case-insensitive compare function found, please report!") - ENDIF() - - SET(CPP_DEFS ${CPP_DEFS} strcasecmp=_stricmp) -ENDIF() - -CHECK_FUNCTION_EXISTS(strncasecmp HAVE_STRNCASECMP) -IF(NOT HAVE_STRNCASECMP) - CHECK_FUNCTION_EXISTS(_strnicmp HAVE__STRNICMP) - IF(NOT HAVE__STRNICMP) - MESSAGE(FATAL_ERROR "No case-insensitive size-limitted compare function found, please report!") - ENDIF() - - SET(CPP_DEFS ${CPP_DEFS} strncasecmp=_strnicmp) -ENDIF() - - -# Check if we have Windows headers -SET(OLD_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}) -SET(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} -D_WIN32_WINNT=0x0502) -CHECK_INCLUDE_FILE(windows.h HAVE_WINDOWS_H) -SET(CMAKE_REQUIRED_DEFINITIONS ${OLD_REQUIRED_DEFINITIONS}) -UNSET(OLD_REQUIRED_DEFINITIONS) - -IF(NOT HAVE_WINDOWS_H) - CHECK_SYMBOL_EXISTS(gettimeofday sys/time.h HAVE_GETTIMEOFDAY) - IF(NOT HAVE_GETTIMEOFDAY) - MESSAGE(FATAL_ERROR "No timing function found!") - ENDIF() - - CHECK_SYMBOL_EXISTS(nanosleep time.h HAVE_NANOSLEEP) - IF(NOT HAVE_NANOSLEEP) - MESSAGE(FATAL_ERROR "No sleep function found!") - ENDIF() - - # We need pthreads outside of Windows - CHECK_INCLUDE_FILE(pthread.h HAVE_PTHREAD_H) - IF(NOT HAVE_PTHREAD_H) - MESSAGE(FATAL_ERROR "PThreads is required for non-Windows builds!") - ENDIF() - # Some systems need pthread_np.h to get recursive mutexes - CHECK_INCLUDE_FILES("pthread.h;pthread_np.h" HAVE_PTHREAD_NP_H) - - CHECK_C_COMPILER_FLAG(-pthread HAVE_PTHREAD) - IF(HAVE_PTHREAD) - SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -pthread") - SET(C_FLAGS ${C_FLAGS} -pthread) - SET(LINKER_FLAGS ${LINKER_FLAGS} -pthread) - ENDIF() - - CHECK_LIBRARY_EXISTS(pthread pthread_create "" HAVE_LIBPTHREAD) - IF(HAVE_LIBPTHREAD) - SET(EXTRA_LIBS pthread ${EXTRA_LIBS}) - ENDIF() - - CHECK_SYMBOL_EXISTS(pthread_setschedparam pthread.h HAVE_PTHREAD_SETSCHEDPARAM) - - IF(HAVE_PTHREAD_NP_H) - CHECK_SYMBOL_EXISTS(pthread_setname_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SETNAME_NP) - IF(NOT HAVE_PTHREAD_SETNAME_NP) - CHECK_SYMBOL_EXISTS(pthread_set_name_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SET_NAME_NP) - ELSE() - CHECK_C_SOURCE_COMPILES(" -#include -#include -int main() -{ - pthread_setname_np(\"testname\"); - return 0; -}" - PTHREAD_SETNAME_NP_ONE_PARAM - ) - CHECK_C_SOURCE_COMPILES(" -#include -#include -int main() -{ - pthread_setname_np(pthread_self(), \"%s\", \"testname\"); - return 0; -}" - PTHREAD_SETNAME_NP_THREE_PARAMS - ) - ENDIF() - ELSE() - CHECK_SYMBOL_EXISTS(pthread_setname_np pthread.h HAVE_PTHREAD_SETNAME_NP) - IF(NOT HAVE_PTHREAD_SETNAME_NP) - CHECK_SYMBOL_EXISTS(pthread_set_name_np pthread.h HAVE_PTHREAD_SET_NAME_NP) - ELSE() - CHECK_C_SOURCE_COMPILES(" -#include -int main() -{ - pthread_setname_np(\"testname\"); - return 0; -}" - PTHREAD_SETNAME_NP_ONE_PARAM - ) - CHECK_C_SOURCE_COMPILES(" -#include -int main() -{ - pthread_setname_np(pthread_self(), \"%s\", \"testname\"); - return 0; -}" - PTHREAD_SETNAME_NP_THREE_PARAMS - ) - ENDIF() - ENDIF() - - CHECK_LIBRARY_EXISTS(rt clock_gettime "" HAVE_LIBRT) - IF(HAVE_LIBRT) - SET(EXTRA_LIBS rt ${EXTRA_LIBS}) - ENDIF() -ENDIF() - -CHECK_SYMBOL_EXISTS(getopt unistd.h HAVE_GETOPT) - -# Check for a 64-bit type -CHECK_INCLUDE_FILE(stdint.h HAVE_STDINT_H) -IF(NOT HAVE_STDINT_H) - IF(NOT SIZEOF_LONG MATCHES "8") - IF(NOT SIZEOF_LONG_LONG MATCHES "8") - MESSAGE(FATAL_ERROR "No 64-bit types found, please report!") - ENDIF() - ENDIF() -ENDIF() - - -# Common sources used by both the OpenAL implementation library and potentially -# the OpenAL router. -SET(COMMON_OBJS +endif() + +check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN) +check_symbol_exists(_aligned_malloc malloc.h HAVE__ALIGNED_MALLOC) +check_symbol_exists(proc_pidpath libproc.h HAVE_PROC_PIDPATH) + +if(NOT WIN32) + # We need pthreads outside of Windows, for semaphores. It's also used to + # set the priority and name of threads, when possible. + check_include_file(pthread.h HAVE_PTHREAD_H) + if(NOT HAVE_PTHREAD_H) + message(FATAL_ERROR "PThreads is required for non-Windows builds!") + endif() + + check_c_compiler_flag(-pthread HAVE_PTHREAD) + if(HAVE_PTHREAD) + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -pthread") + set(C_FLAGS ${C_FLAGS} -pthread) + set(LINKER_FLAGS ${LINKER_FLAGS} -pthread) + endif() + + check_symbol_exists(pthread_setschedparam pthread.h HAVE_PTHREAD_SETSCHEDPARAM) + + # Some systems need pthread_np.h to get pthread_setname_np + check_include_files("pthread.h;pthread_np.h" HAVE_PTHREAD_NP_H) + if(HAVE_PTHREAD_NP_H) + check_symbol_exists(pthread_setname_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SETNAME_NP) + if(NOT HAVE_PTHREAD_SETNAME_NP) + check_symbol_exists(pthread_set_name_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SET_NAME_NP) + endif() + else() + check_symbol_exists(pthread_setname_np pthread.h HAVE_PTHREAD_SETNAME_NP) + if(NOT HAVE_PTHREAD_SETNAME_NP) + check_symbol_exists(pthread_set_name_np pthread.h HAVE_PTHREAD_SET_NAME_NP) + endif() + endif() +endif() + +check_symbol_exists(getopt unistd.h HAVE_GETOPT) + + +# Common sources used by both the OpenAL implementation library, the OpenAL +# router, and certain tools and examples. +set(COMMON_OBJS + common/albit.h + common/albyte.h common/alcomplex.cpp common/alcomplex.h - common/alexcpt.cpp - common/alexcpt.h + common/aldeque.h + common/alfstream.cpp + common/alfstream.h common/almalloc.cpp common/almalloc.h + common/alnumbers.h common/alnumeric.h + common/aloptional.h common/alspan.h + common/alstring.cpp + common/alstring.h common/atomic.h - common/math_defs.h + common/comptr.h + common/dynload.cpp + common/dynload.h + common/intrusive_ptr.h common/opthelpers.h + common/phase_shifter.h + common/polyphase_resampler.cpp + common/polyphase_resampler.h + common/pragmadefs.h + common/ringbuffer.cpp + common/ringbuffer.h + common/strutils.cpp + common/strutils.h common/threads.cpp common/threads.h common/vecmat.h + common/vector.h) + +# Core library routines +set(CORE_OBJS + core/ambdec.cpp + core/ambdec.h + core/ambidefs.cpp + core/ambidefs.h + core/async_event.h + core/bformatdec.cpp + core/bformatdec.h + core/bs2b.cpp + core/bs2b.h + core/bsinc_defs.h + core/bsinc_tables.cpp + core/bsinc_tables.h + core/bufferline.h + core/buffer_storage.cpp + core/buffer_storage.h + core/context.cpp + core/context.h + core/converter.cpp + core/converter.h + core/cpu_caps.cpp + core/cpu_caps.h + core/devformat.cpp + core/devformat.h + core/device.cpp + core/device.h + core/effects/base.h + core/effectslot.cpp + core/effectslot.h + core/except.cpp + core/except.h + core/filters/biquad.h + core/filters/biquad.cpp + core/filters/nfc.cpp + 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 + core/front_stablizer.h + core/helpers.cpp + core/helpers.h + core/hrtf.cpp + core/hrtf.h + core/logging.cpp + core/logging.h + core/mastering.cpp + core/mastering.h + core/mixer.cpp + core/mixer.h + core/resampler_limits.h + core/uhjfilter.cpp + core/uhjfilter.h + core/uiddefs.cpp + core/voice.cpp + core/voice.h + core/voice_change.h) + +set(HAVE_RTKIT 0) +option(ALSOFT_REQUIRE_RTKIT "Require RTKit/D-Bus support" FALSE) +find_package(DBus1 QUIET) +if(DBus1_FOUND) + option(ALSOFT_RTKIT "Enable RTKit support" ON) + if(ALSOFT_RTKIT) + set(HAVE_RTKIT 1) + set(CORE_OBJS ${CORE_OBJS} core/dbus_wrap.cpp core/dbus_wrap.h core/rtkit.cpp core/rtkit.h) + if(WIN32 OR HAVE_DLFCN_H) + set(INC_PATHS ${INC_PATHS} ${DBus1_INCLUDE_DIRS}) + set(CPP_DEFS ${CPP_DEFS} ${DBus1_DEFINITIONS}) + else() + set(EXTRA_LIBS ${EXTRA_LIBS} ${DBus1_LIBRARIES}) + endif() + endif() +else() + set(MISSING_VARS "") + if(NOT DBus1_INCLUDE_DIRS) + set(MISSING_VARS "${MISSING_VARS} DBus1_INCLUDE_DIRS") + endif() + if(NOT DBus1_LIBRARIES) + set(MISSING_VARS "${MISSING_VARS} DBus1_LIBRARIES") + endif() + message(STATUS "Could NOT find DBus1 (missing:${MISSING_VARS})") + unset(MISSING_VARS) +endif() +if(ALSOFT_REQUIRE_RTKIT AND NOT HAVE_RTKIT) + message(FATAL_ERROR "Failed to enabled required RTKit support") +endif() + +# Default mixers, always available +set(CORE_OBJS ${CORE_OBJS} + core/mixer/defs.h + core/mixer/hrtfbase.h + core/mixer/hrtfdefs.h + core/mixer/mixer_c.cpp) + +# AL and related routines +set(OPENAL_OBJS + al/auxeffectslot.cpp + al/auxeffectslot.h + al/buffer.cpp + al/buffer.h + al/effect.cpp + al/effect.h + al/effects/autowah.cpp + al/effects/chorus.cpp + al/effects/compressor.cpp + al/effects/convolution.cpp + al/effects/dedicated.cpp + al/effects/distortion.cpp + al/effects/echo.cpp + al/effects/effects.cpp + al/effects/effects.h + al/effects/equalizer.cpp + al/effects/fshifter.cpp + al/effects/modulator.cpp + al/effects/null.cpp + al/effects/pshifter.cpp + al/effects/reverb.cpp + al/effects/vmorpher.cpp + al/error.cpp + al/event.cpp + al/event.h + al/extension.cpp + al/filter.cpp + al/filter.h + al/listener.cpp + al/listener.h + al/source.cpp + al/source.h + al/state.cpp) + +# ALC and related routines +set(ALC_OBJS + alc/alc.cpp + alc/alu.cpp + alc/alu.h + alc/alconfig.cpp + alc/alconfig.h + alc/context.cpp + alc/context.h + alc/device.cpp + alc/device.h + alc/effects/base.h + alc/effects/autowah.cpp + alc/effects/chorus.cpp + alc/effects/compressor.cpp + alc/effects/convolution.cpp + alc/effects/dedicated.cpp + alc/effects/distortion.cpp + alc/effects/echo.cpp + alc/effects/equalizer.cpp + alc/effects/fshifter.cpp + alc/effects/modulator.cpp + alc/effects/null.cpp + alc/effects/pshifter.cpp + alc/effects/reverb.cpp + alc/effects/vmorpher.cpp + alc/inprogext.h + alc/panning.cpp) + +if (ALSOFT_EAX) + set(OPENAL_OBJS + ${OPENAL_OBJS} + al/eax_api.cpp + al/eax_api.h + al/eax_eax_call.cpp + al/eax_eax_call.h + al/eax_effect.cpp + al/eax_effect.h + al/eax_exception.cpp + al/eax_exception.h + al/eax_fx_slot_index.cpp + al/eax_fx_slot_index.h + al/eax_fx_slots.cpp + al/eax_fx_slots.h + al/eax_globals.cpp + al/eax_globals.h + al/eax_utils.cpp + al/eax_utils.h + al/eax_x_ram.cpp + al/eax_x_ram.h ) -SET(OPENAL_OBJS - OpenAL32/Include/alMain.h - OpenAL32/Include/alu.h - - OpenAL32/Include/alAuxEffectSlot.h - OpenAL32/alAuxEffectSlot.cpp - OpenAL32/Include/alBuffer.h - OpenAL32/alBuffer.cpp - OpenAL32/Include/alEffect.h - OpenAL32/alEffect.cpp - OpenAL32/Include/alError.h - OpenAL32/alError.cpp - OpenAL32/alExtension.cpp - OpenAL32/Include/alFilter.h - OpenAL32/alFilter.cpp - OpenAL32/Include/alListener.h - OpenAL32/alListener.cpp - OpenAL32/Include/alSource.h - OpenAL32/alSource.cpp - OpenAL32/alState.cpp - OpenAL32/event.cpp - OpenAL32/Include/sample_cvt.h - OpenAL32/sample_cvt.cpp -) -SET(ALC_OBJS - Alc/alc.cpp - Alc/alu.cpp - Alc/alconfig.cpp - Alc/alconfig.h - Alc/alcontext.h - Alc/ambidefs.h - Alc/bs2b.cpp - Alc/bs2b.h - Alc/converter.cpp - Alc/converter.h - Alc/inprogext.h - Alc/mastering.cpp - Alc/mastering.h - Alc/ringbuffer.cpp - Alc/ringbuffer.h - Alc/effects/base.h - Alc/effects/autowah.cpp - Alc/effects/chorus.cpp - Alc/effects/compressor.cpp - Alc/effects/dedicated.cpp - Alc/effects/distortion.cpp - Alc/effects/echo.cpp - Alc/effects/equalizer.cpp - Alc/effects/fshifter.cpp - Alc/effects/modulator.cpp - Alc/effects/null.cpp - Alc/effects/pshifter.cpp - Alc/effects/reverb.cpp - Alc/filters/biquad.h - Alc/filters/biquad.cpp - Alc/filters/nfc.cpp - Alc/filters/nfc.h - Alc/filters/splitter.cpp - Alc/filters/splitter.h - Alc/helpers.cpp - Alc/compat.h - Alc/cpu_caps.h - Alc/fpu_modes.h - Alc/logging.h - Alc/vector.h - Alc/hrtf.cpp - Alc/hrtf.h - Alc/uhjfilter.cpp - Alc/uhjfilter.h - Alc/ambdec.cpp - Alc/ambdec.h - Alc/bformatdec.cpp - Alc/bformatdec.h - Alc/panning.cpp - Alc/mixvoice.cpp - Alc/mixer/defs.h - Alc/mixer/hrtfbase.h - Alc/mixer/mixer_c.cpp -) +endif () +# Include SIMD mixers +set(CPU_EXTS "Default") +if(HAVE_SSE) + set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse.cpp) + set(CPU_EXTS "${CPU_EXTS}, SSE") +endif() +if(HAVE_SSE2) + set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse2.cpp) + set(CPU_EXTS "${CPU_EXTS}, SSE2") +endif() +if(HAVE_SSE3) + set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse3.cpp) + set(CPU_EXTS "${CPU_EXTS}, SSE3") +endif() +if(HAVE_SSE4_1) + set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse41.cpp) + set(CPU_EXTS "${CPU_EXTS}, SSE4.1") +endif() +if(HAVE_NEON) + set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_neon.cpp) + set(CPU_EXTS "${CPU_EXTS}, Neon") +endif() -SET(CPU_EXTS "Default") -SET(HAVE_SSE 0) -SET(HAVE_SSE2 0) -SET(HAVE_SSE3 0) -SET(HAVE_SSE4_1 0) -SET(HAVE_NEON 0) - -# Check for SSE+SSE2 support -OPTION(ALSOFT_REQUIRE_SSE "Require SSE support" OFF) -OPTION(ALSOFT_REQUIRE_SSE2 "Require SSE2 support" OFF) -IF(HAVE_XMMINTRIN_H AND HAVE_EMMINTRIN_H) - OPTION(ALSOFT_CPUEXT_SSE "Enable SSE support" ON) - OPTION(ALSOFT_CPUEXT_SSE2 "Enable SSE2 support" ON) - IF(ALSOFT_CPUEXT_SSE AND ALSOFT_CPUEXT_SSE2) - SET(HAVE_SSE 1) - SET(HAVE_SSE2 1) - SET(ALC_OBJS ${ALC_OBJS} Alc/mixer/mixer_sse.cpp Alc/mixer/mixer_sse2.cpp) - IF(SSE2_SWITCH) - SET_SOURCE_FILES_PROPERTIES(Alc/mixer/mixer_sse.cpp Alc/mixer/mixer_sse2.cpp - PROPERTIES COMPILE_FLAGS "${SSE2_SWITCH}") - ENDIF() - SET(CPU_EXTS "${CPU_EXTS}, SSE, SSE2") - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_SSE AND NOT HAVE_SSE) - MESSAGE(FATAL_ERROR "Failed to enabled required SSE CPU extensions") -ENDIF() -IF(ALSOFT_REQUIRE_SSE2 AND NOT HAVE_SSE2) - MESSAGE(FATAL_ERROR "Failed to enable required SSE2 CPU extensions") -ENDIF() - -OPTION(ALSOFT_REQUIRE_SSE3 "Require SSE3 support" OFF) -IF(HAVE_EMMINTRIN_H) - OPTION(ALSOFT_CPUEXT_SSE3 "Enable SSE3 support" ON) - IF(HAVE_SSE2 AND ALSOFT_CPUEXT_SSE3) - SET(HAVE_SSE3 1) - SET(ALC_OBJS ${ALC_OBJS} Alc/mixer/mixer_sse3.cpp) - IF(SSE2_SWITCH) - SET_SOURCE_FILES_PROPERTIES(Alc/mixer/mixer_sse3.cpp PROPERTIES - COMPILE_FLAGS "${SSE3_SWITCH}") - ENDIF() - SET(CPU_EXTS "${CPU_EXTS}, SSE3") - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_SSE3 AND NOT HAVE_SSE3) - MESSAGE(FATAL_ERROR "Failed to enable required SSE3 CPU extensions") -ENDIF() - -OPTION(ALSOFT_REQUIRE_SSE4_1 "Require SSE4.1 support" OFF) -IF(HAVE_SMMINTRIN_H) - OPTION(ALSOFT_CPUEXT_SSE4_1 "Enable SSE4.1 support" ON) - IF(HAVE_SSE3 AND ALSOFT_CPUEXT_SSE4_1) - SET(HAVE_SSE4_1 1) - SET(ALC_OBJS ${ALC_OBJS} Alc/mixer/mixer_sse41.cpp) - IF(SSE4_1_SWITCH) - SET_SOURCE_FILES_PROPERTIES(Alc/mixer/mixer_sse41.cpp PROPERTIES - COMPILE_FLAGS "${SSE4_1_SWITCH}") - ENDIF() - SET(CPU_EXTS "${CPU_EXTS}, SSE4.1") - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_SSE4_1 AND NOT HAVE_SSE4_1) - MESSAGE(FATAL_ERROR "Failed to enable required SSE4.1 CPU extensions") -ENDIF() -# Check for ARM Neon support -OPTION(ALSOFT_REQUIRE_NEON "Require ARM Neon support" OFF) -IF(HAVE_ARM_NEON_H) - OPTION(ALSOFT_CPUEXT_NEON "Enable ARM Neon support" ON) - IF(ALSOFT_CPUEXT_NEON) - SET(HAVE_NEON 1) - SET(ALC_OBJS ${ALC_OBJS} Alc/mixer/mixer_neon.cpp) - IF(FPU_NEON_SWITCH) - SET_SOURCE_FILES_PROPERTIES(Alc/mixer/mixer_neon.cpp PROPERTIES - COMPILE_FLAGS "${FPU_NEON_SWITCH}") - ENDIF() - SET(CPU_EXTS "${CPU_EXTS}, Neon") - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_NEON AND NOT HAVE_NEON) - MESSAGE(FATAL_ERROR "Failed to enabled required ARM Neon CPU extensions") -ENDIF() - - -SET(HAVE_ALSA 0) -SET(HAVE_OSS 0) -SET(HAVE_SOLARIS 0) -SET(HAVE_SNDIO 0) -SET(HAVE_QSA 0) -SET(HAVE_DSOUND 0) -SET(HAVE_WASAPI 0) -SET(HAVE_WINMM 0) -SET(HAVE_PORTAUDIO 0) -SET(HAVE_PULSEAUDIO 0) -SET(HAVE_COREAUDIO 0) -SET(HAVE_OPENSL 0) -SET(HAVE_WAVE 0) -SET(HAVE_SDL2 0) - -IF(WIN32 OR HAVE_DLFCN_H) - SET(IS_LINKED "") - MACRO(ADD_BACKEND_LIBS _LIBS) - ENDMACRO() -ELSE() - SET(IS_LINKED " (linked)") - MACRO(ADD_BACKEND_LIBS _LIBS) - SET(EXTRA_LIBS ${_LIBS} ${EXTRA_LIBS}) - ENDMACRO() -ENDIF() - -SET(BACKENDS "") -SET(ALC_OBJS ${ALC_OBJS} - Alc/backends/base.cpp - Alc/backends/base.h +set(HAVE_ALSA 0) +set(HAVE_OSS 0) +set(HAVE_PIPEWIRE 0) +set(HAVE_SOLARIS 0) +set(HAVE_SNDIO 0) +set(HAVE_DSOUND 0) +set(HAVE_WASAPI 0) +set(HAVE_WINMM 0) +set(HAVE_PORTAUDIO 0) +set(HAVE_PULSEAUDIO 0) +set(HAVE_COREAUDIO 0) +set(HAVE_OPENSL 0) +set(HAVE_OBOE 0) +set(HAVE_WAVE 0) +set(HAVE_SDL2 0) + +if(WIN32 OR HAVE_DLFCN_H) + set(IS_LINKED "") + macro(ADD_BACKEND_LIBS _LIBS) + endmacro() +else() + set(IS_LINKED " (linked)") + macro(ADD_BACKEND_LIBS _LIBS) + set(EXTRA_LIBS ${_LIBS} ${EXTRA_LIBS}) + endmacro() +endif() + +set(BACKENDS "") +set(ALC_OBJS ${ALC_OBJS} + alc/backends/base.cpp + alc/backends/base.h # Default backends, always available - Alc/backends/loopback.cpp - Alc/backends/loopback.h - Alc/backends/null.cpp - Alc/backends/null.h + alc/backends/loopback.cpp + alc/backends/loopback.h + alc/backends/null.cpp + alc/backends/null.h ) # Check ALSA backend -OPTION(ALSOFT_REQUIRE_ALSA "Require ALSA backend" OFF) -FIND_PACKAGE(ALSA) -IF(ALSA_FOUND) - OPTION(ALSOFT_BACKEND_ALSA "Enable ALSA backend" ON) - IF(ALSOFT_BACKEND_ALSA) - SET(HAVE_ALSA 1) - SET(BACKENDS "${BACKENDS} ALSA${IS_LINKED},") - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/alsa.cpp Alc/backends/alsa.h) - ADD_BACKEND_LIBS(${ALSA_LIBRARIES}) - SET(INC_PATHS ${INC_PATHS} ${ALSA_INCLUDE_DIRS}) - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_ALSA AND NOT HAVE_ALSA) - MESSAGE(FATAL_ERROR "Failed to enabled required ALSA backend") -ENDIF() +option(ALSOFT_REQUIRE_ALSA "Require ALSA backend" OFF) +find_package(ALSA) +if(ALSA_FOUND) + option(ALSOFT_BACKEND_ALSA "Enable ALSA backend" ON) + if(ALSOFT_BACKEND_ALSA) + set(HAVE_ALSA 1) + set(BACKENDS "${BACKENDS} ALSA${IS_LINKED},") + set(ALC_OBJS ${ALC_OBJS} alc/backends/alsa.cpp alc/backends/alsa.h) + add_backend_libs(${ALSA_LIBRARIES}) + set(INC_PATHS ${INC_PATHS} ${ALSA_INCLUDE_DIRS}) + endif() +endif() +if(ALSOFT_REQUIRE_ALSA AND NOT HAVE_ALSA) + message(FATAL_ERROR "Failed to enabled required ALSA backend") +endif() # Check OSS backend -OPTION(ALSOFT_REQUIRE_OSS "Require OSS backend" OFF) -FIND_PACKAGE(OSS) -IF(OSS_FOUND) - OPTION(ALSOFT_BACKEND_OSS "Enable OSS backend" ON) - IF(ALSOFT_BACKEND_OSS) - SET(HAVE_OSS 1) - SET(BACKENDS "${BACKENDS} OSS,") - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/oss.cpp Alc/backends/oss.h) - IF(OSS_LIBRARIES) - SET(EXTRA_LIBS ${OSS_LIBRARIES} ${EXTRA_LIBS}) - ENDIF() - SET(INC_PATHS ${INC_PATHS} ${OSS_INCLUDE_DIRS}) - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_OSS AND NOT HAVE_OSS) - MESSAGE(FATAL_ERROR "Failed to enabled required OSS backend") -ENDIF() +option(ALSOFT_REQUIRE_OSS "Require OSS backend" OFF) +find_package(OSS) +if(OSS_FOUND) + option(ALSOFT_BACKEND_OSS "Enable OSS backend" ON) + if(ALSOFT_BACKEND_OSS) + set(HAVE_OSS 1) + set(BACKENDS "${BACKENDS} OSS,") + set(ALC_OBJS ${ALC_OBJS} alc/backends/oss.cpp alc/backends/oss.h) + if(OSS_LIBRARIES) + set(EXTRA_LIBS ${OSS_LIBRARIES} ${EXTRA_LIBS}) + endif() + set(INC_PATHS ${INC_PATHS} ${OSS_INCLUDE_DIRS}) + endif() +endif() +if(ALSOFT_REQUIRE_OSS AND NOT HAVE_OSS) + message(FATAL_ERROR "Failed to enabled required OSS backend") +endif() + +# Check PipeWire backend +option(ALSOFT_REQUIRE_PIPEWIRE "Require PipeWire backend" OFF) +if(PkgConfig_FOUND) + pkg_check_modules(PIPEWIRE libpipewire-0.3) + if(PIPEWIRE_FOUND) + option(ALSOFT_BACKEND_PIPEWIRE "Enable PipeWire backend" ON) + if(ALSOFT_BACKEND_PIPEWIRE) + set(HAVE_PIPEWIRE 1) + set(BACKENDS "${BACKENDS} PipeWire${IS_LINKED},") + set(ALC_OBJS ${ALC_OBJS} alc/backends/pipewire.cpp alc/backends/pipewire.h) + add_backend_libs(${PIPEWIRE_LIBRARIES}) + set(INC_PATHS ${INC_PATHS} ${PIPEWIRE_INCLUDE_DIRS}) + endif() + endif() +endif() +if(ALSOFT_REQUIRE_PIPEWIRE AND NOT HAVE_PIPEWIRE) + message(FATAL_ERROR "Failed to enabled required PipeWire backend") +endif() # Check Solaris backend -OPTION(ALSOFT_REQUIRE_SOLARIS "Require Solaris backend" OFF) -FIND_PACKAGE(AudioIO) -IF(AUDIOIO_FOUND) - OPTION(ALSOFT_BACKEND_SOLARIS "Enable Solaris backend" ON) - IF(ALSOFT_BACKEND_SOLARIS) - SET(HAVE_SOLARIS 1) - SET(BACKENDS "${BACKENDS} Solaris,") - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/solaris.cpp Alc/backends/solaris.h) - SET(INC_PATHS ${INC_PATHS} ${AUDIOIO_INCLUDE_DIRS}) - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_SOLARIS AND NOT HAVE_SOLARIS) - MESSAGE(FATAL_ERROR "Failed to enabled required Solaris backend") -ENDIF() +option(ALSOFT_REQUIRE_SOLARIS "Require Solaris backend" OFF) +find_package(AudioIO) +if(AUDIOIO_FOUND) + option(ALSOFT_BACKEND_SOLARIS "Enable Solaris backend" ON) + if(ALSOFT_BACKEND_SOLARIS) + set(HAVE_SOLARIS 1) + set(BACKENDS "${BACKENDS} Solaris,") + set(ALC_OBJS ${ALC_OBJS} alc/backends/solaris.cpp alc/backends/solaris.h) + set(INC_PATHS ${INC_PATHS} ${AUDIOIO_INCLUDE_DIRS}) + endif() +endif() +if(ALSOFT_REQUIRE_SOLARIS AND NOT HAVE_SOLARIS) + message(FATAL_ERROR "Failed to enabled required Solaris backend") +endif() # Check SndIO backend -OPTION(ALSOFT_REQUIRE_SNDIO "Require SndIO backend" OFF) -FIND_PACKAGE(SoundIO) -IF(SOUNDIO_FOUND) - OPTION(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" ON) - IF(ALSOFT_BACKEND_SNDIO) - SET(HAVE_SNDIO 1) - SET(BACKENDS "${BACKENDS} SndIO (linked),") - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/sndio.cpp Alc/backends/sndio.h) - SET(EXTRA_LIBS ${SOUNDIO_LIBRARIES} ${EXTRA_LIBS}) - SET(INC_PATHS ${INC_PATHS} ${SOUNDIO_INCLUDE_DIRS}) - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_SNDIO AND NOT HAVE_SNDIO) - MESSAGE(FATAL_ERROR "Failed to enabled required SndIO backend") -ENDIF() - -# Check QSA backend -OPTION(ALSOFT_REQUIRE_QSA "Require QSA backend" OFF) -FIND_PACKAGE(QSA) -IF(QSA_FOUND) - OPTION(ALSOFT_BACKEND_QSA "Enable QSA backend" ON) - IF(ALSOFT_BACKEND_QSA) - SET(HAVE_QSA 1) - SET(BACKENDS "${BACKENDS} QSA (linked),") - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/qsa.cpp Alc/backends/qsa.h) - SET(EXTRA_LIBS ${QSA_LIBRARIES} ${EXTRA_LIBS}) - SET(INC_PATHS ${INC_PATHS} ${QSA_INCLUDE_DIRS}) - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_QSA AND NOT HAVE_QSA) - MESSAGE(FATAL_ERROR "Failed to enabled required QSA backend") -ENDIF() +option(ALSOFT_REQUIRE_SNDIO "Require SndIO backend" OFF) +find_package(SoundIO) +if(SOUNDIO_FOUND) + option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" ON) + if(ALSOFT_BACKEND_SNDIO) + set(HAVE_SNDIO 1) + set(BACKENDS "${BACKENDS} SndIO (linked),") + set(ALC_OBJS ${ALC_OBJS} alc/backends/sndio.cpp alc/backends/sndio.h) + set(EXTRA_LIBS ${SOUNDIO_LIBRARIES} ${EXTRA_LIBS}) + set(INC_PATHS ${INC_PATHS} ${SOUNDIO_INCLUDE_DIRS}) + endif() +endif() +if(ALSOFT_REQUIRE_SNDIO AND NOT HAVE_SNDIO) + message(FATAL_ERROR "Failed to enabled required SndIO backend") +endif() # Check Windows-only backends -OPTION(ALSOFT_REQUIRE_WINMM "Require Windows Multimedia backend" OFF) -OPTION(ALSOFT_REQUIRE_DSOUND "Require DirectSound backend" OFF) -OPTION(ALSOFT_REQUIRE_WASAPI "Require WASAPI backend" OFF) -IF(HAVE_WINDOWS_H) - SET(OLD_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}) - SET(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} -D_WIN32_WINNT=0x0502) - +option(ALSOFT_REQUIRE_WINMM "Require Windows Multimedia backend" OFF) +option(ALSOFT_REQUIRE_DSOUND "Require DirectSound backend" OFF) +option(ALSOFT_REQUIRE_WASAPI "Require WASAPI backend" OFF) +if(WIN32) # Check MMSystem backend - CHECK_INCLUDE_FILES("windows.h;mmsystem.h" HAVE_MMSYSTEM_H) - IF(HAVE_MMSYSTEM_H) - CHECK_SHARED_FUNCTION_EXISTS(waveOutOpen "windows.h;mmsystem.h" winmm "" HAVE_LIBWINMM) - IF(HAVE_LIBWINMM) - OPTION(ALSOFT_BACKEND_WINMM "Enable Windows Multimedia backend" ON) - IF(ALSOFT_BACKEND_WINMM) - SET(HAVE_WINMM 1) - SET(BACKENDS "${BACKENDS} WinMM,") - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/winmm.cpp Alc/backends/winmm.h) - SET(EXTRA_LIBS winmm ${EXTRA_LIBS}) - ENDIF() - ENDIF() - ENDIF() + option(ALSOFT_BACKEND_WINMM "Enable Windows Multimedia backend" ON) + if(ALSOFT_BACKEND_WINMM) + set(HAVE_WINMM 1) + set(BACKENDS "${BACKENDS} WinMM,") + set(ALC_OBJS ${ALC_OBJS} alc/backends/winmm.cpp alc/backends/winmm.h) + # There doesn't seem to be good way to search for winmm.lib for MSVC. + # find_library doesn't find it without being told to look in a specific + # place in the WindowsSDK, but it links anyway. If there ends up being + # Windows targets without this, another means to detect it is needed. + set(EXTRA_LIBS winmm ${EXTRA_LIBS}) + endif() # Check DSound backend - FIND_PACKAGE(DSound) - IF(DSOUND_FOUND) - OPTION(ALSOFT_BACKEND_DSOUND "Enable DirectSound backend" ON) - IF(ALSOFT_BACKEND_DSOUND) - SET(HAVE_DSOUND 1) - SET(BACKENDS "${BACKENDS} DirectSound${IS_LINKED},") - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/dsound.cpp Alc/backends/dsound.h) - ADD_BACKEND_LIBS(${DSOUND_LIBRARIES}) - SET(INC_PATHS ${INC_PATHS} ${DSOUND_INCLUDE_DIRS}) - ENDIF() - ENDIF() + check_include_file(dsound.h HAVE_DSOUND_H) + if(DXSDK_DIR) + find_path(DSOUND_INCLUDE_DIR NAMES "dsound.h" + PATHS "${DXSDK_DIR}" PATH_SUFFIXES include + DOC "The DirectSound include directory") + endif() + if(HAVE_DSOUND_H OR DSOUND_INCLUDE_DIR) + option(ALSOFT_BACKEND_DSOUND "Enable DirectSound backend" ON) + if(ALSOFT_BACKEND_DSOUND) + set(HAVE_DSOUND 1) + set(BACKENDS "${BACKENDS} DirectSound,") + set(ALC_OBJS ${ALC_OBJS} alc/backends/dsound.cpp alc/backends/dsound.h) + + if(NOT HAVE_DSOUND_H) + set(INC_PATHS ${INC_PATHS} ${DSOUND_INCLUDE_DIR}) + endif() + endif() + endif() # Check for WASAPI backend - CHECK_INCLUDE_FILE(mmdeviceapi.h HAVE_MMDEVICEAPI_H) - IF(HAVE_MMDEVICEAPI_H) - OPTION(ALSOFT_BACKEND_WASAPI "Enable WASAPI backend" ON) - IF(ALSOFT_BACKEND_WASAPI) - SET(HAVE_WASAPI 1) - SET(BACKENDS "${BACKENDS} WASAPI,") - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/wasapi.cpp Alc/backends/wasapi.h) - ENDIF() - ENDIF() - - SET(CMAKE_REQUIRED_DEFINITIONS ${OLD_REQUIRED_DEFINITIONS}) - UNSET(OLD_REQUIRED_DEFINITIONS) -ENDIF() -IF(ALSOFT_REQUIRE_WINMM AND NOT HAVE_WINMM) - MESSAGE(FATAL_ERROR "Failed to enabled required WinMM backend") -ENDIF() -IF(ALSOFT_REQUIRE_DSOUND AND NOT HAVE_DSOUND) - MESSAGE(FATAL_ERROR "Failed to enabled required DSound backend") -ENDIF() -IF(ALSOFT_REQUIRE_WASAPI AND NOT HAVE_WASAPI) - MESSAGE(FATAL_ERROR "Failed to enabled required WASAPI backend") -ENDIF() + check_include_file(mmdeviceapi.h HAVE_MMDEVICEAPI_H) + if(HAVE_MMDEVICEAPI_H) + option(ALSOFT_BACKEND_WASAPI "Enable WASAPI backend" ON) + if(ALSOFT_BACKEND_WASAPI) + set(HAVE_WASAPI 1) + set(BACKENDS "${BACKENDS} WASAPI,") + set(ALC_OBJS ${ALC_OBJS} alc/backends/wasapi.cpp alc/backends/wasapi.h) + endif() + endif() +endif() +if(ALSOFT_REQUIRE_WINMM AND NOT HAVE_WINMM) + message(FATAL_ERROR "Failed to enabled required WinMM backend") +endif() +if(ALSOFT_REQUIRE_DSOUND AND NOT HAVE_DSOUND) + message(FATAL_ERROR "Failed to enabled required DSound backend") +endif() +if(ALSOFT_REQUIRE_WASAPI AND NOT HAVE_WASAPI) + message(FATAL_ERROR "Failed to enabled required WASAPI backend") +endif() # Check PortAudio backend -OPTION(ALSOFT_REQUIRE_PORTAUDIO "Require PortAudio backend" OFF) -FIND_PACKAGE(PortAudio) -IF(PORTAUDIO_FOUND) - OPTION(ALSOFT_BACKEND_PORTAUDIO "Enable PortAudio backend" ON) - IF(ALSOFT_BACKEND_PORTAUDIO) - SET(HAVE_PORTAUDIO 1) - SET(BACKENDS "${BACKENDS} PortAudio${IS_LINKED},") - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/portaudio.cpp Alc/backends/portaudio.h) - ADD_BACKEND_LIBS(${PORTAUDIO_LIBRARIES}) - SET(INC_PATHS ${INC_PATHS} ${PORTAUDIO_INCLUDE_DIRS}) - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_PORTAUDIO AND NOT HAVE_PORTAUDIO) - MESSAGE(FATAL_ERROR "Failed to enabled required PortAudio backend") -ENDIF() +option(ALSOFT_REQUIRE_PORTAUDIO "Require PortAudio backend" OFF) +find_package(PortAudio) +if(PORTAUDIO_FOUND) + option(ALSOFT_BACKEND_PORTAUDIO "Enable PortAudio backend" ON) + if(ALSOFT_BACKEND_PORTAUDIO) + set(HAVE_PORTAUDIO 1) + set(BACKENDS "${BACKENDS} PortAudio${IS_LINKED},") + set(ALC_OBJS ${ALC_OBJS} alc/backends/portaudio.cpp alc/backends/portaudio.h) + add_backend_libs(${PORTAUDIO_LIBRARIES}) + set(INC_PATHS ${INC_PATHS} ${PORTAUDIO_INCLUDE_DIRS}) + endif() +endif() +if(ALSOFT_REQUIRE_PORTAUDIO AND NOT HAVE_PORTAUDIO) + message(FATAL_ERROR "Failed to enabled required PortAudio backend") +endif() # Check PulseAudio backend -OPTION(ALSOFT_REQUIRE_PULSEAUDIO "Require PulseAudio backend" OFF) -FIND_PACKAGE(PulseAudio) -IF(PULSEAUDIO_FOUND) - OPTION(ALSOFT_BACKEND_PULSEAUDIO "Enable PulseAudio backend" ON) - IF(ALSOFT_BACKEND_PULSEAUDIO) - SET(HAVE_PULSEAUDIO 1) - SET(BACKENDS "${BACKENDS} PulseAudio${IS_LINKED},") - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/pulseaudio.cpp Alc/backends/pulseaudio.h) - ADD_BACKEND_LIBS(${PULSEAUDIO_LIBRARIES}) - SET(INC_PATHS ${INC_PATHS} ${PULSEAUDIO_INCLUDE_DIRS}) - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_PULSEAUDIO AND NOT HAVE_PULSEAUDIO) - MESSAGE(FATAL_ERROR "Failed to enabled required PulseAudio backend") -ENDIF() +option(ALSOFT_REQUIRE_PULSEAUDIO "Require PulseAudio backend" OFF) +find_package(PulseAudio) +if(PULSEAUDIO_FOUND) + option(ALSOFT_BACKEND_PULSEAUDIO "Enable PulseAudio backend" ON) + if(ALSOFT_BACKEND_PULSEAUDIO) + set(HAVE_PULSEAUDIO 1) + set(BACKENDS "${BACKENDS} PulseAudio${IS_LINKED},") + set(ALC_OBJS ${ALC_OBJS} alc/backends/pulseaudio.cpp alc/backends/pulseaudio.h) + add_backend_libs(${PULSEAUDIO_LIBRARIES}) + set(INC_PATHS ${INC_PATHS} ${PULSEAUDIO_INCLUDE_DIRS}) + endif() +endif() +if(ALSOFT_REQUIRE_PULSEAUDIO AND NOT HAVE_PULSEAUDIO) + message(FATAL_ERROR "Failed to enabled required PulseAudio backend") +endif() # Check JACK backend -OPTION(ALSOFT_REQUIRE_JACK "Require JACK backend" OFF) -FIND_PACKAGE(JACK) -IF(JACK_FOUND) - OPTION(ALSOFT_BACKEND_JACK "Enable JACK backend" ON) - IF(ALSOFT_BACKEND_JACK) - SET(HAVE_JACK 1) - SET(BACKENDS "${BACKENDS} JACK${IS_LINKED},") - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/jack.cpp Alc/backends/jack.h) - ADD_BACKEND_LIBS(${JACK_LIBRARIES}) - SET(INC_PATHS ${INC_PATHS} ${JACK_INCLUDE_DIRS}) - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_JACK AND NOT HAVE_JACK) - MESSAGE(FATAL_ERROR "Failed to enabled required JACK backend") -ENDIF() +option(ALSOFT_REQUIRE_JACK "Require JACK backend" OFF) +find_package(JACK) +if(JACK_FOUND) + option(ALSOFT_BACKEND_JACK "Enable JACK backend" ON) + if(ALSOFT_BACKEND_JACK) + set(HAVE_JACK 1) + set(BACKENDS "${BACKENDS} JACK${IS_LINKED},") + set(ALC_OBJS ${ALC_OBJS} alc/backends/jack.cpp alc/backends/jack.h) + add_backend_libs(${JACK_LIBRARIES}) + set(INC_PATHS ${INC_PATHS} ${JACK_INCLUDE_DIRS}) + endif() +endif() +if(ALSOFT_REQUIRE_JACK AND NOT HAVE_JACK) + message(FATAL_ERROR "Failed to enabled required JACK backend") +endif() # Check CoreAudio backend -OPTION(ALSOFT_REQUIRE_COREAUDIO "Require CoreAudio backend" OFF) -FIND_LIBRARY(COREAUDIO_FRAMEWORK - NAMES CoreAudio - PATHS /System/Library/Frameworks -) -IF(COREAUDIO_FRAMEWORK) - OPTION(ALSOFT_BACKEND_COREAUDIO "Enable CoreAudio backend" ON) - IF(ALSOFT_BACKEND_COREAUDIO) - SET(HAVE_COREAUDIO 1) - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/coreaudio.cpp Alc/backends/coreaudio.h) - SET(BACKENDS "${BACKENDS} CoreAudio,") - SET(EXTRA_LIBS ${COREAUDIO_FRAMEWORK} ${EXTRA_LIBS}) - SET(EXTRA_LIBS /System/Library/Frameworks/AudioUnit.framework ${EXTRA_LIBS}) - SET(EXTRA_LIBS /System/Library/Frameworks/ApplicationServices.framework ${EXTRA_LIBS}) +option(ALSOFT_REQUIRE_COREAUDIO "Require CoreAudio backend" OFF) +find_library(COREAUDIO_FRAMEWORK NAMES CoreAudio) +find_path(AUDIOUNIT_INCLUDE_DIR NAMES AudioUnit/AudioUnit.h) +if(COREAUDIO_FRAMEWORK AND AUDIOUNIT_INCLUDE_DIR) + option(ALSOFT_BACKEND_COREAUDIO "Enable CoreAudio backend" ON) + if(ALSOFT_BACKEND_COREAUDIO) + set(HAVE_COREAUDIO 1) + set(ALC_OBJS ${ALC_OBJS} alc/backends/coreaudio.cpp alc/backends/coreaudio.h) + set(BACKENDS "${BACKENDS} CoreAudio,") + if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + find_library(COREFOUNDATION_FRAMEWORK NAMES CoreFoundation) + set(EXTRA_LIBS ${COREAUDIO_FRAMEWORK} ${COREFOUNDATION_FRAMEWORK} ${EXTRA_LIBS}) + else() + set(EXTRA_LIBS ${COREAUDIO_FRAMEWORK} /System/Library/Frameworks/AudioUnit.framework + /System/Library/Frameworks/ApplicationServices.framework ${EXTRA_LIBS}) + endif() # Some versions of OSX may need the AudioToolbox framework. Add it if # it's found. - FIND_LIBRARY(AUDIOTOOLBOX_LIBRARY - NAMES AudioToolbox - PATHS ~/Library/Frameworks - /Library/Frameworks - /System/Library/Frameworks - ) - IF(AUDIOTOOLBOX_LIBRARY) - SET(EXTRA_LIBS ${AUDIOTOOLBOX_LIBRARY} ${EXTRA_LIBS}) - ENDIF() - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_COREAUDIO AND NOT HAVE_COREAUDIO) - MESSAGE(FATAL_ERROR "Failed to enabled required CoreAudio backend") -ENDIF() + find_library(AUDIOTOOLBOX_LIBRARY NAMES AudioToolbox) + if(AUDIOTOOLBOX_LIBRARY) + set(EXTRA_LIBS ${AUDIOTOOLBOX_LIBRARY} ${EXTRA_LIBS}) + endif() + + set(INC_PATHS ${INC_PATHS} ${AUDIOUNIT_INCLUDE_DIR}) + endif() +endif() +if(ALSOFT_REQUIRE_COREAUDIO AND NOT HAVE_COREAUDIO) + message(FATAL_ERROR "Failed to enabled required CoreAudio backend") +endif() # Check for OpenSL (Android) backend -OPTION(ALSOFT_REQUIRE_OPENSL "Require OpenSL backend" OFF) -CHECK_INCLUDE_FILES("SLES/OpenSLES.h;SLES/OpenSLES_Android.h" HAVE_SLES_OPENSLES_ANDROID_H) -IF(HAVE_SLES_OPENSLES_ANDROID_H) - CHECK_SHARED_FUNCTION_EXISTS(slCreateEngine "SLES/OpenSLES.h" OpenSLES "" HAVE_LIBOPENSLES) - IF(HAVE_LIBOPENSLES) - OPTION(ALSOFT_BACKEND_OPENSL "Enable OpenSL backend" ON) - IF(ALSOFT_BACKEND_OPENSL) - SET(HAVE_OPENSL 1) - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/opensl.cpp Alc/backends/opensl.h) - SET(BACKENDS "${BACKENDS} OpenSL,") - SET(EXTRA_LIBS OpenSLES ${EXTRA_LIBS}) - ENDIF() - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_OPENSL AND NOT HAVE_OPENSL) - MESSAGE(FATAL_ERROR "Failed to enabled required OpenSL backend") -ENDIF() +option(ALSOFT_REQUIRE_OPENSL "Require OpenSL backend" OFF) +find_package(OpenSL) +if(OPENSL_FOUND) + option(ALSOFT_BACKEND_OPENSL "Enable OpenSL backend" ON) + if(ALSOFT_BACKEND_OPENSL) + set(HAVE_OPENSL 1) + set(ALC_OBJS ${ALC_OBJS} alc/backends/opensl.cpp alc/backends/opensl.h) + set(BACKENDS "${BACKENDS} OpenSL,") + set(EXTRA_LIBS "OpenSL::OpenSLES" ${EXTRA_LIBS}) + endif() +endif() +if(ALSOFT_REQUIRE_OPENSL AND NOT HAVE_OPENSL) + message(FATAL_ERROR "Failed to enabled required OpenSL backend") +endif() + +# Check for Oboe (Android) backend +set(OBOE_TARGET ) +if(ANDROID) + set(OBOE_SOURCE "" CACHE STRING "Source directory for Oboe.") + if(OBOE_SOURCE) + # Force Oboe to build with hidden symbols. Don't want to be exporting + # them from OpenAL. + set(OLD_CXX_FLAGS ${CMAKE_CXX_FLAGS}) + check_cxx_compiler_flag(-fvisibility=hidden HAVE_VISIBILITY_HIDDEN_SWITCH) + if(HAVE_VISIBILITY_HIDDEN_SWITCH) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") + endif() + add_subdirectory(${OBOE_SOURCE} ./oboe) + set(CMAKE_CXX_FLAGS ${OLD_CXX_FLAGS}) + unset(OLD_CXX_FLAGS) + + set(OBOE_TARGET oboe) + else() + find_package(Oboe) + if(OBOE_FOUND) + set(OBOE_TARGET "oboe::oboe") + endif() + endif() +endif() + +option(ALSOFT_REQUIRE_OBOE "Require Oboe backend" OFF) +if(OBOE_TARGET) + option(ALSOFT_BACKEND_OBOE "Enable Oboe backend" ON) + if(ALSOFT_BACKEND_OBOE) + set(HAVE_OBOE 1) + set(ALC_OBJS ${ALC_OBJS} alc/backends/oboe.cpp alc/backends/oboe.h) + set(BACKENDS "${BACKENDS} Oboe,") + set(EXTRA_LIBS ${OBOE_TARGET} ${EXTRA_LIBS}) + endif() +endif() +if(ALSOFT_REQUIRE_OBOE AND NOT HAVE_OBOE) + message(FATAL_ERROR "Failed to enabled required Oboe backend") +endif() # Check for SDL2 backend -OPTION(ALSOFT_REQUIRE_SDL2 "Require SDL2 backend" OFF) -FIND_PACKAGE(SDL2) -IF(SDL2_FOUND) +option(ALSOFT_REQUIRE_SDL2 "Require SDL2 backend" OFF) +find_package(SDL2) +if(SDL2_FOUND) # Off by default, since it adds a runtime dependency - OPTION(ALSOFT_BACKEND_SDL2 "Enable SDL2 backend" OFF) - IF(ALSOFT_BACKEND_SDL2) - SET(HAVE_SDL2 1) - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/sdl2.cpp Alc/backends/sdl2.h) - SET(BACKENDS "${BACKENDS} SDL2,") - SET(EXTRA_LIBS ${SDL2_LIBRARY} ${EXTRA_LIBS}) - SET(INC_PATHS ${INC_PATHS} ${SDL2_INCLUDE_DIR}) - ENDIF() -ENDIF() -IF(ALSOFT_REQUIRE_SDL2 AND NOT SDL2_FOUND) - MESSAGE(FATAL_ERROR "Failed to enabled required SDL2 backend") -ENDIF() + option(ALSOFT_BACKEND_SDL2 "Enable SDL2 backend" OFF) + if(ALSOFT_BACKEND_SDL2) + set(HAVE_SDL2 1) + set(ALC_OBJS ${ALC_OBJS} alc/backends/sdl2.cpp alc/backends/sdl2.h) + set(BACKENDS "${BACKENDS} SDL2,") + set(EXTRA_LIBS ${SDL2_LIBRARY} ${EXTRA_LIBS}) + set(INC_PATHS ${INC_PATHS} ${SDL2_INCLUDE_DIR}) + endif() +endif() +if(ALSOFT_REQUIRE_SDL2 AND NOT SDL2_FOUND) + message(FATAL_ERROR "Failed to enabled required SDL2 backend") +endif() # Optionally enable the Wave Writer backend -OPTION(ALSOFT_BACKEND_WAVE "Enable Wave Writer backend" ON) -IF(ALSOFT_BACKEND_WAVE) - SET(HAVE_WAVE 1) - SET(ALC_OBJS ${ALC_OBJS} Alc/backends/wave.cpp Alc/backends/wave.h) - SET(BACKENDS "${BACKENDS} WaveFile,") -ENDIF() +option(ALSOFT_BACKEND_WAVE "Enable Wave Writer backend" ON) +if(ALSOFT_BACKEND_WAVE) + set(HAVE_WAVE 1) + set(ALC_OBJS ${ALC_OBJS} alc/backends/wave.cpp alc/backends/wave.h) + set(BACKENDS "${BACKENDS} WaveFile,") +endif() # This is always available -SET(BACKENDS "${BACKENDS} Null") +set(BACKENDS "${BACKENDS} Null") -FIND_PACKAGE(Git) -IF(GIT_FOUND AND EXISTS "${OpenAL_SOURCE_DIR}/.git") +find_package(Git) +if(ALSOFT_UPDATE_BUILD_VERSION AND GIT_FOUND AND EXISTS "${OpenAL_SOURCE_DIR}/.git") # Get the current working branch and its latest abbreviated commit hash - ADD_CUSTOM_TARGET(build_version - ${CMAKE_COMMAND} -D GIT_EXECUTABLE=${GIT_EXECUTABLE} - -D LIB_VERSION=${LIB_VERSION} - -D SRC=${OpenAL_SOURCE_DIR}/version.h.in - -D DST=${OpenAL_BINARY_DIR}/version.h - -P ${OpenAL_SOURCE_DIR}/version.cmake + add_custom_target(build_version + ${CMAKE_COMMAND} -D GIT_EXECUTABLE=${GIT_EXECUTABLE} -D LIB_VERSION=${LIB_VERSION} + -D LIB_VERSION_NUM=${LIB_VERSION_NUM} -D SRC=${OpenAL_SOURCE_DIR}/version.h.in + -D DST=${OpenAL_BINARY_DIR}/version.h -P ${OpenAL_SOURCE_DIR}/version.cmake WORKING_DIRECTORY "${OpenAL_SOURCE_DIR}" VERBATIM ) -ELSE() - SET(GIT_BRANCH "UNKNOWN") - SET(GIT_COMMIT_HASH "unknown") - CONFIGURE_FILE( +else() + set(GIT_BRANCH "UNKNOWN") + set(GIT_COMMIT_HASH "unknown") + configure_file( "${OpenAL_SOURCE_DIR}/version.h.in" "${OpenAL_BINARY_DIR}/version.h") -ENDIF() - - -SET(NATIVE_SRC_DIR "${OpenAL_SOURCE_DIR}/native-tools") - -SET(ALSOFT_NATIVE_TOOLS_PATH "" CACHE STRING "Path to prebuilt native tools (leave blank to auto-build)") -IF(ALSOFT_NATIVE_TOOLS_PATH) - SET(BIN2H_COMMAND "${ALSOFT_NATIVE_TOOLS_PATH}/bin2h") - SET(BSINCGEN_COMMAND "${ALSOFT_NATIVE_TOOLS_PATH}/bsincgen") -ELSE() - SET(NATIVE_BIN_DIR "${OpenAL_BINARY_DIR}/native-tools") - FILE(MAKE_DIRECTORY "${NATIVE_BIN_DIR}") - - SET(BIN2H_COMMAND "${NATIVE_BIN_DIR}/bin2h") - SET(BSINCGEN_COMMAND "${NATIVE_BIN_DIR}/bsincgen") - ADD_CUSTOM_COMMAND(OUTPUT "${BIN2H_COMMAND}" "${BSINCGEN_COMMAND}" - COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" "${NATIVE_SRC_DIR}" - COMMAND ${CMAKE_COMMAND} -E remove "${BIN2H_COMMAND}" "${BSINCGEN_COMMAND}" - COMMAND ${CMAKE_COMMAND} --build . --config "Release" - WORKING_DIRECTORY "${NATIVE_BIN_DIR}" - DEPENDS "${NATIVE_SRC_DIR}/CMakeLists.txt" - IMPLICIT_DEPENDS C "${NATIVE_SRC_DIR}/bin2h.c" - C "${NATIVE_SRC_DIR}/bsincgen.c" - VERBATIM - ) -ENDIF() -ADD_CUSTOM_TARGET(native-tools - DEPENDS "${BIN2H_COMMAND}" "${BSINCGEN_COMMAND}" - VERBATIM -) +endif() + option(ALSOFT_EMBED_HRTF_DATA "Embed the HRTF data files (increases library footprint)" ON) if(ALSOFT_EMBED_HRTF_DATA) - MACRO(make_hrtf_header FILENAME VARNAME) - SET(infile "${OpenAL_SOURCE_DIR}/hrtf/${FILENAME}") - SET(outfile "${OpenAL_BINARY_DIR}/${FILENAME}.h") - - ADD_CUSTOM_COMMAND(OUTPUT "${outfile}" - COMMAND "${BIN2H_COMMAND}" "${infile}" "${outfile}" ${VARNAME} - DEPENDS native-tools "${infile}" + macro(make_hrtf_header FILENAME VARNAME) + set(infile "${OpenAL_SOURCE_DIR}/hrtf/${FILENAME}") + set(outfile "${OpenAL_BINARY_DIR}/${VARNAME}.h") + + add_custom_command(OUTPUT "${outfile}" + COMMAND ${CMAKE_COMMAND} -D "INPUT_FILE=${infile}" -D "OUTPUT_FILE=${outfile}" + -D "VARIABLE_NAME=${VARNAME}" -P "${CMAKE_MODULE_PATH}/bin2h.script.cmake" + WORKING_DIRECTORY "${OpenAL_SOURCE_DIR}" + DEPENDS "${infile}" "${CMAKE_MODULE_PATH}/bin2h.script.cmake" VERBATIM ) - SET(ALC_OBJS ${ALC_OBJS} "${outfile}") - ENDMACRO() + set(ALC_OBJS ${ALC_OBJS} "${outfile}") + endmacro() - make_hrtf_header(default-44100.mhr "hrtf_default_44100") - make_hrtf_header(default-48000.mhr "hrtf_default_48000") + make_hrtf_header("Default HRTF.mhr" "hrtf_default") endif() -ADD_CUSTOM_COMMAND(OUTPUT "${OpenAL_BINARY_DIR}/bsinc_inc.h" - COMMAND "${BSINCGEN_COMMAND}" "${OpenAL_BINARY_DIR}/bsinc_inc.h" - DEPENDS native-tools "${NATIVE_SRC_DIR}/bsincgen.c" - VERBATIM -) -SET(ALC_OBJS ${ALC_OBJS} "${OpenAL_BINARY_DIR}/bsinc_inc.h") - - -IF(ALSOFT_UTILS AND NOT ALSOFT_NO_CONFIG_UTIL) - add_subdirectory(utils/alsoft-config) -ENDIF() -IF(ALSOFT_EXAMPLES) - IF(NOT SDL2_FOUND) - FIND_PACKAGE(SDL2) - ENDIF() - IF(SDL2_FOUND) - FIND_PACKAGE(SDL_sound) - FIND_PACKAGE(FFmpeg COMPONENTS AVFORMAT AVCODEC AVUTIL SWSCALE SWRESAMPLE) - ENDIF() -ENDIF() - -IF(NOT WIN32) - SET(LIBNAME "openal") -ELSE() - SET(LIBNAME "OpenAL32") -ENDIF() + +if(ALSOFT_UTILS) + find_package(MySOFA) + if(NOT ALSOFT_NO_CONFIG_UTIL) + find_package(Qt5Widgets QUIET) + if(NOT Qt5Widgets_FOUND) + message(STATUS "Could NOT find Qt5Widgets") + endif() + endif() +endif() +if(ALSOFT_UTILS OR ALSOFT_EXAMPLES) + find_package(SndFile) + find_package(SDL2) + if(SDL2_FOUND) + find_package(FFmpeg COMPONENTS AVFORMAT AVCODEC AVUTIL SWSCALE SWRESAMPLE) + endif() +endif() + +if(NOT WIN32) + set(LIBNAME "openal") +else() + set(LIBNAME "OpenAL32") +endif() # Needed for openal.pc.in -SET(prefix ${CMAKE_INSTALL_PREFIX}) -SET(exec_prefix "\${prefix}") -SET(libdir "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}") -SET(bindir "\${exec_prefix}/${CMAKE_INSTALL_BINDIR}") -SET(includedir "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") -SET(PACKAGE_VERSION "${LIB_VERSION}") -SET(PKG_CONFIG_CFLAGS ) -SET(PKG_CONFIG_PRIVATE_LIBS ) -IF(LIBTYPE STREQUAL "STATIC") - SET(PKG_CONFIG_CFLAGS -DAL_LIBTYPE_STATIC) - FOREACH(FLAG ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) +set(prefix ${CMAKE_INSTALL_PREFIX}) +set(exec_prefix "\${prefix}") +set(libdir "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}") +set(bindir "\${exec_prefix}/${CMAKE_INSTALL_BINDIR}") +set(includedir "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") +set(PACKAGE_VERSION "${LIB_VERSION}") +set(PKG_CONFIG_CFLAGS ) +set(PKG_CONFIG_PRIVATE_LIBS ) +if(LIBTYPE STREQUAL "STATIC") + set(PKG_CONFIG_CFLAGS -DAL_LIBTYPE_STATIC) + foreach(FLAG ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) # If this is already a linker flag, or is a full path+file, add it # as-is. Otherwise, it's a name intended to be dressed as -lname. - IF(FLAG MATCHES "^-.*" OR EXISTS "${FLAG}") - SET(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} ${FLAG}") - ELSE() - SET(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} -l${FLAG}") - ENDIF() - ENDFOREACH() -ENDIF() + if(FLAG MATCHES "^-.*" OR EXISTS "${FLAG}") + set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} ${FLAG}") + else() + set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} -l${FLAG}") + endif() + endforeach() +endif() # End configuration -CONFIGURE_FILE( +configure_file( "${OpenAL_SOURCE_DIR}/config.h.in" "${OpenAL_BINARY_DIR}/config.h") -CONFIGURE_FILE( +configure_file( "${OpenAL_SOURCE_DIR}/openal.pc.in" "${OpenAL_BINARY_DIR}/openal.pc" @ONLY) -UNSET(HAS_ROUTER) -SET(IMPL_TARGET OpenAL) # Either OpenAL or soft_oal. -SET(SUBSYS_FLAG ) +add_library(common STATIC EXCLUDE_FROM_ALL ${COMMON_OBJS}) +target_include_directories(common PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/include) +target_compile_definitions(common PRIVATE ${CPP_DEFS}) +target_compile_options(common PRIVATE ${C_FLAGS}) +set_target_properties(common PROPERTIES POSITION_INDEPENDENT_CODE TRUE) + + +unset(HAS_ROUTER) +set(IMPL_TARGET OpenAL) # Either OpenAL or soft_oal. # Build main library -IF(LIBTYPE STREQUAL "STATIC") - SET(CPP_DEFS ${CPP_DEFS} AL_LIBTYPE_STATIC) - IF(WIN32 AND ALSOFT_NO_UID_DEFS) - SET(CPP_DEFS ${CPP_DEFS} AL_NO_UID_DEFS) - ENDIF() - ADD_LIBRARY(OpenAL STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS}) -ELSE() - - IF(WIN32) - IF(MSVC) - SET(SUBSYS_FLAG ${SUBSYS_FLAG} "/SUBSYSTEM:WINDOWS") - ELSEIF(CMAKE_COMPILER_IS_GNUCC) - SET(SUBSYS_FLAG ${SUBSYS_FLAG} "-mwindows") - ENDIF() - ENDIF() - - IF(WIN32 AND ALSOFT_BUILD_ROUTER) - ADD_LIBRARY(OpenAL SHARED +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}) + + if(WIN32) + # This option is for static linking OpenAL Soft into another project + # that already defines the IDs. It is up to that project to ensure all + # required IDs are defined. + option(ALSOFT_NO_UID_DEFS "Do not define GUIDs, IIDs, CLSIDs, or PropertyKeys" OFF) + if(ALSOFT_NO_UID_DEFS) + target_compile_definitions(${IMPL_TARGET} PRIVATE AL_NO_UID_DEFS) + endif() + endif() +else() + set(RC_CONFIG resources/openal32.rc) + if(WIN32 AND ALSOFT_BUILD_ROUTER) + add_library(OpenAL SHARED + resources/router.rc router/router.cpp router/router.h router/alc.cpp router/al.cpp - ${COMMON_OBJS} ) - TARGET_COMPILE_DEFINITIONS(OpenAL - PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES ${CPP_DEFS}) - TARGET_COMPILE_OPTIONS(OpenAL PRIVATE ${C_FLAGS}) - TARGET_LINK_LIBRARIES(OpenAL PRIVATE ${LINKER_FLAGS}) - TARGET_INCLUDE_DIRECTORIES(OpenAL + target_compile_definitions(OpenAL + PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" + "AL_API=${EXPORT_DECL}" ${CPP_DEFS}) + target_compile_options(OpenAL PRIVATE ${C_FLAGS}) + target_link_libraries(OpenAL PRIVATE common ${LINKER_FLAGS}) + target_include_directories(OpenAL PUBLIC $ - $ + $ PRIVATE ${OpenAL_SOURCE_DIR}/common ${OpenAL_BINARY_DIR} ) - SET_TARGET_PROPERTIES(OpenAL PROPERTIES PREFIX "") - SET_TARGET_PROPERTIES(OpenAL PROPERTIES OUTPUT_NAME ${LIBNAME}) - IF(TARGET build_version) - ADD_DEPENDENCIES(OpenAL build_version) - ENDIF() - SET(HAS_ROUTER 1) - - SET(LIBNAME "soft_oal") - SET(IMPL_TARGET soft_oal) - ENDIF() - - ADD_LIBRARY(${IMPL_TARGET} SHARED ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS}) - IF(WIN32) - SET_TARGET_PROPERTIES(${IMPL_TARGET} PROPERTIES PREFIX "") - ENDIF() -ENDIF() - -TARGET_LINK_LIBRARIES(${IMPL_TARGET} - PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) - -TARGET_INCLUDE_DIRECTORIES(${IMPL_TARGET} + set_target_properties(OpenAL PROPERTIES PREFIX "") + set_target_properties(OpenAL PROPERTIES OUTPUT_NAME ${LIBNAME}) + if(TARGET build_version) + add_dependencies(OpenAL build_version) + endif() + set(HAS_ROUTER 1) + + set(LIBNAME "soft_oal") + set(IMPL_TARGET soft_oal) + set(RC_CONFIG resources/soft_oal.rc) + endif() + + # !important: for osx framework public header works, the headers must be in + # the project + set(TARGET_PUBLIC_HEADERS include/AL/al.h include/AL/alc.h include/AL/alext.h include/AL/efx.h) + add_library(${IMPL_TARGET} SHARED ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS} ${RC_CONFIG} + ${TARGET_PUBLIC_HEADERS}) + if(WIN32) + set_target_properties(${IMPL_TARGET} PROPERTIES PREFIX "") + endif() + target_link_libraries(${IMPL_TARGET} PRIVATE common ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) + + if(APPLE AND ALSOFT_OSX_FRAMEWORK) + # Sets framework name to soft_oal to avoid ambiguity with the system OpenAL.framework + set(LIBNAME "soft_oal") + if(GIT_FOUND) + EXECUTE_PROCESS(COMMAND ${GIT_EXECUTABLE} rev-list --count head + TIMEOUT 5 + OUTPUT_VARIABLE BUNDLE_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE) + else() + set(BUNDLE_VERSION 1) + endif() + + # Build: Fix rpath in iOS shared libraries + # If this flag is not set, the final dylib binary ld path will be + # /User/xxx/*/soft_oal.framework/soft_oal, which can't be loaded by iOS devices. + # See also: https://github.com/libjpeg-turbo/libjpeg-turbo/commit/c80ddef7a4ce21ace9e3ca0fd190d320cc8cdaeb + if(NOT CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG) + set(CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG "-Wl,-rpath,") + endif() + + set_target_properties(${IMPL_TARGET} PROPERTIES + FRAMEWORK TRUE + FRAMEWORK_VERSION C + MACOSX_FRAMEWORK_NAME "${IMPL_TARGET}" + MACOSX_FRAMEWORK_IDENTIFIER "org.openal-soft.openal" + MACOSX_FRAMEWORK_SHORT_VERSION_STRING ${LIB_VERSION} + MACOSX_FRAMEWORK_BUNDLE_VERSION ${BUNDLE_VERSION} + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "" + XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO" + XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO" + PUBLIC_HEADER "${TARGET_PUBLIC_HEADERS}" + MACOSX_RPATH TRUE) + endif() +endif() + +target_include_directories(${IMPL_TARGET} PUBLIC $ - $ + INTERFACE + $ + $ + $ PRIVATE ${INC_PATHS} ${OpenAL_BINARY_DIR} - ${OpenAL_SOURCE_DIR}/Alc - ${OpenAL_SOURCE_DIR}/OpenAL32/Include + ${OpenAL_SOURCE_DIR} ${OpenAL_SOURCE_DIR}/common ) -SET_TARGET_PROPERTIES(${IMPL_TARGET} PROPERTIES OUTPUT_NAME ${LIBNAME} +set_target_properties(${IMPL_TARGET} PROPERTIES OUTPUT_NAME ${LIBNAME} VERSION ${LIB_VERSION} SOVERSION ${LIB_MAJOR_VERSION} ) -TARGET_COMPILE_DEFINITIONS(${IMPL_TARGET} - PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES ${CPP_DEFS}) -TARGET_COMPILE_OPTIONS(${IMPL_TARGET} PRIVATE ${C_FLAGS}) - -IF(TARGET build_version) - ADD_DEPENDENCIES(${IMPL_TARGET} build_version) -ENDIF() - -IF(WIN32 AND MINGW AND ALSOFT_BUILD_IMPORT_LIB AND NOT LIBTYPE STREQUAL "STATIC") - FIND_PROGRAM(SED_EXECUTABLE NAMES sed DOC "sed executable") - FIND_PROGRAM(DLLTOOL_EXECUTABLE NAMES "${DLLTOOL}" DOC "dlltool executable") - IF(NOT SED_EXECUTABLE OR NOT DLLTOOL_EXECUTABLE) - MESSAGE(STATUS "") - IF(NOT SED_EXECUTABLE) - MESSAGE(STATUS "WARNING: Cannot find sed, disabling .def/.lib generation") - ENDIF() - IF(NOT DLLTOOL_EXECUTABLE) - MESSAGE(STATUS "WARNING: Cannot find dlltool, disabling .def/.lib generation") - ENDIF() - ELSE() - SET_PROPERTY(TARGET OpenAL APPEND_STRING PROPERTY LINK_FLAGS - " -Wl,--output-def,OpenAL32.def") - ADD_CUSTOM_COMMAND(TARGET OpenAL POST_BUILD +target_compile_definitions(${IMPL_TARGET} + 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) +endif() + +if(WIN32 AND MINGW AND ALSOFT_BUILD_IMPORT_LIB AND NOT LIBTYPE STREQUAL "STATIC") + find_program(SED_EXECUTABLE NAMES sed DOC "sed executable") + if(NOT SED_EXECUTABLE OR NOT CMAKE_DLLTOOL) + message(STATUS "") + if(NOT SED_EXECUTABLE) + message(STATUS "WARNING: Cannot find sed, disabling .def/.lib generation") + endif() + if(NOT CMAKE_DLLTOOL) + message(STATUS "WARNING: Cannot find dlltool, disabling .def/.lib generation") + endif() + else() + set_property(TARGET OpenAL APPEND_STRING PROPERTY + LINK_FLAGS " -Wl,--output-def,OpenAL32.def") + add_custom_command(TARGET OpenAL POST_BUILD COMMAND "${SED_EXECUTABLE}" -i -e "s/ @[^ ]*//" OpenAL32.def - COMMAND "${DLLTOOL_EXECUTABLE}" -d OpenAL32.def -l OpenAL32.lib -D OpenAL32.dll + COMMAND "${CMAKE_DLLTOOL}" -d OpenAL32.def -l OpenAL32.lib -D OpenAL32.dll COMMENT "Stripping ordinals from OpenAL32.def and generating OpenAL32.lib..." VERBATIM ) - ENDIF() -ENDIF() - -IF(ALSOFT_INSTALL) - INSTALL(TARGETS OpenAL EXPORT OpenAL - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/AL - ) - EXPORT(TARGETS OpenAL - NAMESPACE OpenAL:: - FILE OpenALConfig.cmake) - INSTALL(EXPORT OpenAL - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL - NAMESPACE OpenAL:: - FILE OpenALConfig.cmake) - INSTALL(FILES include/AL/al.h - include/AL/alc.h - include/AL/alext.h - include/AL/efx.h - include/AL/efx-creative.h - include/AL/efx-presets.h - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/AL - ) - INSTALL(FILES "${OpenAL_BINARY_DIR}/openal.pc" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") - IF(TARGET soft_oal) - INSTALL(TARGETS soft_oal - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - ) - ENDIF() -ENDIF() - + endif() +endif() if(HAS_ROUTER) message(STATUS "") message(STATUS "Building DLL router") endif() -MESSAGE(STATUS "") -MESSAGE(STATUS "Building OpenAL with support for the following backends:") -MESSAGE(STATUS " ${BACKENDS}") -MESSAGE(STATUS "") -MESSAGE(STATUS "Building with support for CPU extensions:") -MESSAGE(STATUS " ${CPU_EXTS}") -MESSAGE(STATUS "") -IF(FPMATH_SET) - MESSAGE(STATUS "Building with SSE${FPMATH_SET} codegen") - MESSAGE(STATUS "") -ENDIF() - -IF(WIN32) - IF(NOT HAVE_DSOUND) - MESSAGE(STATUS "WARNING: Building the Windows version without DirectSound output") - MESSAGE(STATUS " This is probably NOT what you want!") - MESSAGE(STATUS "") - ENDIF() -ENDIF() +message(STATUS "") +message(STATUS "Building OpenAL with support for the following backends:") +message(STATUS " ${BACKENDS}") +message(STATUS "") +message(STATUS "Building with support for CPU extensions:") +message(STATUS " ${CPU_EXTS}") +message(STATUS "") +if(FPMATH_SET) + message(STATUS "Building with SSE${FPMATH_SET} codegen") + message(STATUS "") +endif() + +if(ALSOFT_EAX) + message(STATUS "Building with legacy EAX extension support") + message(STATUS "") +endif() if(ALSOFT_EMBED_HRTF_DATA) message(STATUS "Embedding HRTF datasets") message(STATUS "") endif() -# Install alsoft.conf configuration file -IF(ALSOFT_CONFIG) - INSTALL(FILES alsoftrc.sample - DESTINATION ${CMAKE_INSTALL_DATADIR}/openal - ) - MESSAGE(STATUS "Installing sample configuration") - MESSAGE(STATUS "") -ENDIF() - -# Install HRTF definitions -IF(ALSOFT_HRTF_DEFS) - INSTALL(FILES hrtf/default-44100.mhr - hrtf/default-48000.mhr - DESTINATION ${CMAKE_INSTALL_DATADIR}/openal/hrtf - ) - MESSAGE(STATUS "Installing HRTF definitions") - MESSAGE(STATUS "") -ENDIF() - -# Install AmbDec presets -IF(ALSOFT_AMBDEC_PRESETS) - INSTALL(FILES presets/3D7.1.ambdec - presets/hexagon.ambdec - presets/itu5.1.ambdec - presets/itu5.1-nocenter.ambdec - presets/rectangle.ambdec - presets/square.ambdec - presets/presets.txt - DESTINATION ${CMAKE_INSTALL_DATADIR}/openal/presets - ) - MESSAGE(STATUS "Installing AmbDec presets") - MESSAGE(STATUS "") -ENDIF() +# An alias for sub-project builds +add_library(OpenAL::OpenAL ALIAS OpenAL) + +# Install main library +if(ALSOFT_INSTALL) + configure_package_config_file(OpenALConfig.cmake.in OpenALConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL) + install(TARGETS OpenAL EXPORT OpenAL + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + FRAMEWORK DESTINATION ${CMAKE_INSTALL_LIBDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/AL) + export(TARGETS OpenAL + NAMESPACE OpenAL:: + FILE OpenALTargets.cmake) + install(EXPORT OpenAL + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL + NAMESPACE OpenAL:: + FILE OpenALTargets.cmake) + install(DIRECTORY include/AL + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + install(FILES "${OpenAL_BINARY_DIR}/openal.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + install(FILES "${OpenAL_BINARY_DIR}/OpenALConfig.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL") + if(TARGET soft_oal) + install(TARGETS soft_oal + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + endif() + message(STATUS "Installing library and headers") +else() + message(STATUS "NOT installing library and headers") +endif() -IF(ALSOFT_UTILS) - set(UTIL_TARGETS ) +if(ALSOFT_INSTALL_CONFIG) + install(FILES alsoftrc.sample + DESTINATION ${CMAKE_INSTALL_DATADIR}/openal) + message(STATUS "Installing sample configuration") +endif() - ADD_EXECUTABLE(openal-info utils/openal-info.c) - TARGET_INCLUDE_DIRECTORIES(openal-info PRIVATE ${OpenAL_SOURCE_DIR}/common) - TARGET_COMPILE_OPTIONS(openal-info PRIVATE ${C_FLAGS}) - TARGET_LINK_LIBRARIES(openal-info PRIVATE ${LINKER_FLAGS} OpenAL) - set(UTIL_TARGETS ${UTIL_TARGETS} openal-info) +if(ALSOFT_INSTALL_HRTF_DATA) + install(DIRECTORY hrtf + DESTINATION ${CMAKE_INSTALL_DATADIR}/openal) + message(STATUS "Installing HRTF data files") +endif() + +if(ALSOFT_INSTALL_AMBDEC_PRESETS) + install(DIRECTORY presets + DESTINATION ${CMAKE_INSTALL_DATADIR}/openal) + message(STATUS "Installing AmbDec presets") +endif() +message(STATUS "") + +set(UNICODE_FLAG ) +if(MINGW) + set(UNICODE_FLAG ${UNICODE_FLAG} -municode) +endif() +set(EXTRA_INSTALLS ) +if(ALSOFT_UTILS) + add_executable(openal-info utils/openal-info.c) + target_include_directories(openal-info PRIVATE ${OpenAL_SOURCE_DIR}/common) + target_compile_options(openal-info PRIVATE ${C_FLAGS}) + target_link_libraries(openal-info PRIVATE ${LINKER_FLAGS} OpenAL ${UNICODE_FLAG}) + if(ALSOFT_INSTALL_EXAMPLES) + set(EXTRA_INSTALLS ${EXTRA_INSTALLS} openal-info) + endif() + + if(SNDFILE_FOUND) + add_executable(uhjdecoder utils/uhjdecoder.cpp) + target_compile_definitions(uhjdecoder PRIVATE ${CPP_DEFS}) + target_include_directories(uhjdecoder + PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) + target_compile_options(uhjdecoder PRIVATE ${C_FLAGS}) + target_link_libraries(uhjdecoder PUBLIC common + PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG}) + + add_executable(uhjencoder utils/uhjencoder.cpp) + target_compile_definitions(uhjencoder PRIVATE ${CPP_DEFS}) + target_include_directories(uhjencoder + PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) + target_compile_options(uhjencoder PRIVATE ${C_FLAGS}) + target_link_libraries(uhjencoder PUBLIC common + PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG}) + endif() - find_package(MySOFA) 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 common MySOFA::MySOFA PRIVATE ${LINKER_FLAGS}) + set(MAKEMHR_SRCS utils/makemhr/loaddef.cpp utils/makemhr/loaddef.h @@ -1489,173 +1563,148 @@ IF(ALSOFT_UTILS) add_executable(makemhr ${MAKEMHR_SRCS}) target_compile_definitions(makemhr PRIVATE ${CPP_DEFS}) target_include_directories(makemhr - PRIVATE ${OpenAL_SOURCE_DIR}/common ${OpenAL_BINARY_DIR}) + PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/utils) target_compile_options(makemhr PRIVATE ${C_FLAGS}) - target_link_libraries(makemhr PRIVATE ${LINKER_FLAGS} MySOFA::MySOFA) - set(UTIL_TARGETS ${UTIL_TARGETS} makemhr) + target_link_libraries(makemhr PRIVATE ${LINKER_FLAGS} sofa-support ${UNICODE_FLAG}) + if(ALSOFT_INSTALL_EXAMPLES) + set(EXTRA_INSTALLS ${EXTRA_INSTALLS} makemhr) + endif() set(SOFAINFO_SRCS utils/sofa-info.cpp) add_executable(sofa-info ${SOFAINFO_SRCS}) target_compile_definitions(sofa-info PRIVATE ${CPP_DEFS}) - target_include_directories(sofa-info PRIVATE ${OpenAL_SOURCE_DIR}/common) + target_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} MySOFA::MySOFA) + target_link_libraries(sofa-info PRIVATE ${LINKER_FLAGS} sofa-support ${UNICODE_FLAG}) endif() + message(STATUS "Building utility programs") - IF(ALSOFT_INSTALL) - INSTALL(TARGETS ${UTIL_TARGETS} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - ) - ENDIF() - - MESSAGE(STATUS "Building utility programs") - IF(TARGET alsoft-config) - MESSAGE(STATUS "Building configuration program") - ENDIF() - MESSAGE(STATUS "") -ENDIF() - -IF(ALSOFT_TESTS) - ADD_EXECUTABLE(altonegen - examples/altonegen.c - examples/common/alhelpers.c - ) - TARGET_COMPILE_DEFINITIONS(altonegen PRIVATE ${CPP_DEFS}) - TARGET_INCLUDE_DIRECTORIES(altonegen PRIVATE ${OpenAL_SOURCE_DIR}/common) - TARGET_COMPILE_OPTIONS(altonegen PRIVATE ${C_FLAGS}) - TARGET_LINK_LIBRARIES(altonegen PRIVATE ${LINKER_FLAGS} OpenAL ${MATH_LIB}) - - IF(ALSOFT_INSTALL) - INSTALL(TARGETS altonegen - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - ) - ENDIF() - - MESSAGE(STATUS "Building test programs") - MESSAGE(STATUS "") -ENDIF() - -IF(ALSOFT_EXAMPLES) - # Add a static library with common functions used by multiple targets - ADD_LIBRARY(ex-common STATIC examples/common/alhelpers.c) - TARGET_COMPILE_DEFINITIONS(ex-common PUBLIC ${CPP_DEFS}) - TARGET_INCLUDE_DIRECTORIES(ex-common PUBLIC ${OpenAL_SOURCE_DIR}/common) - TARGET_COMPILE_OPTIONS(ex-common PUBLIC ${C_FLAGS}) - TARGET_LINK_LIBRARIES(ex-common PUBLIC OpenAL) - - ADD_EXECUTABLE(alrecord examples/alrecord.c) - TARGET_LINK_LIBRARIES(alrecord PRIVATE ${LINKER_FLAGS} ex-common) - - IF(ALSOFT_INSTALL) - INSTALL(TARGETS alrecord - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - ) - ENDIF() - - MESSAGE(STATUS "Building example programs") - - IF(SDL2_FOUND) - IF(SDL_SOUND_FOUND) - ADD_EXECUTABLE(alplay examples/alplay.c) - TARGET_INCLUDE_DIRECTORIES(alplay - PRIVATE ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR}) - TARGET_LINK_LIBRARIES(alplay - PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common) - - ADD_EXECUTABLE(alstream examples/alstream.c) - TARGET_INCLUDE_DIRECTORIES(alstream - PRIVATE ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR}) - TARGET_LINK_LIBRARIES(alstream - PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common) - - ADD_EXECUTABLE(alreverb examples/alreverb.c) - TARGET_INCLUDE_DIRECTORIES(alreverb - PRIVATE ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR}) - TARGET_LINK_LIBRARIES(alreverb - PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common) - - ADD_EXECUTABLE(almultireverb examples/almultireverb.c) - TARGET_INCLUDE_DIRECTORIES(almultireverb - PRIVATE ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR}) - TARGET_LINK_LIBRARIES(almultireverb - PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common - ${MATH_LIB}) - - ADD_EXECUTABLE(allatency examples/allatency.c) - TARGET_INCLUDE_DIRECTORIES(allatency - PRIVATE ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR}) - TARGET_LINK_LIBRARIES(allatency - PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common) - - ADD_EXECUTABLE(alloopback examples/alloopback.c) - TARGET_INCLUDE_DIRECTORIES(alloopback - PRIVATE ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR}) - TARGET_LINK_LIBRARIES(alloopback - PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common ${MATH_LIB}) - - ADD_EXECUTABLE(alhrtf examples/alhrtf.c) - TARGET_INCLUDE_DIRECTORIES(alhrtf - PRIVATE ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR}) - TARGET_LINK_LIBRARIES(alhrtf - PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common ${MATH_LIB}) - - IF(ALSOFT_INSTALL) - INSTALL(TARGETS alplay alstream alreverb almultireverb allatency alloopback alhrtf - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - ) - ENDIF() - - MESSAGE(STATUS "Building SDL_sound example programs") - ENDIF() - - SET(FFVER_OK FALSE) - IF(FFMPEG_FOUND) - SET(FFVER_OK TRUE) - IF(AVFORMAT_VERSION VERSION_LESS "57.56.101") - MESSAGE(STATUS "libavformat is too old! (${AVFORMAT_VERSION}, wanted 57.56.101)") - SET(FFVER_OK FALSE) - ENDIF() - IF(AVCODEC_VERSION VERSION_LESS "57.64.101") - MESSAGE(STATUS "libavcodec is too old! (${AVCODEC_VERSION}, wanted 57.64.101)") - SET(FFVER_OK FALSE) - ENDIF() - IF(AVUTIL_VERSION VERSION_LESS "55.34.101") - MESSAGE(STATUS "libavutil is too old! (${AVUTIL_VERSION}, wanted 55.34.101)") - SET(FFVER_OK FALSE) - ENDIF() - IF(SWSCALE_VERSION VERSION_LESS "4.2.100") - MESSAGE(STATUS "libswscale is too old! (${SWSCALE_VERSION}, wanted 4.2.100)") - SET(FFVER_OK FALSE) - ENDIF() - IF(SWRESAMPLE_VERSION VERSION_LESS "2.3.100") - MESSAGE(STATUS "libswresample is too old! (${SWRESAMPLE_VERSION}, wanted 2.3.100)") - SET(FFVER_OK FALSE) - ENDIF() - ENDIF() - IF(FFVER_OK) - ADD_EXECUTABLE(alffplay examples/alffplay.cpp) - TARGET_INCLUDE_DIRECTORIES(alffplay + if(NOT ALSOFT_NO_CONFIG_UTIL) + add_subdirectory(utils/alsoft-config) + endif() + message(STATUS "") +endif() + + +# Add a static library with common functions used by multiple example targets +add_library(ex-common STATIC EXCLUDE_FROM_ALL + examples/common/alhelpers.c + examples/common/alhelpers.h) +target_compile_definitions(ex-common PUBLIC ${CPP_DEFS}) +target_include_directories(ex-common PUBLIC ${OpenAL_SOURCE_DIR}/common) +target_compile_options(ex-common PUBLIC ${C_FLAGS}) +target_link_libraries(ex-common PUBLIC OpenAL PRIVATE ${RT_LIB}) + +if(ALSOFT_EXAMPLES) + add_executable(altonegen examples/altonegen.c) + target_link_libraries(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} ex-common ${UNICODE_FLAG}) + + add_executable(alrecord examples/alrecord.c) + target_link_libraries(alrecord PRIVATE ${LINKER_FLAGS} ex-common ${UNICODE_FLAG}) + + if(ALSOFT_INSTALL_EXAMPLES) + set(EXTRA_INSTALLS ${EXTRA_INSTALLS} altonegen alrecord) + endif() + + message(STATUS "Building example programs") + + if(SNDFILE_FOUND) + add_executable(alplay examples/alplay.c) + target_link_libraries(alplay PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + ${UNICODE_FLAG}) + + add_executable(alstream examples/alstream.c) + target_link_libraries(alstream PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + ${UNICODE_FLAG}) + + add_executable(alreverb examples/alreverb.c) + target_link_libraries(alreverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + ${UNICODE_FLAG}) + + add_executable(almultireverb examples/almultireverb.c) + target_link_libraries(almultireverb + PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common ${MATH_LIB} ${UNICODE_FLAG}) + + add_executable(allatency examples/allatency.c) + target_link_libraries(allatency PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + ${UNICODE_FLAG}) + + add_executable(alhrtf examples/alhrtf.c) + target_link_libraries(alhrtf + PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common ${MATH_LIB} ${UNICODE_FLAG}) + + add_executable(alstreamcb examples/alstreamcb.cpp) + target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + ${UNICODE_FLAG}) + + add_executable(alconvolve examples/alconvolve.c) + target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} common SndFile::SndFile ex-common + ${UNICODE_FLAG}) + + if(ALSOFT_INSTALL_EXAMPLES) + set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alplay alstream alreverb almultireverb allatency + alhrtf) + endif() + + message(STATUS "Building SndFile example programs") + endif() + + if(SDL2_FOUND) + add_executable(alloopback examples/alloopback.c) + target_include_directories(alloopback PRIVATE ${SDL2_INCLUDE_DIR}) + target_link_libraries(alloopback + PRIVATE ${LINKER_FLAGS} ${SDL2_LIBRARY} ex-common ${MATH_LIB}) + + 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) + if(AVFORMAT_VERSION VERSION_LESS "57.56.101") + message(STATUS "libavformat is too old! (${AVFORMAT_VERSION}, wanted 57.56.101)") + set(FFVER_OK FALSE) + endif() + if(AVCODEC_VERSION VERSION_LESS "57.64.101") + message(STATUS "libavcodec is too old! (${AVCODEC_VERSION}, wanted 57.64.101)") + set(FFVER_OK FALSE) + endif() + if(AVUTIL_VERSION VERSION_LESS "55.34.101") + message(STATUS "libavutil is too old! (${AVUTIL_VERSION}, wanted 55.34.101)") + set(FFVER_OK FALSE) + endif() + if(SWSCALE_VERSION VERSION_LESS "4.2.100") + message(STATUS "libswscale is too old! (${SWSCALE_VERSION}, wanted 4.2.100)") + set(FFVER_OK FALSE) + endif() + if(SWRESAMPLE_VERSION VERSION_LESS "2.3.100") + message(STATUS "libswresample is too old! (${SWRESAMPLE_VERSION}, wanted 2.3.100)") + set(FFVER_OK FALSE) + endif() + endif() + if(FFVER_OK) + add_executable(alffplay examples/alffplay.cpp) + target_include_directories(alffplay PRIVATE ${SDL2_INCLUDE_DIR} ${FFMPEG_INCLUDE_DIRS}) - TARGET_LINK_LIBRARIES(alffplay + target_link_libraries(alffplay PRIVATE ${LINKER_FLAGS} ${SDL2_LIBRARY} ${FFMPEG_LIBRARIES} ex-common) - IF(ALSOFT_INSTALL) - INSTALL(TARGETS alffplay - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - ) - ENDIF() - MESSAGE(STATUS "Building SDL+FFmpeg example programs") - ENDIF() - MESSAGE(STATUS "") - ENDIF() -ENDIF() + if(ALSOFT_INSTALL_EXAMPLES) + set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alffplay) + endif() + message(STATUS "Building SDL+FFmpeg example programs") + endif() + endif() + message(STATUS "") +endif() + +if(EXTRA_INSTALLS) + install(TARGETS ${EXTRA_INSTALLS} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif() diff --git a/modules/openal-soft/ChangeLog b/modules/openal-soft/ChangeLog index d234c14..c26f096 100644 --- a/modules/openal-soft/ChangeLog +++ b/modules/openal-soft/ChangeLog @@ -1,3 +1,256 @@ +openal-soft-1.22.0: + + Implemented the ALC_SOFT_reopen_device extension. This allows for moving + devices to different outputs without losing object state. + + Implemented the ALC_SOFT_output_mode extension. + + Implemented the AL_SOFT_callback_buffer extension. + + Implemented the AL_SOFT_UHJ extension. This supports native UHJ buffer + formats and Super Stereo processing. + + Implemented the legacy EAX extensions. Enabled by default only on Windows. + + Improved sound positioning stability when a source is near the listener. + + Improved the default 5.1 output decoder. + + Improved the high frequency response for the HRTF second-order ambisonic + decoder. + + Improved SoundIO capture behavior. + + Fixed UHJ output on NEON-capable CPUs. + + Fixed redundant effect updates when setting an effect property to the + current value. + + Fixed WASAPI capture using really low sample rates, and sources with very + high pitch shifts when using a bsinc resampler. + + Added a PipeWire backend. + + Added enumeration for the JACK and CoreAudio backends. + + Added optional support for RTKit to get real-time priority. Only used as a + backup when pthread_setschedparam fails. + + Added an option for JACK playback to render directly in the real-time + processing callback. For lower playback latency, on by default. + + Added an option for custom JACK devices. + + Added utilities to encode and decode UHJ audio files. Files are decoded to + the .amb format, and are encoded from libsndfile-compatible formats. + + Added an in-progress extension to hold sources in a playing state when a + device disconnects. Allows devices to be reset or reopened and have sources + resume from where they left off. + + Lowered the priority of the JACK backend. To avoid it getting picked when + PipeWire is providing JACK compatibility, since the JACK backend is less + robust with auto-configuration. + +openal-soft-1.21.1: + + Improved alext.h's detection of standard types. + + Improved slightly the local source position when the listener and source + are near each other. + + Improved click/pop prevention for sounds that stop prematurely. + + Fixed compilation for Windows ARM targets with MSVC. + + Fixed ARM NEON detection on Windows. + + Fixed CoreAudio capture when the requested sample rate doesn't match the + system configuration. + + Fixed OpenSL capture desyncing from the internal capture buffer. + + Fixed sources missing a batch update when applied after quickly restarting + the source. + + Fixed missing source stop events when stopping a paused source. + + Added capture support to the experimental Oboe backend. + +openal-soft-1.21.0: + + Updated library codebase to C++14. + + Implemented the AL_SOFT_effect_target extension. + + Implemented the AL_SOFT_events extension. + + Implemented the ALC_SOFT_loopback_bformat extension. + + Improved memory use for mixing voices. + + Improved detection of NEON capabilities. + + Improved handling of PulseAudio devices that lack manual start control. + + Improved mixing performance with PulseAudio. + + Improved high-frequency scaling quality for the HRTF B-Format decoder. + + Improved makemhr's HRIR delay calculation. + + Improved WASAPI capture of mono formats with multichannel input. + + Reimplemented the modulation stage for reverb. + + Enabled real-time mixing priority by default, for backends that use the + setting. It can still be disabled in the config file. + + Enabled dual-band processing for the built-in quad and 7.1 output decoders. + + Fixed a potential crash when deleting an effect slot immediately after the + last source using it stops. + + Fixed building with the static runtime on MSVC. + + Fixed using source stereo angles outside of -pi...+pi. + + Fixed the buffer processed event count for sources that start with empty + buffers. + + Fixed trying to open an unopenable WASAPI device causing all devices to + stop working. + + Fixed stale devices when re-enumerating WASAPI devices. + + Fixed using unicode paths with the log file on Windows. + + Fixed DirectSound capture reporting bad sample counts or erroring when + reading samples. + + Added an in-progress extension for a callback-driven buffer type. + + Added an in-progress extension for higher-order B-Format buffers. + + Added an in-progress extension for convolution reverb. + + Added an experimental Oboe backend for Android playback. This requires the + Oboe sources at build time, so that it's built as a static library included + in libopenal. + + Added an option for auto-connecting JACK ports. + + Added greater-than-stereo support to the SoundIO backend. + + Modified the mixer to be fully asynchronous with the external API, and + should now be real-time safe. Although alcRenderSamplesSOFT is not due to + locking to check the device handle validity. + + Modified the UHJ encoder to use an all-pass FIR filter that's less harmful + to non-filtered signal phase. + + Converted examples from SDL_sound to libsndfile. To avoid issues when + combining SDL2 and SDL_sound. + + Worked around a 32-bit GCC/MinGW bug with TLS destructors. See: + https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83562 + + Reduced the maximum number of source sends from 16 to 6. + + Removed the QSA backend. It's been broken for who knows how long. + + Got rid of the compile-time native-tools targets, using cmake and global + initialization instead. This should make cross-compiling less troublesome. + +openal-soft-1.20.1: + + Implemented the AL_SOFT_direct_channels_remix extension. This extends + AL_DIRECT_CHANNELS_SOFT to optionally remix input channels that don't have + a matching output channel. + + Implemented the AL_SOFT_bformat_ex extension. This extends B-Format buffer + support for N3D or SN3D scaling, or ACN channel ordering. + + Fixed a potential voice leak when a source is started and stopped or + restarted in quick succession. + + Fixed a potential device reset failure with JACK. + + Improved handling of unsupported channel configurations with WASAPI. Such + setups will now try to output at least a stereo mix. + + Improved clarity a bit for the HRTF second-order ambisonic decoder. + + Improved detection of compatible layouts for SOFA files in makemhr and + sofa-info. + + Added the ability to resample HRTFs on load. MHR files no longer need to + match the device sample rate to be usable. + + Added an option to limit the HRTF's filter length. + +openal-soft-1.20.0: + + Converted the library codebase to C++11. A lot of hacks and custom + structures have been replaced with standard or cleaner implementations. + + Partially implemented the Vocal Morpher effect. + + Fixed the bsinc SSE resamplers on non-GCC compilers. + + Fixed OpenSL capture. + + Fixed support for extended capture formats with OpenSL. + + Fixed handling of WASAPI not reporting a default device. + + Fixed performance problems relating to semaphores on macOS. + + Modified the bsinc12 resampler's transition band to better avoid aliasing + noise. + + Modified alcResetDeviceSOFT to attempt recovery of disconnected devices. + + Modified the virtual speaker layout for HRTF B-Format decoding. + + Modified the PulseAudio backend to use a custom processing loop. + + Renamed the makehrtf utility to makemhr. + + Improved the efficiency of the bsinc resamplers when up-sampling. + + Improved the quality of the bsinc resamplers slightly. + + Improved the efficiency of the HRTF filters. + + Improved the HRTF B-Format decoder coefficient generation. + + Improved reverb feedback fading to be more consistent with pan fading. + + Improved handling of sources that end prematurely, avoiding loud clicks. + + Improved the performance of some reverb processing loops. + + Added fast_bsinc12 and 24 resamplers that improve efficiency at the cost of + some quality. Notably, down-sampling has less smooth pitch ramping. + + Added support for SOFA input files with makemhr. + + Added a build option to use pre-built native tools. For cross-compiling, + use with caution and ensure the native tools' binaries are kept up-to-date. + + Added an adjust-latency config option for the PulseAudio backend. + + Added basic support for multi-field HRTFs. + + Added an option for mixing first- or second-order B-Format with HRTF + output. This can improve HRTF performance given a number of sources. + + Added an RC file for proper DLL version information. + + Disabled some old KDE workarounds by default. Specifically, PulseAudio + streams can now be moved (KDE may try to move them after opening). + openal-soft-1.19.1: Implemented capture support for the SoundIO backend. diff --git a/modules/openal-soft/OpenAL32/Include/alAuxEffectSlot.h b/modules/openal-soft/OpenAL32/Include/alAuxEffectSlot.h deleted file mode 100644 index 0668127..0000000 --- a/modules/openal-soft/OpenAL32/Include/alAuxEffectSlot.h +++ /dev/null @@ -1,100 +0,0 @@ -#ifndef _AL_AUXEFFECTSLOT_H_ -#define _AL_AUXEFFECTSLOT_H_ - -#include - -#include "alMain.h" -#include "alEffect.h" -#include "ambidefs.h" -#include "effects/base.h" - -#include "almalloc.h" -#include "atomic.h" - - -struct ALeffectslot; - - -using ALeffectslotArray = al::FlexArray; - - -struct ALeffectslotProps { - ALfloat Gain; - ALboolean AuxSendAuto; - ALeffectslot *Target; - - ALenum Type; - EffectProps Props; - - EffectState *State; - - std::atomic next; -}; - - -struct ALeffectslot { - ALfloat Gain{1.0f}; - ALboolean AuxSendAuto{AL_TRUE}; - ALeffectslot *Target{nullptr}; - - struct { - ALenum Type{AL_EFFECT_NULL}; - EffectProps Props{}; - - EffectState *State{nullptr}; - } Effect; - - std::atomic_flag PropsClean; - - RefCount ref{0u}; - - std::atomic Update{nullptr}; - - struct { - ALfloat Gain{1.0f}; - ALboolean AuxSendAuto{AL_TRUE}; - ALeffectslot *Target{nullptr}; - - ALenum EffectType{AL_EFFECT_NULL}; - EffectProps mEffectProps{}; - EffectState *mEffectState{nullptr}; - - ALfloat RoomRolloff{0.0f}; /* Added to the source's room rolloff, not multiplied. */ - ALfloat DecayTime{0.0f}; - ALfloat DecayLFRatio{0.0f}; - ALfloat DecayHFRatio{0.0f}; - ALboolean DecayHFLimit{AL_FALSE}; - ALfloat AirAbsorptionGainHF{1.0f}; - } Params; - - /* Self ID */ - ALuint id{}; - - /* Mixing buffer used by the Wet mix. */ - al::vector,16> MixBuffer; - - /* Wet buffer configuration is ACN channel order with N3D scaling. - * Consequently, effects that only want to work with mono input can use - * channel 0 by itself. Effects that want multichannel can process the - * ambisonics signal and make a B-Format source pan. - */ - MixParams Wet; - - ALeffectslot() { PropsClean.test_and_set(std::memory_order_relaxed); } - ALeffectslot(const ALeffectslot&) = delete; - ALeffectslot& operator=(const ALeffectslot&) = delete; - ~ALeffectslot(); - - static ALeffectslotArray *CreatePtrArray(size_t count) noexcept; - - DEF_PLACE_NEWDEL() -}; - -ALenum InitEffectSlot(ALeffectslot *slot); -void UpdateEffectSlotProps(ALeffectslot *slot, ALCcontext *context); -void UpdateAllEffectSlotProps(ALCcontext *context); - - -ALenum InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect); - -#endif diff --git a/modules/openal-soft/OpenAL32/Include/alBuffer.h b/modules/openal-soft/OpenAL32/Include/alBuffer.h deleted file mode 100644 index ec9f6c3..0000000 --- a/modules/openal-soft/OpenAL32/Include/alBuffer.h +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef _AL_BUFFER_H_ -#define _AL_BUFFER_H_ - -#include "AL/alc.h" -#include "AL/al.h" -#include "AL/alext.h" - -#include "inprogext.h" -#include "atomic.h" -#include "vector.h" - - -/* User formats */ -enum UserFmtType { - UserFmtUByte, - UserFmtShort, - UserFmtFloat, - UserFmtDouble, - UserFmtMulaw, - UserFmtAlaw, - UserFmtIMA4, - UserFmtMSADPCM, -}; -enum UserFmtChannels { - UserFmtMono, - UserFmtStereo, - UserFmtRear, - UserFmtQuad, - UserFmtX51, /* (WFX order) */ - UserFmtX61, /* (WFX order) */ - UserFmtX71, /* (WFX order) */ - UserFmtBFormat2D, /* WXY */ - UserFmtBFormat3D, /* WXYZ */ -}; - -ALsizei BytesFromUserFmt(UserFmtType type); -ALsizei ChannelsFromUserFmt(UserFmtChannels chans); -inline ALsizei FrameSizeFromUserFmt(UserFmtChannels chans, UserFmtType type) -{ return ChannelsFromUserFmt(chans) * BytesFromUserFmt(type); } - - -/* Storable formats */ -enum FmtType { - FmtUByte = UserFmtUByte, - FmtShort = UserFmtShort, - FmtFloat = UserFmtFloat, - FmtDouble = UserFmtDouble, - FmtMulaw = UserFmtMulaw, - FmtAlaw = UserFmtAlaw, -}; -enum FmtChannels { - FmtMono = UserFmtMono, - FmtStereo = UserFmtStereo, - FmtRear = UserFmtRear, - FmtQuad = UserFmtQuad, - FmtX51 = UserFmtX51, - FmtX61 = UserFmtX61, - FmtX71 = UserFmtX71, - FmtBFormat2D = UserFmtBFormat2D, - FmtBFormat3D = UserFmtBFormat3D, -}; -#define MAX_INPUT_CHANNELS (8) - -/* DevFmtType traits, providing the type, etc given a DevFmtType. */ -template -struct FmtTypeTraits { }; - -template<> -struct FmtTypeTraits { using Type = ALubyte; }; -template<> -struct FmtTypeTraits { using Type = ALshort; }; -template<> -struct FmtTypeTraits { using Type = ALfloat; }; -template<> -struct FmtTypeTraits { using Type = ALdouble; }; -template<> -struct FmtTypeTraits { using Type = ALubyte; }; -template<> -struct FmtTypeTraits { using Type = ALubyte; }; - - -ALsizei BytesFromFmt(FmtType type); -ALsizei ChannelsFromFmt(FmtChannels chans); -inline ALsizei FrameSizeFromFmt(FmtChannels chans, FmtType type) -{ return ChannelsFromFmt(chans) * BytesFromFmt(type); } - - -struct ALbuffer { - al::vector mData; - - ALsizei Frequency{0}; - ALbitfieldSOFT Access{0u}; - ALsizei SampleLen{0}; - - FmtChannels mFmtChannels{}; - FmtType mFmtType{}; - ALsizei BytesAlloc{0}; - - UserFmtType OriginalType{}; - ALsizei OriginalSize{0}; - ALsizei OriginalAlign{0}; - - ALsizei LoopStart{0}; - ALsizei LoopEnd{0}; - - std::atomic UnpackAlign{0}; - std::atomic PackAlign{0}; - - ALbitfieldSOFT MappedAccess{0u}; - ALsizei MappedOffset{0}; - ALsizei MappedSize{0}; - - /* Number of times buffer was attached to a source (deletion can only occur when 0) */ - RefCount ref{0u}; - - /* Self ID */ - ALuint id{0}; -}; - -#endif diff --git a/modules/openal-soft/OpenAL32/Include/alError.h b/modules/openal-soft/OpenAL32/Include/alError.h deleted file mode 100644 index 858f81d..0000000 --- a/modules/openal-soft/OpenAL32/Include/alError.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef _AL_ERROR_H_ -#define _AL_ERROR_H_ - -#include "alMain.h" -#include "logging.h" - -#ifdef __cplusplus -extern "C" { -#endif - -extern ALboolean TrapALError; - -void alSetError(ALCcontext *context, ALenum errorCode, const char *msg, ...) DECL_FORMAT(printf, 3, 4); - -#define SETERR_GOTO(ctx, err, lbl, ...) do { \ - alSetError((ctx), (err), __VA_ARGS__); \ - goto lbl; \ -} while(0) - -#define SETERR_RETURN(ctx, err, retval, ...) do { \ - alSetError((ctx), (err), __VA_ARGS__); \ - return retval; \ -} while(0) - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/modules/openal-soft/OpenAL32/Include/alFilter.h b/modules/openal-soft/OpenAL32/Include/alFilter.h deleted file mode 100644 index 1c033ac..0000000 --- a/modules/openal-soft/OpenAL32/Include/alFilter.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef _AL_FILTER_H_ -#define _AL_FILTER_H_ - -#include "AL/alc.h" -#include "AL/al.h" - - -#define LOWPASSFREQREF (5000.0f) -#define HIGHPASSFREQREF (250.0f) - - -struct ALfilter; - -struct ALfilterVtable { - void (*const setParami)(ALfilter *filter, ALCcontext *context, ALenum param, ALint val); - void (*const setParamiv)(ALfilter *filter, ALCcontext *context, ALenum param, const ALint *vals); - void (*const setParamf)(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val); - void (*const setParamfv)(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals); - - void (*const getParami)(ALfilter *filter, ALCcontext *context, ALenum param, ALint *val); - void (*const getParamiv)(ALfilter *filter, ALCcontext *context, ALenum param, ALint *vals); - void (*const getParamf)(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val); - void (*const getParamfv)(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals); -}; - -#define DEFINE_ALFILTER_VTABLE(T) \ -const ALfilterVtable T##_vtable = { \ - T##_setParami, T##_setParamiv, T##_setParamf, T##_setParamfv, \ - T##_getParami, T##_getParamiv, T##_getParamf, T##_getParamfv, \ -} - -struct ALfilter { - // Filter type (AL_FILTER_NULL, ...) - ALenum type; - - ALfloat Gain; - ALfloat GainHF; - ALfloat HFReference; - ALfloat GainLF; - ALfloat LFReference; - - const ALfilterVtable *vtab; - - /* Self ID */ - ALuint id; -}; -#define ALfilter_setParami(o, c, p, v) ((o)->vtab->setParami(o, c, p, v)) -#define ALfilter_setParamf(o, c, p, v) ((o)->vtab->setParamf(o, c, p, v)) -#define ALfilter_setParamiv(o, c, p, v) ((o)->vtab->setParamiv(o, c, p, v)) -#define ALfilter_setParamfv(o, c, p, v) ((o)->vtab->setParamfv(o, c, p, v)) -#define ALfilter_getParami(o, c, p, v) ((o)->vtab->getParami(o, c, p, v)) -#define ALfilter_getParamf(o, c, p, v) ((o)->vtab->getParamf(o, c, p, v)) -#define ALfilter_getParamiv(o, c, p, v) ((o)->vtab->getParamiv(o, c, p, v)) -#define ALfilter_getParamfv(o, c, p, v) ((o)->vtab->getParamfv(o, c, p, v)) - -#endif diff --git a/modules/openal-soft/OpenAL32/Include/alListener.h b/modules/openal-soft/OpenAL32/Include/alListener.h deleted file mode 100644 index 4d59dbf..0000000 --- a/modules/openal-soft/OpenAL32/Include/alListener.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef _AL_LISTENER_H_ -#define _AL_LISTENER_H_ - -#include - -#include "AL/alc.h" -#include "AL/al.h" -#include "AL/alext.h" - -#include "atomic.h" -#include "vecmat.h" - -enum class DistanceModel; - - -struct ALlistenerProps { - std::array Position; - std::array Velocity; - std::array OrientAt; - std::array OrientUp; - ALfloat Gain; - - std::atomic next; -}; - -struct ALlistener { - std::array Position{{0.0f, 0.0f, 0.0f}}; - std::array Velocity{{0.0f, 0.0f, 0.0f}}; - std::array OrientAt{{0.0f, 0.0f, -1.0f}}; - std::array OrientUp{{0.0f, 1.0f, 0.0f}}; - ALfloat Gain{1.0f}; - - std::atomic_flag PropsClean; - - /* Pointer to the most recent property values that are awaiting an update. - */ - std::atomic Update{nullptr}; - - struct { - alu::Matrix Matrix; - alu::Vector Velocity; - - ALfloat Gain; - ALfloat MetersPerUnit; - - ALfloat DopplerFactor; - ALfloat SpeedOfSound; /* in units per sec! */ - ALfloat ReverbSpeedOfSound; /* in meters per sec! */ - - ALboolean SourceDistanceModel; - DistanceModel mDistanceModel; - } Params; - - ALlistener() { PropsClean.test_and_set(std::memory_order_relaxed); } -}; - -void UpdateListenerProps(ALCcontext *context); - -#endif diff --git a/modules/openal-soft/OpenAL32/Include/alMain.h b/modules/openal-soft/OpenAL32/Include/alMain.h deleted file mode 100644 index a8c7f3c..0000000 --- a/modules/openal-soft/OpenAL32/Include/alMain.h +++ /dev/null @@ -1,573 +0,0 @@ -#ifndef AL_MAIN_H -#define AL_MAIN_H - -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_STRINGS_H -#include -#endif - -#include -#include -#include -#include -#include - -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/alext.h" - -#include "inprogext.h" -#include "atomic.h" -#include "vector.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "threads.h" -#include "ambidefs.h" -#include "hrtf.h" - - -template -constexpr inline size_t countof(const T(&)[N]) noexcept -{ return N; } -#define COUNTOF countof - - -#ifndef UNUSED -#if defined(__cplusplus) -#define UNUSED(x) -#elif defined(__GNUC__) -#define UNUSED(x) UNUSED_##x __attribute__((unused)) -#elif defined(__LCLINT__) -#define UNUSED(x) /*@unused@*/ x -#else -#define UNUSED(x) x -#endif -#endif - - -#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) -#define IS_LITTLE_ENDIAN (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) -#else -static const union { - ALuint u; - ALubyte b[sizeof(ALuint)]; -} EndianTest = { 1 }; -#define IS_LITTLE_ENDIAN (EndianTest.b[0] == 1) -#endif - - -struct HrtfEntry; -struct HrtfHandle; -struct EnumeratedHrtf; -struct DirectHrtfState; -struct FrontStablizer; -struct Compressor; -struct BackendBase; -struct ALbuffer; -struct ALeffect; -struct ALfilter; -struct EffectState; -struct Uhj2Encoder; -class BFormatDec; -class AmbiUpsampler; -struct bs2b; - - -#define MIN_OUTPUT_RATE 8000 -#define DEFAULT_OUTPUT_RATE 44100 -#define DEFAULT_UPDATE_SIZE 882 /* 20ms */ -#define DEFAULT_NUM_UPDATES 3 - - -enum Channel { - FrontLeft = 0, - FrontRight, - FrontCenter, - LFE, - BackLeft, - BackRight, - BackCenter, - SideLeft, - SideRight, - - UpperFrontLeft, - UpperFrontRight, - UpperBackLeft, - UpperBackRight, - LowerFrontLeft, - LowerFrontRight, - LowerBackLeft, - LowerBackRight, - - Aux0, - Aux1, - Aux2, - Aux3, - Aux4, - Aux5, - Aux6, - Aux7, - Aux8, - Aux9, - Aux10, - Aux11, - Aux12, - Aux13, - Aux14, - Aux15, - - MaxChannels -}; - - -/* Device formats */ -enum DevFmtType { - DevFmtByte = ALC_BYTE_SOFT, - DevFmtUByte = ALC_UNSIGNED_BYTE_SOFT, - DevFmtShort = ALC_SHORT_SOFT, - DevFmtUShort = ALC_UNSIGNED_SHORT_SOFT, - DevFmtInt = ALC_INT_SOFT, - DevFmtUInt = ALC_UNSIGNED_INT_SOFT, - DevFmtFloat = ALC_FLOAT_SOFT, - - DevFmtTypeDefault = DevFmtFloat -}; -enum DevFmtChannels { - DevFmtMono = ALC_MONO_SOFT, - DevFmtStereo = ALC_STEREO_SOFT, - DevFmtQuad = ALC_QUAD_SOFT, - DevFmtX51 = ALC_5POINT1_SOFT, - DevFmtX61 = ALC_6POINT1_SOFT, - DevFmtX71 = ALC_7POINT1_SOFT, - DevFmtAmbi3D = ALC_BFORMAT3D_SOFT, - - /* Similar to 5.1, except using rear channels instead of sides */ - DevFmtX51Rear = 0x80000000, - - DevFmtChannelsDefault = DevFmtStereo -}; -#define MAX_OUTPUT_CHANNELS (16) - -/* DevFmtType traits, providing the type, etc given a DevFmtType. */ -template -struct DevFmtTypeTraits { }; - -template<> -struct DevFmtTypeTraits { using Type = ALbyte; }; -template<> -struct DevFmtTypeTraits { using Type = ALubyte; }; -template<> -struct DevFmtTypeTraits { using Type = ALshort; }; -template<> -struct DevFmtTypeTraits { using Type = ALushort; }; -template<> -struct DevFmtTypeTraits { using Type = ALint; }; -template<> -struct DevFmtTypeTraits { using Type = ALuint; }; -template<> -struct DevFmtTypeTraits { using Type = ALfloat; }; - - -ALsizei BytesFromDevFmt(DevFmtType type) noexcept; -ALsizei ChannelsFromDevFmt(DevFmtChannels chans, ALsizei ambiorder) noexcept; -inline ALsizei FrameSizeFromDevFmt(DevFmtChannels chans, DevFmtType type, ALsizei ambiorder) noexcept -{ return ChannelsFromDevFmt(chans, ambiorder) * BytesFromDevFmt(type); } - -enum class AmbiLayout { - FuMa = ALC_FUMA_SOFT, /* FuMa channel order */ - ACN = ALC_ACN_SOFT, /* ACN channel order */ - - Default = ACN -}; - -enum class AmbiNorm { - FuMa = ALC_FUMA_SOFT, /* FuMa normalization */ - SN3D = ALC_SN3D_SOFT, /* SN3D normalization */ - N3D = ALC_N3D_SOFT, /* N3D normalization */ - - Default = SN3D -}; - - -enum DeviceType { - Playback, - Capture, - Loopback -}; - - -enum RenderMode { - NormalRender, - StereoPair, - HrtfRender -}; - - -struct BufferSubList { - uint64_t FreeMask{~0_u64}; - ALbuffer *Buffers{nullptr}; /* 64 */ - - BufferSubList() noexcept = default; - BufferSubList(const BufferSubList&) = delete; - BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers} - { rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; } - ~BufferSubList(); - - BufferSubList& operator=(const BufferSubList&) = delete; - BufferSubList& operator=(BufferSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; } -}; - -struct EffectSubList { - uint64_t FreeMask{~0_u64}; - ALeffect *Effects{nullptr}; /* 64 */ - - EffectSubList() noexcept = default; - EffectSubList(const EffectSubList&) = delete; - EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects} - { rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; } - ~EffectSubList(); - - EffectSubList& operator=(const EffectSubList&) = delete; - EffectSubList& operator=(EffectSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; } -}; - -struct FilterSubList { - uint64_t FreeMask{~0_u64}; - ALfilter *Filters{nullptr}; /* 64 */ - - FilterSubList() noexcept = default; - FilterSubList(const FilterSubList&) = delete; - FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters} - { rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; } - ~FilterSubList(); - - FilterSubList& operator=(const FilterSubList&) = delete; - FilterSubList& operator=(FilterSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; } -}; - - -/* Maximum delay in samples for speaker distance compensation. */ -#define MAX_DELAY_LENGTH 1024 - -class DistanceComp { -public: - struct DistData { - ALfloat Gain{1.0f}; - ALsizei Length{0}; /* Valid range is [0...MAX_DELAY_LENGTH). */ - ALfloat *Buffer{nullptr}; - }; - -private: - DistData mChannel[MAX_OUTPUT_CHANNELS]; - al::vector mSamples; - -public: - void resize(size_t new_size) { mSamples.resize(new_size); } - void shrink_to_fit() { mSamples.shrink_to_fit(); } - void clear() noexcept - { - for(auto &chan : mChannel) - { - chan.Gain = 1.0f; - chan.Length = 0; - chan.Buffer = nullptr; - } - mSamples.clear(); - } - - DistData *begin() noexcept { return std::begin(mChannel); } - const DistData *begin() const noexcept { return std::begin(mChannel); } - const DistData *cbegin() const noexcept { return std::begin(mChannel); } - DistData *end() noexcept { return std::end(mChannel); } - const DistData *end() const noexcept { return std::end(mChannel); } - const DistData *cend() const noexcept { return std::end(mChannel); } - - ALfloat *data() noexcept { return mSamples.data(); } - const ALfloat *data() const noexcept { return mSamples.data(); } - - DistData& operator[](size_t o) noexcept { return mChannel[o]; } - const DistData& operator[](size_t o) const noexcept { return mChannel[o]; } -}; - -struct BFChannelConfig { - ALfloat Scale; - ALsizei Index; -}; - -/* Size for temporary storage of buffer data, in ALfloats. Larger values need - * more memory, while smaller values may need more iterations. The value needs - * to be a sensible size, however, as it constrains the max stepping value used - * for mixing, as well as the maximum number of samples per mixing iteration. - */ -#define BUFFERSIZE 1024 - -/* Maximum number of samples to pad on either end of a buffer for resampling. - * Note that both the beginning and end need padding! - */ -#define MAX_RESAMPLE_PADDING 24 - - -struct MixParams { - /* Coefficient channel mapping for mixing to the buffer. */ - std::array AmbiMap; - - ALfloat (*Buffer)[BUFFERSIZE]{nullptr}; - ALsizei NumChannels{0}; -}; - -struct RealMixParams { - std::array ChannelIndex{}; - - ALfloat (*Buffer)[BUFFERSIZE]{nullptr}; - ALsizei NumChannels{0}; -}; - -using POSTPROCESS = void(*)(ALCdevice *device, const ALsizei SamplesToDo); - -struct ALCdevice { - RefCount ref{1u}; - - std::atomic Connected{true}; - const DeviceType Type{}; - - ALuint Frequency{}; - ALuint UpdateSize{}; - ALuint BufferSize{}; - - DevFmtChannels FmtChans{}; - DevFmtType FmtType{}; - ALboolean IsHeadphones{AL_FALSE}; - ALsizei mAmbiOrder{0}; - /* For DevFmtAmbi* output only, specifies the channel order and - * normalization. - */ - AmbiLayout mAmbiLayout{AmbiLayout::Default}; - AmbiNorm mAmbiScale{AmbiNorm::Default}; - - ALCenum LimiterState{ALC_DONT_CARE_SOFT}; - - std::string DeviceName; - - // Device flags - ALuint Flags{0u}; - - std::string HrtfName; - al::vector HrtfList; - ALCenum HrtfStatus{ALC_FALSE}; - - std::atomic LastError{ALC_NO_ERROR}; - - // Maximum number of sources that can be created - ALuint SourcesMax{}; - // Maximum number of slots that can be created - ALuint AuxiliaryEffectSlotMax{}; - - ALCuint NumMonoSources{}; - ALCuint NumStereoSources{}; - ALsizei NumAuxSends{}; - - // Map of Buffers for this device - std::mutex BufferLock; - al::vector BufferList; - - // Map of Effects for this device - std::mutex EffectLock; - al::vector EffectList; - - // Map of Filters for this device - std::mutex FilterLock; - al::vector FilterList; - - /* Rendering mode. */ - RenderMode mRenderMode{NormalRender}; - - /* The average speaker distance as determined by the ambdec configuration - * (or alternatively, by the NFC-HOA reference delay). Only used for NFC. - */ - ALfloat AvgSpeakerDist{0.0f}; - - ALuint SamplesDone{0u}; - std::chrono::nanoseconds ClockBase{0}; - std::chrono::nanoseconds FixedLatency{0}; - - /* Temp storage used for mixer processing. */ - alignas(16) ALfloat SourceData[BUFFERSIZE + MAX_RESAMPLE_PADDING*2]; - alignas(16) ALfloat ResampledData[BUFFERSIZE]; - alignas(16) ALfloat FilteredData[BUFFERSIZE]; - union { - alignas(16) ALfloat HrtfSourceData[BUFFERSIZE + HRTF_HISTORY_LENGTH]; - alignas(16) ALfloat NfcSampleData[BUFFERSIZE]; - }; - alignas(16) float2 HrtfAccumData[BUFFERSIZE + HRIR_LENGTH]; - - /* Mixing buffer used by the Dry mix and Real output. */ - al::vector, 16> MixBuffer; - - /* The "dry" path corresponds to the main output. */ - MixParams Dry; - ALsizei NumChannelsPerOrder[MAX_AMBI_ORDER+1]{}; - - /* "Real" output, which will be written to the device buffer. May alias the - * dry buffer. - */ - RealMixParams RealOut; - - /* HRTF state and info */ - std::unique_ptr mHrtfState; - HrtfEntry *mHrtf{nullptr}; - - /* Ambisonic-to-UHJ encoder */ - std::unique_ptr Uhj_Encoder; - - /* Ambisonic decoder for speakers */ - std::unique_ptr AmbiDecoder; - - /* Stereo-to-binaural filter */ - std::unique_ptr Bs2b; - - POSTPROCESS PostProcess{}; - - std::unique_ptr Stablizer; - - std::unique_ptr Limiter; - - /* Delay buffers used to compensate for speaker distances. */ - DistanceComp ChannelDelay; - - /* Dithering control. */ - ALfloat DitherDepth{0.0f}; - ALuint DitherSeed{0u}; - - /* Running count of the mixer invocations, in 31.1 fixed point. This - * actually increments *twice* when mixing, first at the start and then at - * the end, so the bottom bit indicates if the device is currently mixing - * and the upper bits indicates how many mixes have been done. - */ - RefCount MixCount{0u}; - - // Contexts created on this device - std::atomic ContextList{nullptr}; - - /* This lock protects the device state (format, update size, etc) from - * being from being changed in multiple threads, or being accessed while - * being changed. It's also used to serialize calls to the backend. - */ - std::mutex StateLock; - std::unique_ptr Backend; - - - ALCdevice(DeviceType type); - ALCdevice(const ALCdevice&) = delete; - ALCdevice& operator=(const ALCdevice&) = delete; - ~ALCdevice(); - - ALsizei bytesFromFmt() const noexcept { return BytesFromDevFmt(FmtType); } - ALsizei channelsFromFmt() const noexcept { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); } - ALsizei frameSizeFromFmt() const noexcept { return bytesFromFmt() * channelsFromFmt(); } - - static constexpr inline const char *CurrentPrefix() noexcept { return "ALCdevice::"; } - DEF_NEWDEL(ALCdevice) -}; - -// Frequency was requested by the app or config file -#define DEVICE_FREQUENCY_REQUEST (1u<<1) -// Channel configuration was requested by the config file -#define DEVICE_CHANNELS_REQUEST (1u<<2) -// Sample type was requested by the config file -#define DEVICE_SAMPLE_TYPE_REQUEST (1u<<3) - -// Specifies if the DSP is paused at user request -#define DEVICE_PAUSED (1u<<30) - -// Specifies if the device is currently running -#define DEVICE_RUNNING (1u<<31) - - -/* Must be less than 15 characters (16 including terminating null) for - * compatibility with pthread_setname_np limitations. */ -#define MIXER_THREAD_NAME "alsoft-mixer" - -#define RECORD_THREAD_NAME "alsoft-record" - - -enum { - /* End event thread processing. */ - EventType_KillThread = 0, - - /* User event types. */ - EventType_SourceStateChange = 1<<0, - EventType_BufferCompleted = 1<<1, - EventType_Error = 1<<2, - EventType_Performance = 1<<3, - EventType_Deprecated = 1<<4, - EventType_Disconnected = 1<<5, - - /* Internal events. */ - EventType_ReleaseEffectState = 65536, -}; - -struct AsyncEvent { - unsigned int EnumType{0u}; - union { - char dummy; - struct { - ALuint id; - ALenum state; - } srcstate; - struct { - ALuint id; - ALsizei count; - } bufcomp; - struct { - ALenum type; - ALuint id; - ALuint param; - ALchar msg[1008]; - } user; - EffectState *mEffectState; - } u{}; - - AsyncEvent() noexcept = default; - constexpr AsyncEvent(unsigned int type) noexcept : EnumType{type} { } -}; - - -void AllocateVoices(ALCcontext *context, ALsizei num_voices, ALsizei old_sends); - - -extern ALint RTPrioLevel; -void SetRTPriority(void); - -void SetDefaultChannelOrder(ALCdevice *device); -void SetDefaultWFXChannelOrder(ALCdevice *device); - -const ALCchar *DevFmtTypeString(DevFmtType type) noexcept; -const ALCchar *DevFmtChannelsString(DevFmtChannels chans) noexcept; - -/** - * GetChannelIdxByName - * - * Returns the index for the given channel name (e.g. FrontCenter), or -1 if it - * doesn't exist. - */ -inline ALint GetChannelIdxByName(const RealMixParams &real, Channel chan) noexcept -{ return real.ChannelIndex[chan]; } - - -void StartEventThrd(ALCcontext *ctx); -void StopEventThrd(ALCcontext *ctx); - - -al::vector SearchDataFiles(const char *match, const char *subdir); - -#endif diff --git a/modules/openal-soft/OpenAL32/Include/alSource.h b/modules/openal-soft/OpenAL32/Include/alSource.h deleted file mode 100644 index 0343a94..0000000 --- a/modules/openal-soft/OpenAL32/Include/alSource.h +++ /dev/null @@ -1,127 +0,0 @@ -#ifndef _AL_SOURCE_H_ -#define _AL_SOURCE_H_ - -#include - -#include "alMain.h" -#include "alu.h" -#include "hrtf.h" -#include "almalloc.h" -#include "atomic.h" - -#define DEFAULT_SENDS 2 - -struct ALbuffer; -struct ALsource; -struct ALeffectslot; - - -struct ALbufferlistitem { - std::atomic next; - ALsizei max_samples; - ALsizei num_buffers; - ALbuffer *buffers[]; - - static constexpr size_t Sizeof(size_t num_buffers) noexcept - { - return maxz(offsetof(ALbufferlistitem, buffers) + sizeof(ALbuffer*)*num_buffers, - sizeof(ALbufferlistitem)); - } -}; - - -struct ALsource { - /** Source properties. */ - ALfloat Pitch; - ALfloat Gain; - ALfloat OuterGain; - ALfloat MinGain; - ALfloat MaxGain; - ALfloat InnerAngle; - ALfloat OuterAngle; - ALfloat RefDistance; - ALfloat MaxDistance; - ALfloat RolloffFactor; - std::array Position; - std::array Velocity; - std::array Direction; - std::array OrientAt; - std::array OrientUp; - ALboolean HeadRelative; - ALboolean Looping; - DistanceModel mDistanceModel; - Resampler mResampler; - ALboolean DirectChannels; - SpatializeMode mSpatialize; - - ALboolean DryGainHFAuto; - ALboolean WetGainAuto; - ALboolean WetGainHFAuto; - ALfloat OuterGainHF; - - ALfloat AirAbsorptionFactor; - ALfloat RoomRolloffFactor; - ALfloat DopplerFactor; - - /* NOTE: Stereo pan angles are specified in radians, counter-clockwise - * rather than clockwise. - */ - std::array StereoPan; - - ALfloat Radius; - - /** Direct filter and auxiliary send info. */ - struct { - ALfloat Gain; - ALfloat GainHF; - ALfloat HFReference; - ALfloat GainLF; - ALfloat LFReference; - } Direct; - struct SendData { - ALeffectslot *Slot; - ALfloat Gain; - ALfloat GainHF; - ALfloat HFReference; - ALfloat GainLF; - ALfloat LFReference; - }; - al::vector Send; - - /** - * Last user-specified offset, and the offset type (bytes, samples, or - * seconds). - */ - ALdouble Offset; - ALenum OffsetType; - - /** Source type (static, streaming, or undetermined) */ - ALint SourceType; - - /** Source state (initial, playing, paused, or stopped) */ - ALenum state; - - /** Source Buffer Queue head. */ - ALbufferlistitem *queue; - - std::atomic_flag PropsClean; - - /* Index into the context's Voices array. Lazily updated, only checked and - * reset when looking up the voice. - */ - ALint VoiceIdx; - - /** Self ID */ - ALuint id; - - - ALsource(ALsizei num_sends); - ~ALsource(); - - ALsource(const ALsource&) = delete; - ALsource& operator=(const ALsource&) = delete; -}; - -void UpdateAllSourceProps(ALCcontext *context); - -#endif diff --git a/modules/openal-soft/OpenAL32/Include/alu.h b/modules/openal-soft/OpenAL32/Include/alu.h deleted file mode 100644 index 642aedd..0000000 --- a/modules/openal-soft/OpenAL32/Include/alu.h +++ /dev/null @@ -1,441 +0,0 @@ -#ifndef _ALU_H_ -#define _ALU_H_ - -#include -#include -#ifdef HAVE_FLOAT_H -#include -#endif -#ifdef HAVE_IEEEFP_H -#include -#endif - -#include -#include - -#include "alMain.h" -#include "alBuffer.h" - -#include "hrtf.h" -#include "logging.h" -#include "math_defs.h" -#include "filters/biquad.h" -#include "filters/splitter.h" -#include "filters/nfc.h" -#include "almalloc.h" -#include "alnumeric.h" - - -enum class DistanceModel; - -#define MAX_PITCH 255 -#define MAX_SENDS 16 - - -struct BSincTable; -struct ALsource; -struct ALbufferlistitem; -struct ALvoice; -struct ALeffectslot; - - -#define DITHER_RNG_SEED 22222 - - -enum SpatializeMode { - SpatializeOff = AL_FALSE, - SpatializeOn = AL_TRUE, - SpatializeAuto = AL_AUTO_SOFT -}; - -enum Resampler { - PointResampler, - LinearResampler, - FIR4Resampler, - BSinc12Resampler, - BSinc24Resampler, - - ResamplerMax = BSinc24Resampler -}; -extern Resampler ResamplerDefault; - -/* The number of distinct scale and phase intervals within the bsinc filter - * table. - */ -#define BSINC_SCALE_BITS 4 -#define BSINC_SCALE_COUNT (1< *Coeffs; - ALsizei Delay[2]; - ALfloat Gain; - ALfloat GainStep; -}; - - -struct DirectParams { - BiquadFilter LowPass; - BiquadFilter HighPass; - - NfcFilter NFCtrlFilter; - - struct { - HrtfParams Old; - HrtfParams Target; - HrtfState State; - } Hrtf; - - struct { - ALfloat Current[MAX_OUTPUT_CHANNELS]; - ALfloat Target[MAX_OUTPUT_CHANNELS]; - } Gains; -}; - -struct SendParams { - BiquadFilter LowPass; - BiquadFilter HighPass; - - struct { - ALfloat Current[MAX_OUTPUT_CHANNELS]; - ALfloat Target[MAX_OUTPUT_CHANNELS]; - } Gains; -}; - - -struct ALvoicePropsBase { - ALfloat Pitch; - ALfloat Gain; - ALfloat OuterGain; - ALfloat MinGain; - ALfloat MaxGain; - ALfloat InnerAngle; - ALfloat OuterAngle; - ALfloat RefDistance; - ALfloat MaxDistance; - ALfloat RolloffFactor; - std::array Position; - std::array Velocity; - std::array Direction; - std::array OrientAt; - std::array OrientUp; - ALboolean HeadRelative; - DistanceModel mDistanceModel; - Resampler mResampler; - ALboolean DirectChannels; - SpatializeMode mSpatializeMode; - - ALboolean DryGainHFAuto; - ALboolean WetGainAuto; - ALboolean WetGainHFAuto; - ALfloat OuterGainHF; - - ALfloat AirAbsorptionFactor; - ALfloat RoomRolloffFactor; - ALfloat DopplerFactor; - - std::array StereoPan; - - ALfloat Radius; - - /** Direct filter and auxiliary send info. */ - struct { - ALfloat Gain; - ALfloat GainHF; - ALfloat HFReference; - ALfloat GainLF; - ALfloat LFReference; - } Direct; - struct SendData { - ALeffectslot *Slot; - ALfloat Gain; - ALfloat GainHF; - ALfloat HFReference; - ALfloat GainLF; - ALfloat LFReference; - } Send[MAX_SENDS]; -}; - -struct ALvoiceProps : public ALvoicePropsBase { - std::atomic next{nullptr}; - - DEF_NEWDEL(ALvoiceProps) -}; - -#define VOICE_IS_STATIC (1u<<0) -#define VOICE_IS_FADING (1u<<1) /* Fading sources use gain stepping for smooth transitions. */ -#define VOICE_IS_AMBISONIC (1u<<2) /* Voice needs HF scaling for ambisonic upsampling. */ -#define VOICE_HAS_HRTF (1u<<3) -#define VOICE_HAS_NFC (1u<<4) - -struct ALvoice { - enum State { - Stopped = 0, - Playing = 1, - Stopping = 2 - }; - - std::atomic mUpdate{nullptr}; - - std::atomic mSourceID{0u}; - std::atomic mPlayState{Stopped}; - - ALvoicePropsBase mProps; - - /** - * Source offset in samples, relative to the currently playing buffer, NOT - * the whole queue. - */ - std::atomic mPosition; - /** Fractional (fixed-point) offset to the next sample. */ - std::atomic mPositionFrac; - - /* Current buffer queue item being played. */ - std::atomic mCurrentBuffer; - - /* Buffer queue item to loop to at end of queue (will be NULL for non- - * looping voices). - */ - std::atomic mLoopBuffer; - - /* Properties for the attached buffer(s). */ - FmtChannels mFmtChannels; - ALuint mFrequency; - ALsizei mNumChannels; - ALsizei mSampleSize; - - /** Current target parameters used for mixing. */ - ALint mStep; - - ResamplerFunc mResampler; - - InterpState mResampleState; - - ALuint mFlags; - - struct ResampleData { - alignas(16) std::array mPrevSamples; - - ALfloat mAmbiScale; - BandSplitter mAmbiSplitter; - }; - std::array mResampleData; - - struct { - int FilterType; - DirectParams Params[MAX_INPUT_CHANNELS]; - - ALfloat (*Buffer)[BUFFERSIZE]; - ALsizei Channels; - ALsizei ChannelsPerOrder[MAX_AMBI_ORDER+1]; - } mDirect; - - struct SendData { - int FilterType; - SendParams Params[MAX_INPUT_CHANNELS]; - - ALfloat (*Buffer)[BUFFERSIZE]; - ALsizei Channels; - }; - al::FlexArray mSend; - - ALvoice(size_t numsends) : mSend{numsends} { } - ALvoice(const ALvoice&) = delete; - ALvoice& operator=(const ALvoice&) = delete; - - static constexpr size_t Sizeof(size_t numsends) noexcept - { - return maxz(sizeof(ALvoice), - al::FlexArray::Sizeof(numsends, offsetof(ALvoice, mSend))); - } -}; - -void DeinitVoice(ALvoice *voice) noexcept; - - -using MixerFunc = void(*)(const ALfloat *data, const ALsizei OutChans, - ALfloat (*OutBuffer)[BUFFERSIZE], ALfloat *CurrentGains, const ALfloat *TargetGains, - const ALsizei Counter, const ALsizei OutPos, const ALsizei BufferSize); -using RowMixerFunc = void(*)(ALfloat *OutBuffer, const ALfloat *gains, - const ALfloat (*data)[BUFFERSIZE], const ALsizei InChans, const ALsizei InPos, - const ALsizei BufferSize); -using HrtfMixerFunc = void(*)(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, - const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - MixHrtfParams *hrtfparams, const ALsizei BufferSize); -using HrtfMixerBlendFunc = void(*)(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, - const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, - const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize); -using HrtfDirectMixerFunc = void(*)(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, - const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State, - const ALsizei NumChans, const ALsizei BufferSize); - - -#define GAIN_MIX_MAX (1000.0f) /* +60dB */ - -#define GAIN_SILENCE_THRESHOLD (0.00001f) /* -100dB */ - -#define SPEEDOFSOUNDMETRESPERSEC (343.3f) -#define AIRABSORBGAINHF (0.99426f) /* -0.05dB */ - -/* Target gain for the reverb decay feedback reaching the decay time. */ -#define REVERB_DECAY_GAIN (0.001f) /* -60 dB */ - -#define FRACTIONBITS (12) -#define FRACTIONONE (1< GetAmbiIdentityRow(size_t i) noexcept -{ - std::array ret{}; - ret[i] = 1.0f; - return ret; -} - - -void MixVoice(ALvoice *voice, ALvoice::State vstate, const ALuint SourceID, ALCcontext *Context, const ALsizei SamplesToDo); - -void aluMixData(ALCdevice *device, ALvoid *OutBuffer, ALsizei NumSamples); -/* Caller must lock the device state, and the mixer must not be running. */ -void aluHandleDisconnect(ALCdevice *device, const char *msg, ...) DECL_FORMAT(printf, 2, 3); - -extern MixerFunc MixSamples; -extern RowMixerFunc MixRowSamples; - -extern const ALfloat ConeScale; -extern const ALfloat ZScale; -extern const ALboolean OverrideReverbSpeedOfSound; - -#endif diff --git a/modules/openal-soft/OpenAL32/Include/sample_cvt.h b/modules/openal-soft/OpenAL32/Include/sample_cvt.h deleted file mode 100644 index a3b53d4..0000000 --- a/modules/openal-soft/OpenAL32/Include/sample_cvt.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef SAMPLE_CVT_H -#define SAMPLE_CVT_H - -#include "AL/al.h" -#include "alBuffer.h" - -#ifdef __cplusplus -extern "C" { -#endif - -extern const ALshort muLawDecompressionTable[256]; -extern const ALshort aLawDecompressionTable[256]; - -void Convert_ALshort_ALima4(ALshort *dst, const ALubyte *src, ALsizei numchans, ALsizei len, - ALsizei align); -void Convert_ALshort_ALmsadpcm(ALshort *dst, const ALubyte *src, ALsizei numchans, ALsizei len, - ALsizei align); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif /* SAMPLE_CVT_H */ diff --git a/modules/openal-soft/OpenAL32/alAuxEffectSlot.cpp b/modules/openal-soft/OpenAL32/alAuxEffectSlot.cpp deleted file mode 100644 index 4450343..0000000 --- a/modules/openal-soft/OpenAL32/alAuxEffectSlot.cpp +++ /dev/null @@ -1,803 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include -#include - -#include -#include - -#include "AL/al.h" -#include "AL/alc.h" - -#include "alMain.h" -#include "alcontext.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alListener.h" -#include "alSource.h" - -#include "fpu_modes.h" -#include "alexcpt.h" -#include "almalloc.h" - - -namespace { - -inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -{ - ALuint lidx = (id-1) >> 6; - ALsizei slidx = (id-1) & 0x3f; - - if(UNLIKELY(lidx >= context->EffectSlotList.size())) - return nullptr; - EffectSlotSubList &sublist{context->EffectSlotList[lidx]}; - if(UNLIKELY(sublist.FreeMask & (1_u64 << slidx))) - return nullptr; - return sublist.EffectSlots + slidx; -} - -inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) noexcept -{ - ALuint lidx = (id-1) >> 6; - ALsizei slidx = (id-1) & 0x3f; - - if(UNLIKELY(lidx >= device->EffectList.size())) - return nullptr; - EffectSubList &sublist = device->EffectList[lidx]; - if(UNLIKELY(sublist.FreeMask & (1_u64 << slidx))) - return nullptr; - return sublist.Effects + slidx; -} - - -void AddActiveEffectSlots(const ALuint *slotids, ALsizei count, ALCcontext *context) -{ - if(count < 1) return; - ALeffectslotArray *curarray{context->ActiveAuxSlots.load(std::memory_order_acquire)}; - size_t newcount{curarray->size() + count}; - - /* Insert the new effect slots into the head of the array, followed by the - * existing ones. - */ - ALeffectslotArray *newarray = ALeffectslot::CreatePtrArray(newcount); - auto slotiter = std::transform(slotids, slotids+count, newarray->begin(), - [context](ALuint id) noexcept -> ALeffectslot* - { return LookupEffectSlot(context, id); } - ); - std::copy(curarray->begin(), curarray->end(), slotiter); - - /* Remove any duplicates (first instance of each will be kept). */ - auto last = newarray->end(); - for(auto start=newarray->begin()+1;;) - { - last = std::remove(start, last, *(start-1)); - if(start == last) break; - ++start; - } - newcount = static_cast(std::distance(newarray->begin(), last)); - - /* Reallocate newarray if the new size ended up smaller from duplicate - * removal. - */ - if(UNLIKELY(newcount < newarray->size())) - { - curarray = newarray; - newarray = ALeffectslot::CreatePtrArray(newcount); - std::copy_n(curarray->begin(), newcount, newarray->begin()); - delete curarray; - curarray = nullptr; - } - - curarray = context->ActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel); - ALCdevice *device{context->Device}; - while((device->MixCount.load(std::memory_order_acquire)&1)) - std::this_thread::yield(); - delete curarray; -} - -void RemoveActiveEffectSlots(const ALuint *slotids, ALsizei count, ALCcontext *context) -{ - if(count < 1) return; - ALeffectslotArray *curarray{context->ActiveAuxSlots.load(std::memory_order_acquire)}; - - /* Don't shrink the allocated array size since we don't know how many (if - * any) of the effect slots to remove are in the array. - */ - ALeffectslotArray *newarray = ALeffectslot::CreatePtrArray(curarray->size()); - - /* Copy each element in curarray to newarray whose ID is not in slotids. */ - const ALuint *slotids_end{slotids + count}; - auto slotiter = std::copy_if(curarray->begin(), curarray->end(), newarray->begin(), - [slotids, slotids_end](const ALeffectslot *slot) -> bool - { return std::find(slotids, slotids_end, slot->id) == slotids_end; } - ); - - /* Reallocate with the new size. */ - auto newsize = static_cast(std::distance(newarray->begin(), slotiter)); - if(LIKELY(newsize != newarray->size())) - { - curarray = newarray; - newarray = ALeffectslot::CreatePtrArray(newsize); - std::copy_n(curarray->begin(), newsize, newarray->begin()); - - delete curarray; - curarray = nullptr; - } - - curarray = context->ActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel); - ALCdevice *device{context->Device}; - while((device->MixCount.load(std::memory_order_acquire)&1)) - std::this_thread::yield(); - delete curarray; -} - - -ALeffectslot *AllocEffectSlot(ALCcontext *context) -{ - ALCdevice *device{context->Device}; - std::lock_guard _{context->EffectSlotLock}; - if(context->NumEffectSlots >= device->AuxiliaryEffectSlotMax) - { - alSetError(context, AL_OUT_OF_MEMORY, "Exceeding %u effect slot limit", - device->AuxiliaryEffectSlotMax); - return nullptr; - } - auto sublist = std::find_if(context->EffectSlotList.begin(), context->EffectSlotList.end(), - [](const EffectSlotSubList &entry) noexcept -> bool - { return entry.FreeMask != 0; } - ); - auto lidx = static_cast(std::distance(context->EffectSlotList.begin(), sublist)); - ALeffectslot *slot; - ALsizei slidx; - if(LIKELY(sublist != context->EffectSlotList.end())) - { - slidx = CTZ64(sublist->FreeMask); - slot = sublist->EffectSlots + slidx; - } - else - { - /* Don't allocate so many list entries that the 32-bit ID could - * overflow... - */ - if(UNLIKELY(context->EffectSlotList.size() >= 1<<25)) - { - alSetError(context, AL_OUT_OF_MEMORY, "Too many effect slots allocated"); - return nullptr; - } - context->EffectSlotList.emplace_back(); - sublist = context->EffectSlotList.end() - 1; - - sublist->FreeMask = ~0_u64; - sublist->EffectSlots = static_cast(al_calloc(16, sizeof(ALeffectslot)*64)); - if(UNLIKELY(!sublist->EffectSlots)) - { - context->EffectSlotList.pop_back(); - alSetError(context, AL_OUT_OF_MEMORY, "Failed to allocate effect slot batch"); - return nullptr; - } - - slidx = 0; - slot = sublist->EffectSlots + slidx; - } - - slot = new (slot) ALeffectslot{}; - ALenum err{InitEffectSlot(slot)}; - if(err != AL_NO_ERROR) - { - slot->~ALeffectslot(); - alSetError(context, err, "Effect slot object initialization failed"); - return nullptr; - } - aluInitEffectPanning(slot, device); - - /* Add 1 to avoid source ID 0. */ - slot->id = ((lidx<<6) | slidx) + 1; - - context->NumEffectSlots += 1; - sublist->FreeMask &= ~(1_u64 << slidx); - - return slot; -} - -void FreeEffectSlot(ALCcontext *context, ALeffectslot *slot) -{ - ALuint id = slot->id - 1; - ALsizei lidx = id >> 6; - ALsizei slidx = id & 0x3f; - - slot->~ALeffectslot(); - - context->EffectSlotList[lidx].FreeMask |= 1_u64 << slidx; - context->NumEffectSlots--; -} - - -#define DO_UPDATEPROPS() do { \ - if(!context->DeferUpdates.load(std::memory_order_acquire)) \ - UpdateEffectSlotProps(slot, context.get()); \ - else \ - slot->PropsClean.clear(std::memory_order_release); \ -} while(0) - -} // namespace - -ALeffectslotArray *ALeffectslot::CreatePtrArray(size_t count) noexcept -{ - /* Allocate space for twice as many pointers, so the mixer has scratch - * space to store a sorted list during mixing. - */ - void *ptr{al_calloc(DEF_ALIGN, ALeffectslotArray::Sizeof(count*2))}; - return new (ptr) ALeffectslotArray{count}; -} - - -AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(n < 0) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Generating %d effect slots", n); - if(n == 0) return; - - if(n == 1) - { - ALeffectslot *slot{AllocEffectSlot(context.get())}; - if(!slot) return; - effectslots[0] = slot->id; - } - else - { - auto tempids = al::vector(n); - auto alloc_end = std::find_if_not(tempids.begin(), tempids.end(), - [&context](ALuint &id) -> bool - { - ALeffectslot *slot{AllocEffectSlot(context.get())}; - if(!slot) return false; - id = slot->id; - return true; - } - ); - if(alloc_end != tempids.end()) - { - auto count = static_cast(std::distance(tempids.begin(), alloc_end)); - alDeleteAuxiliaryEffectSlots(count, tempids.data()); - return; - } - - std::copy(tempids.cbegin(), tempids.cend(), effectslots); - } - - std::unique_lock slotlock{context->EffectSlotLock}; - AddActiveEffectSlots(effectslots, n, context.get()); -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(n < 0) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Deleting %d effect slots", n); - if(n == 0) return; - - std::lock_guard _{context->EffectSlotLock}; - auto effectslots_end = effectslots + n; - auto bad_slot = std::find_if(effectslots, effectslots_end, - [&context](ALuint id) -> bool - { - ALeffectslot *slot{LookupEffectSlot(context.get(), id)}; - if(!slot) - { - alSetError(context.get(), AL_INVALID_NAME, "Invalid effect slot ID %u", id); - return true; - } - if(ReadRef(&slot->ref) != 0) - { - alSetError(context.get(), AL_INVALID_NAME, "Deleting in-use effect slot %u", id); - return true; - } - return false; - } - ); - if(bad_slot != effectslots_end) - return; - - // All effectslots are valid, remove and delete them - RemoveActiveEffectSlots(effectslots, n, context.get()); - std::for_each(effectslots, effectslots_end, - [&context](ALuint sid) -> void - { - ALeffectslot *slot{LookupEffectSlot(context.get(), sid)}; - if(slot) FreeEffectSlot(context.get(), slot); - } - ); -} -END_API_FUNC - -AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(LIKELY(context)) - { - std::lock_guard _{context->EffectSlotLock}; - if(LookupEffectSlot(context.get(), effectslot) != nullptr) - return AL_TRUE; - } - return AL_FALSE; -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->EffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(UNLIKELY(!slot)) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); - - ALeffectslot *target{}; - ALCdevice *device{}; - ALenum err{}; - switch(param) - { - case AL_EFFECTSLOT_EFFECT: - device = context->Device; - - { std::lock_guard ___{device->EffectLock}; - ALeffect *effect{value ? LookupEffect(device, value) : nullptr}; - if(!(value == 0 || effect != nullptr)) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Invalid effect ID %u", value); - err = InitializeEffect(context.get(), slot, effect); - } - if(err != AL_NO_ERROR) - { - alSetError(context.get(), err, "Effect initialization failed"); - return; - } - break; - - case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: - if(!(value == AL_TRUE || value == AL_FALSE)) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, - "Effect slot auxiliary send auto out of range"); - slot->AuxSendAuto = value; - break; - - case AL_EFFECTSLOT_TARGET_SOFT: - target = (value ? LookupEffectSlot(context.get(), value) : nullptr); - if(value && !target) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Invalid effect slot target ID"); - if(target) - { - ALeffectslot *checker{target}; - while(checker && checker != slot) - checker = checker->Target; - if(checker) - SETERR_RETURN(context.get(), AL_INVALID_OPERATION,, - "Setting target of effect slot ID %u to %u creates circular chain", slot->id, - target->id); - } - - if(ALeffectslot *oldtarget{slot->Target}) - { - /* We must force an update if there was an existing effect slot - * target, in case it's about to be deleted. - */ - if(target) IncrementRef(&target->ref); - DecrementRef(&oldtarget->ref); - slot->Target = target; - UpdateEffectSlotProps(slot, context.get()); - return; - } - - if(target) IncrementRef(&target->ref); - slot->Target = target; - break; - - default: - SETERR_RETURN(context.get(), AL_INVALID_ENUM,, - "Invalid effect slot integer property 0x%04x", param); - } - DO_UPDATEPROPS(); -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *values) -START_API_FUNC -{ - switch(param) - { - case AL_EFFECTSLOT_EFFECT: - case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: - case AL_EFFECTSLOT_TARGET_SOFT: - alAuxiliaryEffectSloti(effectslot, param, values[0]); - return; - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->EffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(UNLIKELY(!slot)) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); - - switch(param) - { - default: - SETERR_RETURN(context.get(), AL_INVALID_ENUM,, - "Invalid effect slot integer-vector property 0x%04x", param); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->EffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(UNLIKELY(!slot)) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); - - switch(param) - { - case AL_EFFECTSLOT_GAIN: - if(!(value >= 0.0f && value <= 1.0f)) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Effect slot gain out of range"); - slot->Gain = value; - break; - - default: - SETERR_RETURN(context.get(), AL_INVALID_ENUM,, "Invalid effect slot float property 0x%04x", - param); - } - DO_UPDATEPROPS(); -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *values) -START_API_FUNC -{ - switch(param) - { - case AL_EFFECTSLOT_GAIN: - alAuxiliaryEffectSlotf(effectslot, param, values[0]); - return; - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->EffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(UNLIKELY(!slot)) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); - - switch(param) - { - default: - SETERR_RETURN(context.get(), AL_INVALID_ENUM,, - "Invalid effect slot float-vector property 0x%04x", param); - } -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->EffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(UNLIKELY(!slot)) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); - - switch(param) - { - case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: - *value = slot->AuxSendAuto; - break; - - case AL_EFFECTSLOT_TARGET_SOFT: - *value = slot->Target ? slot->Target->id : 0; - break; - - default: - SETERR_RETURN(context.get(), AL_INVALID_ENUM,, - "Invalid effect slot integer property 0x%04x", param); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *values) -START_API_FUNC -{ - switch(param) - { - case AL_EFFECTSLOT_EFFECT: - case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: - case AL_EFFECTSLOT_TARGET_SOFT: - alGetAuxiliaryEffectSloti(effectslot, param, values); - return; - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->EffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(UNLIKELY(!slot)) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); - - switch(param) - { - default: - SETERR_RETURN(context.get(), AL_INVALID_ENUM,, - "Invalid effect slot integer-vector property 0x%04x", param); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->EffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(UNLIKELY(!slot)) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); - - switch(param) - { - case AL_EFFECTSLOT_GAIN: - *value = slot->Gain; - break; - - default: - SETERR_RETURN(context.get(), AL_INVALID_ENUM,, - "Invalid effect slot float property 0x%04x", param); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *values) -START_API_FUNC -{ - switch(param) - { - case AL_EFFECTSLOT_GAIN: - alGetAuxiliaryEffectSlotf(effectslot, param, values); - return; - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->EffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(UNLIKELY(!slot)) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); - - switch(param) - { - default: - SETERR_RETURN(context.get(), AL_INVALID_ENUM,, - "Invalid effect slot float-vector property 0x%04x", param); - } -} -END_API_FUNC - - -ALenum InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect) -{ - ALenum newtype{effect ? effect->type : AL_EFFECT_NULL}; - if(newtype != EffectSlot->Effect.Type) - { - EffectStateFactory *factory{getFactoryByType(newtype)}; - if(!factory) - { - ERR("Failed to find factory for effect type 0x%04x\n", newtype); - return AL_INVALID_ENUM; - } - EffectState *State{factory->create()}; - if(!State) return AL_OUT_OF_MEMORY; - - FPUCtl mixer_mode{}; - ALCdevice *Device{Context->Device}; - std::unique_lock statelock{Device->StateLock}; - State->mOutBuffer = Device->Dry.Buffer; - State->mOutChannels = Device->Dry.NumChannels; - if(State->deviceUpdate(Device) == AL_FALSE) - { - statelock.unlock(); - mixer_mode.leave(); - State->DecRef(); - return AL_OUT_OF_MEMORY; - } - mixer_mode.leave(); - - if(!effect) - { - EffectSlot->Effect.Type = AL_EFFECT_NULL; - EffectSlot->Effect.Props = EffectProps {}; - } - else - { - EffectSlot->Effect.Type = effect->type; - EffectSlot->Effect.Props = effect->Props; - } - - EffectSlot->Effect.State->DecRef(); - EffectSlot->Effect.State = State; - } - else if(effect) - EffectSlot->Effect.Props = effect->Props; - - /* Remove state references from old effect slot property updates. */ - ALeffectslotProps *props{Context->FreeEffectslotProps.load()}; - while(props) - { - if(props->State) - props->State->DecRef(); - props->State = nullptr; - props = props->next.load(std::memory_order_relaxed); - } - - return AL_NO_ERROR; -} - - -void EffectState::IncRef() noexcept -{ - auto ref = IncrementRef(&mRef); - TRACEREF("%p increasing refcount to %u\n", this, ref); -} - -void EffectState::DecRef() noexcept -{ - auto ref = DecrementRef(&mRef); - TRACEREF("%p decreasing refcount to %u\n", this, ref); - if(ref == 0) delete this; -} - - -ALenum InitEffectSlot(ALeffectslot *slot) -{ - EffectStateFactory *factory{getFactoryByType(slot->Effect.Type)}; - if(!factory) return AL_INVALID_VALUE; - slot->Effect.State = factory->create(); - if(!slot->Effect.State) return AL_OUT_OF_MEMORY; - - slot->Effect.State->IncRef(); - slot->Params.mEffectState = slot->Effect.State; - return AL_NO_ERROR; -} - -ALeffectslot::~ALeffectslot() -{ - if(Target) - DecrementRef(&Target->ref); - Target = nullptr; - - ALeffectslotProps *props{Update.load()}; - if(props) - { - if(props->State) props->State->DecRef(); - TRACE("Freed unapplied AuxiliaryEffectSlot update %p\n", props); - al_free(props); - } - - if(Effect.State) - Effect.State->DecRef(); - if(Params.mEffectState) - Params.mEffectState->DecRef(); -} - -void UpdateEffectSlotProps(ALeffectslot *slot, ALCcontext *context) -{ - /* Get an unused property container, or allocate a new one as needed. */ - ALeffectslotProps *props{context->FreeEffectslotProps.load(std::memory_order_relaxed)}; - if(!props) - props = static_cast(al_calloc(16, sizeof(*props))); - else - { - ALeffectslotProps *next; - do { - next = props->next.load(std::memory_order_relaxed); - } while(context->FreeEffectslotProps.compare_exchange_weak(props, next, - std::memory_order_seq_cst, std::memory_order_acquire) == 0); - } - - /* Copy in current property values. */ - props->Gain = slot->Gain; - props->AuxSendAuto = slot->AuxSendAuto; - props->Target = slot->Target; - - props->Type = slot->Effect.Type; - props->Props = slot->Effect.Props; - /* Swap out any stale effect state object there may be in the container, to - * delete it. - */ - EffectState *oldstate{props->State}; - slot->Effect.State->IncRef(); - props->State = slot->Effect.State; - - /* Set the new container for updating internal parameters. */ - props = slot->Update.exchange(props, std::memory_order_acq_rel); - if(props) - { - /* If there was an unused update container, put it back in the - * freelist. - */ - if(props->State) - props->State->DecRef(); - props->State = nullptr; - AtomicReplaceHead(context->FreeEffectslotProps, props); - } - - if(oldstate) - oldstate->DecRef(); -} - -void UpdateAllEffectSlotProps(ALCcontext *context) -{ - std::lock_guard _{context->EffectSlotLock}; - ALeffectslotArray *auxslots{context->ActiveAuxSlots.load(std::memory_order_acquire)}; - for(ALeffectslot *slot : *auxslots) - { - if(!slot->PropsClean.test_and_set(std::memory_order_acq_rel)) - UpdateEffectSlotProps(slot, context); - } -} - -EffectSlotSubList::~EffectSlotSubList() -{ - uint64_t usemask{~FreeMask}; - while(usemask) - { - ALsizei idx{CTZ64(usemask)}; - EffectSlots[idx].~ALeffectslot(); - usemask &= ~(1_u64 << idx); - } - FreeMask = ~usemask; - al_free(EffectSlots); - EffectSlots = nullptr; -} diff --git a/modules/openal-soft/OpenAL32/alBuffer.cpp b/modules/openal-soft/OpenAL32/alBuffer.cpp deleted file mode 100644 index 1dc37ec..0000000 --- a/modules/openal-soft/OpenAL32/alBuffer.cpp +++ /dev/null @@ -1,1230 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include -#include -#include -#ifdef HAVE_MALLOC_H -#include -#endif - -#include -#include -#include -#include -#include - -#include "alMain.h" -#include "alcontext.h" -#include "alu.h" -#include "alError.h" -#include "alBuffer.h" -#include "sample_cvt.h" -#include "alexcpt.h" - - -namespace { - -constexpr ALbitfieldSOFT INVALID_STORAGE_MASK{~unsigned(AL_MAP_READ_BIT_SOFT | - AL_MAP_WRITE_BIT_SOFT | AL_MAP_PERSISTENT_BIT_SOFT | AL_PRESERVE_DATA_BIT_SOFT)}; -constexpr ALbitfieldSOFT MAP_READ_WRITE_FLAGS{AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT}; -constexpr ALbitfieldSOFT INVALID_MAP_FLAGS{~unsigned(AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT | - AL_MAP_PERSISTENT_BIT_SOFT)}; - - -ALbuffer *AllocBuffer(ALCcontext *context) -{ - ALCdevice *device{context->Device}; - std::lock_guard _{device->BufferLock}; - auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(), - [](const BufferSubList &entry) noexcept -> bool - { return entry.FreeMask != 0; } - ); - - auto lidx = static_cast(std::distance(device->BufferList.begin(), sublist)); - ALbuffer *buffer{nullptr}; - ALsizei slidx{0}; - if(LIKELY(sublist != device->BufferList.end())) - { - slidx = CTZ64(sublist->FreeMask); - buffer = sublist->Buffers + slidx; - } - else - { - /* Don't allocate so many list entries that the 32-bit ID could - * overflow... - */ - if(UNLIKELY(device->BufferList.size() >= 1<<25)) - { - alSetError(context, AL_OUT_OF_MEMORY, "Too many buffers allocated"); - return nullptr; - } - device->BufferList.emplace_back(); - sublist = device->BufferList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->Buffers = reinterpret_cast(al_calloc(16, sizeof(ALbuffer)*64)); - if(UNLIKELY(!sublist->Buffers)) - { - device->BufferList.pop_back(); - alSetError(context, AL_OUT_OF_MEMORY, "Failed to allocate buffer batch"); - return nullptr; - } - - slidx = 0; - buffer = sublist->Buffers + slidx; - } - - buffer = new (buffer) ALbuffer{}; - /* Add 1 to avoid buffer ID 0. */ - buffer->id = ((lidx<<6) | slidx) + 1; - - sublist->FreeMask &= ~(1_u64 << slidx); - - return buffer; -} - -void FreeBuffer(ALCdevice *device, ALbuffer *buffer) -{ - ALuint id{buffer->id - 1}; - ALsizei lidx = id >> 6; - ALsizei slidx = id & 0x3f; - - buffer->~ALbuffer(); - - device->BufferList[lidx].FreeMask |= 1_u64 << slidx; -} - -inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) -{ - ALuint lidx = (id-1) >> 6; - ALsizei slidx = (id-1) & 0x3f; - - if(UNLIKELY(lidx >= device->BufferList.size())) - return nullptr; - BufferSubList &sublist = device->BufferList[lidx]; - if(UNLIKELY(sublist.FreeMask & (1_u64 << slidx))) - return nullptr; - return sublist.Buffers + slidx; -} - - -ALsizei SanitizeAlignment(UserFmtType type, ALsizei align) -{ - if(align < 0) - return 0; - - if(align == 0) - { - if(type == UserFmtIMA4) - { - /* Here is where things vary: - * nVidia and Apple use 64+1 sample frames per block -> block_size=36 bytes per channel - * Most PC sound software uses 2040+1 sample frames per block -> block_size=1024 bytes per channel - */ - return 65; - } - if(type == UserFmtMSADPCM) - return 64; - return 1; - } - - if(type == UserFmtIMA4) - { - /* IMA4 block alignment must be a multiple of 8, plus 1. */ - if((align&7) == 1) return align; - return 0; - } - if(type == UserFmtMSADPCM) - { - /* MSADPCM block alignment must be a multiple of 2. */ - if((align&1) == 0) return align; - return 0; - } - - return align; -} - - -const ALchar *NameFromUserFmtType(UserFmtType type) -{ - switch(type) - { - case UserFmtUByte: return "Unsigned Byte"; - case UserFmtShort: return "Signed Short"; - case UserFmtFloat: return "Float32"; - case UserFmtDouble: return "Float64"; - case UserFmtMulaw: return "muLaw"; - case UserFmtAlaw: return "aLaw"; - case UserFmtIMA4: return "IMA4 ADPCM"; - case UserFmtMSADPCM: return "MSADPCM"; - } - return ""; -} - -/* - * LoadData - * - * Loads the specified data into the buffer, using the specified format. - */ -void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALuint freq, ALsizei size, UserFmtChannels SrcChannels, UserFmtType SrcType, const ALvoid *data, ALbitfieldSOFT access) -{ - if(UNLIKELY(ReadRef(&ALBuf->ref) != 0 || ALBuf->MappedAccess != 0)) - SETERR_RETURN(context, AL_INVALID_OPERATION,, "Modifying storage for in-use buffer %u", - ALBuf->id); - - /* Currently no channel configurations need to be converted. */ - FmtChannels DstChannels{FmtMono}; - switch(SrcChannels) - { - case UserFmtMono: DstChannels = FmtMono; break; - case UserFmtStereo: DstChannels = FmtStereo; break; - case UserFmtRear: DstChannels = FmtRear; break; - case UserFmtQuad: DstChannels = FmtQuad; break; - case UserFmtX51: DstChannels = FmtX51; break; - case UserFmtX61: DstChannels = FmtX61; break; - case UserFmtX71: DstChannels = FmtX71; break; - case UserFmtBFormat2D: DstChannels = FmtBFormat2D; break; - case UserFmtBFormat3D: DstChannels = FmtBFormat3D; break; - } - if (UNLIKELY(static_cast(SrcChannels) != - static_cast(DstChannels))) - SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format"); - - /* IMA4 and MSADPCM convert to 16-bit short. */ - FmtType DstType{FmtUByte}; - switch(SrcType) - { - case UserFmtUByte: DstType = FmtUByte; break; - case UserFmtShort: DstType = FmtShort; break; - case UserFmtFloat: DstType = FmtFloat; break; - case UserFmtDouble: DstType = FmtDouble; break; - case UserFmtAlaw: DstType = FmtAlaw; break; - case UserFmtMulaw: DstType = FmtMulaw; break; - case UserFmtIMA4: DstType = FmtShort; break; - case UserFmtMSADPCM: DstType = FmtShort; break; - } - - /* TODO: Currently we can only map samples when they're not converted. To - * allow it would need some kind of double-buffering to hold onto a copy of - * the original data. - */ - if((access&MAP_READ_WRITE_FLAGS)) - { - if (UNLIKELY(static_cast(SrcType) != static_cast(DstType))) - SETERR_RETURN(context, AL_INVALID_VALUE, , - "%s samples cannot be mapped", - NameFromUserFmtType(SrcType)); - } - - ALsizei unpackalign{ALBuf->UnpackAlign.load()}; - ALsizei align{SanitizeAlignment(SrcType, unpackalign)}; - if(UNLIKELY(align < 1)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid unpack alignment %d for %s samples", - unpackalign, NameFromUserFmtType(SrcType)); - - if((access&AL_PRESERVE_DATA_BIT_SOFT)) - { - /* Can only preserve data with the same format and alignment. */ - if(UNLIKELY(ALBuf->mFmtChannels != DstChannels || ALBuf->OriginalType != SrcType)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched format"); - if(UNLIKELY(ALBuf->OriginalAlign != align)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched alignment"); - } - - /* Convert the input/source size in bytes to sample frames using the unpack - * block alignment. - */ - ALsizei SrcByteAlign{ - (SrcType == UserFmtIMA4) ? ((align-1)/2 + 4) * ChannelsFromUserFmt(SrcChannels) : - (SrcType == UserFmtMSADPCM) ? ((align-2)/2 + 7) * ChannelsFromUserFmt(SrcChannels) : - (align * FrameSizeFromUserFmt(SrcChannels, SrcType)) - }; - if(UNLIKELY((size%SrcByteAlign) != 0)) - SETERR_RETURN(context, AL_INVALID_VALUE,, - "Data size %d is not a multiple of frame size %d (%d unpack alignment)", - size, SrcByteAlign, align); - - if(UNLIKELY(size/SrcByteAlign > std::numeric_limits::max()/align)) - SETERR_RETURN(context, AL_OUT_OF_MEMORY,, - "Buffer size overflow, %d blocks x %d samples per block", size/SrcByteAlign, align); - ALsizei frames{size / SrcByteAlign * align}; - - /* Convert the sample frames to the number of bytes needed for internal - * storage. - */ - ALsizei NumChannels{ChannelsFromFmt(DstChannels)}; - ALsizei FrameSize{NumChannels * BytesFromFmt(DstType)}; - if(UNLIKELY(frames > std::numeric_limits::max()/FrameSize)) - SETERR_RETURN(context, AL_OUT_OF_MEMORY,, - "Buffer size overflow, %d frames x %d bytes per frame", frames, FrameSize); - ALsizei newsize{frames*FrameSize}; - - /* Round up to the next 16-byte multiple. This could reallocate only when - * increasing or the new size is less than half the current, but then the - * buffer's AL_SIZE would not be very reliable for accounting buffer memory - * usage, and reporting the real size could cause problems for apps that - * use AL_SIZE to try to get the buffer's play length. - */ - if(LIKELY(newsize <= std::numeric_limits::max()-15)) - newsize = (newsize+15) & ~0xf; - if(newsize != ALBuf->BytesAlloc) - { - al::vector newdata(newsize); - if((access&AL_PRESERVE_DATA_BIT_SOFT)) - { - ALsizei tocopy{std::min(newsize, ALBuf->BytesAlloc)}; - std::copy_n(ALBuf->mData.begin(), tocopy, newdata.begin()); - } - ALBuf->mData = std::move(newdata); - ALBuf->BytesAlloc = newsize; - } - - if(SrcType == UserFmtIMA4) - { - assert(DstType == FmtShort); - if(data != nullptr && !ALBuf->mData.empty()) - Convert_ALshort_ALima4(reinterpret_cast(ALBuf->mData.data()), - static_cast(data), NumChannels, frames, align); - ALBuf->OriginalAlign = align; - } - else if(SrcType == UserFmtMSADPCM) - { - assert(DstType == FmtShort); - if(data != nullptr && !ALBuf->mData.empty()) - Convert_ALshort_ALmsadpcm(reinterpret_cast(ALBuf->mData.data()), - static_cast(data), NumChannels, frames, align); - ALBuf->OriginalAlign = align; - } - else - { - assert(static_cast(SrcType) == static_cast(DstType)); - if(data != nullptr && !ALBuf->mData.empty()) - std::copy_n(static_cast(data), frames*FrameSize, ALBuf->mData.begin()); - ALBuf->OriginalAlign = 1; - } - ALBuf->OriginalSize = size; - ALBuf->OriginalType = SrcType; - - ALBuf->Frequency = freq; - ALBuf->mFmtChannels = DstChannels; - ALBuf->mFmtType = DstType; - ALBuf->Access = access; - - ALBuf->SampleLen = frames; - ALBuf->LoopStart = 0; - ALBuf->LoopEnd = ALBuf->SampleLen; -} - -using DecompResult = std::tuple; -DecompResult DecomposeUserFormat(ALenum format) -{ - struct FormatMap { - ALenum format; - UserFmtChannels channels; - UserFmtType type; - }; - static constexpr std::array UserFmtList{{ - { AL_FORMAT_MONO8, UserFmtMono, UserFmtUByte }, - { AL_FORMAT_MONO16, UserFmtMono, UserFmtShort }, - { AL_FORMAT_MONO_FLOAT32, UserFmtMono, UserFmtFloat }, - { AL_FORMAT_MONO_DOUBLE_EXT, UserFmtMono, UserFmtDouble }, - { AL_FORMAT_MONO_IMA4, UserFmtMono, UserFmtIMA4 }, - { AL_FORMAT_MONO_MSADPCM_SOFT, UserFmtMono, UserFmtMSADPCM }, - { AL_FORMAT_MONO_MULAW, UserFmtMono, UserFmtMulaw }, - { AL_FORMAT_MONO_ALAW_EXT, UserFmtMono, UserFmtAlaw }, - - { AL_FORMAT_STEREO8, UserFmtStereo, UserFmtUByte }, - { AL_FORMAT_STEREO16, UserFmtStereo, UserFmtShort }, - { AL_FORMAT_STEREO_FLOAT32, UserFmtStereo, UserFmtFloat }, - { AL_FORMAT_STEREO_DOUBLE_EXT, UserFmtStereo, UserFmtDouble }, - { AL_FORMAT_STEREO_IMA4, UserFmtStereo, UserFmtIMA4 }, - { AL_FORMAT_STEREO_MSADPCM_SOFT, UserFmtStereo, UserFmtMSADPCM }, - { AL_FORMAT_STEREO_MULAW, UserFmtStereo, UserFmtMulaw }, - { AL_FORMAT_STEREO_ALAW_EXT, UserFmtStereo, UserFmtAlaw }, - - { AL_FORMAT_REAR8, UserFmtRear, UserFmtUByte }, - { AL_FORMAT_REAR16, UserFmtRear, UserFmtShort }, - { AL_FORMAT_REAR32, UserFmtRear, UserFmtFloat }, - { AL_FORMAT_REAR_MULAW, UserFmtRear, UserFmtMulaw }, - - { AL_FORMAT_QUAD8_LOKI, UserFmtQuad, UserFmtUByte }, - { AL_FORMAT_QUAD16_LOKI, UserFmtQuad, UserFmtShort }, - - { AL_FORMAT_QUAD8, UserFmtQuad, UserFmtUByte }, - { AL_FORMAT_QUAD16, UserFmtQuad, UserFmtShort }, - { AL_FORMAT_QUAD32, UserFmtQuad, UserFmtFloat }, - { AL_FORMAT_QUAD_MULAW, UserFmtQuad, UserFmtMulaw }, - - { AL_FORMAT_51CHN8, UserFmtX51, UserFmtUByte }, - { AL_FORMAT_51CHN16, UserFmtX51, UserFmtShort }, - { AL_FORMAT_51CHN32, UserFmtX51, UserFmtFloat }, - { AL_FORMAT_51CHN_MULAW, UserFmtX51, UserFmtMulaw }, - - { AL_FORMAT_61CHN8, UserFmtX61, UserFmtUByte }, - { AL_FORMAT_61CHN16, UserFmtX61, UserFmtShort }, - { AL_FORMAT_61CHN32, UserFmtX61, UserFmtFloat }, - { AL_FORMAT_61CHN_MULAW, UserFmtX61, UserFmtMulaw }, - - { AL_FORMAT_71CHN8, UserFmtX71, UserFmtUByte }, - { AL_FORMAT_71CHN16, UserFmtX71, UserFmtShort }, - { AL_FORMAT_71CHN32, UserFmtX71, UserFmtFloat }, - { AL_FORMAT_71CHN_MULAW, UserFmtX71, UserFmtMulaw }, - - { AL_FORMAT_BFORMAT2D_8, UserFmtBFormat2D, UserFmtUByte }, - { AL_FORMAT_BFORMAT2D_16, UserFmtBFormat2D, UserFmtShort }, - { AL_FORMAT_BFORMAT2D_FLOAT32, UserFmtBFormat2D, UserFmtFloat }, - { AL_FORMAT_BFORMAT2D_MULAW, UserFmtBFormat2D, UserFmtMulaw }, - - { AL_FORMAT_BFORMAT3D_8, UserFmtBFormat3D, UserFmtUByte }, - { AL_FORMAT_BFORMAT3D_16, UserFmtBFormat3D, UserFmtShort }, - { AL_FORMAT_BFORMAT3D_FLOAT32, UserFmtBFormat3D, UserFmtFloat }, - { AL_FORMAT_BFORMAT3D_MULAW, UserFmtBFormat3D, UserFmtMulaw }, - }}; - - DecompResult ret{}; - for(const auto &fmt : UserFmtList) - { - if(fmt.format == format) - { - std::get<0>(ret) = true; - std::get<1>(ret) = fmt.channels; - std::get<2>(ret) = fmt.type; - break; - } - } - return ret; -} - -} // namespace - - -AL_API ALvoid AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(UNLIKELY(n < 0)) - { - alSetError(context.get(), AL_INVALID_VALUE, "Generating %d buffers", n); - return; - } - - if(LIKELY(n == 1)) - { - /* Special handling for the easy and normal case. */ - ALbuffer *buffer = AllocBuffer(context.get()); - if(buffer) buffers[0] = buffer->id; - } - else if(n > 1) - { - /* Store the allocated buffer IDs in a separate local list, to avoid - * modifying the user storage in case of failure. - */ - al::vector ids; - ids.reserve(n); - do { - ALbuffer *buffer = AllocBuffer(context.get()); - if(!buffer) - { - alDeleteBuffers(static_cast(ids.size()), ids.data()); - return; - } - - ids.emplace_back(buffer->id); - } while(--n); - std::copy(ids.begin(), ids.end(), buffers); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(UNLIKELY(n < 0)) - { - alSetError(context.get(), AL_INVALID_VALUE, "Deleting %d buffers", n); - return; - } - if(UNLIKELY(n == 0)) - return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - /* First try to find any buffers that are invalid or in-use. */ - const ALuint *buffers_end = buffers + n; - auto invbuf = std::find_if(buffers, buffers_end, - [device, &context](ALuint bid) -> bool - { - if(!bid) return false; - ALbuffer *ALBuf = LookupBuffer(device, bid); - if(UNLIKELY(!ALBuf)) - { - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", bid); - return true; - } - if(UNLIKELY(ReadRef(&ALBuf->ref) != 0)) - { - alSetError(context.get(), AL_INVALID_OPERATION, "Deleting in-use buffer %u", bid); - return true; - } - return false; - } - ); - if(LIKELY(invbuf == buffers_end)) - { - /* All good. Delete non-0 buffer IDs. */ - std::for_each(buffers, buffers_end, - [device](ALuint bid) -> void - { - ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr}; - if(buffer) FreeBuffer(device, buffer); - } - ); - } -} -END_API_FUNC - -AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(LIKELY(context)) - { - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - if(!buffer || LookupBuffer(device, buffer)) - return AL_TRUE; - } - return AL_FALSE; -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq) -START_API_FUNC -{ alBufferStorageSOFT(buffer, format, data, size, freq, 0); } -END_API_FUNC - -AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if(UNLIKELY(!albuf)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(UNLIKELY(size < 0)) - alSetError(context.get(), AL_INVALID_VALUE, "Negative storage size %d", size); - else if(UNLIKELY(freq < 1)) - alSetError(context.get(), AL_INVALID_VALUE, "Invalid sample rate %d", freq); - else if(UNLIKELY((flags&INVALID_STORAGE_MASK) != 0)) - alSetError(context.get(), AL_INVALID_VALUE, "Invalid storage flags 0x%x", - flags&INVALID_STORAGE_MASK); - else if(UNLIKELY((flags&AL_MAP_PERSISTENT_BIT_SOFT) && !(flags&MAP_READ_WRITE_FLAGS))) - alSetError(context.get(), AL_INVALID_VALUE, - "Declaring persistently mapped storage without read or write access"); - else - { - UserFmtType srctype{UserFmtUByte}; - UserFmtChannels srcchannels{UserFmtMono}; - bool success; - - std::tie(success, srcchannels, srctype) = DecomposeUserFormat(format); - if(UNLIKELY(!success)) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid format 0x%04x", format); - else - LoadData(context.get(), albuf, freq, size, srcchannels, srctype, data, flags); - } -} -END_API_FUNC - -AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return nullptr; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if(UNLIKELY(!albuf)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(UNLIKELY((access&INVALID_MAP_FLAGS) != 0)) - alSetError(context.get(), AL_INVALID_VALUE, "Invalid map flags 0x%x", access&INVALID_MAP_FLAGS); - else if(UNLIKELY(!(access&MAP_READ_WRITE_FLAGS))) - alSetError(context.get(), AL_INVALID_VALUE, "Mapping buffer %u without read or write access", - buffer); - else - { - ALbitfieldSOFT unavailable = (albuf->Access^access) & access; - if(UNLIKELY(ReadRef(&albuf->ref) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT))) - alSetError(context.get(), AL_INVALID_OPERATION, - "Mapping in-use buffer %u without persistent mapping", buffer); - else if(UNLIKELY(albuf->MappedAccess != 0)) - alSetError(context.get(), AL_INVALID_OPERATION, "Mapping already-mapped buffer %u", buffer); - else if(UNLIKELY((unavailable&AL_MAP_READ_BIT_SOFT))) - alSetError(context.get(), AL_INVALID_VALUE, - "Mapping buffer %u for reading without read access", buffer); - else if(UNLIKELY((unavailable&AL_MAP_WRITE_BIT_SOFT))) - alSetError(context.get(), AL_INVALID_VALUE, - "Mapping buffer %u for writing without write access", buffer); - else if(UNLIKELY((unavailable&AL_MAP_PERSISTENT_BIT_SOFT))) - alSetError(context.get(), AL_INVALID_VALUE, - "Mapping buffer %u persistently without persistent access", buffer); - else if(UNLIKELY(offset < 0 || offset >= albuf->OriginalSize || - length <= 0 || length > albuf->OriginalSize - offset)) - alSetError(context.get(), AL_INVALID_VALUE, "Mapping invalid range %d+%d for buffer %u", - offset, length, buffer); - else - { - void *retval = albuf->mData.data() + offset; - albuf->MappedAccess = access; - albuf->MappedOffset = offset; - albuf->MappedSize = length; - return retval; - } - } - - return nullptr; -} -END_API_FUNC - -AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if(UNLIKELY(!albuf)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(albuf->MappedAccess == 0) - alSetError(context.get(), AL_INVALID_OPERATION, "Unmapping unmapped buffer %u", buffer); - else - { - albuf->MappedAccess = 0; - albuf->MappedOffset = 0; - albuf->MappedSize = 0; - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if(UNLIKELY(!albuf)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(UNLIKELY(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT))) - alSetError(context.get(), AL_INVALID_OPERATION, - "Flushing buffer %u while not mapped for writing", buffer); - else if(UNLIKELY(offset < albuf->MappedOffset || - offset >= albuf->MappedOffset+albuf->MappedSize || - length <= 0 || length > albuf->MappedOffset+albuf->MappedSize-offset)) - alSetError(context.get(), AL_INVALID_VALUE, "Flushing invalid range %d+%d on buffer %u", - offset, length, buffer); - else - { - /* FIXME: Need to use some method of double-buffering for the mixer and - * app to hold separate memory, which can be safely transfered - * asynchronously. Currently we just say the app shouldn't write where - * OpenAL's reading, and hope for the best... - */ - std::atomic_thread_fence(std::memory_order_seq_cst); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alBufferSubDataSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if(UNLIKELY(!albuf)) - { - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - return; - } - - UserFmtType srctype{UserFmtUByte}; - UserFmtChannels srcchannels{UserFmtMono}; - bool success; - std::tie(success, srcchannels, srctype) = DecomposeUserFormat(format); - if(UNLIKELY(!success)) - { - alSetError(context.get(), AL_INVALID_ENUM, "Invalid format 0x%04x", format); - return; - } - - ALsizei unpack_align{albuf->UnpackAlign.load()}; - ALsizei align{SanitizeAlignment(srctype, unpack_align)}; - if(UNLIKELY(align < 1)) - alSetError(context.get(), AL_INVALID_VALUE, "Invalid unpack alignment %d", unpack_align); - else if (UNLIKELY(static_cast(srcchannels) != - static_cast(albuf->mFmtChannels) || - srctype != albuf->OriginalType)) - alSetError(context.get(), AL_INVALID_ENUM, - "Unpacking data with mismatched format"); - else if(UNLIKELY(align != albuf->OriginalAlign)) - alSetError(context.get(), AL_INVALID_VALUE, - "Unpacking data with alignment %u does not match original alignment %u", - align, albuf->OriginalAlign); - else if(UNLIKELY(albuf->MappedAccess != 0)) - alSetError(context.get(), AL_INVALID_OPERATION, "Unpacking data into mapped buffer %u", - buffer); - else - { - ALsizei num_chans{ChannelsFromFmt(albuf->mFmtChannels)}; - ALsizei frame_size{num_chans * BytesFromFmt(albuf->mFmtType)}; - ALsizei byte_align{ - (albuf->OriginalType == UserFmtIMA4) ? ((align-1)/2 + 4) * num_chans : - (albuf->OriginalType == UserFmtMSADPCM) ? ((align-2)/2 + 7) * num_chans : - (align * frame_size) - }; - - if(UNLIKELY(offset < 0 || length < 0 || offset > albuf->OriginalSize || - length > albuf->OriginalSize-offset)) - alSetError(context.get(), AL_INVALID_VALUE, "Invalid data sub-range %d+%d on buffer %u", - offset, length, buffer); - else if(UNLIKELY((offset%byte_align) != 0)) - alSetError(context.get(), AL_INVALID_VALUE, - "Sub-range offset %d is not a multiple of frame size %d (%d unpack alignment)", - offset, byte_align, align); - else if(UNLIKELY((length%byte_align) != 0)) - alSetError(context.get(), AL_INVALID_VALUE, - "Sub-range length %d is not a multiple of frame size %d (%d unpack alignment)", - length, byte_align, align); - else - { - /* offset -> byte offset, length -> sample count */ - offset = offset/byte_align * align * frame_size; - length = length/byte_align * align; - - void *dst = albuf->mData.data() + offset; - if(srctype == UserFmtIMA4 && albuf->mFmtType == FmtShort) - Convert_ALshort_ALima4(static_cast(dst), - static_cast(data), num_chans, length, align); - else if(srctype == UserFmtMSADPCM && albuf->mFmtType == FmtShort) - Convert_ALshort_ALmsadpcm(static_cast(dst), - static_cast(data), num_chans, length, align); - else - { - assert(static_cast(srctype) == - static_cast(albuf->mFmtType)); - memcpy(dst, data, length * frame_size); - } - } - } -} -END_API_FUNC - - -AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint UNUSED(buffer), - ALuint UNUSED(samplerate), ALenum UNUSED(internalformat), ALsizei UNUSED(samples), - ALenum UNUSED(channels), ALenum UNUSED(type), const ALvoid *UNUSED(data)) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - alSetError(context.get(), AL_INVALID_OPERATION, "alBufferSamplesSOFT not supported"); -} -END_API_FUNC - -AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint UNUSED(buffer), - ALsizei UNUSED(offset), ALsizei UNUSED(samples), - ALenum UNUSED(channels), ALenum UNUSED(type), const ALvoid *UNUSED(data)) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - alSetError(context.get(), AL_INVALID_OPERATION, "alBufferSubSamplesSOFT not supported"); -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint UNUSED(buffer), - ALsizei UNUSED(offset), ALsizei UNUSED(samples), - ALenum UNUSED(channels), ALenum UNUSED(type), ALvoid *UNUSED(data)) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - alSetError(context.get(), AL_INVALID_OPERATION, "alGetBufferSamplesSOFT not supported"); -} -END_API_FUNC - -AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum UNUSED(format)) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) return AL_FALSE; - - alSetError(context.get(), AL_INVALID_OPERATION, "alIsBufferFormatSupportedSOFT not supported"); - return AL_FALSE; -} -END_API_FUNC - - -AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat UNUSED(value)) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - if(UNLIKELY(LookupBuffer(device, buffer) == nullptr)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else switch(param) - { - default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param, ALfloat UNUSED(value1), ALfloat UNUSED(value2), ALfloat UNUSED(value3)) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - if(UNLIKELY(LookupBuffer(device, buffer) == nullptr)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else switch(param) - { - default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - if(UNLIKELY(LookupBuffer(device, buffer) == nullptr)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(UNLIKELY(!values)) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param); - } -} -END_API_FUNC - - -AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if(UNLIKELY(!albuf)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else switch(param) - { - case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: - if(UNLIKELY(value < 0)) - alSetError(context.get(), AL_INVALID_VALUE, "Invalid unpack block alignment %d", value); - else - albuf->UnpackAlign.store(value); - break; - - case AL_PACK_BLOCK_ALIGNMENT_SOFT: - if(UNLIKELY(value < 0)) - alSetError(context.get(), AL_INVALID_VALUE, "Invalid pack block alignment %d", value); - else - albuf->PackAlign.store(value); - break; - - default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param, ALint UNUSED(value1), ALint UNUSED(value2), ALint UNUSED(value3)) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - if(UNLIKELY(LookupBuffer(device, buffer) == nullptr)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else switch(param) - { - default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alBufferiv(ALuint buffer, ALenum param, const ALint *values) -START_API_FUNC -{ - if(values) - { - switch(param) - { - case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: - case AL_PACK_BLOCK_ALIGNMENT_SOFT: - alBufferi(buffer, param, values[0]); - return; - } - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if(UNLIKELY(!albuf)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(UNLIKELY(!values)) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - case AL_LOOP_POINTS_SOFT: - if(UNLIKELY(ReadRef(&albuf->ref) != 0)) - alSetError(context.get(), AL_INVALID_OPERATION, "Modifying in-use buffer %u's loop points", - buffer); - else if(UNLIKELY(values[0] >= values[1] || values[0] < 0 || values[1] > albuf->SampleLen)) - alSetError(context.get(), AL_INVALID_VALUE, "Invalid loop point range %d -> %d o buffer %u", - values[0], values[1], buffer); - else - { - albuf->LoopStart = values[0]; - albuf->LoopEnd = values[1]; - } - break; - - default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", - param); - } -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if(UNLIKELY(!albuf)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(UNLIKELY(!value)) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - if(UNLIKELY(LookupBuffer(device, buffer) == nullptr)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(UNLIKELY(!value1 || !value2 || !value3)) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values) -START_API_FUNC -{ - switch(param) - { - case AL_SEC_LENGTH_SOFT: - alGetBufferf(buffer, param, values); - return; - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - - if(UNLIKELY(LookupBuffer(device, buffer) == nullptr)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(UNLIKELY(!values)) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param); - } -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(UNLIKELY(!albuf)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(UNLIKELY(!value)) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - case AL_FREQUENCY: - *value = albuf->Frequency; - break; - - case AL_BITS: - *value = BytesFromFmt(albuf->mFmtType) * 8; - break; - - case AL_CHANNELS: - *value = ChannelsFromFmt(albuf->mFmtChannels); - break; - - case AL_SIZE: - *value = albuf->SampleLen * FrameSizeFromFmt(albuf->mFmtChannels, albuf->mFmtType); - break; - - case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: - *value = albuf->UnpackAlign.load(); - break; - - case AL_PACK_BLOCK_ALIGNMENT_SOFT: - *value = albuf->PackAlign.load(); - break; - - default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - if(UNLIKELY(LookupBuffer(device, buffer) == nullptr)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(UNLIKELY(!value1 || !value2 || !value3)) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum param, ALint *values) -START_API_FUNC -{ - switch(param) - { - case AL_FREQUENCY: - case AL_BITS: - case AL_CHANNELS: - case AL_SIZE: - case AL_INTERNAL_FORMAT_SOFT: - case AL_BYTE_LENGTH_SOFT: - case AL_SAMPLE_LENGTH_SOFT: - case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: - case AL_PACK_BLOCK_ALIGNMENT_SOFT: - alGetBufferi(buffer, param, values); - return; - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device = context->Device; - std::lock_guard _{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(UNLIKELY(!albuf)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(UNLIKELY(!values)) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - case AL_LOOP_POINTS_SOFT: - values[0] = albuf->LoopStart; - values[1] = albuf->LoopEnd; - break; - - default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", - param); - } -} -END_API_FUNC - - -ALsizei BytesFromUserFmt(UserFmtType type) -{ - switch(type) - { - case UserFmtUByte: return sizeof(ALubyte); - case UserFmtShort: return sizeof(ALshort); - case UserFmtFloat: return sizeof(ALfloat); - case UserFmtDouble: return sizeof(ALdouble); - case UserFmtMulaw: return sizeof(ALubyte); - case UserFmtAlaw: return sizeof(ALubyte); - case UserFmtIMA4: break; /* not handled here */ - case UserFmtMSADPCM: break; /* not handled here */ - } - return 0; -} -ALsizei ChannelsFromUserFmt(UserFmtChannels chans) -{ - switch(chans) - { - case UserFmtMono: return 1; - case UserFmtStereo: return 2; - case UserFmtRear: return 2; - case UserFmtQuad: return 4; - case UserFmtX51: return 6; - case UserFmtX61: return 7; - case UserFmtX71: return 8; - case UserFmtBFormat2D: return 3; - case UserFmtBFormat3D: return 4; - } - return 0; -} - -ALsizei BytesFromFmt(FmtType type) -{ - switch(type) - { - case FmtUByte: return sizeof(ALubyte); - case FmtShort: return sizeof(ALshort); - case FmtFloat: return sizeof(ALfloat); - case FmtDouble: return sizeof(ALdouble); - case FmtMulaw: return sizeof(ALubyte); - case FmtAlaw: return sizeof(ALubyte); - } - return 0; -} -ALsizei ChannelsFromFmt(FmtChannels chans) -{ - switch(chans) - { - case FmtMono: return 1; - case FmtStereo: return 2; - case FmtRear: return 2; - case FmtQuad: return 4; - case FmtX51: return 6; - case FmtX61: return 7; - case FmtX71: return 8; - case FmtBFormat2D: return 3; - case FmtBFormat3D: return 4; - } - return 0; -} - - -BufferSubList::~BufferSubList() -{ - uint64_t usemask{~FreeMask}; - while(usemask) - { - ALsizei idx{CTZ64(usemask)}; - Buffers[idx].~ALbuffer(); - usemask &= ~(1_u64 << idx); - } - FreeMask = ~usemask; - al_free(Buffers); - Buffers = nullptr; -} diff --git a/modules/openal-soft/OpenAL32/alEffect.cpp b/modules/openal-soft/OpenAL32/alEffect.cpp deleted file mode 100644 index 36a0944..0000000 --- a/modules/openal-soft/OpenAL32/alEffect.cpp +++ /dev/null @@ -1,727 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include -#include -#include - -#include - -#include "AL/al.h" -#include "AL/alc.h" - -#include "alMain.h" -#include "alcontext.h" -#include "alEffect.h" -#include "alError.h" -#include "alexcpt.h" - -#include "effects/base.h" - - -const EffectList gEffectList[14]{ - { "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB }, - { "reverb", REVERB_EFFECT, AL_EFFECT_REVERB }, - { "autowah", AUTOWAH_EFFECT, AL_EFFECT_AUTOWAH }, - { "chorus", CHORUS_EFFECT, AL_EFFECT_CHORUS }, - { "compressor", COMPRESSOR_EFFECT, AL_EFFECT_COMPRESSOR }, - { "distortion", DISTORTION_EFFECT, AL_EFFECT_DISTORTION }, - { "echo", ECHO_EFFECT, AL_EFFECT_ECHO }, - { "equalizer", EQUALIZER_EFFECT, AL_EFFECT_EQUALIZER }, - { "flanger", FLANGER_EFFECT, AL_EFFECT_FLANGER }, - { "fshifter", FSHIFTER_EFFECT, AL_EFFECT_FREQUENCY_SHIFTER }, - { "modulator", MODULATOR_EFFECT, AL_EFFECT_RING_MODULATOR }, - { "pshifter", PSHIFTER_EFFECT, AL_EFFECT_PITCH_SHIFTER }, - { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT }, - { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_DIALOGUE }, -}; - -ALboolean DisabledEffects[MAX_EFFECTS]; - -namespace { - -constexpr struct FactoryItem { - ALenum Type; - EffectStateFactory* (&GetFactory)(void); -} FactoryList[] = { - { AL_EFFECT_NULL, NullStateFactory_getFactory }, - { AL_EFFECT_EAXREVERB, ReverbStateFactory_getFactory }, - { AL_EFFECT_REVERB, StdReverbStateFactory_getFactory }, - { AL_EFFECT_AUTOWAH, AutowahStateFactory_getFactory }, - { AL_EFFECT_CHORUS, ChorusStateFactory_getFactory }, - { AL_EFFECT_COMPRESSOR, CompressorStateFactory_getFactory }, - { AL_EFFECT_DISTORTION, DistortionStateFactory_getFactory }, - { AL_EFFECT_ECHO, EchoStateFactory_getFactory }, - { AL_EFFECT_EQUALIZER, EqualizerStateFactory_getFactory }, - { AL_EFFECT_FLANGER, FlangerStateFactory_getFactory }, - { AL_EFFECT_FREQUENCY_SHIFTER, FshifterStateFactory_getFactory }, - { AL_EFFECT_RING_MODULATOR, ModulatorStateFactory_getFactory }, - { AL_EFFECT_PITCH_SHIFTER, PshifterStateFactory_getFactory}, - { AL_EFFECT_DEDICATED_DIALOGUE, DedicatedStateFactory_getFactory }, - { AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, DedicatedStateFactory_getFactory } -}; - - -template -void ALeffect_setParami(ALeffect *effect, T&& ...args) -{ effect->vtab->setParami(&effect->Props, std::forward(args)...); } -template -void ALeffect_setParamiv(ALeffect *effect, T&& ...args) -{ effect->vtab->setParamiv(&effect->Props, std::forward(args)...); } -template -void ALeffect_setParamf(ALeffect *effect, T&& ...args) -{ effect->vtab->setParamf(&effect->Props, std::forward(args)...); } -template -void ALeffect_setParamfv(ALeffect *effect, T&& ...args) -{ effect->vtab->setParamfv(&effect->Props, std::forward(args)...); } - -template -void ALeffect_getParami(const ALeffect *effect, T&& ...args) -{ effect->vtab->getParami(&effect->Props, std::forward(args)...); } -template -void ALeffect_getParamiv(const ALeffect *effect, T&& ...args) -{ effect->vtab->getParamiv(&effect->Props, std::forward(args)...); } -template -void ALeffect_getParamf(const ALeffect *effect, T&& ...args) -{ effect->vtab->getParamf(&effect->Props, std::forward(args)...); } -template -void ALeffect_getParamfv(const ALeffect *effect, T&& ...args) -{ effect->vtab->getParamfv(&effect->Props, std::forward(args)...); } - - -void InitEffectParams(ALeffect *effect, ALenum type) -{ - EffectStateFactory *factory = getFactoryByType(type); - if(factory) - { - effect->Props = factory->getDefaultProps(); - effect->vtab = factory->getEffectVtable(); - } - else - { - effect->Props = EffectProps {}; - effect->vtab = nullptr; - } - effect->type = type; -} - -ALeffect *AllocEffect(ALCcontext *context) -{ - ALCdevice *device{context->Device}; - std::lock_guard _{device->EffectLock}; - auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(), - [](const EffectSubList &entry) noexcept -> bool - { return entry.FreeMask != 0; } - ); - - auto lidx = static_cast(std::distance(device->EffectList.begin(), sublist)); - ALeffect *effect{nullptr}; - ALsizei slidx{0}; - if(LIKELY(sublist != device->EffectList.end())) - { - slidx = CTZ64(sublist->FreeMask); - effect = sublist->Effects + slidx; - } - else - { - /* Don't allocate so many list entries that the 32-bit ID could - * overflow... - */ - if(UNLIKELY(device->EffectList.size() >= 1<<25)) - { - alSetError(context, AL_OUT_OF_MEMORY, "Too many effects allocated"); - return nullptr; - } - device->EffectList.emplace_back(); - sublist = device->EffectList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->Effects = static_cast(al_calloc(16, sizeof(ALeffect)*64)); - if(UNLIKELY(!sublist->Effects)) - { - device->EffectList.pop_back(); - alSetError(context, AL_OUT_OF_MEMORY, "Failed to allocate effect batch"); - return nullptr; - } - - slidx = 0; - effect = sublist->Effects + slidx; - } - - effect = new (effect) ALeffect{}; - InitEffectParams(effect, AL_EFFECT_NULL); - - /* Add 1 to avoid effect ID 0. */ - effect->id = ((lidx<<6) | slidx) + 1; - - sublist->FreeMask &= ~(1_u64 << slidx); - - return effect; -} - -void FreeEffect(ALCdevice *device, ALeffect *effect) -{ - ALuint id = effect->id - 1; - ALsizei lidx = id >> 6; - ALsizei slidx = id & 0x3f; - - effect->~ALeffect(); - - device->EffectList[lidx].FreeMask |= 1_u64 << slidx; -} - -inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) -{ - ALuint lidx = (id-1) >> 6; - ALsizei slidx = (id-1) & 0x3f; - - if(UNLIKELY(lidx >= device->EffectList.size())) - return nullptr; - EffectSubList &sublist = device->EffectList[lidx]; - if(UNLIKELY(sublist.FreeMask & (1_u64 << slidx))) - return nullptr; - return sublist.Effects + slidx; -} - -} // namespace - -AL_API ALvoid AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(UNLIKELY(n < 0)) - { - alSetError(context.get(), AL_INVALID_VALUE, "Generating %d effects", n); - return; - } - - if(LIKELY(n == 1)) - { - /* Special handling for the easy and normal case. */ - ALeffect *effect = AllocEffect(context.get()); - if(effect) effects[0] = effect->id; - } - else if(n > 1) - { - /* Store the allocated buffer IDs in a separate local list, to avoid - * modifying the user storage in case of failure. - */ - al::vector ids; - ids.reserve(n); - do { - ALeffect *effect = AllocEffect(context.get()); - if(!effect) - { - alDeleteEffects(static_cast(ids.size()), ids.data()); - return; - } - - ids.emplace_back(effect->id); - } while(--n); - std::copy(ids.begin(), ids.end(), effects); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(UNLIKELY(n < 0)) - { - alSetError(context.get(), AL_INVALID_VALUE, "Deleting %d effects", n); - return; - } - if(UNLIKELY(n == 0)) - return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->EffectLock}; - - /* First try to find any effects that are invalid. */ - const ALuint *effects_end = effects + n; - auto inveffect = std::find_if(effects, effects_end, - [device, &context](ALuint eid) -> bool - { - if(!eid) return false; - ALeffect *effect{LookupEffect(device, eid)}; - if(UNLIKELY(!effect)) - { - alSetError(context.get(), AL_INVALID_NAME, "Invalid effect ID %u", eid); - return true; - } - return false; - } - ); - if(LIKELY(inveffect == effects_end)) - { - /* All good. Delete non-0 effect IDs. */ - std::for_each(effects, effects_end, - [device](ALuint eid) -> void - { - ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr}; - if(effect) FreeEffect(device, effect); - } - ); - } -} -END_API_FUNC - -AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(LIKELY(context)) - { - ALCdevice *device{context->Device}; - std::lock_guard _{device->EffectLock}; - if(!effect || LookupEffect(device, effect)) - return AL_TRUE; - } - return AL_FALSE; -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->EffectLock}; - - ALeffect *aleffect{LookupEffect(device, effect)}; - if(UNLIKELY(!aleffect)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid effect ID %u", effect); - else - { - if(param == AL_EFFECT_TYPE) - { - ALboolean isOk = (value == AL_EFFECT_NULL); - for(size_t i{0u};!isOk && i < countof(gEffectList);i++) - { - if(value == gEffectList[i].val && !DisabledEffects[gEffectList[i].type]) - isOk = AL_TRUE; - } - - if(isOk) - InitEffectParams(aleffect, value); - else - alSetError(context.get(), AL_INVALID_VALUE, "Effect type 0x%04x not supported", value); - } - else - { - /* Call the appropriate handler */ - ALeffect_setParami(aleffect, context.get(), param, value); - } - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *values) -START_API_FUNC -{ - switch(param) - { - case AL_EFFECT_TYPE: - alEffecti(effect, param, values[0]); - return; - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->EffectLock}; - - ALeffect *aleffect{LookupEffect(device, effect)}; - if(UNLIKELY(!aleffect)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid effect ID %u", effect); - else - { - /* Call the appropriate handler */ - ALeffect_setParamiv(aleffect, context.get(), param, values); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->EffectLock}; - - ALeffect *aleffect{LookupEffect(device, effect)}; - if(UNLIKELY(!aleffect)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid effect ID %u", effect); - else - { - /* Call the appropriate handler */ - ALeffect_setParamf(aleffect, context.get(), param, value); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->EffectLock}; - - ALeffect *aleffect{LookupEffect(device, effect)}; - if(UNLIKELY(!aleffect)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid effect ID %u", effect); - else - { - /* Call the appropriate handler */ - ALeffect_setParamfv(aleffect, context.get(), param, values); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->EffectLock}; - - const ALeffect *aleffect{LookupEffect(device, effect)}; - if(UNLIKELY(!aleffect)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid effect ID %u", effect); - else - { - if(param == AL_EFFECT_TYPE) - *value = aleffect->type; - else - { - /* Call the appropriate handler */ - ALeffect_getParami(aleffect, context.get(), param, value); - } - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *values) -START_API_FUNC -{ - switch(param) - { - case AL_EFFECT_TYPE: - alGetEffecti(effect, param, values); - return; - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->EffectLock}; - - const ALeffect *aleffect{LookupEffect(device, effect)}; - if(UNLIKELY(!aleffect)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid effect ID %u", effect); - else - { - /* Call the appropriate handler */ - ALeffect_getParamiv(aleffect, context.get(), param, values); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->EffectLock}; - - const ALeffect *aleffect{LookupEffect(device, effect)}; - if(UNLIKELY(!aleffect)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid effect ID %u", effect); - else - { - /* Call the appropriate handler */ - ALeffect_getParamf(aleffect, context.get(), param, value); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->EffectLock}; - - const ALeffect *aleffect{LookupEffect(device, effect)}; - if(UNLIKELY(!aleffect)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid effect ID %u", effect); - else - { - /* Call the appropriate handler */ - ALeffect_getParamfv(aleffect, context.get(), param, values); - } -} -END_API_FUNC - - -void InitEffect(ALeffect *effect) -{ - InitEffectParams(effect, AL_EFFECT_NULL); -} - -EffectSubList::~EffectSubList() -{ - uint64_t usemask{~FreeMask}; - while(usemask) - { - ALsizei idx = CTZ64(usemask); - Effects[idx].~ALeffect(); - usemask &= ~(1_u64 << idx); - } - FreeMask = ~usemask; - al_free(Effects); - Effects = nullptr; -} - - -EffectStateFactory *getFactoryByType(ALenum type) -{ - auto iter = std::find_if(std::begin(FactoryList), std::end(FactoryList), - [type](const FactoryItem &item) noexcept -> bool - { return item.Type == type; } - ); - return (iter != std::end(FactoryList)) ? iter->GetFactory() : nullptr; -} - - -#include "AL/efx-presets.h" - -#define DECL(x) { #x, EFX_REVERB_PRESET_##x } -static const struct { - const char name[32]; - EFXEAXREVERBPROPERTIES props; -} reverblist[] = { - DECL(GENERIC), - DECL(PADDEDCELL), - DECL(ROOM), - DECL(BATHROOM), - DECL(LIVINGROOM), - DECL(STONEROOM), - DECL(AUDITORIUM), - DECL(CONCERTHALL), - DECL(CAVE), - DECL(ARENA), - DECL(HANGAR), - DECL(CARPETEDHALLWAY), - DECL(HALLWAY), - DECL(STONECORRIDOR), - DECL(ALLEY), - DECL(FOREST), - DECL(CITY), - DECL(MOUNTAINS), - DECL(QUARRY), - DECL(PLAIN), - DECL(PARKINGLOT), - DECL(SEWERPIPE), - DECL(UNDERWATER), - DECL(DRUGGED), - DECL(DIZZY), - DECL(PSYCHOTIC), - - DECL(CASTLE_SMALLROOM), - DECL(CASTLE_SHORTPASSAGE), - DECL(CASTLE_MEDIUMROOM), - DECL(CASTLE_LARGEROOM), - DECL(CASTLE_LONGPASSAGE), - DECL(CASTLE_HALL), - DECL(CASTLE_CUPBOARD), - DECL(CASTLE_COURTYARD), - DECL(CASTLE_ALCOVE), - - DECL(FACTORY_SMALLROOM), - DECL(FACTORY_SHORTPASSAGE), - DECL(FACTORY_MEDIUMROOM), - DECL(FACTORY_LARGEROOM), - DECL(FACTORY_LONGPASSAGE), - DECL(FACTORY_HALL), - DECL(FACTORY_CUPBOARD), - DECL(FACTORY_COURTYARD), - DECL(FACTORY_ALCOVE), - - DECL(ICEPALACE_SMALLROOM), - DECL(ICEPALACE_SHORTPASSAGE), - DECL(ICEPALACE_MEDIUMROOM), - DECL(ICEPALACE_LARGEROOM), - DECL(ICEPALACE_LONGPASSAGE), - DECL(ICEPALACE_HALL), - DECL(ICEPALACE_CUPBOARD), - DECL(ICEPALACE_COURTYARD), - DECL(ICEPALACE_ALCOVE), - - DECL(SPACESTATION_SMALLROOM), - DECL(SPACESTATION_SHORTPASSAGE), - DECL(SPACESTATION_MEDIUMROOM), - DECL(SPACESTATION_LARGEROOM), - DECL(SPACESTATION_LONGPASSAGE), - DECL(SPACESTATION_HALL), - DECL(SPACESTATION_CUPBOARD), - DECL(SPACESTATION_ALCOVE), - - DECL(WOODEN_SMALLROOM), - DECL(WOODEN_SHORTPASSAGE), - DECL(WOODEN_MEDIUMROOM), - DECL(WOODEN_LARGEROOM), - DECL(WOODEN_LONGPASSAGE), - DECL(WOODEN_HALL), - DECL(WOODEN_CUPBOARD), - DECL(WOODEN_COURTYARD), - DECL(WOODEN_ALCOVE), - - DECL(SPORT_EMPTYSTADIUM), - DECL(SPORT_SQUASHCOURT), - DECL(SPORT_SMALLSWIMMINGPOOL), - DECL(SPORT_LARGESWIMMINGPOOL), - DECL(SPORT_GYMNASIUM), - DECL(SPORT_FULLSTADIUM), - DECL(SPORT_STADIUMTANNOY), - - DECL(PREFAB_WORKSHOP), - DECL(PREFAB_SCHOOLROOM), - DECL(PREFAB_PRACTISEROOM), - DECL(PREFAB_OUTHOUSE), - DECL(PREFAB_CARAVAN), - - DECL(DOME_TOMB), - DECL(PIPE_SMALL), - DECL(DOME_SAINTPAULS), - DECL(PIPE_LONGTHIN), - DECL(PIPE_LARGE), - DECL(PIPE_RESONANT), - - DECL(OUTDOORS_BACKYARD), - DECL(OUTDOORS_ROLLINGPLAINS), - DECL(OUTDOORS_DEEPCANYON), - DECL(OUTDOORS_CREEK), - DECL(OUTDOORS_VALLEY), - - DECL(MOOD_HEAVEN), - DECL(MOOD_HELL), - DECL(MOOD_MEMORY), - - DECL(DRIVING_COMMENTATOR), - DECL(DRIVING_PITGARAGE), - DECL(DRIVING_INCAR_RACER), - DECL(DRIVING_INCAR_SPORTS), - DECL(DRIVING_INCAR_LUXURY), - DECL(DRIVING_FULLGRANDSTAND), - DECL(DRIVING_EMPTYGRANDSTAND), - DECL(DRIVING_TUNNEL), - - DECL(CITY_STREETS), - DECL(CITY_SUBWAY), - DECL(CITY_MUSEUM), - DECL(CITY_LIBRARY), - DECL(CITY_UNDERPASS), - DECL(CITY_ABANDONED), - - DECL(DUSTYROOM), - DECL(CHAPEL), - DECL(SMALLWATERROOM), -}; -#undef DECL - -void LoadReverbPreset(const char *name, ALeffect *effect) -{ - size_t i; - - if(strcasecmp(name, "NONE") == 0) - { - InitEffectParams(effect, AL_EFFECT_NULL); - TRACE("Loading reverb '%s'\n", "NONE"); - return; - } - - if(!DisabledEffects[EAXREVERB_EFFECT]) - InitEffectParams(effect, AL_EFFECT_EAXREVERB); - else if(!DisabledEffects[REVERB_EFFECT]) - InitEffectParams(effect, AL_EFFECT_REVERB); - else - InitEffectParams(effect, AL_EFFECT_NULL); - for(i = 0;i < COUNTOF(reverblist);i++) - { - const EFXEAXREVERBPROPERTIES *props; - - if(strcasecmp(name, reverblist[i].name) != 0) - continue; - - TRACE("Loading reverb '%s'\n", reverblist[i].name); - props = &reverblist[i].props; - effect->Props.Reverb.Density = props->flDensity; - effect->Props.Reverb.Diffusion = props->flDiffusion; - effect->Props.Reverb.Gain = props->flGain; - effect->Props.Reverb.GainHF = props->flGainHF; - effect->Props.Reverb.GainLF = props->flGainLF; - effect->Props.Reverb.DecayTime = props->flDecayTime; - effect->Props.Reverb.DecayHFRatio = props->flDecayHFRatio; - effect->Props.Reverb.DecayLFRatio = props->flDecayLFRatio; - effect->Props.Reverb.ReflectionsGain = props->flReflectionsGain; - effect->Props.Reverb.ReflectionsDelay = props->flReflectionsDelay; - effect->Props.Reverb.ReflectionsPan[0] = props->flReflectionsPan[0]; - effect->Props.Reverb.ReflectionsPan[1] = props->flReflectionsPan[1]; - effect->Props.Reverb.ReflectionsPan[2] = props->flReflectionsPan[2]; - effect->Props.Reverb.LateReverbGain = props->flLateReverbGain; - effect->Props.Reverb.LateReverbDelay = props->flLateReverbDelay; - effect->Props.Reverb.LateReverbPan[0] = props->flLateReverbPan[0]; - effect->Props.Reverb.LateReverbPan[1] = props->flLateReverbPan[1]; - effect->Props.Reverb.LateReverbPan[2] = props->flLateReverbPan[2]; - effect->Props.Reverb.EchoTime = props->flEchoTime; - effect->Props.Reverb.EchoDepth = props->flEchoDepth; - effect->Props.Reverb.ModulationTime = props->flModulationTime; - effect->Props.Reverb.ModulationDepth = props->flModulationDepth; - effect->Props.Reverb.AirAbsorptionGainHF = props->flAirAbsorptionGainHF; - effect->Props.Reverb.HFReference = props->flHFReference; - effect->Props.Reverb.LFReference = props->flLFReference; - effect->Props.Reverb.RoomRolloffFactor = props->flRoomRolloffFactor; - effect->Props.Reverb.DecayHFLimit = props->iDecayHFLimit; - return; - } - - WARN("Reverb preset '%s' not found\n", name); -} diff --git a/modules/openal-soft/OpenAL32/alFilter.cpp b/modules/openal-soft/OpenAL32/alFilter.cpp deleted file mode 100644 index cf39369..0000000 --- a/modules/openal-soft/OpenAL32/alFilter.cpp +++ /dev/null @@ -1,656 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include - -#include - -#include "alMain.h" -#include "alcontext.h" -#include "alu.h" -#include "alFilter.h" -#include "alError.h" -#include "alexcpt.h" - - -namespace { - -#define FILTER_MIN_GAIN 0.0f -#define FILTER_MAX_GAIN 4.0f /* +12dB */ - -void ALlowpass_setParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint UNUSED(val)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param); } -void ALlowpass_setParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALint *UNUSED(vals)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", param); } -void ALlowpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_LOWPASS_GAIN: - if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Low-pass gain %f out of range", val); - filter->Gain = val; - break; - - case AL_LOWPASS_GAINHF: - if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Low-pass gainhf %f out of range", val); - filter->GainHF = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param); - } -} -void ALlowpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals) -{ ALlowpass_setParamf(filter, context, param, vals[0]); } - -void ALlowpass_getParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(val)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param); } -void ALlowpass_getParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(vals)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", param); } -void ALlowpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_LOWPASS_GAIN: - *val = filter->Gain; - break; - - case AL_LOWPASS_GAINHF: - *val = filter->GainHF; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param); - } -} -void ALlowpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals) -{ ALlowpass_getParamf(filter, context, param, vals); } - -DEFINE_ALFILTER_VTABLE(ALlowpass); - - -void ALhighpass_setParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint UNUSED(val)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param); } -void ALhighpass_setParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALint *UNUSED(vals)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", param); } -void ALhighpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_HIGHPASS_GAIN: - if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "High-pass gain out of range"); - filter->Gain = val; - break; - - case AL_HIGHPASS_GAINLF: - if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "High-pass gainlf out of range"); - filter->GainLF = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param); - } -} -void ALhighpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals) -{ ALhighpass_setParamf(filter, context, param, vals[0]); } - -void ALhighpass_getParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(val)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param); } -void ALhighpass_getParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(vals)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", param); } -void ALhighpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_HIGHPASS_GAIN: - *val = filter->Gain; - break; - - case AL_HIGHPASS_GAINLF: - *val = filter->GainLF; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param); - } -} -void ALhighpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals) -{ ALhighpass_getParamf(filter, context, param, vals); } - -DEFINE_ALFILTER_VTABLE(ALhighpass); - - -void ALbandpass_setParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint UNUSED(val)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param); } -void ALbandpass_setParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALint *UNUSED(vals)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", param); } -void ALbandpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val) -{ - switch(param) - { - case AL_BANDPASS_GAIN: - if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gain out of range"); - filter->Gain = val; - break; - - case AL_BANDPASS_GAINHF: - if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gainhf out of range"); - filter->GainHF = val; - break; - - case AL_BANDPASS_GAINLF: - if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gainlf out of range"); - filter->GainLF = val; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param); - } -} -void ALbandpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals) -{ ALbandpass_setParamf(filter, context, param, vals[0]); } - -void ALbandpass_getParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(val)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param); } -void ALbandpass_getParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(vals)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", param); } -void ALbandpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val) -{ - switch(param) - { - case AL_BANDPASS_GAIN: - *val = filter->Gain; - break; - - case AL_BANDPASS_GAINHF: - *val = filter->GainHF; - break; - - case AL_BANDPASS_GAINLF: - *val = filter->GainLF; - break; - - default: - alSetError(context, AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param); - } -} -void ALbandpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals) -{ ALbandpass_getParamf(filter, context, param, vals); } - -DEFINE_ALFILTER_VTABLE(ALbandpass); - - -void ALnullfilter_setParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint UNUSED(val)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); } -void ALnullfilter_setParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALint *UNUSED(vals)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); } -void ALnullfilter_setParamf(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALfloat UNUSED(val)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); } -void ALnullfilter_setParamfv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALfloat *UNUSED(vals)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); } - -void ALnullfilter_getParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(val)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); } -void ALnullfilter_getParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(vals)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); } -void ALnullfilter_getParamf(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALfloat *UNUSED(val)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); } -void ALnullfilter_getParamfv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALfloat *UNUSED(vals)) -{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); } - -DEFINE_ALFILTER_VTABLE(ALnullfilter); - - -void InitFilterParams(ALfilter *filter, ALenum type) -{ - if(type == AL_FILTER_LOWPASS) - { - filter->Gain = AL_LOWPASS_DEFAULT_GAIN; - filter->GainHF = AL_LOWPASS_DEFAULT_GAINHF; - filter->HFReference = LOWPASSFREQREF; - filter->GainLF = 1.0f; - filter->LFReference = HIGHPASSFREQREF; - filter->vtab = &ALlowpass_vtable; - } - else if(type == AL_FILTER_HIGHPASS) - { - filter->Gain = AL_HIGHPASS_DEFAULT_GAIN; - filter->GainHF = 1.0f; - filter->HFReference = LOWPASSFREQREF; - filter->GainLF = AL_HIGHPASS_DEFAULT_GAINLF; - filter->LFReference = HIGHPASSFREQREF; - filter->vtab = &ALhighpass_vtable; - } - else if(type == AL_FILTER_BANDPASS) - { - filter->Gain = AL_BANDPASS_DEFAULT_GAIN; - filter->GainHF = AL_BANDPASS_DEFAULT_GAINHF; - filter->HFReference = LOWPASSFREQREF; - filter->GainLF = AL_BANDPASS_DEFAULT_GAINLF; - filter->LFReference = HIGHPASSFREQREF; - filter->vtab = &ALbandpass_vtable; - } - else - { - filter->Gain = 1.0f; - filter->GainHF = 1.0f; - filter->HFReference = LOWPASSFREQREF; - filter->GainLF = 1.0f; - filter->LFReference = HIGHPASSFREQREF; - filter->vtab = &ALnullfilter_vtable; - } - filter->type = type; -} - -ALfilter *AllocFilter(ALCcontext *context) -{ - ALCdevice *device{context->Device}; - std::lock_guard _{device->FilterLock}; - auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(), - [](const FilterSubList &entry) noexcept -> bool - { return entry.FreeMask != 0; } - ); - - auto lidx = static_cast(std::distance(device->FilterList.begin(), sublist)); - ALfilter *filter{nullptr}; - ALsizei slidx{0}; - if(LIKELY(sublist != device->FilterList.end())) - { - slidx = CTZ64(sublist->FreeMask); - filter = sublist->Filters + slidx; - } - else - { - /* Don't allocate so many list entries that the 32-bit ID could - * overflow... - */ - if(UNLIKELY(device->FilterList.size() >= 1<<25)) - { - alSetError(context, AL_OUT_OF_MEMORY, "Too many filters allocated"); - return nullptr; - } - device->FilterList.emplace_back(); - sublist = device->FilterList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->Filters = static_cast(al_calloc(16, sizeof(ALfilter)*64)); - if(UNLIKELY(!sublist->Filters)) - { - device->FilterList.pop_back(); - alSetError(context, AL_OUT_OF_MEMORY, "Failed to allocate filter batch"); - return nullptr; - } - - slidx = 0; - filter = sublist->Filters + slidx; - } - - filter = new (filter) ALfilter{}; - InitFilterParams(filter, AL_FILTER_NULL); - - /* Add 1 to avoid filter ID 0. */ - filter->id = ((lidx<<6) | slidx) + 1; - - sublist->FreeMask &= ~(1_u64 << slidx); - - return filter; -} - -void FreeFilter(ALCdevice *device, ALfilter *filter) -{ - ALuint id = filter->id - 1; - ALsizei lidx = id >> 6; - ALsizei slidx = id & 0x3f; - - filter->~ALfilter(); - - device->FilterList[lidx].FreeMask |= 1_u64 << slidx; -} - - -inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) -{ - ALuint lidx = (id-1) >> 6; - ALsizei slidx = (id-1) & 0x3f; - - if(UNLIKELY(lidx >= device->FilterList.size())) - return nullptr; - FilterSubList &sublist = device->FilterList[lidx]; - if(UNLIKELY(sublist.FreeMask & (1_u64 << slidx))) - return nullptr; - return sublist.Filters + slidx; -} - -} // namespace - -AL_API ALvoid AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(UNLIKELY(n < 0)) - { - alSetError(context.get(), AL_INVALID_VALUE, "Generating %d filters", n); - return; - } - - if(LIKELY(n == 1)) - { - /* Special handling for the easy and normal case. */ - ALfilter *filter = AllocFilter(context.get()); - if(filter) filters[0] = filter->id; - } - else if(n > 1) - { - /* Store the allocated buffer IDs in a separate local list, to avoid - * modifying the user storage in case of failure. - */ - al::vector ids; - ids.reserve(n); - do { - ALfilter *filter = AllocFilter(context.get()); - if(!filter) - { - alDeleteFilters(static_cast(ids.size()), ids.data()); - return; - } - - ids.emplace_back(filter->id); - } while(--n); - std::copy(ids.begin(), ids.end(), filters); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(UNLIKELY(n < 0)) - { - alSetError(context.get(), AL_INVALID_VALUE, "Deleting %d filters", n); - return; - } - if(UNLIKELY(n == 0)) - return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->FilterLock}; - - /* First try to find any filters that are invalid. */ - const ALuint *filters_end = filters + n; - auto invflt = std::find_if(filters, filters_end, - [device, &context](ALuint fid) -> bool - { - if(!fid) return false; - ALfilter *filter{LookupFilter(device, fid)}; - if(UNLIKELY(!filter)) - { - alSetError(context.get(), AL_INVALID_NAME, "Invalid filter ID %u", fid); - return true; - } - return false; - } - ); - if(LIKELY(invflt == filters_end)) - { - /* All good. Delete non-0 filter IDs. */ - std::for_each(filters, filters_end, - [device](ALuint fid) -> void - { - ALfilter *filter{fid ? LookupFilter(device, fid) : nullptr}; - if(filter) FreeFilter(device, filter); - } - ); - } -} -END_API_FUNC - -AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(LIKELY(context)) - { - ALCdevice *device{context->Device}; - std::lock_guard _{device->FilterLock}; - if(!filter || LookupFilter(device, filter)) - return AL_TRUE; - } - return AL_FALSE; -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if(UNLIKELY(!alfilt)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid filter ID %u", filter); - else - { - if(param == AL_FILTER_TYPE) - { - if(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS || - value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS) - InitFilterParams(alfilt, value); - else - alSetError(context.get(), AL_INVALID_VALUE, "Invalid filter type 0x%04x", value); - } - else - { - /* Call the appropriate handler */ - ALfilter_setParami(alfilt, context.get(), param, value); - } - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *values) -START_API_FUNC -{ - switch(param) - { - case AL_FILTER_TYPE: - alFilteri(filter, param, values[0]); - return; - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if(UNLIKELY(!alfilt)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid filter ID %u", filter); - else - { - /* Call the appropriate handler */ - ALfilter_setParamiv(alfilt, context.get(), param, values); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if(UNLIKELY(!alfilt)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid filter ID %u", filter); - else - { - /* Call the appropriate handler */ - ALfilter_setParamf(alfilt, context.get(), param, value); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if(UNLIKELY(!alfilt)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid filter ID %u", filter); - else - { - /* Call the appropriate handler */ - ALfilter_setParamfv(alfilt, context.get(), param, values); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if(UNLIKELY(!alfilt)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid filter ID %u", filter); - else - { - if(param == AL_FILTER_TYPE) - *value = alfilt->type; - else - { - /* Call the appropriate handler */ - ALfilter_getParami(alfilt, context.get(), param, value); - } - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *values) -START_API_FUNC -{ - switch(param) - { - case AL_FILTER_TYPE: - alGetFilteri(filter, param, values); - return; - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if(UNLIKELY(!alfilt)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid filter ID %u", filter); - else - { - /* Call the appropriate handler */ - ALfilter_getParamiv(alfilt, context.get(), param, values); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if(UNLIKELY(!alfilt)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid filter ID %u", filter); - else - { - /* Call the appropriate handler */ - ALfilter_getParamf(alfilt, context.get(), param, value); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCdevice *device{context->Device}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if(UNLIKELY(!alfilt)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid filter ID %u", filter); - else - { - /* Call the appropriate handler */ - ALfilter_getParamfv(alfilt, context.get(), param, values); - } -} -END_API_FUNC - - -FilterSubList::~FilterSubList() -{ - uint64_t usemask{~FreeMask}; - while(usemask) - { - ALsizei idx = CTZ64(usemask); - Filters[idx].~ALfilter(); - usemask &= ~(1_u64 << idx); - } - FreeMask = ~usemask; - al_free(Filters); - Filters = nullptr; -} diff --git a/modules/openal-soft/OpenAL32/alSource.cpp b/modules/openal-soft/OpenAL32/alSource.cpp deleted file mode 100644 index 02396fd..0000000 --- a/modules/openal-soft/OpenAL32/alSource.cpp +++ /dev/null @@ -1,3582 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "AL/al.h" -#include "AL/alc.h" - -#include "alMain.h" -#include "alcontext.h" -#include "alError.h" -#include "alSource.h" -#include "alBuffer.h" -#include "alFilter.h" -#include "alAuxEffectSlot.h" -#include "ringbuffer.h" -#include "bformatdec.h" - -#include "backends/base.h" - -#include "threads.h" -#include "alexcpt.h" -#include "almalloc.h" - - -namespace { - -using namespace std::placeholders; - -inline ALvoice *GetSourceVoice(ALsource *source, ALCcontext *context) -{ - ALint idx{source->VoiceIdx}; - if(idx >= 0 && idx < context->VoiceCount.load(std::memory_order_relaxed)) - { - ALuint sid{source->id}; - ALvoice *voice{context->Voices[idx]}; - if(voice->mSourceID.load(std::memory_order_acquire) == sid) - return voice; - } - source->VoiceIdx = -1; - return nullptr; -} - -void UpdateSourceProps(const ALsource *source, ALvoice *voice, ALCcontext *context) -{ - /* Get an unused property container, or allocate a new one as needed. */ - ALvoiceProps *props{context->FreeVoiceProps.load(std::memory_order_acquire)}; - if(!props) - props = new ALvoiceProps{}; - else - { - ALvoiceProps *next; - do { - next = props->next.load(std::memory_order_relaxed); - } while(context->FreeVoiceProps.compare_exchange_weak(props, next, - std::memory_order_acq_rel, std::memory_order_acquire) == 0); - } - - /* Copy in current property values. */ - props->Pitch = source->Pitch; - props->Gain = source->Gain; - props->OuterGain = source->OuterGain; - props->MinGain = source->MinGain; - props->MaxGain = source->MaxGain; - props->InnerAngle = source->InnerAngle; - props->OuterAngle = source->OuterAngle; - props->RefDistance = source->RefDistance; - props->MaxDistance = source->MaxDistance; - props->RolloffFactor = source->RolloffFactor; - props->Position = source->Position; - props->Velocity = source->Velocity; - props->Direction = source->Direction; - props->OrientAt = source->OrientAt; - props->OrientUp = source->OrientUp; - props->HeadRelative = source->HeadRelative; - props->mDistanceModel = source->mDistanceModel; - props->mResampler = source->mResampler; - props->DirectChannels = source->DirectChannels; - props->mSpatializeMode = source->mSpatialize; - - props->DryGainHFAuto = source->DryGainHFAuto; - props->WetGainAuto = source->WetGainAuto; - props->WetGainHFAuto = source->WetGainHFAuto; - props->OuterGainHF = source->OuterGainHF; - - props->AirAbsorptionFactor = source->AirAbsorptionFactor; - props->RoomRolloffFactor = source->RoomRolloffFactor; - props->DopplerFactor = source->DopplerFactor; - - props->StereoPan = source->StereoPan; - - props->Radius = source->Radius; - - props->Direct.Gain = source->Direct.Gain; - props->Direct.GainHF = source->Direct.GainHF; - props->Direct.HFReference = source->Direct.HFReference; - props->Direct.GainLF = source->Direct.GainLF; - props->Direct.LFReference = source->Direct.LFReference; - - auto copy_send = [](const ALsource::SendData &srcsend) noexcept -> ALvoicePropsBase::SendData - { - ALvoicePropsBase::SendData ret; - ret.Slot = srcsend.Slot; - ret.Gain = srcsend.Gain; - ret.GainHF = srcsend.GainHF; - ret.HFReference = srcsend.HFReference; - ret.GainLF = srcsend.GainLF; - ret.LFReference = srcsend.LFReference; - return ret; - }; - std::transform(source->Send.cbegin(), source->Send.cend(), props->Send, copy_send); - - /* Set the new container for updating internal parameters. */ - props = voice->mUpdate.exchange(props, std::memory_order_acq_rel); - if(props) - { - /* If there was an unused update container, put it back in the - * freelist. - */ - AtomicReplaceHead(context->FreeVoiceProps, props); - } -} - -/* GetSourceSampleOffset - * - * Gets the current read offset for the given Source, in 32.32 fixed-point - * samples. The offset is relative to the start of the queue (not the start of - * the current buffer). - */ -int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, std::chrono::nanoseconds *clocktime) -{ - ALCdevice *device{context->Device}; - const ALbufferlistitem *Current; - uint64_t readPos; - ALuint refcount; - ALvoice *voice; - - do { - Current = nullptr; - readPos = 0; - while(((refcount=device->MixCount.load(std::memory_order_acquire))&1)) - std::this_thread::yield(); - *clocktime = GetDeviceClockTime(device); - - voice = GetSourceVoice(Source, context); - if(voice) - { - Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); - - readPos = uint64_t{voice->mPosition.load(std::memory_order_relaxed)} << 32; - readPos |= int64_t{voice->mPositionFrac.load(std::memory_order_relaxed)} << - (32-FRACTIONBITS); - } - std::atomic_thread_fence(std::memory_order_acquire); - } while(refcount != device->MixCount.load(std::memory_order_relaxed)); - - if(voice) - { - const ALbufferlistitem *BufferList{Source->queue}; - while(BufferList && BufferList != Current) - { - readPos += int64_t{BufferList->max_samples} << 32; - BufferList = BufferList->next.load(std::memory_order_relaxed); - } - readPos = minu64(readPos, 0x7fffffffffffffff_u64); - } - - return static_cast(readPos); -} - -/* GetSourceSecOffset - * - * Gets the current read offset for the given Source, in seconds. The offset is - * relative to the start of the queue (not the start of the current buffer). - */ -ALdouble GetSourceSecOffset(ALsource *Source, ALCcontext *context, std::chrono::nanoseconds *clocktime) -{ - ALCdevice *device{context->Device}; - const ALbufferlistitem *Current; - uint64_t readPos; - ALuint refcount; - ALvoice *voice; - - do { - Current = nullptr; - readPos = 0; - while(((refcount=device->MixCount.load(std::memory_order_acquire))&1)) - std::this_thread::yield(); - *clocktime = GetDeviceClockTime(device); - - voice = GetSourceVoice(Source, context); - if(voice) - { - Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); - - readPos = uint64_t{voice->mPosition.load(std::memory_order_relaxed)} << FRACTIONBITS; - readPos |= voice->mPositionFrac.load(std::memory_order_relaxed); - } - std::atomic_thread_fence(std::memory_order_acquire); - } while(refcount != device->MixCount.load(std::memory_order_relaxed)); - - ALdouble offset{0.0}; - if(voice) - { - const ALbufferlistitem *BufferList{Source->queue}; - const ALbuffer *BufferFmt{nullptr}; - while(BufferList && BufferList != Current) - { - for(ALsizei i{0};!BufferFmt && i < BufferList->num_buffers;++i) - BufferFmt = BufferList->buffers[i]; - readPos += int64_t{BufferList->max_samples} << FRACTIONBITS; - BufferList = BufferList->next.load(std::memory_order_relaxed); - } - - while(BufferList && !BufferFmt) - { - for(ALsizei i{0};!BufferFmt && i < BufferList->num_buffers;++i) - BufferFmt = BufferList->buffers[i]; - BufferList = BufferList->next.load(std::memory_order_relaxed); - } - assert(BufferFmt != nullptr); - - offset = static_cast(readPos) / static_castFRACTIONONE / - static_cast(BufferFmt->Frequency); - } - - return offset; -} - -/* GetSourceOffset - * - * Gets the current read offset for the given Source, in the appropriate format - * (Bytes, Samples or Seconds). The offset is relative to the start of the - * queue (not the start of the current buffer). - */ -ALdouble GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) -{ - ALCdevice *device{context->Device}; - const ALbufferlistitem *Current; - ALuint readPos; - ALsizei readPosFrac; - ALuint refcount; - ALvoice *voice; - - do { - Current = nullptr; - readPos = readPosFrac = 0; - while(((refcount=device->MixCount.load(std::memory_order_acquire))&1)) - std::this_thread::yield(); - voice = GetSourceVoice(Source, context); - if(voice) - { - Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); - - readPos = voice->mPosition.load(std::memory_order_relaxed); - readPosFrac = voice->mPositionFrac.load(std::memory_order_relaxed); - } - std::atomic_thread_fence(std::memory_order_acquire); - } while(refcount != device->MixCount.load(std::memory_order_relaxed)); - - ALdouble offset{0.0}; - if(voice) - { - const ALbufferlistitem *BufferList{Source->queue}; - const ALbuffer *BufferFmt{nullptr}; - ALboolean readFin{AL_FALSE}; - ALuint totalBufferLen{0u}; - - while(BufferList) - { - for(ALsizei i{0};!BufferFmt && i < BufferList->num_buffers;++i) - BufferFmt = BufferList->buffers[i]; - - readFin |= (BufferList == Current); - totalBufferLen += BufferList->max_samples; - if(!readFin) readPos += BufferList->max_samples; - - BufferList = BufferList->next.load(std::memory_order_relaxed); - } - assert(BufferFmt != nullptr); - - if(Source->Looping) - readPos %= totalBufferLen; - else - { - /* Wrap back to 0 */ - if(readPos >= totalBufferLen) - readPos = readPosFrac = 0; - } - - offset = 0.0; - switch(name) - { - case AL_SEC_OFFSET: - offset = (readPos + static_cast(readPosFrac)/FRACTIONONE) / BufferFmt->Frequency; - break; - - case AL_SAMPLE_OFFSET: - offset = readPos + static_cast(readPosFrac)/FRACTIONONE; - break; - - case AL_BYTE_OFFSET: - if(BufferFmt->OriginalType == UserFmtIMA4) - { - ALsizei align = (BufferFmt->OriginalAlign-1)/2 + 4; - ALuint BlockSize = align * ChannelsFromFmt(BufferFmt->mFmtChannels); - ALuint FrameBlockSize = BufferFmt->OriginalAlign; - - /* Round down to nearest ADPCM block */ - offset = static_cast(readPos / FrameBlockSize * BlockSize); - } - else if(BufferFmt->OriginalType == UserFmtMSADPCM) - { - ALsizei align = (BufferFmt->OriginalAlign-2)/2 + 7; - ALuint BlockSize = align * ChannelsFromFmt(BufferFmt->mFmtChannels); - ALuint FrameBlockSize = BufferFmt->OriginalAlign; - - /* Round down to nearest ADPCM block */ - offset = static_cast(readPos / FrameBlockSize * BlockSize); - } - else - { - const ALsizei FrameSize{FrameSizeFromFmt(BufferFmt->mFmtChannels, - BufferFmt->mFmtType)}; - offset = static_cast(readPos * FrameSize); - } - break; - } - } - - return offset; -} - - -/* GetSampleOffset - * - * Retrieves the sample offset into the Source's queue (from the Sample, Byte - * or Second offset supplied by the application). This takes into account the - * fact that the buffer format may have been modifed since. - */ -ALboolean GetSampleOffset(ALsource *Source, ALuint *offset, ALsizei *frac) -{ - const ALbuffer *BufferFmt{nullptr}; - const ALbufferlistitem *BufferList; - - /* Find the first valid Buffer in the Queue */ - BufferList = Source->queue; - while(BufferList) - { - for(ALsizei i{0};i < BufferList->num_buffers && !BufferFmt;i++) - BufferFmt = BufferList->buffers[i]; - if(BufferFmt) break; - BufferList = BufferList->next.load(std::memory_order_relaxed); - } - if(!BufferFmt) - { - Source->OffsetType = AL_NONE; - Source->Offset = 0.0; - return AL_FALSE; - } - - ALdouble dbloff, dblfrac; - switch(Source->OffsetType) - { - case AL_BYTE_OFFSET: - /* Determine the ByteOffset (and ensure it is block aligned) */ - *offset = static_cast(Source->Offset); - if(BufferFmt->OriginalType == UserFmtIMA4) - { - ALsizei align = (BufferFmt->OriginalAlign-1)/2 + 4; - *offset /= align * ChannelsFromFmt(BufferFmt->mFmtChannels); - *offset *= BufferFmt->OriginalAlign; - } - else if(BufferFmt->OriginalType == UserFmtMSADPCM) - { - ALsizei align = (BufferFmt->OriginalAlign-2)/2 + 7; - *offset /= align * ChannelsFromFmt(BufferFmt->mFmtChannels); - *offset *= BufferFmt->OriginalAlign; - } - else - *offset /= FrameSizeFromFmt(BufferFmt->mFmtChannels, BufferFmt->mFmtType); - *frac = 0; - break; - - case AL_SAMPLE_OFFSET: - dblfrac = modf(Source->Offset, &dbloff); - *offset = static_cast(mind(dbloff, std::numeric_limits::max())); - *frac = static_cast(mind(dblfrac*FRACTIONONE, FRACTIONONE-1.0)); - break; - - case AL_SEC_OFFSET: - dblfrac = modf(Source->Offset*BufferFmt->Frequency, &dbloff); - *offset = static_cast(mind(dbloff, std::numeric_limits::max())); - *frac = static_cast(mind(dblfrac*FRACTIONONE, FRACTIONONE-1.0)); - break; - } - Source->OffsetType = AL_NONE; - Source->Offset = 0.0; - - return AL_TRUE; -} - -/* ApplyOffset - * - * Apply the stored playback offset to the Source. This function will update - * the number of buffers "played" given the stored offset. - */ -ALboolean ApplyOffset(ALsource *Source, ALvoice *voice) -{ - /* Get sample frame offset */ - ALuint offset{0u}; - ALsizei frac{0}; - if(!GetSampleOffset(Source, &offset, &frac)) - return AL_FALSE; - - ALuint totalBufferLen{0u}; - ALbufferlistitem *BufferList{Source->queue}; - while(BufferList && totalBufferLen <= offset) - { - if(static_cast(BufferList->max_samples) > offset-totalBufferLen) - { - /* Offset is in this buffer */ - voice->mPosition.store(offset - totalBufferLen, std::memory_order_relaxed); - voice->mPositionFrac.store(frac, std::memory_order_relaxed); - voice->mCurrentBuffer.store(BufferList, std::memory_order_release); - return AL_TRUE; - } - totalBufferLen += BufferList->max_samples; - - BufferList = BufferList->next.load(std::memory_order_relaxed); - } - - /* Offset is out of range of the queue */ - return AL_FALSE; -} - - -ALsource *AllocSource(ALCcontext *context) -{ - ALCdevice *device{context->Device}; - std::lock_guard _{context->SourceLock}; - if(context->NumSources >= device->SourcesMax) - { - alSetError(context, AL_OUT_OF_MEMORY, "Exceeding %u source limit", device->SourcesMax); - return nullptr; - } - auto sublist = std::find_if(context->SourceList.begin(), context->SourceList.end(), - [](const SourceSubList &entry) noexcept -> bool - { return entry.FreeMask != 0; } - ); - auto lidx = static_cast(std::distance(context->SourceList.begin(), sublist)); - ALsource *source; - ALsizei slidx; - if(LIKELY(sublist != context->SourceList.end())) - { - slidx = CTZ64(sublist->FreeMask); - source = sublist->Sources + slidx; - } - else - { - /* Don't allocate so many list entries that the 32-bit ID could - * overflow... - */ - if(UNLIKELY(context->SourceList.size() >= 1<<25)) - { - alSetError(context, AL_OUT_OF_MEMORY, "Too many sources allocated"); - return nullptr; - } - context->SourceList.emplace_back(); - sublist = context->SourceList.end() - 1; - - sublist->FreeMask = ~0_u64; - sublist->Sources = static_cast(al_calloc(16, sizeof(ALsource)*64)); - if(UNLIKELY(!sublist->Sources)) - { - context->SourceList.pop_back(); - alSetError(context, AL_OUT_OF_MEMORY, "Failed to allocate source batch"); - return nullptr; - } - - slidx = 0; - source = sublist->Sources + slidx; - } - - source = new (source) ALsource{device->NumAuxSends}; - - /* Add 1 to avoid source ID 0. */ - source->id = ((lidx<<6) | slidx) + 1; - - context->NumSources += 1; - sublist->FreeMask &= ~(1_u64 << slidx); - - return source; -} - -void FreeSource(ALCcontext *context, ALsource *source) -{ - ALuint id = source->id - 1; - ALsizei lidx = id >> 6; - ALsizei slidx = id & 0x3f; - - ALCdevice *device{context->Device}; - BackendUniqueLock backlock{*device->Backend}; - if(ALvoice *voice{GetSourceVoice(source, context)}) - { - voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); - voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); - voice->mSourceID.store(0u, std::memory_order_relaxed); - std::atomic_thread_fence(std::memory_order_release); - /* Don't set the voice to stopping if it was already stopped or - * stopping. - */ - ALvoice::State oldvstate{ALvoice::Playing}; - voice->mPlayState.compare_exchange_strong(oldvstate, ALvoice::Stopping, - std::memory_order_acq_rel, std::memory_order_acquire); - } - backlock.unlock(); - - source->~ALsource(); - - context->SourceList[lidx].FreeMask |= 1_u64 << slidx; - context->NumSources--; -} - - -inline ALsource *LookupSource(ALCcontext *context, ALuint id) noexcept -{ - ALuint lidx = (id-1) >> 6; - ALsizei slidx = (id-1) & 0x3f; - - if(UNLIKELY(lidx >= context->SourceList.size())) - return nullptr; - SourceSubList &sublist{context->SourceList[lidx]}; - if(UNLIKELY(sublist.FreeMask & (1_u64 << slidx))) - return nullptr; - return sublist.Sources + slidx; -} - -inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) noexcept -{ - ALuint lidx = (id-1) >> 6; - ALsizei slidx = (id-1) & 0x3f; - - if(UNLIKELY(lidx >= device->BufferList.size())) - return nullptr; - BufferSubList &sublist = device->BufferList[lidx]; - if(UNLIKELY(sublist.FreeMask & (1_u64 << slidx))) - return nullptr; - return sublist.Buffers + slidx; -} - -inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) noexcept -{ - ALuint lidx = (id-1) >> 6; - ALsizei slidx = (id-1) & 0x3f; - - if(UNLIKELY(lidx >= device->FilterList.size())) - return nullptr; - FilterSubList &sublist = device->FilterList[lidx]; - if(UNLIKELY(sublist.FreeMask & (1_u64 << slidx))) - return nullptr; - return sublist.Filters + slidx; -} - -inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -{ - ALuint lidx = (id-1) >> 6; - ALsizei slidx = (id-1) & 0x3f; - - if(UNLIKELY(lidx >= context->EffectSlotList.size())) - return nullptr; - EffectSlotSubList &sublist{context->EffectSlotList[lidx]}; - if(UNLIKELY(sublist.FreeMask & (1_u64 << slidx))) - return nullptr; - return sublist.EffectSlots + slidx; -} - - -enum SourceProp : ALenum { - srcPitch = AL_PITCH, - srcGain = AL_GAIN, - srcMinGain = AL_MIN_GAIN, - srcMaxGain = AL_MAX_GAIN, - srcMaxDistance = AL_MAX_DISTANCE, - srcRolloffFactor = AL_ROLLOFF_FACTOR, - srcDopplerFactor = AL_DOPPLER_FACTOR, - srcConeOuterGain = AL_CONE_OUTER_GAIN, - srcSecOffset = AL_SEC_OFFSET, - srcSampleOffset = AL_SAMPLE_OFFSET, - srcByteOffset = AL_BYTE_OFFSET, - srcConeInnerAngle = AL_CONE_INNER_ANGLE, - srcConeOuterAngle = AL_CONE_OUTER_ANGLE, - srcRefDistance = AL_REFERENCE_DISTANCE, - - srcPosition = AL_POSITION, - srcVelocity = AL_VELOCITY, - srcDirection = AL_DIRECTION, - - srcSourceRelative = AL_SOURCE_RELATIVE, - srcLooping = AL_LOOPING, - srcBuffer = AL_BUFFER, - srcSourceState = AL_SOURCE_STATE, - srcBuffersQueued = AL_BUFFERS_QUEUED, - srcBuffersProcessed = AL_BUFFERS_PROCESSED, - srcSourceType = AL_SOURCE_TYPE, - - /* ALC_EXT_EFX */ - srcConeOuterGainHF = AL_CONE_OUTER_GAINHF, - srcAirAbsorptionFactor = AL_AIR_ABSORPTION_FACTOR, - srcRoomRolloffFactor = AL_ROOM_ROLLOFF_FACTOR, - srcDirectFilterGainHFAuto = AL_DIRECT_FILTER_GAINHF_AUTO, - srcAuxSendFilterGainAuto = AL_AUXILIARY_SEND_FILTER_GAIN_AUTO, - srcAuxSendFilterGainHFAuto = AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO, - srcDirectFilter = AL_DIRECT_FILTER, - srcAuxSendFilter = AL_AUXILIARY_SEND_FILTER, - - /* AL_SOFT_direct_channels */ - srcDirectChannelsSOFT = AL_DIRECT_CHANNELS_SOFT, - - /* AL_EXT_source_distance_model */ - srcDistanceModel = AL_DISTANCE_MODEL, - - /* AL_SOFT_source_latency */ - srcSampleOffsetLatencySOFT = AL_SAMPLE_OFFSET_LATENCY_SOFT, - srcSecOffsetLatencySOFT = AL_SEC_OFFSET_LATENCY_SOFT, - - /* AL_EXT_STEREO_ANGLES */ - srcAngles = AL_STEREO_ANGLES, - - /* AL_EXT_SOURCE_RADIUS */ - srcRadius = AL_SOURCE_RADIUS, - - /* AL_EXT_BFORMAT */ - srcOrientation = AL_ORIENTATION, - - /* AL_SOFT_source_resampler */ - srcResampler = AL_SOURCE_RESAMPLER_SOFT, - - /* AL_SOFT_source_spatialize */ - srcSpatialize = AL_SOURCE_SPATIALIZE_SOFT, - - /* ALC_SOFT_device_clock */ - srcSampleOffsetClockSOFT = AL_SAMPLE_OFFSET_CLOCK_SOFT, - srcSecOffsetClockSOFT = AL_SEC_OFFSET_CLOCK_SOFT, -}; - -/** - * Returns if the last known state for the source was playing or paused. Does - * not sync with the mixer voice. - */ -inline bool IsPlayingOrPaused(ALsource *source) -{ return source->state == AL_PLAYING || source->state == AL_PAUSED; } - -/** - * Returns an updated source state using the matching voice's status (or lack - * thereof). - */ -inline ALenum GetSourceState(ALsource *source, ALvoice *voice) -{ - if(!voice && source->state == AL_PLAYING) - source->state = AL_STOPPED; - return source->state; -} - -/** - * Returns if the source should specify an update, given the context's - * deferring state and the source's last known state. - */ -inline bool SourceShouldUpdate(ALsource *source, ALCcontext *context) -{ - return !context->DeferUpdates.load(std::memory_order_acquire) && - IsPlayingOrPaused(source); -} - - -/** Can only be called while the mixer is locked! */ -void SendStateChangeEvent(ALCcontext *context, ALuint id, ALenum state) -{ - ALbitfieldSOFT enabledevt{context->EnabledEvts.load(std::memory_order_acquire)}; - if(!(enabledevt&EventType_SourceStateChange)) return; - - /* The mixer may have queued a state change that's not yet been processed, - * and we don't want state change messages to occur out of order, so send - * it through the async queue to ensure proper ordering. - */ - RingBuffer *ring{context->AsyncEvents.get()}; - auto evt_vec = ring->getWriteVector(); - if(evt_vec.first.len < 1) return; - - AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}}; - evt->u.srcstate.id = id; - evt->u.srcstate.state = state; - ring->writeAdvance(1); - context->EventSem.post(); -} - - -ALint FloatValsByProp(ALenum prop) -{ - switch(static_cast(prop)) - { - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_MAX_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_DOPPLER_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_REFERENCE_DISTANCE: - case AL_CONE_OUTER_GAINHF: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_DIRECT_FILTER_GAINHF_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - case AL_DIRECT_CHANNELS_SOFT: - case AL_DISTANCE_MODEL: - case AL_SOURCE_RELATIVE: - case AL_LOOPING: - case AL_SOURCE_STATE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - case AL_SOURCE_TYPE: - case AL_SOURCE_RADIUS: - case AL_SOURCE_RESAMPLER_SOFT: - case AL_SOURCE_SPATIALIZE_SOFT: - return 1; - - case AL_STEREO_ANGLES: - return 2; - - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - return 3; - - case AL_ORIENTATION: - return 6; - - case AL_SEC_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_CLOCK_SOFT: - break; /* Double only */ - - case AL_BUFFER: - case AL_DIRECT_FILTER: - case AL_AUXILIARY_SEND_FILTER: - break; /* i/i64 only */ - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - break; /* i64 only */ - } - return 0; -} -ALint DoubleValsByProp(ALenum prop) -{ - switch(static_cast(prop)) - { - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_MAX_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_DOPPLER_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_REFERENCE_DISTANCE: - case AL_CONE_OUTER_GAINHF: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_DIRECT_FILTER_GAINHF_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - case AL_DIRECT_CHANNELS_SOFT: - case AL_DISTANCE_MODEL: - case AL_SOURCE_RELATIVE: - case AL_LOOPING: - case AL_SOURCE_STATE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - case AL_SOURCE_TYPE: - case AL_SOURCE_RADIUS: - case AL_SOURCE_RESAMPLER_SOFT: - case AL_SOURCE_SPATIALIZE_SOFT: - return 1; - - case AL_SEC_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_CLOCK_SOFT: - case AL_STEREO_ANGLES: - return 2; - - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - return 3; - - case AL_ORIENTATION: - return 6; - - case AL_BUFFER: - case AL_DIRECT_FILTER: - case AL_AUXILIARY_SEND_FILTER: - break; /* i/i64 only */ - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - break; /* i64 only */ - } - return 0; -} - -ALint IntValsByProp(ALenum prop) -{ - switch(static_cast(prop)) - { - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_MAX_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_DOPPLER_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_REFERENCE_DISTANCE: - case AL_CONE_OUTER_GAINHF: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_DIRECT_FILTER_GAINHF_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - case AL_DIRECT_CHANNELS_SOFT: - case AL_DISTANCE_MODEL: - case AL_SOURCE_RELATIVE: - case AL_LOOPING: - case AL_BUFFER: - case AL_SOURCE_STATE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - case AL_SOURCE_TYPE: - case AL_DIRECT_FILTER: - case AL_SOURCE_RADIUS: - case AL_SOURCE_RESAMPLER_SOFT: - case AL_SOURCE_SPATIALIZE_SOFT: - return 1; - - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - case AL_AUXILIARY_SEND_FILTER: - return 3; - - case AL_ORIENTATION: - return 6; - - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - break; /* i64 only */ - case AL_SEC_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_CLOCK_SOFT: - break; /* Double only */ - case AL_STEREO_ANGLES: - break; /* Float/double only */ - } - return 0; -} -ALint Int64ValsByProp(ALenum prop) -{ - switch(static_cast(prop)) - { - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_MAX_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_DOPPLER_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_REFERENCE_DISTANCE: - case AL_CONE_OUTER_GAINHF: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_DIRECT_FILTER_GAINHF_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - case AL_DIRECT_CHANNELS_SOFT: - case AL_DISTANCE_MODEL: - case AL_SOURCE_RELATIVE: - case AL_LOOPING: - case AL_BUFFER: - case AL_SOURCE_STATE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - case AL_SOURCE_TYPE: - case AL_DIRECT_FILTER: - case AL_SOURCE_RADIUS: - case AL_SOURCE_RESAMPLER_SOFT: - case AL_SOURCE_SPATIALIZE_SOFT: - return 1; - - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - return 2; - - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - case AL_AUXILIARY_SEND_FILTER: - return 3; - - case AL_ORIENTATION: - return 6; - - case AL_SEC_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_CLOCK_SOFT: - break; /* Double only */ - case AL_STEREO_ANGLES: - break; /* Float/double only */ - } - return 0; -} - - -ALboolean SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const ALfloat *values); -ALboolean SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const ALint *values); -ALboolean SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const ALint64SOFT *values); - -#define CHECKVAL(x) do { \ - if(!(x)) \ - { \ - alSetError(Context, AL_INVALID_VALUE, "Value out of range"); \ - return AL_FALSE; \ - } \ -} while(0) - -void UpdateSourceProps(ALsource *source, ALCcontext *context) -{ - ALvoice *voice; - if(SourceShouldUpdate(source, context) && (voice=GetSourceVoice(source, context)) != nullptr) - UpdateSourceProps(source, voice, context); - else - source->PropsClean.clear(std::memory_order_release); -} - -ALboolean SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const ALfloat *values) -{ - ALint ival; - - switch(prop) - { - case AL_SEC_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_CLOCK_SOFT: - /* Query only */ - SETERR_RETURN(Context, AL_INVALID_OPERATION, AL_FALSE, - "Setting read-only source property 0x%04x", prop); - - case AL_PITCH: - CHECKVAL(*values >= 0.0f); - - Source->Pitch = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_CONE_INNER_ANGLE: - CHECKVAL(*values >= 0.0f && *values <= 360.0f); - - Source->InnerAngle = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_CONE_OUTER_ANGLE: - CHECKVAL(*values >= 0.0f && *values <= 360.0f); - - Source->OuterAngle = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_GAIN: - CHECKVAL(*values >= 0.0f); - - Source->Gain = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_MAX_DISTANCE: - CHECKVAL(*values >= 0.0f); - - Source->MaxDistance = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_ROLLOFF_FACTOR: - CHECKVAL(*values >= 0.0f); - - Source->RolloffFactor = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_REFERENCE_DISTANCE: - CHECKVAL(*values >= 0.0f); - - Source->RefDistance = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_MIN_GAIN: - CHECKVAL(*values >= 0.0f); - - Source->MinGain = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_MAX_GAIN: - CHECKVAL(*values >= 0.0f); - - Source->MaxGain = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_CONE_OUTER_GAIN: - CHECKVAL(*values >= 0.0f && *values <= 1.0f); - - Source->OuterGain = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_CONE_OUTER_GAINHF: - CHECKVAL(*values >= 0.0f && *values <= 1.0f); - - Source->OuterGainHF = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_AIR_ABSORPTION_FACTOR: - CHECKVAL(*values >= 0.0f && *values <= 10.0f); - - Source->AirAbsorptionFactor = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_ROOM_ROLLOFF_FACTOR: - CHECKVAL(*values >= 0.0f && *values <= 10.0f); - - Source->RoomRolloffFactor = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_DOPPLER_FACTOR: - CHECKVAL(*values >= 0.0f && *values <= 1.0f); - - Source->DopplerFactor = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - CHECKVAL(*values >= 0.0f); - - Source->OffsetType = prop; - Source->Offset = *values; - - if(IsPlayingOrPaused(Source)) - { - ALCdevice *device{Context->Device}; - BackendLockGuard _{*device->Backend}; - /* Double-check that the source is still playing while we have - * the lock. - */ - if(ALvoice *voice{GetSourceVoice(Source, Context)}) - { - if(ApplyOffset(Source, voice) == AL_FALSE) - SETERR_RETURN(Context, AL_INVALID_VALUE, AL_FALSE, "Invalid offset"); - } - } - return AL_TRUE; - - case AL_SOURCE_RADIUS: - CHECKVAL(*values >= 0.0f && std::isfinite(*values)); - - Source->Radius = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_STEREO_ANGLES: - CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1])); - - Source->StereoPan[0] = values[0]; - Source->StereoPan[1] = values[1]; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - - case AL_POSITION: - CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); - - Source->Position[0] = values[0]; - Source->Position[1] = values[1]; - Source->Position[2] = values[2]; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_VELOCITY: - CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); - - Source->Velocity[0] = values[0]; - Source->Velocity[1] = values[1]; - Source->Velocity[2] = values[2]; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_DIRECTION: - CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); - - Source->Direction[0] = values[0]; - Source->Direction[1] = values[1]; - Source->Direction[2] = values[2]; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_ORIENTATION: - CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) && - std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5])); - - Source->OrientAt[0] = values[0]; - Source->OrientAt[1] = values[1]; - Source->OrientAt[2] = values[2]; - Source->OrientUp[0] = values[3]; - Source->OrientUp[1] = values[4]; - Source->OrientUp[2] = values[5]; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - - case AL_SOURCE_RELATIVE: - case AL_LOOPING: - case AL_SOURCE_STATE: - case AL_SOURCE_TYPE: - case AL_DISTANCE_MODEL: - case AL_DIRECT_FILTER_GAINHF_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - case AL_DIRECT_CHANNELS_SOFT: - case AL_SOURCE_RESAMPLER_SOFT: - case AL_SOURCE_SPATIALIZE_SOFT: - ival = static_cast(values[0]); - return SetSourceiv(Source, Context, prop, &ival); - - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - ival = static_cast(static_cast(values[0])); - return SetSourceiv(Source, Context, prop, &ival); - - case AL_BUFFER: - case AL_DIRECT_FILTER: - case AL_AUXILIARY_SEND_FILTER: - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - break; - } - - ERR("Unexpected property: 0x%04x\n", prop); - SETERR_RETURN(Context, AL_INVALID_ENUM, AL_FALSE, "Invalid source float property 0x%04x", prop); -} - -ALboolean SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const ALint *values) -{ - ALCdevice *device{Context->Device}; - ALbuffer *buffer{nullptr}; - ALfilter *filter{nullptr}; - ALeffectslot *slot{nullptr}; - ALbufferlistitem *oldlist{nullptr}; - std::unique_lock slotlock; - std::unique_lock filtlock; - std::unique_lock buflock; - ALfloat fvals[6]; - - switch(prop) - { - case AL_SOURCE_STATE: - case AL_SOURCE_TYPE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - /* Query only */ - SETERR_RETURN(Context, AL_INVALID_OPERATION, AL_FALSE, - "Setting read-only source property 0x%04x", prop); - - case AL_SOURCE_RELATIVE: - CHECKVAL(*values == AL_FALSE || *values == AL_TRUE); - - Source->HeadRelative = static_cast(*values); - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_LOOPING: - CHECKVAL(*values == AL_FALSE || *values == AL_TRUE); - - Source->Looping = static_cast(*values); - if(IsPlayingOrPaused(Source)) - { - ALvoice *voice{GetSourceVoice(Source, Context)}; - if(voice) - { - if(Source->Looping) - voice->mLoopBuffer.store(Source->queue, std::memory_order_release); - else - voice->mLoopBuffer.store(nullptr, std::memory_order_release); - - /* If the source is playing, wait for the current mix to finish - * to ensure it isn't currently looping back or reaching the - * end. - */ - while((device->MixCount.load(std::memory_order_acquire)&1)) - std::this_thread::yield(); - } - } - return AL_TRUE; - - case AL_BUFFER: - buflock = std::unique_lock{device->BufferLock}; - if(!(*values == 0 || (buffer=LookupBuffer(device, *values)) != nullptr)) - SETERR_RETURN(Context, AL_INVALID_VALUE, AL_FALSE, "Invalid buffer ID %u", - *values); - - if(buffer && buffer->MappedAccess != 0 && - !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) - SETERR_RETURN(Context, AL_INVALID_OPERATION, AL_FALSE, - "Setting non-persistently mapped buffer %u", buffer->id); - else - { - ALenum state = GetSourceState(Source, GetSourceVoice(Source, Context)); - if(state == AL_PLAYING || state == AL_PAUSED) - SETERR_RETURN(Context, AL_INVALID_OPERATION, AL_FALSE, - "Setting buffer on playing or paused source %u", Source->id); - } - - oldlist = Source->queue; - if(buffer != nullptr) - { - /* Add the selected buffer to a one-item queue */ - auto newlist = static_cast(al_calloc(DEF_ALIGN, - ALbufferlistitem::Sizeof(1u))); - newlist->next.store(nullptr, std::memory_order_relaxed); - newlist->max_samples = buffer->SampleLen; - newlist->num_buffers = 1; - newlist->buffers[0] = buffer; - IncrementRef(&buffer->ref); - - /* Source is now Static */ - Source->SourceType = AL_STATIC; - Source->queue = newlist; - } - else - { - /* Source is now Undetermined */ - Source->SourceType = AL_UNDETERMINED; - Source->queue = nullptr; - } - buflock.unlock(); - - /* Delete all elements in the previous queue */ - while(oldlist != nullptr) - { - ALbufferlistitem *temp{oldlist}; - oldlist = temp->next.load(std::memory_order_relaxed); - - for(ALsizei i{0};i < temp->num_buffers;i++) - { - if(temp->buffers[i]) - DecrementRef(&temp->buffers[i]->ref); - } - al_free(temp); - } - return AL_TRUE; - - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - CHECKVAL(*values >= 0); - - Source->OffsetType = prop; - Source->Offset = *values; - - if(IsPlayingOrPaused(Source)) - { - ALCdevice *device{Context->Device}; - BackendLockGuard _{*device->Backend}; - if(ALvoice *voice{GetSourceVoice(Source, Context)}) - { - if(ApplyOffset(Source, voice) == AL_FALSE) - SETERR_RETURN(Context, AL_INVALID_VALUE, AL_FALSE, - "Invalid source offset"); - } - } - return AL_TRUE; - - case AL_DIRECT_FILTER: - filtlock = std::unique_lock{device->FilterLock}; - if(!(*values == 0 || (filter=LookupFilter(device, *values)) != nullptr)) - SETERR_RETURN(Context, AL_INVALID_VALUE, AL_FALSE, "Invalid filter ID %u", - *values); - - if(!filter) - { - Source->Direct.Gain = 1.0f; - Source->Direct.GainHF = 1.0f; - Source->Direct.HFReference = LOWPASSFREQREF; - Source->Direct.GainLF = 1.0f; - Source->Direct.LFReference = HIGHPASSFREQREF; - } - else - { - Source->Direct.Gain = filter->Gain; - Source->Direct.GainHF = filter->GainHF; - Source->Direct.HFReference = filter->HFReference; - Source->Direct.GainLF = filter->GainLF; - Source->Direct.LFReference = filter->LFReference; - } - filtlock.unlock(); - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_DIRECT_FILTER_GAINHF_AUTO: - CHECKVAL(*values == AL_FALSE || *values == AL_TRUE); - - Source->DryGainHFAuto = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - CHECKVAL(*values == AL_FALSE || *values == AL_TRUE); - - Source->WetGainAuto = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - CHECKVAL(*values == AL_FALSE || *values == AL_TRUE); - - Source->WetGainHFAuto = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_DIRECT_CHANNELS_SOFT: - CHECKVAL(*values == AL_FALSE || *values == AL_TRUE); - - Source->DirectChannels = *values; - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_DISTANCE_MODEL: - CHECKVAL(*values == AL_NONE || - *values == AL_INVERSE_DISTANCE || - *values == AL_INVERSE_DISTANCE_CLAMPED || - *values == AL_LINEAR_DISTANCE || - *values == AL_LINEAR_DISTANCE_CLAMPED || - *values == AL_EXPONENT_DISTANCE || - *values == AL_EXPONENT_DISTANCE_CLAMPED); - - Source->mDistanceModel = static_cast(*values); - if(Context->SourceDistanceModel) - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_SOURCE_RESAMPLER_SOFT: - CHECKVAL(*values >= 0 && *values <= ResamplerMax); - - Source->mResampler = static_cast(*values); - UpdateSourceProps(Source, Context); - return AL_TRUE; - - case AL_SOURCE_SPATIALIZE_SOFT: - CHECKVAL(*values >= AL_FALSE && *values <= AL_AUTO_SOFT); - - Source->mSpatialize = static_cast(*values); - UpdateSourceProps(Source, Context); - return AL_TRUE; - - - case AL_AUXILIARY_SEND_FILTER: - slotlock = std::unique_lock{Context->EffectSlotLock}; - if(!(values[0] == 0 || (slot=LookupEffectSlot(Context, values[0])) != nullptr)) - SETERR_RETURN(Context, AL_INVALID_VALUE, AL_FALSE, "Invalid effect ID %u", - values[0]); - if(static_cast(values[1]) >= static_cast(device->NumAuxSends)) - SETERR_RETURN(Context, AL_INVALID_VALUE, AL_FALSE, "Invalid send %u", values[1]); - - filtlock = std::unique_lock{device->FilterLock}; - if(!(values[2] == 0 || (filter=LookupFilter(device, values[2])) != nullptr)) - SETERR_RETURN(Context, AL_INVALID_VALUE, AL_FALSE, "Invalid filter ID %u", - values[2]); - - if(!filter) - { - /* Disable filter */ - Source->Send[values[1]].Gain = 1.0f; - Source->Send[values[1]].GainHF = 1.0f; - Source->Send[values[1]].HFReference = LOWPASSFREQREF; - Source->Send[values[1]].GainLF = 1.0f; - Source->Send[values[1]].LFReference = HIGHPASSFREQREF; - } - else - { - Source->Send[values[1]].Gain = filter->Gain; - Source->Send[values[1]].GainHF = filter->GainHF; - Source->Send[values[1]].HFReference = filter->HFReference; - Source->Send[values[1]].GainLF = filter->GainLF; - Source->Send[values[1]].LFReference = filter->LFReference; - } - filtlock.unlock(); - - if(slot != Source->Send[values[1]].Slot && IsPlayingOrPaused(Source)) - { - /* Add refcount on the new slot, and release the previous slot */ - if(slot) IncrementRef(&slot->ref); - if(Source->Send[values[1]].Slot) - DecrementRef(&Source->Send[values[1]].Slot->ref); - Source->Send[values[1]].Slot = slot; - - /* We must force an update if the auxiliary slot changed on an - * active source, in case the slot is about to be deleted. - */ - ALvoice *voice{GetSourceVoice(Source, Context)}; - if(voice) UpdateSourceProps(Source, voice, Context); - else Source->PropsClean.clear(std::memory_order_release); - } - else - { - if(slot) IncrementRef(&slot->ref); - if(Source->Send[values[1]].Slot) - DecrementRef(&Source->Send[values[1]].Slot->ref); - Source->Send[values[1]].Slot = slot; - UpdateSourceProps(Source, Context); - } - - return AL_TRUE; - - - /* 1x float */ - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_REFERENCE_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_MAX_DISTANCE: - case AL_DOPPLER_FACTOR: - case AL_CONE_OUTER_GAINHF: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_SOURCE_RADIUS: - fvals[0] = static_cast(*values); - return SetSourcefv(Source, Context, prop, fvals); - - /* 3x float */ - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - fvals[0] = static_cast(values[0]); - fvals[1] = static_cast(values[1]); - fvals[2] = static_cast(values[2]); - return SetSourcefv(Source, Context, prop, fvals); - - /* 6x float */ - case AL_ORIENTATION: - fvals[0] = static_cast(values[0]); - fvals[1] = static_cast(values[1]); - fvals[2] = static_cast(values[2]); - fvals[3] = static_cast(values[3]); - fvals[4] = static_cast(values[4]); - fvals[5] = static_cast(values[5]); - return SetSourcefv(Source, Context, prop, fvals); - - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_CLOCK_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - case AL_STEREO_ANGLES: - break; - } - - ERR("Unexpected property: 0x%04x\n", prop); - SETERR_RETURN(Context, AL_INVALID_ENUM, AL_FALSE, "Invalid source integer property 0x%04x", - prop); -} - -ALboolean SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const ALint64SOFT *values) -{ - ALfloat fvals[6]; - ALint ivals[3]; - - switch(prop) - { - case AL_SOURCE_TYPE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - case AL_SOURCE_STATE: - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - /* Query only */ - SETERR_RETURN(Context, AL_INVALID_OPERATION, AL_FALSE, - "Setting read-only source property 0x%04x", prop); - - /* 1x int */ - case AL_SOURCE_RELATIVE: - case AL_LOOPING: - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - case AL_DIRECT_FILTER_GAINHF_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - case AL_DIRECT_CHANNELS_SOFT: - case AL_DISTANCE_MODEL: - case AL_SOURCE_RESAMPLER_SOFT: - case AL_SOURCE_SPATIALIZE_SOFT: - CHECKVAL(*values <= INT_MAX && *values >= INT_MIN); - - ivals[0] = static_cast(*values); - return SetSourceiv(Source, Context, prop, ivals); - - /* 1x uint */ - case AL_BUFFER: - case AL_DIRECT_FILTER: - CHECKVAL(*values <= UINT_MAX && *values >= 0); - - ivals[0] = static_cast(*values); - return SetSourceiv(Source, Context, prop, ivals); - - /* 3x uint */ - case AL_AUXILIARY_SEND_FILTER: - CHECKVAL(values[0] <= UINT_MAX && values[0] >= 0 && - values[1] <= UINT_MAX && values[1] >= 0 && - values[2] <= UINT_MAX && values[2] >= 0); - - ivals[0] = static_cast(values[0]); - ivals[1] = static_cast(values[1]); - ivals[2] = static_cast(values[2]); - return SetSourceiv(Source, Context, prop, ivals); - - /* 1x float */ - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_REFERENCE_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_MAX_DISTANCE: - case AL_DOPPLER_FACTOR: - case AL_CONE_OUTER_GAINHF: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_SOURCE_RADIUS: - fvals[0] = static_cast(*values); - return SetSourcefv(Source, Context, prop, fvals); - - /* 3x float */ - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - fvals[0] = static_cast(values[0]); - fvals[1] = static_cast(values[1]); - fvals[2] = static_cast(values[2]); - return SetSourcefv(Source, Context, prop, fvals); - - /* 6x float */ - case AL_ORIENTATION: - fvals[0] = static_cast(values[0]); - fvals[1] = static_cast(values[1]); - fvals[2] = static_cast(values[2]); - fvals[3] = static_cast(values[3]); - fvals[4] = static_cast(values[4]); - fvals[5] = static_cast(values[5]); - return SetSourcefv(Source, Context, prop, fvals); - - case AL_SEC_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_CLOCK_SOFT: - case AL_STEREO_ANGLES: - break; - } - - ERR("Unexpected property: 0x%04x\n", prop); - SETERR_RETURN(Context, AL_INVALID_ENUM, AL_FALSE, "Invalid source integer64 property 0x%04x", - prop); -} - -#undef CHECKVAL - - -ALboolean GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, ALdouble *values); -ALboolean GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, ALint *values); -ALboolean GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, ALint64SOFT *values); - -ALboolean GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, ALdouble *values) -{ - ALCdevice *device{Context->Device}; - ClockLatency clocktime; - std::chrono::nanoseconds srcclock; - ALint ivals[3]; - ALboolean err; - - switch(prop) - { - case AL_GAIN: - *values = Source->Gain; - return AL_TRUE; - - case AL_PITCH: - *values = Source->Pitch; - return AL_TRUE; - - case AL_MAX_DISTANCE: - *values = Source->MaxDistance; - return AL_TRUE; - - case AL_ROLLOFF_FACTOR: - *values = Source->RolloffFactor; - return AL_TRUE; - - case AL_REFERENCE_DISTANCE: - *values = Source->RefDistance; - return AL_TRUE; - - case AL_CONE_INNER_ANGLE: - *values = Source->InnerAngle; - return AL_TRUE; - - case AL_CONE_OUTER_ANGLE: - *values = Source->OuterAngle; - return AL_TRUE; - - case AL_MIN_GAIN: - *values = Source->MinGain; - return AL_TRUE; - - case AL_MAX_GAIN: - *values = Source->MaxGain; - return AL_TRUE; - - case AL_CONE_OUTER_GAIN: - *values = Source->OuterGain; - return AL_TRUE; - - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - *values = GetSourceOffset(Source, prop, Context); - return AL_TRUE; - - case AL_CONE_OUTER_GAINHF: - *values = Source->OuterGainHF; - return AL_TRUE; - - case AL_AIR_ABSORPTION_FACTOR: - *values = Source->AirAbsorptionFactor; - return AL_TRUE; - - case AL_ROOM_ROLLOFF_FACTOR: - *values = Source->RoomRolloffFactor; - return AL_TRUE; - - case AL_DOPPLER_FACTOR: - *values = Source->DopplerFactor; - return AL_TRUE; - - case AL_SOURCE_RADIUS: - *values = Source->Radius; - return AL_TRUE; - - case AL_STEREO_ANGLES: - values[0] = Source->StereoPan[0]; - values[1] = Source->StereoPan[1]; - return AL_TRUE; - - case AL_SEC_OFFSET_LATENCY_SOFT: - /* Get the source offset with the clock time first. Then get the - * clock time with the device latency. Order is important. - */ - values[0] = GetSourceSecOffset(Source, Context, &srcclock); - { std::lock_guard _{device->StateLock}; - clocktime = GetClockLatency(device); - } - if(srcclock == clocktime.ClockTime) - values[1] = static_cast(clocktime.Latency.count()) / 1000000000.0; - else - { - /* If the clock time incremented, reduce the latency by that - * much since it's that much closer to the source offset it got - * earlier. - */ - std::chrono::nanoseconds diff = clocktime.ClockTime - srcclock; - values[1] = static_cast((clocktime.Latency - std::min(clocktime.Latency, diff)).count()) / - 1000000000.0; - } - return AL_TRUE; - - case AL_SEC_OFFSET_CLOCK_SOFT: - values[0] = GetSourceSecOffset(Source, Context, &srcclock); - values[1] = srcclock.count() / 1000000000.0; - return AL_TRUE; - - case AL_POSITION: - values[0] = Source->Position[0]; - values[1] = Source->Position[1]; - values[2] = Source->Position[2]; - return AL_TRUE; - - case AL_VELOCITY: - values[0] = Source->Velocity[0]; - values[1] = Source->Velocity[1]; - values[2] = Source->Velocity[2]; - return AL_TRUE; - - case AL_DIRECTION: - values[0] = Source->Direction[0]; - values[1] = Source->Direction[1]; - values[2] = Source->Direction[2]; - return AL_TRUE; - - case AL_ORIENTATION: - values[0] = Source->OrientAt[0]; - values[1] = Source->OrientAt[1]; - values[2] = Source->OrientAt[2]; - values[3] = Source->OrientUp[0]; - values[4] = Source->OrientUp[1]; - values[5] = Source->OrientUp[2]; - return AL_TRUE; - - /* 1x int */ - case AL_SOURCE_RELATIVE: - case AL_LOOPING: - case AL_SOURCE_STATE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - case AL_SOURCE_TYPE: - case AL_DIRECT_FILTER_GAINHF_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - case AL_DIRECT_CHANNELS_SOFT: - case AL_DISTANCE_MODEL: - case AL_SOURCE_RESAMPLER_SOFT: - case AL_SOURCE_SPATIALIZE_SOFT: - if((err=GetSourceiv(Source, Context, prop, ivals)) != AL_FALSE) - *values = static_cast(ivals[0]); - return err; - - case AL_BUFFER: - case AL_DIRECT_FILTER: - case AL_AUXILIARY_SEND_FILTER: - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - break; - } - - ERR("Unexpected property: 0x%04x\n", prop); - SETERR_RETURN(Context, AL_INVALID_ENUM, AL_FALSE, "Invalid source double property 0x%04x", - prop); -} - -ALboolean GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, ALint *values) -{ - ALbufferlistitem *BufferList; - ALdouble dvals[6]; - ALboolean err; - - switch(prop) - { - case AL_SOURCE_RELATIVE: - *values = Source->HeadRelative; - return AL_TRUE; - - case AL_LOOPING: - *values = Source->Looping; - return AL_TRUE; - - case AL_BUFFER: - BufferList = (Source->SourceType == AL_STATIC) ? Source->queue : nullptr; - *values = (BufferList && BufferList->num_buffers >= 1 && BufferList->buffers[0]) ? - BufferList->buffers[0]->id : 0; - return AL_TRUE; - - case AL_SOURCE_STATE: - *values = GetSourceState(Source, GetSourceVoice(Source, Context)); - return AL_TRUE; - - case AL_BUFFERS_QUEUED: - if(!(BufferList=Source->queue)) - *values = 0; - else - { - ALsizei count = 0; - do { - count += BufferList->num_buffers; - BufferList = BufferList->next.load(std::memory_order_relaxed); - } while(BufferList != nullptr); - *values = count; - } - return AL_TRUE; - - case AL_BUFFERS_PROCESSED: - if(Source->Looping || Source->SourceType != AL_STREAMING) - { - /* Buffers on a looping source are in a perpetual state of - * PENDING, so don't report any as PROCESSED */ - *values = 0; - } - else - { - const ALbufferlistitem *BufferList{Source->queue}; - const ALbufferlistitem *Current{nullptr}; - ALsizei played{0}; - - ALvoice *voice{GetSourceVoice(Source, Context)}; - if(voice != nullptr) - Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); - else if(Source->state == AL_INITIAL) - Current = BufferList; - - while(BufferList && BufferList != Current) - { - played += BufferList->num_buffers; - BufferList = BufferList->next.load(std::memory_order_relaxed); - } - *values = played; - } - return AL_TRUE; - - case AL_SOURCE_TYPE: - *values = Source->SourceType; - return AL_TRUE; - - case AL_DIRECT_FILTER_GAINHF_AUTO: - *values = Source->DryGainHFAuto; - return AL_TRUE; - - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - *values = Source->WetGainAuto; - return AL_TRUE; - - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - *values = Source->WetGainHFAuto; - return AL_TRUE; - - case AL_DIRECT_CHANNELS_SOFT: - *values = Source->DirectChannels; - return AL_TRUE; - - case AL_DISTANCE_MODEL: - *values = static_cast(Source->mDistanceModel); - return AL_TRUE; - - case AL_SOURCE_RESAMPLER_SOFT: - *values = Source->mResampler; - return AL_TRUE; - - case AL_SOURCE_SPATIALIZE_SOFT: - *values = Source->mSpatialize; - return AL_TRUE; - - /* 1x float/double */ - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_REFERENCE_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_MAX_DISTANCE: - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - case AL_DOPPLER_FACTOR: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAINHF: - case AL_SOURCE_RADIUS: - if((err=GetSourcedv(Source, Context, prop, dvals)) != AL_FALSE) - *values = static_cast(dvals[0]); - return err; - - /* 3x float/double */ - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - if((err=GetSourcedv(Source, Context, prop, dvals)) != AL_FALSE) - { - values[0] = static_cast(dvals[0]); - values[1] = static_cast(dvals[1]); - values[2] = static_cast(dvals[2]); - } - return err; - - /* 6x float/double */ - case AL_ORIENTATION: - if((err=GetSourcedv(Source, Context, prop, dvals)) != AL_FALSE) - { - values[0] = static_cast(dvals[0]); - values[1] = static_cast(dvals[1]); - values[2] = static_cast(dvals[2]); - values[3] = static_cast(dvals[3]); - values[4] = static_cast(dvals[4]); - values[5] = static_cast(dvals[5]); - } - return err; - - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - break; /* i64 only */ - case AL_SEC_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_CLOCK_SOFT: - break; /* Double only */ - case AL_STEREO_ANGLES: - break; /* Float/double only */ - - case AL_DIRECT_FILTER: - case AL_AUXILIARY_SEND_FILTER: - break; /* ??? */ - } - - ERR("Unexpected property: 0x%04x\n", prop); - SETERR_RETURN(Context, AL_INVALID_ENUM, AL_FALSE, "Invalid source integer property 0x%04x", - prop); -} - -ALboolean GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, ALint64SOFT *values) -{ - ALCdevice *device = Context->Device; - ClockLatency clocktime; - std::chrono::nanoseconds srcclock; - ALdouble dvals[6]; - ALint ivals[3]; - ALboolean err; - - switch(prop) - { - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - /* Get the source offset with the clock time first. Then get the - * clock time with the device latency. Order is important. - */ - values[0] = GetSourceSampleOffset(Source, Context, &srcclock); - { std::lock_guard _{device->StateLock}; - clocktime = GetClockLatency(device); - } - if(srcclock == clocktime.ClockTime) - values[1] = clocktime.Latency.count(); - else - { - /* If the clock time incremented, reduce the latency by that - * much since it's that much closer to the source offset it got - * earlier. - */ - auto diff = clocktime.ClockTime - srcclock; - values[1] = (clocktime.Latency - std::min(clocktime.Latency, diff)).count(); - } - return AL_TRUE; - - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - values[0] = GetSourceSampleOffset(Source, Context, &srcclock); - values[1] = srcclock.count(); - return AL_TRUE; - - /* 1x float/double */ - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_REFERENCE_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_MAX_DISTANCE: - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - case AL_DOPPLER_FACTOR: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAINHF: - case AL_SOURCE_RADIUS: - if((err=GetSourcedv(Source, Context, prop, dvals)) != AL_FALSE) - *values = static_cast(dvals[0]); - return err; - - /* 3x float/double */ - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - if((err=GetSourcedv(Source, Context, prop, dvals)) != AL_FALSE) - { - values[0] = static_cast(dvals[0]); - values[1] = static_cast(dvals[1]); - values[2] = static_cast(dvals[2]); - } - return err; - - /* 6x float/double */ - case AL_ORIENTATION: - if((err=GetSourcedv(Source, Context, prop, dvals)) != AL_FALSE) - { - values[0] = static_cast(dvals[0]); - values[1] = static_cast(dvals[1]); - values[2] = static_cast(dvals[2]); - values[3] = static_cast(dvals[3]); - values[4] = static_cast(dvals[4]); - values[5] = static_cast(dvals[5]); - } - return err; - - /* 1x int */ - case AL_SOURCE_RELATIVE: - case AL_LOOPING: - case AL_SOURCE_STATE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - case AL_SOURCE_TYPE: - case AL_DIRECT_FILTER_GAINHF_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - case AL_DIRECT_CHANNELS_SOFT: - case AL_DISTANCE_MODEL: - case AL_SOURCE_RESAMPLER_SOFT: - case AL_SOURCE_SPATIALIZE_SOFT: - if((err=GetSourceiv(Source, Context, prop, ivals)) != AL_FALSE) - *values = ivals[0]; - return err; - - /* 1x uint */ - case AL_BUFFER: - case AL_DIRECT_FILTER: - if((err=GetSourceiv(Source, Context, prop, ivals)) != AL_FALSE) - *values = static_cast(ivals[0]); - return err; - - /* 3x uint */ - case AL_AUXILIARY_SEND_FILTER: - if((err=GetSourceiv(Source, Context, prop, ivals)) != AL_FALSE) - { - values[0] = static_cast(ivals[0]); - values[1] = static_cast(ivals[1]); - values[2] = static_cast(ivals[2]); - } - return err; - - case AL_SEC_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_CLOCK_SOFT: - break; /* Double only */ - case AL_STEREO_ANGLES: - break; /* Float/double only */ - } - - ERR("Unexpected property: 0x%04x\n", prop); - SETERR_RETURN(Context, AL_INVALID_ENUM, AL_FALSE, "Invalid source integer64 property 0x%04x", - prop); -} - -} // namespace - -AL_API ALvoid AL_APIENTRY alGenSources(ALsizei n, ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(n < 0) - alSetError(context.get(), AL_INVALID_VALUE, "Generating %d sources", n); - else if(n == 1) - { - ALsource *source = AllocSource(context.get()); - if(source) sources[0] = source->id; - } - else - { - al::vector tempids(n); - auto alloc_end = std::find_if_not(tempids.begin(), tempids.end(), - [&context](ALuint &id) -> bool - { - ALsource *source{AllocSource(context.get())}; - if(!source) return false; - id = source->id; - return true; - } - ); - if(alloc_end != tempids.end()) - alDeleteSources(static_cast(std::distance(tempids.begin(), alloc_end)), - tempids.data()); - else - std::copy(tempids.cbegin(), tempids.cend(), sources); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(n < 0) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Deleting %d sources", n); - - std::lock_guard _{context->SourceLock}; - - /* Check that all Sources are valid */ - const ALuint *sources_end = sources + n; - auto invsrc = std::find_if_not(sources, sources_end, - [&context](ALuint sid) -> bool - { - if(!LookupSource(context.get(), sid)) - { - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", sid); - return false; - } - return true; - } - ); - if(LIKELY(invsrc == sources_end)) - { - /* All good. Delete source IDs. */ - std::for_each(sources, sources_end, - [&context](ALuint sid) -> void - { - ALsource *src{LookupSource(context.get(), sid)}; - if(src) FreeSource(context.get(), src); - } - ); - } -} -END_API_FUNC - -AL_API ALboolean AL_APIENTRY alIsSource(ALuint source) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(LIKELY(context)) - { - std::lock_guard _{context->SourceLock}; - if(LookupSource(context.get(), source) != nullptr) - return AL_TRUE; - } - return AL_FALSE; -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->SourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(FloatValsByProp(param) != 1) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid float property 0x%04x", param); - else - SetSourcefv(Source, context.get(), static_cast(param), &value); -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->SourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(FloatValsByProp(param) != 3) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid 3-float property 0x%04x", param); - else - { - ALfloat fvals[3] = { value1, value2, value3 }; - SetSourcefv(Source, context.get(), static_cast(param), fvals); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->SourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else if(FloatValsByProp(param) < 1) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid float-vector property 0x%04x", param); - else - SetSourcefv(Source, context.get(), static_cast(param), values); -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->SourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(DoubleValsByProp(param) != 1) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid double property 0x%04x", param); - else - { - ALfloat fval = static_cast(value); - SetSourcefv(Source, context.get(), static_cast(param), &fval); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->SourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(DoubleValsByProp(param) != 3) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid 3-double property 0x%04x", param); - else { - ALfloat fvals[3] = {static_cast(value1), - static_cast(value2), - static_cast(value3)}; - SetSourcefv(Source, context.get(), static_cast(param), fvals); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->SourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else - { - ALint count{DoubleValsByProp(param)}; - if(count < 1 || count > 6) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid double-vector property 0x%04x", param); - else - { - ALfloat fvals[6]; - ALint i; - - for(i = 0;i < count;i++) - fvals[i] = static_cast(values[i]); - SetSourcefv(Source, context.get(), static_cast(param), fvals); - } - } -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->SourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(IntValsByProp(param) != 1) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid integer property 0x%04x", param); - else - SetSourceiv(Source, context.get(), static_cast(param), &value); -} -END_API_FUNC - -AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->SourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(IntValsByProp(param) != 3) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid 3-integer property 0x%04x", param); - else - { - ALint ivals[3] = { value1, value2, value3 }; - SetSourceiv(Source, context.get(), static_cast(param), ivals); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->SourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else if(IntValsByProp(param) < 1) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid integer-vector property 0x%04x", param); - else - SetSourceiv(Source, context.get(), static_cast(param), values); -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(Int64ValsByProp(param) != 1) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid integer64 property 0x%04x", param); - else - SetSourcei64v(Source, context.get(), static_cast(param), &value); -} -END_API_FUNC - -AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(Int64ValsByProp(param) != 3) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid 3-integer64 property 0x%04x", param); - else - { - ALint64SOFT i64vals[3] = { value1, value2, value3 }; - SetSourcei64v(Source, context.get(), static_cast(param), i64vals); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else if(Int64ValsByProp(param) < 1) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid integer64-vector property 0x%04x", param); - else - SetSourcei64v(Source, context.get(), static_cast(param), values); -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!value) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else if(FloatValsByProp(param) != 1) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid float property 0x%04x", param); - else - { - ALdouble dval; - if(GetSourcedv(Source, context.get(), static_cast(param), &dval)) - *value = static_cast(dval); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!(value1 && value2 && value3)) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else if(FloatValsByProp(param) != 3) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid 3-float property 0x%04x", param); - else - { - ALdouble dvals[3]; - if(GetSourcedv(Source, context.get(), static_cast(param), dvals)) - { - *value1 = static_cast(dvals[0]); - *value2 = static_cast(dvals[1]); - *value3 = static_cast(dvals[2]); - } - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else - { - ALint count{FloatValsByProp(param)}; - if(count < 1 && count > 6) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid float-vector property 0x%04x", param); - else - { - ALdouble dvals[6]; - if(GetSourcedv(Source, context.get(), static_cast(param), dvals)) - { - for(ALint i{0};i < count;i++) - values[i] = static_cast(dvals[i]); - } - } - } -} -END_API_FUNC - - -AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!value) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else if(DoubleValsByProp(param) != 1) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid double property 0x%04x", param); - else - GetSourcedv(Source, context.get(), static_cast(param), value); -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!(value1 && value2 && value3)) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else if(DoubleValsByProp(param) != 3) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid 3-double property 0x%04x", param); - else - { - ALdouble dvals[3]; - if(GetSourcedv(Source, context.get(), static_cast(param), dvals)) - { - *value1 = dvals[0]; - *value2 = dvals[1]; - *value3 = dvals[2]; - } - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else if(DoubleValsByProp(param) < 1) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid double-vector property 0x%04x", param); - else - GetSourcedv(Source, context.get(), static_cast(param), values); -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!value) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else if(IntValsByProp(param) != 1) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid integer property 0x%04x", param); - else - GetSourceiv(Source, context.get(), static_cast(param), value); -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!(value1 && value2 && value3)) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else if(IntValsByProp(param) != 3) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid 3-integer property 0x%04x", param); - else - { - ALint ivals[3]; - if(GetSourceiv(Source, context.get(), static_cast(param), ivals)) - { - *value1 = ivals[0]; - *value2 = ivals[1]; - *value3 = ivals[2]; - } - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else if(IntValsByProp(param) < 1) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid integer-vector property 0x%04x", param); - else - GetSourceiv(Source, context.get(), static_cast(param), values); -} -END_API_FUNC - - -AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!value) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else if(Int64ValsByProp(param) != 1) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid integer64 property 0x%04x", param); - else - GetSourcei64v(Source, context.get(), static_cast(param), value); -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!(value1 && value2 && value3)) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else if(Int64ValsByProp(param) != 3) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid 3-integer64 property 0x%04x", param); - else - { - ALint64SOFT i64vals[3]; - if(GetSourcei64v(Source, context.get(), static_cast(param), i64vals)) - { - *value1 = i64vals[0]; - *value2 = i64vals[1]; - *value3 = i64vals[2]; - } - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->SourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(UNLIKELY(!Source)) - alSetError(context.get(), AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else if(Int64ValsByProp(param) < 1) - alSetError(context.get(), AL_INVALID_ENUM, "Invalid integer64-vector property 0x%04x", param); - else - GetSourcei64v(Source, context.get(), static_cast(param), values); -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alSourcePlay(ALuint source) -START_API_FUNC -{ alSourcePlayv(1, &source); } -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(n < 0) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Playing %d sources", n); - if(n == 0) return; - - std::lock_guard _{context->SourceLock}; - auto sources_end = sources+n; - auto bad_sid = std::find_if_not(sources, sources_end, - [&context](ALuint sid) -> bool - { - ALsource *source{LookupSource(context.get(), sid)}; - return LIKELY(source != nullptr); - } - ); - if(UNLIKELY(bad_sid != sources+n)) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid source ID %u", *bad_sid); - - ALCdevice *device{context->Device}; - BackendLockGuard __{*device->Backend}; - /* If the device is disconnected, go right to stopped. */ - if(UNLIKELY(!device->Connected.load(std::memory_order_acquire))) - { - /* TODO: Send state change event? */ - std::for_each(sources, sources_end, - [&context](ALuint sid) -> void - { - ALsource *source{LookupSource(context.get(), sid)}; - source->OffsetType = AL_NONE; - source->Offset = 0.0; - source->state = AL_STOPPED; - } - ); - return; - } - - /* Count the number of reusable voices. */ - auto voices_end = context->Voices + context->VoiceCount.load(std::memory_order_relaxed); - auto free_voices = std::accumulate(context->Voices, voices_end, ALsizei{0}, - [](const ALsizei count, const ALvoice *voice) noexcept -> ALsizei - { - if(voice->mPlayState.load(std::memory_order_acquire) == ALvoice::Stopped && - voice->mSourceID.load(std::memory_order_relaxed) == 0u) - return count + 1; - return count; - } - ); - if(UNLIKELY(n > free_voices)) - { - /* Increment the number of voices to handle the request. */ - const ALsizei need_voices{n - free_voices}; - const ALsizei rem_voices{context->MaxVoices - - context->VoiceCount.load(std::memory_order_relaxed)}; - - if(UNLIKELY(need_voices > rem_voices)) - { - /* Allocate more voices to get enough. */ - const ALsizei alloc_count{need_voices - rem_voices}; - if(UNLIKELY(context->MaxVoices > std::numeric_limits::max()-alloc_count)) - SETERR_RETURN(context.get(), AL_OUT_OF_MEMORY,, - "Overflow increasing voice count to %d + %d", context->MaxVoices, alloc_count); - - const ALsizei newcount{context->MaxVoices + alloc_count}; - AllocateVoices(context.get(), newcount, device->NumAuxSends); - } - - context->VoiceCount.fetch_add(need_voices, std::memory_order_relaxed); - } - - auto start_source = [&context,device](ALuint sid) -> void - { - ALsource *source{LookupSource(context.get(), sid)}; - /* Check that there is a queue containing at least one valid, non zero - * length buffer. - */ - ALbufferlistitem *BufferList{source->queue}; - while(BufferList && BufferList->max_samples == 0) - BufferList = BufferList->next.load(std::memory_order_relaxed); - - /* If there's nothing to play, go right to stopped. */ - if(UNLIKELY(!BufferList)) - { - /* NOTE: A source without any playable buffers should not have an - * ALvoice since it shouldn't be in a playing or paused state. So - * there's no need to look up its voice and clear the source. - */ - ALenum oldstate{GetSourceState(source, nullptr)}; - source->OffsetType = AL_NONE; - source->Offset = 0.0; - if(oldstate != AL_STOPPED) - { - source->state = AL_STOPPED; - SendStateChangeEvent(context.get(), source->id, AL_STOPPED); - } - return; - } - - ALvoice *voice{GetSourceVoice(source, context.get())}; - switch(GetSourceState(source, voice)) - { - case AL_PLAYING: - assert(voice != nullptr); - /* A source that's already playing is restarted from the beginning. */ - voice->mCurrentBuffer.store(BufferList, std::memory_order_relaxed); - voice->mPosition.store(0u, std::memory_order_relaxed); - voice->mPositionFrac.store(0, std::memory_order_release); - return; - - case AL_PAUSED: - assert(voice != nullptr); - /* A source that's paused simply resumes. */ - voice->mPlayState.store(ALvoice::Playing, std::memory_order_release); - source->state = AL_PLAYING; - SendStateChangeEvent(context.get(), source->id, AL_PLAYING); - return; - - default: - assert(voice == nullptr); - break; - } - - /* Look for an unused voice to play this source with. */ - auto voices_end = context->Voices + context->VoiceCount.load(std::memory_order_relaxed); - auto voice_iter = std::find_if(context->Voices, voices_end, - [](const ALvoice *voice) noexcept -> bool - { - return voice->mPlayState.load(std::memory_order_acquire) == ALvoice::Stopped && - voice->mSourceID.load(std::memory_order_relaxed) == 0u; - } - ); - assert(voice_iter != voices_end); - auto vidx = static_cast(std::distance(context->Voices, voice_iter)); - voice = *voice_iter; - voice->mPlayState.store(ALvoice::Stopped, std::memory_order_release); - - source->PropsClean.test_and_set(std::memory_order_acquire); - UpdateSourceProps(source, voice, context.get()); - - /* A source that's not playing or paused has any offset applied when it - * starts playing. - */ - if(source->Looping) - voice->mLoopBuffer.store(source->queue, std::memory_order_relaxed); - else - voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); - voice->mCurrentBuffer.store(BufferList, std::memory_order_relaxed); - voice->mPosition.store(0u, std::memory_order_relaxed); - voice->mPositionFrac.store(0, std::memory_order_relaxed); - bool start_fading{false}; - if(ApplyOffset(source, voice) != AL_FALSE) - start_fading = voice->mPosition.load(std::memory_order_relaxed) != 0 || - voice->mPositionFrac.load(std::memory_order_relaxed) != 0 || - voice->mCurrentBuffer.load(std::memory_order_relaxed) != BufferList; - - auto buffers_end = BufferList->buffers + BufferList->num_buffers; - auto buffer = std::find_if(BufferList->buffers, buffers_end, - std::bind(std::not_equal_to{}, _1, nullptr)); - if(buffer != buffers_end) - { - voice->mFrequency = (*buffer)->Frequency; - voice->mFmtChannels = (*buffer)->mFmtChannels; - voice->mNumChannels = ChannelsFromFmt((*buffer)->mFmtChannels); - voice->mSampleSize = BytesFromFmt((*buffer)->mFmtType); - } - - /* Clear the stepping value so the mixer knows not to mix this until - * the update gets applied. - */ - voice->mStep = 0; - - voice->mFlags = start_fading ? VOICE_IS_FADING : 0; - if(source->SourceType == AL_STATIC) voice->mFlags |= VOICE_IS_STATIC; - - /* Don't need to set the VOICE_IS_AMBISONIC flag if the device is - * mixing in first order. No HF scaling is necessary to mix it. - */ - if((voice->mFmtChannels == FmtBFormat2D || voice->mFmtChannels == FmtBFormat3D) && - device->mAmbiOrder > 1) - { - const int *OrderFromChan; - if(voice->mFmtChannels == FmtBFormat2D) - { - static constexpr int Order2DFromChan[MAX_AMBI2D_CHANNELS]{ - 0, 1,1, 2,2, 3,3 - }; - OrderFromChan = Order2DFromChan; - } - else - { - static constexpr int Order3DFromChan[MAX_AMBI_CHANNELS]{ - 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3, - }; - OrderFromChan = Order3DFromChan; - } - - BandSplitter splitter{400.0f / static_cast(device->Frequency)}; - - const auto scales = BFormatDec::GetHFOrderScales(1, device->mAmbiOrder); - auto init_ambi = [scales,&OrderFromChan,&splitter](ALvoice::ResampleData &resdata) -> void - { - resdata.mPrevSamples.fill(0.0f); - resdata.mAmbiScale = scales[*(OrderFromChan++)]; - resdata.mAmbiSplitter = splitter; - }; - std::for_each(voice->mResampleData.begin(), - voice->mResampleData.begin()+voice->mNumChannels, init_ambi); - - voice->mFlags |= VOICE_IS_AMBISONIC; - } - else - { - /* Clear previous samples. */ - auto clear_prevs = [](ALvoice::ResampleData &resdata) -> void - { resdata.mPrevSamples.fill(0.0f); }; - std::for_each(voice->mResampleData.begin(), - voice->mResampleData.begin()+voice->mNumChannels, clear_prevs); - } - - std::fill_n(std::begin(voice->mDirect.Params), voice->mNumChannels, DirectParams{}); - std::for_each(voice->mSend.begin(), voice->mSend.end(), - [voice](ALvoice::SendData &send) -> void - { std::fill_n(std::begin(send.Params), voice->mNumChannels, SendParams{}); } - ); - - if(device->AvgSpeakerDist > 0.0f) - { - const ALfloat w1{SPEEDOFSOUNDMETRESPERSEC / - (device->AvgSpeakerDist * device->Frequency)}; - std::for_each(voice->mDirect.Params+0, voice->mDirect.Params+voice->mNumChannels, - [w1](DirectParams &parms) noexcept -> void - { parms.NFCtrlFilter.init(w1); } - ); - } - - voice->mSourceID.store(source->id, std::memory_order_relaxed); - voice->mPlayState.store(ALvoice::Playing, std::memory_order_release); - source->state = AL_PLAYING; - source->VoiceIdx = vidx; - - SendStateChangeEvent(context.get(), source->id, AL_PLAYING); - }; - std::for_each(sources, sources_end, start_source); -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alSourcePause(ALuint source) -START_API_FUNC -{ alSourcePausev(1, &source); } -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(n < 0) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Pausing %d sources", n); - if(n == 0) return; - - std::lock_guard _{context->SourceLock}; - for(ALsizei i{0};i < n;i++) - { - if(!LookupSource(context.get(), sources[i])) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid source ID %u", sources[i]); - } - - ALCdevice *device{context->Device}; - BackendLockGuard __{*device->Backend}; - for(ALsizei i{0};i < n;i++) - { - ALsource *source{LookupSource(context.get(), sources[i])}; - ALvoice *voice{GetSourceVoice(source, context.get())}; - if(voice) - { - std::atomic_thread_fence(std::memory_order_release); - ALvoice::State oldvstate{ALvoice::Playing}; - voice->mPlayState.compare_exchange_strong(oldvstate, ALvoice::Stopping, - std::memory_order_acq_rel, std::memory_order_acquire); - } - if(GetSourceState(source, voice) == AL_PLAYING) - { - source->state = AL_PAUSED; - SendStateChangeEvent(context.get(), source->id, AL_PAUSED); - } - } -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alSourceStop(ALuint source) -START_API_FUNC -{ alSourceStopv(1, &source); } -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(n < 0) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Stopping %d sources", n); - if(n == 0) return; - - std::lock_guard _{context->SourceLock}; - for(ALsizei i{0};i < n;i++) - { - if(!LookupSource(context.get(), sources[i])) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid source ID %u", sources[i]); - } - - ALCdevice *device{context->Device}; - BackendLockGuard __{*device->Backend}; - for(ALsizei i{0};i < n;i++) - { - ALsource *source{LookupSource(context.get(), sources[i])}; - ALvoice *voice{GetSourceVoice(source, context.get())}; - if(voice != nullptr) - { - voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); - voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); - voice->mSourceID.store(0u, std::memory_order_relaxed); - std::atomic_thread_fence(std::memory_order_release); - ALvoice::State oldvstate{ALvoice::Playing}; - voice->mPlayState.compare_exchange_strong(oldvstate, ALvoice::Stopping, - std::memory_order_acq_rel, std::memory_order_acquire); - voice = nullptr; - } - ALenum oldstate{GetSourceState(source, voice)}; - if(oldstate != AL_INITIAL && oldstate != AL_STOPPED) - { - source->state = AL_STOPPED; - SendStateChangeEvent(context.get(), source->id, AL_STOPPED); - } - source->OffsetType = AL_NONE; - source->Offset = 0.0; - } -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alSourceRewind(ALuint source) -START_API_FUNC -{ alSourceRewindv(1, &source); } -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(n < 0) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Rewinding %d sources", n); - if(n == 0) return; - - std::lock_guard _{context->SourceLock}; - for(ALsizei i{0};i < n;i++) - { - if(!LookupSource(context.get(), sources[i])) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid source ID %u", sources[i]); - } - - ALCdevice *device{context->Device}; - BackendLockGuard __{*device->Backend}; - for(ALsizei i{0};i < n;i++) - { - ALsource *source{LookupSource(context.get(), sources[i])}; - ALvoice *voice{GetSourceVoice(source, context.get())}; - if(voice != nullptr) - { - voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); - voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); - voice->mSourceID.store(0u, std::memory_order_relaxed); - std::atomic_thread_fence(std::memory_order_release); - ALvoice::State oldvstate{ALvoice::Playing}; - voice->mPlayState.compare_exchange_strong(oldvstate, ALvoice::Stopping, - std::memory_order_acq_rel, std::memory_order_acquire); - voice = nullptr; - } - if(GetSourceState(source, voice) != AL_INITIAL) - { - source->state = AL_INITIAL; - SendStateChangeEvent(context.get(), source->id, AL_INITIAL); - } - source->OffsetType = AL_NONE; - source->Offset = 0.0; - } -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alSourceQueueBuffers(ALuint src, ALsizei nb, const ALuint *buffers) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(nb < 0) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Queueing %d buffers", nb); - if(nb == 0) return; - - std::lock_guard _{context->SourceLock}; - ALsource *source{LookupSource(context.get(),src)}; - if(UNLIKELY(!source)) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid source ID %u", src); - - /* Can't queue on a Static Source */ - if(UNLIKELY(source->SourceType == AL_STATIC)) - SETERR_RETURN(context.get(), AL_INVALID_OPERATION,, "Queueing onto static source %u", src); - - /* Check for a valid Buffer, for its frequency and format */ - ALCdevice *device{context->Device}; - ALbuffer *BufferFmt{nullptr}; - ALbufferlistitem *BufferList{source->queue}; - while(BufferList) - { - for(ALsizei i{0};i < BufferList->num_buffers;i++) - { - if((BufferFmt=BufferList->buffers[i]) != nullptr) - break; - } - if(BufferFmt) break; - BufferList = BufferList->next.load(std::memory_order_relaxed); - } - - std::unique_lock buflock{device->BufferLock}; - ALbufferlistitem *BufferListStart{nullptr}; - BufferList = nullptr; - for(ALsizei i{0};i < nb;i++) - { - ALbuffer *buffer{nullptr}; - if(buffers[i] && (buffer=LookupBuffer(device, buffers[i])) == nullptr) - { - alSetError(context.get(), AL_INVALID_NAME, "Queueing invalid buffer ID %u", - buffers[i]); - goto buffer_error; - } - - if(!BufferListStart) - { - BufferListStart = static_cast(al_calloc(DEF_ALIGN, - ALbufferlistitem::Sizeof(1u))); - BufferList = BufferListStart; - } - else - { - auto item = static_cast(al_calloc(DEF_ALIGN, - ALbufferlistitem::Sizeof(1u))); - BufferList->next.store(item, std::memory_order_relaxed); - BufferList = item; - } - BufferList->next.store(nullptr, std::memory_order_relaxed); - BufferList->max_samples = buffer ? buffer->SampleLen : 0; - BufferList->num_buffers = 1; - BufferList->buffers[0] = buffer; - if(!buffer) continue; - - IncrementRef(&buffer->ref); - - if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) - { - alSetError(context.get(), AL_INVALID_OPERATION, - "Queueing non-persistently mapped buffer %u", buffer->id); - goto buffer_error; - } - - if(BufferFmt == nullptr) - BufferFmt = buffer; - else if(BufferFmt->Frequency != buffer->Frequency || - BufferFmt->mFmtChannels != buffer->mFmtChannels || - BufferFmt->OriginalType != buffer->OriginalType) - { - alSetError(context.get(), AL_INVALID_OPERATION, - "Queueing buffer with mismatched format"); - - buffer_error: - /* A buffer failed (invalid ID or format), so unlock and release - * each buffer we had. */ - while(BufferListStart) - { - ALbufferlistitem *next = BufferListStart->next.load(std::memory_order_relaxed); - for(i = 0;i < BufferListStart->num_buffers;i++) - { - if((buffer=BufferListStart->buffers[i]) != nullptr) - DecrementRef(&buffer->ref); - } - al_free(BufferListStart); - BufferListStart = next; - } - return; - } - } - /* All buffers good. */ - buflock.unlock(); - - /* Source is now streaming */ - source->SourceType = AL_STREAMING; - - if(!(BufferList=source->queue)) - source->queue = BufferListStart; - else - { - ALbufferlistitem *next; - while((next=BufferList->next.load(std::memory_order_relaxed)) != nullptr) - BufferList = next; - BufferList->next.store(BufferListStart, std::memory_order_release); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb, const ALuint *buffers) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(nb < 0) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Queueing %d buffer layers", nb); - if(nb == 0) return; - - std::lock_guard _{context->SourceLock}; - ALsource *source{LookupSource(context.get(),src)}; - if(UNLIKELY(!source)) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid source ID %u", src); - - /* Can't queue on a Static Source */ - if(UNLIKELY(source->SourceType == AL_STATIC)) - SETERR_RETURN(context.get(), AL_INVALID_OPERATION,, "Queueing onto static source %u", src); - - /* Check for a valid Buffer, for its frequency and format */ - ALCdevice *device{context->Device}; - ALbuffer *BufferFmt{nullptr}; - ALbufferlistitem *BufferList{source->queue}; - while(BufferList) - { - for(ALsizei i{0};i < BufferList->num_buffers;i++) - { - if((BufferFmt=BufferList->buffers[i]) != nullptr) - break; - } - if(BufferFmt) break; - BufferList = BufferList->next.load(std::memory_order_relaxed); - } - - std::unique_lock buflock{device->BufferLock}; - auto BufferListStart = static_cast(al_calloc(DEF_ALIGN, - ALbufferlistitem::Sizeof(nb))); - BufferList = BufferListStart; - BufferList->next.store(nullptr, std::memory_order_relaxed); - BufferList->max_samples = 0; - BufferList->num_buffers = 0; - - for(ALsizei i{0};i < nb;i++) - { - ALbuffer *buffer{nullptr}; - if(buffers[i] && (buffer=LookupBuffer(device, buffers[i])) == nullptr) - { - alSetError(context.get(), AL_INVALID_NAME, "Queueing invalid buffer ID %u", - buffers[i]); - goto buffer_error; - } - - BufferList->buffers[BufferList->num_buffers++] = buffer; - if(!buffer) continue; - - IncrementRef(&buffer->ref); - - BufferList->max_samples = maxi(BufferList->max_samples, buffer->SampleLen); - - if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) - { - alSetError(context.get(), AL_INVALID_OPERATION, - "Queueing non-persistently mapped buffer %u", buffer->id); - goto buffer_error; - } - - if(BufferFmt == nullptr) - BufferFmt = buffer; - else if(BufferFmt->Frequency != buffer->Frequency || - BufferFmt->mFmtChannels != buffer->mFmtChannels || - BufferFmt->OriginalType != buffer->OriginalType) - { - alSetError(context.get(), AL_INVALID_OPERATION, - "Queueing buffer with mismatched format"); - - buffer_error: - /* A buffer failed (invalid ID or format), so unlock and release - * each buffer we had. */ - while(BufferListStart) - { - ALbufferlistitem *next{BufferListStart->next.load(std::memory_order_relaxed)}; - for(i = 0;i < BufferListStart->num_buffers;i++) - { - if((buffer=BufferListStart->buffers[i]) != nullptr) - DecrementRef(&buffer->ref); - } - al_free(BufferListStart); - BufferListStart = next; - } - return; - } - } - /* All buffers good. */ - buflock.unlock(); - - /* Source is now streaming */ - source->SourceType = AL_STREAMING; - - if(!(BufferList=source->queue)) - source->queue = BufferListStart; - else - { - ALbufferlistitem *next; - while((next=BufferList->next.load(std::memory_order_relaxed)) != nullptr) - BufferList = next; - BufferList->next.store(BufferListStart, std::memory_order_release); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alSourceUnqueueBuffers(ALuint src, ALsizei nb, ALuint *buffers) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(nb < 0) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Unqueueing %d buffers", nb); - if(nb == 0) return; - - std::lock_guard _{context->SourceLock}; - ALsource *source{LookupSource(context.get(),src)}; - if(UNLIKELY(!source)) - SETERR_RETURN(context.get(), AL_INVALID_NAME,, "Invalid source ID %u", src); - - if(UNLIKELY(source->Looping)) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Unqueueing from looping source %u", src); - if(UNLIKELY(source->SourceType != AL_STREAMING)) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, - "Unqueueing from a non-streaming source %u", src); - - /* Make sure enough buffers have been processed to unqueue. */ - ALbufferlistitem *BufferList{source->queue}; - ALvoice *voice{GetSourceVoice(source, context.get())}; - ALbufferlistitem *Current{nullptr}; - if(voice) - Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); - else if(source->state == AL_INITIAL) - Current = BufferList; - if(UNLIKELY(BufferList == Current)) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Unqueueing pending buffers"); - - ALsizei i{BufferList->num_buffers}; - while(i < nb) - { - /* If the next bufferlist to check is NULL or is the current one, it's - * trying to unqueue pending buffers. - */ - ALbufferlistitem *next{BufferList->next.load(std::memory_order_relaxed)}; - if(UNLIKELY(!next) || UNLIKELY(next == Current)) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Unqueueing pending buffers"); - BufferList = next; - - i += BufferList->num_buffers; - } - - while(nb > 0) - { - ALbufferlistitem *head{source->queue}; - ALbufferlistitem *next{head->next.load(std::memory_order_relaxed)}; - for(i = 0;i < head->num_buffers && nb > 0;i++,nb--) - { - ALbuffer *buffer{head->buffers[i]}; - if(!buffer) - *(buffers++) = 0; - else - { - *(buffers++) = buffer->id; - DecrementRef(&buffer->ref); - } - } - if(i < head->num_buffers) - { - /* This head has some buffers left over, so move them to the front - * and update the sample and buffer count. - */ - ALsizei max_length{0}; - ALsizei j{0}; - while(i < head->num_buffers) - { - ALbuffer *buffer{head->buffers[i++]}; - if(buffer) max_length = maxi(max_length, buffer->SampleLen); - head->buffers[j++] = buffer; - } - head->max_samples = max_length; - head->num_buffers = j; - break; - } - - /* Otherwise, free this item and set the source queue head to the next - * one. - */ - al_free(head); - source->queue = next; - } -} -END_API_FUNC - - -ALsource::ALsource(ALsizei num_sends) -{ - InnerAngle = 360.0f; - OuterAngle = 360.0f; - Pitch = 1.0f; - Position[0] = 0.0f; - Position[1] = 0.0f; - Position[2] = 0.0f; - Velocity[0] = 0.0f; - Velocity[1] = 0.0f; - Velocity[2] = 0.0f; - Direction[0] = 0.0f; - Direction[1] = 0.0f; - Direction[2] = 0.0f; - OrientAt[0] = 0.0f; - OrientAt[1] = 0.0f; - OrientAt[2] = -1.0f; - OrientUp[0] = 0.0f; - OrientUp[1] = 1.0f; - OrientUp[2] = 0.0f; - RefDistance = 1.0f; - MaxDistance = std::numeric_limits::max(); - RolloffFactor = 1.0f; - Gain = 1.0f; - MinGain = 0.0f; - MaxGain = 1.0f; - OuterGain = 0.0f; - OuterGainHF = 1.0f; - - DryGainHFAuto = AL_TRUE; - WetGainAuto = AL_TRUE; - WetGainHFAuto = AL_TRUE; - AirAbsorptionFactor = 0.0f; - RoomRolloffFactor = 0.0f; - DopplerFactor = 1.0f; - HeadRelative = AL_FALSE; - Looping = AL_FALSE; - mDistanceModel = DistanceModel::Default; - mResampler = ResamplerDefault; - DirectChannels = AL_FALSE; - mSpatialize = SpatializeAuto; - - StereoPan[0] = Deg2Rad( 30.0f); - StereoPan[1] = Deg2Rad(-30.0f); - - Radius = 0.0f; - - Direct.Gain = 1.0f; - Direct.GainHF = 1.0f; - Direct.HFReference = LOWPASSFREQREF; - Direct.GainLF = 1.0f; - Direct.LFReference = HIGHPASSFREQREF; - Send.resize(num_sends); - for(auto &send : Send) - { - send.Slot = nullptr; - send.Gain = 1.0f; - send.GainHF = 1.0f; - send.HFReference = LOWPASSFREQREF; - send.GainLF = 1.0f; - send.LFReference = HIGHPASSFREQREF; - } - - Offset = 0.0; - OffsetType = AL_NONE; - SourceType = AL_UNDETERMINED; - state = AL_INITIAL; - - queue = nullptr; - - PropsClean.test_and_set(std::memory_order_relaxed); - - VoiceIdx = -1; -} - -ALsource::~ALsource() -{ - ALbufferlistitem *BufferList{queue}; - while(BufferList != nullptr) - { - ALbufferlistitem *next{BufferList->next.load(std::memory_order_relaxed)}; - for(ALsizei i{0};i < BufferList->num_buffers;i++) - { - if(BufferList->buffers[i]) - DecrementRef(&BufferList->buffers[i]->ref); - } - al_free(BufferList); - BufferList = next; - } - queue = nullptr; - - std::for_each(Send.begin(), Send.end(), - [](ALsource::SendData &send) -> void - { - if(send.Slot) - DecrementRef(&send.Slot->ref); - send.Slot = nullptr; - } - ); -} - -void UpdateAllSourceProps(ALCcontext *context) -{ - auto voices_end = context->Voices + context->VoiceCount.load(std::memory_order_relaxed); - std::for_each(context->Voices, voices_end, - [context](ALvoice *voice) -> void - { - ALuint sid{voice->mSourceID.load(std::memory_order_acquire)}; - ALsource *source = sid ? LookupSource(context, sid) : nullptr; - if(source && !source->PropsClean.test_and_set(std::memory_order_acq_rel)) - UpdateSourceProps(source, voice, context); - } - ); -} - -SourceSubList::~SourceSubList() -{ - uint64_t usemask{~FreeMask}; - while(usemask) - { - ALsizei idx{CTZ64(usemask)}; - Sources[idx].~ALsource(); - usemask &= ~(1_u64 << idx); - } - FreeMask = ~usemask; - al_free(Sources); - Sources = nullptr; -} diff --git a/modules/openal-soft/OpenAL32/alState.cpp b/modules/openal-soft/OpenAL32/alState.cpp deleted file mode 100644 index 3f6a038..0000000 --- a/modules/openal-soft/OpenAL32/alState.cpp +++ /dev/null @@ -1,849 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2000 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "version.h" - -#include -#include - -#include "alMain.h" -#include "alcontext.h" -#include "alu.h" -#include "alError.h" -#include "alexcpt.h" - -#include "backends/base.h" - - -namespace { - -constexpr ALchar alVendor[] = "OpenAL Community"; -constexpr ALchar alVersion[] = "1.1 ALSOFT " ALSOFT_VERSION; -constexpr ALchar alRenderer[] = "OpenAL Soft"; - -// Error Messages -constexpr ALchar alNoError[] = "No Error"; -constexpr ALchar alErrInvalidName[] = "Invalid Name"; -constexpr ALchar alErrInvalidEnum[] = "Invalid Enum"; -constexpr ALchar alErrInvalidValue[] = "Invalid Value"; -constexpr ALchar alErrInvalidOp[] = "Invalid Operation"; -constexpr ALchar alErrOutOfMemory[] = "Out of Memory"; - -/* Resampler strings */ -constexpr ALchar alPointResampler[] = "Nearest"; -constexpr ALchar alLinearResampler[] = "Linear"; -constexpr ALchar alCubicResampler[] = "Cubic"; -constexpr ALchar alBSinc12Resampler[] = "11th order Sinc"; -constexpr ALchar alBSinc24Resampler[] = "23rd order Sinc"; - -} // namespace - -/* WARNING: Non-standard export! Not part of any extension, or exposed in the - * alcFunctions list. - */ -extern "C" AL_API const ALchar* AL_APIENTRY alsoft_get_version(void) -START_API_FUNC -{ - const char *spoof{getenv("ALSOFT_SPOOF_VERSION")}; - if(spoof && spoof[0] != '\0') return spoof; - return ALSOFT_VERSION; -} -END_API_FUNC - -#define DO_UPDATEPROPS() do { \ - if(!context->DeferUpdates.load(std::memory_order_acquire)) \ - UpdateContextProps(context.get()); \ - else \ - context->PropsClean.clear(std::memory_order_release); \ -} while(0) - - -AL_API ALvoid AL_APIENTRY alEnable(ALenum capability) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - switch(capability) - { - case AL_SOURCE_DISTANCE_MODEL: - context->SourceDistanceModel = AL_TRUE; - DO_UPDATEPROPS(); - break; - - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid enable property 0x%04x", capability); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alDisable(ALenum capability) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - switch(capability) - { - case AL_SOURCE_DISTANCE_MODEL: - context->SourceDistanceModel = AL_FALSE; - DO_UPDATEPROPS(); - break; - - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid disable property 0x%04x", capability); - } -} -END_API_FUNC - -AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return AL_FALSE; - - std::lock_guard _{context->PropLock}; - ALboolean value{AL_FALSE}; - switch(capability) - { - case AL_SOURCE_DISTANCE_MODEL: - value = context->SourceDistanceModel; - break; - - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability); - } - - return value; -} -END_API_FUNC - -AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum pname) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return AL_FALSE; - - std::lock_guard _{context->PropLock}; - ALboolean value{AL_FALSE}; - switch(pname) - { - case AL_DOPPLER_FACTOR: - if(context->DopplerFactor != 0.0f) - value = AL_TRUE; - break; - - case AL_DOPPLER_VELOCITY: - if(context->DopplerVelocity != 0.0f) - value = AL_TRUE; - break; - - case AL_DISTANCE_MODEL: - if(context->mDistanceModel == DistanceModel::Default) - value = AL_TRUE; - break; - - case AL_SPEED_OF_SOUND: - if(context->SpeedOfSound != 0.0f) - value = AL_TRUE; - break; - - case AL_DEFERRED_UPDATES_SOFT: - if(context->DeferUpdates.load(std::memory_order_acquire)) - value = AL_TRUE; - break; - - case AL_GAIN_LIMIT_SOFT: - if(GAIN_MIX_MAX/context->GainBoost != 0.0f) - value = AL_TRUE; - break; - - case AL_NUM_RESAMPLERS_SOFT: - /* Always non-0. */ - value = AL_TRUE; - break; - - case AL_DEFAULT_RESAMPLER_SOFT: - value = ResamplerDefault ? AL_TRUE : AL_FALSE; - break; - - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid boolean property 0x%04x", pname); - } - - return value; -} -END_API_FUNC - -AL_API ALdouble AL_APIENTRY alGetDouble(ALenum pname) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return 0.0; - - std::lock_guard _{context->PropLock}; - ALdouble value{0.0}; - switch(pname) - { - case AL_DOPPLER_FACTOR: - value = static_cast(context->DopplerFactor); - break; - - case AL_DOPPLER_VELOCITY: - value = static_cast(context->DopplerVelocity); - break; - - case AL_DISTANCE_MODEL: - value = static_cast(context->mDistanceModel); - break; - - case AL_SPEED_OF_SOUND: - value = static_cast(context->SpeedOfSound); - break; - - case AL_DEFERRED_UPDATES_SOFT: - if(context->DeferUpdates.load(std::memory_order_acquire)) - value = static_cast(AL_TRUE); - break; - - case AL_GAIN_LIMIT_SOFT: - value = static_castGAIN_MIX_MAX/context->GainBoost; - break; - - case AL_NUM_RESAMPLERS_SOFT: - value = static_cast(ResamplerMax + 1); - break; - - case AL_DEFAULT_RESAMPLER_SOFT: - value = static_cast(ResamplerDefault); - break; - - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid double property 0x%04x", pname); - } - - return value; -} -END_API_FUNC - -AL_API ALfloat AL_APIENTRY alGetFloat(ALenum pname) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return 0.0f; - - std::lock_guard _{context->PropLock}; - ALfloat value{0.0f}; - switch(pname) - { - case AL_DOPPLER_FACTOR: - value = context->DopplerFactor; - break; - - case AL_DOPPLER_VELOCITY: - value = context->DopplerVelocity; - break; - - case AL_DISTANCE_MODEL: - value = static_cast(context->mDistanceModel); - break; - - case AL_SPEED_OF_SOUND: - value = context->SpeedOfSound; - break; - - case AL_DEFERRED_UPDATES_SOFT: - if(context->DeferUpdates.load(std::memory_order_acquire)) - value = static_cast(AL_TRUE); - break; - - case AL_GAIN_LIMIT_SOFT: - value = GAIN_MIX_MAX/context->GainBoost; - break; - - case AL_NUM_RESAMPLERS_SOFT: - value = static_cast(ResamplerMax + 1); - break; - - case AL_DEFAULT_RESAMPLER_SOFT: - value = static_cast(ResamplerDefault); - break; - - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid float property 0x%04x", pname); - } - - return value; -} -END_API_FUNC - -AL_API ALint AL_APIENTRY alGetInteger(ALenum pname) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return 0; - - std::lock_guard _{context->PropLock}; - ALint value{0}; - switch(pname) - { - case AL_DOPPLER_FACTOR: - value = static_cast(context->DopplerFactor); - break; - - case AL_DOPPLER_VELOCITY: - value = static_cast(context->DopplerVelocity); - break; - - case AL_DISTANCE_MODEL: - value = static_cast(context->mDistanceModel); - break; - - case AL_SPEED_OF_SOUND: - value = static_cast(context->SpeedOfSound); - break; - - case AL_DEFERRED_UPDATES_SOFT: - if(context->DeferUpdates.load(std::memory_order_acquire)) - value = static_cast(AL_TRUE); - break; - - case AL_GAIN_LIMIT_SOFT: - value = static_cast(GAIN_MIX_MAX/context->GainBoost); - break; - - case AL_NUM_RESAMPLERS_SOFT: - value = ResamplerMax + 1; - break; - - case AL_DEFAULT_RESAMPLER_SOFT: - value = ResamplerDefault; - break; - - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname); - } - - return value; -} -END_API_FUNC - -extern "C" AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return 0; - - std::lock_guard _{context->PropLock}; - ALint64SOFT value{0}; - switch(pname) - { - case AL_DOPPLER_FACTOR: - value = (ALint64SOFT)context->DopplerFactor; - break; - - case AL_DOPPLER_VELOCITY: - value = (ALint64SOFT)context->DopplerVelocity; - break; - - case AL_DISTANCE_MODEL: - value = (ALint64SOFT)context->mDistanceModel; - break; - - case AL_SPEED_OF_SOUND: - value = (ALint64SOFT)context->SpeedOfSound; - break; - - case AL_DEFERRED_UPDATES_SOFT: - if(context->DeferUpdates.load(std::memory_order_acquire)) - value = (ALint64SOFT)AL_TRUE; - break; - - case AL_GAIN_LIMIT_SOFT: - value = (ALint64SOFT)(GAIN_MIX_MAX/context->GainBoost); - break; - - case AL_NUM_RESAMPLERS_SOFT: - value = (ALint64SOFT)(ResamplerMax + 1); - break; - - case AL_DEFAULT_RESAMPLER_SOFT: - value = (ALint64SOFT)ResamplerDefault; - break; - - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid integer64 property 0x%04x", pname); - } - - return value; -} -END_API_FUNC - -AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return nullptr; - - std::lock_guard _{context->PropLock}; - void *value{nullptr}; - switch(pname) - { - case AL_EVENT_CALLBACK_FUNCTION_SOFT: - value = reinterpret_cast(context->EventCb); - break; - - case AL_EVENT_CALLBACK_USER_PARAM_SOFT: - value = context->EventParam; - break; - - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid pointer property 0x%04x", pname); - } - - return value; -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetBooleanv(ALenum pname, ALboolean *values) -START_API_FUNC -{ - if(values) - { - switch(pname) - { - case AL_DOPPLER_FACTOR: - case AL_DOPPLER_VELOCITY: - case AL_DISTANCE_MODEL: - case AL_SPEED_OF_SOUND: - case AL_DEFERRED_UPDATES_SOFT: - case AL_GAIN_LIMIT_SOFT: - case AL_NUM_RESAMPLERS_SOFT: - case AL_DEFAULT_RESAMPLER_SOFT: - values[0] = alGetBoolean(pname); - return; - } - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid boolean-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetDoublev(ALenum pname, ALdouble *values) -START_API_FUNC -{ - if(values) - { - switch(pname) - { - case AL_DOPPLER_FACTOR: - case AL_DOPPLER_VELOCITY: - case AL_DISTANCE_MODEL: - case AL_SPEED_OF_SOUND: - case AL_DEFERRED_UPDATES_SOFT: - case AL_GAIN_LIMIT_SOFT: - case AL_NUM_RESAMPLERS_SOFT: - case AL_DEFAULT_RESAMPLER_SOFT: - values[0] = alGetDouble(pname); - return; - } - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid double-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetFloatv(ALenum pname, ALfloat *values) -START_API_FUNC -{ - if(values) - { - switch(pname) - { - case AL_DOPPLER_FACTOR: - case AL_DOPPLER_VELOCITY: - case AL_DISTANCE_MODEL: - case AL_SPEED_OF_SOUND: - case AL_DEFERRED_UPDATES_SOFT: - case AL_GAIN_LIMIT_SOFT: - case AL_NUM_RESAMPLERS_SOFT: - case AL_DEFAULT_RESAMPLER_SOFT: - values[0] = alGetFloat(pname); - return; - } - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid float-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alGetIntegerv(ALenum pname, ALint *values) -START_API_FUNC -{ - if(values) - { - switch(pname) - { - case AL_DOPPLER_FACTOR: - case AL_DOPPLER_VELOCITY: - case AL_DISTANCE_MODEL: - case AL_SPEED_OF_SOUND: - case AL_DEFERRED_UPDATES_SOFT: - case AL_GAIN_LIMIT_SOFT: - case AL_NUM_RESAMPLERS_SOFT: - case AL_DEFAULT_RESAMPLER_SOFT: - values[0] = alGetInteger(pname); - return; - } - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid integer-vector property 0x%04x", pname); - } -} -END_API_FUNC - -extern "C" AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) -START_API_FUNC -{ - if(values) - { - switch(pname) - { - case AL_DOPPLER_FACTOR: - case AL_DOPPLER_VELOCITY: - case AL_DISTANCE_MODEL: - case AL_SPEED_OF_SOUND: - case AL_DEFERRED_UPDATES_SOFT: - case AL_GAIN_LIMIT_SOFT: - case AL_NUM_RESAMPLERS_SOFT: - case AL_DEFAULT_RESAMPLER_SOFT: - values[0] = alGetInteger64SOFT(pname); - return; - } - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid integer64-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values) -START_API_FUNC -{ - if(values) - { - switch(pname) - { - case AL_EVENT_CALLBACK_FUNCTION_SOFT: - case AL_EVENT_CALLBACK_USER_PARAM_SOFT: - values[0] = alGetPointerSOFT(pname); - return; - } - } - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid pointer-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API const ALchar* AL_APIENTRY alGetString(ALenum pname) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return nullptr; - - const ALchar *value{nullptr}; - switch(pname) - { - case AL_VENDOR: - value = alVendor; - break; - - case AL_VERSION: - value = alVersion; - break; - - case AL_RENDERER: - value = alRenderer; - break; - - case AL_EXTENSIONS: - value = context->ExtensionList; - break; - - case AL_NO_ERROR: - value = alNoError; - break; - - case AL_INVALID_NAME: - value = alErrInvalidName; - break; - - case AL_INVALID_ENUM: - value = alErrInvalidEnum; - break; - - case AL_INVALID_VALUE: - value = alErrInvalidValue; - break; - - case AL_INVALID_OPERATION: - value = alErrInvalidOp; - break; - - case AL_OUT_OF_MEMORY: - value = alErrOutOfMemory; - break; - - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid string property 0x%04x", pname); - } - return value; -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alDopplerFactor(ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(!(value >= 0.0f && std::isfinite(value))) - alSetError(context.get(), AL_INVALID_VALUE, "Doppler factor %f out of range", value); - else - { - std::lock_guard _{context->PropLock}; - context->DopplerFactor = value; - DO_UPDATEPROPS(); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alDopplerVelocity(ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if((context->EnabledEvts.load(std::memory_order_relaxed)&EventType_Deprecated)) - { - static constexpr ALCchar msg[] = - "alDopplerVelocity is deprecated in AL1.1, use alSpeedOfSound"; - const ALsizei msglen = static_cast(strlen(msg)); - std::lock_guard _{context->EventCbLock}; - ALbitfieldSOFT enabledevts{context->EnabledEvts.load(std::memory_order_relaxed)}; - if((enabledevts&EventType_Deprecated) && context->EventCb) - (*context->EventCb)(AL_EVENT_TYPE_DEPRECATED_SOFT, 0, 0, msglen, msg, - context->EventParam); - } - - if(!(value >= 0.0f && std::isfinite(value))) - alSetError(context.get(), AL_INVALID_VALUE, "Doppler velocity %f out of range", value); - else - { - std::lock_guard _{context->PropLock}; - context->DopplerVelocity = value; - DO_UPDATEPROPS(); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alSpeedOfSound(ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(!(value > 0.0f && std::isfinite(value))) - alSetError(context.get(), AL_INVALID_VALUE, "Speed of sound %f out of range", value); - else - { - std::lock_guard _{context->PropLock}; - context->SpeedOfSound = value; - DO_UPDATEPROPS(); - } -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alDistanceModel(ALenum value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(!(value == AL_INVERSE_DISTANCE || value == AL_INVERSE_DISTANCE_CLAMPED || - value == AL_LINEAR_DISTANCE || value == AL_LINEAR_DISTANCE_CLAMPED || - value == AL_EXPONENT_DISTANCE || value == AL_EXPONENT_DISTANCE_CLAMPED || - value == AL_NONE)) - alSetError(context.get(), AL_INVALID_VALUE, "Distance model 0x%04x out of range", value); - else - { - std::lock_guard _{context->PropLock}; - context->mDistanceModel = static_cast(value); - if(!context->SourceDistanceModel) - DO_UPDATEPROPS(); - } -} -END_API_FUNC - - -AL_API ALvoid AL_APIENTRY alDeferUpdatesSOFT(void) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCcontext_DeferUpdates(context.get()); -} -END_API_FUNC - -AL_API ALvoid AL_APIENTRY alProcessUpdatesSOFT(void) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - ALCcontext_ProcessUpdates(context.get()); -} -END_API_FUNC - - -AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index) -START_API_FUNC -{ - const char *ResamplerNames[] = { - alPointResampler, alLinearResampler, - alCubicResampler, alBSinc12Resampler, - alBSinc24Resampler, - }; - static_assert(COUNTOF(ResamplerNames) == ResamplerMax+1, "Incorrect ResamplerNames list"); - - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return nullptr; - - const ALchar *value{nullptr}; - switch(pname) - { - case AL_RESAMPLER_NAME_SOFT: - if(index < 0 || static_cast(index) >= COUNTOF(ResamplerNames)) - alSetError(context.get(), AL_INVALID_VALUE, "Resampler name index %d out of range", - index); - else - value = ResamplerNames[index]; - break; - - default: - alSetError(context.get(), AL_INVALID_VALUE, "Invalid string indexed property"); - } - return value; -} -END_API_FUNC - - -void UpdateContextProps(ALCcontext *context) -{ - /* Get an unused proprty container, or allocate a new one as needed. */ - ALcontextProps *props{context->FreeContextProps.load(std::memory_order_acquire)}; - if(!props) - props = static_cast(al_calloc(16, sizeof(*props))); - else - { - ALcontextProps *next; - do { - next = props->next.load(std::memory_order_relaxed); - } while(context->FreeContextProps.compare_exchange_weak(props, next, - std::memory_order_seq_cst, std::memory_order_acquire) == 0); - } - - /* Copy in current property values. */ - props->MetersPerUnit = context->MetersPerUnit; - - props->DopplerFactor = context->DopplerFactor; - props->DopplerVelocity = context->DopplerVelocity; - props->SpeedOfSound = context->SpeedOfSound; - - props->SourceDistanceModel = context->SourceDistanceModel; - props->mDistanceModel = context->mDistanceModel; - - /* Set the new container for updating internal parameters. */ - props = context->Update.exchange(props, std::memory_order_acq_rel); - if(props) - { - /* If there was an unused update container, put it back in the - * freelist. - */ - AtomicReplaceHead(context->FreeContextProps, props); - } -} diff --git a/modules/openal-soft/OpenAL32/event.cpp b/modules/openal-soft/OpenAL32/event.cpp deleted file mode 100644 index f01227e..0000000 --- a/modules/openal-soft/OpenAL32/event.cpp +++ /dev/null @@ -1,204 +0,0 @@ - -#include "config.h" - -#include - -#include "AL/alc.h" -#include "AL/al.h" -#include "AL/alext.h" - -#include "alMain.h" -#include "alcontext.h" -#include "alError.h" -#include "alAuxEffectSlot.h" -#include "ringbuffer.h" -#include "threads.h" -#include "alexcpt.h" - - -static int EventThread(ALCcontext *context) -{ - RingBuffer *ring{context->AsyncEvents.get()}; - bool quitnow{false}; - while(LIKELY(!quitnow)) - { - auto evt_data = ring->getReadVector().first; - if(evt_data.len == 0) - { - context->EventSem.wait(); - continue; - } - - std::lock_guard _{context->EventCbLock}; - do { - auto &evt = *reinterpret_cast(evt_data.buf); - evt_data.buf += sizeof(AsyncEvent); - evt_data.len -= 1; - /* This automatically destructs the event object and advances the - * ringbuffer's read offset at the end of scope. - */ - const struct EventAutoDestructor { - AsyncEvent &evt_; - RingBuffer *ring_; - ~EventAutoDestructor() - { - evt_.~AsyncEvent(); - ring_->readAdvance(1); - } - } _{evt, ring}; - - quitnow = evt.EnumType == EventType_KillThread; - if(UNLIKELY(quitnow)) break; - - if(evt.EnumType == EventType_ReleaseEffectState) - { - evt.u.mEffectState->DecRef(); - continue; - } - - ALbitfieldSOFT enabledevts{context->EnabledEvts.load(std::memory_order_acquire)}; - if(!context->EventCb) continue; - - if(evt.EnumType == EventType_SourceStateChange) - { - if(!(enabledevts&EventType_SourceStateChange)) - continue; - std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)}; - msg += " state has changed to "; - msg += (evt.u.srcstate.state==AL_INITIAL) ? "AL_INITIAL" : - (evt.u.srcstate.state==AL_PLAYING) ? "AL_PLAYING" : - (evt.u.srcstate.state==AL_PAUSED) ? "AL_PAUSED" : - (evt.u.srcstate.state==AL_STOPPED) ? "AL_STOPPED" : ""; - context->EventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id, - evt.u.srcstate.state, static_cast(msg.length()), msg.c_str(), - context->EventParam - ); - } - else if(evt.EnumType == EventType_BufferCompleted) - { - if(!(enabledevts&EventType_BufferCompleted)) - continue; - std::string msg{std::to_string(evt.u.bufcomp.count)}; - if(evt.u.bufcomp.count == 1) msg += " buffer completed"; - else msg += " buffers completed"; - context->EventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.u.bufcomp.id, - evt.u.bufcomp.count, static_cast(msg.length()), msg.c_str(), - context->EventParam - ); - } - else if((enabledevts&evt.EnumType) == evt.EnumType) - context->EventCb(evt.u.user.type, evt.u.user.id, evt.u.user.param, - static_cast(strlen(evt.u.user.msg)), evt.u.user.msg, - context->EventParam - ); - } while(evt_data.len != 0); - } - return 0; -} - -void StartEventThrd(ALCcontext *ctx) -{ - try { - ctx->EventThread = std::thread(EventThread, ctx); - } - catch(std::exception& e) { - ERR("Failed to start event thread: %s\n", e.what()); - } - catch(...) { - ERR("Failed to start event thread! Expect problems.\n"); - } -} - -void StopEventThrd(ALCcontext *ctx) -{ - static constexpr AsyncEvent kill_evt{EventType_KillThread}; - RingBuffer *ring{ctx->AsyncEvents.get()}; - auto evt_data = ring->getWriteVector().first; - if(evt_data.len == 0) - { - do { - std::this_thread::yield(); - evt_data = ring->getWriteVector().first; - } while(evt_data.len == 0); - } - new (evt_data.buf) AsyncEvent{kill_evt}; - ring->writeAdvance(1); - - ctx->EventSem.post(); - if(ctx->EventThread.joinable()) - ctx->EventThread.join(); -} - -AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - if(count < 0) SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Controlling %d events", count); - if(count == 0) return; - if(!types) SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "NULL pointer"); - - ALbitfieldSOFT flags{0}; - const ALenum *types_end = types+count; - auto bad_type = std::find_if_not(types, types_end, - [&flags](ALenum type) noexcept -> bool - { - if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT) - flags |= EventType_BufferCompleted; - else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT) - flags |= EventType_SourceStateChange; - else if(type == AL_EVENT_TYPE_ERROR_SOFT) - flags |= EventType_Error; - else if(type == AL_EVENT_TYPE_PERFORMANCE_SOFT) - flags |= EventType_Performance; - else if(type == AL_EVENT_TYPE_DEPRECATED_SOFT) - flags |= EventType_Deprecated; - else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT) - flags |= EventType_Disconnected; - else - return false; - return true; - } - ); - if(bad_type != types_end) - SETERR_RETURN(context.get(), AL_INVALID_ENUM,, "Invalid event type 0x%04x", *bad_type); - - if(enable) - { - ALbitfieldSOFT enabledevts{context->EnabledEvts.load(std::memory_order_relaxed)}; - while(context->EnabledEvts.compare_exchange_weak(enabledevts, enabledevts|flags, - std::memory_order_acq_rel, std::memory_order_acquire) == 0) - { - /* enabledevts is (re-)filled with the current value on failure, so - * just try again. - */ - } - } - else - { - ALbitfieldSOFT enabledevts{context->EnabledEvts.load(std::memory_order_relaxed)}; - while(context->EnabledEvts.compare_exchange_weak(enabledevts, enabledevts&~flags, - std::memory_order_acq_rel, std::memory_order_acquire) == 0) - { - } - /* Wait to ensure the event handler sees the changed flags before - * returning. - */ - std::lock_guard{context->EventCbLock}; - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; - - std::lock_guard _{context->PropLock}; - std::lock_guard __{context->EventCbLock}; - context->EventCb = callback; - context->EventParam = userParam; -} -END_API_FUNC diff --git a/modules/openal-soft/OpenAL32/sample_cvt.cpp b/modules/openal-soft/OpenAL32/sample_cvt.cpp deleted file mode 100644 index 6d6707f..0000000 --- a/modules/openal-soft/OpenAL32/sample_cvt.cpp +++ /dev/null @@ -1,274 +0,0 @@ - -#include "config.h" - -#include "sample_cvt.h" - -#include "AL/al.h" -#include "alu.h" -#include "alBuffer.h" - - -/* A quick'n'dirty lookup table to decode a muLaw-encoded byte sample into a - * signed 16-bit sample */ -const ALshort muLawDecompressionTable[256] = { - -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, - -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, - -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, - -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316, - -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, - -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, - -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, - -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, - -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, - -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, - -876, -844, -812, -780, -748, -716, -684, -652, - -620, -588, -556, -524, -492, -460, -428, -396, - -372, -356, -340, -324, -308, -292, -276, -260, - -244, -228, -212, -196, -180, -164, -148, -132, - -120, -112, -104, -96, -88, -80, -72, -64, - -56, -48, -40, -32, -24, -16, -8, 0, - 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, - 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, - 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, - 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, - 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, - 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, - 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, - 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, - 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, - 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, - 876, 844, 812, 780, 748, 716, 684, 652, - 620, 588, 556, 524, 492, 460, 428, 396, - 372, 356, 340, 324, 308, 292, 276, 260, - 244, 228, 212, 196, 180, 164, 148, 132, - 120, 112, 104, 96, 88, 80, 72, 64, - 56, 48, 40, 32, 24, 16, 8, 0 -}; - -/* A quick'n'dirty lookup table to decode an aLaw-encoded byte sample into a - * signed 16-bit sample */ -const ALshort aLawDecompressionTable[256] = { - -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, - -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, - -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, - -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, - -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944, - -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136, - -11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472, - -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568, - -344, -328, -376, -360, -280, -264, -312, -296, - -472, -456, -504, -488, -408, -392, -440, -424, - -88, -72, -120, -104, -24, -8, -56, -40, - -216, -200, -248, -232, -152, -136, -184, -168, - -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, - -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, - -688, -656, -752, -720, -560, -528, -624, -592, - -944, -912, -1008, -976, -816, -784, -880, -848, - 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, - 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, - 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, - 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, - 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, - 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, - 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, - 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, - 344, 328, 376, 360, 280, 264, 312, 296, - 472, 456, 504, 488, 408, 392, 440, 424, - 88, 72, 120, 104, 24, 8, 56, 40, - 216, 200, 248, 232, 152, 136, 184, 168, - 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, - 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, - 688, 656, 752, 720, 560, 528, 624, 592, - 944, 912, 1008, 976, 816, 784, 880, 848 -}; - -namespace { - -/* IMA ADPCM Stepsize table */ -constexpr int IMAStep_size[89] = { - 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, - 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, - 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, - 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, - 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, - 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, - 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493,10442, - 11487,12635,13899,15289,16818,18500,20350,22358,24633,27086,29794, - 32767 -}; - -/* IMA4 ADPCM Codeword decode table */ -constexpr int IMA4Codeword[16] = { - 1, 3, 5, 7, 9, 11, 13, 15, - -1,-3,-5,-7,-9,-11,-13,-15, -}; - -/* IMA4 ADPCM Step index adjust decode table */ -constexpr int IMA4Index_adjust[16] = { - -1,-1,-1,-1, 2, 4, 6, 8, - -1,-1,-1,-1, 2, 4, 6, 8 -}; - - -/* MSADPCM Adaption table */ -constexpr int MSADPCMAdaption[16] = { - 230, 230, 230, 230, 307, 409, 512, 614, - 768, 614, 512, 409, 307, 230, 230, 230 -}; - -/* MSADPCM Adaption Coefficient tables */ -constexpr int MSADPCMAdaptionCoeff[7][2] = { - { 256, 0 }, - { 512, -256 }, - { 0, 0 }, - { 192, 64 }, - { 240, 0 }, - { 460, -208 }, - { 392, -232 } -}; - -void DecodeIMA4Block(ALshort *dst, const ALubyte *src, ALint numchans, ALsizei align) -{ - ALint sample[MAX_INPUT_CHANNELS]{}; - ALint index[MAX_INPUT_CHANNELS]{}; - ALuint code[MAX_INPUT_CHANNELS]{}; - - for(int c{0};c < numchans;c++) - { - sample[c] = *(src++); - sample[c] |= *(src++) << 8; - sample[c] = (sample[c]^0x8000) - 32768; - index[c] = *(src++); - index[c] |= *(src++) << 8; - index[c] = (index[c]^0x8000) - 32768; - - index[c] = clampi(index[c], 0, 88); - - dst[c] = sample[c]; - } - - for(int i{1};i < align;i++) - { - if((i&7) == 1) - { - for(int c{0};c < numchans;c++) - { - code[c] = *(src++); - code[c] |= *(src++) << 8; - code[c] |= *(src++) << 16; - code[c] |= *(src++) << 24; - } - } - - for(int c{0};c < numchans;c++) - { - int nibble = code[c]&0xf; - code[c] >>= 4; - - sample[c] += IMA4Codeword[nibble] * IMAStep_size[index[c]] / 8; - sample[c] = clampi(sample[c], -32768, 32767); - - index[c] += IMA4Index_adjust[nibble]; - index[c] = clampi(index[c], 0, 88); - - *(dst++) = sample[c]; - } - } -} - -void DecodeMSADPCMBlock(ALshort *dst, const ALubyte *src, ALint numchans, ALsizei align) -{ - ALubyte blockpred[MAX_INPUT_CHANNELS]{}; - ALint delta[MAX_INPUT_CHANNELS]{}; - ALshort samples[MAX_INPUT_CHANNELS][2]{}; - - for(int c{0};c < numchans;c++) - { - blockpred[c] = *(src++); - blockpred[c] = minu(blockpred[c], 6); - } - for(int c{0};c < numchans;c++) - { - delta[c] = *(src++); - delta[c] |= *(src++) << 8; - delta[c] = (delta[c]^0x8000) - 32768; - } - for(int c{0};c < numchans;c++) - { - samples[c][0] = *(src++); - samples[c][0] |= *(src++) << 8; - samples[c][0] = (samples[c][0]^0x8000) - 32768; - } - for(int c{0};c < numchans;c++) - { - samples[c][1] = *(src++); - samples[c][1] |= *(src++) << 8; - samples[c][1] = (samples[c][1]^0x8000) - 0x8000; - } - - /* Second sample is written first. */ - for(int c{0};c < numchans;c++) - *(dst++) = samples[c][1]; - for(int c{0};c < numchans;c++) - *(dst++) = samples[c][0]; - - for(int i{2};i < align;i++) - { - for(int c{0};c < numchans;c++) - { - const ALint num = (i*numchans) + c; - ALint nibble, pred; - - /* Read the nibble (first is in the upper bits). */ - if(!(num&1)) - nibble = (*src>>4)&0x0f; - else - nibble = (*(src++))&0x0f; - - pred = (samples[c][0]*MSADPCMAdaptionCoeff[blockpred[c]][0] + - samples[c][1]*MSADPCMAdaptionCoeff[blockpred[c]][1]) / 256; - pred += ((nibble^0x08) - 0x08) * delta[c]; - pred = clampi(pred, -32768, 32767); - - samples[c][1] = samples[c][0]; - samples[c][0] = pred; - - delta[c] = (MSADPCMAdaption[nibble] * delta[c]) / 256; - delta[c] = maxi(16, delta[c]); - - *(dst++) = pred; - } - } -} - -} // namespace - -void Convert_ALshort_ALima4(ALshort *dst, const ALubyte *src, ALsizei numchans, ALsizei len, - ALsizei align) -{ - ALsizei byte_align = ((align-1)/2 + 4) * numchans; - - assert(align > 0 && (len%align) == 0); - len /= align; - while(len--) - { - DecodeIMA4Block(dst, src, numchans, align); - src += byte_align; - dst += align*numchans; - } -} - -void Convert_ALshort_ALmsadpcm(ALshort *dst, const ALubyte *src, ALsizei numchans, ALsizei len, - ALsizei align) -{ - const ALsizei byte_align = ((align-2)/2 + 7) * numchans; - - assert(align > 1 && (len%align) == 0); - len /= align; - while(len--) - { - DecodeMSADPCMBlock(dst, src, numchans, align); - src += byte_align; - dst += align*numchans; - } -} diff --git a/modules/openal-soft/OpenALConfig.cmake.in b/modules/openal-soft/OpenALConfig.cmake.in new file mode 100644 index 0000000..128c1a4 --- /dev/null +++ b/modules/openal-soft/OpenALConfig.cmake.in @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.1) + +include("${CMAKE_CURRENT_LIST_DIR}/OpenALTargets.cmake") + +set(OPENAL_FOUND ON) +set(OPENAL_INCLUDE_DIR $) +set(OPENAL_LIBRARY $) +set(OPENAL_DEFINITIONS $) +set(OPENAL_VERSION_STRING @PACKAGE_VERSION@) diff --git a/modules/openal-soft/XCompile-Android.txt b/modules/openal-soft/XCompile-Android.txt index 3dd88e8..693f0ed 100644 --- a/modules/openal-soft/XCompile-Android.txt +++ b/modules/openal-soft/XCompile-Android.txt @@ -1,39 +1,16 @@ -# Cross-compiling requires CMake 2.6 or newer. Example: -# cmake .. -DCMAKE_TOOLCHAIN_FILE=../XCompile-Android.txt -DHOST=arm-linux-androideabi -# Where 'arm-linux-androideabi' is the host prefix for the cross-compiler. If -# you already have a toolchain file setup, you may use that instead of this -# file. Make sure to set CMAKE_FIND_ROOT_PATH to where the NDK toolchain was -# installed (e.g. "$ENV{HOME}/toolchains/arm-linux-androideabi-r10c-21"). +# Cross-compiling for Android is handled by the NDK's own provided toolchain, +# which as of this writing, should be in +# ${ndk_root}/build/cmake/android.toolchain.cmake +# +# Certain older NDK versions may also need to explicitly pick the libc++ +# runtime. So for example: +# cmake .. -DANDROID_STL=c++_shared \ +# -DCMAKE_TOOLCHAIN_FILE=${ndk_root}/build/cmake/android.toolchain.cmake +# +# Certain NDK versions may also need to use the lld linker to avoid errors +# about missing liblog.so and libOpenSLES.so. That can be done by: +# cmake .. -DANDROID_LD=lld \ +# -DCMAKE_TOOLCHAIN_FILE=${ndk_root}/build/cmake/android.toolchain.cmake +# -# the name of the target operating system -SET(CMAKE_SYSTEM_NAME Linux) - -# which compilers to use for C and C++ -SET(CMAKE_C_COMPILER "${HOST}-gcc") -SET(CMAKE_CXX_COMPILER "${HOST}-g++") -SET(CMAKE_RC_COMPILER "${HOST}-windres") - -# here is the target environment located -SET(CMAKE_FIND_ROOT_PATH "SET THIS TO THE NDK TOOLCHAIN'S INSTALL PATH") - -# here is where stuff gets installed to -SET(CMAKE_INSTALL_PREFIX "${CMAKE_FIND_ROOT_PATH}" CACHE STRING "Install path prefix, prepended onto install directories." FORCE) - -# adjust the default behaviour of the FIND_XXX() commands: -# search headers and libraries in the target environment, search -# programs in the host environment -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) - -# set env vars so that pkg-config will look in the appropriate directory for -# .pc files (as there seems to be no way to force using ${HOST}-pkg-config) -set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig") -set(ENV{PKG_CONFIG_PATH} "") - -# Qt4 tools -SET(QT_QMAKE_EXECUTABLE ${HOST}-qmake) -SET(QT_MOC_EXECUTABLE ${HOST}-moc) -SET(QT_RCC_EXECUTABLE ${HOST}-rcc) -SET(QT_UIC_EXECUTABLE ${HOST}-uic) -SET(QT_LRELEASE_EXECUTABLE ${HOST}-lrelease) +MESSAGE(FATAL_ERROR "Use the toolchain provided by the Android NDK") diff --git a/modules/openal-soft/al/auxeffectslot.cpp b/modules/openal-soft/al/auxeffectslot.cpp new file mode 100644 index 0000000..c33fb14 --- /dev/null +++ b/modules/openal-soft/al/auxeffectslot.cpp @@ -0,0 +1,1823 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "auxeffectslot.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/efx.h" + +#include "albit.h" +#include "alc/alu.h" +#include "alc/context.h" +#include "alc/device.h" +#include "alc/inprogext.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" +#include "buffer.h" +#include "core/except.h" +#include "core/fpu_ctrl.h" +#include "core/logging.h" +#include "effect.h" +#include "opthelpers.h" + +#ifdef ALSOFT_EAX +#include "eax_exception.h" +#include "eax_utils.h" +#endif // ALSOFT_EAX + +namespace { + +struct FactoryItem { + EffectSlotType Type; + EffectStateFactory* (&GetFactory)(void); +}; +constexpr FactoryItem FactoryList[] = { + { EffectSlotType::None, NullStateFactory_getFactory }, + { EffectSlotType::EAXReverb, ReverbStateFactory_getFactory }, + { EffectSlotType::Reverb, StdReverbStateFactory_getFactory }, + { EffectSlotType::Autowah, AutowahStateFactory_getFactory }, + { EffectSlotType::Chorus, ChorusStateFactory_getFactory }, + { EffectSlotType::Compressor, CompressorStateFactory_getFactory }, + { EffectSlotType::Distortion, DistortionStateFactory_getFactory }, + { EffectSlotType::Echo, EchoStateFactory_getFactory }, + { EffectSlotType::Equalizer, EqualizerStateFactory_getFactory }, + { EffectSlotType::Flanger, FlangerStateFactory_getFactory }, + { EffectSlotType::FrequencyShifter, FshifterStateFactory_getFactory }, + { EffectSlotType::RingModulator, ModulatorStateFactory_getFactory }, + { EffectSlotType::PitchShifter, PshifterStateFactory_getFactory }, + { EffectSlotType::VocalMorpher, VmorpherStateFactory_getFactory }, + { EffectSlotType::DedicatedDialog, DedicatedStateFactory_getFactory }, + { EffectSlotType::DedicatedLFE, DedicatedStateFactory_getFactory }, + { EffectSlotType::Convolution, ConvolutionStateFactory_getFactory }, +}; + +EffectStateFactory *getFactoryByType(EffectSlotType type) +{ + auto iter = std::find_if(std::begin(FactoryList), std::end(FactoryList), + [type](const FactoryItem &item) noexcept -> bool + { return item.Type == type; }); + return (iter != std::end(FactoryList)) ? iter->GetFactory() : nullptr; +} + + +inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept +{ + const size_t lidx{(id-1) >> 6}; + const ALuint slidx{(id-1) & 0x3f}; + + if UNLIKELY(lidx >= context->mEffectSlotList.size()) + return nullptr; + EffectSlotSubList &sublist{context->mEffectSlotList[lidx]}; + if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + return nullptr; + return sublist.EffectSlots + slidx; +} + +inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) noexcept +{ + const size_t lidx{(id-1) >> 6}; + const ALuint slidx{(id-1) & 0x3f}; + + if UNLIKELY(lidx >= device->EffectList.size()) + return nullptr; + EffectSubList &sublist = device->EffectList[lidx]; + if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + return nullptr; + return sublist.Effects + slidx; +} + +inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) noexcept +{ + const size_t lidx{(id-1) >> 6}; + const ALuint slidx{(id-1) & 0x3f}; + + if UNLIKELY(lidx >= device->BufferList.size()) + return nullptr; + BufferSubList &sublist = device->BufferList[lidx]; + if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + return nullptr; + return sublist.Buffers + slidx; +} + + +inline auto GetEffectBuffer(ALbuffer *buffer) noexcept -> EffectState::Buffer +{ + if(!buffer) return EffectState::Buffer{}; + return EffectState::Buffer{buffer, buffer->mData}; +} + + +void AddActiveEffectSlots(const al::span auxslots, ALCcontext *context) +{ + if(auxslots.empty()) return; + EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)}; + size_t newcount{curarray->size() + auxslots.size()}; + + /* Insert the new effect slots into the head of the array, followed by the + * existing ones. + */ + EffectSlotArray *newarray = EffectSlot::CreatePtrArray(newcount); + auto slotiter = std::transform(auxslots.begin(), auxslots.end(), newarray->begin(), + [](ALeffectslot *auxslot) noexcept { return &auxslot->mSlot; }); + std::copy(curarray->begin(), curarray->end(), slotiter); + + /* Remove any duplicates (first instance of each will be kept). */ + auto last = newarray->end(); + for(auto start=newarray->begin()+1;;) + { + last = std::remove(start, last, *(start-1)); + if(start == last) break; + ++start; + } + newcount = static_cast(std::distance(newarray->begin(), last)); + + /* Reallocate newarray if the new size ended up smaller from duplicate + * removal. + */ + if UNLIKELY(newcount < newarray->size()) + { + curarray = newarray; + newarray = EffectSlot::CreatePtrArray(newcount); + std::copy_n(curarray->begin(), newcount, newarray->begin()); + delete curarray; + curarray = nullptr; + } + std::uninitialized_fill_n(newarray->end(), newcount, nullptr); + + curarray = context->mActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel); + context->mDevice->waitForMix(); + + al::destroy_n(curarray->end(), curarray->size()); + delete curarray; +} + +void RemoveActiveEffectSlots(const al::span auxslots, ALCcontext *context) +{ + if(auxslots.empty()) return; + EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)}; + + /* Don't shrink the allocated array size since we don't know how many (if + * any) of the effect slots to remove are in the array. + */ + EffectSlotArray *newarray = EffectSlot::CreatePtrArray(curarray->size()); + + auto new_end = std::copy(curarray->begin(), curarray->end(), newarray->begin()); + /* Remove elements from newarray that match any ID in slotids. */ + for(const ALeffectslot *auxslot : auxslots) + { + auto slot_match = [auxslot](EffectSlot *slot) noexcept -> bool + { return (slot == &auxslot->mSlot); }; + new_end = std::remove_if(newarray->begin(), new_end, slot_match); + } + + /* Reallocate with the new size. */ + auto newsize = static_cast(std::distance(newarray->begin(), new_end)); + if LIKELY(newsize != newarray->size()) + { + curarray = newarray; + newarray = EffectSlot::CreatePtrArray(newsize); + std::copy_n(curarray->begin(), newsize, newarray->begin()); + + delete curarray; + curarray = nullptr; + } + std::uninitialized_fill_n(newarray->end(), newsize, nullptr); + + curarray = context->mActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel); + context->mDevice->waitForMix(); + + al::destroy_n(curarray->end(), curarray->size()); + delete curarray; +} + + +EffectSlotType EffectSlotTypeFromEnum(ALenum type) +{ + switch(type) + { + case AL_EFFECT_NULL: return EffectSlotType::None; + case AL_EFFECT_REVERB: return EffectSlotType::Reverb; + case AL_EFFECT_CHORUS: return EffectSlotType::Chorus; + case AL_EFFECT_DISTORTION: return EffectSlotType::Distortion; + case AL_EFFECT_ECHO: return EffectSlotType::Echo; + case AL_EFFECT_FLANGER: return EffectSlotType::Flanger; + case AL_EFFECT_FREQUENCY_SHIFTER: return EffectSlotType::FrequencyShifter; + case AL_EFFECT_VOCAL_MORPHER: return EffectSlotType::VocalMorpher; + case AL_EFFECT_PITCH_SHIFTER: return EffectSlotType::PitchShifter; + case AL_EFFECT_RING_MODULATOR: return EffectSlotType::RingModulator; + case AL_EFFECT_AUTOWAH: return EffectSlotType::Autowah; + case AL_EFFECT_COMPRESSOR: return EffectSlotType::Compressor; + case AL_EFFECT_EQUALIZER: return EffectSlotType::Equalizer; + case AL_EFFECT_EAXREVERB: return EffectSlotType::EAXReverb; + case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return EffectSlotType::DedicatedLFE; + case AL_EFFECT_DEDICATED_DIALOGUE: return EffectSlotType::DedicatedDialog; + case AL_EFFECT_CONVOLUTION_REVERB_SOFT: return EffectSlotType::Convolution; + } + ERR("Unhandled effect enum: 0x%04x\n", type); + return EffectSlotType::None; +} + +bool EnsureEffectSlots(ALCcontext *context, size_t needed) +{ + size_t count{std::accumulate(context->mEffectSlotList.cbegin(), + context->mEffectSlotList.cend(), size_t{0}, + [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t + { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; + + while(needed > count) + { + if UNLIKELY(context->mEffectSlotList.size() >= 1<<25) + return false; + + context->mEffectSlotList.emplace_back(); + auto sublist = context->mEffectSlotList.end() - 1; + sublist->FreeMask = ~0_u64; + sublist->EffectSlots = static_cast( + al_calloc(alignof(ALeffectslot), sizeof(ALeffectslot)*64)); + if UNLIKELY(!sublist->EffectSlots) + { + context->mEffectSlotList.pop_back(); + return false; + } + count += 64; + } + return true; +} + +ALeffectslot *AllocEffectSlot(ALCcontext *context) +{ + auto sublist = std::find_if(context->mEffectSlotList.begin(), context->mEffectSlotList.end(), + [](const EffectSlotSubList &entry) noexcept -> bool + { return entry.FreeMask != 0; }); + auto lidx = static_cast(std::distance(context->mEffectSlotList.begin(), sublist)); + auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); + ASSUME(slidx < 64); + + ALeffectslot *slot{al::construct_at(sublist->EffectSlots + slidx)}; + aluInitEffectPanning(&slot->mSlot, context); + + /* Add 1 to avoid source ID 0. */ + slot->id = ((lidx<<6) | slidx) + 1; + + context->mNumEffectSlots += 1; + sublist->FreeMask &= ~(1_u64 << slidx); + + return slot; +} + +void FreeEffectSlot(ALCcontext *context, ALeffectslot *slot) +{ + const ALuint id{slot->id - 1}; + const size_t lidx{id >> 6}; + const ALuint slidx{id & 0x3f}; + + al::destroy_at(slot); + + context->mEffectSlotList[lidx].FreeMask |= 1_u64 << slidx; + context->mNumEffectSlots--; +} + + +inline void UpdateProps(ALeffectslot *slot, ALCcontext *context) +{ + if(!context->mDeferUpdates && slot->mState == SlotState::Playing) + { + slot->updateProps(context); + return; + } + slot->mPropsDirty = true; +} + +} // namespace + + +AL_API void AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Generating %d effect slots", n); + if UNLIKELY(n <= 0) return; + + std::unique_lock slotlock{context->mEffectSlotLock}; + ALCdevice *device{context->mALDevice.get()}; + if(static_cast(n) > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots) + { + context->setError(AL_OUT_OF_MEMORY, "Exceeding %u effect slot limit (%u + %d)", + device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, n); + return; + } + if(!EnsureEffectSlots(context.get(), static_cast(n))) + { + context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effectslot%s", n, + (n==1) ? "" : "s"); + return; + } + + if(n == 1) + { + ALeffectslot *slot{AllocEffectSlot(context.get())}; + if(!slot) return; + effectslots[0] = slot->id; + } + else + { + al::vector ids; + ALsizei count{n}; + ids.reserve(static_cast(count)); + do { + ALeffectslot *slot{AllocEffectSlot(context.get())}; + if(!slot) + { + slotlock.unlock(); + alDeleteAuxiliaryEffectSlots(static_cast(ids.size()), ids.data()); + return; + } + ids.emplace_back(slot->id); + } while(--count); + std::copy(ids.cbegin(), ids.cend(), effectslots); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Deleting %d effect slots", n); + if UNLIKELY(n <= 0) return; + + std::lock_guard _{context->mEffectSlotLock}; + if(n == 1) + { + ALeffectslot *slot{LookupEffectSlot(context.get(), effectslots[0])}; + if UNLIKELY(!slot) + { + context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslots[0]); + return; + } + if UNLIKELY(ReadRef(slot->ref) != 0) + { + context->setError(AL_INVALID_OPERATION, "Deleting in-use effect slot %u", + effectslots[0]); + return; + } + RemoveActiveEffectSlots({&slot, 1u}, context.get()); + FreeEffectSlot(context.get(), slot); + } + else + { + auto slots = al::vector(static_cast(n)); + for(size_t i{0};i < slots.size();++i) + { + ALeffectslot *slot{LookupEffectSlot(context.get(), effectslots[i])}; + if UNLIKELY(!slot) + { + context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslots[i]); + return; + } + if UNLIKELY(ReadRef(slot->ref) != 0) + { + context->setError(AL_INVALID_OPERATION, "Deleting in-use effect slot %u", + effectslots[i]); + return; + } + slots[i] = slot; + } + /* Remove any duplicates. */ + auto slots_end = slots.end(); + for(auto start=slots.begin()+1;start != slots_end;++start) + { + slots_end = std::remove(start, slots_end, *(start-1)); + if(start == slots_end) break; + } + slots.erase(slots_end, slots.end()); + + /* All effectslots are valid, remove and delete them */ + RemoveActiveEffectSlots(slots, context.get()); + for(ALeffectslot *slot : slots) + FreeEffectSlot(context.get(), slot); + } +} +END_API_FUNC + +AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if LIKELY(context) + { + std::lock_guard _{context->mEffectSlotLock}; + if(LookupEffectSlot(context.get(), effectslot) != nullptr) + return AL_TRUE; + } + return AL_FALSE; +} +END_API_FUNC + + +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mEffectSlotLock}; + ALeffectslot *slot{LookupEffectSlot(context.get(), slotid)}; + if UNLIKELY(!slot) + { + context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotid); + return; + } + if(slot->mState == SlotState::Playing) + return; + + slot->mPropsDirty = false; + slot->updateProps(context.get()); + + AddActiveEffectSlots({&slot, 1}, context.get()); + slot->mState = SlotState::Playing; +} +END_API_FUNC + +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Playing %d effect slots", n); + if UNLIKELY(n <= 0) return; + + auto slots = al::vector(static_cast(n)); + std::lock_guard _{context->mEffectSlotLock}; + for(size_t i{0};i < slots.size();++i) + { + ALeffectslot *slot{LookupEffectSlot(context.get(), slotids[i])}; + if UNLIKELY(!slot) + { + context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotids[i]); + return; + } + + if(slot->mState != SlotState::Playing) + { + slot->mPropsDirty = false; + slot->updateProps(context.get()); + } + slots[i] = slot; + }; + + AddActiveEffectSlots(slots, context.get()); + for(auto slot : slots) + slot->mState = SlotState::Playing; +} +END_API_FUNC + +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mEffectSlotLock}; + ALeffectslot *slot{LookupEffectSlot(context.get(), slotid)}; + if UNLIKELY(!slot) + { + context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotid); + return; + } + + RemoveActiveEffectSlots({&slot, 1}, context.get()); + slot->mState = SlotState::Stopped; +} +END_API_FUNC + +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Stopping %d effect slots", n); + if UNLIKELY(n <= 0) return; + + auto slots = al::vector(static_cast(n)); + std::lock_guard _{context->mEffectSlotLock}; + for(size_t i{0};i < slots.size();++i) + { + ALeffectslot *slot{LookupEffectSlot(context.get(), slotids[i])}; + if UNLIKELY(!slot) + { + context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotids[i]); + return; + } + + slots[i] = slot; + }; + + RemoveActiveEffectSlots(slots, context.get()); + for(auto slot : slots) + slot->mState = SlotState::Stopped; +} +END_API_FUNC + + +AL_API void AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mEffectSlotLock}; + ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); + if UNLIKELY(!slot) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); + + ALeffectslot *target{}; + ALCdevice *device{}; + ALenum err{}; + switch(param) + { + case AL_EFFECTSLOT_EFFECT: + device = context->mALDevice.get(); + + { + std::lock_guard ___{device->EffectLock}; + ALeffect *effect{value ? LookupEffect(device, static_cast(value)) : nullptr}; + if(effect) + err = slot->initEffect(effect->type, effect->Props, context.get()); + else + { + if(value != 0) + SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect ID %u", value); + err = slot->initEffect(AL_EFFECT_NULL, EffectProps{}, context.get()); + } + } + if UNLIKELY(err != AL_NO_ERROR) + { + context->setError(err, "Effect initialization failed"); + return; + } + if UNLIKELY(slot->mState == SlotState::Initial) + { + slot->mPropsDirty = false; + slot->updateProps(context.get()); + + AddActiveEffectSlots({&slot, 1}, context.get()); + slot->mState = SlotState::Playing; + return; + } + break; + + case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: + if(!(value == AL_TRUE || value == AL_FALSE)) + SETERR_RETURN(context, AL_INVALID_VALUE,, + "Effect slot auxiliary send auto out of range"); + if UNLIKELY(slot->AuxSendAuto == !!value) + return; + slot->AuxSendAuto = !!value; + break; + + case AL_EFFECTSLOT_TARGET_SOFT: + target = LookupEffectSlot(context.get(), static_cast(value)); + if(value && !target) + SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect slot target ID"); + if UNLIKELY(slot->Target == target) + return; + if(target) + { + ALeffectslot *checker{target}; + while(checker && checker != slot) + checker = checker->Target; + if(checker) + SETERR_RETURN(context, AL_INVALID_OPERATION,, + "Setting target of effect slot ID %u to %u creates circular chain", slot->id, + target->id); + } + + if(ALeffectslot *oldtarget{slot->Target}) + { + /* We must force an update if there was an existing effect slot + * target, in case it's about to be deleted. + */ + if(target) IncrementRef(target->ref); + DecrementRef(oldtarget->ref); + slot->Target = target; + slot->updateProps(context.get()); + return; + } + + if(target) IncrementRef(target->ref); + slot->Target = target; + break; + + case AL_BUFFER: + device = context->mALDevice.get(); + + if(slot->mState == SlotState::Playing) + SETERR_RETURN(context, AL_INVALID_OPERATION,, + "Setting buffer on playing effect slot %u", slot->id); + + if(ALbuffer *buffer{slot->Buffer}) + { + if UNLIKELY(buffer->id == static_cast(value)) + return; + } + else if UNLIKELY(value == 0) + return; + + { + std::lock_guard ___{device->BufferLock}; + ALbuffer *buffer{}; + if(value) + { + buffer = LookupBuffer(device, static_cast(value)); + if(!buffer) SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid buffer ID"); + if(buffer->mCallback) + SETERR_RETURN(context, AL_INVALID_OPERATION,, + "Callback buffer not valid for effects"); + + IncrementRef(buffer->ref); + } + + if(ALbuffer *oldbuffer{slot->Buffer}) + DecrementRef(oldbuffer->ref); + slot->Buffer = buffer; + + FPUCtl mixer_mode{}; + auto *state = slot->Effect.State.get(); + state->deviceUpdate(device, GetEffectBuffer(buffer)); + } + break; + + case AL_EFFECTSLOT_STATE_SOFT: + SETERR_RETURN(context, AL_INVALID_OPERATION,, "AL_EFFECTSLOT_STATE_SOFT is read-only"); + + default: + SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid effect slot integer property 0x%04x", + param); + } + UpdateProps(slot, context.get()); +} +END_API_FUNC + +AL_API void AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *values) +START_API_FUNC +{ + switch(param) + { + case AL_EFFECTSLOT_EFFECT: + case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: + case AL_EFFECTSLOT_TARGET_SOFT: + case AL_EFFECTSLOT_STATE_SOFT: + case AL_BUFFER: + alAuxiliaryEffectSloti(effectslot, param, values[0]); + return; + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mEffectSlotLock}; + ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); + if UNLIKELY(!slot) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); + + switch(param) + { + default: + SETERR_RETURN(context, AL_INVALID_ENUM,, + "Invalid effect slot integer-vector property 0x%04x", param); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mEffectSlotLock}; + ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); + if UNLIKELY(!slot) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); + + switch(param) + { + case AL_EFFECTSLOT_GAIN: + if(!(value >= 0.0f && value <= 1.0f)) + SETERR_RETURN(context, AL_INVALID_VALUE,, "Effect slot gain out of range"); + if UNLIKELY(slot->Gain == value) + return; + slot->Gain = value; + break; + + default: + SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid effect slot float property 0x%04x", + param); + } + UpdateProps(slot, context.get()); +} +END_API_FUNC + +AL_API void AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *values) +START_API_FUNC +{ + switch(param) + { + case AL_EFFECTSLOT_GAIN: + alAuxiliaryEffectSlotf(effectslot, param, values[0]); + return; + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mEffectSlotLock}; + ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); + if UNLIKELY(!slot) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); + + switch(param) + { + default: + SETERR_RETURN(context, AL_INVALID_ENUM,, + "Invalid effect slot float-vector property 0x%04x", param); + } +} +END_API_FUNC + + +AL_API void AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mEffectSlotLock}; + ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); + if UNLIKELY(!slot) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); + + switch(param) + { + case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: + *value = slot->AuxSendAuto ? AL_TRUE : AL_FALSE; + break; + + case AL_EFFECTSLOT_TARGET_SOFT: + if(auto *target = slot->Target) + *value = static_cast(target->id); + else + *value = 0; + break; + + case AL_EFFECTSLOT_STATE_SOFT: + *value = static_cast(slot->mState); + break; + + case AL_BUFFER: + if(auto *buffer = slot->Buffer) + *value = static_cast(buffer->id); + else + *value = 0; + break; + + default: + context->setError(AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *values) +START_API_FUNC +{ + switch(param) + { + case AL_EFFECTSLOT_EFFECT: + case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: + case AL_EFFECTSLOT_TARGET_SOFT: + case AL_EFFECTSLOT_STATE_SOFT: + case AL_BUFFER: + alGetAuxiliaryEffectSloti(effectslot, param, values); + return; + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mEffectSlotLock}; + ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); + if UNLIKELY(!slot) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); + + switch(param) + { + default: + context->setError(AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x", + param); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mEffectSlotLock}; + ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); + if UNLIKELY(!slot) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); + + switch(param) + { + case AL_EFFECTSLOT_GAIN: + *value = slot->Gain; + break; + + default: + context->setError(AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *values) +START_API_FUNC +{ + switch(param) + { + case AL_EFFECTSLOT_GAIN: + alGetAuxiliaryEffectSlotf(effectslot, param, values); + return; + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mEffectSlotLock}; + ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); + if UNLIKELY(!slot) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); + + switch(param) + { + default: + context->setError(AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x", + param); + } +} +END_API_FUNC + + +ALeffectslot::ALeffectslot() +{ + EffectStateFactory *factory{getFactoryByType(EffectSlotType::None)}; + if(!factory) throw std::runtime_error{"Failed to get null effect factory"}; + + al::intrusive_ptr state{factory->create()}; + Effect.State = state; + mSlot.mEffectState = state.release(); +} + +ALeffectslot::~ALeffectslot() +{ + if(Target) + DecrementRef(Target->ref); + Target = nullptr; + if(Buffer) + DecrementRef(Buffer->ref); + Buffer = nullptr; + + EffectSlotProps *props{mSlot.Update.exchange(nullptr)}; + if(props) + { + TRACE("Freed unapplied AuxiliaryEffectSlot update %p\n", + decltype(std::declval()){props}); + delete props; + } + + if(mSlot.mEffectState) + mSlot.mEffectState->release(); +} + +ALenum ALeffectslot::initEffect(ALenum effectType, const EffectProps &effectProps, + ALCcontext *context) +{ + EffectSlotType newtype{EffectSlotTypeFromEnum(effectType)}; + if(newtype != Effect.Type) + { + EffectStateFactory *factory{getFactoryByType(newtype)}; + if(!factory) + { + ERR("Failed to find factory for effect slot type %d\n", static_cast(newtype)); + return AL_INVALID_ENUM; + } + al::intrusive_ptr state{factory->create()}; + + ALCdevice *device{context->mALDevice.get()}; + std::unique_lock statelock{device->StateLock}; + state->mOutTarget = device->Dry.Buffer; + { + FPUCtl mixer_mode{}; + state->deviceUpdate(device, GetEffectBuffer(Buffer)); + } + + Effect.Type = newtype; + Effect.Props = effectProps; + + Effect.State = std::move(state); + } + else if(newtype != EffectSlotType::None) + Effect.Props = effectProps; + + /* Remove state references from old effect slot property updates. */ + EffectSlotProps *props{context->mFreeEffectslotProps.load()}; + while(props) + { + props->State = nullptr; + props = props->next.load(std::memory_order_relaxed); + } + + return AL_NO_ERROR; +} + +void ALeffectslot::updateProps(ALCcontext *context) +{ + /* Get an unused property container, or allocate a new one as needed. */ + EffectSlotProps *props{context->mFreeEffectslotProps.load(std::memory_order_relaxed)}; + if(!props) + props = new EffectSlotProps{}; + else + { + EffectSlotProps *next; + do { + next = props->next.load(std::memory_order_relaxed); + } while(context->mFreeEffectslotProps.compare_exchange_weak(props, next, + std::memory_order_seq_cst, std::memory_order_acquire) == 0); + } + + /* Copy in current property values. */ + props->Gain = Gain; + props->AuxSendAuto = AuxSendAuto; + props->Target = Target ? &Target->mSlot : nullptr; + + props->Type = Effect.Type; + props->Props = Effect.Props; + props->State = Effect.State; + + /* Set the new container for updating internal parameters. */ + props = mSlot.Update.exchange(props, std::memory_order_acq_rel); + if(props) + { + /* If there was an unused update container, put it back in the + * freelist. + */ + props->State = nullptr; + AtomicReplaceHead(context->mFreeEffectslotProps, props); + } +} + +void UpdateAllEffectSlotProps(ALCcontext *context) +{ + std::lock_guard _{context->mEffectSlotLock}; +#ifdef ALSOFT_EAX + if(context->has_eax()) + context->eax_commit_fx_slots(); +#endif + for(auto &sublist : context->mEffectSlotList) + { + uint64_t usemask{~sublist.FreeMask}; + while(usemask) + { + const int idx{al::countr_zero(usemask)}; + usemask &= ~(1_u64 << idx); + ALeffectslot *slot{sublist.EffectSlots + idx}; + + if(slot->mState != SlotState::Stopped && std::exchange(slot->mPropsDirty, false)) + slot->updateProps(context); + } + } +} + +EffectSlotSubList::~EffectSlotSubList() +{ + uint64_t usemask{~FreeMask}; + while(usemask) + { + const int idx{al::countr_zero(usemask)}; + al::destroy_at(EffectSlots+idx); + usemask &= ~(1_u64 << idx); + } + FreeMask = ~usemask; + al_free(EffectSlots); + EffectSlots = nullptr; +} + +#ifdef ALSOFT_EAX +namespace { + +class EaxFxSlotException : + public EaxException +{ +public: + explicit EaxFxSlotException( + const char* message) + : + EaxException{"EAX_FX_SLOT", message} + { + } +}; // EaxFxSlotException + + +} // namespace + + +void ALeffectslot::eax_initialize( + ALCcontext& al_context, + EaxFxSlotIndexValue index) +{ + eax_al_context_ = &al_context; + + if (index >= EAX_MAX_FXSLOTS) + { + eax_fail("Index out of range."); + } + + eax_fx_slot_index_ = index; + + eax_initialize_eax(); + eax_initialize_lock(); + eax_initialize_effects(); +} + +const EAX50FXSLOTPROPERTIES& ALeffectslot::eax_get_eax_fx_slot() const noexcept +{ + return eax_eax_fx_slot_; +} + +void ALeffectslot::eax_ensure_is_unlocked() const +{ + if (eax_is_locked_) + eax_fail("Locked."); +} + +void ALeffectslot::eax_validate_fx_slot_effect( + const GUID& eax_effect_id) +{ + eax_ensure_is_unlocked(); + + if (eax_effect_id != EAX_NULL_GUID && + eax_effect_id != EAX_REVERB_EFFECT && + eax_effect_id != EAX_AGCCOMPRESSOR_EFFECT && + eax_effect_id != EAX_AUTOWAH_EFFECT && + eax_effect_id != EAX_CHORUS_EFFECT && + eax_effect_id != EAX_DISTORTION_EFFECT && + eax_effect_id != EAX_ECHO_EFFECT && + eax_effect_id != EAX_EQUALIZER_EFFECT && + eax_effect_id != EAX_FLANGER_EFFECT && + eax_effect_id != EAX_FREQUENCYSHIFTER_EFFECT && + eax_effect_id != EAX_VOCALMORPHER_EFFECT && + eax_effect_id != EAX_PITCHSHIFTER_EFFECT && + eax_effect_id != EAX_RINGMODULATOR_EFFECT) + { + eax_fail("Unsupported EAX effect GUID."); + } +} + +void ALeffectslot::eax_validate_fx_slot_volume( + long eax_volume) +{ + eax_validate_range( + "Volume", + eax_volume, + EAXFXSLOT_MINVOLUME, + EAXFXSLOT_MAXVOLUME); +} + +void ALeffectslot::eax_validate_fx_slot_lock( + long eax_lock) +{ + eax_ensure_is_unlocked(); + + eax_validate_range( + "Lock", + eax_lock, + EAXFXSLOT_MINLOCK, + EAXFXSLOT_MAXLOCK); +} + +void ALeffectslot::eax_validate_fx_slot_flags( + unsigned long eax_flags, + int eax_version) +{ + eax_validate_range( + "Flags", + eax_flags, + 0UL, + ~(eax_version == 4 ? EAX40FXSLOTFLAGS_RESERVED : EAX50FXSLOTFLAGS_RESERVED)); +} + +void ALeffectslot::eax_validate_fx_slot_occlusion( + long eax_occlusion) +{ + eax_validate_range( + "Occlusion", + eax_occlusion, + EAXFXSLOT_MINOCCLUSION, + EAXFXSLOT_MAXOCCLUSION); +} + +void ALeffectslot::eax_validate_fx_slot_occlusion_lf_ratio( + float eax_occlusion_lf_ratio) +{ + eax_validate_range( + "Occlusion LF Ratio", + eax_occlusion_lf_ratio, + EAXFXSLOT_MINOCCLUSIONLFRATIO, + EAXFXSLOT_MAXOCCLUSIONLFRATIO); +} + +void ALeffectslot::eax_validate_fx_slot_all( + const EAX40FXSLOTPROPERTIES& fx_slot, + int eax_version) +{ + eax_validate_fx_slot_effect(fx_slot.guidLoadEffect); + eax_validate_fx_slot_volume(fx_slot.lVolume); + eax_validate_fx_slot_lock(fx_slot.lLock); + eax_validate_fx_slot_flags(fx_slot.ulFlags, eax_version); +} + +void ALeffectslot::eax_validate_fx_slot_all( + const EAX50FXSLOTPROPERTIES& fx_slot, + int eax_version) +{ + eax_validate_fx_slot_all(static_cast(fx_slot), eax_version); + + eax_validate_fx_slot_occlusion(fx_slot.lOcclusion); + eax_validate_fx_slot_occlusion_lf_ratio(fx_slot.flOcclusionLFRatio); +} + +void ALeffectslot::eax_set_fx_slot_effect( + const GUID& eax_effect_id) +{ + if (eax_eax_fx_slot_.guidLoadEffect == eax_effect_id) + { + return; + } + + eax_eax_fx_slot_.guidLoadEffect = eax_effect_id; + + eax_set_fx_slot_effect(); +} + +void ALeffectslot::eax_set_fx_slot_volume( + long eax_volume) +{ + if (eax_eax_fx_slot_.lVolume == eax_volume) + { + return; + } + + eax_eax_fx_slot_.lVolume = eax_volume; + + eax_set_fx_slot_volume(); +} + +void ALeffectslot::eax_set_fx_slot_lock( + long eax_lock) +{ + if (eax_eax_fx_slot_.lLock == eax_lock) + { + return; + } + + eax_eax_fx_slot_.lLock = eax_lock; +} + +void ALeffectslot::eax_set_fx_slot_flags( + unsigned long eax_flags) +{ + if (eax_eax_fx_slot_.ulFlags == eax_flags) + { + return; + } + + eax_eax_fx_slot_.ulFlags = eax_flags; + + eax_set_fx_slot_flags(); +} + +// [[nodiscard]] +bool ALeffectslot::eax_set_fx_slot_occlusion( + long eax_occlusion) +{ + if (eax_eax_fx_slot_.lOcclusion == eax_occlusion) + { + return false; + } + + eax_eax_fx_slot_.lOcclusion = eax_occlusion; + + return true; +} + +// [[nodiscard]] +bool ALeffectslot::eax_set_fx_slot_occlusion_lf_ratio( + float eax_occlusion_lf_ratio) +{ + if (eax_eax_fx_slot_.flOcclusionLFRatio == eax_occlusion_lf_ratio) + { + return false; + } + + eax_eax_fx_slot_.flOcclusionLFRatio = eax_occlusion_lf_ratio; + + return true; +} + +void ALeffectslot::eax_set_fx_slot_all( + const EAX40FXSLOTPROPERTIES& eax_fx_slot) +{ + eax_set_fx_slot_effect(eax_fx_slot.guidLoadEffect); + eax_set_fx_slot_volume(eax_fx_slot.lVolume); + eax_set_fx_slot_lock(eax_fx_slot.lLock); + eax_set_fx_slot_flags(eax_fx_slot.ulFlags); +} + +// [[nodiscard]] +bool ALeffectslot::eax_set_fx_slot_all( + const EAX50FXSLOTPROPERTIES& eax_fx_slot) +{ + eax_set_fx_slot_all(static_cast(eax_fx_slot)); + + const auto is_occlusion_modified = eax_set_fx_slot_occlusion(eax_fx_slot.lOcclusion); + const auto is_occlusion_lf_ratio_modified = eax_set_fx_slot_occlusion_lf_ratio(eax_fx_slot.flOcclusionLFRatio); + + return is_occlusion_modified || is_occlusion_lf_ratio_modified; +} + +void ALeffectslot::eax_unlock_legacy() noexcept +{ + assert(eax_fx_slot_index_ < 2); + eax_is_locked_ = false; + eax_eax_fx_slot_.lLock = EAXFXSLOT_UNLOCKED; +} + +[[noreturn]] +void ALeffectslot::eax_fail( + const char* message) +{ + throw EaxFxSlotException{message}; +} + +GUID ALeffectslot::eax_get_eax_default_effect_guid() const noexcept +{ + switch (eax_fx_slot_index_) + { + case 0: return EAX_REVERB_EFFECT; + case 1: return EAX_CHORUS_EFFECT; + default: return EAX_NULL_GUID; + } +} + +long ALeffectslot::eax_get_eax_default_lock() const noexcept +{ + return eax_fx_slot_index_ < 2 ? EAXFXSLOT_LOCKED : EAXFXSLOT_UNLOCKED; +} + +void ALeffectslot::eax_set_eax_fx_slot_defaults() +{ + eax_eax_fx_slot_.guidLoadEffect = eax_get_eax_default_effect_guid(); + eax_eax_fx_slot_.lVolume = EAXFXSLOT_DEFAULTVOLUME; + eax_eax_fx_slot_.lLock = eax_get_eax_default_lock(); + eax_eax_fx_slot_.ulFlags = EAX40FXSLOT_DEFAULTFLAGS; + eax_eax_fx_slot_.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION; + eax_eax_fx_slot_.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO; +} + +void ALeffectslot::eax_initialize_eax() +{ + eax_set_eax_fx_slot_defaults(); +} + +void ALeffectslot::eax_initialize_lock() +{ + eax_is_locked_ = (eax_fx_slot_index_ < 2); +} + +void ALeffectslot::eax_initialize_effects() +{ + eax_set_fx_slot_effect(); +} + +void ALeffectslot::eax_get_fx_slot_all( + const EaxEaxCall& eax_call) const +{ + switch (eax_call.get_version()) + { + case 4: + eax_call.set_value(eax_eax_fx_slot_); + break; + + case 5: + eax_call.set_value(eax_eax_fx_slot_); + break; + + default: + eax_fail("Unsupported EAX version."); + } +} + +void ALeffectslot::eax_get_fx_slot( + const EaxEaxCall& eax_call) const +{ + switch (eax_call.get_property_id()) + { + case EAXFXSLOT_ALLPARAMETERS: + eax_get_fx_slot_all(eax_call); + break; + + case EAXFXSLOT_LOADEFFECT: + eax_call.set_value(eax_eax_fx_slot_.guidLoadEffect); + break; + + case EAXFXSLOT_VOLUME: + eax_call.set_value(eax_eax_fx_slot_.lVolume); + break; + + case EAXFXSLOT_LOCK: + eax_call.set_value(eax_eax_fx_slot_.lLock); + break; + + case EAXFXSLOT_FLAGS: + eax_call.set_value(eax_eax_fx_slot_.ulFlags); + break; + + case EAXFXSLOT_OCCLUSION: + eax_call.set_value(eax_eax_fx_slot_.lOcclusion); + break; + + case EAXFXSLOT_OCCLUSIONLFRATIO: + eax_call.set_value(eax_eax_fx_slot_.flOcclusionLFRatio); + break; + + default: + eax_fail("Unsupported FX slot property id."); + } +} + +// [[nodiscard]] +bool ALeffectslot::eax_get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_set_id()) + { + case EaxEaxCallPropertySetId::fx_slot: + eax_get_fx_slot(eax_call); + break; + + case EaxEaxCallPropertySetId::fx_slot_effect: + eax_dispatch_effect(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } + + return false; +} + +void ALeffectslot::eax_set_fx_slot_effect( + ALenum al_effect_type) +{ + if(!IsValidEffectType(al_effect_type)) + eax_fail("Unsupported effect."); + + eax_effect_ = nullptr; + eax_effect_ = eax_create_eax_effect(al_effect_type); + + eax_set_effect_slot_effect(*eax_effect_); +} + +void ALeffectslot::eax_set_fx_slot_effect() +{ + auto al_effect_type = ALenum{}; + + if (false) + { + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_NULL_GUID) + { + al_effect_type = AL_EFFECT_NULL; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_AUTOWAH_EFFECT) + { + al_effect_type = AL_EFFECT_AUTOWAH; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_CHORUS_EFFECT) + { + al_effect_type = AL_EFFECT_CHORUS; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_AGCCOMPRESSOR_EFFECT) + { + al_effect_type = AL_EFFECT_COMPRESSOR; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_DISTORTION_EFFECT) + { + al_effect_type = AL_EFFECT_DISTORTION; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_REVERB_EFFECT) + { + al_effect_type = AL_EFFECT_EAXREVERB; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_ECHO_EFFECT) + { + al_effect_type = AL_EFFECT_ECHO; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_EQUALIZER_EFFECT) + { + al_effect_type = AL_EFFECT_EQUALIZER; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_FLANGER_EFFECT) + { + al_effect_type = AL_EFFECT_FLANGER; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_FREQUENCYSHIFTER_EFFECT) + { + al_effect_type = AL_EFFECT_FREQUENCY_SHIFTER; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_PITCHSHIFTER_EFFECT) + { + al_effect_type = AL_EFFECT_PITCH_SHIFTER; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_RINGMODULATOR_EFFECT) + { + al_effect_type = AL_EFFECT_RING_MODULATOR; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_VOCALMORPHER_EFFECT) + { + al_effect_type = AL_EFFECT_VOCAL_MORPHER; + } + else + { + eax_fail("Unsupported effect."); + } + + eax_set_fx_slot_effect(al_effect_type); +} + +void ALeffectslot::eax_set_efx_effect_slot_gain() +{ + const auto gain = level_mb_to_gain( + static_cast(clamp( + eax_eax_fx_slot_.lVolume, + EAXFXSLOT_MINVOLUME, + EAXFXSLOT_MAXVOLUME))); + + eax_set_effect_slot_gain(gain); +} + +void ALeffectslot::eax_set_fx_slot_volume() +{ + eax_set_efx_effect_slot_gain(); +} + +void ALeffectslot::eax_set_effect_slot_send_auto() +{ + eax_set_effect_slot_send_auto((eax_eax_fx_slot_.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0); +} + +void ALeffectslot::eax_set_fx_slot_flags() +{ + eax_set_effect_slot_send_auto(); +} + +void ALeffectslot::eax_set_fx_slot_effect( + const EaxEaxCall& eax_call) +{ + const auto& eax_effect_id = + eax_call.get_value(); + + eax_validate_fx_slot_effect(eax_effect_id); + eax_set_fx_slot_effect(eax_effect_id); +} + +void ALeffectslot::eax_set_fx_slot_volume( + const EaxEaxCall& eax_call) +{ + const auto& eax_volume = + eax_call.get_value(); + + eax_validate_fx_slot_volume(eax_volume); + eax_set_fx_slot_volume(eax_volume); +} + +void ALeffectslot::eax_set_fx_slot_lock( + const EaxEaxCall& eax_call) +{ + const auto& eax_lock = + eax_call.get_value(); + + eax_validate_fx_slot_lock(eax_lock); + eax_set_fx_slot_lock(eax_lock); +} + +void ALeffectslot::eax_set_fx_slot_flags( + const EaxEaxCall& eax_call) +{ + const auto& eax_flags = + eax_call.get_value(); + + eax_validate_fx_slot_flags(eax_flags, eax_call.get_version()); + eax_set_fx_slot_flags(eax_flags); +} + +// [[nodiscard]] +bool ALeffectslot::eax_set_fx_slot_occlusion( + const EaxEaxCall& eax_call) +{ + const auto& eax_occlusion = + eax_call.get_value(); + + eax_validate_fx_slot_occlusion(eax_occlusion); + + return eax_set_fx_slot_occlusion(eax_occlusion); +} + +// [[nodiscard]] +bool ALeffectslot::eax_set_fx_slot_occlusion_lf_ratio( + const EaxEaxCall& eax_call) +{ + const auto& eax_occlusion_lf_ratio = + eax_call.get_value(); + + eax_validate_fx_slot_occlusion_lf_ratio(eax_occlusion_lf_ratio); + + return eax_set_fx_slot_occlusion_lf_ratio(eax_occlusion_lf_ratio); +} + +// [[nodiscard]] +bool ALeffectslot::eax_set_fx_slot_all( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_version()) + { + case 4: + { + const auto& eax_all = + eax_call.get_value(); + + eax_validate_fx_slot_all(eax_all, eax_call.get_version()); + eax_set_fx_slot_all(eax_all); + + return false; + } + + case 5: + { + const auto& eax_all = + eax_call.get_value(); + + eax_validate_fx_slot_all(eax_all, eax_call.get_version()); + return eax_set_fx_slot_all(eax_all); + } + + default: + eax_fail("Unsupported EAX version."); + } +} + +bool ALeffectslot::eax_set_fx_slot( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXFXSLOT_NONE: + return false; + + case EAXFXSLOT_ALLPARAMETERS: + return eax_set_fx_slot_all(eax_call); + + case EAXFXSLOT_LOADEFFECT: + eax_set_fx_slot_effect(eax_call); + return false; + + case EAXFXSLOT_VOLUME: + eax_set_fx_slot_volume(eax_call); + return false; + + case EAXFXSLOT_LOCK: + eax_set_fx_slot_lock(eax_call); + return false; + + case EAXFXSLOT_FLAGS: + eax_set_fx_slot_flags(eax_call); + return false; + + case EAXFXSLOT_OCCLUSION: + return eax_set_fx_slot_occlusion(eax_call); + + case EAXFXSLOT_OCCLUSIONLFRATIO: + return eax_set_fx_slot_occlusion_lf_ratio(eax_call); + + + default: + eax_fail("Unsupported FX slot property id."); + } +} + +// [[nodiscard]] +bool ALeffectslot::eax_set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_set_id()) + { + case EaxEaxCallPropertySetId::fx_slot: + return eax_set_fx_slot(eax_call); + + case EaxEaxCallPropertySetId::fx_slot_effect: + eax_dispatch_effect(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } + + return false; +} + +void ALeffectslot::eax_dispatch_effect(const EaxEaxCall& eax_call) +{ if(eax_effect_) eax_effect_->dispatch(eax_call); } + +void ALeffectslot::eax_apply_deferred() +{ + /* The other FXSlot properties (volume, effect, etc) aren't deferred? */ + + auto is_changed = false; + if(eax_effect_) + is_changed = eax_effect_->apply_deferred(); + if(is_changed) + eax_set_effect_slot_effect(*eax_effect_); +} + + +void ALeffectslot::eax_set_effect_slot_effect(EaxEffect &effect) +{ +#define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_EFFECT] " + + const auto error = initEffect(effect.al_effect_type_, effect.al_effect_props_, eax_al_context_); + if (error != AL_NO_ERROR) + { + ERR(EAX_PREFIX "%s\n", "Failed to initialize an effect."); + return; + } + + if (mState == SlotState::Initial) + { + mPropsDirty = false; + updateProps(eax_al_context_); + + auto effect_slot_ptr = this; + + AddActiveEffectSlots({&effect_slot_ptr, 1}, eax_al_context_); + mState = SlotState::Playing; + + return; + } + + UpdateProps(this, eax_al_context_); + +#undef EAX_PREFIX +} + +void ALeffectslot::eax_set_effect_slot_send_auto( + bool is_send_auto) +{ + if(AuxSendAuto == is_send_auto) + return; + + AuxSendAuto = is_send_auto; + UpdateProps(this, eax_al_context_); +} + +void ALeffectslot::eax_set_effect_slot_gain( + ALfloat gain) +{ +#define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_GAIN] " + + if(gain == Gain) + return; + if(gain < 0.0f || gain > 1.0f) + ERR(EAX_PREFIX "Gain out of range (%f)\n", gain); + + Gain = clampf(gain, 0.0f, 1.0f); + UpdateProps(this, eax_al_context_); + +#undef EAX_PREFIX +} + + +void ALeffectslot::EaxDeleter::operator()(ALeffectslot* effect_slot) +{ + assert(effect_slot); + eax_delete_al_effect_slot(*effect_slot->eax_al_context_, *effect_slot); +} + + +EaxAlEffectSlotUPtr eax_create_al_effect_slot( + ALCcontext& context) +{ +#define EAX_PREFIX "[EAX_MAKE_EFFECT_SLOT] " + + std::unique_lock effect_slot_lock{context.mEffectSlotLock}; + + auto& device = *context.mALDevice; + + if (context.mNumEffectSlots == device.AuxiliaryEffectSlotMax) + { + ERR(EAX_PREFIX "%s\n", "Out of memory."); + return nullptr; + } + + if (!EnsureEffectSlots(&context, 1)) + { + ERR(EAX_PREFIX "%s\n", "Failed to ensure."); + return nullptr; + } + + auto effect_slot = EaxAlEffectSlotUPtr{AllocEffectSlot(&context)}; + if (!effect_slot) + { + ERR(EAX_PREFIX "%s\n", "Failed to allocate."); + return nullptr; + } + + return effect_slot; + +#undef EAX_PREFIX +} + +void eax_delete_al_effect_slot( + ALCcontext& context, + ALeffectslot& effect_slot) +{ +#define EAX_PREFIX "[EAX_DELETE_EFFECT_SLOT] " + + std::lock_guard effect_slot_lock{context.mEffectSlotLock}; + + if (ReadRef(effect_slot.ref) != 0) + { + ERR(EAX_PREFIX "Deleting in-use effect slot %u.\n", effect_slot.id); + return; + } + + auto effect_slot_ptr = &effect_slot; + + RemoveActiveEffectSlots({&effect_slot_ptr, 1}, &context); + FreeEffectSlot(&context, &effect_slot); + +#undef EAX_PREFIX +} +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/auxeffectslot.h b/modules/openal-soft/al/auxeffectslot.h new file mode 100644 index 0000000..ca0dcd3 --- /dev/null +++ b/modules/openal-soft/al/auxeffectslot.h @@ -0,0 +1,277 @@ +#ifndef AL_AUXEFFECTSLOT_H +#define AL_AUXEFFECTSLOT_H + +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/efx.h" + +#include "alc/device.h" +#include "alc/effects/base.h" +#include "almalloc.h" +#include "atomic.h" +#include "core/effectslot.h" +#include "intrusive_ptr.h" +#include "vector.h" + +#ifdef ALSOFT_EAX +#include + +#include "eax_eax_call.h" +#include "eax_effect.h" +#include "eax_fx_slot_index.h" +#endif // ALSOFT_EAX + +struct ALbuffer; +struct ALeffect; +struct WetBuffer; + + +enum class SlotState : ALenum { + Initial = AL_INITIAL, + Playing = AL_PLAYING, + Stopped = AL_STOPPED, +}; + +struct ALeffectslot { + float Gain{1.0f}; + bool AuxSendAuto{true}; + ALeffectslot *Target{nullptr}; + ALbuffer *Buffer{nullptr}; + + struct { + EffectSlotType Type{EffectSlotType::None}; + EffectProps Props{}; + + al::intrusive_ptr State; + } Effect; + + bool mPropsDirty{true}; + + SlotState mState{SlotState::Initial}; + + RefCount ref{0u}; + + EffectSlot mSlot; + + /* Self ID */ + ALuint id{}; + + ALeffectslot(); + ALeffectslot(const ALeffectslot&) = delete; + ALeffectslot& operator=(const ALeffectslot&) = delete; + ~ALeffectslot(); + + ALenum initEffect(ALenum effectType, const EffectProps &effectProps, ALCcontext *context); + void updateProps(ALCcontext *context); + + /* This can be new'd for the context's default effect slot. */ + DEF_NEWDEL(ALeffectslot) + + +#ifdef ALSOFT_EAX +public: + void eax_initialize( + ALCcontext& al_context, + EaxFxSlotIndexValue index); + + const EAX50FXSLOTPROPERTIES& eax_get_eax_fx_slot() const noexcept; + + + // [[nodiscard]] + bool eax_dispatch(const EaxEaxCall& eax_call) + { return eax_call.is_get() ? eax_get(eax_call) : eax_set(eax_call); } + + + void eax_unlock_legacy() noexcept; + + void eax_commit() { eax_apply_deferred(); } + +private: + ALCcontext* eax_al_context_{}; + + EaxFxSlotIndexValue eax_fx_slot_index_{}; + + EAX50FXSLOTPROPERTIES eax_eax_fx_slot_{}; + + EaxEffectUPtr eax_effect_{}; + bool eax_is_locked_{}; + + + [[noreturn]] + static void eax_fail( + const char* message); + + + GUID eax_get_eax_default_effect_guid() const noexcept; + long eax_get_eax_default_lock() const noexcept; + + void eax_set_eax_fx_slot_defaults(); + + void eax_initialize_eax(); + + void eax_initialize_lock(); + + + void eax_initialize_effects(); + + + void eax_get_fx_slot_all( + const EaxEaxCall& eax_call) const; + + void eax_get_fx_slot( + const EaxEaxCall& eax_call) const; + + // [[nodiscard]] + bool eax_get( + const EaxEaxCall& eax_call); + + + void eax_set_fx_slot_effect( + ALenum effect_type); + + void eax_set_fx_slot_effect(); + + + void eax_set_efx_effect_slot_gain(); + + void eax_set_fx_slot_volume(); + + + void eax_set_effect_slot_send_auto(); + + void eax_set_fx_slot_flags(); + + + void eax_ensure_is_unlocked() const; + + + void eax_validate_fx_slot_effect( + const GUID& eax_effect_id); + + void eax_validate_fx_slot_volume( + long eax_volume); + + void eax_validate_fx_slot_lock( + long eax_lock); + + void eax_validate_fx_slot_flags( + unsigned long eax_flags, + int eax_version); + + void eax_validate_fx_slot_occlusion( + long eax_occlusion); + + void eax_validate_fx_slot_occlusion_lf_ratio( + float eax_occlusion_lf_ratio); + + void eax_validate_fx_slot_all( + const EAX40FXSLOTPROPERTIES& fx_slot, + int eax_version); + + void eax_validate_fx_slot_all( + const EAX50FXSLOTPROPERTIES& fx_slot, + int eax_version); + + + void eax_set_fx_slot_effect( + const GUID& eax_effect_id); + + void eax_set_fx_slot_volume( + long eax_volume); + + void eax_set_fx_slot_lock( + long eax_lock); + + void eax_set_fx_slot_flags( + unsigned long eax_flags); + + // [[nodiscard]] + bool eax_set_fx_slot_occlusion( + long eax_occlusion); + + // [[nodiscard]] + bool eax_set_fx_slot_occlusion_lf_ratio( + float eax_occlusion_lf_ratio); + + void eax_set_fx_slot_all( + const EAX40FXSLOTPROPERTIES& eax_fx_slot); + + // [[nodiscard]] + bool eax_set_fx_slot_all( + const EAX50FXSLOTPROPERTIES& eax_fx_slot); + + + void eax_set_fx_slot_effect( + const EaxEaxCall& eax_call); + + void eax_set_fx_slot_volume( + const EaxEaxCall& eax_call); + + void eax_set_fx_slot_lock( + const EaxEaxCall& eax_call); + + void eax_set_fx_slot_flags( + const EaxEaxCall& eax_call); + + // [[nodiscard]] + bool eax_set_fx_slot_occlusion( + const EaxEaxCall& eax_call); + + // [[nodiscard]] + bool eax_set_fx_slot_occlusion_lf_ratio( + const EaxEaxCall& eax_call); + + // [[nodiscard]] + bool eax_set_fx_slot_all( + const EaxEaxCall& eax_call); + + bool eax_set_fx_slot( + const EaxEaxCall& eax_call); + + void eax_apply_deferred(); + + // [[nodiscard]] + bool eax_set( + const EaxEaxCall& eax_call); + + + void eax_dispatch_effect( + const EaxEaxCall& eax_call); + + + // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_EFFECT, effect)` + void eax_set_effect_slot_effect(EaxEffect &effect); + + // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_AUXILIARY_SEND_AUTO, value)` + void eax_set_effect_slot_send_auto(bool is_send_auto); + + // `alAuxiliaryEffectSlotf(effect_slot, AL_EFFECTSLOT_GAIN, gain)` + void eax_set_effect_slot_gain(ALfloat gain); + +public: + class EaxDeleter { + public: + void operator()(ALeffectslot *effect_slot); + }; // EaxAlEffectSlotDeleter +#endif // ALSOFT_EAX +}; + +void UpdateAllEffectSlotProps(ALCcontext *context); + +#ifdef ALSOFT_EAX + +using EaxAlEffectSlotUPtr = std::unique_ptr; + + +EaxAlEffectSlotUPtr eax_create_al_effect_slot( + ALCcontext& context); + +void eax_delete_al_effect_slot( + ALCcontext& context, + ALeffectslot& effect_slot); +#endif // ALSOFT_EAX + +#endif diff --git a/modules/openal-soft/al/buffer.cpp b/modules/openal-soft/al/buffer.cpp new file mode 100644 index 0000000..1340710 --- /dev/null +++ b/modules/openal-soft/al/buffer.cpp @@ -0,0 +1,1862 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "buffer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "albit.h" +#include "albyte.h" +#include "alc/context.h" +#include "alc/device.h" +#include "alc/inprogext.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "aloptional.h" +#include "atomic.h" +#include "core/except.h" +#include "core/logging.h" +#include "core/voice.h" +#include "opthelpers.h" + +#ifdef ALSOFT_EAX +#include "eax_globals.h" +#include "eax_x_ram.h" +#endif // ALSOFT_EAX + + +namespace { + +constexpr int MaxAdpcmChannels{2}; + +/* IMA ADPCM Stepsize table */ +constexpr int IMAStep_size[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, + 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, + 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, + 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, + 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, + 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, + 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493,10442, + 11487,12635,13899,15289,16818,18500,20350,22358,24633,27086,29794, + 32767 +}; + +/* IMA4 ADPCM Codeword decode table */ +constexpr int IMA4Codeword[16] = { + 1, 3, 5, 7, 9, 11, 13, 15, + -1,-3,-5,-7,-9,-11,-13,-15, +}; + +/* IMA4 ADPCM Step index adjust decode table */ +constexpr int IMA4Index_adjust[16] = { + -1,-1,-1,-1, 2, 4, 6, 8, + -1,-1,-1,-1, 2, 4, 6, 8 +}; + + +/* MSADPCM Adaption table */ +constexpr int MSADPCMAdaption[16] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 +}; + +/* MSADPCM Adaption Coefficient tables */ +constexpr int MSADPCMAdaptionCoeff[7][2] = { + { 256, 0 }, + { 512, -256 }, + { 0, 0 }, + { 192, 64 }, + { 240, 0 }, + { 460, -208 }, + { 392, -232 } +}; + + +void DecodeIMA4Block(int16_t *dst, const al::byte *src, size_t numchans, size_t align) +{ + int sample[MaxAdpcmChannels]{}; + int index[MaxAdpcmChannels]{}; + ALuint code[MaxAdpcmChannels]{}; + + for(size_t c{0};c < numchans;c++) + { + sample[c] = src[0] | (src[1]<<8); + sample[c] = (sample[c]^0x8000) - 32768; + src += 2; + index[c] = src[0] | (src[1]<<8); + index[c] = clampi((index[c]^0x8000) - 32768, 0, 88); + src += 2; + + *(dst++) = static_cast(sample[c]); + } + + for(size_t i{1};i < align;i++) + { + if((i&7) == 1) + { + for(size_t c{0};c < numchans;c++) + { + code[c] = ALuint{src[0]} | (ALuint{src[1]}<< 8) | (ALuint{src[2]}<<16) + | (ALuint{src[3]}<<24); + src += 4; + } + } + + for(size_t c{0};c < numchans;c++) + { + const ALuint nibble{code[c]&0xf}; + code[c] >>= 4; + + sample[c] += IMA4Codeword[nibble] * IMAStep_size[index[c]] / 8; + sample[c] = clampi(sample[c], -32768, 32767); + + index[c] += IMA4Index_adjust[nibble]; + index[c] = clampi(index[c], 0, 88); + + *(dst++) = static_cast(sample[c]); + } + } +} + +void DecodeMSADPCMBlock(int16_t *dst, const al::byte *src, size_t numchans, size_t align) +{ + uint8_t blockpred[MaxAdpcmChannels]{}; + int delta[MaxAdpcmChannels]{}; + int16_t samples[MaxAdpcmChannels][2]{}; + + for(size_t c{0};c < numchans;c++) + { + blockpred[c] = std::min(src[0], 6); + ++src; + } + for(size_t c{0};c < numchans;c++) + { + delta[c] = src[0] | (src[1]<<8); + delta[c] = (delta[c]^0x8000) - 32768; + src += 2; + } + for(size_t c{0};c < numchans;c++) + { + samples[c][0] = static_cast(src[0] | (src[1]<<8)); + src += 2; + } + for(size_t c{0};c < numchans;c++) + { + samples[c][1] = static_cast(src[0] | (src[1]<<8)); + src += 2; + } + + /* Second sample is written first. */ + for(size_t c{0};c < numchans;c++) + *(dst++) = samples[c][1]; + for(size_t c{0};c < numchans;c++) + *(dst++) = samples[c][0]; + + int num{0}; + for(size_t i{2};i < align;i++) + { + for(size_t c{0};c < numchans;c++) + { + /* Read the nibble (first is in the upper bits). */ + al::byte nibble; + if(!(num++ & 1)) + nibble = *src >> 4; + else + nibble = *(src++) & 0x0f; + + int pred{(samples[c][0]*MSADPCMAdaptionCoeff[blockpred[c]][0] + + samples[c][1]*MSADPCMAdaptionCoeff[blockpred[c]][1]) / 256}; + pred += ((nibble^0x08) - 0x08) * delta[c]; + pred = clampi(pred, -32768, 32767); + + samples[c][1] = samples[c][0]; + samples[c][0] = static_cast(pred); + + delta[c] = (MSADPCMAdaption[nibble] * delta[c]) / 256; + delta[c] = maxi(16, delta[c]); + + *(dst++) = static_cast(pred); + } + } +} + +void Convert_int16_ima4(int16_t *dst, const al::byte *src, size_t numchans, size_t len, + size_t align) +{ + assert(numchans <= MaxAdpcmChannels); + const size_t byte_align{((align-1)/2 + 4) * numchans}; + + len /= align; + while(len--) + { + DecodeIMA4Block(dst, src, numchans, align); + src += byte_align; + dst += align*numchans; + } +} + +void Convert_int16_msadpcm(int16_t *dst, const al::byte *src, size_t numchans, size_t len, + size_t align) +{ + assert(numchans <= MaxAdpcmChannels); + const size_t byte_align{((align-2)/2 + 7) * numchans}; + + len /= align; + while(len--) + { + DecodeMSADPCMBlock(dst, src, numchans, align); + src += byte_align; + dst += align*numchans; + } +} + + +ALuint BytesFromUserFmt(UserFmtType type) noexcept +{ + switch(type) + { + case UserFmtUByte: return sizeof(uint8_t); + case UserFmtShort: return sizeof(int16_t); + case UserFmtFloat: return sizeof(float); + case UserFmtDouble: return sizeof(double); + case UserFmtMulaw: return sizeof(uint8_t); + case UserFmtAlaw: return sizeof(uint8_t); + case UserFmtIMA4: break; /* not handled here */ + case UserFmtMSADPCM: break; /* not handled here */ + } + return 0; +} +ALuint ChannelsFromUserFmt(UserFmtChannels chans, ALuint ambiorder) noexcept +{ + switch(chans) + { + case UserFmtMono: return 1; + case UserFmtStereo: return 2; + case UserFmtRear: return 2; + case UserFmtQuad: return 4; + case UserFmtX51: return 6; + case UserFmtX61: return 7; + case UserFmtX71: return 8; + case UserFmtBFormat2D: return (ambiorder*2) + 1; + case UserFmtBFormat3D: return (ambiorder+1) * (ambiorder+1); + case UserFmtUHJ2: return 2; + case UserFmtUHJ3: return 3; + case UserFmtUHJ4: return 4; + } + return 0; +} + +al::optional AmbiLayoutFromEnum(ALenum layout) +{ + switch(layout) + { + case AL_FUMA_SOFT: return al::make_optional(AmbiLayout::FuMa); + case AL_ACN_SOFT: return al::make_optional(AmbiLayout::ACN); + } + return al::nullopt; +} +ALenum EnumFromAmbiLayout(AmbiLayout layout) +{ + switch(layout) + { + 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))}; +} + +al::optional AmbiScalingFromEnum(ALenum scale) +{ + switch(scale) + { + case AL_FUMA_SOFT: return al::make_optional(AmbiScaling::FuMa); + case AL_SN3D_SOFT: return al::make_optional(AmbiScaling::SN3D); + case AL_N3D_SOFT: return al::make_optional(AmbiScaling::N3D); + } + return al::nullopt; +} +ALenum EnumFromAmbiScaling(AmbiScaling scale) +{ + switch(scale) + { + case AmbiScaling::FuMa: return AL_FUMA_SOFT; + case AmbiScaling::SN3D: return AL_SN3D_SOFT; + case AmbiScaling::N3D: return AL_N3D_SOFT; + case AmbiScaling::UHJ: break; + } + throw std::runtime_error{"Invalid AmbiScaling: "+std::to_string(int(scale))}; +} + +al::optional FmtFromUserFmt(UserFmtChannels chans) +{ + switch(chans) + { + case UserFmtMono: return al::make_optional(FmtMono); + case UserFmtStereo: return al::make_optional(FmtStereo); + case UserFmtRear: return al::make_optional(FmtRear); + case UserFmtQuad: return al::make_optional(FmtQuad); + case UserFmtX51: return al::make_optional(FmtX51); + case UserFmtX61: return al::make_optional(FmtX61); + case UserFmtX71: return al::make_optional(FmtX71); + case UserFmtBFormat2D: return al::make_optional(FmtBFormat2D); + case UserFmtBFormat3D: return al::make_optional(FmtBFormat3D); + case UserFmtUHJ2: return al::make_optional(FmtUHJ2); + case UserFmtUHJ3: return al::make_optional(FmtUHJ3); + case UserFmtUHJ4: return al::make_optional(FmtUHJ4); + } + return al::nullopt; +} +al::optional FmtFromUserFmt(UserFmtType type) +{ + switch(type) + { + case UserFmtUByte: return al::make_optional(FmtUByte); + case UserFmtShort: return al::make_optional(FmtShort); + case UserFmtFloat: return al::make_optional(FmtFloat); + case UserFmtDouble: return al::make_optional(FmtDouble); + case UserFmtMulaw: return al::make_optional(FmtMulaw); + case UserFmtAlaw: return al::make_optional(FmtAlaw); + /* ADPCM not handled here. */ + case UserFmtIMA4: break; + case UserFmtMSADPCM: break; + } + return al::nullopt; +} + + +#ifdef ALSOFT_EAX +bool eax_x_ram_check_availability(const ALCdevice &device, const ALbuffer &buffer, + const ALuint newsize) noexcept +{ + ALuint freemem{device.eax_x_ram_free_size}; + /* If the buffer is currently in "hardware", add its memory to the free + * pool since it'll be "replaced". + */ + if(buffer.eax_x_ram_is_hardware) + freemem += buffer.OriginalSize; + return freemem >= newsize; +} + +void eax_x_ram_apply(ALCdevice &device, ALbuffer &buffer) noexcept +{ + if(buffer.eax_x_ram_is_hardware) + return; + + if(device.eax_x_ram_free_size >= buffer.OriginalSize) + { + device.eax_x_ram_free_size -= buffer.OriginalSize; + buffer.eax_x_ram_is_hardware = true; + } +} + +void eax_x_ram_clear(ALCdevice& al_device, ALbuffer& al_buffer) +{ + if(al_buffer.eax_x_ram_is_hardware) + al_device.eax_x_ram_free_size += al_buffer.OriginalSize; + al_buffer.eax_x_ram_is_hardware = false; +} +#endif // ALSOFT_EAX + + +constexpr ALbitfieldSOFT INVALID_STORAGE_MASK{~unsigned(AL_MAP_READ_BIT_SOFT | + AL_MAP_WRITE_BIT_SOFT | AL_MAP_PERSISTENT_BIT_SOFT | AL_PRESERVE_DATA_BIT_SOFT)}; +constexpr ALbitfieldSOFT MAP_READ_WRITE_FLAGS{AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT}; +constexpr ALbitfieldSOFT INVALID_MAP_FLAGS{~unsigned(AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT | + AL_MAP_PERSISTENT_BIT_SOFT)}; + + +bool EnsureBuffers(ALCdevice *device, size_t needed) +{ + size_t count{std::accumulate(device->BufferList.cbegin(), device->BufferList.cend(), size_t{0}, + [](size_t cur, const BufferSubList &sublist) noexcept -> size_t + { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; + + while(needed > count) + { + if UNLIKELY(device->BufferList.size() >= 1<<25) + return false; + + device->BufferList.emplace_back(); + auto sublist = device->BufferList.end() - 1; + sublist->FreeMask = ~0_u64; + sublist->Buffers = static_cast(al_calloc(alignof(ALbuffer), sizeof(ALbuffer)*64)); + if UNLIKELY(!sublist->Buffers) + { + device->BufferList.pop_back(); + return false; + } + count += 64; + } + return true; +} + +ALbuffer *AllocBuffer(ALCdevice *device) +{ + auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(), + [](const BufferSubList &entry) noexcept -> bool + { return entry.FreeMask != 0; }); + auto lidx = static_cast(std::distance(device->BufferList.begin(), sublist)); + auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); + ASSUME(slidx < 64); + + ALbuffer *buffer{al::construct_at(sublist->Buffers + slidx)}; + + /* Add 1 to avoid buffer ID 0. */ + buffer->id = ((lidx<<6) | slidx) + 1; + + sublist->FreeMask &= ~(1_u64 << slidx); + + return buffer; +} + +void FreeBuffer(ALCdevice *device, ALbuffer *buffer) +{ +#ifdef ALSOFT_EAX + eax_x_ram_clear(*device, *buffer); +#endif // ALSOFT_EAX + + const ALuint id{buffer->id - 1}; + const size_t lidx{id >> 6}; + const ALuint slidx{id & 0x3f}; + + al::destroy_at(buffer); + + device->BufferList[lidx].FreeMask |= 1_u64 << slidx; +} + +inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) +{ + const size_t lidx{(id-1) >> 6}; + const ALuint slidx{(id-1) & 0x3f}; + + if UNLIKELY(lidx >= device->BufferList.size()) + return nullptr; + BufferSubList &sublist = device->BufferList[lidx]; + if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + return nullptr; + return sublist.Buffers + slidx; +} + + +ALuint SanitizeAlignment(UserFmtType type, ALuint align) +{ + if(align == 0) + { + if(type == UserFmtIMA4) + { + /* Here is where things vary: + * nVidia and Apple use 64+1 sample frames per block -> block_size=36 bytes per channel + * Most PC sound software uses 2040+1 sample frames per block -> block_size=1024 bytes per channel + */ + return 65; + } + if(type == UserFmtMSADPCM) + return 64; + return 1; + } + + if(type == UserFmtIMA4) + { + /* IMA4 block alignment must be a multiple of 8, plus 1. */ + if((align&7) == 1) return static_cast(align); + return 0; + } + if(type == UserFmtMSADPCM) + { + /* MSADPCM block alignment must be a multiple of 2. */ + if((align&1) == 0) return static_cast(align); + return 0; + } + + return static_cast(align); +} + + +const ALchar *NameFromUserFmtType(UserFmtType type) +{ + switch(type) + { + case UserFmtUByte: return "UInt8"; + case UserFmtShort: return "Int16"; + case UserFmtFloat: return "Float32"; + case UserFmtDouble: return "Float64"; + case UserFmtMulaw: return "muLaw"; + case UserFmtAlaw: return "aLaw"; + case UserFmtIMA4: return "IMA4 ADPCM"; + case UserFmtMSADPCM: return "MSADPCM"; + } + return ""; +} + +/** Loads the specified data into the buffer, using the specified format. */ +void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, + UserFmtChannels SrcChannels, UserFmtType SrcType, const al::byte *SrcData, + ALbitfieldSOFT access) +{ + if UNLIKELY(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) + SETERR_RETURN(context, AL_INVALID_OPERATION,, "Modifying storage for in-use buffer %u", + ALBuf->id); + + /* Currently no channel configurations need to be converted. */ + auto DstChannels = FmtFromUserFmt(SrcChannels); + if UNLIKELY(!DstChannels) + SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format"); + + /* IMA4 and MSADPCM convert to 16-bit short. + * + * TODO: Currently we can only map samples when they're not converted. To + * allow it would need some kind of double-buffering to hold onto a copy of + * the original data. + */ + if((access&MAP_READ_WRITE_FLAGS)) + { + if UNLIKELY(SrcType == UserFmtIMA4 || SrcType == UserFmtMSADPCM) + SETERR_RETURN(context, AL_INVALID_VALUE,, "%s samples cannot be mapped", + NameFromUserFmtType(SrcType)); + } + auto DstType = (SrcType == UserFmtIMA4 || SrcType == UserFmtMSADPCM) + ? al::make_optional(FmtShort) : FmtFromUserFmt(SrcType); + if UNLIKELY(!DstType) + SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format"); + + const ALuint unpackalign{ALBuf->UnpackAlign}; + const ALuint align{SanitizeAlignment(SrcType, unpackalign)}; + if UNLIKELY(align < 1) + SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid unpack alignment %u for %s samples", + unpackalign, NameFromUserFmtType(SrcType)); + + const ALuint ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder : + (IsUHJ(*DstChannels) ? 1 : 0)}; + + if((access&AL_PRESERVE_DATA_BIT_SOFT)) + { + /* Can only preserve data with the same format and alignment. */ + if UNLIKELY(ALBuf->mChannels != *DstChannels || ALBuf->OriginalType != SrcType) + SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched format"); + if UNLIKELY(ALBuf->OriginalAlign != align) + SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched alignment"); + if(ALBuf->mAmbiOrder != ambiorder) + SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched order"); + } + + /* Convert the input/source size in bytes to sample frames using the unpack + * block alignment. + */ + const ALuint SrcByteAlign{ChannelsFromUserFmt(SrcChannels, ambiorder) * + ((SrcType == UserFmtIMA4) ? (align-1)/2 + 4 : + (SrcType == UserFmtMSADPCM) ? (align-2)/2 + 7 : + (align * BytesFromUserFmt(SrcType)))}; + if UNLIKELY((size%SrcByteAlign) != 0) + SETERR_RETURN(context, AL_INVALID_VALUE,, + "Data size %d is not a multiple of frame size %d (%d unpack alignment)", + size, SrcByteAlign, align); + + if UNLIKELY(size/SrcByteAlign > std::numeric_limits::max()/align) + SETERR_RETURN(context, AL_OUT_OF_MEMORY,, + "Buffer size overflow, %d blocks x %d samples per block", size/SrcByteAlign, align); + const ALuint frames{size / SrcByteAlign * align}; + + /* Convert the sample frames to the number of bytes needed for internal + * storage. + */ + ALuint NumChannels{ChannelsFromFmt(*DstChannels, ambiorder)}; + ALuint FrameSize{NumChannels * BytesFromFmt(*DstType)}; + if UNLIKELY(frames > std::numeric_limits::max()/FrameSize) + SETERR_RETURN(context, AL_OUT_OF_MEMORY,, + "Buffer size overflow, %d frames x %d bytes per frame", frames, FrameSize); + size_t newsize{static_cast(frames) * FrameSize}; + +#ifdef ALSOFT_EAX + if(ALBuf->eax_x_ram_mode == AL_STORAGE_HARDWARE) + { + ALCdevice &device = *context->mALDevice; + if(!eax_x_ram_check_availability(device, *ALBuf, size)) + SETERR_RETURN(context, AL_OUT_OF_MEMORY,, + "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, size); + } +#endif + + /* Round up to the next 16-byte multiple. This could reallocate only when + * increasing or the new size is less than half the current, but then the + * buffer's AL_SIZE would not be very reliable for accounting buffer memory + * usage, and reporting the real size could cause problems for apps that + * use AL_SIZE to try to get the buffer's play length. + */ + newsize = RoundUp(newsize, 16); + if(newsize != ALBuf->mData.size()) + { + auto newdata = al::vector(newsize, al::byte{}); + if((access&AL_PRESERVE_DATA_BIT_SOFT)) + { + const size_t tocopy{minz(newdata.size(), ALBuf->mData.size())}; + std::copy_n(ALBuf->mData.begin(), tocopy, newdata.begin()); + } + newdata.swap(ALBuf->mData); + } + + if(SrcType == UserFmtIMA4) + { + assert(*DstType == FmtShort); + if(SrcData != nullptr && !ALBuf->mData.empty()) + Convert_int16_ima4(reinterpret_cast(ALBuf->mData.data()), SrcData, + NumChannels, frames, align); + ALBuf->OriginalAlign = align; + } + else if(SrcType == UserFmtMSADPCM) + { + assert(*DstType == FmtShort); + if(SrcData != nullptr && !ALBuf->mData.empty()) + Convert_int16_msadpcm(reinterpret_cast(ALBuf->mData.data()), SrcData, + NumChannels, frames, align); + ALBuf->OriginalAlign = align; + } + else + { + assert(DstType.has_value()); + if(SrcData != nullptr && !ALBuf->mData.empty()) + std::copy_n(SrcData, frames*FrameSize, ALBuf->mData.begin()); + ALBuf->OriginalAlign = 1; + } + ALBuf->OriginalSize = size; + ALBuf->OriginalType = SrcType; + + ALBuf->Access = access; + + ALBuf->mSampleRate = static_cast(freq); + ALBuf->mChannels = *DstChannels; + ALBuf->mType = *DstType; + ALBuf->mAmbiOrder = ambiorder; + + ALBuf->mCallback = nullptr; + ALBuf->mUserData = nullptr; + + ALBuf->mSampleLen = frames; + ALBuf->mLoopStart = 0; + ALBuf->mLoopEnd = ALBuf->mSampleLen; + +#ifdef ALSOFT_EAX + if(eax_g_is_enabled && ALBuf->eax_x_ram_mode != AL_STORAGE_ACCESSIBLE) + eax_x_ram_apply(*context->mALDevice, *ALBuf); +#endif +} + +/** Prepares the buffer to use the specified callback, using the specified format. */ +void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, + UserFmtChannels SrcChannels, UserFmtType SrcType, ALBUFFERCALLBACKTYPESOFT callback, + void *userptr) +{ + if UNLIKELY(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) + SETERR_RETURN(context, AL_INVALID_OPERATION,, "Modifying callback for in-use buffer %u", + ALBuf->id); + + /* Currently no channel configurations need to be converted. */ + auto DstChannels = FmtFromUserFmt(SrcChannels); + if UNLIKELY(!DstChannels) + SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid format"); + + /* IMA4 and MSADPCM convert to 16-bit short. Not supported with callbacks. */ + auto DstType = FmtFromUserFmt(SrcType); + if UNLIKELY(!DstType) + SETERR_RETURN(context, AL_INVALID_ENUM,, "Unsupported callback format"); + + const ALuint ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder : + (IsUHJ(*DstChannels) ? 1 : 0)}; + + static constexpr uint line_size{BufferLineSize + MaxPostVoiceLoad}; + al::vector(FrameSizeFromFmt(*DstChannels, *DstType, ambiorder) * + size_t{line_size}).swap(ALBuf->mData); + +#ifdef ALSOFT_EAX + eax_x_ram_clear(*context->mALDevice, *ALBuf); +#endif + + ALBuf->mCallback = callback; + ALBuf->mUserData = userptr; + + ALBuf->OriginalType = SrcType; + ALBuf->OriginalSize = 0; + ALBuf->OriginalAlign = 1; + ALBuf->Access = 0; + + ALBuf->mSampleRate = static_cast(freq); + ALBuf->mChannels = *DstChannels; + ALBuf->mType = *DstType; + ALBuf->mAmbiOrder = ambiorder; + + ALBuf->mSampleLen = 0; + ALBuf->mLoopStart = 0; + ALBuf->mLoopEnd = ALBuf->mSampleLen; +} + + +struct DecompResult { UserFmtChannels channels; UserFmtType type; }; +al::optional DecomposeUserFormat(ALenum format) +{ + struct FormatMap { + ALenum format; + UserFmtChannels channels; + UserFmtType type; + }; + static const std::array UserFmtList{{ + { AL_FORMAT_MONO8, UserFmtMono, UserFmtUByte }, + { AL_FORMAT_MONO16, UserFmtMono, UserFmtShort }, + { AL_FORMAT_MONO_FLOAT32, UserFmtMono, UserFmtFloat }, + { AL_FORMAT_MONO_DOUBLE_EXT, UserFmtMono, UserFmtDouble }, + { AL_FORMAT_MONO_IMA4, UserFmtMono, UserFmtIMA4 }, + { AL_FORMAT_MONO_MSADPCM_SOFT, UserFmtMono, UserFmtMSADPCM }, + { AL_FORMAT_MONO_MULAW, UserFmtMono, UserFmtMulaw }, + { AL_FORMAT_MONO_ALAW_EXT, UserFmtMono, UserFmtAlaw }, + + { AL_FORMAT_STEREO8, UserFmtStereo, UserFmtUByte }, + { AL_FORMAT_STEREO16, UserFmtStereo, UserFmtShort }, + { AL_FORMAT_STEREO_FLOAT32, UserFmtStereo, UserFmtFloat }, + { AL_FORMAT_STEREO_DOUBLE_EXT, UserFmtStereo, UserFmtDouble }, + { AL_FORMAT_STEREO_IMA4, UserFmtStereo, UserFmtIMA4 }, + { AL_FORMAT_STEREO_MSADPCM_SOFT, UserFmtStereo, UserFmtMSADPCM }, + { AL_FORMAT_STEREO_MULAW, UserFmtStereo, UserFmtMulaw }, + { AL_FORMAT_STEREO_ALAW_EXT, UserFmtStereo, UserFmtAlaw }, + + { AL_FORMAT_REAR8, UserFmtRear, UserFmtUByte }, + { AL_FORMAT_REAR16, UserFmtRear, UserFmtShort }, + { AL_FORMAT_REAR32, UserFmtRear, UserFmtFloat }, + { AL_FORMAT_REAR_MULAW, UserFmtRear, UserFmtMulaw }, + + { AL_FORMAT_QUAD8_LOKI, UserFmtQuad, UserFmtUByte }, + { AL_FORMAT_QUAD16_LOKI, UserFmtQuad, UserFmtShort }, + + { AL_FORMAT_QUAD8, UserFmtQuad, UserFmtUByte }, + { AL_FORMAT_QUAD16, UserFmtQuad, UserFmtShort }, + { AL_FORMAT_QUAD32, UserFmtQuad, UserFmtFloat }, + { AL_FORMAT_QUAD_MULAW, UserFmtQuad, UserFmtMulaw }, + + { AL_FORMAT_51CHN8, UserFmtX51, UserFmtUByte }, + { AL_FORMAT_51CHN16, UserFmtX51, UserFmtShort }, + { AL_FORMAT_51CHN32, UserFmtX51, UserFmtFloat }, + { AL_FORMAT_51CHN_MULAW, UserFmtX51, UserFmtMulaw }, + + { AL_FORMAT_61CHN8, UserFmtX61, UserFmtUByte }, + { AL_FORMAT_61CHN16, UserFmtX61, UserFmtShort }, + { AL_FORMAT_61CHN32, UserFmtX61, UserFmtFloat }, + { AL_FORMAT_61CHN_MULAW, UserFmtX61, UserFmtMulaw }, + + { AL_FORMAT_71CHN8, UserFmtX71, UserFmtUByte }, + { AL_FORMAT_71CHN16, UserFmtX71, UserFmtShort }, + { AL_FORMAT_71CHN32, UserFmtX71, UserFmtFloat }, + { AL_FORMAT_71CHN_MULAW, UserFmtX71, UserFmtMulaw }, + + { AL_FORMAT_BFORMAT2D_8, UserFmtBFormat2D, UserFmtUByte }, + { AL_FORMAT_BFORMAT2D_16, UserFmtBFormat2D, UserFmtShort }, + { AL_FORMAT_BFORMAT2D_FLOAT32, UserFmtBFormat2D, UserFmtFloat }, + { AL_FORMAT_BFORMAT2D_MULAW, UserFmtBFormat2D, UserFmtMulaw }, + + { AL_FORMAT_BFORMAT3D_8, UserFmtBFormat3D, UserFmtUByte }, + { AL_FORMAT_BFORMAT3D_16, UserFmtBFormat3D, UserFmtShort }, + { AL_FORMAT_BFORMAT3D_FLOAT32, UserFmtBFormat3D, UserFmtFloat }, + { AL_FORMAT_BFORMAT3D_MULAW, UserFmtBFormat3D, UserFmtMulaw }, + + { AL_FORMAT_UHJ2CHN8_SOFT, UserFmtUHJ2, UserFmtUByte }, + { AL_FORMAT_UHJ2CHN16_SOFT, UserFmtUHJ2, UserFmtShort }, + { AL_FORMAT_UHJ2CHN_FLOAT32_SOFT, UserFmtUHJ2, UserFmtFloat }, + + { AL_FORMAT_UHJ3CHN8_SOFT, UserFmtUHJ3, UserFmtUByte }, + { AL_FORMAT_UHJ3CHN16_SOFT, UserFmtUHJ3, UserFmtShort }, + { AL_FORMAT_UHJ3CHN_FLOAT32_SOFT, UserFmtUHJ3, UserFmtFloat }, + + { AL_FORMAT_UHJ4CHN8_SOFT, UserFmtUHJ4, UserFmtUByte }, + { AL_FORMAT_UHJ4CHN16_SOFT, UserFmtUHJ4, UserFmtShort }, + { AL_FORMAT_UHJ4CHN_FLOAT32_SOFT, UserFmtUHJ4, UserFmtFloat }, + }}; + + for(const auto &fmt : UserFmtList) + { + if(fmt.format == format) + return al::make_optional({fmt.channels, fmt.type}); + } + return al::nullopt; +} + +} // namespace + + +AL_API void AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Generating %d buffers", n); + if UNLIKELY(n <= 0) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + if(!EnsureBuffers(device, static_cast(n))) + { + context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d buffer%s", n, (n==1)?"":"s"); + return; + } + + if LIKELY(n == 1) + { + /* Special handling for the easy and normal case. */ + ALbuffer *buffer{AllocBuffer(device)}; + buffers[0] = buffer->id; + } + else + { + /* Store the allocated buffer IDs in a separate local list, to avoid + * modifying the user storage in case of failure. + */ + al::vector ids; + ids.reserve(static_cast(n)); + do { + ALbuffer *buffer{AllocBuffer(device)}; + ids.emplace_back(buffer->id); + } while(--n); + std::copy(ids.begin(), ids.end(), buffers); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Deleting %d buffers", n); + if UNLIKELY(n <= 0) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + /* First try to find any buffers that are invalid or in-use. */ + auto validate_buffer = [device, &context](const ALuint bid) -> bool + { + if(!bid) return true; + ALbuffer *ALBuf{LookupBuffer(device, bid)}; + if UNLIKELY(!ALBuf) + { + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", bid); + return false; + } + if UNLIKELY(ReadRef(ALBuf->ref) != 0) + { + context->setError(AL_INVALID_OPERATION, "Deleting in-use buffer %u", bid); + return false; + } + return true; + }; + const ALuint *buffers_end = buffers + n; + auto invbuf = std::find_if_not(buffers, buffers_end, validate_buffer); + if UNLIKELY(invbuf != buffers_end) return; + + /* All good. Delete non-0 buffer IDs. */ + auto delete_buffer = [device](const ALuint bid) -> void + { + ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr}; + if(buffer) FreeBuffer(device, buffer); + }; + std::for_each(buffers, buffers_end, delete_buffer); +} +END_API_FUNC + +AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if LIKELY(context) + { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + if(!buffer || LookupBuffer(device, buffer)) + return AL_TRUE; + } + return AL_FALSE; +} +END_API_FUNC + + +AL_API void AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq) +START_API_FUNC +{ alBufferStorageSOFT(buffer, format, data, size, freq, 0); } +END_API_FUNC + +AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + ALbuffer *albuf = LookupBuffer(device, buffer); + if UNLIKELY(!albuf) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(size < 0) + context->setError(AL_INVALID_VALUE, "Negative storage size %d", size); + else if UNLIKELY(freq < 1) + context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq); + else if UNLIKELY((flags&INVALID_STORAGE_MASK) != 0) + context->setError(AL_INVALID_VALUE, "Invalid storage flags 0x%x", + flags&INVALID_STORAGE_MASK); + else if UNLIKELY((flags&AL_MAP_PERSISTENT_BIT_SOFT) && !(flags&MAP_READ_WRITE_FLAGS)) + context->setError(AL_INVALID_VALUE, + "Declaring persistently mapped storage without read or write access"); + else + { + auto usrfmt = DecomposeUserFormat(format); + if UNLIKELY(!usrfmt) + context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); + else + { + LoadData(context.get(), albuf, freq, static_cast(size), usrfmt->channels, + usrfmt->type, static_cast(data), flags); + } + } +} +END_API_FUNC + +AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return nullptr; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + ALbuffer *albuf = LookupBuffer(device, buffer); + if UNLIKELY(!albuf) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY((access&INVALID_MAP_FLAGS) != 0) + context->setError(AL_INVALID_VALUE, "Invalid map flags 0x%x", access&INVALID_MAP_FLAGS); + else if UNLIKELY(!(access&MAP_READ_WRITE_FLAGS)) + context->setError(AL_INVALID_VALUE, "Mapping buffer %u without read or write access", + buffer); + else + { + ALbitfieldSOFT unavailable = (albuf->Access^access) & access; + if UNLIKELY(ReadRef(albuf->ref) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT)) + context->setError(AL_INVALID_OPERATION, + "Mapping in-use buffer %u without persistent mapping", buffer); + else if UNLIKELY(albuf->MappedAccess != 0) + context->setError(AL_INVALID_OPERATION, "Mapping already-mapped buffer %u", buffer); + else if UNLIKELY((unavailable&AL_MAP_READ_BIT_SOFT)) + context->setError(AL_INVALID_VALUE, + "Mapping buffer %u for reading without read access", buffer); + else if UNLIKELY((unavailable&AL_MAP_WRITE_BIT_SOFT)) + context->setError(AL_INVALID_VALUE, + "Mapping buffer %u for writing without write access", buffer); + else if UNLIKELY((unavailable&AL_MAP_PERSISTENT_BIT_SOFT)) + context->setError(AL_INVALID_VALUE, + "Mapping buffer %u persistently without persistent access", buffer); + else if UNLIKELY(offset < 0 || length <= 0 + || static_cast(offset) >= albuf->OriginalSize + || static_cast(length) > albuf->OriginalSize - static_cast(offset)) + context->setError(AL_INVALID_VALUE, "Mapping invalid range %d+%d for buffer %u", + offset, length, buffer); + else + { + void *retval{albuf->mData.data() + offset}; + albuf->MappedAccess = access; + albuf->MappedOffset = offset; + albuf->MappedSize = length; + return retval; + } + } + + return nullptr; +} +END_API_FUNC + +AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + ALbuffer *albuf = LookupBuffer(device, buffer); + if UNLIKELY(!albuf) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(albuf->MappedAccess == 0) + context->setError(AL_INVALID_OPERATION, "Unmapping unmapped buffer %u", buffer); + else + { + albuf->MappedAccess = 0; + albuf->MappedOffset = 0; + albuf->MappedSize = 0; + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + ALbuffer *albuf = LookupBuffer(device, buffer); + if UNLIKELY(!albuf) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT)) + context->setError(AL_INVALID_OPERATION, "Flushing buffer %u while not mapped for writing", + buffer); + else if UNLIKELY(offset < albuf->MappedOffset || length <= 0 + || offset >= albuf->MappedOffset+albuf->MappedSize + || length > albuf->MappedOffset+albuf->MappedSize-offset) + context->setError(AL_INVALID_VALUE, "Flushing invalid range %d+%d on buffer %u", offset, + length, buffer); + else + { + /* FIXME: Need to use some method of double-buffering for the mixer and + * app to hold separate memory, which can be safely transfered + * asynchronously. Currently we just say the app shouldn't write where + * OpenAL's reading, and hope for the best... + */ + std::atomic_thread_fence(std::memory_order_seq_cst); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + ALbuffer *albuf = LookupBuffer(device, buffer); + if UNLIKELY(!albuf) + { + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + return; + } + + auto usrfmt = DecomposeUserFormat(format); + if UNLIKELY(!usrfmt) + { + context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); + return; + } + + ALuint unpack_align{albuf->UnpackAlign}; + ALuint align{SanitizeAlignment(usrfmt->type, unpack_align)}; + if UNLIKELY(align < 1) + context->setError(AL_INVALID_VALUE, "Invalid unpack alignment %u", unpack_align); + else if UNLIKELY(long{usrfmt->channels} != long{albuf->mChannels} + || usrfmt->type != albuf->OriginalType) + context->setError(AL_INVALID_ENUM, "Unpacking data with mismatched format"); + else if UNLIKELY(align != albuf->OriginalAlign) + context->setError(AL_INVALID_VALUE, + "Unpacking data with alignment %u does not match original alignment %u", align, + albuf->OriginalAlign); + else if UNLIKELY(albuf->isBFormat() && albuf->UnpackAmbiOrder != albuf->mAmbiOrder) + context->setError(AL_INVALID_VALUE, "Unpacking data with mismatched ambisonic order"); + else if UNLIKELY(albuf->MappedAccess != 0) + context->setError(AL_INVALID_OPERATION, "Unpacking data into mapped buffer %u", buffer); + else + { + ALuint num_chans{albuf->channelsFromFmt()}; + ALuint frame_size{num_chans * albuf->bytesFromFmt()}; + ALuint byte_align{ + (albuf->OriginalType == UserFmtIMA4) ? ((align-1)/2 + 4) * num_chans : + (albuf->OriginalType == UserFmtMSADPCM) ? ((align-2)/2 + 7) * num_chans : + (align * frame_size) + }; + + if UNLIKELY(offset < 0 || length < 0 || static_cast(offset) > albuf->OriginalSize + || static_cast(length) > albuf->OriginalSize-static_cast(offset)) + context->setError(AL_INVALID_VALUE, "Invalid data sub-range %d+%d on buffer %u", + offset, length, buffer); + else if UNLIKELY((static_cast(offset)%byte_align) != 0) + context->setError(AL_INVALID_VALUE, + "Sub-range offset %d is not a multiple of frame size %d (%d unpack alignment)", + offset, byte_align, align); + else if UNLIKELY((static_cast(length)%byte_align) != 0) + context->setError(AL_INVALID_VALUE, + "Sub-range length %d is not a multiple of frame size %d (%d unpack alignment)", + length, byte_align, align); + else + { + /* offset -> byte offset, length -> sample count */ + size_t byteoff{static_cast(offset)/byte_align * align * frame_size}; + size_t samplen{static_cast(length)/byte_align * align}; + + void *dst = albuf->mData.data() + byteoff; + if(usrfmt->type == UserFmtIMA4 && albuf->mType == FmtShort) + Convert_int16_ima4(static_cast(dst), static_cast(data), + num_chans, samplen, align); + else if(usrfmt->type == UserFmtMSADPCM && albuf->mType == FmtShort) + Convert_int16_msadpcm(static_cast(dst), + static_cast(data), num_chans, samplen, align); + else + { + assert(long{usrfmt->type} == long{albuf->mType}); + memcpy(dst, data, size_t{samplen} * frame_size); + } + } + } +} +END_API_FUNC + + +AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint /*buffer*/, ALuint /*samplerate*/, + ALenum /*internalformat*/, ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, + const ALvoid* /*data*/) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + context->setError(AL_INVALID_OPERATION, "alBufferSamplesSOFT not supported"); +} +END_API_FUNC + +AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/, + ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, const ALvoid* /*data*/) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + context->setError(AL_INVALID_OPERATION, "alBufferSubSamplesSOFT not supported"); +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/, + ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, ALvoid* /*data*/) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + context->setError(AL_INVALID_OPERATION, "alGetBufferSamplesSOFT not supported"); +} +END_API_FUNC + +AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum /*format*/) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return AL_FALSE; + + context->setError(AL_INVALID_OPERATION, "alIsBufferFormatSupportedSOFT not supported"); + return AL_FALSE; +} +END_API_FUNC + + +AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat /*value*/) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else switch(param) + { + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param, + ALfloat /*value1*/, ALfloat /*value2*/, ALfloat /*value3*/) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else switch(param) + { + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *values) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(param) + { + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param); + } +} +END_API_FUNC + + +AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + ALbuffer *albuf = LookupBuffer(device, buffer); + if UNLIKELY(!albuf) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else switch(param) + { + case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: + if UNLIKELY(value < 0) + context->setError(AL_INVALID_VALUE, "Invalid unpack block alignment %d", value); + else + albuf->UnpackAlign = static_cast(value); + break; + + case AL_PACK_BLOCK_ALIGNMENT_SOFT: + if UNLIKELY(value < 0) + context->setError(AL_INVALID_VALUE, "Invalid pack block alignment %d", value); + else + albuf->PackAlign = static_cast(value); + break; + + case AL_AMBISONIC_LAYOUT_SOFT: + if UNLIKELY(ReadRef(albuf->ref) != 0) + context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's ambisonic layout", + buffer); + else if UNLIKELY(value != AL_FUMA_SOFT && value != AL_ACN_SOFT) + context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic layout %04x", value); + else + albuf->mAmbiLayout = AmbiLayoutFromEnum(value).value(); + break; + + case AL_AMBISONIC_SCALING_SOFT: + if UNLIKELY(ReadRef(albuf->ref) != 0) + context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's ambisonic scaling", + buffer); + else if UNLIKELY(value != AL_FUMA_SOFT && value != AL_SN3D_SOFT && value != AL_N3D_SOFT) + context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic scaling %04x", value); + else + albuf->mAmbiScaling = AmbiScalingFromEnum(value).value(); + break; + + case AL_UNPACK_AMBISONIC_ORDER_SOFT: + if UNLIKELY(value < 1 || value > 14) + context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic order %d", value); + else + albuf->UnpackAmbiOrder = static_cast(value); + break; + + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param, + ALint /*value1*/, ALint /*value2*/, ALint /*value3*/) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else switch(param) + { + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alBufferiv(ALuint buffer, ALenum param, const ALint *values) +START_API_FUNC +{ + if(values) + { + switch(param) + { + case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: + case AL_PACK_BLOCK_ALIGNMENT_SOFT: + case AL_AMBISONIC_LAYOUT_SOFT: + case AL_AMBISONIC_SCALING_SOFT: + case AL_UNPACK_AMBISONIC_ORDER_SOFT: + alBufferi(buffer, param, values[0]); + return; + } + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + ALbuffer *albuf = LookupBuffer(device, buffer); + if UNLIKELY(!albuf) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(param) + { + case AL_LOOP_POINTS_SOFT: + if UNLIKELY(ReadRef(albuf->ref) != 0) + context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's loop points", + buffer); + else if UNLIKELY(values[0] < 0 || values[0] >= values[1] + || static_cast(values[1]) > albuf->mSampleLen) + context->setError(AL_INVALID_VALUE, "Invalid loop point range %d -> %d on buffer %u", + values[0], values[1], buffer); + else + { + albuf->mLoopStart = static_cast(values[0]); + albuf->mLoopEnd = static_cast(values[1]); + } + break; + + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", param); + } +} +END_API_FUNC + + +AL_API void AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + ALbuffer *albuf = LookupBuffer(device, buffer); + if UNLIKELY(!albuf) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(!value) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(param) + { + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(!value1 || !value2 || !value3) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(param) + { + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values) +START_API_FUNC +{ + switch(param) + { + case AL_SEC_LENGTH_SOFT: + alGetBufferf(buffer, param, values); + return; + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(param) + { + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param); + } +} +END_API_FUNC + + +AL_API void AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + ALbuffer *albuf = LookupBuffer(device, buffer); + if UNLIKELY(!albuf) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(!value) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(param) + { + case AL_FREQUENCY: + *value = static_cast(albuf->mSampleRate); + break; + + case AL_BITS: + *value = static_cast(albuf->bytesFromFmt() * 8); + break; + + case AL_CHANNELS: + *value = static_cast(albuf->channelsFromFmt()); + break; + + case AL_SIZE: + *value = static_cast(albuf->mSampleLen * albuf->frameSizeFromFmt()); + break; + + case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: + *value = static_cast(albuf->UnpackAlign); + break; + + case AL_PACK_BLOCK_ALIGNMENT_SOFT: + *value = static_cast(albuf->PackAlign); + break; + + case AL_AMBISONIC_LAYOUT_SOFT: + *value = EnumFromAmbiLayout(albuf->mAmbiLayout); + break; + + case AL_AMBISONIC_SCALING_SOFT: + *value = EnumFromAmbiScaling(albuf->mAmbiScaling); + break; + + case AL_UNPACK_AMBISONIC_ORDER_SOFT: + *value = static_cast(albuf->UnpackAmbiOrder); + break; + + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(!value1 || !value2 || !value3) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(param) + { + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum param, ALint *values) +START_API_FUNC +{ + switch(param) + { + case AL_FREQUENCY: + case AL_BITS: + case AL_CHANNELS: + case AL_SIZE: + case AL_INTERNAL_FORMAT_SOFT: + case AL_BYTE_LENGTH_SOFT: + case AL_SAMPLE_LENGTH_SOFT: + case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: + case AL_PACK_BLOCK_ALIGNMENT_SOFT: + case AL_AMBISONIC_LAYOUT_SOFT: + case AL_AMBISONIC_SCALING_SOFT: + case AL_UNPACK_AMBISONIC_ORDER_SOFT: + alGetBufferi(buffer, param, values); + return; + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + ALbuffer *albuf = LookupBuffer(device, buffer); + if UNLIKELY(!albuf) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(param) + { + case AL_LOOP_POINTS_SOFT: + values[0] = static_cast(albuf->mLoopStart); + values[1] = static_cast(albuf->mLoopEnd); + break; + + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", param); + } +} +END_API_FUNC + + +AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, + ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + + ALbuffer *albuf = LookupBuffer(device, buffer); + if UNLIKELY(!albuf) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(freq < 1) + context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq); + else if UNLIKELY(callback == nullptr) + context->setError(AL_INVALID_VALUE, "NULL callback"); + else + { + auto usrfmt = DecomposeUserFormat(format); + if UNLIKELY(!usrfmt) + context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); + else + PrepareCallback(context.get(), albuf, freq, usrfmt->channels, usrfmt->type, callback, + userptr); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + ALbuffer *albuf = LookupBuffer(device, buffer); + if UNLIKELY(!albuf) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(!value) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(param) + { + case AL_BUFFER_CALLBACK_FUNCTION_SOFT: + *value = reinterpret_cast(albuf->mCallback); + break; + case AL_BUFFER_CALLBACK_USER_PARAM_SOFT: + *value = albuf->mUserData; + break; + + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer pointer property 0x%04x", param); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(!value1 || !value2 || !value3) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(param) + { + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer 3-pointer property 0x%04x", param); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **values) +START_API_FUNC +{ + switch(param) + { + case AL_BUFFER_CALLBACK_FUNCTION_SOFT: + case AL_BUFFER_CALLBACK_USER_PARAM_SOFT: + alGetBufferPtrSOFT(buffer, param, values); + return; + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->BufferLock}; + if UNLIKELY(LookupBuffer(device, buffer) == nullptr) + context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + else if UNLIKELY(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(param) + { + default: + context->setError(AL_INVALID_ENUM, "Invalid buffer pointer-vector property 0x%04x", param); + } +} +END_API_FUNC + + +BufferSubList::~BufferSubList() +{ + uint64_t usemask{~FreeMask}; + while(usemask) + { + const int idx{al::countr_zero(usemask)}; + al::destroy_at(Buffers+idx); + usemask &= ~(1_u64 << idx); + } + FreeMask = ~usemask; + al_free(Buffers); + Buffers = nullptr; +} + + +#ifdef ALSOFT_EAX +FORCE_ALIGN ALboolean AL_APIENTRY EAXSetBufferMode(ALsizei n, const ALuint* buffers, ALint value) +START_API_FUNC +{ +#define EAX_PREFIX "[EAXSetBufferMode] " + + const auto context = ContextRef{GetContextRef()}; + if(!context) + { + ERR(EAX_PREFIX "%s\n", "No current context."); + return ALC_FALSE; + } + + if(!eax_g_is_enabled) + { + context->setError(AL_INVALID_OPERATION, EAX_PREFIX "%s", "EAX not enabled."); + return ALC_FALSE; + } + + switch(value) + { + case AL_STORAGE_AUTOMATIC: + case AL_STORAGE_HARDWARE: + case AL_STORAGE_ACCESSIBLE: + break; + + default: + context->setError(AL_INVALID_ENUM, EAX_PREFIX "Unsupported X-RAM mode 0x%x", value); + return ALC_FALSE; + } + + if(n == 0) + return ALC_TRUE; + + if(n < 0) + { + context->setError(AL_INVALID_VALUE, EAX_PREFIX "Buffer count %d out of range", n); + return ALC_FALSE; + } + + if(!buffers) + { + context->setError(AL_INVALID_VALUE, EAX_PREFIX "%s", "Null AL buffers"); + return ALC_FALSE; + } + + auto device = context->mALDevice.get(); + std::lock_guard device_lock{device->BufferLock}; + size_t total_needed{0}; + + // Validate the buffers. + // + for(auto i = 0;i < n;++i) + { + const auto buffer = buffers[i]; + if(buffer == AL_NONE) + continue; + + const auto al_buffer = LookupBuffer(device, buffer); + if (!al_buffer) + { + ERR(EAX_PREFIX "Invalid buffer ID %u.\n", buffer); + return ALC_FALSE; + } + + /* TODO: Is the store location allowed to change for in-use buffers, or + * only when not set/queued on a source? + */ + + if(value == AL_STORAGE_HARDWARE && !al_buffer->eax_x_ram_is_hardware) + { + /* FIXME: This doesn't account for duplicate buffers. When the same + * buffer ID is specified multiple times in the provided list, it + * counts each instance as more memory that needs to fit in X-RAM. + */ + if(unlikely(std::numeric_limits::max()-al_buffer->OriginalSize < total_needed)) + { + context->setError(AL_OUT_OF_MEMORY, EAX_PREFIX "Buffer size overflow (%u + %zu)\n", + al_buffer->OriginalSize, total_needed); + return ALC_FALSE; + } + total_needed += al_buffer->OriginalSize; + } + } + if(total_needed > device->eax_x_ram_free_size) + { + context->setError(AL_INVALID_ENUM, EAX_PREFIX "Out of X-RAM memory (need: %zu, avail: %u)", + total_needed, device->eax_x_ram_free_size); + return ALC_FALSE; + } + + // Update the mode. + // + for(auto i = 0;i < n;++i) + { + const auto buffer = buffers[i]; + if(buffer == AL_NONE) + continue; + + const auto al_buffer = LookupBuffer(device, buffer); + assert(al_buffer); + + if(value != AL_STORAGE_ACCESSIBLE) + eax_x_ram_apply(*device, *al_buffer); + else + eax_x_ram_clear(*device, *al_buffer); + al_buffer->eax_x_ram_mode = value; + } + + return AL_TRUE; + +#undef EAX_PREFIX +} +END_API_FUNC + +FORCE_ALIGN ALenum AL_APIENTRY EAXGetBufferMode(ALuint buffer, ALint* pReserved) +START_API_FUNC +{ +#define EAX_PREFIX "[EAXGetBufferMode] " + + const auto context = ContextRef{GetContextRef()}; + if(!context) + { + ERR(EAX_PREFIX "%s\n", "No current context."); + return AL_NONE; + } + + if(!eax_g_is_enabled) + { + context->setError(AL_INVALID_OPERATION, EAX_PREFIX "%s", "EAX not enabled."); + return AL_NONE; + } + + if(pReserved) + { + context->setError(AL_INVALID_VALUE, EAX_PREFIX "%s", "Non-null reserved parameter"); + return AL_NONE; + } + + auto device = context->mALDevice.get(); + std::lock_guard device_lock{device->BufferLock}; + + const auto al_buffer = LookupBuffer(device, buffer); + if(!al_buffer) + { + context->setError(AL_INVALID_NAME, EAX_PREFIX "Invalid buffer ID %u", buffer); + return AL_NONE; + } + + return al_buffer->eax_x_ram_mode; + +#undef EAX_PREFIX +} +END_API_FUNC + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/buffer.h b/modules/openal-soft/al/buffer.h new file mode 100644 index 0000000..b3a0f0d --- /dev/null +++ b/modules/openal-soft/al/buffer.h @@ -0,0 +1,81 @@ +#ifndef AL_BUFFER_H +#define AL_BUFFER_H + +#include + +#include "AL/al.h" + +#include "albyte.h" +#include "alc/inprogext.h" +#include "almalloc.h" +#include "atomic.h" +#include "core/buffer_storage.h" +#include "vector.h" + +#ifdef ALSOFT_EAX +#include "eax_x_ram.h" +#endif // ALSOFT_EAX + +/* User formats */ +enum UserFmtType : unsigned char { + UserFmtUByte = FmtUByte, + UserFmtShort = FmtShort, + UserFmtFloat = FmtFloat, + UserFmtMulaw = FmtMulaw, + UserFmtAlaw = FmtAlaw, + UserFmtDouble = FmtDouble, + + UserFmtIMA4 = 128, + UserFmtMSADPCM, +}; +enum UserFmtChannels : unsigned char { + UserFmtMono = FmtMono, + UserFmtStereo = FmtStereo, + UserFmtRear = FmtRear, + UserFmtQuad = FmtQuad, + UserFmtX51 = FmtX51, + UserFmtX61 = FmtX61, + UserFmtX71 = FmtX71, + UserFmtBFormat2D = FmtBFormat2D, + UserFmtBFormat3D = FmtBFormat3D, + UserFmtUHJ2 = FmtUHJ2, + UserFmtUHJ3 = FmtUHJ3, + UserFmtUHJ4 = FmtUHJ4, +}; + + +struct ALbuffer : public BufferStorage { + ALbitfieldSOFT Access{0u}; + + al::vector mData; + + UserFmtType OriginalType{UserFmtShort}; + ALuint OriginalSize{0}; + ALuint OriginalAlign{0}; + + ALuint UnpackAlign{0}; + ALuint PackAlign{0}; + ALuint UnpackAmbiOrder{1}; + + ALbitfieldSOFT MappedAccess{0u}; + ALsizei MappedOffset{0}; + ALsizei MappedSize{0}; + + ALuint mLoopStart{0u}; + ALuint mLoopEnd{0u}; + + /* Number of times buffer was attached to a source (deletion can only occur when 0) */ + RefCount ref{0u}; + + /* Self ID */ + ALuint id{0}; + + DISABLE_ALLOC() + +#ifdef ALSOFT_EAX + ALenum eax_x_ram_mode{AL_STORAGE_AUTOMATIC}; + bool eax_x_ram_is_hardware{}; +#endif // ALSOFT_EAX +}; + +#endif diff --git a/modules/openal-soft/al/eax_api.cpp b/modules/openal-soft/al/eax_api.cpp new file mode 100644 index 0000000..6b1f7fc --- /dev/null +++ b/modules/openal-soft/al/eax_api.cpp @@ -0,0 +1,1213 @@ +// +// EAX API. +// +// Based on headers `eax[2-5].h` included in Doom 3 source code: +// https://github.com/id-Software/DOOM-3/tree/master/neo/openal/include +// + +#include "config.h" + +#include + +#include "al/eax_api.h" + + +const GUID DSPROPSETID_EAX_ReverbProperties = +{ + 0x4A4E6FC1, + 0xC341, + 0x11D1, + {0xB7, 0x3A, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00} +}; + +const GUID DSPROPSETID_EAXBUFFER_ReverbProperties = +{ + 0x4A4E6FC0, + 0xC341, + 0x11D1, + {0xB7, 0x3A, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00} +}; + +const GUID DSPROPSETID_EAX20_ListenerProperties = +{ + 0x306A6A8, + 0xB224, + 0x11D2, + {0x99, 0xE5, 0x00, 0x00, 0xE8, 0xD8, 0xC7, 0x22} +}; + +const GUID DSPROPSETID_EAX20_BufferProperties = +{ + 0x306A6A7, + 0xB224, + 0x11D2, + {0x99, 0xE5, 0x00, 0x00, 0xE8, 0xD8, 0xC7, 0x22} +}; + +const GUID DSPROPSETID_EAX30_ListenerProperties = +{ + 0xA8FA6882, + 0xB476, + 0x11D3, + {0xBD, 0xB9, 0x00, 0xC0, 0xF0, 0x2D, 0xDF, 0x87} +}; + +const GUID DSPROPSETID_EAX30_BufferProperties = +{ + 0xA8FA6881, + 0xB476, + 0x11D3, + {0xBD, 0xB9, 0x00, 0xC0, 0xF0, 0x2D, 0xDF, 0x87} +}; + +const GUID EAX_NULL_GUID = +{ + 0x00000000, + 0x0000, + 0x0000, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +const GUID EAX_PrimaryFXSlotID = +{ + 0xF317866D, + 0x924C, + 0x450C, + {0x86, 0x1B, 0xE6, 0xDA, 0xA2, 0x5E, 0x7C, 0x20} +}; + +const GUID EAXPROPERTYID_EAX40_Context = +{ + 0x1D4870AD, + 0xDEF, + 0x43C0, + {0xA4, 0xC, 0x52, 0x36, 0x32, 0x29, 0x63, 0x42} +}; + +const GUID EAXPROPERTYID_EAX50_Context = +{ + 0x57E13437, + 0xB932, + 0x4AB2, + {0xB8, 0xBD, 0x52, 0x66, 0xC1, 0xA8, 0x87, 0xEE} +}; + +const GUID EAXPROPERTYID_EAX40_FXSlot0 = +{ + 0xC4D79F1E, + 0xF1AC, + 0x436B, + {0xA8, 0x1D, 0xA7, 0x38, 0xE7, 0x04, 0x54, 0x69} +}; + +const GUID EAXPROPERTYID_EAX50_FXSlot0 = +{ + 0x91F9590F, + 0xC388, + 0x407A, + {0x84, 0xB0, 0x1B, 0xAE, 0xE, 0xF7, 0x1A, 0xBC} +}; + +const GUID EAXPROPERTYID_EAX40_FXSlot1 = +{ + 0x8C00E96, + 0x74BE, + 0x4491, + {0x93, 0xAA, 0xE8, 0xAD, 0x35, 0xA4, 0x91, 0x17} +}; + +const GUID EAXPROPERTYID_EAX50_FXSlot1 = +{ + 0x8F5F7ACA, + 0x9608, + 0x4965, + {0x81, 0x37, 0x82, 0x13, 0xC7, 0xB9, 0xD9, 0xDE} +}; + +const GUID EAXPROPERTYID_EAX40_FXSlot2 = +{ + 0x1D433B88, + 0xF0F6, + 0x4637, + {0x91, 0x9F, 0x60, 0xE7, 0xE0, 0x6B, 0x5E, 0xDD} +}; + +const GUID EAXPROPERTYID_EAX50_FXSlot2 = +{ + 0x3C0F5252, + 0x9834, + 0x46F0, + {0xA1, 0xD8, 0x5B, 0x95, 0xC4, 0xA0, 0xA, 0x30} +}; + +const GUID EAXPROPERTYID_EAX40_FXSlot3 = +{ + 0xEFFF08EA, + 0xC7D8, + 0x44AB, + {0x93, 0xAD, 0x6D, 0xBD, 0x5F, 0x91, 0x00, 0x64} +}; + +const GUID EAXPROPERTYID_EAX50_FXSlot3 = +{ + 0xE2EB0EAA, + 0xE806, + 0x45E7, + {0x9F, 0x86, 0x06, 0xC1, 0x57, 0x1A, 0x6F, 0xA3} +}; + +const GUID EAXPROPERTYID_EAX40_Source = +{ + 0x1B86B823, + 0x22DF, + 0x4EAE, + {0x8B, 0x3C, 0x12, 0x78, 0xCE, 0x54, 0x42, 0x27} +}; + +const GUID EAXPROPERTYID_EAX50_Source = +{ + 0x5EDF82F0, + 0x24A7, + 0x4F38, + {0x8E, 0x64, 0x2F, 0x09, 0xCA, 0x05, 0xDE, 0xE1} +}; + +const GUID EAX_REVERB_EFFECT = +{ + 0xCF95C8F, + 0xA3CC, + 0x4849, + {0xB0, 0xB6, 0x83, 0x2E, 0xCC, 0x18, 0x22, 0xDF} +}; + +const GUID EAX_AGCCOMPRESSOR_EFFECT = +{ + 0xBFB7A01E, + 0x7825, + 0x4039, + {0x92, 0x7F, 0x03, 0xAA, 0xBD, 0xA0, 0xC5, 0x60} +}; + +const GUID EAX_AUTOWAH_EFFECT = +{ + 0xEC3130C0, + 0xAC7A, + 0x11D2, + {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + +const GUID EAX_CHORUS_EFFECT = +{ + 0xDE6D6FE0, + 0xAC79, + 0x11D2, + {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + +const GUID EAX_DISTORTION_EFFECT = +{ + 0x975A4CE0, + 0xAC7E, + 0x11D2, + {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + +const GUID EAX_ECHO_EFFECT = +{ + 0xE9F1BC0, + 0xAC82, + 0x11D2, + {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + +const GUID EAX_EQUALIZER_EFFECT = +{ + 0x65F94CE0, + 0x9793, + 0x11D3, + {0x93, 0x9D, 0x00, 0xC0, 0xF0, 0x2D, 0xD6, 0xF0} +}; + +const GUID EAX_FLANGER_EFFECT = +{ + 0xA70007C0, + 0x7D2, + 0x11D3, + {0x9B, 0x1E, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + +const GUID EAX_FREQUENCYSHIFTER_EFFECT = +{ + 0xDC3E1880, + 0x9212, + 0x11D3, + {0x93, 0x9D, 0x00, 0xC0, 0xF0, 0x2D, 0xD6, 0xF0} +}; + +const GUID EAX_VOCALMORPHER_EFFECT = +{ + 0xE41CF10C, + 0x3383, + 0x11D2, + {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + +const GUID EAX_PITCHSHIFTER_EFFECT = +{ + 0xE7905100, + 0xAFB2, + 0x11D2, + {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + +const GUID EAX_RINGMODULATOR_EFFECT = +{ + 0xB89FE60, + 0xAFB5, + 0x11D2, + {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + + +bool operator==( + const EAX40CONTEXTPROPERTIES& lhs, + const EAX40CONTEXTPROPERTIES& rhs) noexcept +{ + return + lhs.guidPrimaryFXSlotID == rhs.guidPrimaryFXSlotID && + lhs.flDistanceFactor == rhs.flDistanceFactor && + lhs.flAirAbsorptionHF == rhs.flAirAbsorptionHF && + lhs.flHFReference == rhs.flHFReference; +} + +bool operator==( + const EAX50CONTEXTPROPERTIES& lhs, + const EAX50CONTEXTPROPERTIES& rhs) noexcept +{ + return + static_cast(lhs) == static_cast(rhs) && + lhs.flMacroFXFactor == rhs.flMacroFXFactor; +} + + +const GUID EAXCONTEXT_DEFAULTPRIMARYFXSLOTID = EAXPROPERTYID_EAX40_FXSlot0; + +bool operator==( + const EAX40FXSLOTPROPERTIES& lhs, + const EAX40FXSLOTPROPERTIES& rhs) noexcept +{ + return + lhs.guidLoadEffect == rhs.guidLoadEffect && + lhs.lVolume == rhs.lVolume && + lhs.lLock == rhs.lLock && + lhs.ulFlags == rhs.ulFlags; +} + +bool operator==( + const EAX50FXSLOTPROPERTIES& lhs, + const EAX50FXSLOTPROPERTIES& rhs) noexcept +{ + return + static_cast(lhs) == static_cast(rhs) && + lhs.lOcclusion == rhs.lOcclusion && + lhs.flOcclusionLFRatio == rhs.flOcclusionLFRatio; +} + +const EAX50ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS +{{ + EAX_NULL_GUID, + EAXPROPERTYID_EAX40_FXSlot0, +}}; + +bool operator==( + const EAX50ACTIVEFXSLOTS& lhs, + const EAX50ACTIVEFXSLOTS& rhs) noexcept +{ + return std::equal( + std::cbegin(lhs.guidActiveFXSlots), + std::cend(lhs.guidActiveFXSlots), + std::begin(rhs.guidActiveFXSlots)); +} + +bool operator!=( + const EAX50ACTIVEFXSLOTS& lhs, + const EAX50ACTIVEFXSLOTS& rhs) noexcept +{ + return !(lhs == rhs); +} + + +const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS +{{ + EAX_NULL_GUID, + EAX_PrimaryFXSlotID, + EAX_NULL_GUID, + EAX_NULL_GUID, +}}; + + +const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS +{{ + EAX_NULL_GUID, + EAX_NULL_GUID, + EAX_NULL_GUID, + EAX_NULL_GUID, +}}; + +bool operator==( + const EAXREVERBPROPERTIES& lhs, + const EAXREVERBPROPERTIES& rhs) noexcept +{ + return + lhs.ulEnvironment == rhs.ulEnvironment && + lhs.flEnvironmentSize == rhs.flEnvironmentSize && + lhs.flEnvironmentDiffusion == rhs.flEnvironmentDiffusion && + lhs.lRoom == rhs.lRoom && + lhs.lRoomHF == rhs.lRoomHF && + lhs.lRoomLF == rhs.lRoomLF && + lhs.flDecayTime == rhs.flDecayTime && + lhs.flDecayHFRatio == rhs.flDecayHFRatio && + lhs.flDecayLFRatio == rhs.flDecayLFRatio && + lhs.lReflections == rhs.lReflections && + lhs.flReflectionsDelay == rhs.flReflectionsDelay && + lhs.vReflectionsPan == rhs.vReflectionsPan && + lhs.lReverb == rhs.lReverb && + lhs.flReverbDelay == rhs.flReverbDelay && + lhs.vReverbPan == rhs.vReverbPan && + lhs.flEchoTime == rhs.flEchoTime && + lhs.flEchoDepth == rhs.flEchoDepth && + lhs.flModulationTime == rhs.flModulationTime && + lhs.flModulationDepth == rhs.flModulationDepth && + lhs.flAirAbsorptionHF == rhs.flAirAbsorptionHF && + lhs.flHFReference == rhs.flHFReference && + lhs.flLFReference == rhs.flLFReference && + lhs.flRoomRolloffFactor == rhs.flRoomRolloffFactor && + lhs.ulFlags == rhs.ulFlags; +} + +bool operator!=( + const EAXREVERBPROPERTIES& lhs, + const EAXREVERBPROPERTIES& rhs) noexcept +{ + return !(lhs == rhs); +} + + +namespace { + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_GENERIC = +{ + EAXREVERB_DEFAULTENVIRONMENT, + EAXREVERB_DEFAULTENVIRONMENTSIZE, + EAXREVERB_DEFAULTENVIRONMENTDIFFUSION, + EAXREVERB_DEFAULTROOM, + EAXREVERB_DEFAULTROOMHF, + EAXREVERB_DEFAULTROOMLF, + EAXREVERB_DEFAULTDECAYTIME, + EAXREVERB_DEFAULTDECAYHFRATIO, + EAXREVERB_DEFAULTDECAYLFRATIO, + EAXREVERB_DEFAULTREFLECTIONS, + EAXREVERB_DEFAULTREFLECTIONSDELAY, + EAXREVERB_DEFAULTREFLECTIONSPAN, + EAXREVERB_DEFAULTREVERB, + EAXREVERB_DEFAULTREVERBDELAY, + EAXREVERB_DEFAULTREVERBPAN, + EAXREVERB_DEFAULTECHOTIME, + EAXREVERB_DEFAULTECHODEPTH, + EAXREVERB_DEFAULTMODULATIONTIME, + EAXREVERB_DEFAULTMODULATIONDEPTH, + EAXREVERB_DEFAULTAIRABSORPTIONHF, + EAXREVERB_DEFAULTHFREFERENCE, + EAXREVERB_DEFAULTLFREFERENCE, + EAXREVERB_DEFAULTROOMROLLOFFFACTOR, + EAXREVERB_DEFAULTFLAGS, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PADDEDCELL = +{ + EAX_ENVIRONMENT_PADDEDCELL, + 1.4F, + 1.0F, + -1'000L, + -6'000L, + 0L, + 0.17F, + 0.10F, + 1.0F, + -1'204L, + 0.001F, + EAXVECTOR{}, + 207L, + 0.002F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ROOM = +{ + EAX_ENVIRONMENT_ROOM, + 1.9F, + 1.0F, + -1'000L, + -454L, + 0L, + 0.40F, + 0.83F, + 1.0F, + -1'646L, + 0.002F, + EAXVECTOR{}, + 53L, + 0.003F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_BATHROOM = +{ + EAX_ENVIRONMENT_BATHROOM, + 1.4F, + 1.0F, + -1'000L, + -1'200L, + 0L, + 1.49F, + 0.54F, + 1.0F, + -370L, + 0.007F, + EAXVECTOR{}, + 1'030L, + 0.011F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_LIVINGROOM = +{ + EAX_ENVIRONMENT_LIVINGROOM, + 2.5F, + 1.0F, + -1'000L, + -6'000L, + 0L, + 0.50F, + 0.10F, + 1.0F, + -1'376, + 0.003F, + EAXVECTOR{}, + -1'104L, + 0.004F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_STONEROOM = +{ + EAX_ENVIRONMENT_STONEROOM, + 11.6F, + 1.0F, + -1'000L, + -300L, + 0L, + 2.31F, + 0.64F, + 1.0F, + -711L, + 0.012F, + EAXVECTOR{}, + 83L, + 0.017F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_AUDITORIUM = +{ + EAX_ENVIRONMENT_AUDITORIUM, + 21.6F, + 1.0F, + -1'000L, + -476L, + 0L, + 4.32F, + 0.59F, + 1.0F, + -789L, + 0.020F, + EAXVECTOR{}, + -289L, + 0.030F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CONCERTHALL = +{ + EAX_ENVIRONMENT_CONCERTHALL, + 19.6F, + 1.0F, + -1'000L, + -500L, + 0L, + 3.92F, + 0.70F, + 1.0F, + -1'230L, + 0.020F, + EAXVECTOR{}, + -2L, + 0.029F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CAVE = +{ + EAX_ENVIRONMENT_CAVE, + 14.6F, + 1.0F, + -1'000L, + 0L, + 0L, + 2.91F, + 1.30F, + 1.0F, + -602L, + 0.015F, + EAXVECTOR{}, + -302L, + 0.022F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x1FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ARENA = +{ + EAX_ENVIRONMENT_ARENA, + 36.2F, + 1.0F, + -1'000L, + -698L, + 0L, + 7.24F, + 0.33F, + 1.0F, + -1'166L, + 0.020F, + EAXVECTOR{}, + 16L, + 0.030F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_HANGAR = +{ + EAX_ENVIRONMENT_HANGAR, + 50.3F, + 1.0F, + -1'000L, + -1'000L, + 0L, + 10.05F, + 0.23F, + 1.0F, + -602L, + 0.020F, + EAXVECTOR{}, + 198L, + 0.030F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CARPETTEDHALLWAY = +{ + EAX_ENVIRONMENT_CARPETEDHALLWAY, + 1.9F, + 1.0F, + -1'000L, + -4'000L, + 0L, + 0.30F, + 0.10F, + 1.0F, + -1'831L, + 0.002F, + EAXVECTOR{}, + -1'630L, + 0.030F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_HALLWAY = +{ + EAX_ENVIRONMENT_HALLWAY, + 1.8F, + 1.0F, + -1'000L, + -300L, + 0L, + 1.49F, + 0.59F, + 1.0F, + -1'219L, + 0.007F, + EAXVECTOR{}, + 441L, + 0.011F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_STONECORRIDOR = +{ + EAX_ENVIRONMENT_STONECORRIDOR, + 13.5F, + 1.0F, + -1'000L, + -237L, + 0L, + 2.70F, + 0.79F, + 1.0F, + -1'214L, + 0.013F, + EAXVECTOR{}, + 395L, + 0.020F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ALLEY = +{ + EAX_ENVIRONMENT_ALLEY, + 7.5F, + 0.300F, + -1'000L, + -270L, + 0L, + 1.49F, + 0.86F, + 1.0F, + -1'204L, + 0.007F, + EAXVECTOR{}, + -4L, + 0.011F, + EAXVECTOR{}, + 0.125F, + 0.950F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_FOREST = +{ + EAX_ENVIRONMENT_FOREST, + 38.0F, + 0.300F, + -1'000L, + -3'300L, + 0L, + 1.49F, + 0.54F, + 1.0F, + -2'560L, + 0.162F, + EAXVECTOR{}, + -229L, + 0.088F, + EAXVECTOR{}, + 0.125F, + 1.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CITY = +{ + EAX_ENVIRONMENT_CITY, + 7.5F, + 0.500F, + -1'000L, + -800L, + 0L, + 1.49F, + 0.67F, + 1.0F, + -2'273L, + 0.007F, + EAXVECTOR{}, + -1'691L, + 0.011F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_MOUNTAINS = +{ + EAX_ENVIRONMENT_MOUNTAINS, + 100.0F, + 0.270F, + -1'000L, + -2'500L, + 0L, + 1.49F, + 0.21F, + 1.0F, + -2'780L, + 0.300F, + EAXVECTOR{}, + -1'434L, + 0.100F, + EAXVECTOR{}, + 0.250F, + 1.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x1FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_QUARRY = +{ + EAX_ENVIRONMENT_QUARRY, + 17.5F, + 1.0F, + -1'000L, + -1'000L, + 0L, + 1.49F, + 0.83F, + 1.0F, + -10'000L, + 0.061F, + EAXVECTOR{}, + 500L, + 0.025F, + EAXVECTOR{}, + 0.125F, + 0.700F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PLAIN = +{ + EAX_ENVIRONMENT_PLAIN, + 42.5F, + 0.210F, + -1'000L, + -2'000L, + 0L, + 1.49F, + 0.50F, + 1.0F, + -2'466L, + 0.179F, + EAXVECTOR{}, + -1'926L, + 0.100F, + EAXVECTOR{}, + 0.250F, + 1.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PARKINGLOT = +{ + EAX_ENVIRONMENT_PARKINGLOT, + 8.3F, + 1.0F, + -1'000L, + 0L, + 0L, + 1.65F, + 1.50F, + 1.0F, + -1'363L, + 0.008F, + EAXVECTOR{}, + -1'153L, + 0.012F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x1FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_SEWERPIPE = +{ + EAX_ENVIRONMENT_SEWERPIPE, + 1.7F, + 0.800F, + -1'000L, + -1'000L, + 0L, + 2.81F, + 0.14F, + 1.0F, + 429L, + 0.014F, + EAXVECTOR{}, + 1'023L, + 0.021F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_UNDERWATER = +{ + EAX_ENVIRONMENT_UNDERWATER, + 1.8F, + 1.0F, + -1'000L, + -4'000L, + 0L, + 1.49F, + 0.10F, + 1.0F, + -449L, + 0.007F, + EAXVECTOR{}, + 1'700L, + 0.011F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 1.180F, + 0.348F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_DRUGGED = +{ + EAX_ENVIRONMENT_DRUGGED, + 1.9F, + 0.500F, + -1'000L, + 0L, + 0L, + 8.39F, + 1.39F, + 1.0F, + -115L, + 0.002F, + EAXVECTOR{}, + 985L, + 0.030F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 1.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x1FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_DIZZY = +{ + EAX_ENVIRONMENT_DIZZY, + 1.8F, + 0.600F, + -1'000L, + -400L, + 0L, + 17.23F, + 0.56F, + 1.0F, + -1'713L, + 0.020F, + EAXVECTOR{}, + -613L, + 0.030F, + EAXVECTOR{}, + 0.250F, + 1.0F, + 0.810F, + 0.310F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x1FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PSYCHOTIC = +{ + EAX_ENVIRONMENT_PSYCHOTIC, + 1.0F, + 0.500F, + -1'000L, + -151L, + 0L, + 7.56F, + 0.91F, + 1.0F, + -626L, + 0.020F, + EAXVECTOR{}, + 774L, + 0.030F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 4.0F, + 1.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x1FUL, +}; + +} // namespace + +const EaxReverbPresets EAXREVERB_PRESETS{{ + EAXREVERB_PRESET_GENERIC, + EAXREVERB_PRESET_PADDEDCELL, + EAXREVERB_PRESET_ROOM, + EAXREVERB_PRESET_BATHROOM, + EAXREVERB_PRESET_LIVINGROOM, + EAXREVERB_PRESET_STONEROOM, + EAXREVERB_PRESET_AUDITORIUM, + EAXREVERB_PRESET_CONCERTHALL, + EAXREVERB_PRESET_CAVE, + EAXREVERB_PRESET_ARENA, + EAXREVERB_PRESET_HANGAR, + EAXREVERB_PRESET_CARPETTEDHALLWAY, + EAXREVERB_PRESET_HALLWAY, + EAXREVERB_PRESET_STONECORRIDOR, + EAXREVERB_PRESET_ALLEY, + EAXREVERB_PRESET_FOREST, + EAXREVERB_PRESET_CITY, + EAXREVERB_PRESET_MOUNTAINS, + EAXREVERB_PRESET_QUARRY, + EAXREVERB_PRESET_PLAIN, + EAXREVERB_PRESET_PARKINGLOT, + EAXREVERB_PRESET_SEWERPIPE, + EAXREVERB_PRESET_UNDERWATER, + EAXREVERB_PRESET_DRUGGED, + EAXREVERB_PRESET_DIZZY, + EAXREVERB_PRESET_PSYCHOTIC, +}}; + +namespace { +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_GENERIC = {EAX_ENVIRONMENT_GENERIC, 0.5F, 1.493F, 0.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PADDEDCELL = {EAX_ENVIRONMENT_PADDEDCELL, 0.25F, 0.1F, 0.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ROOM = {EAX_ENVIRONMENT_ROOM, 0.417F, 0.4F, 0.666F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_BATHROOM = {EAX_ENVIRONMENT_BATHROOM, 0.653F, 1.499F, 0.166F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_LIVINGROOM = {EAX_ENVIRONMENT_LIVINGROOM, 0.208F, 0.478F, 0.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_STONEROOM = {EAX_ENVIRONMENT_STONEROOM, 0.5F, 2.309F, 0.888F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_AUDITORIUM = {EAX_ENVIRONMENT_AUDITORIUM, 0.403F, 4.279F, 0.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CONCERTHALL = {EAX_ENVIRONMENT_CONCERTHALL, 0.5F, 3.961F, 0.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CAVE = {EAX_ENVIRONMENT_CAVE, 0.5F, 2.886F, 1.304F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ARENA = {EAX_ENVIRONMENT_ARENA, 0.361F, 7.284F, 0.332F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_HANGAR = {EAX_ENVIRONMENT_HANGAR, 0.5F, 10.0F, 0.3F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CARPETTEDHALLWAY = {EAX_ENVIRONMENT_CARPETEDHALLWAY, 0.153F, 0.259F, 2.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_HALLWAY = {EAX_ENVIRONMENT_HALLWAY, 0.361F, 1.493F, 0.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_STONECORRIDOR = {EAX_ENVIRONMENT_STONECORRIDOR, 0.444F, 2.697F, 0.638F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ALLEY = {EAX_ENVIRONMENT_ALLEY, 0.25F, 1.752F, 0.776F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_FOREST = {EAX_ENVIRONMENT_FOREST, 0.111F, 3.145F, 0.472F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CITY = {EAX_ENVIRONMENT_CITY, 0.111F, 2.767F, 0.224F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_MOUNTAINS = {EAX_ENVIRONMENT_MOUNTAINS, 0.194F, 7.841F, 0.472F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_QUARRY = {EAX_ENVIRONMENT_QUARRY, 1.0F, 1.499F, 0.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PLAIN = {EAX_ENVIRONMENT_PLAIN, 0.097F, 2.767F, 0.224F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PARKINGLOT = {EAX_ENVIRONMENT_PARKINGLOT, 0.208F, 1.652F, 1.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_SEWERPIPE = {EAX_ENVIRONMENT_SEWERPIPE, 0.652F, 2.886F, 0.25F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_UNDERWATER = {EAX_ENVIRONMENT_UNDERWATER, 1.0F, 1.499F, 0.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_DRUGGED = {EAX_ENVIRONMENT_DRUGGED, 0.875F, 8.392F, 1.388F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_DIZZY = {EAX_ENVIRONMENT_DIZZY, 0.139F, 17.234F, 0.666F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PSYCHOTIC = {EAX_ENVIRONMENT_PSYCHOTIC, 0.486F, 7.563F, 0.806F}; +} // namespace + +const Eax1ReverbPresets EAX1REVERB_PRESETS{{ + EAX1REVERB_PRESET_GENERIC, + EAX1REVERB_PRESET_PADDEDCELL, + EAX1REVERB_PRESET_ROOM, + EAX1REVERB_PRESET_BATHROOM, + EAX1REVERB_PRESET_LIVINGROOM, + EAX1REVERB_PRESET_STONEROOM, + EAX1REVERB_PRESET_AUDITORIUM, + EAX1REVERB_PRESET_CONCERTHALL, + EAX1REVERB_PRESET_CAVE, + EAX1REVERB_PRESET_ARENA, + EAX1REVERB_PRESET_HANGAR, + EAX1REVERB_PRESET_CARPETTEDHALLWAY, + EAX1REVERB_PRESET_HALLWAY, + EAX1REVERB_PRESET_STONECORRIDOR, + EAX1REVERB_PRESET_ALLEY, + EAX1REVERB_PRESET_FOREST, + EAX1REVERB_PRESET_CITY, + EAX1REVERB_PRESET_MOUNTAINS, + EAX1REVERB_PRESET_QUARRY, + EAX1REVERB_PRESET_PLAIN, + EAX1REVERB_PRESET_PARKINGLOT, + EAX1REVERB_PRESET_SEWERPIPE, + EAX1REVERB_PRESET_UNDERWATER, + EAX1REVERB_PRESET_DRUGGED, + EAX1REVERB_PRESET_DIZZY, + EAX1REVERB_PRESET_PSYCHOTIC, +}}; diff --git a/modules/openal-soft/al/eax_api.h b/modules/openal-soft/al/eax_api.h new file mode 100644 index 0000000..d0737d1 --- /dev/null +++ b/modules/openal-soft/al/eax_api.h @@ -0,0 +1,1557 @@ +#ifndef EAX_API_INCLUDED +#define EAX_API_INCLUDED + + +// +// EAX API. +// +// Based on headers `eax[2-5].h` included in Doom 3 source code: +// https://github.com/id-Software/DOOM-3/tree/master/neo/openal/include +// + + +#include +#include +#include + +#include + +#include "AL/al.h" + + +#ifndef GUID_DEFINED +#define GUID_DEFINED +typedef struct _GUID +{ + std::uint32_t Data1; + std::uint16_t Data2; + std::uint16_t Data3; + std::uint8_t Data4[8]; +} GUID; + +inline bool operator==(const GUID& lhs, const GUID& rhs) noexcept +{ + return std::memcmp(&lhs, &rhs, sizeof(GUID)) == 0; +} + +inline bool operator!=(const GUID& lhs, const GUID& rhs) noexcept +{ + return !(lhs == rhs); +} +#endif // GUID_DEFINED + + +extern const GUID DSPROPSETID_EAX_ReverbProperties; + +enum DSPROPERTY_EAX_REVERBPROPERTY : unsigned int +{ + DSPROPERTY_EAX_ALL, + DSPROPERTY_EAX_ENVIRONMENT, + DSPROPERTY_EAX_VOLUME, + DSPROPERTY_EAX_DECAYTIME, + DSPROPERTY_EAX_DAMPING, +}; // DSPROPERTY_EAX_REVERBPROPERTY + +struct EAX_REVERBPROPERTIES +{ + unsigned long environment; + float fVolume; + float fDecayTime_sec; + float fDamping; +}; // EAX_REVERBPROPERTIES + +inline bool operator==(const EAX_REVERBPROPERTIES& lhs, const EAX_REVERBPROPERTIES& rhs) noexcept +{ + return std::memcmp(&lhs, &rhs, sizeof(EAX_REVERBPROPERTIES)) == 0; +} + +extern const GUID DSPROPSETID_EAXBUFFER_ReverbProperties; + +enum DSPROPERTY_EAXBUFFER_REVERBPROPERTY : unsigned int +{ + DSPROPERTY_EAXBUFFER_ALL, + DSPROPERTY_EAXBUFFER_REVERBMIX, +}; // DSPROPERTY_EAXBUFFER_REVERBPROPERTY + +struct EAXBUFFER_REVERBPROPERTIES +{ + float fMix; +}; + +inline bool operator==(const EAXBUFFER_REVERBPROPERTIES& lhs, const EAXBUFFER_REVERBPROPERTIES& rhs) noexcept +{ + return lhs.fMix == rhs.fMix; +} + +constexpr auto EAX_BUFFER_MINREVERBMIX = 0.0F; +constexpr auto EAX_BUFFER_MAXREVERBMIX = 1.0F; +constexpr auto EAX_REVERBMIX_USEDISTANCE = -1.0F; + + +extern const GUID DSPROPSETID_EAX20_ListenerProperties; + +enum DSPROPERTY_EAX20_LISTENERPROPERTY : + unsigned int +{ + DSPROPERTY_EAX20LISTENER_NONE, + DSPROPERTY_EAX20LISTENER_ALLPARAMETERS, + DSPROPERTY_EAX20LISTENER_ROOM, + DSPROPERTY_EAX20LISTENER_ROOMHF, + DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAX20LISTENER_DECAYTIME, + DSPROPERTY_EAX20LISTENER_DECAYHFRATIO, + DSPROPERTY_EAX20LISTENER_REFLECTIONS, + DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY, + DSPROPERTY_EAX20LISTENER_REVERB, + DSPROPERTY_EAX20LISTENER_REVERBDELAY, + DSPROPERTY_EAX20LISTENER_ENVIRONMENT, + DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE, + DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION, + DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF, + DSPROPERTY_EAX20LISTENER_FLAGS +}; // DSPROPERTY_EAX20_LISTENERPROPERTY + +struct EAX20LISTENERPROPERTIES +{ + long lRoom; // room effect level at low frequencies + long lRoomHF; // room effect high-frequency level re. low frequency level + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + float flDecayTime; // reverberation decay time at low frequencies + float flDecayHFRatio; // high-frequency to low-frequency decay time ratio + long lReflections; // early reflections level relative to room effect + float flReflectionsDelay; // initial reflection delay time + long lReverb; // late reverberation level relative to room effect + float flReverbDelay; // late reverberation delay time relative to initial reflection + unsigned long dwEnvironment; // sets all listener properties + float flEnvironmentSize; // environment size in meters + float flEnvironmentDiffusion; // environment diffusion + float flAirAbsorptionHF; // change in level per meter at 5 kHz + unsigned long dwFlags; // modifies the behavior of properties +}; // EAX20LISTENERPROPERTIES + + +extern const GUID DSPROPSETID_EAX20_BufferProperties; + + +enum DSPROPERTY_EAX20_BUFFERPROPERTY : + unsigned int +{ + DSPROPERTY_EAX20BUFFER_NONE, + DSPROPERTY_EAX20BUFFER_ALLPARAMETERS, + DSPROPERTY_EAX20BUFFER_DIRECT, + DSPROPERTY_EAX20BUFFER_DIRECTHF, + DSPROPERTY_EAX20BUFFER_ROOM, + DSPROPERTY_EAX20BUFFER_ROOMHF, + DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAX20BUFFER_OBSTRUCTION, + DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO, + DSPROPERTY_EAX20BUFFER_OCCLUSION, + DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO, + DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO, + DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF, + DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR, + DSPROPERTY_EAX20BUFFER_FLAGS +}; // DSPROPERTY_EAX20_BUFFERPROPERTY + + +struct EAX20BUFFERPROPERTIES +{ + long lDirect; // direct path level + long lDirectHF; // direct path level at high frequencies + long lRoom; // room effect level + long lRoomHF; // room effect level at high frequencies + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + long lObstruction; // main obstruction control (attenuation at high frequencies) + float flObstructionLFRatio; // obstruction low-frequency level re. main control + long lOcclusion; // main occlusion control (attenuation at high frequencies) + float flOcclusionLFRatio; // occlusion low-frequency level re. main control + float flOcclusionRoomRatio; // occlusion room effect level re. main control + long lOutsideVolumeHF; // outside sound cone level at high frequencies + float flAirAbsorptionFactor; // multiplies DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF + unsigned long dwFlags; // modifies the behavior of properties +}; // EAX20BUFFERPROPERTIES + + +extern const GUID DSPROPSETID_EAX30_ListenerProperties; + +extern const GUID DSPROPSETID_EAX30_BufferProperties; + + +constexpr auto EAX_MAX_FXSLOTS = 4; + +constexpr auto EAX40_MAX_ACTIVE_FXSLOTS = 2; +constexpr auto EAX50_MAX_ACTIVE_FXSLOTS = 4; + + +constexpr auto EAX_OK = 0L; +constexpr auto EAXERR_INVALID_OPERATION = -1L; +constexpr auto EAXERR_INVALID_VALUE = -2L; +constexpr auto EAXERR_NO_EFFECT_LOADED = -3L; +constexpr auto EAXERR_UNKNOWN_EFFECT = -4L; +constexpr auto EAXERR_INCOMPATIBLE_SOURCE_TYPE = -5L; +constexpr auto EAXERR_INCOMPATIBLE_EAX_VERSION = -6L; + + +extern const GUID EAX_NULL_GUID; + +extern const GUID EAX_PrimaryFXSlotID; + + +struct EAXVECTOR +{ + float x; + float y; + float z; +}; // EAXVECTOR + +inline bool operator==(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept +{ return std::memcmp(&lhs, &rhs, sizeof(EAXVECTOR)) == 0; } + +inline bool operator!=(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept +{ return !(lhs == rhs); } + + +extern const GUID EAXPROPERTYID_EAX40_Context; + +extern const GUID EAXPROPERTYID_EAX50_Context; + +// EAX50 +enum : + unsigned long +{ + HEADPHONES = 0, + SPEAKERS_2, + SPEAKERS_4, + SPEAKERS_5, // 5.1 speakers + SPEAKERS_6, // 6.1 speakers + SPEAKERS_7, // 7.1 speakers +}; + +// EAX50 +enum : + unsigned long +{ + EAX_40 = 5, // EAX 4.0 + EAX_50 = 6, // EAX 5.0 +}; + +constexpr auto EAXCONTEXT_MINEAXSESSION = EAX_40; +constexpr auto EAXCONTEXT_MAXEAXSESSION = EAX_50; +constexpr auto EAXCONTEXT_DEFAULTEAXSESSION = EAX_40; + +constexpr auto EAXCONTEXT_MINMAXACTIVESENDS = 2UL; +constexpr auto EAXCONTEXT_MAXMAXACTIVESENDS = 4UL; +constexpr auto EAXCONTEXT_DEFAULTMAXACTIVESENDS = 2UL; + +// EAX50 +struct EAXSESSIONPROPERTIES +{ + unsigned long ulEAXVersion; + unsigned long ulMaxActiveSends; +}; // EAXSESSIONPROPERTIES + +enum EAXCONTEXT_PROPERTY : + unsigned int +{ + EAXCONTEXT_NONE = 0, + EAXCONTEXT_ALLPARAMETERS, + EAXCONTEXT_PRIMARYFXSLOTID, + EAXCONTEXT_DISTANCEFACTOR, + EAXCONTEXT_AIRABSORPTIONHF, + EAXCONTEXT_HFREFERENCE, + EAXCONTEXT_LASTERROR, + + // EAX50 + EAXCONTEXT_SPEAKERCONFIG, + EAXCONTEXT_EAXSESSION, + EAXCONTEXT_MACROFXFACTOR, +}; // EAXCONTEXT_PROPERTY + +struct EAX40CONTEXTPROPERTIES +{ + GUID guidPrimaryFXSlotID; + float flDistanceFactor; + float flAirAbsorptionHF; + float flHFReference; +}; // EAX40CONTEXTPROPERTIES + +struct EAX50CONTEXTPROPERTIES : + public EAX40CONTEXTPROPERTIES +{ + float flMacroFXFactor; +}; // EAX40CONTEXTPROPERTIES + + +bool operator==( + const EAX40CONTEXTPROPERTIES& lhs, + const EAX40CONTEXTPROPERTIES& rhs) noexcept; + +bool operator==( + const EAX50CONTEXTPROPERTIES& lhs, + const EAX50CONTEXTPROPERTIES& rhs) noexcept; + + +constexpr auto EAXCONTEXT_MINDISTANCEFACTOR = FLT_MIN; +constexpr auto EAXCONTEXT_MAXDISTANCEFACTOR = FLT_MAX; +constexpr auto EAXCONTEXT_DEFAULTDISTANCEFACTOR = 1.0F; + +constexpr auto EAXCONTEXT_MINAIRABSORPTIONHF = -100.0F; +constexpr auto EAXCONTEXT_MAXAIRABSORPTIONHF = 0.0F; +constexpr auto EAXCONTEXT_DEFAULTAIRABSORPTIONHF = -5.0F; + +constexpr auto EAXCONTEXT_MINHFREFERENCE = 1000.0F; +constexpr auto EAXCONTEXT_MAXHFREFERENCE = 20000.0F; +constexpr auto EAXCONTEXT_DEFAULTHFREFERENCE = 5000.0F; + +constexpr auto EAXCONTEXT_MINMACROFXFACTOR = 0.0F; +constexpr auto EAXCONTEXT_MAXMACROFXFACTOR = 1.0F; +constexpr auto EAXCONTEXT_DEFAULTMACROFXFACTOR = 0.0F; + + +extern const GUID EAXPROPERTYID_EAX40_FXSlot0; + +extern const GUID EAXPROPERTYID_EAX50_FXSlot0; + +extern const GUID EAXPROPERTYID_EAX40_FXSlot1; + +extern const GUID EAXPROPERTYID_EAX50_FXSlot1; + +extern const GUID EAXPROPERTYID_EAX40_FXSlot2; + +extern const GUID EAXPROPERTYID_EAX50_FXSlot2; + +extern const GUID EAXPROPERTYID_EAX40_FXSlot3; + +extern const GUID EAXPROPERTYID_EAX50_FXSlot3; + +extern const GUID EAXCONTEXT_DEFAULTPRIMARYFXSLOTID; + +enum EAXFXSLOT_PROPERTY : + unsigned int +{ + EAXFXSLOT_PARAMETER = 0, + + EAXFXSLOT_NONE = 0x10000, + EAXFXSLOT_ALLPARAMETERS, + EAXFXSLOT_LOADEFFECT, + EAXFXSLOT_VOLUME, + EAXFXSLOT_LOCK, + EAXFXSLOT_FLAGS, + + // EAX50 + EAXFXSLOT_OCCLUSION, + EAXFXSLOT_OCCLUSIONLFRATIO, +}; // EAXFXSLOT_PROPERTY + +constexpr auto EAXFXSLOTFLAGS_ENVIRONMENT = 0x00000001UL; +// EAX50 +constexpr auto EAXFXSLOTFLAGS_UPMIX = 0x00000002UL; + +constexpr auto EAX40FXSLOTFLAGS_RESERVED = 0xFFFFFFFEUL; // reserved future use +constexpr auto EAX50FXSLOTFLAGS_RESERVED = 0xFFFFFFFCUL; // reserved future use + + +constexpr auto EAXFXSLOT_MINVOLUME = -10'000L; +constexpr auto EAXFXSLOT_MAXVOLUME = 0L; +constexpr auto EAXFXSLOT_DEFAULTVOLUME = 0L; + +constexpr auto EAXFXSLOT_MINLOCK = 0L; +constexpr auto EAXFXSLOT_MAXLOCK = 1L; + +enum : + long +{ + EAXFXSLOT_UNLOCKED = 0, + EAXFXSLOT_LOCKED = 1 +}; + +constexpr auto EAXFXSLOT_MINOCCLUSION = -10'000L; +constexpr auto EAXFXSLOT_MAXOCCLUSION = 0L; +constexpr auto EAXFXSLOT_DEFAULTOCCLUSION = 0L; + +constexpr auto EAXFXSLOT_MINOCCLUSIONLFRATIO = 0.0F; +constexpr auto EAXFXSLOT_MAXOCCLUSIONLFRATIO = 1.0F; +constexpr auto EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO = 0.25F; + +constexpr auto EAX40FXSLOT_DEFAULTFLAGS = EAXFXSLOTFLAGS_ENVIRONMENT; + +constexpr auto EAX50FXSLOT_DEFAULTFLAGS = + EAXFXSLOTFLAGS_ENVIRONMENT | + EAXFXSLOTFLAGS_UPMIX; // ignored for reverb; + +struct EAX40FXSLOTPROPERTIES +{ + GUID guidLoadEffect; + long lVolume; + long lLock; + unsigned long ulFlags; +}; // EAX40FXSLOTPROPERTIES + +struct EAX50FXSLOTPROPERTIES : + public EAX40FXSLOTPROPERTIES +{ + long lOcclusion; + float flOcclusionLFRatio; +}; // EAX50FXSLOTPROPERTIES + +bool operator==( + const EAX40FXSLOTPROPERTIES& lhs, + const EAX40FXSLOTPROPERTIES& rhs) noexcept; + +bool operator==( + const EAX50FXSLOTPROPERTIES& lhs, + const EAX50FXSLOTPROPERTIES& rhs) noexcept; + +extern const GUID EAXPROPERTYID_EAX40_Source; + +extern const GUID EAXPROPERTYID_EAX50_Source; + +// Source object properties +enum EAXSOURCE_PROPERTY : + unsigned int +{ + // EAX30 + + EAXSOURCE_NONE, + EAXSOURCE_ALLPARAMETERS, + EAXSOURCE_OBSTRUCTIONPARAMETERS, + EAXSOURCE_OCCLUSIONPARAMETERS, + EAXSOURCE_EXCLUSIONPARAMETERS, + EAXSOURCE_DIRECT, + EAXSOURCE_DIRECTHF, + EAXSOURCE_ROOM, + EAXSOURCE_ROOMHF, + EAXSOURCE_OBSTRUCTION, + EAXSOURCE_OBSTRUCTIONLFRATIO, + EAXSOURCE_OCCLUSION, + EAXSOURCE_OCCLUSIONLFRATIO, + EAXSOURCE_OCCLUSIONROOMRATIO, + EAXSOURCE_OCCLUSIONDIRECTRATIO, + EAXSOURCE_EXCLUSION, + EAXSOURCE_EXCLUSIONLFRATIO, + EAXSOURCE_OUTSIDEVOLUMEHF, + EAXSOURCE_DOPPLERFACTOR, + EAXSOURCE_ROLLOFFFACTOR, + EAXSOURCE_ROOMROLLOFFFACTOR, + EAXSOURCE_AIRABSORPTIONFACTOR, + EAXSOURCE_FLAGS, + + // EAX40 + + EAXSOURCE_SENDPARAMETERS, + EAXSOURCE_ALLSENDPARAMETERS, + EAXSOURCE_OCCLUSIONSENDPARAMETERS, + EAXSOURCE_EXCLUSIONSENDPARAMETERS, + EAXSOURCE_ACTIVEFXSLOTID, + + // EAX50 + + EAXSOURCE_MACROFXFACTOR, + EAXSOURCE_SPEAKERLEVELS, + EAXSOURCE_ALL2DPARAMETERS, +}; // EAXSOURCE_PROPERTY + + +constexpr auto EAXSOURCEFLAGS_DIRECTHFAUTO = 0x00000001UL; // relates to EAXSOURCE_DIRECTHF +constexpr auto EAXSOURCEFLAGS_ROOMAUTO = 0x00000002UL; // relates to EAXSOURCE_ROOM +constexpr auto EAXSOURCEFLAGS_ROOMHFAUTO = 0x00000004UL; // relates to EAXSOURCE_ROOMHF +// EAX50 +constexpr auto EAXSOURCEFLAGS_3DELEVATIONFILTER = 0x00000008UL; +// EAX50 +constexpr auto EAXSOURCEFLAGS_UPMIX = 0x00000010UL; +// EAX50 +constexpr auto EAXSOURCEFLAGS_APPLYSPEAKERLEVELS = 0x00000020UL; + +constexpr auto EAX20SOURCEFLAGS_RESERVED = 0xFFFFFFF8UL; // reserved future use +constexpr auto EAX50SOURCEFLAGS_RESERVED = 0xFFFFFFC0UL; // reserved future use + + +constexpr auto EAXSOURCE_MINSEND = -10'000L; +constexpr auto EAXSOURCE_MAXSEND = 0L; +constexpr auto EAXSOURCE_DEFAULTSEND = 0L; + +constexpr auto EAXSOURCE_MINSENDHF = -10'000L; +constexpr auto EAXSOURCE_MAXSENDHF = 0L; +constexpr auto EAXSOURCE_DEFAULTSENDHF = 0L; + +constexpr auto EAXSOURCE_MINDIRECT = -10'000L; +constexpr auto EAXSOURCE_MAXDIRECT = 1'000L; +constexpr auto EAXSOURCE_DEFAULTDIRECT = 0L; + +constexpr auto EAXSOURCE_MINDIRECTHF = -10'000L; +constexpr auto EAXSOURCE_MAXDIRECTHF = 0L; +constexpr auto EAXSOURCE_DEFAULTDIRECTHF = 0L; + +constexpr auto EAXSOURCE_MINROOM = -10'000L; +constexpr auto EAXSOURCE_MAXROOM = 1'000L; +constexpr auto EAXSOURCE_DEFAULTROOM = 0L; + +constexpr auto EAXSOURCE_MINROOMHF = -10'000L; +constexpr auto EAXSOURCE_MAXROOMHF = 0L; +constexpr auto EAXSOURCE_DEFAULTROOMHF = 0L; + +constexpr auto EAXSOURCE_MINOBSTRUCTION = -10'000L; +constexpr auto EAXSOURCE_MAXOBSTRUCTION = 0L; +constexpr auto EAXSOURCE_DEFAULTOBSTRUCTION = 0L; + +constexpr auto EAXSOURCE_MINOBSTRUCTIONLFRATIO = 0.0F; +constexpr auto EAXSOURCE_MAXOBSTRUCTIONLFRATIO = 1.0F; +constexpr auto EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO = 0.0F; + +constexpr auto EAXSOURCE_MINOCCLUSION = -10'000L; +constexpr auto EAXSOURCE_MAXOCCLUSION = 0L; +constexpr auto EAXSOURCE_DEFAULTOCCLUSION = 0L; + +constexpr auto EAXSOURCE_MINOCCLUSIONLFRATIO = 0.0F; +constexpr auto EAXSOURCE_MAXOCCLUSIONLFRATIO = 1.0F; +constexpr auto EAXSOURCE_DEFAULTOCCLUSIONLFRATIO = 0.25F; + +constexpr auto EAXSOURCE_MINOCCLUSIONROOMRATIO = 0.0F; +constexpr auto EAXSOURCE_MAXOCCLUSIONROOMRATIO = 10.0F; +constexpr auto EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO = 1.5F; + +constexpr auto EAXSOURCE_MINOCCLUSIONDIRECTRATIO = 0.0F; +constexpr auto EAXSOURCE_MAXOCCLUSIONDIRECTRATIO = 10.0F; +constexpr auto EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO = 1.0F; + +constexpr auto EAXSOURCE_MINEXCLUSION = -10'000L; +constexpr auto EAXSOURCE_MAXEXCLUSION = 0L; +constexpr auto EAXSOURCE_DEFAULTEXCLUSION = 0L; + +constexpr auto EAXSOURCE_MINEXCLUSIONLFRATIO = 0.0F; +constexpr auto EAXSOURCE_MAXEXCLUSIONLFRATIO = 1.0F; +constexpr auto EAXSOURCE_DEFAULTEXCLUSIONLFRATIO = 1.0F; + +constexpr auto EAXSOURCE_MINOUTSIDEVOLUMEHF = -10'000L; +constexpr auto EAXSOURCE_MAXOUTSIDEVOLUMEHF = 0L; +constexpr auto EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF = 0L; + +constexpr auto EAXSOURCE_MINDOPPLERFACTOR = 0.0F; +constexpr auto EAXSOURCE_MAXDOPPLERFACTOR = 10.0F; +constexpr auto EAXSOURCE_DEFAULTDOPPLERFACTOR = 1.0F; + +constexpr auto EAXSOURCE_MINROLLOFFFACTOR = 0.0F; +constexpr auto EAXSOURCE_MAXROLLOFFFACTOR = 10.0F; +constexpr auto EAXSOURCE_DEFAULTROLLOFFFACTOR = 0.0F; + +constexpr auto EAXSOURCE_MINROOMROLLOFFFACTOR = 0.0F; +constexpr auto EAXSOURCE_MAXROOMROLLOFFFACTOR = 10.0F; +constexpr auto EAXSOURCE_DEFAULTROOMROLLOFFFACTOR = 0.0F; + +constexpr auto EAXSOURCE_MINAIRABSORPTIONFACTOR = 0.0F; +constexpr auto EAXSOURCE_MAXAIRABSORPTIONFACTOR = 10.0F; +constexpr auto EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR = 0.0F; + +// EAX50 + +constexpr auto EAXSOURCE_MINMACROFXFACTOR = 0.0F; +constexpr auto EAXSOURCE_MAXMACROFXFACTOR = 1.0F; +constexpr auto EAXSOURCE_DEFAULTMACROFXFACTOR = 1.0F; + +// EAX50 + +constexpr auto EAXSOURCE_MINSPEAKERLEVEL = -10'000L; +constexpr auto EAXSOURCE_MAXSPEAKERLEVEL = 0L; +constexpr auto EAXSOURCE_DEFAULTSPEAKERLEVEL = -10'000L; + +constexpr auto EAXSOURCE_DEFAULTFLAGS = + EAXSOURCEFLAGS_DIRECTHFAUTO | + EAXSOURCEFLAGS_ROOMAUTO | + EAXSOURCEFLAGS_ROOMHFAUTO; + +enum : + long +{ + EAXSPEAKER_FRONT_LEFT = 1, + EAXSPEAKER_FRONT_CENTER = 2, + EAXSPEAKER_FRONT_RIGHT = 3, + EAXSPEAKER_SIDE_RIGHT = 4, + EAXSPEAKER_REAR_RIGHT = 5, + EAXSPEAKER_REAR_CENTER = 6, + EAXSPEAKER_REAR_LEFT = 7, + EAXSPEAKER_SIDE_LEFT = 8, + EAXSPEAKER_LOW_FREQUENCY = 9 +}; + +// EAXSOURCEFLAGS_DIRECTHFAUTO, EAXSOURCEFLAGS_ROOMAUTO and EAXSOURCEFLAGS_ROOMHFAUTO are ignored for 2D sources +// EAXSOURCEFLAGS_UPMIX is ignored for 3D sources +constexpr auto EAX50SOURCE_DEFAULTFLAGS = + EAXSOURCEFLAGS_DIRECTHFAUTO | + EAXSOURCEFLAGS_ROOMAUTO | + EAXSOURCEFLAGS_ROOMHFAUTO | + EAXSOURCEFLAGS_UPMIX; + +struct EAX30SOURCEPROPERTIES +{ + long lDirect; // direct path level (at low and mid frequencies) + long lDirectHF; // relative direct path level at high frequencies + long lRoom; // room effect level (at low and mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lObstruction; // main obstruction control (attenuation at high frequencies) + float flObstructionLFRatio; // obstruction low-frequency level re. main control + long lOcclusion; // main occlusion control (attenuation at high frequencies) + float flOcclusionLFRatio; // occlusion low-frequency level re. main control + float flOcclusionRoomRatio; // relative occlusion control for room effect + float flOcclusionDirectRatio; // relative occlusion control for direct path + long lExclusion; // main exlusion control (attenuation at high frequencies) + float flExclusionLFRatio; // exclusion low-frequency level re. main control + long lOutsideVolumeHF; // outside sound cone level at high frequencies + float flDopplerFactor; // like DS3D flDopplerFactor but per source + float flRolloffFactor; // like DS3D flRolloffFactor but per source + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + float flAirAbsorptionFactor; // multiplies EAXREVERB_AIRABSORPTIONHF + unsigned long ulFlags; // modifies the behavior of properties +}; // EAX30SOURCEPROPERTIES + +struct EAX50SOURCEPROPERTIES : + public EAX30SOURCEPROPERTIES +{ + float flMacroFXFactor; +}; // EAX50SOURCEPROPERTIES + +struct EAXSOURCEALLSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lSend; // send level (at low and mid frequencies) + long lSendHF; // relative send level at high frequencies + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; + long lExclusion; + float flExclusionLFRatio; +}; // EAXSOURCEALLSENDPROPERTIES + +struct EAXSOURCE2DPROPERTIES +{ + long lDirect; // direct path level (at low and mid frequencies) + long lDirectHF; // relative direct path level at high frequencies + long lRoom; // room effect level (at low and mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + unsigned long ulFlags; // modifies the behavior of properties +}; // EAXSOURCE2DPROPERTIES + +struct EAXSPEAKERLEVELPROPERTIES +{ + long lSpeakerID; + long lLevel; +}; // EAXSPEAKERLEVELPROPERTIES + +struct EAX40ACTIVEFXSLOTS +{ + GUID guidActiveFXSlots[EAX40_MAX_ACTIVE_FXSLOTS]; +}; // EAX40ACTIVEFXSLOTS + +struct EAX50ACTIVEFXSLOTS +{ + GUID guidActiveFXSlots[EAX50_MAX_ACTIVE_FXSLOTS]; +}; // EAX50ACTIVEFXSLOTS + +bool operator==( + const EAX50ACTIVEFXSLOTS& lhs, + const EAX50ACTIVEFXSLOTS& rhs) noexcept; + +bool operator!=( + const EAX50ACTIVEFXSLOTS& lhs, + const EAX50ACTIVEFXSLOTS& rhs) noexcept; + +// Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property. +struct EAXOBSTRUCTIONPROPERTIES +{ + long lObstruction; + float flObstructionLFRatio; +}; // EAXOBSTRUCTIONPROPERTIES + +// Use this structure for EAXSOURCE_OCCLUSIONPARAMETERS property. +struct EAXOCCLUSIONPROPERTIES +{ + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; +}; // EAXOCCLUSIONPROPERTIES + +// Use this structure for EAXSOURCE_EXCLUSIONPARAMETERS property. +struct EAXEXCLUSIONPROPERTIES +{ + long lExclusion; + float flExclusionLFRatio; +}; // EAXEXCLUSIONPROPERTIES + +// Use this structure for EAXSOURCE_SENDPARAMETERS properties. +struct EAXSOURCESENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lSend; + long lSendHF; +}; // EAXSOURCESENDPROPERTIES + +// Use this structure for EAXSOURCE_OCCLUSIONSENDPARAMETERS +struct EAXSOURCEOCCLUSIONSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; +}; // EAXSOURCEOCCLUSIONSENDPROPERTIES + +// Use this structure for EAXSOURCE_EXCLUSIONSENDPARAMETERS +struct EAXSOURCEEXCLUSIONSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lExclusion; + float flExclusionLFRatio; +}; // EAXSOURCEEXCLUSIONSENDPROPERTIES + +extern const EAX50ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID; + +extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; + +extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID; + + +// EAX Reverb Effect + +extern const GUID EAX_REVERB_EFFECT; + +// Reverb effect properties +enum EAXREVERB_PROPERTY : + unsigned int +{ + EAXREVERB_NONE, + EAXREVERB_ALLPARAMETERS, + EAXREVERB_ENVIRONMENT, + EAXREVERB_ENVIRONMENTSIZE, + EAXREVERB_ENVIRONMENTDIFFUSION, + EAXREVERB_ROOM, + EAXREVERB_ROOMHF, + EAXREVERB_ROOMLF, + EAXREVERB_DECAYTIME, + EAXREVERB_DECAYHFRATIO, + EAXREVERB_DECAYLFRATIO, + EAXREVERB_REFLECTIONS, + EAXREVERB_REFLECTIONSDELAY, + EAXREVERB_REFLECTIONSPAN, + EAXREVERB_REVERB, + EAXREVERB_REVERBDELAY, + EAXREVERB_REVERBPAN, + EAXREVERB_ECHOTIME, + EAXREVERB_ECHODEPTH, + EAXREVERB_MODULATIONTIME, + EAXREVERB_MODULATIONDEPTH, + EAXREVERB_AIRABSORPTIONHF, + EAXREVERB_HFREFERENCE, + EAXREVERB_LFREFERENCE, + EAXREVERB_ROOMROLLOFFFACTOR, + EAXREVERB_FLAGS, +}; // EAXREVERB_PROPERTY + +// used by EAXREVERB_ENVIRONMENT +enum : + unsigned long +{ + EAX_ENVIRONMENT_GENERIC, + EAX_ENVIRONMENT_PADDEDCELL, + EAX_ENVIRONMENT_ROOM, + EAX_ENVIRONMENT_BATHROOM, + EAX_ENVIRONMENT_LIVINGROOM, + EAX_ENVIRONMENT_STONEROOM, + EAX_ENVIRONMENT_AUDITORIUM, + EAX_ENVIRONMENT_CONCERTHALL, + EAX_ENVIRONMENT_CAVE, + EAX_ENVIRONMENT_ARENA, + EAX_ENVIRONMENT_HANGAR, + EAX_ENVIRONMENT_CARPETEDHALLWAY, + EAX_ENVIRONMENT_HALLWAY, + EAX_ENVIRONMENT_STONECORRIDOR, + EAX_ENVIRONMENT_ALLEY, + EAX_ENVIRONMENT_FOREST, + EAX_ENVIRONMENT_CITY, + EAX_ENVIRONMENT_MOUNTAINS, + EAX_ENVIRONMENT_QUARRY, + EAX_ENVIRONMENT_PLAIN, + EAX_ENVIRONMENT_PARKINGLOT, + EAX_ENVIRONMENT_SEWERPIPE, + EAX_ENVIRONMENT_UNDERWATER, + EAX_ENVIRONMENT_DRUGGED, + EAX_ENVIRONMENT_DIZZY, + EAX_ENVIRONMENT_PSYCHOTIC, + + EAX1_ENVIRONMENT_COUNT, + + // EAX30 + EAX_ENVIRONMENT_UNDEFINED = EAX1_ENVIRONMENT_COUNT, + + EAX3_ENVIRONMENT_COUNT, +}; + + +// reverberation decay time +constexpr auto EAXREVERBFLAGS_DECAYTIMESCALE = 0x00000001UL; + +// reflection level +constexpr auto EAXREVERBFLAGS_REFLECTIONSSCALE = 0x00000002UL; + +// initial reflection delay time +constexpr auto EAXREVERBFLAGS_REFLECTIONSDELAYSCALE = 0x00000004UL; + +// reflections level +constexpr auto EAXREVERBFLAGS_REVERBSCALE = 0x00000008UL; + +// late reverberation delay time +constexpr auto EAXREVERBFLAGS_REVERBDELAYSCALE = 0x00000010UL; + +// echo time +// EAX30+ +constexpr auto EAXREVERBFLAGS_ECHOTIMESCALE = 0x00000040UL; + +// modulation time +// EAX30+ +constexpr auto EAXREVERBFLAGS_MODULATIONTIMESCALE = 0x00000080UL; + +// This flag limits high-frequency decay time according to air absorption. +constexpr auto EAXREVERBFLAGS_DECAYHFLIMIT = 0x00000020UL; + +constexpr auto EAXREVERBFLAGS_RESERVED = 0xFFFFFF00UL; // reserved future use + + +struct EAXREVERBPROPERTIES +{ + unsigned long ulEnvironment; // sets all reverb properties + float flEnvironmentSize; // environment size in meters + float flEnvironmentDiffusion; // environment diffusion + long lRoom; // room effect level (at mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lRoomLF; // relative room effect level at low frequencies + float flDecayTime; // reverberation decay time at mid frequencies + float flDecayHFRatio; // high-frequency to mid-frequency decay time ratio + float flDecayLFRatio; // low-frequency to mid-frequency decay time ratio + long lReflections; // early reflections level relative to room effect + float flReflectionsDelay; // initial reflection delay time + EAXVECTOR vReflectionsPan; // early reflections panning vector + long lReverb; // late reverberation level relative to room effect + float flReverbDelay; // late reverberation delay time relative to initial reflection + EAXVECTOR vReverbPan; // late reverberation panning vector + float flEchoTime; // echo time + float flEchoDepth; // echo depth + float flModulationTime; // modulation time + float flModulationDepth; // modulation depth + float flAirAbsorptionHF; // change in level per meter at high frequencies + float flHFReference; // reference high frequency + float flLFReference; // reference low frequency + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + unsigned long ulFlags; // modifies the behavior of properties +}; // EAXREVERBPROPERTIES + +bool operator==( + const EAXREVERBPROPERTIES& lhs, + const EAXREVERBPROPERTIES& rhs) noexcept; + +bool operator!=( + const EAXREVERBPROPERTIES& lhs, + const EAXREVERBPROPERTIES& rhs) noexcept; + + +constexpr auto EAXREVERB_MINENVIRONMENT = static_cast(EAX_ENVIRONMENT_GENERIC); +constexpr auto EAX1REVERB_MAXENVIRONMENT = static_cast(EAX_ENVIRONMENT_PSYCHOTIC); +constexpr auto EAX30REVERB_MAXENVIRONMENT = static_cast(EAX_ENVIRONMENT_UNDEFINED); +constexpr auto EAXREVERB_DEFAULTENVIRONMENT = static_cast(EAX_ENVIRONMENT_GENERIC); + +constexpr auto EAXREVERB_MINENVIRONMENTSIZE = 1.0F; +constexpr auto EAXREVERB_MAXENVIRONMENTSIZE = 100.0F; +constexpr auto EAXREVERB_DEFAULTENVIRONMENTSIZE = 7.5F; + +constexpr auto EAXREVERB_MINENVIRONMENTDIFFUSION = 0.0F; +constexpr auto EAXREVERB_MAXENVIRONMENTDIFFUSION = 1.0F; +constexpr auto EAXREVERB_DEFAULTENVIRONMENTDIFFUSION = 1.0F; + +constexpr auto EAXREVERB_MINROOM = -10'000L; +constexpr auto EAXREVERB_MAXROOM = 0L; +constexpr auto EAXREVERB_DEFAULTROOM = -1'000L; + +constexpr auto EAXREVERB_MINROOMHF = -10'000L; +constexpr auto EAXREVERB_MAXROOMHF = 0L; +constexpr auto EAXREVERB_DEFAULTROOMHF = -100L; + +constexpr auto EAXREVERB_MINROOMLF = -10'000L; +constexpr auto EAXREVERB_MAXROOMLF = 0L; +constexpr auto EAXREVERB_DEFAULTROOMLF = 0L; + +constexpr auto EAXREVERB_MINDECAYTIME = 0.1F; +constexpr auto EAXREVERB_MAXDECAYTIME = 20.0F; +constexpr auto EAXREVERB_DEFAULTDECAYTIME = 1.49F; + +constexpr auto EAXREVERB_MINDECAYHFRATIO = 0.1F; +constexpr auto EAXREVERB_MAXDECAYHFRATIO = 2.0F; +constexpr auto EAXREVERB_DEFAULTDECAYHFRATIO = 0.83F; + +constexpr auto EAXREVERB_MINDECAYLFRATIO = 0.1F; +constexpr auto EAXREVERB_MAXDECAYLFRATIO = 2.0F; +constexpr auto EAXREVERB_DEFAULTDECAYLFRATIO = 1.0F; + +constexpr auto EAXREVERB_MINREFLECTIONS = -10'000L; +constexpr auto EAXREVERB_MAXREFLECTIONS = 1'000L; +constexpr auto EAXREVERB_DEFAULTREFLECTIONS = -2'602L; + +constexpr auto EAXREVERB_MINREFLECTIONSDELAY = 0.0F; +constexpr auto EAXREVERB_MAXREFLECTIONSDELAY = 0.3F; +constexpr auto EAXREVERB_DEFAULTREFLECTIONSDELAY = 0.007F; + +constexpr auto EAXREVERB_DEFAULTREFLECTIONSPAN = EAXVECTOR{0.0F, 0.0F, 0.0F}; + +constexpr auto EAXREVERB_MINREVERB = -10'000L; +constexpr auto EAXREVERB_MAXREVERB = 2'000L; +constexpr auto EAXREVERB_DEFAULTREVERB = 200L; + +constexpr auto EAXREVERB_MINREVERBDELAY = 0.0F; +constexpr auto EAXREVERB_MAXREVERBDELAY = 0.1F; +constexpr auto EAXREVERB_DEFAULTREVERBDELAY = 0.011F; + +constexpr auto EAXREVERB_DEFAULTREVERBPAN = EAXVECTOR{0.0F, 0.0F, 0.0F}; + +constexpr auto EAXREVERB_MINECHOTIME = 0.075F; +constexpr auto EAXREVERB_MAXECHOTIME = 0.25F; +constexpr auto EAXREVERB_DEFAULTECHOTIME = 0.25F; + +constexpr auto EAXREVERB_MINECHODEPTH = 0.0F; +constexpr auto EAXREVERB_MAXECHODEPTH = 1.0F; +constexpr auto EAXREVERB_DEFAULTECHODEPTH = 0.0F; + +constexpr auto EAXREVERB_MINMODULATIONTIME = 0.04F; +constexpr auto EAXREVERB_MAXMODULATIONTIME = 4.0F; +constexpr auto EAXREVERB_DEFAULTMODULATIONTIME = 0.25F; + +constexpr auto EAXREVERB_MINMODULATIONDEPTH = 0.0F; +constexpr auto EAXREVERB_MAXMODULATIONDEPTH = 1.0F; +constexpr auto EAXREVERB_DEFAULTMODULATIONDEPTH = 0.0F; + +constexpr auto EAXREVERB_MINAIRABSORPTIONHF = -100.0F; +constexpr auto EAXREVERB_MAXAIRABSORPTIONHF = 0.0F; +constexpr auto EAXREVERB_DEFAULTAIRABSORPTIONHF = -5.0F; + +constexpr auto EAXREVERB_MINHFREFERENCE = 1'000.0F; +constexpr auto EAXREVERB_MAXHFREFERENCE = 20'000.0F; +constexpr auto EAXREVERB_DEFAULTHFREFERENCE = 5'000.0F; + +constexpr auto EAXREVERB_MINLFREFERENCE = 20.0F; +constexpr auto EAXREVERB_MAXLFREFERENCE = 1'000.0F; +constexpr auto EAXREVERB_DEFAULTLFREFERENCE = 250.0F; + +constexpr auto EAXREVERB_MINROOMROLLOFFFACTOR = 0.0F; +constexpr auto EAXREVERB_MAXROOMROLLOFFFACTOR = 10.0F; +constexpr auto EAXREVERB_DEFAULTROOMROLLOFFFACTOR = 0.0F; + +constexpr auto EAX1REVERB_MINVOLUME = 0.0F; +constexpr auto EAX1REVERB_MAXVOLUME = 1.0F; + +constexpr auto EAX1REVERB_MINDAMPING = 0.0F; +constexpr auto EAX1REVERB_MAXDAMPING = 2.0F; + +constexpr auto EAXREVERB_DEFAULTFLAGS = + EAXREVERBFLAGS_DECAYTIMESCALE | + EAXREVERBFLAGS_REFLECTIONSSCALE | + EAXREVERBFLAGS_REFLECTIONSDELAYSCALE | + EAXREVERBFLAGS_REVERBSCALE | + EAXREVERBFLAGS_REVERBDELAYSCALE | + EAXREVERBFLAGS_DECAYHFLIMIT; + + +using EaxReverbPresets = std::array; +extern const EaxReverbPresets EAXREVERB_PRESETS; + + +using Eax1ReverbPresets = std::array; +extern const Eax1ReverbPresets EAX1REVERB_PRESETS; + + +// AGC Compressor Effect + +extern const GUID EAX_AGCCOMPRESSOR_EFFECT; + +enum EAXAGCCOMPRESSOR_PROPERTY : + unsigned int +{ + EAXAGCCOMPRESSOR_NONE, + EAXAGCCOMPRESSOR_ALLPARAMETERS, + EAXAGCCOMPRESSOR_ONOFF, +}; // EAXAGCCOMPRESSOR_PROPERTY + +struct EAXAGCCOMPRESSORPROPERTIES +{ + unsigned long ulOnOff; // Switch Compressor on or off +}; // EAXAGCCOMPRESSORPROPERTIES + + +constexpr auto EAXAGCCOMPRESSOR_MINONOFF = 0UL; +constexpr auto EAXAGCCOMPRESSOR_MAXONOFF = 1UL; +constexpr auto EAXAGCCOMPRESSOR_DEFAULTONOFF = EAXAGCCOMPRESSOR_MAXONOFF; + + +// Autowah Effect + +extern const GUID EAX_AUTOWAH_EFFECT; + +enum EAXAUTOWAH_PROPERTY : + unsigned int +{ + EAXAUTOWAH_NONE, + EAXAUTOWAH_ALLPARAMETERS, + EAXAUTOWAH_ATTACKTIME, + EAXAUTOWAH_RELEASETIME, + EAXAUTOWAH_RESONANCE, + EAXAUTOWAH_PEAKLEVEL, +}; // EAXAUTOWAH_PROPERTY + +struct EAXAUTOWAHPROPERTIES +{ + float flAttackTime; // Attack time (seconds) + float flReleaseTime; // Release time (seconds) + long lResonance; // Resonance (mB) + long lPeakLevel; // Peak level (mB) +}; // EAXAUTOWAHPROPERTIES + + +constexpr auto EAXAUTOWAH_MINATTACKTIME = 0.0001F; +constexpr auto EAXAUTOWAH_MAXATTACKTIME = 1.0F; +constexpr auto EAXAUTOWAH_DEFAULTATTACKTIME = 0.06F; + +constexpr auto EAXAUTOWAH_MINRELEASETIME = 0.0001F; +constexpr auto EAXAUTOWAH_MAXRELEASETIME = 1.0F; +constexpr auto EAXAUTOWAH_DEFAULTRELEASETIME = 0.06F; + +constexpr auto EAXAUTOWAH_MINRESONANCE = 600L; +constexpr auto EAXAUTOWAH_MAXRESONANCE = 6000L; +constexpr auto EAXAUTOWAH_DEFAULTRESONANCE = 6000L; + +constexpr auto EAXAUTOWAH_MINPEAKLEVEL = -9000L; +constexpr auto EAXAUTOWAH_MAXPEAKLEVEL = 9000L; +constexpr auto EAXAUTOWAH_DEFAULTPEAKLEVEL = 2100L; + + +// Chorus Effect + +extern const GUID EAX_CHORUS_EFFECT; + + +enum EAXCHORUS_PROPERTY : + unsigned int +{ + EAXCHORUS_NONE, + EAXCHORUS_ALLPARAMETERS, + EAXCHORUS_WAVEFORM, + EAXCHORUS_PHASE, + EAXCHORUS_RATE, + EAXCHORUS_DEPTH, + EAXCHORUS_FEEDBACK, + EAXCHORUS_DELAY, +}; // EAXCHORUS_PROPERTY + +enum : + unsigned long +{ + EAX_CHORUS_SINUSOID, + EAX_CHORUS_TRIANGLE, +}; + +struct EAXCHORUSPROPERTIES +{ + unsigned long ulWaveform; // Waveform selector - see enum above + long lPhase; // Phase (Degrees) + float flRate; // Rate (Hz) + float flDepth; // Depth (0 to 1) + float flFeedback; // Feedback (-1 to 1) + float flDelay; // Delay (seconds) +}; // EAXCHORUSPROPERTIES + + +constexpr auto EAXCHORUS_MINWAVEFORM = 0UL; +constexpr auto EAXCHORUS_MAXWAVEFORM = 1UL; +constexpr auto EAXCHORUS_DEFAULTWAVEFORM = 1UL; + +constexpr auto EAXCHORUS_MINPHASE = -180L; +constexpr auto EAXCHORUS_MAXPHASE = 180L; +constexpr auto EAXCHORUS_DEFAULTPHASE = 90L; + +constexpr auto EAXCHORUS_MINRATE = 0.0F; +constexpr auto EAXCHORUS_MAXRATE = 10.0F; +constexpr auto EAXCHORUS_DEFAULTRATE = 1.1F; + +constexpr auto EAXCHORUS_MINDEPTH = 0.0F; +constexpr auto EAXCHORUS_MAXDEPTH = 1.0F; +constexpr auto EAXCHORUS_DEFAULTDEPTH = 0.1F; + +constexpr auto EAXCHORUS_MINFEEDBACK = -1.0F; +constexpr auto EAXCHORUS_MAXFEEDBACK = 1.0F; +constexpr auto EAXCHORUS_DEFAULTFEEDBACK = 0.25F; + +constexpr auto EAXCHORUS_MINDELAY = 0.0002F; +constexpr auto EAXCHORUS_MAXDELAY = 0.016F; +constexpr auto EAXCHORUS_DEFAULTDELAY = 0.016F; + + +// Distortion Effect + +extern const GUID EAX_DISTORTION_EFFECT; + +enum EAXDISTORTION_PROPERTY : + unsigned int +{ + EAXDISTORTION_NONE, + EAXDISTORTION_ALLPARAMETERS, + EAXDISTORTION_EDGE, + EAXDISTORTION_GAIN, + EAXDISTORTION_LOWPASSCUTOFF, + EAXDISTORTION_EQCENTER, + EAXDISTORTION_EQBANDWIDTH, +}; // EAXDISTORTION_PROPERTY + + +struct EAXDISTORTIONPROPERTIES +{ + float flEdge; // Controls the shape of the distortion (0 to 1) + long lGain; // Controls the post distortion gain (mB) + float flLowPassCutOff; // Controls the cut-off of the filter pre-distortion (Hz) + float flEQCenter; // Controls the center frequency of the EQ post-distortion (Hz) + float flEQBandwidth; // Controls the bandwidth of the EQ post-distortion (Hz) +}; // EAXDISTORTIONPROPERTIES + + +constexpr auto EAXDISTORTION_MINEDGE = 0.0F; +constexpr auto EAXDISTORTION_MAXEDGE = 1.0F; +constexpr auto EAXDISTORTION_DEFAULTEDGE = 0.2F; + +constexpr auto EAXDISTORTION_MINGAIN = -6000L; +constexpr auto EAXDISTORTION_MAXGAIN = 0L; +constexpr auto EAXDISTORTION_DEFAULTGAIN = -2600L; + +constexpr auto EAXDISTORTION_MINLOWPASSCUTOFF = 80.0F; +constexpr auto EAXDISTORTION_MAXLOWPASSCUTOFF = 24000.0F; +constexpr auto EAXDISTORTION_DEFAULTLOWPASSCUTOFF = 8000.0F; + +constexpr auto EAXDISTORTION_MINEQCENTER = 80.0F; +constexpr auto EAXDISTORTION_MAXEQCENTER = 24000.0F; +constexpr auto EAXDISTORTION_DEFAULTEQCENTER = 3600.0F; + +constexpr auto EAXDISTORTION_MINEQBANDWIDTH = 80.0F; +constexpr auto EAXDISTORTION_MAXEQBANDWIDTH = 24000.0F; +constexpr auto EAXDISTORTION_DEFAULTEQBANDWIDTH = 3600.0F; + + +// Echo Effect + +extern const GUID EAX_ECHO_EFFECT; + + +enum EAXECHO_PROPERTY : + unsigned int +{ + EAXECHO_NONE, + EAXECHO_ALLPARAMETERS, + EAXECHO_DELAY, + EAXECHO_LRDELAY, + EAXECHO_DAMPING, + EAXECHO_FEEDBACK, + EAXECHO_SPREAD, +}; // EAXECHO_PROPERTY + + +struct EAXECHOPROPERTIES +{ + float flDelay; // Controls the initial delay time (seconds) + float flLRDelay; // Controls the delay time between the first and second taps (seconds) + float flDamping; // Controls a low-pass filter that dampens the echoes (0 to 1) + float flFeedback; // Controls the duration of echo repetition (0 to 1) + float flSpread; // Controls the left-right spread of the echoes +}; // EAXECHOPROPERTIES + + +constexpr auto EAXECHO_MINDAMPING = 0.0F; +constexpr auto EAXECHO_MAXDAMPING = 0.99F; +constexpr auto EAXECHO_DEFAULTDAMPING = 0.5F; + +constexpr auto EAXECHO_MINDELAY = 0.002F; +constexpr auto EAXECHO_MAXDELAY = 0.207F; +constexpr auto EAXECHO_DEFAULTDELAY = 0.1F; + +constexpr auto EAXECHO_MINLRDELAY = 0.0F; +constexpr auto EAXECHO_MAXLRDELAY = 0.404F; +constexpr auto EAXECHO_DEFAULTLRDELAY = 0.1F; + +constexpr auto EAXECHO_MINFEEDBACK = 0.0F; +constexpr auto EAXECHO_MAXFEEDBACK = 1.0F; +constexpr auto EAXECHO_DEFAULTFEEDBACK = 0.5F; + +constexpr auto EAXECHO_MINSPREAD = -1.0F; +constexpr auto EAXECHO_MAXSPREAD = 1.0F; +constexpr auto EAXECHO_DEFAULTSPREAD = -1.0F; + + +// Equalizer Effect + +extern const GUID EAX_EQUALIZER_EFFECT; + + +enum EAXEQUALIZER_PROPERTY : + unsigned int +{ + EAXEQUALIZER_NONE, + EAXEQUALIZER_ALLPARAMETERS, + EAXEQUALIZER_LOWGAIN, + EAXEQUALIZER_LOWCUTOFF, + EAXEQUALIZER_MID1GAIN, + EAXEQUALIZER_MID1CENTER, + EAXEQUALIZER_MID1WIDTH, + EAXEQUALIZER_MID2GAIN, + EAXEQUALIZER_MID2CENTER, + EAXEQUALIZER_MID2WIDTH, + EAXEQUALIZER_HIGHGAIN, + EAXEQUALIZER_HIGHCUTOFF, +}; // EAXEQUALIZER_PROPERTY + + +struct EAXEQUALIZERPROPERTIES +{ + long lLowGain; // (mB) + float flLowCutOff; // (Hz) + long lMid1Gain; // (mB) + float flMid1Center; // (Hz) + float flMid1Width; // (octaves) + long lMid2Gain; // (mB) + float flMid2Center; // (Hz) + float flMid2Width; // (octaves) + long lHighGain; // (mB) + float flHighCutOff; // (Hz) +}; // EAXEQUALIZERPROPERTIES + + +constexpr auto EAXEQUALIZER_MINLOWGAIN = -1800L; +constexpr auto EAXEQUALIZER_MAXLOWGAIN = 1800L; +constexpr auto EAXEQUALIZER_DEFAULTLOWGAIN = 0L; + +constexpr auto EAXEQUALIZER_MINLOWCUTOFF = 50.0F; +constexpr auto EAXEQUALIZER_MAXLOWCUTOFF = 800.0F; +constexpr auto EAXEQUALIZER_DEFAULTLOWCUTOFF = 200.0F; + +constexpr auto EAXEQUALIZER_MINMID1GAIN = -1800L; +constexpr auto EAXEQUALIZER_MAXMID1GAIN = 1800L; +constexpr auto EAXEQUALIZER_DEFAULTMID1GAIN = 0L; + +constexpr auto EAXEQUALIZER_MINMID1CENTER = 200.0F; +constexpr auto EAXEQUALIZER_MAXMID1CENTER = 3000.0F; +constexpr auto EAXEQUALIZER_DEFAULTMID1CENTER = 500.0F; + +constexpr auto EAXEQUALIZER_MINMID1WIDTH = 0.01F; +constexpr auto EAXEQUALIZER_MAXMID1WIDTH = 1.0F; +constexpr auto EAXEQUALIZER_DEFAULTMID1WIDTH = 1.0F; + +constexpr auto EAXEQUALIZER_MINMID2GAIN = -1800L; +constexpr auto EAXEQUALIZER_MAXMID2GAIN = 1800L; +constexpr auto EAXEQUALIZER_DEFAULTMID2GAIN = 0L; + +constexpr auto EAXEQUALIZER_MINMID2CENTER = 1000.0F; +constexpr auto EAXEQUALIZER_MAXMID2CENTER = 8000.0F; +constexpr auto EAXEQUALIZER_DEFAULTMID2CENTER = 3000.0F; + +constexpr auto EAXEQUALIZER_MINMID2WIDTH = 0.01F; +constexpr auto EAXEQUALIZER_MAXMID2WIDTH = 1.0F; +constexpr auto EAXEQUALIZER_DEFAULTMID2WIDTH = 1.0F; + +constexpr auto EAXEQUALIZER_MINHIGHGAIN = -1800L; +constexpr auto EAXEQUALIZER_MAXHIGHGAIN = 1800L; +constexpr auto EAXEQUALIZER_DEFAULTHIGHGAIN = 0L; + +constexpr auto EAXEQUALIZER_MINHIGHCUTOFF = 4000.0F; +constexpr auto EAXEQUALIZER_MAXHIGHCUTOFF = 16000.0F; +constexpr auto EAXEQUALIZER_DEFAULTHIGHCUTOFF = 6000.0F; + + +// Flanger Effect + +extern const GUID EAX_FLANGER_EFFECT; + +enum EAXFLANGER_PROPERTY : + unsigned int +{ + EAXFLANGER_NONE, + EAXFLANGER_ALLPARAMETERS, + EAXFLANGER_WAVEFORM, + EAXFLANGER_PHASE, + EAXFLANGER_RATE, + EAXFLANGER_DEPTH, + EAXFLANGER_FEEDBACK, + EAXFLANGER_DELAY, +}; // EAXFLANGER_PROPERTY + +enum : + unsigned long +{ + EAX_FLANGER_SINUSOID, + EAX_FLANGER_TRIANGLE, +}; + +struct EAXFLANGERPROPERTIES +{ + unsigned long ulWaveform; // Waveform selector - see enum above + long lPhase; // Phase (Degrees) + float flRate; // Rate (Hz) + float flDepth; // Depth (0 to 1) + float flFeedback; // Feedback (0 to 1) + float flDelay; // Delay (seconds) +}; // EAXFLANGERPROPERTIES + + +constexpr auto EAXFLANGER_MINWAVEFORM = 0UL; +constexpr auto EAXFLANGER_MAXWAVEFORM = 1UL; +constexpr auto EAXFLANGER_DEFAULTWAVEFORM = 1UL; + +constexpr auto EAXFLANGER_MINPHASE = -180L; +constexpr auto EAXFLANGER_MAXPHASE = 180L; +constexpr auto EAXFLANGER_DEFAULTPHASE = 0L; + +constexpr auto EAXFLANGER_MINRATE = 0.0F; +constexpr auto EAXFLANGER_MAXRATE = 10.0F; +constexpr auto EAXFLANGER_DEFAULTRATE = 0.27F; + +constexpr auto EAXFLANGER_MINDEPTH = 0.0F; +constexpr auto EAXFLANGER_MAXDEPTH = 1.0F; +constexpr auto EAXFLANGER_DEFAULTDEPTH = 1.0F; + +constexpr auto EAXFLANGER_MINFEEDBACK = -1.0F; +constexpr auto EAXFLANGER_MAXFEEDBACK = 1.0F; +constexpr auto EAXFLANGER_DEFAULTFEEDBACK = -0.5F; + +constexpr auto EAXFLANGER_MINDELAY = 0.0002F; +constexpr auto EAXFLANGER_MAXDELAY = 0.004F; +constexpr auto EAXFLANGER_DEFAULTDELAY = 0.002F; + + +// Frequency Shifter Effect + +extern const GUID EAX_FREQUENCYSHIFTER_EFFECT; + +enum EAXFREQUENCYSHIFTER_PROPERTY : + unsigned int +{ + EAXFREQUENCYSHIFTER_NONE, + EAXFREQUENCYSHIFTER_ALLPARAMETERS, + EAXFREQUENCYSHIFTER_FREQUENCY, + EAXFREQUENCYSHIFTER_LEFTDIRECTION, + EAXFREQUENCYSHIFTER_RIGHTDIRECTION, +}; // EAXFREQUENCYSHIFTER_PROPERTY + +enum : + unsigned long +{ + EAX_FREQUENCYSHIFTER_DOWN, + EAX_FREQUENCYSHIFTER_UP, + EAX_FREQUENCYSHIFTER_OFF +}; + +struct EAXFREQUENCYSHIFTERPROPERTIES +{ + float flFrequency; // (Hz) + unsigned long ulLeftDirection; // see enum above + unsigned long ulRightDirection; // see enum above +}; // EAXFREQUENCYSHIFTERPROPERTIES + + +constexpr auto EAXFREQUENCYSHIFTER_MINFREQUENCY = 0.0F; +constexpr auto EAXFREQUENCYSHIFTER_MAXFREQUENCY = 24000.0F; +constexpr auto EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY = EAXFREQUENCYSHIFTER_MINFREQUENCY; + +constexpr auto EAXFREQUENCYSHIFTER_MINLEFTDIRECTION = 0UL; +constexpr auto EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION = 2UL; +constexpr auto EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION = EAXFREQUENCYSHIFTER_MINLEFTDIRECTION; + +constexpr auto EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION = 0UL; +constexpr auto EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION = 2UL; +constexpr auto EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION = EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION; + + +// Vocal Morpher Effect + +extern const GUID EAX_VOCALMORPHER_EFFECT; + +enum EAXVOCALMORPHER_PROPERTY : + unsigned int +{ + EAXVOCALMORPHER_NONE, + EAXVOCALMORPHER_ALLPARAMETERS, + EAXVOCALMORPHER_PHONEMEA, + EAXVOCALMORPHER_PHONEMEACOARSETUNING, + EAXVOCALMORPHER_PHONEMEB, + EAXVOCALMORPHER_PHONEMEBCOARSETUNING, + EAXVOCALMORPHER_WAVEFORM, + EAXVOCALMORPHER_RATE, +}; // EAXVOCALMORPHER_PROPERTY + +enum : + unsigned long +{ + A, + E, + I, + O, + U, + AA, + AE, + AH, + AO, + EH, + ER, + IH, + IY, + UH, + UW, + B, + D, + F, + G, + J, + K, + L, + M, + N, + P, + R, + S, + T, + V, + Z, +}; + +enum : + unsigned long +{ + EAX_VOCALMORPHER_SINUSOID, + EAX_VOCALMORPHER_TRIANGLE, + EAX_VOCALMORPHER_SAWTOOTH +}; + +// Use this structure for EAXVOCALMORPHER_ALLPARAMETERS +struct EAXVOCALMORPHERPROPERTIES +{ + unsigned long ulPhonemeA; // see enum above + long lPhonemeACoarseTuning; // (semitones) + unsigned long ulPhonemeB; // see enum above + long lPhonemeBCoarseTuning; // (semitones) + unsigned long ulWaveform; // Waveform selector - see enum above + float flRate; // (Hz) +}; // EAXVOCALMORPHERPROPERTIES + + +constexpr auto EAXVOCALMORPHER_MINPHONEMEA = 0UL; +constexpr auto EAXVOCALMORPHER_MAXPHONEMEA = 29UL; +constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEA = EAXVOCALMORPHER_MINPHONEMEA; + +constexpr auto EAXVOCALMORPHER_MINPHONEMEACOARSETUNING = -24L; +constexpr auto EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING = 24L; +constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING = 0L; + +constexpr auto EAXVOCALMORPHER_MINPHONEMEB = 0UL; +constexpr auto EAXVOCALMORPHER_MAXPHONEMEB = 29UL; +constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEB = 10UL; + +constexpr auto EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING = -24L; +constexpr auto EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING = 24L; +constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING = 0L; + +constexpr auto EAXVOCALMORPHER_MINWAVEFORM = 0UL; +constexpr auto EAXVOCALMORPHER_MAXWAVEFORM = 2UL; +constexpr auto EAXVOCALMORPHER_DEFAULTWAVEFORM = EAXVOCALMORPHER_MINWAVEFORM; + +constexpr auto EAXVOCALMORPHER_MINRATE = 0.0F; +constexpr auto EAXVOCALMORPHER_MAXRATE = 10.0F; +constexpr auto EAXVOCALMORPHER_DEFAULTRATE = 1.41F; + + +// Pitch Shifter Effect + +extern const GUID EAX_PITCHSHIFTER_EFFECT; + +enum EAXPITCHSHIFTER_PROPERTY : + unsigned int +{ + EAXPITCHSHIFTER_NONE, + EAXPITCHSHIFTER_ALLPARAMETERS, + EAXPITCHSHIFTER_COARSETUNE, + EAXPITCHSHIFTER_FINETUNE, +}; // EAXPITCHSHIFTER_PROPERTY + +struct EAXPITCHSHIFTERPROPERTIES +{ + long lCoarseTune; // Amount of pitch shift (semitones) + long lFineTune; // Amount of pitch shift (cents) +}; // EAXPITCHSHIFTERPROPERTIES + + +constexpr auto EAXPITCHSHIFTER_MINCOARSETUNE = -12L; +constexpr auto EAXPITCHSHIFTER_MAXCOARSETUNE = 12L; +constexpr auto EAXPITCHSHIFTER_DEFAULTCOARSETUNE = 12L; + +constexpr auto EAXPITCHSHIFTER_MINFINETUNE = -50L; +constexpr auto EAXPITCHSHIFTER_MAXFINETUNE = 50L; +constexpr auto EAXPITCHSHIFTER_DEFAULTFINETUNE = 0L; + + +// Ring Modulator Effect + +extern const GUID EAX_RINGMODULATOR_EFFECT; + +enum EAXRINGMODULATOR_PROPERTY : + unsigned int +{ + EAXRINGMODULATOR_NONE, + EAXRINGMODULATOR_ALLPARAMETERS, + EAXRINGMODULATOR_FREQUENCY, + EAXRINGMODULATOR_HIGHPASSCUTOFF, + EAXRINGMODULATOR_WAVEFORM, +}; // EAXRINGMODULATOR_PROPERTY + +enum : + unsigned long +{ + EAX_RINGMODULATOR_SINUSOID, + EAX_RINGMODULATOR_SAWTOOTH, + EAX_RINGMODULATOR_SQUARE, +}; + +// Use this structure for EAXRINGMODULATOR_ALLPARAMETERS +struct EAXRINGMODULATORPROPERTIES +{ + float flFrequency; // Frequency of modulation (Hz) + float flHighPassCutOff; // Cut-off frequency of high-pass filter (Hz) + unsigned long ulWaveform; // Waveform selector - see enum above +}; // EAXRINGMODULATORPROPERTIES + + +constexpr auto EAXRINGMODULATOR_MINFREQUENCY = 0.0F; +constexpr auto EAXRINGMODULATOR_MAXFREQUENCY = 8000.0F; +constexpr auto EAXRINGMODULATOR_DEFAULTFREQUENCY = 440.0F; + +constexpr auto EAXRINGMODULATOR_MINHIGHPASSCUTOFF = 0.0F; +constexpr auto EAXRINGMODULATOR_MAXHIGHPASSCUTOFF = 24000.0F; +constexpr auto EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF = 800.0F; + +constexpr auto EAXRINGMODULATOR_MINWAVEFORM = 0UL; +constexpr auto EAXRINGMODULATOR_MAXWAVEFORM = 2UL; +constexpr auto EAXRINGMODULATOR_DEFAULTWAVEFORM = EAXRINGMODULATOR_MINWAVEFORM; + + +using LPEAXSET = ALenum(AL_APIENTRY*)( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size); + +using LPEAXGET = ALenum(AL_APIENTRY*)( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size); + + +#endif // !EAX_API_INCLUDED diff --git a/modules/openal-soft/al/eax_eax_call.cpp b/modules/openal-soft/al/eax_eax_call.cpp new file mode 100644 index 0000000..914d2fb --- /dev/null +++ b/modules/openal-soft/al/eax_eax_call.cpp @@ -0,0 +1,324 @@ +#include "config.h" + +#include "al/eax_eax_call.h" + +#include "al/eax_exception.h" + + +namespace { + +constexpr auto deferred_flag = 0x80000000U; + +class EaxEaxCallException : + public EaxException +{ +public: + explicit EaxEaxCallException( + const char* message) + : + EaxException{"EAX_EAX_CALL", message} + { + } +}; // EaxEaxCallException + +} // namespace + + +EaxEaxCall::EaxEaxCall( + bool is_get, + const GUID& property_set_guid, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size) + : is_get_{is_get}, version_{0}, property_set_id_{EaxEaxCallPropertySetId::none} + , property_id_{property_id & ~deferred_flag}, property_source_id_{property_source_id} + , property_buffer_{property_buffer}, property_size_{property_size} +{ + if (false) + { + } + else if (property_set_guid == EAXPROPERTYID_EAX40_Context) + { + version_ = 4; + property_set_id_ = EaxEaxCallPropertySetId::context; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_Context) + { + version_ = 5; + property_set_id_ = EaxEaxCallPropertySetId::context; + } + else if (property_set_guid == DSPROPSETID_EAX20_ListenerProperties) + { + version_ = 2; + fx_slot_index_ = 0u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect; + property_id_ = convert_eax_v2_0_listener_property_id(property_id_); + } + else if (property_set_guid == DSPROPSETID_EAX30_ListenerProperties) + { + version_ = 3; + fx_slot_index_ = 0u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot0) + { + version_ = 4; + fx_slot_index_ = 0u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot0) + { + version_ = 5; + fx_slot_index_ = 0u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot1) + { + version_ = 4; + fx_slot_index_ = 1u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot1) + { + version_ = 5; + fx_slot_index_ = 1u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot2) + { + version_ = 4; + fx_slot_index_ = 2u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot2) + { + version_ = 5; + fx_slot_index_ = 2u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot3) + { + version_ = 4; + fx_slot_index_ = 3u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot3) + { + version_ = 5; + fx_slot_index_ = 3u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == DSPROPSETID_EAX20_BufferProperties) + { + version_ = 2; + property_set_id_ = EaxEaxCallPropertySetId::source; + property_id_ = convert_eax_v2_0_buffer_property_id(property_id_); + } + else if (property_set_guid == DSPROPSETID_EAX30_BufferProperties) + { + version_ = 3; + property_set_id_ = EaxEaxCallPropertySetId::source; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_Source) + { + version_ = 4; + property_set_id_ = EaxEaxCallPropertySetId::source; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_Source) + { + version_ = 5; + property_set_id_ = EaxEaxCallPropertySetId::source; + } + else if (property_set_guid == DSPROPSETID_EAX_ReverbProperties) + { + version_ = 1; + fx_slot_index_ = 0u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect; + } + else if (property_set_guid == DSPROPSETID_EAXBUFFER_ReverbProperties) + { + version_ = 1; + property_set_id_ = EaxEaxCallPropertySetId::source; + } + else + { + fail("Unsupported property set id."); + } + + if (version_ < 1 || version_ > 5) + { + fail("EAX version out of range."); + } + + if(!(property_id&deferred_flag)) + { + if(property_set_id_ != EaxEaxCallPropertySetId::fx_slot && property_id_ != 0) + { + if (!property_buffer) + { + fail("Null property buffer."); + } + + if (property_size == 0) + { + fail("Empty property."); + } + } + } + + if(property_set_id_ == EaxEaxCallPropertySetId::source && property_source_id_ == 0) + { + fail("Null AL source id."); + } + + if (property_set_id_ == EaxEaxCallPropertySetId::fx_slot) + { + if (property_id_ < EAXFXSLOT_NONE) + { + property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect; + } + } +} + +[[noreturn]] +void EaxEaxCall::fail( + const char* message) +{ + throw EaxEaxCallException{message}; +} + +ALuint EaxEaxCall::convert_eax_v2_0_listener_property_id( + ALuint property_id) +{ + switch (property_id) + { + case DSPROPERTY_EAX20LISTENER_NONE: + return EAXREVERB_NONE; + + case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS: + return EAXREVERB_ALLPARAMETERS; + + case DSPROPERTY_EAX20LISTENER_ROOM: + return EAXREVERB_ROOM; + + case DSPROPERTY_EAX20LISTENER_ROOMHF: + return EAXREVERB_ROOMHF; + + case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: + return EAXREVERB_ROOMROLLOFFFACTOR; + + case DSPROPERTY_EAX20LISTENER_DECAYTIME: + return EAXREVERB_DECAYTIME; + + case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: + return EAXREVERB_DECAYHFRATIO; + + case DSPROPERTY_EAX20LISTENER_REFLECTIONS: + return EAXREVERB_REFLECTIONS; + + case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: + return EAXREVERB_REFLECTIONSDELAY; + + case DSPROPERTY_EAX20LISTENER_REVERB: + return EAXREVERB_REVERB; + + case DSPROPERTY_EAX20LISTENER_REVERBDELAY: + return EAXREVERB_REVERBDELAY; + + case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: + return EAXREVERB_ENVIRONMENT; + + case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: + return EAXREVERB_ENVIRONMENTSIZE; + + case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: + return EAXREVERB_ENVIRONMENTDIFFUSION; + + case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: + return EAXREVERB_AIRABSORPTIONHF; + + case DSPROPERTY_EAX20LISTENER_FLAGS: + return EAXREVERB_FLAGS; + + default: + fail("Unsupported EAX 2.0 listener property id."); + } +} + +ALuint EaxEaxCall::convert_eax_v2_0_buffer_property_id( + ALuint property_id) +{ + switch (property_id) + { + case DSPROPERTY_EAX20BUFFER_NONE: + return EAXSOURCE_NONE; + + case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS: + return EAXSOURCE_ALLPARAMETERS; + + case DSPROPERTY_EAX20BUFFER_DIRECT: + return EAXSOURCE_DIRECT; + + case DSPROPERTY_EAX20BUFFER_DIRECTHF: + return EAXSOURCE_DIRECTHF; + + case DSPROPERTY_EAX20BUFFER_ROOM: + return EAXSOURCE_ROOM; + + case DSPROPERTY_EAX20BUFFER_ROOMHF: + return EAXSOURCE_ROOMHF; + + case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR: + return EAXSOURCE_ROOMROLLOFFFACTOR; + + case DSPROPERTY_EAX20BUFFER_OBSTRUCTION: + return EAXSOURCE_OBSTRUCTION; + + case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO: + return EAXSOURCE_OBSTRUCTIONLFRATIO; + + case DSPROPERTY_EAX20BUFFER_OCCLUSION: + return EAXSOURCE_OCCLUSION; + + case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO: + return EAXSOURCE_OCCLUSIONLFRATIO; + + case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO: + return EAXSOURCE_OCCLUSIONROOMRATIO; + + case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF: + return EAXSOURCE_OUTSIDEVOLUMEHF; + + case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR: + return EAXSOURCE_AIRABSORPTIONFACTOR; + + case DSPROPERTY_EAX20BUFFER_FLAGS: + return EAXSOURCE_FLAGS; + + default: + fail("Unsupported EAX 2.0 buffer property id."); + } +} + + +EaxEaxCall create_eax_call( + bool is_get, + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size) +{ + if(!property_set_id) + throw EaxEaxCallException{"Null property set ID."}; + + return EaxEaxCall{ + is_get, + *property_set_id, + property_id, + property_source_id, + property_buffer, + property_size + }; +} diff --git a/modules/openal-soft/al/eax_eax_call.h b/modules/openal-soft/al/eax_eax_call.h new file mode 100644 index 0000000..7b990d8 --- /dev/null +++ b/modules/openal-soft/al/eax_eax_call.h @@ -0,0 +1,117 @@ +#ifndef EAX_EAX_CALL_INCLUDED +#define EAX_EAX_CALL_INCLUDED + + +#include "AL/al.h" + +#include "alspan.h" + +#include "eax_api.h" +#include "eax_fx_slot_index.h" + + +enum class EaxEaxCallPropertySetId +{ + none, + + context, + fx_slot, + source, + fx_slot_effect, +}; // EaxEaxCallPropertySetId + + +class EaxEaxCall +{ +public: + EaxEaxCall( + bool is_get, + const GUID& property_set_guid, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size); + + bool is_get() const noexcept { return is_get_; } + int get_version() const noexcept { return version_; } + EaxEaxCallPropertySetId get_property_set_id() const noexcept { return property_set_id_; } + ALuint get_property_id() const noexcept { return property_id_; } + ALuint get_property_al_name() const noexcept { return property_source_id_; } + EaxFxSlotIndex get_fx_slot_index() const noexcept { return fx_slot_index_; } + + template< + typename TException, + typename TValue + > + TValue& get_value() const + { + if (property_size_ < static_cast(sizeof(TValue))) + { + throw TException{"Property buffer too small."}; + } + + return *static_cast(property_buffer_); + } + + template< + typename TException, + typename TValue + > + al::span get_values() const + { + if (property_size_ < static_cast(sizeof(TValue))) + { + throw TException{"Property buffer too small."}; + } + + const auto count = property_size_ / sizeof(TValue); + + return al::span{static_cast(property_buffer_), count}; + } + + template< + typename TException, + typename TValue + > + void set_value( + const TValue& value) const + { + get_value() = value; + } + + +private: + const bool is_get_; + int version_; + EaxFxSlotIndex fx_slot_index_; + EaxEaxCallPropertySetId property_set_id_; + + ALuint property_id_; + const ALuint property_source_id_; + ALvoid*const property_buffer_; + const ALuint property_size_; + + + [[noreturn]] + static void fail( + const char* message); + + + static ALuint convert_eax_v2_0_listener_property_id( + ALuint property_id); + + static ALuint convert_eax_v2_0_buffer_property_id( + ALuint property_id); +}; // EaxEaxCall + + +EaxEaxCall create_eax_call( + bool is_get, + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size); + + +#endif // !EAX_EAX_CALL_INCLUDED diff --git a/modules/openal-soft/al/eax_effect.cpp b/modules/openal-soft/al/eax_effect.cpp new file mode 100644 index 0000000..9cbf4c1 --- /dev/null +++ b/modules/openal-soft/al/eax_effect.cpp @@ -0,0 +1,3 @@ +#include "config.h" + +#include "eax_effect.h" diff --git a/modules/openal-soft/al/eax_effect.h b/modules/openal-soft/al/eax_effect.h new file mode 100644 index 0000000..45315ca --- /dev/null +++ b/modules/openal-soft/al/eax_effect.h @@ -0,0 +1,44 @@ +#ifndef EAX_EFFECT_INCLUDED +#define EAX_EFFECT_INCLUDED + + +#include + +#include "AL/al.h" +#include "core/effects/base.h" +#include "eax_eax_call.h" + +class EaxEffect +{ +public: + EaxEffect(ALenum type) : al_effect_type_{type} { } + virtual ~EaxEffect() = default; + + const ALenum al_effect_type_; + EffectProps al_effect_props_{}; + + virtual void dispatch(const EaxEaxCall& eax_call) = 0; + + // Returns "true" if any immediated property was changed. + // [[nodiscard]] + virtual bool apply_deferred() = 0; +}; // EaxEffect + + +using EaxEffectUPtr = std::unique_ptr; + +EaxEffectUPtr eax_create_eax_null_effect(); +EaxEffectUPtr eax_create_eax_chorus_effect(); +EaxEffectUPtr eax_create_eax_distortion_effect(); +EaxEffectUPtr eax_create_eax_echo_effect(); +EaxEffectUPtr eax_create_eax_flanger_effect(); +EaxEffectUPtr eax_create_eax_frequency_shifter_effect(); +EaxEffectUPtr eax_create_eax_vocal_morpher_effect(); +EaxEffectUPtr eax_create_eax_pitch_shifter_effect(); +EaxEffectUPtr eax_create_eax_ring_modulator_effect(); +EaxEffectUPtr eax_create_eax_auto_wah_effect(); +EaxEffectUPtr eax_create_eax_compressor_effect(); +EaxEffectUPtr eax_create_eax_equalizer_effect(); +EaxEffectUPtr eax_create_eax_reverb_effect(); + +#endif // !EAX_EFFECT_INCLUDED diff --git a/modules/openal-soft/al/eax_exception.cpp b/modules/openal-soft/al/eax_exception.cpp new file mode 100644 index 0000000..fdeecaa --- /dev/null +++ b/modules/openal-soft/al/eax_exception.cpp @@ -0,0 +1,63 @@ +#include "config.h" + +#include "eax_exception.h" + +#include + +#include + + +EaxException::EaxException( + const char* context, + const char* message) + : + std::runtime_error{make_message(context, message)} +{ +} + +std::string EaxException::make_message( + const char* context, + const char* message) +{ + const auto context_size = (context ? std::string::traits_type::length(context) : 0); + const auto has_contex = (context_size > 0); + + const auto message_size = (message ? std::string::traits_type::length(message) : 0); + const auto has_message = (message_size > 0); + + if (!has_contex && !has_message) + { + return std::string{}; + } + + static constexpr char left_prefix[] = "["; + const auto left_prefix_size = std::string::traits_type::length(left_prefix); + + static constexpr char right_prefix[] = "] "; + const auto right_prefix_size = std::string::traits_type::length(right_prefix); + + const auto what_size = + ( + has_contex ? + left_prefix_size + context_size + right_prefix_size : + 0) + + message_size + + 1; + + auto what = std::string{}; + what.reserve(what_size); + + if (has_contex) + { + what.append(left_prefix, left_prefix_size); + what.append(context, context_size); + what.append(right_prefix, right_prefix_size); + } + + if (has_message) + { + what.append(message, message_size); + } + + return what; +} diff --git a/modules/openal-soft/al/eax_exception.h b/modules/openal-soft/al/eax_exception.h new file mode 100644 index 0000000..9a7acf7 --- /dev/null +++ b/modules/openal-soft/al/eax_exception.h @@ -0,0 +1,25 @@ +#ifndef EAX_EXCEPTION_INCLUDED +#define EAX_EXCEPTION_INCLUDED + + +#include +#include + + +class EaxException : + public std::runtime_error +{ +public: + EaxException( + const char* context, + const char* message); + + +private: + static std::string make_message( + const char* context, + const char* message); +}; // EaxException + + +#endif // !EAX_EXCEPTION_INCLUDED diff --git a/modules/openal-soft/al/eax_fx_slot_index.cpp b/modules/openal-soft/al/eax_fx_slot_index.cpp new file mode 100644 index 0000000..9aa695a --- /dev/null +++ b/modules/openal-soft/al/eax_fx_slot_index.cpp @@ -0,0 +1,71 @@ +#include "config.h" + +#include "eax_fx_slot_index.h" + +#include "eax_exception.h" + + +namespace +{ + + +class EaxFxSlotIndexException : + public EaxException +{ +public: + explicit EaxFxSlotIndexException( + const char* message) + : + EaxException{"EAX_FX_SLOT_INDEX", message} + { + } +}; // EaxFxSlotIndexException + + +} // namespace + + +void EaxFxSlotIndex::set(EaxFxSlotIndexValue index) +{ + if(index >= EaxFxSlotIndexValue{EAX_MAX_FXSLOTS}) + fail("Index out of range."); + + emplace(index); +} + +void EaxFxSlotIndex::set(const GUID &guid) +{ + if (false) + { + } + else if (guid == EAX_NULL_GUID) + { + reset(); + } + else if (guid == EAXPROPERTYID_EAX40_FXSlot0 || guid == EAXPROPERTYID_EAX50_FXSlot0) + { + emplace(0u); + } + else if (guid == EAXPROPERTYID_EAX40_FXSlot1 || guid == EAXPROPERTYID_EAX50_FXSlot1) + { + emplace(1u); + } + else if (guid == EAXPROPERTYID_EAX40_FXSlot2 || guid == EAXPROPERTYID_EAX50_FXSlot2) + { + emplace(2u); + } + else if (guid == EAXPROPERTYID_EAX40_FXSlot3 || guid == EAXPROPERTYID_EAX50_FXSlot3) + { + emplace(3u); + } + else + { + fail("Unsupported GUID."); + } +} + +[[noreturn]] +void EaxFxSlotIndex::fail(const char* message) +{ + throw EaxFxSlotIndexException{message}; +} diff --git a/modules/openal-soft/al/eax_fx_slot_index.h b/modules/openal-soft/al/eax_fx_slot_index.h new file mode 100644 index 0000000..e1e5475 --- /dev/null +++ b/modules/openal-soft/al/eax_fx_slot_index.h @@ -0,0 +1,41 @@ +#ifndef EAX_FX_SLOT_INDEX_INCLUDED +#define EAX_FX_SLOT_INDEX_INCLUDED + + +#include + +#include "aloptional.h" +#include "eax_api.h" + + +using EaxFxSlotIndexValue = std::size_t; + +class EaxFxSlotIndex : public al::optional +{ +public: + using al::optional::optional; + + EaxFxSlotIndex& operator=(const EaxFxSlotIndexValue &value) { set(value); return *this; } + EaxFxSlotIndex& operator=(const GUID &guid) { set(guid); return *this; } + + void set(EaxFxSlotIndexValue index); + void set(const GUID& guid); + +private: + [[noreturn]] + static void fail(const char *message); +}; // EaxFxSlotIndex + +inline bool operator==(const EaxFxSlotIndex& lhs, const EaxFxSlotIndex& rhs) noexcept +{ + if(lhs.has_value() != rhs.has_value()) + return false; + if(lhs.has_value()) + return *lhs == *rhs; + return true; +} + +inline bool operator!=(const EaxFxSlotIndex& lhs, const EaxFxSlotIndex& rhs) noexcept +{ return !(lhs == rhs); } + +#endif // !EAX_FX_SLOT_INDEX_INCLUDED diff --git a/modules/openal-soft/al/eax_fx_slots.cpp b/modules/openal-soft/al/eax_fx_slots.cpp new file mode 100644 index 0000000..3a27dab --- /dev/null +++ b/modules/openal-soft/al/eax_fx_slots.cpp @@ -0,0 +1,84 @@ +#include "config.h" + +#include "eax_fx_slots.h" + +#include + +#include "eax_exception.h" + +#include "eax_api.h" + + +namespace +{ + + +class EaxFxSlotsException : + public EaxException +{ +public: + explicit EaxFxSlotsException( + const char* message) + : + EaxException{"EAX_FX_SLOTS", message} + { + } +}; // EaxFxSlotsException + + +} // namespace + + +void EaxFxSlots::initialize( + ALCcontext& al_context) +{ + initialize_fx_slots(al_context); +} + +void EaxFxSlots::uninitialize() noexcept +{ + for (auto& fx_slot : fx_slots_) + { + fx_slot = nullptr; + } +} + +const ALeffectslot& EaxFxSlots::get(EaxFxSlotIndex index) const +{ + if(!index.has_value()) + fail("Empty index."); + return *fx_slots_[index.value()]; +} + +ALeffectslot& EaxFxSlots::get(EaxFxSlotIndex index) +{ + if(!index.has_value()) + fail("Empty index."); + return *fx_slots_[index.value()]; +} + +void EaxFxSlots::unlock_legacy() noexcept +{ + fx_slots_[0]->eax_unlock_legacy(); + fx_slots_[1]->eax_unlock_legacy(); +} + +[[noreturn]] +void EaxFxSlots::fail( + const char* message) +{ + throw EaxFxSlotsException{message}; +} + +void EaxFxSlots::initialize_fx_slots( + ALCcontext& al_context) +{ + auto fx_slot_index = EaxFxSlotIndexValue{}; + + for (auto& fx_slot : fx_slots_) + { + fx_slot = eax_create_al_effect_slot(al_context); + fx_slot->eax_initialize(al_context, fx_slot_index); + fx_slot_index += 1; + } +} diff --git a/modules/openal-soft/al/eax_fx_slots.h b/modules/openal-soft/al/eax_fx_slots.h new file mode 100644 index 0000000..a104c6a --- /dev/null +++ b/modules/openal-soft/al/eax_fx_slots.h @@ -0,0 +1,54 @@ +#ifndef EAX_FX_SLOTS_INCLUDED +#define EAX_FX_SLOTS_INCLUDED + + +#include + +#include "al/auxeffectslot.h" + +#include "eax_api.h" + +#include "eax_fx_slot_index.h" + + +class EaxFxSlots +{ +public: + void initialize( + ALCcontext& al_context); + + void uninitialize() noexcept; + + void commit() + { + for(auto& fx_slot : fx_slots_) + fx_slot->eax_commit(); + } + + + const ALeffectslot& get( + EaxFxSlotIndex index) const; + + ALeffectslot& get( + EaxFxSlotIndex index); + + void unlock_legacy() noexcept; + + +private: + using Items = std::array; + + + Items fx_slots_{}; + + + [[noreturn]] + static void fail( + const char* message); + + void initialize_fx_slots( + ALCcontext& al_context); +}; // EaxFxSlots + + +#endif // !EAX_FX_SLOTS_INCLUDED diff --git a/modules/openal-soft/al/eax_globals.cpp b/modules/openal-soft/al/eax_globals.cpp new file mode 100644 index 0000000..0790945 --- /dev/null +++ b/modules/openal-soft/al/eax_globals.cpp @@ -0,0 +1,21 @@ +#include "config.h" + +#include "eax_globals.h" + + +bool eax_g_is_enabled = true; + + +const char eax1_ext_name[] = "EAX"; +const char eax2_ext_name[] = "EAX2.0"; +const char eax3_ext_name[] = "EAX3.0"; +const char eax4_ext_name[] = "EAX4.0"; +const char eax5_ext_name[] = "EAX5.0"; + +const char eax_x_ram_ext_name[] = "EAX-RAM"; + +const char eax_eax_set_func_name[] = "EAXSet"; +const char eax_eax_get_func_name[] = "EAXGet"; + +const char eax_eax_set_buffer_mode_func_name[] = "EAXSetBufferMode"; +const char eax_eax_get_buffer_mode_func_name[] = "EAXGetBufferMode"; diff --git a/modules/openal-soft/al/eax_globals.h b/modules/openal-soft/al/eax_globals.h new file mode 100644 index 0000000..1b4d63b --- /dev/null +++ b/modules/openal-soft/al/eax_globals.h @@ -0,0 +1,22 @@ +#ifndef EAX_GLOBALS_INCLUDED +#define EAX_GLOBALS_INCLUDED + + +extern bool eax_g_is_enabled; + + +extern const char eax1_ext_name[]; +extern const char eax2_ext_name[]; +extern const char eax3_ext_name[]; +extern const char eax4_ext_name[]; +extern const char eax5_ext_name[]; + +extern const char eax_x_ram_ext_name[]; + +extern const char eax_eax_set_func_name[]; +extern const char eax_eax_get_func_name[]; + +extern const char eax_eax_set_buffer_mode_func_name[]; +extern const char eax_eax_get_buffer_mode_func_name[]; + +#endif // !EAX_GLOBALS_INCLUDED diff --git a/modules/openal-soft/al/eax_utils.cpp b/modules/openal-soft/al/eax_utils.cpp new file mode 100644 index 0000000..67389de --- /dev/null +++ b/modules/openal-soft/al/eax_utils.cpp @@ -0,0 +1,36 @@ +#include "config.h" + +#include "eax_utils.h" + +#include +#include + +#include "core/logging.h" + + +void eax_log_exception( + const char* message) noexcept +{ + const auto exception_ptr = std::current_exception(); + + assert(exception_ptr); + + if (message) + { + ERR("%s\n", message); + } + + try + { + std::rethrow_exception(exception_ptr); + } + catch (const std::exception& ex) + { + const auto ex_message = ex.what(); + ERR("%s\n", ex_message); + } + catch (...) + { + ERR("%s\n", "Generic exception."); + } +} diff --git a/modules/openal-soft/al/eax_utils.h b/modules/openal-soft/al/eax_utils.h new file mode 100644 index 0000000..d3d4a19 --- /dev/null +++ b/modules/openal-soft/al/eax_utils.h @@ -0,0 +1,132 @@ +#ifndef EAX_UTILS_INCLUDED +#define EAX_UTILS_INCLUDED + +#include +#include +#include +#include + + +struct EaxAlLowPassParam +{ + float gain; + float gain_hf; +}; // EaxAlLowPassParam + + +void eax_log_exception( + const char* message = nullptr) noexcept; + + +template< + typename TException, + typename TValue +> +void eax_validate_range( + const char* value_name, + const TValue& value, + const TValue& min_value, + const TValue& max_value) +{ + if (value >= min_value && value <= max_value) + { + 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) + ")."; + + throw TException{message.c_str()}; +} + + +namespace detail +{ + + +template< + typename T +> +struct EaxIsBitFieldStruct +{ +private: + using yes = std::true_type; + using no = std::false_type; + + template< + typename U + > + static auto test(int) -> decltype(std::declval(), yes{}); + + template< + typename + > + static no test(...); + + +public: + static constexpr auto value = std::is_same(0)), yes>::value; +}; // EaxIsBitFieldStruct + + +template< + typename T, + typename TValue +> +inline bool eax_bit_fields_are_equal( + const T& lhs, + const T& rhs) noexcept +{ + 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/modules/openal-soft/al/eax_x_ram.cpp b/modules/openal-soft/al/eax_x_ram.cpp new file mode 100644 index 0000000..ac3e7eb --- /dev/null +++ b/modules/openal-soft/al/eax_x_ram.cpp @@ -0,0 +1,3 @@ +#include "config.h" + +#include "eax_x_ram.h" diff --git a/modules/openal-soft/al/eax_x_ram.h b/modules/openal-soft/al/eax_x_ram.h new file mode 100644 index 0000000..438b991 --- /dev/null +++ b/modules/openal-soft/al/eax_x_ram.h @@ -0,0 +1,38 @@ +#ifndef EAX_X_RAM_INCLUDED +#define EAX_X_RAM_INCLUDED + + +#include "AL/al.h" + + +constexpr auto eax_x_ram_min_size = ALsizei{}; +constexpr auto eax_x_ram_max_size = ALsizei{64 * 1'024 * 1'024}; + + +constexpr auto AL_EAX_RAM_SIZE = ALenum{0x202201}; +constexpr auto AL_EAX_RAM_FREE = ALenum{0x202202}; + +constexpr auto AL_STORAGE_AUTOMATIC = ALenum{0x202203}; +constexpr auto AL_STORAGE_HARDWARE = ALenum{0x202204}; +constexpr auto AL_STORAGE_ACCESSIBLE = ALenum{0x202205}; + + +constexpr auto AL_EAX_RAM_SIZE_NAME = "AL_EAX_RAM_SIZE"; +constexpr auto AL_EAX_RAM_FREE_NAME = "AL_EAX_RAM_FREE"; + +constexpr auto AL_STORAGE_AUTOMATIC_NAME = "AL_STORAGE_AUTOMATIC"; +constexpr auto AL_STORAGE_HARDWARE_NAME = "AL_STORAGE_HARDWARE"; +constexpr auto AL_STORAGE_ACCESSIBLE_NAME = "AL_STORAGE_ACCESSIBLE"; + + +ALboolean AL_APIENTRY EAXSetBufferMode( + ALsizei n, + const ALuint* buffers, + ALint value); + +ALenum AL_APIENTRY EAXGetBufferMode( + ALuint buffer, + ALint* pReserved); + + +#endif // !EAX_X_RAM_INCLUDED diff --git a/modules/openal-soft/al/effect.cpp b/modules/openal-soft/al/effect.cpp new file mode 100644 index 0000000..5a74ca5 --- /dev/null +++ b/modules/openal-soft/al/effect.cpp @@ -0,0 +1,765 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "effect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" +#include "AL/efx-presets.h" +#include "AL/efx.h" + +#include "albit.h" +#include "alc/context.h" +#include "alc/device.h" +#include "alc/effects/base.h" +#include "alc/inprogext.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "alstring.h" +#include "core/except.h" +#include "core/logging.h" +#include "opthelpers.h" +#include "vector.h" + +#ifdef ALSOFT_EAX +#include + +#include "eax_exception.h" +#endif // ALSOFT_EAX + +const EffectList gEffectList[16]{ + { "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB }, + { "reverb", REVERB_EFFECT, AL_EFFECT_REVERB }, + { "autowah", AUTOWAH_EFFECT, AL_EFFECT_AUTOWAH }, + { "chorus", CHORUS_EFFECT, AL_EFFECT_CHORUS }, + { "compressor", COMPRESSOR_EFFECT, AL_EFFECT_COMPRESSOR }, + { "distortion", DISTORTION_EFFECT, AL_EFFECT_DISTORTION }, + { "echo", ECHO_EFFECT, AL_EFFECT_ECHO }, + { "equalizer", EQUALIZER_EFFECT, AL_EFFECT_EQUALIZER }, + { "flanger", FLANGER_EFFECT, AL_EFFECT_FLANGER }, + { "fshifter", FSHIFTER_EFFECT, AL_EFFECT_FREQUENCY_SHIFTER }, + { "modulator", MODULATOR_EFFECT, AL_EFFECT_RING_MODULATOR }, + { "pshifter", PSHIFTER_EFFECT, AL_EFFECT_PITCH_SHIFTER }, + { "vmorpher", VMORPHER_EFFECT, AL_EFFECT_VOCAL_MORPHER }, + { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT }, + { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_DIALOGUE }, + { "convolution", CONVOLUTION_EFFECT, AL_EFFECT_CONVOLUTION_REVERB_SOFT }, +}; + +bool DisabledEffects[MAX_EFFECTS]; + + +effect_exception::effect_exception(ALenum code, const char *msg, ...) : mErrorCode{code} +{ + std::va_list args; + va_start(args, msg); + setMessage(msg, args); + va_end(args); +} + +namespace { + +struct EffectPropsItem { + ALenum Type; + const EffectProps &DefaultProps; + const EffectVtable &Vtable; +}; +constexpr EffectPropsItem EffectPropsList[] = { + { AL_EFFECT_NULL, NullEffectProps, NullEffectVtable }, + { AL_EFFECT_EAXREVERB, ReverbEffectProps, ReverbEffectVtable }, + { AL_EFFECT_REVERB, StdReverbEffectProps, StdReverbEffectVtable }, + { AL_EFFECT_AUTOWAH, AutowahEffectProps, AutowahEffectVtable }, + { AL_EFFECT_CHORUS, ChorusEffectProps, ChorusEffectVtable }, + { AL_EFFECT_COMPRESSOR, CompressorEffectProps, CompressorEffectVtable }, + { AL_EFFECT_DISTORTION, DistortionEffectProps, DistortionEffectVtable }, + { AL_EFFECT_ECHO, EchoEffectProps, EchoEffectVtable }, + { AL_EFFECT_EQUALIZER, EqualizerEffectProps, EqualizerEffectVtable }, + { AL_EFFECT_FLANGER, FlangerEffectProps, FlangerEffectVtable }, + { AL_EFFECT_FREQUENCY_SHIFTER, FshifterEffectProps, FshifterEffectVtable }, + { AL_EFFECT_RING_MODULATOR, ModulatorEffectProps, ModulatorEffectVtable }, + { AL_EFFECT_PITCH_SHIFTER, PshifterEffectProps, PshifterEffectVtable }, + { AL_EFFECT_VOCAL_MORPHER, VmorpherEffectProps, VmorpherEffectVtable }, + { AL_EFFECT_DEDICATED_DIALOGUE, DedicatedEffectProps, DedicatedEffectVtable }, + { AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, DedicatedEffectProps, DedicatedEffectVtable }, + { AL_EFFECT_CONVOLUTION_REVERB_SOFT, ConvolutionEffectProps, ConvolutionEffectVtable }, +}; + + +void ALeffect_setParami(ALeffect *effect, ALenum param, int value) +{ effect->vtab->setParami(&effect->Props, param, value); } +void ALeffect_setParamiv(ALeffect *effect, ALenum param, const int *values) +{ effect->vtab->setParamiv(&effect->Props, param, values); } +void ALeffect_setParamf(ALeffect *effect, ALenum param, float value) +{ effect->vtab->setParamf(&effect->Props, param, value); } +void ALeffect_setParamfv(ALeffect *effect, ALenum param, const float *values) +{ effect->vtab->setParamfv(&effect->Props, param, values); } + +void ALeffect_getParami(const ALeffect *effect, ALenum param, int *value) +{ effect->vtab->getParami(&effect->Props, param, value); } +void ALeffect_getParamiv(const ALeffect *effect, ALenum param, int *values) +{ effect->vtab->getParamiv(&effect->Props, param, values); } +void ALeffect_getParamf(const ALeffect *effect, ALenum param, float *value) +{ effect->vtab->getParamf(&effect->Props, param, value); } +void ALeffect_getParamfv(const ALeffect *effect, ALenum param, float *values) +{ effect->vtab->getParamfv(&effect->Props, param, values); } + + +const EffectPropsItem *getEffectPropsItemByType(ALenum type) +{ + auto iter = std::find_if(std::begin(EffectPropsList), std::end(EffectPropsList), + [type](const EffectPropsItem &item) noexcept -> bool + { return item.Type == type; }); + return (iter != std::end(EffectPropsList)) ? std::addressof(*iter) : nullptr; +} + +void InitEffectParams(ALeffect *effect, ALenum type) +{ + const EffectPropsItem *item{getEffectPropsItemByType(type)}; + if(item) + { + effect->Props = item->DefaultProps; + effect->vtab = &item->Vtable; + } + else + { + effect->Props = EffectProps{}; + effect->vtab = &NullEffectVtable; + } + effect->type = type; +} + +bool EnsureEffects(ALCdevice *device, size_t needed) +{ + size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), size_t{0}, + [](size_t cur, const EffectSubList &sublist) noexcept -> size_t + { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; + + while(needed > count) + { + if UNLIKELY(device->EffectList.size() >= 1<<25) + return false; + + device->EffectList.emplace_back(); + auto sublist = device->EffectList.end() - 1; + sublist->FreeMask = ~0_u64; + sublist->Effects = static_cast(al_calloc(alignof(ALeffect), sizeof(ALeffect)*64)); + if UNLIKELY(!sublist->Effects) + { + device->EffectList.pop_back(); + return false; + } + count += 64; + } + return true; +} + +ALeffect *AllocEffect(ALCdevice *device) +{ + auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(), + [](const EffectSubList &entry) noexcept -> bool + { return entry.FreeMask != 0; }); + auto lidx = static_cast(std::distance(device->EffectList.begin(), sublist)); + auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); + ASSUME(slidx < 64); + + ALeffect *effect{al::construct_at(sublist->Effects + slidx)}; + InitEffectParams(effect, AL_EFFECT_NULL); + + /* Add 1 to avoid effect ID 0. */ + effect->id = ((lidx<<6) | slidx) + 1; + + sublist->FreeMask &= ~(1_u64 << slidx); + + return effect; +} + +void FreeEffect(ALCdevice *device, ALeffect *effect) +{ + const ALuint id{effect->id - 1}; + const size_t lidx{id >> 6}; + const ALuint slidx{id & 0x3f}; + + al::destroy_at(effect); + + device->EffectList[lidx].FreeMask |= 1_u64 << slidx; +} + +inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) +{ + const size_t lidx{(id-1) >> 6}; + const ALuint slidx{(id-1) & 0x3f}; + + if UNLIKELY(lidx >= device->EffectList.size()) + return nullptr; + EffectSubList &sublist = device->EffectList[lidx]; + if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + return nullptr; + return sublist.Effects + slidx; +} + +} // namespace + +AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Generating %d effects", n); + if UNLIKELY(n <= 0) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->EffectLock}; + if(!EnsureEffects(device, static_cast(n))) + { + context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, (n==1)?"":"s"); + return; + } + + if LIKELY(n == 1) + { + /* Special handling for the easy and normal case. */ + ALeffect *effect{AllocEffect(device)}; + effects[0] = effect->id; + } + else + { + /* Store the allocated buffer IDs in a separate local list, to avoid + * modifying the user storage in case of failure. + */ + al::vector ids; + ids.reserve(static_cast(n)); + do { + ALeffect *effect{AllocEffect(device)}; + ids.emplace_back(effect->id); + } while(--n); + std::copy(ids.cbegin(), ids.cend(), effects); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Deleting %d effects", n); + if UNLIKELY(n <= 0) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->EffectLock}; + + /* First try to find any effects that are invalid. */ + auto validate_effect = [device](const ALuint eid) -> bool + { return !eid || LookupEffect(device, eid) != nullptr; }; + + const ALuint *effects_end = effects + n; + auto inveffect = std::find_if_not(effects, effects_end, validate_effect); + if UNLIKELY(inveffect != effects_end) + { + context->setError(AL_INVALID_NAME, "Invalid effect ID %u", *inveffect); + return; + } + + /* All good. Delete non-0 effect IDs. */ + auto delete_effect = [device](ALuint eid) -> void + { + ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr}; + if(effect) FreeEffect(device, effect); + }; + std::for_each(effects, effects_end, delete_effect); +} +END_API_FUNC + +AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if LIKELY(context) + { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->EffectLock}; + if(!effect || LookupEffect(device, effect)) + return AL_TRUE; + } + return AL_FALSE; +} +END_API_FUNC + +AL_API void AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->EffectLock}; + + ALeffect *aleffect{LookupEffect(device, effect)}; + if UNLIKELY(!aleffect) + context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); + else if(param == AL_EFFECT_TYPE) + { + bool isOk{value == AL_EFFECT_NULL}; + if(!isOk) + { + for(const EffectList &effectitem : gEffectList) + { + if(value == effectitem.val && !DisabledEffects[effectitem.type]) + { + isOk = true; + break; + } + } + } + + if(isOk) + InitEffectParams(aleffect, value); + else + context->setError(AL_INVALID_VALUE, "Effect type 0x%04x not supported", value); + } + else try + { + /* Call the appropriate handler */ + ALeffect_setParami(aleffect, param, value); + } + catch(effect_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *values) +START_API_FUNC +{ + switch(param) + { + case AL_EFFECT_TYPE: + alEffecti(effect, param, values[0]); + return; + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->EffectLock}; + + ALeffect *aleffect{LookupEffect(device, effect)}; + if UNLIKELY(!aleffect) + context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); + else try + { + /* Call the appropriate handler */ + ALeffect_setParamiv(aleffect, param, values); + } + catch(effect_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->EffectLock}; + + ALeffect *aleffect{LookupEffect(device, effect)}; + if UNLIKELY(!aleffect) + context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); + else try + { + /* Call the appropriate handler */ + ALeffect_setParamf(aleffect, param, value); + } + catch(effect_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *values) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->EffectLock}; + + ALeffect *aleffect{LookupEffect(device, effect)}; + if UNLIKELY(!aleffect) + context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); + else try + { + /* Call the appropriate handler */ + ALeffect_setParamfv(aleffect, param, values); + } + catch(effect_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->EffectLock}; + + const ALeffect *aleffect{LookupEffect(device, effect)}; + if UNLIKELY(!aleffect) + context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); + else if(param == AL_EFFECT_TYPE) + *value = aleffect->type; + else try + { + /* Call the appropriate handler */ + ALeffect_getParami(aleffect, param, value); + } + catch(effect_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *values) +START_API_FUNC +{ + switch(param) + { + case AL_EFFECT_TYPE: + alGetEffecti(effect, param, values); + return; + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->EffectLock}; + + const ALeffect *aleffect{LookupEffect(device, effect)}; + if UNLIKELY(!aleffect) + context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); + else try + { + /* Call the appropriate handler */ + ALeffect_getParamiv(aleffect, param, values); + } + catch(effect_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->EffectLock}; + + const ALeffect *aleffect{LookupEffect(device, effect)}; + if UNLIKELY(!aleffect) + context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); + else try + { + /* Call the appropriate handler */ + ALeffect_getParamf(aleffect, param, value); + } + catch(effect_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *values) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->EffectLock}; + + const ALeffect *aleffect{LookupEffect(device, effect)}; + if UNLIKELY(!aleffect) + context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); + else try + { + /* Call the appropriate handler */ + ALeffect_getParamfv(aleffect, param, values); + } + catch(effect_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } +} +END_API_FUNC + + +void InitEffect(ALeffect *effect) +{ + InitEffectParams(effect, AL_EFFECT_NULL); +} + +EffectSubList::~EffectSubList() +{ + uint64_t usemask{~FreeMask}; + while(usemask) + { + const int idx{al::countr_zero(usemask)}; + al::destroy_at(Effects+idx); + usemask &= ~(1_u64 << idx); + } + FreeMask = ~usemask; + al_free(Effects); + Effects = nullptr; +} + + +#define DECL(x) { #x, EFX_REVERB_PRESET_##x } +static const struct { + const char name[32]; + EFXEAXREVERBPROPERTIES props; +} reverblist[] = { + DECL(GENERIC), + DECL(PADDEDCELL), + DECL(ROOM), + DECL(BATHROOM), + DECL(LIVINGROOM), + DECL(STONEROOM), + DECL(AUDITORIUM), + DECL(CONCERTHALL), + DECL(CAVE), + DECL(ARENA), + DECL(HANGAR), + DECL(CARPETEDHALLWAY), + DECL(HALLWAY), + DECL(STONECORRIDOR), + DECL(ALLEY), + DECL(FOREST), + DECL(CITY), + DECL(MOUNTAINS), + DECL(QUARRY), + DECL(PLAIN), + DECL(PARKINGLOT), + DECL(SEWERPIPE), + DECL(UNDERWATER), + DECL(DRUGGED), + DECL(DIZZY), + DECL(PSYCHOTIC), + + DECL(CASTLE_SMALLROOM), + DECL(CASTLE_SHORTPASSAGE), + DECL(CASTLE_MEDIUMROOM), + DECL(CASTLE_LARGEROOM), + DECL(CASTLE_LONGPASSAGE), + DECL(CASTLE_HALL), + DECL(CASTLE_CUPBOARD), + DECL(CASTLE_COURTYARD), + DECL(CASTLE_ALCOVE), + + DECL(FACTORY_SMALLROOM), + DECL(FACTORY_SHORTPASSAGE), + DECL(FACTORY_MEDIUMROOM), + DECL(FACTORY_LARGEROOM), + DECL(FACTORY_LONGPASSAGE), + DECL(FACTORY_HALL), + DECL(FACTORY_CUPBOARD), + DECL(FACTORY_COURTYARD), + DECL(FACTORY_ALCOVE), + + DECL(ICEPALACE_SMALLROOM), + DECL(ICEPALACE_SHORTPASSAGE), + DECL(ICEPALACE_MEDIUMROOM), + DECL(ICEPALACE_LARGEROOM), + DECL(ICEPALACE_LONGPASSAGE), + DECL(ICEPALACE_HALL), + DECL(ICEPALACE_CUPBOARD), + DECL(ICEPALACE_COURTYARD), + DECL(ICEPALACE_ALCOVE), + + DECL(SPACESTATION_SMALLROOM), + DECL(SPACESTATION_SHORTPASSAGE), + DECL(SPACESTATION_MEDIUMROOM), + DECL(SPACESTATION_LARGEROOM), + DECL(SPACESTATION_LONGPASSAGE), + DECL(SPACESTATION_HALL), + DECL(SPACESTATION_CUPBOARD), + DECL(SPACESTATION_ALCOVE), + + DECL(WOODEN_SMALLROOM), + DECL(WOODEN_SHORTPASSAGE), + DECL(WOODEN_MEDIUMROOM), + DECL(WOODEN_LARGEROOM), + DECL(WOODEN_LONGPASSAGE), + DECL(WOODEN_HALL), + DECL(WOODEN_CUPBOARD), + DECL(WOODEN_COURTYARD), + DECL(WOODEN_ALCOVE), + + DECL(SPORT_EMPTYSTADIUM), + DECL(SPORT_SQUASHCOURT), + DECL(SPORT_SMALLSWIMMINGPOOL), + DECL(SPORT_LARGESWIMMINGPOOL), + DECL(SPORT_GYMNASIUM), + DECL(SPORT_FULLSTADIUM), + DECL(SPORT_STADIUMTANNOY), + + DECL(PREFAB_WORKSHOP), + DECL(PREFAB_SCHOOLROOM), + DECL(PREFAB_PRACTISEROOM), + DECL(PREFAB_OUTHOUSE), + DECL(PREFAB_CARAVAN), + + DECL(DOME_TOMB), + DECL(PIPE_SMALL), + DECL(DOME_SAINTPAULS), + DECL(PIPE_LONGTHIN), + DECL(PIPE_LARGE), + DECL(PIPE_RESONANT), + + DECL(OUTDOORS_BACKYARD), + DECL(OUTDOORS_ROLLINGPLAINS), + DECL(OUTDOORS_DEEPCANYON), + DECL(OUTDOORS_CREEK), + DECL(OUTDOORS_VALLEY), + + DECL(MOOD_HEAVEN), + DECL(MOOD_HELL), + DECL(MOOD_MEMORY), + + DECL(DRIVING_COMMENTATOR), + DECL(DRIVING_PITGARAGE), + DECL(DRIVING_INCAR_RACER), + DECL(DRIVING_INCAR_SPORTS), + DECL(DRIVING_INCAR_LUXURY), + DECL(DRIVING_FULLGRANDSTAND), + DECL(DRIVING_EMPTYGRANDSTAND), + DECL(DRIVING_TUNNEL), + + DECL(CITY_STREETS), + DECL(CITY_SUBWAY), + DECL(CITY_MUSEUM), + DECL(CITY_LIBRARY), + DECL(CITY_UNDERPASS), + DECL(CITY_ABANDONED), + + DECL(DUSTYROOM), + DECL(CHAPEL), + DECL(SMALLWATERROOM), +}; +#undef DECL + +void LoadReverbPreset(const char *name, ALeffect *effect) +{ + if(al::strcasecmp(name, "NONE") == 0) + { + InitEffectParams(effect, AL_EFFECT_NULL); + TRACE("Loading reverb '%s'\n", "NONE"); + return; + } + + if(!DisabledEffects[EAXREVERB_EFFECT]) + InitEffectParams(effect, AL_EFFECT_EAXREVERB); + else if(!DisabledEffects[REVERB_EFFECT]) + InitEffectParams(effect, AL_EFFECT_REVERB); + else + InitEffectParams(effect, AL_EFFECT_NULL); + for(const auto &reverbitem : reverblist) + { + const EFXEAXREVERBPROPERTIES *props; + + if(al::strcasecmp(name, reverbitem.name) != 0) + continue; + + TRACE("Loading reverb '%s'\n", reverbitem.name); + props = &reverbitem.props; + effect->Props.Reverb.Density = props->flDensity; + effect->Props.Reverb.Diffusion = props->flDiffusion; + effect->Props.Reverb.Gain = props->flGain; + effect->Props.Reverb.GainHF = props->flGainHF; + effect->Props.Reverb.GainLF = props->flGainLF; + effect->Props.Reverb.DecayTime = props->flDecayTime; + effect->Props.Reverb.DecayHFRatio = props->flDecayHFRatio; + effect->Props.Reverb.DecayLFRatio = props->flDecayLFRatio; + effect->Props.Reverb.ReflectionsGain = props->flReflectionsGain; + effect->Props.Reverb.ReflectionsDelay = props->flReflectionsDelay; + effect->Props.Reverb.ReflectionsPan[0] = props->flReflectionsPan[0]; + effect->Props.Reverb.ReflectionsPan[1] = props->flReflectionsPan[1]; + effect->Props.Reverb.ReflectionsPan[2] = props->flReflectionsPan[2]; + effect->Props.Reverb.LateReverbGain = props->flLateReverbGain; + effect->Props.Reverb.LateReverbDelay = props->flLateReverbDelay; + effect->Props.Reverb.LateReverbPan[0] = props->flLateReverbPan[0]; + effect->Props.Reverb.LateReverbPan[1] = props->flLateReverbPan[1]; + effect->Props.Reverb.LateReverbPan[2] = props->flLateReverbPan[2]; + effect->Props.Reverb.EchoTime = props->flEchoTime; + effect->Props.Reverb.EchoDepth = props->flEchoDepth; + effect->Props.Reverb.ModulationTime = props->flModulationTime; + effect->Props.Reverb.ModulationDepth = props->flModulationDepth; + effect->Props.Reverb.AirAbsorptionGainHF = props->flAirAbsorptionGainHF; + effect->Props.Reverb.HFReference = props->flHFReference; + effect->Props.Reverb.LFReference = props->flLFReference; + effect->Props.Reverb.RoomRolloffFactor = props->flRoomRolloffFactor; + effect->Props.Reverb.DecayHFLimit = props->iDecayHFLimit ? AL_TRUE : AL_FALSE; + return; + } + + WARN("Reverb preset '%s' not found\n", name); +} + +bool IsValidEffectType(ALenum type) noexcept +{ + if(type == AL_EFFECT_NULL) + return true; + + for(const auto &effect_item : gEffectList) + { + if(type == effect_item.val && !DisabledEffects[effect_item.type]) + return true; + } + return false; +} diff --git a/modules/openal-soft/OpenAL32/Include/alEffect.h b/modules/openal-soft/al/effect.h similarity index 65% rename from modules/openal-soft/OpenAL32/Include/alEffect.h rename to modules/openal-soft/al/effect.h index 51d6a24..a1d4331 100644 --- a/modules/openal-soft/OpenAL32/Include/alEffect.h +++ b/modules/openal-soft/al/effect.h @@ -1,8 +1,11 @@ -#ifndef _AL_EFFECT_H_ -#define _AL_EFFECT_H_ +#ifndef AL_EFFECT_H +#define AL_EFFECT_H -#include "alMain.h" -#include "effects/base.h" +#include "AL/al.h" +#include "AL/efx.h" + +#include "al/effects/effects.h" +#include "alc/effects/base.h" enum { @@ -18,20 +21,22 @@ enum { FSHIFTER_EFFECT, MODULATOR_EFFECT, PSHIFTER_EFFECT, + VMORPHER_EFFECT, DEDICATED_EFFECT, + CONVOLUTION_EFFECT, MAX_EFFECTS }; -extern ALboolean DisabledEffects[MAX_EFFECTS]; +extern bool DisabledEffects[MAX_EFFECTS]; -extern ALfloat ReverbBoost; +extern float ReverbBoost; struct EffectList { const char name[16]; int type; ALenum val; }; -extern const EffectList gEffectList[14]; +extern const EffectList gEffectList[16]; struct ALeffect { @@ -44,15 +49,14 @@ struct ALeffect { /* Self ID */ ALuint id{0u}; -}; - -inline ALboolean IsReverbEffect(ALenum type) -{ return type == AL_EFFECT_REVERB || type == AL_EFFECT_EAXREVERB; } -EffectStateFactory *getFactoryByType(ALenum type); + DISABLE_ALLOC() +}; void InitEffect(ALeffect *effect); void LoadReverbPreset(const char *name, ALeffect *effect); +bool IsValidEffectType(ALenum type) noexcept; + #endif diff --git a/modules/openal-soft/al/effects/autowah.cpp b/modules/openal-soft/al/effects/autowah.cpp new file mode 100644 index 0000000..273ec7a --- /dev/null +++ b/modules/openal-soft/al/effects/autowah.cpp @@ -0,0 +1,548 @@ + +#include "config.h" + +#include +#include + +#include + +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + + +namespace { + +void Autowah_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_AUTOWAH_ATTACK_TIME: + if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME)) + throw effect_exception{AL_INVALID_VALUE, "Autowah attack time out of range"}; + props->Autowah.AttackTime = val; + break; + + case AL_AUTOWAH_RELEASE_TIME: + if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME)) + throw effect_exception{AL_INVALID_VALUE, "Autowah release time out of range"}; + props->Autowah.ReleaseTime = val; + break; + + case AL_AUTOWAH_RESONANCE: + if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE)) + throw effect_exception{AL_INVALID_VALUE, "Autowah resonance out of range"}; + props->Autowah.Resonance = val; + break; + + case AL_AUTOWAH_PEAK_GAIN: + if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Autowah peak gain out of range"}; + props->Autowah.PeakGain = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param}; + } +} +void Autowah_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Autowah_setParamf(props, param, vals[0]); } + +void Autowah_setParami(EffectProps*, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } +void Autowah_setParamiv(EffectProps*, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", + param}; +} + +void Autowah_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_AUTOWAH_ATTACK_TIME: + *val = props->Autowah.AttackTime; + break; + + case AL_AUTOWAH_RELEASE_TIME: + *val = props->Autowah.ReleaseTime; + break; + + case AL_AUTOWAH_RESONANCE: + *val = props->Autowah.Resonance; + break; + + case AL_AUTOWAH_PEAK_GAIN: + *val = props->Autowah.PeakGain; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param}; + } + +} +void Autowah_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Autowah_getParamf(props, param, vals); } + +void Autowah_getParami(const EffectProps*, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } +void Autowah_getParamiv(const EffectProps*, ALenum param, int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", + param}; +} + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME; + props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME; + props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE; + props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Autowah); + +const EffectProps AutowahEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxAutoWahEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxAutoWahEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxAutoWahEffectDirtyFlagsValue flAttackTime : 1; + EaxAutoWahEffectDirtyFlagsValue flReleaseTime : 1; + EaxAutoWahEffectDirtyFlagsValue lResonance : 1; + EaxAutoWahEffectDirtyFlagsValue lPeakLevel : 1; +}; // EaxAutoWahEffectDirtyFlags + + +class EaxAutoWahEffect final : + public EaxEffect +{ +public: + EaxAutoWahEffect(); + + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXAUTOWAHPROPERTIES eax_{}; + EAXAUTOWAHPROPERTIES eax_d_{}; + EaxAutoWahEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + + void set_efx_attack_time(); + + void set_efx_release_time(); + + void set_efx_resonance(); + + void set_efx_peak_gain(); + + void set_efx_defaults(); + + + void get(const EaxEaxCall& eax_call); + + + void validate_attack_time( + float flAttackTime); + + void validate_release_time( + float flReleaseTime); + + void validate_resonance( + long lResonance); + + void validate_peak_level( + long lPeakLevel); + + void validate_all( + const EAXAUTOWAHPROPERTIES& eax_all); + + + void defer_attack_time( + float flAttackTime); + + void defer_release_time( + float flReleaseTime); + + void defer_resonance( + long lResonance); + + void defer_peak_level( + long lPeakLevel); + + void defer_all( + const EAXAUTOWAHPROPERTIES& eax_all); + + + void defer_attack_time( + const EaxEaxCall& eax_call); + + void defer_release_time( + const EaxEaxCall& eax_call); + + void defer_resonance( + const EaxEaxCall& eax_call); + + void defer_peak_level( + const EaxEaxCall& eax_call); + + void defer_all( + const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxAutoWahEffect + + +class EaxAutoWahEffectException : + public EaxException +{ +public: + explicit EaxAutoWahEffectException( + const char* message) + : + EaxException{"EAX_AUTO_WAH_EFFECT", message} + { + } +}; // EaxAutoWahEffectException + + +EaxAutoWahEffect::EaxAutoWahEffect() + : EaxEffect{AL_EFFECT_AUTOWAH} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxAutoWahEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxAutoWahEffect::set_eax_defaults() +{ + eax_.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME; + eax_.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME; + eax_.lResonance = EAXAUTOWAH_DEFAULTRESONANCE; + eax_.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL; + + eax_d_ = eax_; +} + +void EaxAutoWahEffect::set_efx_attack_time() +{ + const auto attack_time = clamp( + eax_.flAttackTime, + AL_AUTOWAH_MIN_ATTACK_TIME, + AL_AUTOWAH_MAX_ATTACK_TIME); + + al_effect_props_.Autowah.AttackTime = attack_time; +} + +void EaxAutoWahEffect::set_efx_release_time() +{ + const auto release_time = clamp( + eax_.flReleaseTime, + AL_AUTOWAH_MIN_RELEASE_TIME, + AL_AUTOWAH_MAX_RELEASE_TIME); + + al_effect_props_.Autowah.ReleaseTime = release_time; +} + +void EaxAutoWahEffect::set_efx_resonance() +{ + const auto resonance = clamp( + level_mb_to_gain(static_cast(eax_.lResonance)), + AL_AUTOWAH_MIN_RESONANCE, + AL_AUTOWAH_MAX_RESONANCE); + + al_effect_props_.Autowah.Resonance = resonance; +} + +void EaxAutoWahEffect::set_efx_peak_gain() +{ + const auto peak_gain = clamp( + level_mb_to_gain(static_cast(eax_.lPeakLevel)), + AL_AUTOWAH_MIN_PEAK_GAIN, + AL_AUTOWAH_MAX_PEAK_GAIN); + + al_effect_props_.Autowah.PeakGain = peak_gain; +} + +void EaxAutoWahEffect::set_efx_defaults() +{ + set_efx_attack_time(); + set_efx_release_time(); + set_efx_resonance(); + set_efx_peak_gain(); +} + +void EaxAutoWahEffect::get(const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXAUTOWAH_NONE: + break; + + case EAXAUTOWAH_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXAUTOWAH_ATTACKTIME: + eax_call.set_value(eax_.flAttackTime); + break; + + case EAXAUTOWAH_RELEASETIME: + eax_call.set_value(eax_.flReleaseTime); + break; + + case EAXAUTOWAH_RESONANCE: + eax_call.set_value(eax_.lResonance); + break; + + case EAXAUTOWAH_PEAKLEVEL: + eax_call.set_value(eax_.lPeakLevel); + break; + + default: + throw EaxAutoWahEffectException{"Unsupported property id."}; + } +} + +void EaxAutoWahEffect::validate_attack_time( + float flAttackTime) +{ + eax_validate_range( + "Attack Time", + flAttackTime, + EAXAUTOWAH_MINATTACKTIME, + EAXAUTOWAH_MAXATTACKTIME); +} + +void EaxAutoWahEffect::validate_release_time( + float flReleaseTime) +{ + eax_validate_range( + "Release Time", + flReleaseTime, + EAXAUTOWAH_MINRELEASETIME, + EAXAUTOWAH_MAXRELEASETIME); +} + +void EaxAutoWahEffect::validate_resonance( + long lResonance) +{ + eax_validate_range( + "Resonance", + lResonance, + EAXAUTOWAH_MINRESONANCE, + EAXAUTOWAH_MAXRESONANCE); +} + +void EaxAutoWahEffect::validate_peak_level( + long lPeakLevel) +{ + eax_validate_range( + "Peak Level", + lPeakLevel, + EAXAUTOWAH_MINPEAKLEVEL, + EAXAUTOWAH_MAXPEAKLEVEL); +} + +void EaxAutoWahEffect::validate_all( + const EAXAUTOWAHPROPERTIES& eax_all) +{ + validate_attack_time(eax_all.flAttackTime); + validate_release_time(eax_all.flReleaseTime); + validate_resonance(eax_all.lResonance); + validate_peak_level(eax_all.lPeakLevel); +} + +void EaxAutoWahEffect::defer_attack_time( + float flAttackTime) +{ + eax_d_.flAttackTime = flAttackTime; + eax_dirty_flags_.flAttackTime = (eax_.flAttackTime != eax_d_.flAttackTime); +} + +void EaxAutoWahEffect::defer_release_time( + float flReleaseTime) +{ + eax_d_.flReleaseTime = flReleaseTime; + eax_dirty_flags_.flReleaseTime = (eax_.flReleaseTime != eax_d_.flReleaseTime); +} + +void EaxAutoWahEffect::defer_resonance( + long lResonance) +{ + eax_d_.lResonance = lResonance; + eax_dirty_flags_.lResonance = (eax_.lResonance != eax_d_.lResonance); +} + +void EaxAutoWahEffect::defer_peak_level( + long lPeakLevel) +{ + eax_d_.lPeakLevel = lPeakLevel; + eax_dirty_flags_.lPeakLevel = (eax_.lPeakLevel != eax_d_.lPeakLevel); +} + +void EaxAutoWahEffect::defer_all( + const EAXAUTOWAHPROPERTIES& eax_all) +{ + validate_all(eax_all); + + defer_attack_time(eax_all.flAttackTime); + defer_release_time(eax_all.flReleaseTime); + defer_resonance(eax_all.lResonance); + defer_peak_level(eax_all.lPeakLevel); +} + +void EaxAutoWahEffect::defer_attack_time( + const EaxEaxCall& eax_call) +{ + const auto& attack_time = + eax_call.get_value(); + + validate_attack_time(attack_time); + defer_attack_time(attack_time); +} + +void EaxAutoWahEffect::defer_release_time( + const EaxEaxCall& eax_call) +{ + const auto& release_time = + eax_call.get_value(); + + validate_release_time(release_time); + defer_release_time(release_time); +} + +void EaxAutoWahEffect::defer_resonance( + const EaxEaxCall& eax_call) +{ + const auto& resonance = + eax_call.get_value(); + + validate_resonance(resonance); + defer_resonance(resonance); +} + +void EaxAutoWahEffect::defer_peak_level( + const EaxEaxCall& eax_call) +{ + const auto& peak_level = + eax_call.get_value(); + + validate_peak_level(peak_level); + defer_peak_level(peak_level); +} + +void EaxAutoWahEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxAutoWahEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxAutoWahEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flAttackTime) + { + set_efx_attack_time(); + } + + if (eax_dirty_flags_.flReleaseTime) + { + set_efx_release_time(); + } + + if (eax_dirty_flags_.lResonance) + { + set_efx_resonance(); + } + + if (eax_dirty_flags_.lPeakLevel) + { + set_efx_peak_gain(); + } + + eax_dirty_flags_ = EaxAutoWahEffectDirtyFlags{}; + + return true; +} + +void EaxAutoWahEffect::set(const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXAUTOWAH_NONE: + break; + + case EAXAUTOWAH_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXAUTOWAH_ATTACKTIME: + defer_attack_time(eax_call); + break; + + case EAXAUTOWAH_RELEASETIME: + defer_release_time(eax_call); + break; + + case EAXAUTOWAH_RESONANCE: + defer_resonance(eax_call); + break; + + case EAXAUTOWAH_PEAKLEVEL: + defer_peak_level(eax_call); + break; + + default: + throw EaxAutoWahEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_auto_wah_effect() +{ + return std::make_unique<::EaxAutoWahEffect>(); +} + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/effects/chorus.cpp b/modules/openal-soft/al/effects/chorus.cpp new file mode 100644 index 0000000..5631809 --- /dev/null +++ b/modules/openal-soft/al/effects/chorus.cpp @@ -0,0 +1,1357 @@ + +#include "config.h" + +#include + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "aloptional.h" +#include "core/logging.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include + +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + + +namespace { + +static_assert(ChorusMaxDelay >= AL_CHORUS_MAX_DELAY, "Chorus max delay too small"); +static_assert(FlangerMaxDelay >= AL_FLANGER_MAX_DELAY, "Flanger max delay too small"); + +static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch"); +static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch"); + +inline al::optional WaveformFromEnum(ALenum type) +{ + switch(type) + { + case AL_CHORUS_WAVEFORM_SINUSOID: return al::make_optional(ChorusWaveform::Sinusoid); + case AL_CHORUS_WAVEFORM_TRIANGLE: return al::make_optional(ChorusWaveform::Triangle); + } + return al::nullopt; +} +inline ALenum EnumFromWaveform(ChorusWaveform type) +{ + switch(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))}; +} + +void Chorus_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_CHORUS_WAVEFORM: + if(auto formopt = WaveformFromEnum(val)) + props->Chorus.Waveform = *formopt; + else + throw effect_exception{AL_INVALID_VALUE, "Invalid chorus waveform: 0x%04x", val}; + break; + + case AL_CHORUS_PHASE: + if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE)) + throw effect_exception{AL_INVALID_VALUE, "Chorus phase out of range: %d", val}; + props->Chorus.Phase = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; + } +} +void Chorus_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ Chorus_setParami(props, param, vals[0]); } +void Chorus_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_CHORUS_RATE: + if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE)) + throw effect_exception{AL_INVALID_VALUE, "Chorus rate out of range: %f", val}; + props->Chorus.Rate = val; + break; + + case AL_CHORUS_DEPTH: + if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH)) + throw effect_exception{AL_INVALID_VALUE, "Chorus depth out of range: %f", val}; + props->Chorus.Depth = val; + break; + + case AL_CHORUS_FEEDBACK: + if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK)) + throw effect_exception{AL_INVALID_VALUE, "Chorus feedback out of range: %f", val}; + props->Chorus.Feedback = val; + break; + + case AL_CHORUS_DELAY: + if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "Chorus delay out of range: %f", val}; + props->Chorus.Delay = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; + } +} +void Chorus_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Chorus_setParamf(props, param, vals[0]); } + +void Chorus_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_CHORUS_WAVEFORM: + *val = EnumFromWaveform(props->Chorus.Waveform); + break; + + case AL_CHORUS_PHASE: + *val = props->Chorus.Phase; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; + } +} +void Chorus_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ Chorus_getParami(props, param, vals); } +void Chorus_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_CHORUS_RATE: + *val = props->Chorus.Rate; + break; + + case AL_CHORUS_DEPTH: + *val = props->Chorus.Depth; + break; + + case AL_CHORUS_FEEDBACK: + *val = props->Chorus.Feedback; + break; + + case AL_CHORUS_DELAY: + *val = props->Chorus.Delay; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; + } +} +void Chorus_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Chorus_getParamf(props, param, vals); } + +const EffectProps genDefaultChorusProps() noexcept +{ + EffectProps props{}; + props.Chorus.Waveform = *WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM); + props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE; + props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE; + props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH; + props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK; + props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY; + return props; +} + + +void Flanger_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_FLANGER_WAVEFORM: + if(auto formopt = WaveformFromEnum(val)) + props->Chorus.Waveform = *formopt; + else + throw effect_exception{AL_INVALID_VALUE, "Invalid flanger waveform: 0x%04x", val}; + break; + + case AL_FLANGER_PHASE: + if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE)) + throw effect_exception{AL_INVALID_VALUE, "Flanger phase out of range: %d", val}; + props->Chorus.Phase = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; + } +} +void Flanger_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ Flanger_setParami(props, param, vals[0]); } +void Flanger_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_FLANGER_RATE: + if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE)) + throw effect_exception{AL_INVALID_VALUE, "Flanger rate out of range: %f", val}; + props->Chorus.Rate = val; + break; + + case AL_FLANGER_DEPTH: + if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH)) + throw effect_exception{AL_INVALID_VALUE, "Flanger depth out of range: %f", val}; + props->Chorus.Depth = val; + break; + + case AL_FLANGER_FEEDBACK: + if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK)) + throw effect_exception{AL_INVALID_VALUE, "Flanger feedback out of range: %f", val}; + props->Chorus.Feedback = val; + break; + + case AL_FLANGER_DELAY: + if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "Flanger delay out of range: %f", val}; + props->Chorus.Delay = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; + } +} +void Flanger_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Flanger_setParamf(props, param, vals[0]); } + +void Flanger_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_FLANGER_WAVEFORM: + *val = EnumFromWaveform(props->Chorus.Waveform); + break; + + case AL_FLANGER_PHASE: + *val = props->Chorus.Phase; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; + } +} +void Flanger_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ Flanger_getParami(props, param, vals); } +void Flanger_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_FLANGER_RATE: + *val = props->Chorus.Rate; + break; + + case AL_FLANGER_DEPTH: + *val = props->Chorus.Depth; + break; + + case AL_FLANGER_FEEDBACK: + *val = props->Chorus.Feedback; + break; + + case AL_FLANGER_DELAY: + *val = props->Chorus.Delay; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; + } +} +void Flanger_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Flanger_getParamf(props, param, vals); } + +EffectProps genDefaultFlangerProps() noexcept +{ + EffectProps props{}; + props.Chorus.Waveform = *WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM); + props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE; + props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE; + props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH; + props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK; + props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Chorus); + +const EffectProps ChorusEffectProps{genDefaultChorusProps()}; + +DEFINE_ALEFFECT_VTABLE(Flanger); + +const EffectProps FlangerEffectProps{genDefaultFlangerProps()}; + + +#ifdef ALSOFT_EAX +namespace { + +void eax_set_efx_waveform( + ALenum waveform, + EffectProps& al_effect_props) +{ + const auto efx_waveform = WaveformFromEnum(waveform); + assert(efx_waveform.has_value()); + al_effect_props.Chorus.Waveform = *efx_waveform; +} + +void eax_set_efx_phase( + ALint phase, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Phase = phase; +} + +void eax_set_efx_rate( + ALfloat rate, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Rate = rate; +} + +void eax_set_efx_depth( + ALfloat depth, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Depth = depth; +} + +void eax_set_efx_feedback( + ALfloat feedback, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Feedback = feedback; +} + +void eax_set_efx_delay( + ALfloat delay, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Delay = delay; +} + + +using EaxChorusEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxChorusEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxChorusEffectDirtyFlagsValue ulWaveform : 1; + EaxChorusEffectDirtyFlagsValue lPhase : 1; + EaxChorusEffectDirtyFlagsValue flRate : 1; + EaxChorusEffectDirtyFlagsValue flDepth : 1; + EaxChorusEffectDirtyFlagsValue flFeedback : 1; + EaxChorusEffectDirtyFlagsValue flDelay : 1; +}; // EaxChorusEffectDirtyFlags + + +class EaxChorusEffect final : + public EaxEffect +{ +public: + EaxChorusEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXCHORUSPROPERTIES eax_{}; + EAXCHORUSPROPERTIES eax_d_{}; + EaxChorusEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults() noexcept; + + void set_efx_waveform(); + void set_efx_phase(); + void set_efx_rate(); + void set_efx_depth(); + void set_efx_feedback(); + void set_efx_delay(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_waveform(unsigned long ulWaveform); + void validate_phase(long lPhase); + void validate_rate(float flRate); + void validate_depth(float flDepth); + void validate_feedback(float flFeedback); + void validate_delay(float flDelay); + void validate_all(const EAXCHORUSPROPERTIES& eax_all); + + void defer_waveform(unsigned long ulWaveform); + void defer_phase(long lPhase); + void defer_rate(float flRate); + void defer_depth(float flDepth); + void defer_feedback(float flFeedback); + void defer_delay(float flDelay); + void defer_all(const EAXCHORUSPROPERTIES& eax_all); + + void defer_waveform(const EaxEaxCall& eax_call); + void defer_phase(const EaxEaxCall& eax_call); + void defer_rate(const EaxEaxCall& eax_call); + void defer_depth(const EaxEaxCall& eax_call); + void defer_feedback(const EaxEaxCall& eax_call); + void defer_delay(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxChorusEffect + + +class EaxChorusEffectException : + public EaxException +{ +public: + explicit EaxChorusEffectException( + const char* message) + : + EaxException{"EAX_CHORUS_EFFECT", message} + { + } +}; // EaxChorusEffectException + + +EaxChorusEffect::EaxChorusEffect() + : EaxEffect{AL_EFFECT_CHORUS} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxChorusEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxChorusEffect::set_eax_defaults() noexcept +{ + eax_.ulWaveform = EAXCHORUS_DEFAULTWAVEFORM; + eax_.lPhase = EAXCHORUS_DEFAULTPHASE; + eax_.flRate = EAXCHORUS_DEFAULTRATE; + eax_.flDepth = EAXCHORUS_DEFAULTDEPTH; + eax_.flFeedback = EAXCHORUS_DEFAULTFEEDBACK; + eax_.flDelay = EAXCHORUS_DEFAULTDELAY; + + eax_d_ = eax_; +} + +void EaxChorusEffect::set_efx_waveform() +{ + const auto waveform = clamp( + static_cast(eax_.ulWaveform), + AL_CHORUS_MIN_WAVEFORM, + AL_CHORUS_MAX_WAVEFORM); + + eax_set_efx_waveform(waveform, al_effect_props_); +} + +void EaxChorusEffect::set_efx_phase() +{ + const auto phase = clamp( + static_cast(eax_.lPhase), + AL_CHORUS_MIN_PHASE, + AL_CHORUS_MAX_PHASE); + + eax_set_efx_phase(phase, al_effect_props_); +} + +void EaxChorusEffect::set_efx_rate() +{ + const auto rate = clamp( + eax_.flRate, + AL_CHORUS_MIN_RATE, + AL_CHORUS_MAX_RATE); + + eax_set_efx_rate(rate, al_effect_props_); +} + +void EaxChorusEffect::set_efx_depth() +{ + const auto depth = clamp( + eax_.flDepth, + AL_CHORUS_MIN_DEPTH, + AL_CHORUS_MAX_DEPTH); + + eax_set_efx_depth(depth, al_effect_props_); +} + +void EaxChorusEffect::set_efx_feedback() +{ + const auto feedback = clamp( + eax_.flFeedback, + AL_CHORUS_MIN_FEEDBACK, + AL_CHORUS_MAX_FEEDBACK); + + eax_set_efx_feedback(feedback, al_effect_props_); +} + +void EaxChorusEffect::set_efx_delay() +{ + const auto delay = clamp( + eax_.flDelay, + AL_CHORUS_MIN_DELAY, + AL_CHORUS_MAX_DELAY); + + eax_set_efx_delay(delay, al_effect_props_); +} + +void EaxChorusEffect::set_efx_defaults() +{ + set_efx_waveform(); + set_efx_phase(); + set_efx_rate(); + set_efx_depth(); + set_efx_feedback(); + set_efx_delay(); +} + +void EaxChorusEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXCHORUS_NONE: + break; + + case EAXCHORUS_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXCHORUS_WAVEFORM: + eax_call.set_value(eax_.ulWaveform); + break; + + case EAXCHORUS_PHASE: + eax_call.set_value(eax_.lPhase); + break; + + case EAXCHORUS_RATE: + eax_call.set_value(eax_.flRate); + break; + + case EAXCHORUS_DEPTH: + eax_call.set_value(eax_.flDepth); + break; + + case EAXCHORUS_FEEDBACK: + eax_call.set_value(eax_.flFeedback); + break; + + case EAXCHORUS_DELAY: + eax_call.set_value(eax_.flDelay); + break; + + default: + throw EaxChorusEffectException{"Unsupported property id."}; + } +} + +void EaxChorusEffect::validate_waveform( + unsigned long ulWaveform) +{ + eax_validate_range( + "Waveform", + ulWaveform, + EAXCHORUS_MINWAVEFORM, + EAXCHORUS_MAXWAVEFORM); +} + +void EaxChorusEffect::validate_phase( + long lPhase) +{ + eax_validate_range( + "Phase", + lPhase, + EAXCHORUS_MINPHASE, + EAXCHORUS_MAXPHASE); +} + +void EaxChorusEffect::validate_rate( + float flRate) +{ + eax_validate_range( + "Rate", + flRate, + EAXCHORUS_MINRATE, + EAXCHORUS_MAXRATE); +} + +void EaxChorusEffect::validate_depth( + float flDepth) +{ + eax_validate_range( + "Depth", + flDepth, + EAXCHORUS_MINDEPTH, + EAXCHORUS_MAXDEPTH); +} + +void EaxChorusEffect::validate_feedback( + float flFeedback) +{ + eax_validate_range( + "Feedback", + flFeedback, + EAXCHORUS_MINFEEDBACK, + EAXCHORUS_MAXFEEDBACK); +} + +void EaxChorusEffect::validate_delay( + float flDelay) +{ + eax_validate_range( + "Delay", + flDelay, + EAXCHORUS_MINDELAY, + EAXCHORUS_MAXDELAY); +} + +void EaxChorusEffect::validate_all( + const EAXCHORUSPROPERTIES& eax_all) +{ + validate_waveform(eax_all.ulWaveform); + validate_phase(eax_all.lPhase); + validate_rate(eax_all.flRate); + validate_depth(eax_all.flDepth); + validate_feedback(eax_all.flFeedback); + validate_delay(eax_all.flDelay); +} + +void EaxChorusEffect::defer_waveform( + unsigned long ulWaveform) +{ + eax_d_.ulWaveform = ulWaveform; + eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); +} + +void EaxChorusEffect::defer_phase( + long lPhase) +{ + eax_d_.lPhase = lPhase; + eax_dirty_flags_.lPhase = (eax_.lPhase != eax_d_.lPhase); +} + +void EaxChorusEffect::defer_rate( + float flRate) +{ + eax_d_.flRate = flRate; + eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate); +} + +void EaxChorusEffect::defer_depth( + float flDepth) +{ + eax_d_.flDepth = flDepth; + eax_dirty_flags_.flDepth = (eax_.flDepth != eax_d_.flDepth); +} + +void EaxChorusEffect::defer_feedback( + float flFeedback) +{ + eax_d_.flFeedback = flFeedback; + eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback); +} + +void EaxChorusEffect::defer_delay( + float flDelay) +{ + eax_d_.flDelay = flDelay; + eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay); +} + +void EaxChorusEffect::defer_all( + const EAXCHORUSPROPERTIES& eax_all) +{ + defer_waveform(eax_all.ulWaveform); + defer_phase(eax_all.lPhase); + defer_rate(eax_all.flRate); + defer_depth(eax_all.flDepth); + defer_feedback(eax_all.flFeedback); + defer_delay(eax_all.flDelay); +} + +void EaxChorusEffect::defer_waveform( + const EaxEaxCall& eax_call) +{ + const auto& waveform = + eax_call.get_value(); + + validate_waveform(waveform); + defer_waveform(waveform); +} + +void EaxChorusEffect::defer_phase( + const EaxEaxCall& eax_call) +{ + const auto& phase = + eax_call.get_value(); + + validate_phase(phase); + defer_phase(phase); +} + +void EaxChorusEffect::defer_rate( + const EaxEaxCall& eax_call) +{ + const auto& rate = + eax_call.get_value(); + + validate_rate(rate); + defer_rate(rate); +} + +void EaxChorusEffect::defer_depth( + const EaxEaxCall& eax_call) +{ + const auto& depth = + eax_call.get_value(); + + validate_depth(depth); + defer_depth(depth); +} + +void EaxChorusEffect::defer_feedback( + const EaxEaxCall& eax_call) +{ + const auto& feedback = + eax_call.get_value(); + + validate_feedback(feedback); + defer_feedback(feedback); +} + +void EaxChorusEffect::defer_delay( + const EaxEaxCall& eax_call) +{ + const auto& delay = + eax_call.get_value(); + + validate_delay(delay); + defer_delay(delay); +} + +void EaxChorusEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxChorusEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxChorusEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulWaveform) + { + set_efx_waveform(); + } + + if (eax_dirty_flags_.lPhase) + { + set_efx_phase(); + } + + if (eax_dirty_flags_.flRate) + { + set_efx_rate(); + } + + if (eax_dirty_flags_.flDepth) + { + set_efx_depth(); + } + + if (eax_dirty_flags_.flFeedback) + { + set_efx_feedback(); + } + + if (eax_dirty_flags_.flDelay) + { + set_efx_delay(); + } + + eax_dirty_flags_ = EaxChorusEffectDirtyFlags{}; + + return true; +} + +void EaxChorusEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXCHORUS_NONE: + break; + + case EAXCHORUS_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXCHORUS_WAVEFORM: + defer_waveform(eax_call); + break; + + case EAXCHORUS_PHASE: + defer_phase(eax_call); + break; + + case EAXCHORUS_RATE: + defer_rate(eax_call); + break; + + case EAXCHORUS_DEPTH: + defer_depth(eax_call); + break; + + case EAXCHORUS_FEEDBACK: + defer_feedback(eax_call); + break; + + case EAXCHORUS_DELAY: + defer_delay(eax_call); + break; + + default: + throw EaxChorusEffectException{"Unsupported property id."}; + } +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_chorus_effect() +{ + return std::make_unique<::EaxChorusEffect>(); +} + + +namespace +{ + + +using EaxFlangerEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxFlangerEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxFlangerEffectDirtyFlagsValue ulWaveform : 1; + EaxFlangerEffectDirtyFlagsValue lPhase : 1; + EaxFlangerEffectDirtyFlagsValue flRate : 1; + EaxFlangerEffectDirtyFlagsValue flDepth : 1; + EaxFlangerEffectDirtyFlagsValue flFeedback : 1; + EaxFlangerEffectDirtyFlagsValue flDelay : 1; +}; // EaxFlangerEffectDirtyFlags + + +class EaxFlangerEffect final : + public EaxEffect +{ +public: + EaxFlangerEffect(); + + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXFLANGERPROPERTIES eax_{}; + EAXFLANGERPROPERTIES eax_d_{}; + EaxFlangerEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_waveform(); + void set_efx_phase(); + void set_efx_rate(); + void set_efx_depth(); + void set_efx_feedback(); + void set_efx_delay(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_waveform(unsigned long ulWaveform); + void validate_phase(long lPhase); + void validate_rate(float flRate); + void validate_depth(float flDepth); + void validate_feedback(float flFeedback); + void validate_delay(float flDelay); + void validate_all(const EAXFLANGERPROPERTIES& all); + + void defer_waveform(unsigned long ulWaveform); + void defer_phase(long lPhase); + void defer_rate(float flRate); + void defer_depth(float flDepth); + void defer_feedback(float flFeedback); + void defer_delay(float flDelay); + void defer_all(const EAXFLANGERPROPERTIES& all); + + void defer_waveform(const EaxEaxCall& eax_call); + void defer_phase(const EaxEaxCall& eax_call); + void defer_rate(const EaxEaxCall& eax_call); + void defer_depth(const EaxEaxCall& eax_call); + void defer_feedback(const EaxEaxCall& eax_call); + void defer_delay(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxFlangerEffect + + +class EaxFlangerEffectException : + public EaxException +{ +public: + explicit EaxFlangerEffectException( + const char* message) + : + EaxException{"EAX_FLANGER_EFFECT", message} + { + } +}; // EaxFlangerEffectException + + +EaxFlangerEffect::EaxFlangerEffect() + : EaxEffect{AL_EFFECT_FLANGER} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxFlangerEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxFlangerEffect::set_eax_defaults() +{ + eax_.ulWaveform = EAXFLANGER_DEFAULTWAVEFORM; + eax_.lPhase = EAXFLANGER_DEFAULTPHASE; + eax_.flRate = EAXFLANGER_DEFAULTRATE; + eax_.flDepth = EAXFLANGER_DEFAULTDEPTH; + eax_.flFeedback = EAXFLANGER_DEFAULTFEEDBACK; + eax_.flDelay = EAXFLANGER_DEFAULTDELAY; + + eax_d_ = eax_; +} + +void EaxFlangerEffect::set_efx_waveform() +{ + const auto waveform = clamp( + static_cast(eax_.ulWaveform), + AL_FLANGER_MIN_WAVEFORM, + AL_FLANGER_MAX_WAVEFORM); + + eax_set_efx_waveform(waveform, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_phase() +{ + const auto phase = clamp( + static_cast(eax_.lPhase), + AL_FLANGER_MIN_PHASE, + AL_FLANGER_MAX_PHASE); + + eax_set_efx_phase(phase, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_rate() +{ + const auto rate = clamp( + eax_.flRate, + AL_FLANGER_MIN_RATE, + AL_FLANGER_MAX_RATE); + + eax_set_efx_rate(rate, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_depth() +{ + const auto depth = clamp( + eax_.flDepth, + AL_FLANGER_MIN_DEPTH, + AL_FLANGER_MAX_DEPTH); + + eax_set_efx_depth(depth, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_feedback() +{ + const auto feedback = clamp( + eax_.flFeedback, + AL_FLANGER_MIN_FEEDBACK, + AL_FLANGER_MAX_FEEDBACK); + + eax_set_efx_feedback(feedback, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_delay() +{ + const auto delay = clamp( + eax_.flDelay, + AL_FLANGER_MIN_DELAY, + AL_FLANGER_MAX_DELAY); + + eax_set_efx_delay(delay, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_defaults() +{ + set_efx_waveform(); + set_efx_phase(); + set_efx_rate(); + set_efx_depth(); + set_efx_feedback(); + set_efx_delay(); +} + +void EaxFlangerEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXFLANGER_NONE: + break; + + case EAXFLANGER_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXFLANGER_WAVEFORM: + eax_call.set_value(eax_.ulWaveform); + break; + + case EAXFLANGER_PHASE: + eax_call.set_value(eax_.lPhase); + break; + + case EAXFLANGER_RATE: + eax_call.set_value(eax_.flRate); + break; + + case EAXFLANGER_DEPTH: + eax_call.set_value(eax_.flDepth); + break; + + case EAXFLANGER_FEEDBACK: + eax_call.set_value(eax_.flFeedback); + break; + + case EAXFLANGER_DELAY: + eax_call.set_value(eax_.flDelay); + break; + + default: + throw EaxFlangerEffectException{"Unsupported property id."}; + } +} + +void EaxFlangerEffect::validate_waveform( + unsigned long ulWaveform) +{ + eax_validate_range( + "Waveform", + ulWaveform, + EAXFLANGER_MINWAVEFORM, + EAXFLANGER_MAXWAVEFORM); +} + +void EaxFlangerEffect::validate_phase( + long lPhase) +{ + eax_validate_range( + "Phase", + lPhase, + EAXFLANGER_MINPHASE, + EAXFLANGER_MAXPHASE); +} + +void EaxFlangerEffect::validate_rate( + float flRate) +{ + eax_validate_range( + "Rate", + flRate, + EAXFLANGER_MINRATE, + EAXFLANGER_MAXRATE); +} + +void EaxFlangerEffect::validate_depth( + float flDepth) +{ + eax_validate_range( + "Depth", + flDepth, + EAXFLANGER_MINDEPTH, + EAXFLANGER_MAXDEPTH); +} + +void EaxFlangerEffect::validate_feedback( + float flFeedback) +{ + eax_validate_range( + "Feedback", + flFeedback, + EAXFLANGER_MINFEEDBACK, + EAXFLANGER_MAXFEEDBACK); +} + +void EaxFlangerEffect::validate_delay( + float flDelay) +{ + eax_validate_range( + "Delay", + flDelay, + EAXFLANGER_MINDELAY, + EAXFLANGER_MAXDELAY); +} + +void EaxFlangerEffect::validate_all( + const EAXFLANGERPROPERTIES& all) +{ + validate_waveform(all.ulWaveform); + validate_phase(all.lPhase); + validate_rate(all.flRate); + validate_depth(all.flDepth); + validate_feedback(all.flDelay); + validate_delay(all.flDelay); +} + +void EaxFlangerEffect::defer_waveform( + unsigned long ulWaveform) +{ + eax_d_.ulWaveform = ulWaveform; + eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); +} + +void EaxFlangerEffect::defer_phase( + long lPhase) +{ + eax_d_.lPhase = lPhase; + eax_dirty_flags_.lPhase = (eax_.lPhase != eax_d_.lPhase); +} + +void EaxFlangerEffect::defer_rate( + float flRate) +{ + eax_d_.flRate = flRate; + eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate); +} + +void EaxFlangerEffect::defer_depth( + float flDepth) +{ + eax_d_.flDepth = flDepth; + eax_dirty_flags_.flDepth = (eax_.flDepth != eax_d_.flDepth); +} + +void EaxFlangerEffect::defer_feedback( + float flFeedback) +{ + eax_d_.flFeedback = flFeedback; + eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback); +} + +void EaxFlangerEffect::defer_delay( + float flDelay) +{ + eax_d_.flDelay = flDelay; + eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay); +} + +void EaxFlangerEffect::defer_all( + const EAXFLANGERPROPERTIES& all) +{ + defer_waveform(all.ulWaveform); + defer_phase(all.lPhase); + defer_rate(all.flRate); + defer_depth(all.flDepth); + defer_feedback(all.flDelay); + defer_delay(all.flDelay); +} + +void EaxFlangerEffect::defer_waveform( + const EaxEaxCall& eax_call) +{ + const auto& waveform = + eax_call.get_value(); + + validate_waveform(waveform); + defer_waveform(waveform); +} + +void EaxFlangerEffect::defer_phase( + const EaxEaxCall& eax_call) +{ + const auto& phase = + eax_call.get_value(); + + validate_phase(phase); + defer_phase(phase); +} + +void EaxFlangerEffect::defer_rate( + const EaxEaxCall& eax_call) +{ + const auto& rate = + eax_call.get_value(); + + validate_rate(rate); + defer_rate(rate); +} + +void EaxFlangerEffect::defer_depth( + const EaxEaxCall& eax_call) +{ + const auto& depth = + eax_call.get_value(); + + validate_depth(depth); + defer_depth(depth); +} + +void EaxFlangerEffect::defer_feedback( + const EaxEaxCall& eax_call) +{ + const auto& feedback = + eax_call.get_value(); + + validate_feedback(feedback); + defer_feedback(feedback); +} + +void EaxFlangerEffect::defer_delay( + const EaxEaxCall& eax_call) +{ + const auto& delay = + eax_call.get_value(); + + validate_delay(delay); + defer_delay(delay); +} + +void EaxFlangerEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxFlangerEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxFlangerEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulWaveform) + { + set_efx_waveform(); + } + + if (eax_dirty_flags_.lPhase) + { + set_efx_phase(); + } + + if (eax_dirty_flags_.flRate) + { + set_efx_rate(); + } + + if (eax_dirty_flags_.flDepth) + { + set_efx_depth(); + } + + if (eax_dirty_flags_.flFeedback) + { + set_efx_feedback(); + } + + if (eax_dirty_flags_.flDelay) + { + set_efx_delay(); + } + + eax_dirty_flags_ = EaxFlangerEffectDirtyFlags{}; + + return true; +} + +void EaxFlangerEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXFLANGER_NONE: + break; + + case EAXFLANGER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXFLANGER_WAVEFORM: + defer_waveform(eax_call); + break; + + case EAXFLANGER_PHASE: + defer_phase(eax_call); + break; + + case EAXFLANGER_RATE: + defer_rate(eax_call); + break; + + case EAXFLANGER_DEPTH: + defer_depth(eax_call); + break; + + case EAXFLANGER_FEEDBACK: + defer_feedback(eax_call); + break; + + case EAXFLANGER_DELAY: + defer_delay(eax_call); + break; + + default: + throw EaxFlangerEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_flanger_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/effects/compressor.cpp b/modules/openal-soft/al/effects/compressor.cpp new file mode 100644 index 0000000..bb5dfa3 --- /dev/null +++ b/modules/openal-soft/al/effects/compressor.cpp @@ -0,0 +1,296 @@ + +#include "config.h" + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + + +namespace { + +void Compressor_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_COMPRESSOR_ONOFF: + if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF)) + throw effect_exception{AL_INVALID_VALUE, "Compressor state out of range"}; + props->Compressor.OnOff = (val != AL_FALSE); + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", + param}; + } +} +void Compressor_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ Compressor_setParami(props, param, vals[0]); } +void Compressor_setParamf(EffectProps*, ALenum param, float) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } +void Compressor_setParamfv(EffectProps*, ALenum param, const float*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", + param}; +} + +void Compressor_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_COMPRESSOR_ONOFF: + *val = props->Compressor.OnOff; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", + param}; + } +} +void Compressor_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ Compressor_getParami(props, param, vals); } +void Compressor_getParamf(const EffectProps*, ALenum param, float*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } +void Compressor_getParamfv(const EffectProps*, ALenum param, float*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", + param}; +} + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Compressor); + +const EffectProps CompressorEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxCompressorEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxCompressorEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxCompressorEffectDirtyFlagsValue ulOnOff : 1; +}; // EaxCompressorEffectDirtyFlags + + +class EaxCompressorEffect final : + public EaxEffect +{ +public: + EaxCompressorEffect(); + + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXAGCCOMPRESSORPROPERTIES eax_{}; + EAXAGCCOMPRESSORPROPERTIES eax_d_{}; + EaxCompressorEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + void set_efx_on_off(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_on_off(unsigned long ulOnOff); + void validate_all(const EAXAGCCOMPRESSORPROPERTIES& eax_all); + + void defer_on_off(unsigned long ulOnOff); + void defer_all(const EAXAGCCOMPRESSORPROPERTIES& eax_all); + + void defer_on_off(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxCompressorEffect + + +class EaxCompressorEffectException : + public EaxException +{ +public: + explicit EaxCompressorEffectException( + const char* message) + : + EaxException{"EAX_COMPRESSOR_EFFECT", message} + { + } +}; // EaxCompressorEffectException + + +EaxCompressorEffect::EaxCompressorEffect() + : EaxEffect{AL_EFFECT_COMPRESSOR} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +// [[nodiscard]] +void EaxCompressorEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxCompressorEffect::set_eax_defaults() +{ + eax_.ulOnOff = EAXAGCCOMPRESSOR_DEFAULTONOFF; + + eax_d_ = eax_; +} + +void EaxCompressorEffect::set_efx_on_off() +{ + const auto on_off = clamp( + static_cast(eax_.ulOnOff), + AL_COMPRESSOR_MIN_ONOFF, + AL_COMPRESSOR_MAX_ONOFF); + + al_effect_props_.Compressor.OnOff = (on_off != AL_FALSE); +} + +void EaxCompressorEffect::set_efx_defaults() +{ + set_efx_on_off(); +} + +void EaxCompressorEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXAGCCOMPRESSOR_NONE: + break; + + case EAXAGCCOMPRESSOR_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXAGCCOMPRESSOR_ONOFF: + eax_call.set_value(eax_.ulOnOff); + break; + + default: + throw EaxCompressorEffectException{"Unsupported property id."}; + } +} + +void EaxCompressorEffect::validate_on_off( + unsigned long ulOnOff) +{ + eax_validate_range( + "On-Off", + ulOnOff, + EAXAGCCOMPRESSOR_MINONOFF, + EAXAGCCOMPRESSOR_MAXONOFF); +} + +void EaxCompressorEffect::validate_all( + const EAXAGCCOMPRESSORPROPERTIES& eax_all) +{ + validate_on_off(eax_all.ulOnOff); +} + +void EaxCompressorEffect::defer_on_off( + unsigned long ulOnOff) +{ + eax_d_.ulOnOff = ulOnOff; + eax_dirty_flags_.ulOnOff = (eax_.ulOnOff != eax_d_.ulOnOff); +} + +void EaxCompressorEffect::defer_all( + const EAXAGCCOMPRESSORPROPERTIES& eax_all) +{ + defer_on_off(eax_all.ulOnOff); +} + +void EaxCompressorEffect::defer_on_off( + const EaxEaxCall& eax_call) +{ + const auto& on_off = + eax_call.get_value(); + + validate_on_off(on_off); + defer_on_off(on_off); +} + +void EaxCompressorEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxCompressorEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxCompressorEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulOnOff) + { + set_efx_on_off(); + } + + eax_dirty_flags_ = EaxCompressorEffectDirtyFlags{}; + + return true; +} + +void EaxCompressorEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXAGCCOMPRESSOR_NONE: + break; + + case EAXAGCCOMPRESSOR_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXAGCCOMPRESSOR_ONOFF: + defer_on_off(eax_call); + break; + + default: + throw EaxCompressorEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_compressor_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/effects/convolution.cpp b/modules/openal-soft/al/effects/convolution.cpp new file mode 100644 index 0000000..8e850fd --- /dev/null +++ b/modules/openal-soft/al/effects/convolution.cpp @@ -0,0 +1,93 @@ + +#include "config.h" + +#include "AL/al.h" +#include "alc/inprogext.h" + +#include "alc/effects/base.h" +#include "effects.h" + + +namespace { + +void Convolution_setParami(EffectProps* /*props*/, ALenum param, int /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", + param}; + } +} +void Convolution_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ + switch(param) + { + default: + Convolution_setParami(props, param, vals[0]); + } +} +void Convolution_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", + param}; + } +} +void Convolution_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ + switch(param) + { + default: + Convolution_setParamf(props, param, vals[0]); + } +} + +void Convolution_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", + param}; + } +} +void Convolution_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ + switch(param) + { + default: + Convolution_getParami(props, param, vals); + } +} +void Convolution_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", + param}; + } +} +void Convolution_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ + switch(param) + { + default: + Convolution_getParamf(props, param, vals); + } +} + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Convolution); + +const EffectProps ConvolutionEffectProps{genDefaultProps()}; diff --git a/modules/openal-soft/al/effects/dedicated.cpp b/modules/openal-soft/al/effects/dedicated.cpp new file mode 100644 index 0000000..db57003 --- /dev/null +++ b/modules/openal-soft/al/effects/dedicated.cpp @@ -0,0 +1,72 @@ + +#include "config.h" + +#include + +#include "AL/al.h" +#include "AL/alext.h" + +#include "alc/effects/base.h" +#include "effects.h" + + +namespace { + +void Dedicated_setParami(EffectProps*, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } +void Dedicated_setParamiv(EffectProps*, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", + param}; +} +void Dedicated_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_DEDICATED_GAIN: + if(!(val >= 0.0f && std::isfinite(val))) + throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; + props->Dedicated.Gain = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + } +} +void Dedicated_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Dedicated_setParamf(props, param, vals[0]); } + +void Dedicated_getParami(const EffectProps*, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } +void Dedicated_getParamiv(const EffectProps*, ALenum param, int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", + param}; +} +void Dedicated_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_DEDICATED_GAIN: + *val = props->Dedicated.Gain; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + } +} +void Dedicated_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Dedicated_getParamf(props, param, vals); } + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Dedicated.Gain = 1.0f; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Dedicated); + +const EffectProps DedicatedEffectProps{genDefaultProps()}; diff --git a/modules/openal-soft/al/effects/distortion.cpp b/modules/openal-soft/al/effects/distortion.cpp new file mode 100644 index 0000000..13b1f23 --- /dev/null +++ b/modules/openal-soft/al/effects/distortion.cpp @@ -0,0 +1,571 @@ + +#include "config.h" + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + + +namespace { + +void Distortion_setParami(EffectProps*, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } +void Distortion_setParamiv(EffectProps*, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", + param}; +} +void Distortion_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_DISTORTION_EDGE: + if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE)) + throw effect_exception{AL_INVALID_VALUE, "Distortion edge out of range"}; + props->Distortion.Edge = val; + break; + + case AL_DISTORTION_GAIN: + if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Distortion gain out of range"}; + props->Distortion.Gain = val; + break; + + case AL_DISTORTION_LOWPASS_CUTOFF: + if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF)) + throw effect_exception{AL_INVALID_VALUE, "Distortion low-pass cutoff out of range"}; + props->Distortion.LowpassCutoff = val; + break; + + case AL_DISTORTION_EQCENTER: + if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER)) + throw effect_exception{AL_INVALID_VALUE, "Distortion EQ center out of range"}; + props->Distortion.EQCenter = val; + break; + + case AL_DISTORTION_EQBANDWIDTH: + if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH)) + throw effect_exception{AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"}; + props->Distortion.EQBandwidth = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; + } +} +void Distortion_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Distortion_setParamf(props, param, vals[0]); } + +void Distortion_getParami(const EffectProps*, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } +void Distortion_getParamiv(const EffectProps*, ALenum param, int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", + param}; +} +void Distortion_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_DISTORTION_EDGE: + *val = props->Distortion.Edge; + break; + + case AL_DISTORTION_GAIN: + *val = props->Distortion.Gain; + break; + + case AL_DISTORTION_LOWPASS_CUTOFF: + *val = props->Distortion.LowpassCutoff; + break; + + case AL_DISTORTION_EQCENTER: + *val = props->Distortion.EQCenter; + break; + + case AL_DISTORTION_EQBANDWIDTH: + *val = props->Distortion.EQBandwidth; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; + } +} +void Distortion_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Distortion_getParamf(props, param, vals); } + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE; + props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN; + props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF; + props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER; + props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Distortion); + +const EffectProps DistortionEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxDistortionEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxDistortionEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxDistortionEffectDirtyFlagsValue flEdge : 1; + EaxDistortionEffectDirtyFlagsValue lGain : 1; + EaxDistortionEffectDirtyFlagsValue flLowPassCutOff : 1; + EaxDistortionEffectDirtyFlagsValue flEQCenter : 1; + EaxDistortionEffectDirtyFlagsValue flEQBandwidth : 1; +}; // EaxDistortionEffectDirtyFlags + + +class EaxDistortionEffect final : + public EaxEffect +{ +public: + EaxDistortionEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXDISTORTIONPROPERTIES eax_{}; + EAXDISTORTIONPROPERTIES eax_d_{}; + EaxDistortionEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_edge(); + void set_efx_gain(); + void set_efx_lowpass_cutoff(); + void set_efx_eq_center(); + void set_efx_eq_bandwidth(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_edge(float flEdge); + void validate_gain(long lGain); + void validate_lowpass_cutoff(float flLowPassCutOff); + void validate_eq_center(float flEQCenter); + void validate_eq_bandwidth(float flEQBandwidth); + void validate_all(const EAXDISTORTIONPROPERTIES& eax_all); + + void defer_edge(float flEdge); + void defer_gain(long lGain); + void defer_low_pass_cutoff(float flLowPassCutOff); + void defer_eq_center(float flEQCenter); + void defer_eq_bandwidth(float flEQBandwidth); + void defer_all(const EAXDISTORTIONPROPERTIES& eax_all); + + void defer_edge(const EaxEaxCall& eax_call); + void defer_gain(const EaxEaxCall& eax_call); + void defer_low_pass_cutoff(const EaxEaxCall& eax_call); + void defer_eq_center(const EaxEaxCall& eax_call); + void defer_eq_bandwidth(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxDistortionEffect + + +class EaxDistortionEffectException : + public EaxException +{ +public: + explicit EaxDistortionEffectException( + const char* message) + : + EaxException{"EAX_DISTORTION_EFFECT", message} + { + } +}; // EaxDistortionEffectException + + +EaxDistortionEffect::EaxDistortionEffect() + : EaxEffect{AL_EFFECT_DISTORTION} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxDistortionEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxDistortionEffect::set_eax_defaults() +{ + eax_.flEdge = EAXDISTORTION_DEFAULTEDGE; + eax_.lGain = EAXDISTORTION_DEFAULTGAIN; + eax_.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF; + eax_.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER; + eax_.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH; + + eax_d_ = eax_; +} + +void EaxDistortionEffect::set_efx_edge() +{ + const auto edge = clamp( + eax_.flEdge, + AL_DISTORTION_MIN_EDGE, + AL_DISTORTION_MAX_EDGE); + + al_effect_props_.Distortion.Edge = edge; +} + +void EaxDistortionEffect::set_efx_gain() +{ + const auto gain = clamp( + level_mb_to_gain(static_cast(eax_.lGain)), + AL_DISTORTION_MIN_GAIN, + AL_DISTORTION_MAX_GAIN); + + al_effect_props_.Distortion.Gain = gain; +} + +void EaxDistortionEffect::set_efx_lowpass_cutoff() +{ + const auto lowpass_cutoff = clamp( + eax_.flLowPassCutOff, + AL_DISTORTION_MIN_LOWPASS_CUTOFF, + AL_DISTORTION_MAX_LOWPASS_CUTOFF); + + al_effect_props_.Distortion.LowpassCutoff = lowpass_cutoff; +} + +void EaxDistortionEffect::set_efx_eq_center() +{ + const auto eq_center = clamp( + eax_.flEQCenter, + AL_DISTORTION_MIN_EQCENTER, + AL_DISTORTION_MAX_EQCENTER); + + al_effect_props_.Distortion.EQCenter = eq_center; +} + +void EaxDistortionEffect::set_efx_eq_bandwidth() +{ + const auto eq_bandwidth = clamp( + eax_.flEdge, + AL_DISTORTION_MIN_EQBANDWIDTH, + AL_DISTORTION_MAX_EQBANDWIDTH); + + al_effect_props_.Distortion.EQBandwidth = eq_bandwidth; +} + +void EaxDistortionEffect::set_efx_defaults() +{ + set_efx_edge(); + set_efx_gain(); + set_efx_lowpass_cutoff(); + set_efx_eq_center(); + set_efx_eq_bandwidth(); +} + +void EaxDistortionEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXDISTORTION_NONE: + break; + + case EAXDISTORTION_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXDISTORTION_EDGE: + eax_call.set_value(eax_.flEdge); + break; + + case EAXDISTORTION_GAIN: + eax_call.set_value(eax_.lGain); + break; + + case EAXDISTORTION_LOWPASSCUTOFF: + eax_call.set_value(eax_.flLowPassCutOff); + break; + + case EAXDISTORTION_EQCENTER: + eax_call.set_value(eax_.flEQCenter); + break; + + case EAXDISTORTION_EQBANDWIDTH: + eax_call.set_value(eax_.flEQBandwidth); + break; + + default: + throw EaxDistortionEffectException{"Unsupported property id."}; + } +} + +void EaxDistortionEffect::validate_edge( + float flEdge) +{ + eax_validate_range( + "Edge", + flEdge, + EAXDISTORTION_MINEDGE, + EAXDISTORTION_MAXEDGE); +} + +void EaxDistortionEffect::validate_gain( + long lGain) +{ + eax_validate_range( + "Gain", + lGain, + EAXDISTORTION_MINGAIN, + EAXDISTORTION_MAXGAIN); +} + +void EaxDistortionEffect::validate_lowpass_cutoff( + float flLowPassCutOff) +{ + eax_validate_range( + "Low-pass Cut-off", + flLowPassCutOff, + EAXDISTORTION_MINLOWPASSCUTOFF, + EAXDISTORTION_MAXLOWPASSCUTOFF); +} + +void EaxDistortionEffect::validate_eq_center( + float flEQCenter) +{ + eax_validate_range( + "EQ Center", + flEQCenter, + EAXDISTORTION_MINEQCENTER, + EAXDISTORTION_MAXEQCENTER); +} + +void EaxDistortionEffect::validate_eq_bandwidth( + float flEQBandwidth) +{ + eax_validate_range( + "EQ Bandwidth", + flEQBandwidth, + EAXDISTORTION_MINEQBANDWIDTH, + EAXDISTORTION_MAXEQBANDWIDTH); +} + +void EaxDistortionEffect::validate_all( + const EAXDISTORTIONPROPERTIES& eax_all) +{ + validate_edge(eax_all.flEdge); + validate_gain(eax_all.lGain); + validate_lowpass_cutoff(eax_all.flLowPassCutOff); + validate_eq_center(eax_all.flEQCenter); + validate_eq_bandwidth(eax_all.flEQBandwidth); +} + +void EaxDistortionEffect::defer_edge( + float flEdge) +{ + eax_d_.flEdge = flEdge; + eax_dirty_flags_.flEdge = (eax_.flEdge != eax_d_.flEdge); +} + +void EaxDistortionEffect::defer_gain( + long lGain) +{ + eax_d_.lGain = lGain; + eax_dirty_flags_.lGain = (eax_.lGain != eax_d_.lGain); +} + +void EaxDistortionEffect::defer_low_pass_cutoff( + float flLowPassCutOff) +{ + eax_d_.flLowPassCutOff = flLowPassCutOff; + eax_dirty_flags_.flLowPassCutOff = (eax_.flLowPassCutOff != eax_d_.flLowPassCutOff); +} + +void EaxDistortionEffect::defer_eq_center( + float flEQCenter) +{ + eax_d_.flEQCenter = flEQCenter; + eax_dirty_flags_.flEQCenter = (eax_.flEQCenter != eax_d_.flEQCenter); +} + +void EaxDistortionEffect::defer_eq_bandwidth( + float flEQBandwidth) +{ + eax_d_.flEQBandwidth = flEQBandwidth; + eax_dirty_flags_.flEQBandwidth = (eax_.flEQBandwidth != eax_d_.flEQBandwidth); +} + +void EaxDistortionEffect::defer_all( + const EAXDISTORTIONPROPERTIES& eax_all) +{ + defer_edge(eax_all.flEdge); + defer_gain(eax_all.lGain); + defer_low_pass_cutoff(eax_all.flLowPassCutOff); + defer_eq_center(eax_all.flEQCenter); + defer_eq_bandwidth(eax_all.flEQBandwidth); +} + +void EaxDistortionEffect::defer_edge( + const EaxEaxCall& eax_call) +{ + const auto& edge = + eax_call.get_value(); + + validate_edge(edge); + defer_edge(edge); +} + +void EaxDistortionEffect::defer_gain( + const EaxEaxCall& eax_call) +{ + const auto& gain = + eax_call.get_value(); + + validate_gain(gain); + defer_gain(gain); +} + +void EaxDistortionEffect::defer_low_pass_cutoff( + const EaxEaxCall& eax_call) +{ + const auto& lowpass_cutoff = + eax_call.get_value(); + + validate_lowpass_cutoff(lowpass_cutoff); + defer_low_pass_cutoff(lowpass_cutoff); +} + +void EaxDistortionEffect::defer_eq_center( + const EaxEaxCall& eax_call) +{ + const auto& eq_center = + eax_call.get_value(); + + validate_eq_center(eq_center); + defer_eq_center(eq_center); +} + +void EaxDistortionEffect::defer_eq_bandwidth( + const EaxEaxCall& eax_call) +{ + const auto& eq_bandwidth = + eax_call.get_value(); + + validate_eq_bandwidth(eq_bandwidth); + defer_eq_bandwidth(eq_bandwidth); +} + +void EaxDistortionEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxDistortionEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxDistortionEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flEdge) + { + set_efx_edge(); + } + + if (eax_dirty_flags_.lGain) + { + set_efx_gain(); + } + + if (eax_dirty_flags_.flLowPassCutOff) + { + set_efx_lowpass_cutoff(); + } + + if (eax_dirty_flags_.flEQCenter) + { + set_efx_eq_center(); + } + + if (eax_dirty_flags_.flEQBandwidth) + { + set_efx_eq_bandwidth(); + } + + eax_dirty_flags_ = EaxDistortionEffectDirtyFlags{}; + + return true; +} + +void EaxDistortionEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXDISTORTION_NONE: + break; + + case EAXDISTORTION_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXDISTORTION_EDGE: + defer_edge(eax_call); + break; + + case EAXDISTORTION_GAIN: + defer_gain(eax_call); + break; + + case EAXDISTORTION_LOWPASSCUTOFF: + defer_low_pass_cutoff(eax_call); + break; + + case EAXDISTORTION_EQCENTER: + defer_eq_center(eax_call); + break; + + case EAXDISTORTION_EQBANDWIDTH: + defer_eq_bandwidth(eax_call); + break; + + default: + throw EaxDistortionEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_distortion_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/effects/echo.cpp b/modules/openal-soft/al/effects/echo.cpp new file mode 100644 index 0000000..61adad7 --- /dev/null +++ b/modules/openal-soft/al/effects/echo.cpp @@ -0,0 +1,569 @@ + +#include "config.h" + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + + +namespace { + +static_assert(EchoMaxDelay >= AL_ECHO_MAX_DELAY, "Echo max delay too short"); +static_assert(EchoMaxLRDelay >= AL_ECHO_MAX_LRDELAY, "Echo max left-right delay too short"); + +void Echo_setParami(EffectProps*, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } +void Echo_setParamiv(EffectProps*, ALenum param, const int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } +void Echo_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_ECHO_DELAY: + if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "Echo delay out of range"}; + props->Echo.Delay = val; + break; + + case AL_ECHO_LRDELAY: + if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY)) + throw effect_exception{AL_INVALID_VALUE, "Echo LR delay out of range"}; + props->Echo.LRDelay = val; + break; + + case AL_ECHO_DAMPING: + if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING)) + throw effect_exception{AL_INVALID_VALUE, "Echo damping out of range"}; + props->Echo.Damping = val; + break; + + case AL_ECHO_FEEDBACK: + if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK)) + throw effect_exception{AL_INVALID_VALUE, "Echo feedback out of range"}; + props->Echo.Feedback = val; + break; + + case AL_ECHO_SPREAD: + if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD)) + throw effect_exception{AL_INVALID_VALUE, "Echo spread out of range"}; + props->Echo.Spread = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param}; + } +} +void Echo_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Echo_setParamf(props, param, vals[0]); } + +void Echo_getParami(const EffectProps*, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } +void Echo_getParamiv(const EffectProps*, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } +void Echo_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_ECHO_DELAY: + *val = props->Echo.Delay; + break; + + case AL_ECHO_LRDELAY: + *val = props->Echo.LRDelay; + break; + + case AL_ECHO_DAMPING: + *val = props->Echo.Damping; + break; + + case AL_ECHO_FEEDBACK: + *val = props->Echo.Feedback; + break; + + case AL_ECHO_SPREAD: + *val = props->Echo.Spread; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param}; + } +} +void Echo_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Echo_getParamf(props, param, vals); } + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Echo.Delay = AL_ECHO_DEFAULT_DELAY; + props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY; + props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING; + props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK; + props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Echo); + +const EffectProps EchoEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxEchoEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxEchoEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxEchoEffectDirtyFlagsValue flDelay : 1; + EaxEchoEffectDirtyFlagsValue flLRDelay : 1; + EaxEchoEffectDirtyFlagsValue flDamping : 1; + EaxEchoEffectDirtyFlagsValue flFeedback : 1; + EaxEchoEffectDirtyFlagsValue flSpread : 1; +}; // EaxEchoEffectDirtyFlags + + +class EaxEchoEffect final : + public EaxEffect +{ +public: + EaxEchoEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXECHOPROPERTIES eax_{}; + EAXECHOPROPERTIES eax_d_{}; + EaxEchoEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_delay(); + void set_efx_lr_delay(); + void set_efx_damping(); + void set_efx_feedback(); + void set_efx_spread(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_delay(float flDelay); + void validate_lr_delay(float flLRDelay); + void validate_damping(float flDamping); + void validate_feedback(float flFeedback); + void validate_spread(float flSpread); + void validate_all(const EAXECHOPROPERTIES& all); + + void defer_delay(float flDelay); + void defer_lr_delay(float flLRDelay); + void defer_damping(float flDamping); + void defer_feedback(float flFeedback); + void defer_spread(float flSpread); + void defer_all(const EAXECHOPROPERTIES& all); + + void defer_delay(const EaxEaxCall& eax_call); + void defer_lr_delay(const EaxEaxCall& eax_call); + void defer_damping(const EaxEaxCall& eax_call); + void defer_feedback(const EaxEaxCall& eax_call); + void defer_spread(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxEchoEffect + + +class EaxEchoEffectException : + public EaxException +{ +public: + explicit EaxEchoEffectException( + const char* message) + : + EaxException{"EAX_ECHO_EFFECT", message} + { + } +}; // EaxEchoEffectException + + +EaxEchoEffect::EaxEchoEffect() + : EaxEffect{AL_EFFECT_ECHO} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxEchoEffect::dispatch( + const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxEchoEffect::set_eax_defaults() +{ + eax_.flDelay = EAXECHO_DEFAULTDELAY; + eax_.flLRDelay = EAXECHO_DEFAULTLRDELAY; + eax_.flDamping = EAXECHO_DEFAULTDAMPING; + eax_.flFeedback = EAXECHO_DEFAULTFEEDBACK; + eax_.flSpread = EAXECHO_DEFAULTSPREAD; + + eax_d_ = eax_; +} + +void EaxEchoEffect::set_efx_delay() +{ + const auto delay = clamp( + eax_.flDelay, + AL_ECHO_MIN_DELAY, + AL_ECHO_MAX_DELAY); + + al_effect_props_.Echo.Delay = delay; +} + +void EaxEchoEffect::set_efx_lr_delay() +{ + const auto lr_delay = clamp( + eax_.flLRDelay, + AL_ECHO_MIN_LRDELAY, + AL_ECHO_MAX_LRDELAY); + + al_effect_props_.Echo.LRDelay = lr_delay; +} + +void EaxEchoEffect::set_efx_damping() +{ + const auto damping = clamp( + eax_.flDamping, + AL_ECHO_MIN_DAMPING, + AL_ECHO_MAX_DAMPING); + + al_effect_props_.Echo.Damping = damping; +} + +void EaxEchoEffect::set_efx_feedback() +{ + const auto feedback = clamp( + eax_.flFeedback, + AL_ECHO_MIN_FEEDBACK, + AL_ECHO_MAX_FEEDBACK); + + al_effect_props_.Echo.Feedback = feedback; +} + +void EaxEchoEffect::set_efx_spread() +{ + const auto spread = clamp( + eax_.flSpread, + AL_ECHO_MIN_SPREAD, + AL_ECHO_MAX_SPREAD); + + al_effect_props_.Echo.Spread = spread; +} + +void EaxEchoEffect::set_efx_defaults() +{ + set_efx_delay(); + set_efx_lr_delay(); + set_efx_damping(); + set_efx_feedback(); + set_efx_spread(); +} + +void EaxEchoEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXECHO_NONE: + break; + + case EAXECHO_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXECHO_DELAY: + eax_call.set_value(eax_.flDelay); + break; + + case EAXECHO_LRDELAY: + eax_call.set_value(eax_.flLRDelay); + break; + + case EAXECHO_DAMPING: + eax_call.set_value(eax_.flDamping); + break; + + case EAXECHO_FEEDBACK: + eax_call.set_value(eax_.flFeedback); + break; + + case EAXECHO_SPREAD: + eax_call.set_value(eax_.flSpread); + break; + + default: + throw EaxEchoEffectException{"Unsupported property id."}; + } +} + +void EaxEchoEffect::validate_delay( + float flDelay) +{ + eax_validate_range( + "Delay", + flDelay, + EAXECHO_MINDELAY, + EAXECHO_MAXDELAY); +} + +void EaxEchoEffect::validate_lr_delay( + float flLRDelay) +{ + eax_validate_range( + "LR Delay", + flLRDelay, + EAXECHO_MINLRDELAY, + EAXECHO_MAXLRDELAY); +} + +void EaxEchoEffect::validate_damping( + float flDamping) +{ + eax_validate_range( + "Damping", + flDamping, + EAXECHO_MINDAMPING, + EAXECHO_MAXDAMPING); +} + +void EaxEchoEffect::validate_feedback( + float flFeedback) +{ + eax_validate_range( + "Feedback", + flFeedback, + EAXECHO_MINFEEDBACK, + EAXECHO_MAXFEEDBACK); +} + +void EaxEchoEffect::validate_spread( + float flSpread) +{ + eax_validate_range( + "Spread", + flSpread, + EAXECHO_MINSPREAD, + EAXECHO_MAXSPREAD); +} + +void EaxEchoEffect::validate_all( + const EAXECHOPROPERTIES& all) +{ + validate_delay(all.flDelay); + validate_lr_delay(all.flLRDelay); + validate_damping(all.flDamping); + validate_feedback(all.flFeedback); + validate_spread(all.flSpread); +} + +void EaxEchoEffect::defer_delay( + float flDelay) +{ + eax_d_.flDelay = flDelay; + eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay); +} + +void EaxEchoEffect::defer_lr_delay( + float flLRDelay) +{ + eax_d_.flLRDelay = flLRDelay; + eax_dirty_flags_.flLRDelay = (eax_.flLRDelay != eax_d_.flLRDelay); +} + +void EaxEchoEffect::defer_damping( + float flDamping) +{ + eax_d_.flDamping = flDamping; + eax_dirty_flags_.flDamping = (eax_.flDamping != eax_d_.flDamping); +} + +void EaxEchoEffect::defer_feedback( + float flFeedback) +{ + eax_d_.flFeedback = flFeedback; + eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback); +} + +void EaxEchoEffect::defer_spread( + float flSpread) +{ + eax_d_.flSpread = flSpread; + eax_dirty_flags_.flSpread = (eax_.flSpread != eax_d_.flSpread); +} + +void EaxEchoEffect::defer_all( + const EAXECHOPROPERTIES& all) +{ + defer_delay(all.flDelay); + defer_lr_delay(all.flLRDelay); + defer_damping(all.flDamping); + defer_feedback(all.flFeedback); + defer_spread(all.flSpread); +} + +void EaxEchoEffect::defer_delay( + const EaxEaxCall& eax_call) +{ + const auto& delay = + eax_call.get_value(); + + validate_delay(delay); + defer_delay(delay); +} + +void EaxEchoEffect::defer_lr_delay( + const EaxEaxCall& eax_call) +{ + const auto& lr_delay = + eax_call.get_value(); + + validate_lr_delay(lr_delay); + defer_lr_delay(lr_delay); +} + +void EaxEchoEffect::defer_damping( + const EaxEaxCall& eax_call) +{ + const auto& damping = + eax_call.get_value(); + + validate_damping(damping); + defer_damping(damping); +} + +void EaxEchoEffect::defer_feedback( + const EaxEaxCall& eax_call) +{ + const auto& feedback = + eax_call.get_value(); + + validate_feedback(feedback); + defer_feedback(feedback); +} + +void EaxEchoEffect::defer_spread( + const EaxEaxCall& eax_call) +{ + const auto& spread = + eax_call.get_value(); + + validate_spread(spread); + defer_spread(spread); +} + +void EaxEchoEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxEchoEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxEchoEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flDelay) + { + set_efx_delay(); + } + + if (eax_dirty_flags_.flLRDelay) + { + set_efx_lr_delay(); + } + + if (eax_dirty_flags_.flDamping) + { + set_efx_damping(); + } + + if (eax_dirty_flags_.flFeedback) + { + set_efx_feedback(); + } + + if (eax_dirty_flags_.flSpread) + { + set_efx_spread(); + } + + eax_dirty_flags_ = EaxEchoEffectDirtyFlags{}; + + return true; +} + +void EaxEchoEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXECHO_NONE: + break; + + case EAXECHO_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXECHO_DELAY: + defer_delay(eax_call); + break; + + case EAXECHO_LRDELAY: + defer_lr_delay(eax_call); + break; + + case EAXECHO_DAMPING: + defer_damping(eax_call); + break; + + case EAXECHO_FEEDBACK: + defer_feedback(eax_call); + break; + + case EAXECHO_SPREAD: + defer_spread(eax_call); + break; + + default: + throw EaxEchoEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_echo_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/effects/effects.cpp b/modules/openal-soft/al/effects/effects.cpp new file mode 100644 index 0000000..faf322d --- /dev/null +++ b/modules/openal-soft/al/effects/effects.cpp @@ -0,0 +1,66 @@ + +#include "config.h" + +#ifdef ALSOFT_EAX + +#include "effects.h" + +#include + +#include "AL/efx.h" + + +EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type) +{ +#define EAX_PREFIX "[EAX_MAKE_EAX_EFFECT] " + + switch (al_effect_type) + { + case AL_EFFECT_NULL: + return eax_create_eax_null_effect(); + + case AL_EFFECT_CHORUS: + return eax_create_eax_chorus_effect(); + + case AL_EFFECT_DISTORTION: + return eax_create_eax_distortion_effect(); + + case AL_EFFECT_ECHO: + return eax_create_eax_echo_effect(); + + case AL_EFFECT_FLANGER: + return eax_create_eax_flanger_effect(); + + case AL_EFFECT_FREQUENCY_SHIFTER: + return eax_create_eax_frequency_shifter_effect(); + + case AL_EFFECT_VOCAL_MORPHER: + return eax_create_eax_vocal_morpher_effect(); + + case AL_EFFECT_PITCH_SHIFTER: + return eax_create_eax_pitch_shifter_effect(); + + case AL_EFFECT_RING_MODULATOR: + return eax_create_eax_ring_modulator_effect(); + + case AL_EFFECT_AUTOWAH: + return eax_create_eax_auto_wah_effect(); + + case AL_EFFECT_COMPRESSOR: + return eax_create_eax_compressor_effect(); + + case AL_EFFECT_EQUALIZER: + return eax_create_eax_equalizer_effect(); + + case AL_EFFECT_EAXREVERB: + return eax_create_eax_reverb_effect(); + + default: + assert(false && "Unsupported AL effect type."); + return nullptr; + } + +#undef EAX_PREFIX +} + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/effects/effects.h b/modules/openal-soft/al/effects/effects.h new file mode 100644 index 0000000..830e719 --- /dev/null +++ b/modules/openal-soft/al/effects/effects.h @@ -0,0 +1,92 @@ +#ifndef AL_EFFECTS_EFFECTS_H +#define AL_EFFECTS_EFFECTS_H + +#include "AL/al.h" + +#include "core/except.h" + +#ifdef ALSOFT_EAX +#include "al/eax_effect.h" +#endif // ALSOFT_EAX + +union EffectProps; + + +class effect_exception final : public al::base_exception { + ALenum mErrorCode; + +public: +#ifdef __USE_MINGW_ANSI_STDIO + [[gnu::format(gnu_printf, 3, 4)]] +#else + [[gnu::format(printf, 3, 4)]] +#endif + effect_exception(ALenum code, const char *msg, ...); + + ALenum errorCode() const noexcept { return mErrorCode; } +}; + + +struct EffectVtable { + void (*const setParami)(EffectProps *props, ALenum param, int val); + void (*const setParamiv)(EffectProps *props, ALenum param, const int *vals); + void (*const setParamf)(EffectProps *props, ALenum param, float val); + void (*const setParamfv)(EffectProps *props, ALenum param, const float *vals); + + void (*const getParami)(const EffectProps *props, ALenum param, int *val); + void (*const getParamiv)(const EffectProps *props, ALenum param, int *vals); + void (*const getParamf)(const EffectProps *props, ALenum param, float *val); + void (*const getParamfv)(const EffectProps *props, ALenum param, float *vals); +}; + +#define DEFINE_ALEFFECT_VTABLE(T) \ +const EffectVtable T##EffectVtable = { \ + T##_setParami, T##_setParamiv, \ + T##_setParamf, T##_setParamfv, \ + T##_getParami, T##_getParamiv, \ + T##_getParamf, T##_getParamfv, \ +} + + +/* 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 DedicatedEffectProps; +extern const EffectProps ConvolutionEffectProps; + +/* Vtables to get/set properties for the given effect types. */ +extern const EffectVtable NullEffectVtable; +extern const EffectVtable ReverbEffectVtable; +extern const EffectVtable StdReverbEffectVtable; +extern const EffectVtable AutowahEffectVtable; +extern const EffectVtable ChorusEffectVtable; +extern const EffectVtable CompressorEffectVtable; +extern const EffectVtable DistortionEffectVtable; +extern const EffectVtable EchoEffectVtable; +extern const EffectVtable EqualizerEffectVtable; +extern const EffectVtable FlangerEffectVtable; +extern const EffectVtable FshifterEffectVtable; +extern const EffectVtable ModulatorEffectVtable; +extern const EffectVtable PshifterEffectVtable; +extern const EffectVtable VmorpherEffectVtable; +extern const EffectVtable DedicatedEffectVtable; +extern const EffectVtable ConvolutionEffectVtable; + + +#ifdef ALSOFT_EAX +EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type); +#endif // ALSOFT_EAX + +#endif /* AL_EFFECTS_EFFECTS_H */ diff --git a/modules/openal-soft/al/effects/equalizer.cpp b/modules/openal-soft/al/effects/equalizer.cpp new file mode 100644 index 0000000..f829328 --- /dev/null +++ b/modules/openal-soft/al/effects/equalizer.cpp @@ -0,0 +1,921 @@ + +#include "config.h" + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + + +namespace { + +void Equalizer_setParami(EffectProps*, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } +void Equalizer_setParamiv(EffectProps*, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", + param}; +} +void Equalizer_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_EQUALIZER_LOW_GAIN: + if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band gain out of range"}; + props->Equalizer.LowGain = val; + break; + + case AL_EQUALIZER_LOW_CUTOFF: + if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band cutoff out of range"}; + props->Equalizer.LowCutoff = val; + break; + + case AL_EQUALIZER_MID1_GAIN: + if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band gain out of range"}; + props->Equalizer.Mid1Gain = val; + break; + + case AL_EQUALIZER_MID1_CENTER: + if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band center out of range"}; + props->Equalizer.Mid1Center = val; + break; + + case AL_EQUALIZER_MID1_WIDTH: + if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band width out of range"}; + props->Equalizer.Mid1Width = val; + break; + + case AL_EQUALIZER_MID2_GAIN: + if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band gain out of range"}; + props->Equalizer.Mid2Gain = val; + break; + + case AL_EQUALIZER_MID2_CENTER: + if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band center out of range"}; + props->Equalizer.Mid2Center = val; + break; + + case AL_EQUALIZER_MID2_WIDTH: + if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band width out of range"}; + props->Equalizer.Mid2Width = val; + break; + + case AL_EQUALIZER_HIGH_GAIN: + if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band gain out of range"}; + props->Equalizer.HighGain = val; + break; + + case AL_EQUALIZER_HIGH_CUTOFF: + if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band cutoff out of range"}; + props->Equalizer.HighCutoff = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; + } +} +void Equalizer_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Equalizer_setParamf(props, param, vals[0]); } + +void Equalizer_getParami(const EffectProps*, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } +void Equalizer_getParamiv(const EffectProps*, ALenum param, int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", + param}; +} +void Equalizer_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_EQUALIZER_LOW_GAIN: + *val = props->Equalizer.LowGain; + break; + + case AL_EQUALIZER_LOW_CUTOFF: + *val = props->Equalizer.LowCutoff; + break; + + case AL_EQUALIZER_MID1_GAIN: + *val = props->Equalizer.Mid1Gain; + break; + + case AL_EQUALIZER_MID1_CENTER: + *val = props->Equalizer.Mid1Center; + break; + + case AL_EQUALIZER_MID1_WIDTH: + *val = props->Equalizer.Mid1Width; + break; + + case AL_EQUALIZER_MID2_GAIN: + *val = props->Equalizer.Mid2Gain; + break; + + case AL_EQUALIZER_MID2_CENTER: + *val = props->Equalizer.Mid2Center; + break; + + case AL_EQUALIZER_MID2_WIDTH: + *val = props->Equalizer.Mid2Width; + break; + + case AL_EQUALIZER_HIGH_GAIN: + *val = props->Equalizer.HighGain; + break; + + case AL_EQUALIZER_HIGH_CUTOFF: + *val = props->Equalizer.HighCutoff; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; + } +} +void Equalizer_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Equalizer_getParamf(props, param, vals); } + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF; + props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN; + props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER; + props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN; + props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH; + props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER; + props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN; + props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH; + props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF; + props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Equalizer); + +const EffectProps EqualizerEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxEqualizerEffectDirtyFlagsValue = std::uint_least16_t; + +struct EaxEqualizerEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxEqualizerEffectDirtyFlagsValue lLowGain : 1; + EaxEqualizerEffectDirtyFlagsValue flLowCutOff : 1; + EaxEqualizerEffectDirtyFlagsValue lMid1Gain : 1; + EaxEqualizerEffectDirtyFlagsValue flMid1Center : 1; + EaxEqualizerEffectDirtyFlagsValue flMid1Width : 1; + EaxEqualizerEffectDirtyFlagsValue lMid2Gain : 1; + EaxEqualizerEffectDirtyFlagsValue flMid2Center : 1; + EaxEqualizerEffectDirtyFlagsValue flMid2Width : 1; + EaxEqualizerEffectDirtyFlagsValue lHighGain : 1; + EaxEqualizerEffectDirtyFlagsValue flHighCutOff : 1; +}; // EaxEqualizerEffectDirtyFlags + + +class EaxEqualizerEffect final : + public EaxEffect +{ +public: + EaxEqualizerEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXEQUALIZERPROPERTIES eax_{}; + EAXEQUALIZERPROPERTIES eax_d_{}; + EaxEqualizerEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_low_gain(); + void set_efx_low_cutoff(); + void set_efx_mid1_gain(); + void set_efx_mid1_center(); + void set_efx_mid1_width(); + void set_efx_mid2_gain(); + void set_efx_mid2_center(); + void set_efx_mid2_width(); + void set_efx_high_gain(); + void set_efx_high_cutoff(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_low_gain(long lLowGain); + void validate_low_cutoff(float flLowCutOff); + void validate_mid1_gain(long lMid1Gain); + void validate_mid1_center(float flMid1Center); + void validate_mid1_width(float flMid1Width); + void validate_mid2_gain(long lMid2Gain); + void validate_mid2_center(float flMid2Center); + void validate_mid2_width(float flMid2Width); + void validate_high_gain(long lHighGain); + void validate_high_cutoff(float flHighCutOff); + void validate_all(const EAXEQUALIZERPROPERTIES& all); + + void defer_low_gain(long lLowGain); + void defer_low_cutoff(float flLowCutOff); + void defer_mid1_gain(long lMid1Gain); + void defer_mid1_center(float flMid1Center); + void defer_mid1_width(float flMid1Width); + void defer_mid2_gain(long lMid2Gain); + void defer_mid2_center(float flMid2Center); + void defer_mid2_width(float flMid2Width); + void defer_high_gain(long lHighGain); + void defer_high_cutoff(float flHighCutOff); + void defer_all(const EAXEQUALIZERPROPERTIES& all); + + void defer_low_gain(const EaxEaxCall& eax_call); + void defer_low_cutoff(const EaxEaxCall& eax_call); + void defer_mid1_gain(const EaxEaxCall& eax_call); + void defer_mid1_center(const EaxEaxCall& eax_call); + void defer_mid1_width(const EaxEaxCall& eax_call); + void defer_mid2_gain(const EaxEaxCall& eax_call); + void defer_mid2_center(const EaxEaxCall& eax_call); + void defer_mid2_width(const EaxEaxCall& eax_call); + void defer_high_gain(const EaxEaxCall& eax_call); + void defer_high_cutoff(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxEqualizerEffect + + +class EaxEqualizerEffectException : + public EaxException +{ +public: + explicit EaxEqualizerEffectException( + const char* message) + : + EaxException{"EAX_EQUALIZER_EFFECT", message} + { + } +}; // EaxEqualizerEffectException + + +EaxEqualizerEffect::EaxEqualizerEffect() + : EaxEffect{AL_EFFECT_EQUALIZER} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxEqualizerEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxEqualizerEffect::set_eax_defaults() +{ + eax_.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN; + eax_.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF; + eax_.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN; + eax_.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER; + eax_.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH; + eax_.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN; + eax_.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER; + eax_.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH; + eax_.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN; + eax_.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF; + + eax_d_ = eax_; +} + +void EaxEqualizerEffect::set_efx_low_gain() +{ + const auto low_gain = clamp( + level_mb_to_gain(static_cast(eax_.lLowGain)), + AL_EQUALIZER_MIN_LOW_GAIN, + AL_EQUALIZER_MAX_LOW_GAIN); + + al_effect_props_.Equalizer.LowGain = low_gain; +} + +void EaxEqualizerEffect::set_efx_low_cutoff() +{ + const auto low_cutoff = clamp( + eax_.flLowCutOff, + AL_EQUALIZER_MIN_LOW_CUTOFF, + AL_EQUALIZER_MAX_LOW_CUTOFF); + + al_effect_props_.Equalizer.LowCutoff = low_cutoff; +} + +void EaxEqualizerEffect::set_efx_mid1_gain() +{ + const auto mid1_gain = clamp( + level_mb_to_gain(static_cast(eax_.lMid1Gain)), + AL_EQUALIZER_MIN_MID1_GAIN, + AL_EQUALIZER_MAX_MID1_GAIN); + + al_effect_props_.Equalizer.Mid1Gain = mid1_gain; +} + +void EaxEqualizerEffect::set_efx_mid1_center() +{ + const auto mid1_center = clamp( + eax_.flMid1Center, + AL_EQUALIZER_MIN_MID1_CENTER, + AL_EQUALIZER_MAX_MID1_CENTER); + + al_effect_props_.Equalizer.Mid1Center = mid1_center; +} + +void EaxEqualizerEffect::set_efx_mid1_width() +{ + const auto mid1_width = clamp( + eax_.flMid1Width, + AL_EQUALIZER_MIN_MID1_WIDTH, + AL_EQUALIZER_MAX_MID1_WIDTH); + + al_effect_props_.Equalizer.Mid1Width = mid1_width; +} + +void EaxEqualizerEffect::set_efx_mid2_gain() +{ + const auto mid2_gain = clamp( + level_mb_to_gain(static_cast(eax_.lMid2Gain)), + AL_EQUALIZER_MIN_MID2_GAIN, + AL_EQUALIZER_MAX_MID2_GAIN); + + al_effect_props_.Equalizer.Mid2Gain = mid2_gain; +} + +void EaxEqualizerEffect::set_efx_mid2_center() +{ + const auto mid2_center = clamp( + eax_.flMid2Center, + AL_EQUALIZER_MIN_MID2_CENTER, + AL_EQUALIZER_MAX_MID2_CENTER); + + al_effect_props_.Equalizer.Mid2Center = mid2_center; +} + +void EaxEqualizerEffect::set_efx_mid2_width() +{ + const auto mid2_width = clamp( + eax_.flMid2Width, + AL_EQUALIZER_MIN_MID2_WIDTH, + AL_EQUALIZER_MAX_MID2_WIDTH); + + al_effect_props_.Equalizer.Mid2Width = mid2_width; +} + +void EaxEqualizerEffect::set_efx_high_gain() +{ + const auto high_gain = clamp( + level_mb_to_gain(static_cast(eax_.lHighGain)), + AL_EQUALIZER_MIN_HIGH_GAIN, + AL_EQUALIZER_MAX_HIGH_GAIN); + + al_effect_props_.Equalizer.HighGain = high_gain; +} + +void EaxEqualizerEffect::set_efx_high_cutoff() +{ + const auto high_cutoff = clamp( + eax_.flHighCutOff, + AL_EQUALIZER_MIN_HIGH_CUTOFF, + AL_EQUALIZER_MAX_HIGH_CUTOFF); + + al_effect_props_.Equalizer.HighCutoff = high_cutoff; +} + +void EaxEqualizerEffect::set_efx_defaults() +{ + set_efx_low_gain(); + set_efx_low_cutoff(); + set_efx_mid1_gain(); + set_efx_mid1_center(); + set_efx_mid1_width(); + set_efx_mid2_gain(); + set_efx_mid2_center(); + set_efx_mid2_width(); + set_efx_high_gain(); + set_efx_high_cutoff(); +} + +void EaxEqualizerEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXEQUALIZER_NONE: + break; + + case EAXEQUALIZER_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXEQUALIZER_LOWGAIN: + eax_call.set_value(eax_.lLowGain); + break; + + case EAXEQUALIZER_LOWCUTOFF: + eax_call.set_value(eax_.flLowCutOff); + break; + + case EAXEQUALIZER_MID1GAIN: + eax_call.set_value(eax_.lMid1Gain); + break; + + case EAXEQUALIZER_MID1CENTER: + eax_call.set_value(eax_.flMid1Center); + break; + + case EAXEQUALIZER_MID1WIDTH: + eax_call.set_value(eax_.flMid1Width); + break; + + case EAXEQUALIZER_MID2GAIN: + eax_call.set_value(eax_.lMid2Gain); + break; + + case EAXEQUALIZER_MID2CENTER: + eax_call.set_value(eax_.flMid2Center); + break; + + case EAXEQUALIZER_MID2WIDTH: + eax_call.set_value(eax_.flMid2Width); + break; + + case EAXEQUALIZER_HIGHGAIN: + eax_call.set_value(eax_.lHighGain); + break; + + case EAXEQUALIZER_HIGHCUTOFF: + eax_call.set_value(eax_.flHighCutOff); + break; + + default: + throw EaxEqualizerEffectException{"Unsupported property id."}; + } +} + +void EaxEqualizerEffect::validate_low_gain( + long lLowGain) +{ + eax_validate_range( + "Low Gain", + lLowGain, + EAXEQUALIZER_MINLOWGAIN, + EAXEQUALIZER_MAXLOWGAIN); +} + +void EaxEqualizerEffect::validate_low_cutoff( + float flLowCutOff) +{ + eax_validate_range( + "Low Cutoff", + flLowCutOff, + EAXEQUALIZER_MINLOWCUTOFF, + EAXEQUALIZER_MAXLOWCUTOFF); +} + +void EaxEqualizerEffect::validate_mid1_gain( + long lMid1Gain) +{ + eax_validate_range( + "Mid1 Gain", + lMid1Gain, + EAXEQUALIZER_MINMID1GAIN, + EAXEQUALIZER_MAXMID1GAIN); +} + +void EaxEqualizerEffect::validate_mid1_center( + float flMid1Center) +{ + eax_validate_range( + "Mid1 Center", + flMid1Center, + EAXEQUALIZER_MINMID1CENTER, + EAXEQUALIZER_MAXMID1CENTER); +} + +void EaxEqualizerEffect::validate_mid1_width( + float flMid1Width) +{ + eax_validate_range( + "Mid1 Width", + flMid1Width, + EAXEQUALIZER_MINMID1WIDTH, + EAXEQUALIZER_MAXMID1WIDTH); +} + +void EaxEqualizerEffect::validate_mid2_gain( + long lMid2Gain) +{ + eax_validate_range( + "Mid2 Gain", + lMid2Gain, + EAXEQUALIZER_MINMID2GAIN, + EAXEQUALIZER_MAXMID2GAIN); +} + +void EaxEqualizerEffect::validate_mid2_center( + float flMid2Center) +{ + eax_validate_range( + "Mid2 Center", + flMid2Center, + EAXEQUALIZER_MINMID2CENTER, + EAXEQUALIZER_MAXMID2CENTER); +} + +void EaxEqualizerEffect::validate_mid2_width( + float flMid2Width) +{ + eax_validate_range( + "Mid2 Width", + flMid2Width, + EAXEQUALIZER_MINMID2WIDTH, + EAXEQUALIZER_MAXMID2WIDTH); +} + +void EaxEqualizerEffect::validate_high_gain( + long lHighGain) +{ + eax_validate_range( + "High Gain", + lHighGain, + EAXEQUALIZER_MINHIGHGAIN, + EAXEQUALIZER_MAXHIGHGAIN); +} + +void EaxEqualizerEffect::validate_high_cutoff( + float flHighCutOff) +{ + eax_validate_range( + "High Cutoff", + flHighCutOff, + EAXEQUALIZER_MINHIGHCUTOFF, + EAXEQUALIZER_MAXHIGHCUTOFF); +} + +void EaxEqualizerEffect::validate_all( + const EAXEQUALIZERPROPERTIES& all) +{ + validate_low_gain(all.lLowGain); + validate_low_cutoff(all.flLowCutOff); + validate_mid1_gain(all.lMid1Gain); + validate_mid1_center(all.flMid1Center); + validate_mid1_width(all.flMid1Width); + validate_mid2_gain(all.lMid2Gain); + validate_mid2_center(all.flMid2Center); + validate_mid2_width(all.flMid2Width); + validate_high_gain(all.lHighGain); + validate_high_cutoff(all.flHighCutOff); +} + +void EaxEqualizerEffect::defer_low_gain( + long lLowGain) +{ + eax_d_.lLowGain = lLowGain; + eax_dirty_flags_.lLowGain = (eax_.lLowGain != eax_d_.lLowGain); +} + +void EaxEqualizerEffect::defer_low_cutoff( + float flLowCutOff) +{ + eax_d_.flLowCutOff = flLowCutOff; + eax_dirty_flags_.flLowCutOff = (eax_.flLowCutOff != eax_d_.flLowCutOff); +} + +void EaxEqualizerEffect::defer_mid1_gain( + long lMid1Gain) +{ + eax_d_.lMid1Gain = lMid1Gain; + eax_dirty_flags_.lMid1Gain = (eax_.lMid1Gain != eax_d_.lMid1Gain); +} + +void EaxEqualizerEffect::defer_mid1_center( + float flMid1Center) +{ + eax_d_.flMid1Center = flMid1Center; + eax_dirty_flags_.flMid1Center = (eax_.flMid1Center != eax_d_.flMid1Center); +} + +void EaxEqualizerEffect::defer_mid1_width( + float flMid1Width) +{ + eax_d_.flMid1Width = flMid1Width; + eax_dirty_flags_.flMid1Width = (eax_.flMid1Width != eax_d_.flMid1Width); +} + +void EaxEqualizerEffect::defer_mid2_gain( + long lMid2Gain) +{ + eax_d_.lMid2Gain = lMid2Gain; + eax_dirty_flags_.lMid2Gain = (eax_.lMid2Gain != eax_d_.lMid2Gain); +} + +void EaxEqualizerEffect::defer_mid2_center( + float flMid2Center) +{ + eax_d_.flMid2Center = flMid2Center; + eax_dirty_flags_.flMid2Center = (eax_.flMid2Center != eax_d_.flMid2Center); +} + +void EaxEqualizerEffect::defer_mid2_width( + float flMid2Width) +{ + eax_d_.flMid2Width = flMid2Width; + eax_dirty_flags_.flMid2Width = (eax_.flMid2Width != eax_d_.flMid2Width); +} + +void EaxEqualizerEffect::defer_high_gain( + long lHighGain) +{ + eax_d_.lHighGain = lHighGain; + eax_dirty_flags_.lHighGain = (eax_.lHighGain != eax_d_.lHighGain); +} + +void EaxEqualizerEffect::defer_high_cutoff( + float flHighCutOff) +{ + eax_d_.flHighCutOff = flHighCutOff; + eax_dirty_flags_.flHighCutOff = (eax_.flHighCutOff != eax_d_.flHighCutOff); +} + +void EaxEqualizerEffect::defer_all( + const EAXEQUALIZERPROPERTIES& all) +{ + defer_low_gain(all.lLowGain); + defer_low_cutoff(all.flLowCutOff); + defer_mid1_gain(all.lMid1Gain); + defer_mid1_center(all.flMid1Center); + defer_mid1_width(all.flMid1Width); + defer_mid2_gain(all.lMid2Gain); + defer_mid2_center(all.flMid2Center); + defer_mid2_width(all.flMid2Width); + defer_high_gain(all.lHighGain); + defer_high_cutoff(all.flHighCutOff); +} + +void EaxEqualizerEffect::defer_low_gain( + const EaxEaxCall& eax_call) +{ + const auto& low_gain = + eax_call.get_value(); + + validate_low_gain(low_gain); + defer_low_gain(low_gain); +} + +void EaxEqualizerEffect::defer_low_cutoff( + const EaxEaxCall& eax_call) +{ + const auto& low_cutoff = + eax_call.get_value(); + + validate_low_cutoff(low_cutoff); + defer_low_cutoff(low_cutoff); +} + +void EaxEqualizerEffect::defer_mid1_gain( + const EaxEaxCall& eax_call) +{ + const auto& mid1_gain = + eax_call.get_value(); + + validate_mid1_gain(mid1_gain); + defer_mid1_gain(mid1_gain); +} + +void EaxEqualizerEffect::defer_mid1_center( + const EaxEaxCall& eax_call) +{ + const auto& mid1_center = + eax_call.get_value(); + + validate_mid1_center(mid1_center); + defer_mid1_center(mid1_center); +} + +void EaxEqualizerEffect::defer_mid1_width( + const EaxEaxCall& eax_call) +{ + const auto& mid1_width = + eax_call.get_value(); + + validate_mid1_width(mid1_width); + defer_mid1_width(mid1_width); +} + +void EaxEqualizerEffect::defer_mid2_gain( + const EaxEaxCall& eax_call) +{ + const auto& mid2_gain = + eax_call.get_value(); + + validate_mid2_gain(mid2_gain); + defer_mid2_gain(mid2_gain); +} + +void EaxEqualizerEffect::defer_mid2_center( + const EaxEaxCall& eax_call) +{ + const auto& mid2_center = + eax_call.get_value(); + + validate_mid2_center(mid2_center); + defer_mid2_center(mid2_center); +} + +void EaxEqualizerEffect::defer_mid2_width( + const EaxEaxCall& eax_call) +{ + const auto& mid2_width = + eax_call.get_value(); + + validate_mid2_width(mid2_width); + defer_mid2_width(mid2_width); +} + +void EaxEqualizerEffect::defer_high_gain( + const EaxEaxCall& eax_call) +{ + const auto& high_gain = + eax_call.get_value(); + + validate_high_gain(high_gain); + defer_high_gain(high_gain); +} + +void EaxEqualizerEffect::defer_high_cutoff( + const EaxEaxCall& eax_call) +{ + const auto& high_cutoff = + eax_call.get_value(); + + validate_high_cutoff(high_cutoff); + defer_high_cutoff(high_cutoff); +} + +void EaxEqualizerEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxEqualizerEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxEqualizerEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.lLowGain) + { + set_efx_low_gain(); + } + + if (eax_dirty_flags_.flLowCutOff) + { + set_efx_low_cutoff(); + } + + if (eax_dirty_flags_.lMid1Gain) + { + set_efx_mid1_gain(); + } + + if (eax_dirty_flags_.flMid1Center) + { + set_efx_mid1_center(); + } + + if (eax_dirty_flags_.flMid1Width) + { + set_efx_mid1_width(); + } + + if (eax_dirty_flags_.lMid2Gain) + { + set_efx_mid2_gain(); + } + + if (eax_dirty_flags_.flMid2Center) + { + set_efx_mid2_center(); + } + + if (eax_dirty_flags_.flMid2Width) + { + set_efx_mid2_width(); + } + + if (eax_dirty_flags_.lHighGain) + { + set_efx_high_gain(); + } + + if (eax_dirty_flags_.flHighCutOff) + { + set_efx_high_cutoff(); + } + + eax_dirty_flags_ = EaxEqualizerEffectDirtyFlags{}; + + return true; +} + +void EaxEqualizerEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXEQUALIZER_NONE: + break; + + case EAXEQUALIZER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXEQUALIZER_LOWGAIN: + defer_low_gain(eax_call); + break; + + case EAXEQUALIZER_LOWCUTOFF: + defer_low_cutoff(eax_call); + break; + + case EAXEQUALIZER_MID1GAIN: + defer_mid1_gain(eax_call); + break; + + case EAXEQUALIZER_MID1CENTER: + defer_mid1_center(eax_call); + break; + + case EAXEQUALIZER_MID1WIDTH: + defer_mid1_width(eax_call); + break; + + case EAXEQUALIZER_MID2GAIN: + defer_mid2_gain(eax_call); + break; + + case EAXEQUALIZER_MID2CENTER: + defer_mid2_center(eax_call); + break; + + case EAXEQUALIZER_MID2WIDTH: + defer_mid2_width(eax_call); + break; + + case EAXEQUALIZER_HIGHGAIN: + defer_high_gain(eax_call); + break; + + case EAXEQUALIZER_HIGHCUTOFF: + defer_high_cutoff(eax_call); + break; + + default: + throw EaxEqualizerEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_equalizer_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/effects/fshifter.cpp b/modules/openal-soft/al/effects/fshifter.cpp new file mode 100644 index 0000000..d334890 --- /dev/null +++ b/modules/openal-soft/al/effects/fshifter.cpp @@ -0,0 +1,479 @@ + +#include "config.h" + +#include + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "aloptional.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include + +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + + +namespace { + +al::optional DirectionFromEmum(ALenum value) +{ + switch(value) + { + case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN: return al::make_optional(FShifterDirection::Down); + case AL_FREQUENCY_SHIFTER_DIRECTION_UP: return al::make_optional(FShifterDirection::Up); + case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: return al::make_optional(FShifterDirection::Off); + } + return al::nullopt; +} +ALenum EnumFromDirection(FShifterDirection dir) +{ + switch(dir) + { + case FShifterDirection::Down: return AL_FREQUENCY_SHIFTER_DIRECTION_DOWN; + 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))}; +} + +void Fshifter_setParamf(EffectProps *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"}; + props->Fshifter.Frequency = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", + param}; + } +} +void Fshifter_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Fshifter_setParamf(props, param, vals[0]); } + +void Fshifter_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: + if(auto diropt = DirectionFromEmum(val)) + props->Fshifter.LeftDirection = *diropt; + else + throw effect_exception{AL_INVALID_VALUE, + "Unsupported frequency shifter left direction: 0x%04x", val}; + break; + + case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: + if(auto diropt = DirectionFromEmum(val)) + props->Fshifter.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}; + } +} +void Fshifter_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ Fshifter_setParami(props, param, vals[0]); } + +void Fshifter_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: + *val = EnumFromDirection(props->Fshifter.LeftDirection); + break; + case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: + *val = EnumFromDirection(props->Fshifter.RightDirection); + break; + default: + throw effect_exception{AL_INVALID_ENUM, + "Invalid frequency shifter integer property 0x%04x", param}; + } +} +void Fshifter_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ Fshifter_getParami(props, param, vals); } + +void Fshifter_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_FREQUENCY_SHIFTER_FREQUENCY: + *val = props->Fshifter.Frequency; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", + param}; + } +} +void Fshifter_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Fshifter_getParamf(props, param, vals); } + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY; + props.Fshifter.LeftDirection = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION); + props.Fshifter.RightDirection = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION); + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Fshifter); + +const EffectProps FshifterEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxFrequencyShifterEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxFrequencyShifterEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxFrequencyShifterEffectDirtyFlagsValue flFrequency : 1; + EaxFrequencyShifterEffectDirtyFlagsValue ulLeftDirection : 1; + EaxFrequencyShifterEffectDirtyFlagsValue ulRightDirection : 1; +}; // EaxFrequencyShifterEffectDirtyFlags + + +class EaxFrequencyShifterEffect final : + public EaxEffect +{ +public: + EaxFrequencyShifterEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXFREQUENCYSHIFTERPROPERTIES eax_{}; + EAXFREQUENCYSHIFTERPROPERTIES eax_d_{}; + EaxFrequencyShifterEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_frequency(); + void set_efx_left_direction(); + void set_efx_right_direction(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_frequency(float flFrequency); + void validate_left_direction(unsigned long ulLeftDirection); + void validate_right_direction(unsigned long ulRightDirection); + void validate_all(const EAXFREQUENCYSHIFTERPROPERTIES& all); + + void defer_frequency(float flFrequency); + void defer_left_direction(unsigned long ulLeftDirection); + void defer_right_direction(unsigned long ulRightDirection); + void defer_all(const EAXFREQUENCYSHIFTERPROPERTIES& all); + + void defer_frequency(const EaxEaxCall& eax_call); + void defer_left_direction(const EaxEaxCall& eax_call); + void defer_right_direction(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxFrequencyShifterEffect + + +class EaxFrequencyShifterEffectException : + public EaxException +{ +public: + explicit EaxFrequencyShifterEffectException( + const char* message) + : + EaxException{"EAX_FREQUENCY_SHIFTER_EFFECT", message} + { + } +}; // EaxFrequencyShifterEffectException + + +EaxFrequencyShifterEffect::EaxFrequencyShifterEffect() + : EaxEffect{AL_EFFECT_FREQUENCY_SHIFTER} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxFrequencyShifterEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxFrequencyShifterEffect::set_eax_defaults() +{ + eax_.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY; + eax_.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION; + eax_.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION; + + eax_d_ = eax_; +} + +void EaxFrequencyShifterEffect::set_efx_frequency() +{ + const auto frequency = clamp( + eax_.flFrequency, + AL_FREQUENCY_SHIFTER_MIN_FREQUENCY, + AL_FREQUENCY_SHIFTER_MAX_FREQUENCY); + + al_effect_props_.Fshifter.Frequency = frequency; +} + +void EaxFrequencyShifterEffect::set_efx_left_direction() +{ + const auto left_direction = clamp( + static_cast(eax_.ulLeftDirection), + AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION, + AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION); + + const auto efx_left_direction = DirectionFromEmum(left_direction); + assert(efx_left_direction.has_value()); + al_effect_props_.Fshifter.LeftDirection = *efx_left_direction; +} + +void EaxFrequencyShifterEffect::set_efx_right_direction() +{ + const auto right_direction = clamp( + static_cast(eax_.ulRightDirection), + AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION, + AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION); + + const auto efx_right_direction = DirectionFromEmum(right_direction); + assert(efx_right_direction.has_value()); + al_effect_props_.Fshifter.RightDirection = *efx_right_direction; +} + +void EaxFrequencyShifterEffect::set_efx_defaults() +{ + set_efx_frequency(); + set_efx_left_direction(); + set_efx_right_direction(); +} + +void EaxFrequencyShifterEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXFREQUENCYSHIFTER_NONE: + break; + + case EAXFREQUENCYSHIFTER_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXFREQUENCYSHIFTER_FREQUENCY: + eax_call.set_value(eax_.flFrequency); + break; + + case EAXFREQUENCYSHIFTER_LEFTDIRECTION: + eax_call.set_value(eax_.ulLeftDirection); + break; + + case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: + eax_call.set_value(eax_.ulRightDirection); + break; + + default: + throw EaxFrequencyShifterEffectException{"Unsupported property id."}; + } +} + +void EaxFrequencyShifterEffect::validate_frequency( + float flFrequency) +{ + eax_validate_range( + "Frequency", + flFrequency, + EAXFREQUENCYSHIFTER_MINFREQUENCY, + EAXFREQUENCYSHIFTER_MAXFREQUENCY); +} + +void EaxFrequencyShifterEffect::validate_left_direction( + unsigned long ulLeftDirection) +{ + eax_validate_range( + "Left Direction", + ulLeftDirection, + EAXFREQUENCYSHIFTER_MINLEFTDIRECTION, + EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION); +} + +void EaxFrequencyShifterEffect::validate_right_direction( + unsigned long ulRightDirection) +{ + eax_validate_range( + "Right Direction", + ulRightDirection, + EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION, + EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION); +} + +void EaxFrequencyShifterEffect::validate_all( + const EAXFREQUENCYSHIFTERPROPERTIES& all) +{ + validate_frequency(all.flFrequency); + validate_left_direction(all.ulLeftDirection); + validate_right_direction(all.ulRightDirection); +} + +void EaxFrequencyShifterEffect::defer_frequency( + float flFrequency) +{ + eax_d_.flFrequency = flFrequency; + eax_dirty_flags_.flFrequency = (eax_.flFrequency != eax_d_.flFrequency); +} + +void EaxFrequencyShifterEffect::defer_left_direction( + unsigned long ulLeftDirection) +{ + eax_d_.ulLeftDirection = ulLeftDirection; + eax_dirty_flags_.ulLeftDirection = (eax_.ulLeftDirection != eax_d_.ulLeftDirection); +} + +void EaxFrequencyShifterEffect::defer_right_direction( + unsigned long ulRightDirection) +{ + eax_d_.ulRightDirection = ulRightDirection; + eax_dirty_flags_.ulRightDirection = (eax_.ulRightDirection != eax_d_.ulRightDirection); +} + +void EaxFrequencyShifterEffect::defer_all( + const EAXFREQUENCYSHIFTERPROPERTIES& all) +{ + defer_frequency(all.flFrequency); + defer_left_direction(all.ulLeftDirection); + defer_right_direction(all.ulRightDirection); +} + +void EaxFrequencyShifterEffect::defer_frequency( + const EaxEaxCall& eax_call) +{ + const auto& frequency = + eax_call.get_value< + EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::flFrequency)>(); + + validate_frequency(frequency); + defer_frequency(frequency); +} + +void EaxFrequencyShifterEffect::defer_left_direction( + const EaxEaxCall& eax_call) +{ + const auto& left_direction = + eax_call.get_value< + EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::ulLeftDirection)>(); + + validate_left_direction(left_direction); + defer_left_direction(left_direction); +} + +void EaxFrequencyShifterEffect::defer_right_direction( + const EaxEaxCall& eax_call) +{ + const auto& right_direction = + eax_call.get_value< + EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::ulRightDirection)>(); + + validate_right_direction(right_direction); + defer_right_direction(right_direction); +} + +void EaxFrequencyShifterEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value< + EaxFrequencyShifterEffectException, const EAXFREQUENCYSHIFTERPROPERTIES>(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxFrequencyShifterEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxFrequencyShifterEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flFrequency) + { + set_efx_frequency(); + } + + if (eax_dirty_flags_.ulLeftDirection) + { + set_efx_left_direction(); + } + + if (eax_dirty_flags_.ulRightDirection) + { + set_efx_right_direction(); + } + + eax_dirty_flags_ = EaxFrequencyShifterEffectDirtyFlags{}; + + return true; +} + +void EaxFrequencyShifterEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXFREQUENCYSHIFTER_NONE: + break; + + case EAXFREQUENCYSHIFTER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXFREQUENCYSHIFTER_FREQUENCY: + defer_frequency(eax_call); + break; + + case EAXFREQUENCYSHIFTER_LEFTDIRECTION: + defer_left_direction(eax_call); + break; + + case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: + defer_right_direction(eax_call); + break; + + default: + throw EaxFrequencyShifterEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_frequency_shifter_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/effects/modulator.cpp b/modules/openal-soft/al/effects/modulator.cpp new file mode 100644 index 0000000..800b892 --- /dev/null +++ b/modules/openal-soft/al/effects/modulator.cpp @@ -0,0 +1,482 @@ + +#include "config.h" + +#include + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "aloptional.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include + +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + + +namespace { + +al::optional WaveformFromEmum(ALenum value) +{ + switch(value) + { + case AL_RING_MODULATOR_SINUSOID: return al::make_optional(ModulatorWaveform::Sinusoid); + case AL_RING_MODULATOR_SAWTOOTH: return al::make_optional(ModulatorWaveform::Sawtooth); + case AL_RING_MODULATOR_SQUARE: return al::make_optional(ModulatorWaveform::Square); + } + return al::nullopt; +} +ALenum EnumFromWaveform(ModulatorWaveform type) +{ + switch(type) + { + case ModulatorWaveform::Sinusoid: return AL_RING_MODULATOR_SINUSOID; + 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))}; +} + +void Modulator_setParamf(EffectProps *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}; + props->Modulator.Frequency = val; + break; + + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)) + throw effect_exception{AL_INVALID_VALUE, "Modulator high-pass cutoff out of range: %f", val}; + props->Modulator.HighPassCutoff = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param}; + } +} +void Modulator_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Modulator_setParamf(props, param, vals[0]); } +void Modulator_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + Modulator_setParamf(props, param, static_cast(val)); + break; + + case AL_RING_MODULATOR_WAVEFORM: + if(auto formopt = WaveformFromEmum(val)) + props->Modulator.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}; + } +} +void Modulator_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ Modulator_setParami(props, param, vals[0]); } + +void Modulator_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + *val = static_cast(props->Modulator.Frequency); + break; + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + *val = static_cast(props->Modulator.HighPassCutoff); + break; + case AL_RING_MODULATOR_WAVEFORM: + *val = EnumFromWaveform(props->Modulator.Waveform); + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", + param}; + } +} +void Modulator_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ Modulator_getParami(props, param, vals); } +void Modulator_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + *val = props->Modulator.Frequency; + break; + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + *val = props->Modulator.HighPassCutoff; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param}; + } +} +void Modulator_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Modulator_getParamf(props, param, vals); } + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY; + props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF; + props.Modulator.Waveform = *WaveformFromEmum(AL_RING_MODULATOR_DEFAULT_WAVEFORM); + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Modulator); + +const EffectProps ModulatorEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxRingModulatorEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxRingModulatorEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxRingModulatorEffectDirtyFlagsValue flFrequency : 1; + EaxRingModulatorEffectDirtyFlagsValue flHighPassCutOff : 1; + EaxRingModulatorEffectDirtyFlagsValue ulWaveform : 1; +}; // EaxPitchShifterEffectDirtyFlags + + +class EaxRingModulatorEffect final : + public EaxEffect +{ +public: + EaxRingModulatorEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXRINGMODULATORPROPERTIES eax_{}; + EAXRINGMODULATORPROPERTIES eax_d_{}; + EaxRingModulatorEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_frequency(); + void set_efx_high_pass_cutoff(); + void set_efx_waveform(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_frequency(float flFrequency); + void validate_high_pass_cutoff(float flHighPassCutOff); + void validate_waveform(unsigned long ulWaveform); + void validate_all(const EAXRINGMODULATORPROPERTIES& all); + + void defer_frequency(float flFrequency); + void defer_high_pass_cutoff(float flHighPassCutOff); + void defer_waveform(unsigned long ulWaveform); + void defer_all(const EAXRINGMODULATORPROPERTIES& all); + + void defer_frequency(const EaxEaxCall& eax_call); + void defer_high_pass_cutoff(const EaxEaxCall& eax_call); + void defer_waveform(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxRingModulatorEffect + + +class EaxRingModulatorEffectException : + public EaxException +{ +public: + explicit EaxRingModulatorEffectException( + const char* message) + : + EaxException{"EAX_RING_MODULATOR_EFFECT", message} + { + } +}; // EaxRingModulatorEffectException + + +EaxRingModulatorEffect::EaxRingModulatorEffect() + : EaxEffect{AL_EFFECT_RING_MODULATOR} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxRingModulatorEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxRingModulatorEffect::set_eax_defaults() +{ + eax_.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY; + eax_.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF; + eax_.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM; + + eax_d_ = eax_; +} + +void EaxRingModulatorEffect::set_efx_frequency() +{ + const auto frequency = clamp( + eax_.flFrequency, + AL_RING_MODULATOR_MIN_FREQUENCY, + AL_RING_MODULATOR_MAX_FREQUENCY); + + al_effect_props_.Modulator.Frequency = frequency; +} + +void EaxRingModulatorEffect::set_efx_high_pass_cutoff() +{ + const auto high_pass_cutoff = clamp( + eax_.flHighPassCutOff, + AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF, + AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF); + + al_effect_props_.Modulator.HighPassCutoff = high_pass_cutoff; +} + +void EaxRingModulatorEffect::set_efx_waveform() +{ + const auto waveform = clamp( + static_cast(eax_.ulWaveform), + AL_RING_MODULATOR_MIN_WAVEFORM, + AL_RING_MODULATOR_MAX_WAVEFORM); + + const auto efx_waveform = WaveformFromEmum(waveform); + assert(efx_waveform.has_value()); + al_effect_props_.Modulator.Waveform = *efx_waveform; +} + +void EaxRingModulatorEffect::set_efx_defaults() +{ + set_efx_frequency(); + set_efx_high_pass_cutoff(); + set_efx_waveform(); +} + +void EaxRingModulatorEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXRINGMODULATOR_NONE: + break; + + case EAXRINGMODULATOR_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXRINGMODULATOR_FREQUENCY: + eax_call.set_value(eax_.flFrequency); + break; + + case EAXRINGMODULATOR_HIGHPASSCUTOFF: + eax_call.set_value(eax_.flHighPassCutOff); + break; + + case EAXRINGMODULATOR_WAVEFORM: + eax_call.set_value(eax_.ulWaveform); + break; + + default: + throw EaxRingModulatorEffectException{"Unsupported property id."}; + } +} + +void EaxRingModulatorEffect::validate_frequency( + float flFrequency) +{ + eax_validate_range( + "Frequency", + flFrequency, + EAXRINGMODULATOR_MINFREQUENCY, + EAXRINGMODULATOR_MAXFREQUENCY); +} + +void EaxRingModulatorEffect::validate_high_pass_cutoff( + float flHighPassCutOff) +{ + eax_validate_range( + "High-Pass Cutoff", + flHighPassCutOff, + EAXRINGMODULATOR_MINHIGHPASSCUTOFF, + EAXRINGMODULATOR_MAXHIGHPASSCUTOFF); +} + +void EaxRingModulatorEffect::validate_waveform( + unsigned long ulWaveform) +{ + eax_validate_range( + "Waveform", + ulWaveform, + EAXRINGMODULATOR_MINWAVEFORM, + EAXRINGMODULATOR_MAXWAVEFORM); +} + +void EaxRingModulatorEffect::validate_all( + const EAXRINGMODULATORPROPERTIES& all) +{ + validate_frequency(all.flFrequency); + validate_high_pass_cutoff(all.flHighPassCutOff); + validate_waveform(all.ulWaveform); +} + +void EaxRingModulatorEffect::defer_frequency( + float flFrequency) +{ + eax_d_.flFrequency = flFrequency; + eax_dirty_flags_.flFrequency = (eax_.flFrequency != eax_d_.flFrequency); +} + +void EaxRingModulatorEffect::defer_high_pass_cutoff( + float flHighPassCutOff) +{ + eax_d_.flHighPassCutOff = flHighPassCutOff; + eax_dirty_flags_.flHighPassCutOff = (eax_.flHighPassCutOff != eax_d_.flHighPassCutOff); +} + +void EaxRingModulatorEffect::defer_waveform( + unsigned long ulWaveform) +{ + eax_d_.ulWaveform = ulWaveform; + eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); +} + +void EaxRingModulatorEffect::defer_all( + const EAXRINGMODULATORPROPERTIES& all) +{ + defer_frequency(all.flFrequency); + defer_high_pass_cutoff(all.flHighPassCutOff); + defer_waveform(all.ulWaveform); +} + +void EaxRingModulatorEffect::defer_frequency( + const EaxEaxCall& eax_call) +{ + const auto& frequency = + eax_call.get_value< + EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::flFrequency)>(); + + validate_frequency(frequency); + defer_frequency(frequency); +} + +void EaxRingModulatorEffect::defer_high_pass_cutoff( + const EaxEaxCall& eax_call) +{ + const auto& high_pass_cutoff = + eax_call.get_value< + EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::flHighPassCutOff)>(); + + validate_high_pass_cutoff(high_pass_cutoff); + defer_high_pass_cutoff(high_pass_cutoff); +} + +void EaxRingModulatorEffect::defer_waveform( + const EaxEaxCall& eax_call) +{ + const auto& waveform = + eax_call.get_value< + EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::ulWaveform)>(); + + validate_waveform(waveform); + defer_waveform(waveform); +} + +void EaxRingModulatorEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxRingModulatorEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxRingModulatorEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flFrequency) + { + set_efx_frequency(); + } + + if (eax_dirty_flags_.flHighPassCutOff) + { + set_efx_high_pass_cutoff(); + } + + if (eax_dirty_flags_.ulWaveform) + { + set_efx_waveform(); + } + + eax_dirty_flags_ = EaxRingModulatorEffectDirtyFlags{}; + + return true; +} + +void EaxRingModulatorEffect::set(const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXRINGMODULATOR_NONE: + break; + + case EAXRINGMODULATOR_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXRINGMODULATOR_FREQUENCY: + defer_frequency(eax_call); + break; + + case EAXRINGMODULATOR_HIGHPASSCUTOFF: + defer_high_pass_cutoff(eax_call); + break; + + case EAXRINGMODULATOR_WAVEFORM: + defer_waveform(eax_call); + break; + + default: + throw EaxRingModulatorEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_ring_modulator_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/effects/null.cpp b/modules/openal-soft/al/effects/null.cpp new file mode 100644 index 0000000..a0eb224 --- /dev/null +++ b/modules/openal-soft/al/effects/null.cpp @@ -0,0 +1,152 @@ + +#include "config.h" + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include "al/eax_exception.h" +#endif // ALSOFT_EAX + + +namespace { + +void Null_setParami(EffectProps* /*props*/, ALenum param, int /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", + param}; + } +} +void Null_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ + switch(param) + { + default: + Null_setParami(props, param, vals[0]); + } +} +void Null_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", + param}; + } +} +void Null_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ + switch(param) + { + default: + Null_setParamf(props, param, vals[0]); + } +} + +void Null_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", + param}; + } +} +void Null_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ + switch(param) + { + default: + Null_getParami(props, param, vals); + } +} +void Null_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", + param}; + } +} +void Null_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ + switch(param) + { + default: + Null_getParamf(props, param, vals); + } +} + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Null); + +const EffectProps NullEffectProps{genDefaultProps()}; + + +#ifdef ALSOFT_EAX +namespace { + +class EaxNullEffect final : + public EaxEffect +{ +public: + EaxNullEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; +}; // EaxNullEffect + + +class EaxNullEffectException : + public EaxException +{ +public: + explicit EaxNullEffectException( + const char* message) + : + EaxException{"EAX_NULL_EFFECT", message} + { + } +}; // EaxNullEffectException + + +EaxNullEffect::EaxNullEffect() + : EaxEffect{AL_EFFECT_NULL} +{ +} + +void EaxNullEffect::dispatch(const EaxEaxCall& eax_call) +{ + if(eax_call.get_property_id() != 0) + throw EaxNullEffectException{"Unsupported property id."}; +} + +bool EaxNullEffect::apply_deferred() +{ + return false; +} + +} // namespace + +EaxEffectUPtr eax_create_eax_null_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/effects/pshifter.cpp b/modules/openal-soft/al/effects/pshifter.cpp new file mode 100644 index 0000000..1b2dcff --- /dev/null +++ b/modules/openal-soft/al/effects/pshifter.cpp @@ -0,0 +1,364 @@ + +#include "config.h" + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + + +namespace { + +void Pshifter_setParamf(EffectProps*, ALenum param, float) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } +void Pshifter_setParamfv(EffectProps*, ALenum param, const float*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", + param}; +} + +void Pshifter_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_PITCH_SHIFTER_COARSE_TUNE: + if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE)) + throw effect_exception{AL_INVALID_VALUE, "Pitch shifter coarse tune out of range"}; + props->Pshifter.CoarseTune = val; + break; + + case AL_PITCH_SHIFTER_FINE_TUNE: + if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE)) + throw effect_exception{AL_INVALID_VALUE, "Pitch shifter fine tune out of range"}; + props->Pshifter.FineTune = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", + param}; + } +} +void Pshifter_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ Pshifter_setParami(props, param, vals[0]); } + +void Pshifter_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_PITCH_SHIFTER_COARSE_TUNE: + *val = props->Pshifter.CoarseTune; + break; + case AL_PITCH_SHIFTER_FINE_TUNE: + *val = props->Pshifter.FineTune; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", + param}; + } +} +void Pshifter_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ Pshifter_getParami(props, param, vals); } + +void Pshifter_getParamf(const EffectProps*, ALenum param, float*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } +void Pshifter_getParamfv(const EffectProps*, ALenum param, float*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", + param}; +} + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE; + props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Pshifter); + +const EffectProps PshifterEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxPitchShifterEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxPitchShifterEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxPitchShifterEffectDirtyFlagsValue lCoarseTune : 1; + EaxPitchShifterEffectDirtyFlagsValue lFineTune : 1; +}; // EaxPitchShifterEffectDirtyFlags + + +class EaxPitchShifterEffect final : + public EaxEffect +{ +public: + EaxPitchShifterEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXPITCHSHIFTERPROPERTIES eax_{}; + EAXPITCHSHIFTERPROPERTIES eax_d_{}; + EaxPitchShifterEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_coarse_tune(); + void set_efx_fine_tune(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_coarse_tune(long lCoarseTune); + void validate_fine_tune(long lFineTune); + void validate_all(const EAXPITCHSHIFTERPROPERTIES& all); + + void defer_coarse_tune(long lCoarseTune); + void defer_fine_tune(long lFineTune); + void defer_all(const EAXPITCHSHIFTERPROPERTIES& all); + + void defer_coarse_tune(const EaxEaxCall& eax_call); + void defer_fine_tune(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxPitchShifterEffect + + +class EaxPitchShifterEffectException : + public EaxException +{ +public: + explicit EaxPitchShifterEffectException( + const char* message) + : + EaxException{"EAX_PITCH_SHIFTER_EFFECT", message} + { + } +}; // EaxPitchShifterEffectException + + +EaxPitchShifterEffect::EaxPitchShifterEffect() + : EaxEffect{AL_EFFECT_PITCH_SHIFTER} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxPitchShifterEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxPitchShifterEffect::set_eax_defaults() +{ + eax_.lCoarseTune = EAXPITCHSHIFTER_DEFAULTCOARSETUNE; + eax_.lFineTune = EAXPITCHSHIFTER_DEFAULTFINETUNE; + + eax_d_ = eax_; +} + +void EaxPitchShifterEffect::set_efx_coarse_tune() +{ + const auto coarse_tune = clamp( + static_cast(eax_.lCoarseTune), + AL_PITCH_SHIFTER_MIN_COARSE_TUNE, + AL_PITCH_SHIFTER_MAX_COARSE_TUNE); + + al_effect_props_.Pshifter.CoarseTune = coarse_tune; +} + +void EaxPitchShifterEffect::set_efx_fine_tune() +{ + const auto fine_tune = clamp( + static_cast(eax_.lFineTune), + AL_PITCH_SHIFTER_MIN_FINE_TUNE, + AL_PITCH_SHIFTER_MAX_FINE_TUNE); + + al_effect_props_.Pshifter.FineTune = fine_tune; +} + +void EaxPitchShifterEffect::set_efx_defaults() +{ + set_efx_coarse_tune(); + set_efx_fine_tune(); +} + +void EaxPitchShifterEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXPITCHSHIFTER_NONE: + break; + + case EAXPITCHSHIFTER_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXPITCHSHIFTER_COARSETUNE: + eax_call.set_value(eax_.lCoarseTune); + break; + + case EAXPITCHSHIFTER_FINETUNE: + eax_call.set_value(eax_.lFineTune); + break; + + default: + throw EaxPitchShifterEffectException{"Unsupported property id."}; + } +} + +void EaxPitchShifterEffect::validate_coarse_tune( + long lCoarseTune) +{ + eax_validate_range( + "Coarse Tune", + lCoarseTune, + EAXPITCHSHIFTER_MINCOARSETUNE, + EAXPITCHSHIFTER_MAXCOARSETUNE); +} + +void EaxPitchShifterEffect::validate_fine_tune( + long lFineTune) +{ + eax_validate_range( + "Fine Tune", + lFineTune, + EAXPITCHSHIFTER_MINFINETUNE, + EAXPITCHSHIFTER_MAXFINETUNE); +} + +void EaxPitchShifterEffect::validate_all( + const EAXPITCHSHIFTERPROPERTIES& all) +{ + validate_coarse_tune(all.lCoarseTune); + validate_fine_tune(all.lFineTune); +} + +void EaxPitchShifterEffect::defer_coarse_tune( + long lCoarseTune) +{ + eax_d_.lCoarseTune = lCoarseTune; + eax_dirty_flags_.lCoarseTune = (eax_.lCoarseTune != eax_d_.lCoarseTune); +} + +void EaxPitchShifterEffect::defer_fine_tune( + long lFineTune) +{ + eax_d_.lFineTune = lFineTune; + eax_dirty_flags_.lFineTune = (eax_.lFineTune != eax_d_.lFineTune); +} + +void EaxPitchShifterEffect::defer_all( + const EAXPITCHSHIFTERPROPERTIES& all) +{ + defer_coarse_tune(all.lCoarseTune); + defer_fine_tune(all.lFineTune); +} + +void EaxPitchShifterEffect::defer_coarse_tune( + const EaxEaxCall& eax_call) +{ + const auto& coarse_tune = + eax_call.get_value(); + + validate_coarse_tune(coarse_tune); + defer_coarse_tune(coarse_tune); +} + +void EaxPitchShifterEffect::defer_fine_tune( + const EaxEaxCall& eax_call) +{ + const auto& fine_tune = + eax_call.get_value(); + + validate_fine_tune(fine_tune); + defer_fine_tune(fine_tune); +} + +void EaxPitchShifterEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxPitchShifterEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxPitchShifterEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.lCoarseTune) + { + set_efx_coarse_tune(); + } + + if (eax_dirty_flags_.lFineTune) + { + set_efx_fine_tune(); + } + + eax_dirty_flags_ = EaxPitchShifterEffectDirtyFlags{}; + + return true; +} + +void EaxPitchShifterEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXPITCHSHIFTER_NONE: + break; + + case EAXPITCHSHIFTER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXPITCHSHIFTER_COARSETUNE: + defer_coarse_tune(eax_call); + break; + + case EAXPITCHSHIFTER_FINETUNE: + defer_fine_tune(eax_call); + break; + + default: + throw EaxPitchShifterEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_pitch_shifter_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/effects/reverb.cpp b/modules/openal-soft/al/effects/reverb.cpp new file mode 100644 index 0000000..197ea50 --- /dev/null +++ b/modules/openal-soft/al/effects/reverb.cpp @@ -0,0 +1,2542 @@ + +#include "config.h" + +#include + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include +#include "alnumeric.h" +#include "AL/efx-presets.h" +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + + +namespace { + +void Reverb_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_EAXREVERB_DECAY_HFLIMIT: + if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"}; + props->Reverb.DecayHFLimit = val != AL_FALSE; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", + param}; + } +} +void Reverb_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ Reverb_setParami(props, param, vals[0]); } +void Reverb_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_EAXREVERB_DENSITY: + if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"}; + props->Reverb.Density = val; + break; + + case AL_EAXREVERB_DIFFUSION: + if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"}; + props->Reverb.Diffusion = val; + break; + + case AL_EAXREVERB_GAIN: + if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"}; + props->Reverb.Gain = val; + break; + + case AL_EAXREVERB_GAINHF: + if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"}; + props->Reverb.GainHF = val; + break; + + case AL_EAXREVERB_GAINLF: + if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainlf out of range"}; + props->Reverb.GainLF = val; + break; + + case AL_EAXREVERB_DECAY_TIME: + if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"}; + props->Reverb.DecayTime = val; + break; + + case AL_EAXREVERB_DECAY_HFRATIO: + if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"}; + props->Reverb.DecayHFRatio = val; + break; + + case AL_EAXREVERB_DECAY_LFRATIO: + if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay lfratio out of range"}; + props->Reverb.DecayLFRatio = val; + break; + + case AL_EAXREVERB_REFLECTIONS_GAIN: + if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"}; + props->Reverb.ReflectionsGain = val; + break; + + case AL_EAXREVERB_REFLECTIONS_DELAY: + if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"}; + props->Reverb.ReflectionsDelay = val; + break; + + case AL_EAXREVERB_LATE_REVERB_GAIN: + if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"}; + props->Reverb.LateReverbGain = val; + break; + + case AL_EAXREVERB_LATE_REVERB_DELAY: + if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"}; + props->Reverb.LateReverbDelay = val; + break; + + case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: + if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"}; + props->Reverb.AirAbsorptionGainHF = val; + break; + + case AL_EAXREVERB_ECHO_TIME: + if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo time out of range"}; + props->Reverb.EchoTime = val; + break; + + case AL_EAXREVERB_ECHO_DEPTH: + if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo depth out of range"}; + props->Reverb.EchoDepth = val; + break; + + case AL_EAXREVERB_MODULATION_TIME: + if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation time out of range"}; + props->Reverb.ModulationTime = val; + break; + + case AL_EAXREVERB_MODULATION_DEPTH: + if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation depth out of range"}; + props->Reverb.ModulationDepth = val; + break; + + case AL_EAXREVERB_HFREFERENCE: + if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb hfreference out of range"}; + props->Reverb.HFReference = val; + break; + + case AL_EAXREVERB_LFREFERENCE: + if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb lfreference out of range"}; + props->Reverb.LFReference = val; + break; + + case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: + if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"}; + props->Reverb.RoomRolloffFactor = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; + } +} +void Reverb_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ + switch(param) + { + case AL_EAXREVERB_REFLECTIONS_PAN: + if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2]))) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections pan out of range"}; + props->Reverb.ReflectionsPan[0] = vals[0]; + props->Reverb.ReflectionsPan[1] = vals[1]; + props->Reverb.ReflectionsPan[2] = vals[2]; + break; + case AL_EAXREVERB_LATE_REVERB_PAN: + if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2]))) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb pan out of range"}; + props->Reverb.LateReverbPan[0] = vals[0]; + props->Reverb.LateReverbPan[1] = vals[1]; + props->Reverb.LateReverbPan[2] = vals[2]; + break; + + default: + Reverb_setParamf(props, param, vals[0]); + break; + } +} + +void Reverb_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_EAXREVERB_DECAY_HFLIMIT: + *val = props->Reverb.DecayHFLimit; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", + param}; + } +} +void Reverb_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ Reverb_getParami(props, param, vals); } +void Reverb_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_EAXREVERB_DENSITY: + *val = props->Reverb.Density; + break; + + case AL_EAXREVERB_DIFFUSION: + *val = props->Reverb.Diffusion; + break; + + case AL_EAXREVERB_GAIN: + *val = props->Reverb.Gain; + break; + + case AL_EAXREVERB_GAINHF: + *val = props->Reverb.GainHF; + break; + + case AL_EAXREVERB_GAINLF: + *val = props->Reverb.GainLF; + break; + + case AL_EAXREVERB_DECAY_TIME: + *val = props->Reverb.DecayTime; + break; + + case AL_EAXREVERB_DECAY_HFRATIO: + *val = props->Reverb.DecayHFRatio; + break; + + case AL_EAXREVERB_DECAY_LFRATIO: + *val = props->Reverb.DecayLFRatio; + break; + + case AL_EAXREVERB_REFLECTIONS_GAIN: + *val = props->Reverb.ReflectionsGain; + break; + + case AL_EAXREVERB_REFLECTIONS_DELAY: + *val = props->Reverb.ReflectionsDelay; + break; + + case AL_EAXREVERB_LATE_REVERB_GAIN: + *val = props->Reverb.LateReverbGain; + break; + + case AL_EAXREVERB_LATE_REVERB_DELAY: + *val = props->Reverb.LateReverbDelay; + break; + + case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: + *val = props->Reverb.AirAbsorptionGainHF; + break; + + case AL_EAXREVERB_ECHO_TIME: + *val = props->Reverb.EchoTime; + break; + + case AL_EAXREVERB_ECHO_DEPTH: + *val = props->Reverb.EchoDepth; + break; + + case AL_EAXREVERB_MODULATION_TIME: + *val = props->Reverb.ModulationTime; + break; + + case AL_EAXREVERB_MODULATION_DEPTH: + *val = props->Reverb.ModulationDepth; + break; + + case AL_EAXREVERB_HFREFERENCE: + *val = props->Reverb.HFReference; + break; + + case AL_EAXREVERB_LFREFERENCE: + *val = props->Reverb.LFReference; + break; + + case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: + *val = props->Reverb.RoomRolloffFactor; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; + } +} +void Reverb_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ + switch(param) + { + case AL_EAXREVERB_REFLECTIONS_PAN: + vals[0] = props->Reverb.ReflectionsPan[0]; + vals[1] = props->Reverb.ReflectionsPan[1]; + vals[2] = props->Reverb.ReflectionsPan[2]; + break; + case AL_EAXREVERB_LATE_REVERB_PAN: + vals[0] = props->Reverb.LateReverbPan[0]; + vals[1] = props->Reverb.LateReverbPan[1]; + vals[2] = props->Reverb.LateReverbPan[2]; + break; + + default: + Reverb_getParamf(props, param, vals); + break; + } +} + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Reverb.Density = AL_EAXREVERB_DEFAULT_DENSITY; + props.Reverb.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION; + props.Reverb.Gain = AL_EAXREVERB_DEFAULT_GAIN; + props.Reverb.GainHF = AL_EAXREVERB_DEFAULT_GAINHF; + props.Reverb.GainLF = AL_EAXREVERB_DEFAULT_GAINLF; + props.Reverb.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME; + props.Reverb.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO; + props.Reverb.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO; + props.Reverb.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN; + props.Reverb.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY; + props.Reverb.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.Reverb.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.Reverb.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.Reverb.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN; + props.Reverb.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY; + props.Reverb.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.Reverb.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.Reverb.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.Reverb.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME; + props.Reverb.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH; + props.Reverb.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME; + props.Reverb.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH; + props.Reverb.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF; + props.Reverb.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE; + props.Reverb.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE; + props.Reverb.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; + props.Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT; + return props; +} + + +void StdReverb_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_REVERB_DECAY_HFLIMIT: + if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) + throw effect_exception{AL_INVALID_VALUE, "Reverb decay hflimit out of range"}; + props->Reverb.DecayHFLimit = val != AL_FALSE; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param}; + } +} +void StdReverb_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ StdReverb_setParami(props, param, vals[0]); } +void StdReverb_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_REVERB_DENSITY: + if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY)) + throw effect_exception{AL_INVALID_VALUE, "Reverb density out of range"}; + props->Reverb.Density = val; + break; + + case AL_REVERB_DIFFUSION: + if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION)) + throw effect_exception{AL_INVALID_VALUE, "Reverb diffusion out of range"}; + props->Reverb.Diffusion = val; + break; + + case AL_REVERB_GAIN: + if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Reverb gain out of range"}; + props->Reverb.Gain = val; + break; + + case AL_REVERB_GAINHF: + if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF)) + throw effect_exception{AL_INVALID_VALUE, "Reverb gainhf out of range"}; + props->Reverb.GainHF = val; + break; + + case AL_REVERB_DECAY_TIME: + if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME)) + throw effect_exception{AL_INVALID_VALUE, "Reverb decay time out of range"}; + props->Reverb.DecayTime = val; + break; + + case AL_REVERB_DECAY_HFRATIO: + if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO)) + throw effect_exception{AL_INVALID_VALUE, "Reverb decay hfratio out of range"}; + props->Reverb.DecayHFRatio = val; + break; + + case AL_REVERB_REFLECTIONS_GAIN: + if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Reverb reflections gain out of range"}; + props->Reverb.ReflectionsGain = val; + break; + + case AL_REVERB_REFLECTIONS_DELAY: + if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "Reverb reflections delay out of range"}; + props->Reverb.ReflectionsDelay = val; + break; + + case AL_REVERB_LATE_REVERB_GAIN: + if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Reverb late reverb gain out of range"}; + props->Reverb.LateReverbGain = val; + break; + + case AL_REVERB_LATE_REVERB_DELAY: + if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "Reverb late reverb delay out of range"}; + props->Reverb.LateReverbDelay = val; + break; + + case AL_REVERB_AIR_ABSORPTION_GAINHF: + if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) + throw effect_exception{AL_INVALID_VALUE, "Reverb air absorption gainhf out of range"}; + props->Reverb.AirAbsorptionGainHF = val; + break; + + case AL_REVERB_ROOM_ROLLOFF_FACTOR: + if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) + throw effect_exception{AL_INVALID_VALUE, "Reverb room rolloff factor out of range"}; + props->Reverb.RoomRolloffFactor = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param}; + } +} +void StdReverb_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ StdReverb_setParamf(props, param, vals[0]); } + +void StdReverb_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_REVERB_DECAY_HFLIMIT: + *val = props->Reverb.DecayHFLimit; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param}; + } +} +void StdReverb_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ StdReverb_getParami(props, param, vals); } +void StdReverb_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_REVERB_DENSITY: + *val = props->Reverb.Density; + break; + + case AL_REVERB_DIFFUSION: + *val = props->Reverb.Diffusion; + break; + + case AL_REVERB_GAIN: + *val = props->Reverb.Gain; + break; + + case AL_REVERB_GAINHF: + *val = props->Reverb.GainHF; + break; + + case AL_REVERB_DECAY_TIME: + *val = props->Reverb.DecayTime; + break; + + case AL_REVERB_DECAY_HFRATIO: + *val = props->Reverb.DecayHFRatio; + break; + + case AL_REVERB_REFLECTIONS_GAIN: + *val = props->Reverb.ReflectionsGain; + break; + + case AL_REVERB_REFLECTIONS_DELAY: + *val = props->Reverb.ReflectionsDelay; + break; + + case AL_REVERB_LATE_REVERB_GAIN: + *val = props->Reverb.LateReverbGain; + break; + + case AL_REVERB_LATE_REVERB_DELAY: + *val = props->Reverb.LateReverbDelay; + break; + + case AL_REVERB_AIR_ABSORPTION_GAINHF: + *val = props->Reverb.AirAbsorptionGainHF; + break; + + case AL_REVERB_ROOM_ROLLOFF_FACTOR: + *val = props->Reverb.RoomRolloffFactor; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param}; + } +} +void StdReverb_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ StdReverb_getParamf(props, param, vals); } + +EffectProps genDefaultStdProps() noexcept +{ + EffectProps props{}; + props.Reverb.Density = AL_REVERB_DEFAULT_DENSITY; + props.Reverb.Diffusion = AL_REVERB_DEFAULT_DIFFUSION; + props.Reverb.Gain = AL_REVERB_DEFAULT_GAIN; + props.Reverb.GainHF = AL_REVERB_DEFAULT_GAINHF; + props.Reverb.GainLF = 1.0f; + props.Reverb.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME; + props.Reverb.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO; + props.Reverb.DecayLFRatio = 1.0f; + props.Reverb.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN; + props.Reverb.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY; + props.Reverb.ReflectionsPan[0] = 0.0f; + props.Reverb.ReflectionsPan[1] = 0.0f; + props.Reverb.ReflectionsPan[2] = 0.0f; + props.Reverb.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN; + props.Reverb.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY; + props.Reverb.LateReverbPan[0] = 0.0f; + props.Reverb.LateReverbPan[1] = 0.0f; + props.Reverb.LateReverbPan[2] = 0.0f; + props.Reverb.EchoTime = 0.25f; + props.Reverb.EchoDepth = 0.0f; + props.Reverb.ModulationTime = 0.25f; + props.Reverb.ModulationDepth = 0.0f; + props.Reverb.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF; + props.Reverb.HFReference = 5000.0f; + props.Reverb.LFReference = 250.0f; + props.Reverb.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; + props.Reverb.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Reverb); + +const EffectProps ReverbEffectProps{genDefaultProps()}; + +DEFINE_ALEFFECT_VTABLE(StdReverb); + +const EffectProps StdReverbEffectProps{genDefaultStdProps()}; + +#ifdef ALSOFT_EAX +namespace { + +extern const EFXEAXREVERBPROPERTIES eax_efx_reverb_presets[]; + +using EaxReverbEffectDirtyFlagsValue = std::uint_least32_t; + +struct EaxReverbEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxReverbEffectDirtyFlagsValue ulEnvironment : 1; + EaxReverbEffectDirtyFlagsValue flEnvironmentSize : 1; + EaxReverbEffectDirtyFlagsValue flEnvironmentDiffusion : 1; + EaxReverbEffectDirtyFlagsValue lRoom : 1; + EaxReverbEffectDirtyFlagsValue lRoomHF : 1; + EaxReverbEffectDirtyFlagsValue lRoomLF : 1; + EaxReverbEffectDirtyFlagsValue flDecayTime : 1; + EaxReverbEffectDirtyFlagsValue flDecayHFRatio : 1; + EaxReverbEffectDirtyFlagsValue flDecayLFRatio : 1; + EaxReverbEffectDirtyFlagsValue lReflections : 1; + EaxReverbEffectDirtyFlagsValue flReflectionsDelay : 1; + EaxReverbEffectDirtyFlagsValue vReflectionsPan : 1; + EaxReverbEffectDirtyFlagsValue lReverb : 1; + EaxReverbEffectDirtyFlagsValue flReverbDelay : 1; + EaxReverbEffectDirtyFlagsValue vReverbPan : 1; + EaxReverbEffectDirtyFlagsValue flEchoTime : 1; + EaxReverbEffectDirtyFlagsValue flEchoDepth : 1; + EaxReverbEffectDirtyFlagsValue flModulationTime : 1; + EaxReverbEffectDirtyFlagsValue flModulationDepth : 1; + EaxReverbEffectDirtyFlagsValue flAirAbsorptionHF : 1; + EaxReverbEffectDirtyFlagsValue flHFReference : 1; + EaxReverbEffectDirtyFlagsValue flLFReference : 1; + EaxReverbEffectDirtyFlagsValue flRoomRolloffFactor : 1; + EaxReverbEffectDirtyFlagsValue ulFlags : 1; +}; // EaxReverbEffectDirtyFlags + +struct Eax1ReverbEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxReverbEffectDirtyFlagsValue ulEnvironment : 1; + EaxReverbEffectDirtyFlagsValue flVolume : 1; + EaxReverbEffectDirtyFlagsValue flDecayTime : 1; + EaxReverbEffectDirtyFlagsValue flDamping : 1; +}; // Eax1ReverbEffectDirtyFlags + +class EaxReverbEffect final : + public EaxEffect +{ +public: + EaxReverbEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAX_REVERBPROPERTIES eax1_{}; + EAX_REVERBPROPERTIES eax1_d_{}; + Eax1ReverbEffectDirtyFlags eax1_dirty_flags_{}; + EAXREVERBPROPERTIES eax_{}; + EAXREVERBPROPERTIES eax_d_{}; + EaxReverbEffectDirtyFlags eax_dirty_flags_{}; + + [[noreturn]] static void eax_fail(const char* message); + + void set_eax_defaults(); + + void set_efx_density_from_environment_size(); + void set_efx_diffusion(); + void set_efx_gain(); + void set_efx_gain_hf(); + void set_efx_gain_lf(); + void set_efx_decay_time(); + void set_efx_decay_hf_ratio(); + void set_efx_decay_lf_ratio(); + void set_efx_reflections_gain(); + void set_efx_reflections_delay(); + void set_efx_reflections_pan(); + void set_efx_late_reverb_gain(); + void set_efx_late_reverb_delay(); + void set_efx_late_reverb_pan(); + void set_efx_echo_time(); + void set_efx_echo_depth(); + void set_efx_modulation_time(); + void set_efx_modulation_depth(); + void set_efx_air_absorption_gain_hf(); + void set_efx_hf_reference(); + void set_efx_lf_reference(); + void set_efx_room_rolloff_factor(); + void set_efx_flags(); + void set_efx_defaults(); + + void v1_get(const EaxEaxCall& eax_call) const; + + void get_all(const EaxEaxCall& eax_call) const; + + void get(const EaxEaxCall& eax_call) const; + + static void v1_validate_environment(unsigned long environment); + static void v1_validate_volume(float volume); + static void v1_validate_decay_time(float decay_time); + static void v1_validate_damping(float damping); + static void v1_validate_all(const EAX_REVERBPROPERTIES& all); + + void v1_defer_environment(unsigned long environment); + void v1_defer_volume(float volume); + void v1_defer_decay_time(float decay_time); + void v1_defer_damping(float damping); + void v1_defer_all(const EAX_REVERBPROPERTIES& all); + + void v1_defer_environment(const EaxEaxCall& eax_call); + void v1_defer_volume(const EaxEaxCall& eax_call); + void v1_defer_decay_time(const EaxEaxCall& eax_call); + void v1_defer_damping(const EaxEaxCall& eax_call); + void v1_defer_all(const EaxEaxCall& eax_call); + void v1_defer(const EaxEaxCall& eax_call); + + void v1_set_efx(); + + static void validate_environment(unsigned long ulEnvironment, int version, bool is_standalone); + static void validate_environment_size(float flEnvironmentSize); + static void validate_environment_diffusion(float flEnvironmentDiffusion); + static void validate_room(long lRoom); + static void validate_room_hf(long lRoomHF); + static void validate_room_lf(long lRoomLF); + static void validate_decay_time(float flDecayTime); + static void validate_decay_hf_ratio(float flDecayHFRatio); + static void validate_decay_lf_ratio(float flDecayLFRatio); + static void validate_reflections(long lReflections); + static void validate_reflections_delay(float flReflectionsDelay); + static void validate_reflections_pan(const EAXVECTOR& vReflectionsPan); + static void validate_reverb(long lReverb); + static void validate_reverb_delay(float flReverbDelay); + static void validate_reverb_pan(const EAXVECTOR& vReverbPan); + static void validate_echo_time(float flEchoTime); + static void validate_echo_depth(float flEchoDepth); + static void validate_modulation_time(float flModulationTime); + static void validate_modulation_depth(float flModulationDepth); + static void validate_air_absorbtion_hf(float air_absorbtion_hf); + static void validate_hf_reference(float flHFReference); + static void validate_lf_reference(float flLFReference); + static void validate_room_rolloff_factor(float flRoomRolloffFactor); + static void validate_flags(unsigned long ulFlags); + static void validate_all(const EAX20LISTENERPROPERTIES& all, int version); + static void validate_all(const EAXREVERBPROPERTIES& all, int version); + + void defer_environment(unsigned long ulEnvironment); + void defer_environment_size(float flEnvironmentSize); + void defer_environment_diffusion(float flEnvironmentDiffusion); + void defer_room(long lRoom); + void defer_room_hf(long lRoomHF); + void defer_room_lf(long lRoomLF); + void defer_decay_time(float flDecayTime); + void defer_decay_hf_ratio(float flDecayHFRatio); + void defer_decay_lf_ratio(float flDecayLFRatio); + void defer_reflections(long lReflections); + void defer_reflections_delay(float flReflectionsDelay); + void defer_reflections_pan(const EAXVECTOR& vReflectionsPan); + void defer_reverb(long lReverb); + void defer_reverb_delay(float flReverbDelay); + void defer_reverb_pan(const EAXVECTOR& vReverbPan); + void defer_echo_time(float flEchoTime); + void defer_echo_depth(float flEchoDepth); + void defer_modulation_time(float flModulationTime); + void defer_modulation_depth(float flModulationDepth); + void defer_air_absorbtion_hf(float flAirAbsorptionHF); + void defer_hf_reference(float flHFReference); + void defer_lf_reference(float flLFReference); + void defer_room_rolloff_factor(float flRoomRolloffFactor); + void defer_flags(unsigned long ulFlags); + void defer_all(const EAX20LISTENERPROPERTIES& all); + void defer_all(const EAXREVERBPROPERTIES& all); + + void defer_environment(const EaxEaxCall& eax_call); + void defer_environment_size(const EaxEaxCall& eax_call); + void defer_environment_diffusion(const EaxEaxCall& eax_call); + void defer_room(const EaxEaxCall& eax_call); + void defer_room_hf(const EaxEaxCall& eax_call); + void defer_room_lf(const EaxEaxCall& eax_call); + void defer_decay_time(const EaxEaxCall& eax_call); + void defer_decay_hf_ratio(const EaxEaxCall& eax_call); + void defer_decay_lf_ratio(const EaxEaxCall& eax_call); + void defer_reflections(const EaxEaxCall& eax_call); + void defer_reflections_delay(const EaxEaxCall& eax_call); + void defer_reflections_pan(const EaxEaxCall& eax_call); + void defer_reverb(const EaxEaxCall& eax_call); + void defer_reverb_delay(const EaxEaxCall& eax_call); + void defer_reverb_pan(const EaxEaxCall& eax_call); + void defer_echo_time(const EaxEaxCall& eax_call); + void defer_echo_depth(const EaxEaxCall& eax_call); + void defer_modulation_time(const EaxEaxCall& eax_call); + void defer_modulation_depth(const EaxEaxCall& eax_call); + void defer_air_absorbtion_hf(const EaxEaxCall& eax_call); + void defer_hf_reference(const EaxEaxCall& eax_call); + void defer_lf_reference(const EaxEaxCall& eax_call); + void defer_room_rolloff_factor(const EaxEaxCall& eax_call); + void defer_flags(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxReverbEffect + + +class EaxReverbEffectException : + public EaxException +{ +public: + explicit EaxReverbEffectException( + const char* message) + : + EaxException{"EAX_REVERB_EFFECT", message} + { + } +}; // EaxReverbEffectException + + +EaxReverbEffect::EaxReverbEffect() + : EaxEffect{AL_EFFECT_EAXREVERB} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxReverbEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +[[noreturn]] void EaxReverbEffect::eax_fail(const char* message) +{ + throw EaxReverbEffectException{message}; +} + +void EaxReverbEffect::set_eax_defaults() +{ + eax1_ = EAX1REVERB_PRESETS[EAX_ENVIRONMENT_GENERIC]; + eax1_d_ = eax1_; + eax_ = EAXREVERB_PRESETS[EAX_ENVIRONMENT_GENERIC]; + /* HACK: EAX2 has a default room volume of -10,000dB (silence), although + * newer versions use -1,000dB. What should be happening is properties for + * each EAX version is tracked separately, with the last version used for + * the properties to apply (presumably v2 or nothing being the default). + */ + eax_.lRoom = EAXREVERB_MINROOM; + eax_d_ = eax_; +} + +void EaxReverbEffect::set_efx_density_from_environment_size() +{ + const auto eax_environment_size = eax_.flEnvironmentSize; + + const auto efx_density = clamp( + (eax_environment_size * eax_environment_size * eax_environment_size) / 16.0F, + AL_EAXREVERB_MIN_DENSITY, + AL_EAXREVERB_MAX_DENSITY); + + al_effect_props_.Reverb.Density = efx_density; +} + +void EaxReverbEffect::set_efx_diffusion() +{ + const auto efx_diffusion = clamp( + eax_.flEnvironmentDiffusion, + AL_EAXREVERB_MIN_DIFFUSION, + AL_EAXREVERB_MAX_DIFFUSION); + + al_effect_props_.Reverb.Diffusion = efx_diffusion; +} + +void EaxReverbEffect::set_efx_gain() +{ + const auto efx_gain = clamp( + level_mb_to_gain(static_cast(eax_.lRoom)), + AL_EAXREVERB_MIN_GAIN, + AL_EAXREVERB_MAX_GAIN); + + al_effect_props_.Reverb.Gain = efx_gain; +} + +void EaxReverbEffect::set_efx_gain_hf() +{ + const auto efx_gain_hf = clamp( + level_mb_to_gain(static_cast(eax_.lRoomHF)), + AL_EAXREVERB_MIN_GAINHF, + AL_EAXREVERB_MAX_GAINHF); + + al_effect_props_.Reverb.GainHF = efx_gain_hf; +} + +void EaxReverbEffect::set_efx_gain_lf() +{ + const auto efx_gain_lf = clamp( + level_mb_to_gain(static_cast(eax_.lRoomLF)), + AL_EAXREVERB_MIN_GAINLF, + AL_EAXREVERB_MAX_GAINLF); + + al_effect_props_.Reverb.GainLF = efx_gain_lf; +} + +void EaxReverbEffect::set_efx_decay_time() +{ + const auto efx_decay_time = clamp( + eax_.flDecayTime, + AL_EAXREVERB_MIN_DECAY_TIME, + AL_EAXREVERB_MAX_DECAY_TIME); + + al_effect_props_.Reverb.DecayTime = efx_decay_time; +} + +void EaxReverbEffect::set_efx_decay_hf_ratio() +{ + const auto efx_decay_hf_ratio = clamp( + eax_.flDecayHFRatio, + AL_EAXREVERB_MIN_DECAY_HFRATIO, + AL_EAXREVERB_MAX_DECAY_HFRATIO); + + al_effect_props_.Reverb.DecayHFRatio = efx_decay_hf_ratio; +} + +void EaxReverbEffect::set_efx_decay_lf_ratio() +{ + const auto efx_decay_lf_ratio = clamp( + eax_.flDecayLFRatio, + AL_EAXREVERB_MIN_DECAY_LFRATIO, + AL_EAXREVERB_MAX_DECAY_LFRATIO); + + al_effect_props_.Reverb.DecayLFRatio = efx_decay_lf_ratio; +} + +void EaxReverbEffect::set_efx_reflections_gain() +{ + const auto efx_reflections_gain = clamp( + level_mb_to_gain(static_cast(eax_.lReflections)), + AL_EAXREVERB_MIN_REFLECTIONS_GAIN, + AL_EAXREVERB_MAX_REFLECTIONS_GAIN); + + al_effect_props_.Reverb.ReflectionsGain = efx_reflections_gain; +} + +void EaxReverbEffect::set_efx_reflections_delay() +{ + const auto efx_reflections_delay = clamp( + eax_.flReflectionsDelay, + AL_EAXREVERB_MIN_REFLECTIONS_DELAY, + AL_EAXREVERB_MAX_REFLECTIONS_DELAY); + + al_effect_props_.Reverb.ReflectionsDelay = efx_reflections_delay; +} + +void EaxReverbEffect::set_efx_reflections_pan() +{ + al_effect_props_.Reverb.ReflectionsPan[0] = eax_.vReflectionsPan.x; + al_effect_props_.Reverb.ReflectionsPan[1] = eax_.vReflectionsPan.y; + al_effect_props_.Reverb.ReflectionsPan[2] = eax_.vReflectionsPan.z; +} + +void EaxReverbEffect::set_efx_late_reverb_gain() +{ + const auto efx_late_reverb_gain = clamp( + level_mb_to_gain(static_cast(eax_.lReverb)), + AL_EAXREVERB_MIN_LATE_REVERB_GAIN, + AL_EAXREVERB_MAX_LATE_REVERB_GAIN); + + al_effect_props_.Reverb.LateReverbGain = efx_late_reverb_gain; +} + +void EaxReverbEffect::set_efx_late_reverb_delay() +{ + const auto efx_late_reverb_delay = clamp( + eax_.flReverbDelay, + AL_EAXREVERB_MIN_LATE_REVERB_DELAY, + AL_EAXREVERB_MAX_LATE_REVERB_DELAY); + + al_effect_props_.Reverb.LateReverbDelay = efx_late_reverb_delay; +} + +void EaxReverbEffect::set_efx_late_reverb_pan() +{ + al_effect_props_.Reverb.LateReverbPan[0] = eax_.vReverbPan.x; + al_effect_props_.Reverb.LateReverbPan[1] = eax_.vReverbPan.y; + al_effect_props_.Reverb.LateReverbPan[2] = eax_.vReverbPan.z; +} + +void EaxReverbEffect::set_efx_echo_time() +{ + const auto efx_echo_time = clamp( + eax_.flEchoTime, + AL_EAXREVERB_MIN_ECHO_TIME, + AL_EAXREVERB_MAX_ECHO_TIME); + + al_effect_props_.Reverb.EchoTime = efx_echo_time; +} + +void EaxReverbEffect::set_efx_echo_depth() +{ + const auto efx_echo_depth = clamp( + eax_.flEchoDepth, + AL_EAXREVERB_MIN_ECHO_DEPTH, + AL_EAXREVERB_MAX_ECHO_DEPTH); + + al_effect_props_.Reverb.EchoDepth = efx_echo_depth; +} + +void EaxReverbEffect::set_efx_modulation_time() +{ + const auto efx_modulation_time = clamp( + eax_.flModulationTime, + AL_EAXREVERB_MIN_MODULATION_TIME, + AL_EAXREVERB_MAX_MODULATION_TIME); + + al_effect_props_.Reverb.ModulationTime = efx_modulation_time; +} + +void EaxReverbEffect::set_efx_modulation_depth() +{ + const auto efx_modulation_depth = clamp( + eax_.flModulationDepth, + AL_EAXREVERB_MIN_MODULATION_DEPTH, + AL_EAXREVERB_MAX_MODULATION_DEPTH); + + al_effect_props_.Reverb.ModulationDepth = efx_modulation_depth; +} + +void EaxReverbEffect::set_efx_air_absorption_gain_hf() +{ + const auto efx_air_absorption_hf = clamp( + level_mb_to_gain(eax_.flAirAbsorptionHF), + AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF, + AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF); + + al_effect_props_.Reverb.AirAbsorptionGainHF = efx_air_absorption_hf; +} + +void EaxReverbEffect::set_efx_hf_reference() +{ + const auto efx_hf_reference = clamp( + eax_.flHFReference, + AL_EAXREVERB_MIN_HFREFERENCE, + AL_EAXREVERB_MAX_HFREFERENCE); + + al_effect_props_.Reverb.HFReference = efx_hf_reference; +} + +void EaxReverbEffect::set_efx_lf_reference() +{ + const auto efx_lf_reference = clamp( + eax_.flLFReference, + AL_EAXREVERB_MIN_LFREFERENCE, + AL_EAXREVERB_MAX_LFREFERENCE); + + al_effect_props_.Reverb.LFReference = efx_lf_reference; +} + +void EaxReverbEffect::set_efx_room_rolloff_factor() +{ + const auto efx_room_rolloff_factor = clamp( + eax_.flRoomRolloffFactor, + AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR, + AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR); + + al_effect_props_.Reverb.RoomRolloffFactor = efx_room_rolloff_factor; +} + +void EaxReverbEffect::set_efx_flags() +{ + al_effect_props_.Reverb.DecayHFLimit = ((eax_.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0); +} + +void EaxReverbEffect::set_efx_defaults() +{ + set_efx_density_from_environment_size(); + set_efx_diffusion(); + set_efx_gain(); + set_efx_gain_hf(); + set_efx_gain_lf(); + set_efx_decay_time(); + set_efx_decay_hf_ratio(); + set_efx_decay_lf_ratio(); + set_efx_reflections_gain(); + set_efx_reflections_delay(); + set_efx_reflections_pan(); + set_efx_late_reverb_gain(); + set_efx_late_reverb_delay(); + set_efx_late_reverb_pan(); + set_efx_echo_time(); + set_efx_echo_depth(); + set_efx_modulation_time(); + set_efx_modulation_depth(); + set_efx_air_absorption_gain_hf(); + set_efx_hf_reference(); + set_efx_lf_reference(); + set_efx_room_rolloff_factor(); + set_efx_flags(); +} + +void EaxReverbEffect::v1_get(const EaxEaxCall& eax_call) const +{ + switch(eax_call.get_property_id()) + { + case DSPROPERTY_EAX_ALL: + eax_call.set_value(eax1_); + break; + + case DSPROPERTY_EAX_ENVIRONMENT: + eax_call.set_value(eax1_.environment); + break; + + case DSPROPERTY_EAX_VOLUME: + eax_call.set_value(eax1_.fVolume); + break; + + case DSPROPERTY_EAX_DECAYTIME: + eax_call.set_value(eax1_.fDecayTime_sec); + break; + + case DSPROPERTY_EAX_DAMPING: + eax_call.set_value(eax1_.fDamping); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +void EaxReverbEffect::get_all( + const EaxEaxCall& eax_call) const +{ + if (eax_call.get_version() == 2) + { + auto& eax_reverb = eax_call.get_value(); + eax_reverb.lRoom = eax_.lRoom; + eax_reverb.lRoomHF = eax_.lRoomHF; + eax_reverb.flRoomRolloffFactor = eax_.flRoomRolloffFactor; + eax_reverb.flDecayTime = eax_.flDecayTime; + eax_reverb.flDecayHFRatio = eax_.flDecayHFRatio; + eax_reverb.lReflections = eax_.lReflections; + eax_reverb.flReflectionsDelay = eax_.flReflectionsDelay; + eax_reverb.lReverb = eax_.lReverb; + eax_reverb.flReverbDelay = eax_.flReverbDelay; + eax_reverb.dwEnvironment = eax_.ulEnvironment; + eax_reverb.flEnvironmentSize = eax_.flEnvironmentSize; + eax_reverb.flEnvironmentDiffusion = eax_.flEnvironmentDiffusion; + eax_reverb.flAirAbsorptionHF = eax_.flAirAbsorptionHF; + eax_reverb.dwFlags = eax_.ulFlags; + } + else + { + eax_call.set_value(eax_); + } +} + +void EaxReverbEffect::get(const EaxEaxCall& eax_call) const +{ + if(eax_call.get_version() == 1) + v1_get(eax_call); + else switch(eax_call.get_property_id()) + { + case EAXREVERB_NONE: + break; + + case EAXREVERB_ALLPARAMETERS: + get_all(eax_call); + break; + + case EAXREVERB_ENVIRONMENT: + eax_call.set_value(eax_.ulEnvironment); + break; + + case EAXREVERB_ENVIRONMENTSIZE: + eax_call.set_value(eax_.flEnvironmentSize); + break; + + case EAXREVERB_ENVIRONMENTDIFFUSION: + eax_call.set_value(eax_.flEnvironmentDiffusion); + break; + + case EAXREVERB_ROOM: + eax_call.set_value(eax_.lRoom); + break; + + case EAXREVERB_ROOMHF: + eax_call.set_value(eax_.lRoomHF); + break; + + case EAXREVERB_ROOMLF: + eax_call.set_value(eax_.lRoomLF); + break; + + case EAXREVERB_DECAYTIME: + eax_call.set_value(eax_.flDecayTime); + break; + + case EAXREVERB_DECAYHFRATIO: + eax_call.set_value(eax_.flDecayHFRatio); + break; + + case EAXREVERB_DECAYLFRATIO: + eax_call.set_value(eax_.flDecayLFRatio); + break; + + case EAXREVERB_REFLECTIONS: + eax_call.set_value(eax_.lReflections); + break; + + case EAXREVERB_REFLECTIONSDELAY: + eax_call.set_value(eax_.flReflectionsDelay); + break; + + case EAXREVERB_REFLECTIONSPAN: + eax_call.set_value(eax_.vReflectionsPan); + break; + + case EAXREVERB_REVERB: + eax_call.set_value(eax_.lReverb); + break; + + case EAXREVERB_REVERBDELAY: + eax_call.set_value(eax_.flReverbDelay); + break; + + case EAXREVERB_REVERBPAN: + eax_call.set_value(eax_.vReverbPan); + break; + + case EAXREVERB_ECHOTIME: + eax_call.set_value(eax_.flEchoTime); + break; + + case EAXREVERB_ECHODEPTH: + eax_call.set_value(eax_.flEchoDepth); + break; + + case EAXREVERB_MODULATIONTIME: + eax_call.set_value(eax_.flModulationTime); + break; + + case EAXREVERB_MODULATIONDEPTH: + eax_call.set_value(eax_.flModulationDepth); + break; + + case EAXREVERB_AIRABSORPTIONHF: + eax_call.set_value(eax_.flAirAbsorptionHF); + break; + + case EAXREVERB_HFREFERENCE: + eax_call.set_value(eax_.flHFReference); + break; + + case EAXREVERB_LFREFERENCE: + eax_call.set_value(eax_.flLFReference); + break; + + case EAXREVERB_ROOMROLLOFFFACTOR: + eax_call.set_value(eax_.flRoomRolloffFactor); + break; + + case EAXREVERB_FLAGS: + eax_call.set_value(eax_.ulFlags); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +void EaxReverbEffect::v1_validate_environment(unsigned long environment) +{ + validate_environment(environment, 1, true); +} + +void EaxReverbEffect::v1_validate_volume(float volume) +{ + eax_validate_range("Volume", volume, EAX1REVERB_MINVOLUME, EAX1REVERB_MAXVOLUME); +} + +void EaxReverbEffect::v1_validate_decay_time(float decay_time) +{ + validate_decay_time(decay_time); +} + +void EaxReverbEffect::v1_validate_damping(float damping) +{ + eax_validate_range("Damping", damping, EAX1REVERB_MINDAMPING, EAX1REVERB_MAXDAMPING); +} + +void EaxReverbEffect::v1_validate_all(const EAX_REVERBPROPERTIES& all) +{ + v1_validate_environment(all.environment); + v1_validate_volume(all.fVolume); + v1_validate_decay_time(all.fDecayTime_sec); + v1_validate_damping(all.fDamping); +} + +void EaxReverbEffect::validate_environment( + unsigned long ulEnvironment, + int version, + bool is_standalone) +{ + eax_validate_range( + "Environment", + ulEnvironment, + EAXREVERB_MINENVIRONMENT, + (version <= 2 || is_standalone) ? EAX1REVERB_MAXENVIRONMENT : EAX30REVERB_MAXENVIRONMENT); +} + +void EaxReverbEffect::validate_environment_size( + float flEnvironmentSize) +{ + eax_validate_range( + "Environment Size", + flEnvironmentSize, + EAXREVERB_MINENVIRONMENTSIZE, + EAXREVERB_MAXENVIRONMENTSIZE); +} + +void EaxReverbEffect::validate_environment_diffusion( + float flEnvironmentDiffusion) +{ + eax_validate_range( + "Environment Diffusion", + flEnvironmentDiffusion, + EAXREVERB_MINENVIRONMENTDIFFUSION, + EAXREVERB_MAXENVIRONMENTDIFFUSION); +} + +void EaxReverbEffect::validate_room( + long lRoom) +{ + eax_validate_range( + "Room", + lRoom, + EAXREVERB_MINROOM, + EAXREVERB_MAXROOM); +} + +void EaxReverbEffect::validate_room_hf( + long lRoomHF) +{ + eax_validate_range( + "Room HF", + lRoomHF, + EAXREVERB_MINROOMHF, + EAXREVERB_MAXROOMHF); +} + +void EaxReverbEffect::validate_room_lf( + long lRoomLF) +{ + eax_validate_range( + "Room LF", + lRoomLF, + EAXREVERB_MINROOMLF, + EAXREVERB_MAXROOMLF); +} + +void EaxReverbEffect::validate_decay_time( + float flDecayTime) +{ + eax_validate_range( + "Decay Time", + flDecayTime, + EAXREVERB_MINDECAYTIME, + EAXREVERB_MAXDECAYTIME); +} + +void EaxReverbEffect::validate_decay_hf_ratio( + float flDecayHFRatio) +{ + eax_validate_range( + "Decay HF Ratio", + flDecayHFRatio, + EAXREVERB_MINDECAYHFRATIO, + EAXREVERB_MAXDECAYHFRATIO); +} + +void EaxReverbEffect::validate_decay_lf_ratio( + float flDecayLFRatio) +{ + eax_validate_range( + "Decay LF Ratio", + flDecayLFRatio, + EAXREVERB_MINDECAYLFRATIO, + EAXREVERB_MAXDECAYLFRATIO); +} + +void EaxReverbEffect::validate_reflections( + long lReflections) +{ + eax_validate_range( + "Reflections", + lReflections, + EAXREVERB_MINREFLECTIONS, + EAXREVERB_MAXREFLECTIONS); +} + +void EaxReverbEffect::validate_reflections_delay( + float flReflectionsDelay) +{ + eax_validate_range( + "Reflections Delay", + flReflectionsDelay, + EAXREVERB_MINREFLECTIONSDELAY, + EAXREVERB_MAXREFLECTIONSDELAY); +} + +void EaxReverbEffect::validate_reflections_pan( + const EAXVECTOR& vReflectionsPan) +{ + std::ignore = vReflectionsPan; +} + +void EaxReverbEffect::validate_reverb( + long lReverb) +{ + eax_validate_range( + "Reverb", + lReverb, + EAXREVERB_MINREVERB, + EAXREVERB_MAXREVERB); +} + +void EaxReverbEffect::validate_reverb_delay( + float flReverbDelay) +{ + eax_validate_range( + "Reverb Delay", + flReverbDelay, + EAXREVERB_MINREVERBDELAY, + EAXREVERB_MAXREVERBDELAY); +} + +void EaxReverbEffect::validate_reverb_pan( + const EAXVECTOR& vReverbPan) +{ + std::ignore = vReverbPan; +} + +void EaxReverbEffect::validate_echo_time( + float flEchoTime) +{ + eax_validate_range( + "Echo Time", + flEchoTime, + EAXREVERB_MINECHOTIME, + EAXREVERB_MAXECHOTIME); +} + +void EaxReverbEffect::validate_echo_depth( + float flEchoDepth) +{ + eax_validate_range( + "Echo Depth", + flEchoDepth, + EAXREVERB_MINECHODEPTH, + EAXREVERB_MAXECHODEPTH); +} + +void EaxReverbEffect::validate_modulation_time( + float flModulationTime) +{ + eax_validate_range( + "Modulation Time", + flModulationTime, + EAXREVERB_MINMODULATIONTIME, + EAXREVERB_MAXMODULATIONTIME); +} + +void EaxReverbEffect::validate_modulation_depth( + float flModulationDepth) +{ + eax_validate_range( + "Modulation Depth", + flModulationDepth, + EAXREVERB_MINMODULATIONDEPTH, + EAXREVERB_MAXMODULATIONDEPTH); +} + +void EaxReverbEffect::validate_air_absorbtion_hf( + float air_absorbtion_hf) +{ + eax_validate_range( + "Air Absorbtion HF", + air_absorbtion_hf, + EAXREVERB_MINAIRABSORPTIONHF, + EAXREVERB_MAXAIRABSORPTIONHF); +} + +void EaxReverbEffect::validate_hf_reference( + float flHFReference) +{ + eax_validate_range( + "HF Reference", + flHFReference, + EAXREVERB_MINHFREFERENCE, + EAXREVERB_MAXHFREFERENCE); +} + +void EaxReverbEffect::validate_lf_reference( + float flLFReference) +{ + eax_validate_range( + "LF Reference", + flLFReference, + EAXREVERB_MINLFREFERENCE, + EAXREVERB_MAXLFREFERENCE); +} + +void EaxReverbEffect::validate_room_rolloff_factor( + float flRoomRolloffFactor) +{ + eax_validate_range( + "Room Rolloff Factor", + flRoomRolloffFactor, + EAXREVERB_MINROOMROLLOFFFACTOR, + EAXREVERB_MAXROOMROLLOFFFACTOR); +} + +void EaxReverbEffect::validate_flags( + unsigned long ulFlags) +{ + eax_validate_range( + "Flags", + ulFlags, + 0UL, + ~EAXREVERBFLAGS_RESERVED); +} + +void EaxReverbEffect::validate_all( + const EAX20LISTENERPROPERTIES& listener, + int version) +{ + validate_room(listener.lRoom); + validate_room_hf(listener.lRoomHF); + validate_room_rolloff_factor(listener.flRoomRolloffFactor); + validate_decay_time(listener.flDecayTime); + validate_decay_hf_ratio(listener.flDecayHFRatio); + validate_reflections(listener.lReflections); + validate_reflections_delay(listener.flReflectionsDelay); + validate_reverb(listener.lReverb); + validate_reverb_delay(listener.flReverbDelay); + validate_environment(listener.dwEnvironment, version, false); + validate_environment_size(listener.flEnvironmentSize); + validate_environment_diffusion(listener.flEnvironmentDiffusion); + validate_air_absorbtion_hf(listener.flAirAbsorptionHF); + validate_flags(listener.dwFlags); +} + +void EaxReverbEffect::validate_all( + const EAXREVERBPROPERTIES& lReverb, + int version) +{ + validate_environment(lReverb.ulEnvironment, version, false); + validate_environment_size(lReverb.flEnvironmentSize); + validate_environment_diffusion(lReverb.flEnvironmentDiffusion); + validate_room(lReverb.lRoom); + validate_room_hf(lReverb.lRoomHF); + validate_room_lf(lReverb.lRoomLF); + validate_decay_time(lReverb.flDecayTime); + validate_decay_hf_ratio(lReverb.flDecayHFRatio); + validate_decay_lf_ratio(lReverb.flDecayLFRatio); + validate_reflections(lReverb.lReflections); + validate_reflections_delay(lReverb.flReflectionsDelay); + validate_reverb(lReverb.lReverb); + validate_reverb_delay(lReverb.flReverbDelay); + validate_echo_time(lReverb.flEchoTime); + validate_echo_depth(lReverb.flEchoDepth); + validate_modulation_time(lReverb.flModulationTime); + validate_modulation_depth(lReverb.flModulationDepth); + validate_air_absorbtion_hf(lReverb.flAirAbsorptionHF); + validate_hf_reference(lReverb.flHFReference); + validate_lf_reference(lReverb.flLFReference); + validate_room_rolloff_factor(lReverb.flRoomRolloffFactor); + validate_flags(lReverb.ulFlags); +} + +void EaxReverbEffect::v1_defer_environment(unsigned long environment) +{ + eax1_d_ = EAX1REVERB_PRESETS[environment]; + eax1_dirty_flags_.ulEnvironment = true; +} + +void EaxReverbEffect::v1_defer_volume(float volume) +{ + eax1_d_.fVolume = volume; + eax1_dirty_flags_.flVolume = (eax1_.fVolume != eax1_d_.fVolume); +} + +void EaxReverbEffect::v1_defer_decay_time(float decay_time) +{ + eax1_d_.fDecayTime_sec = decay_time; + eax1_dirty_flags_.flDecayTime = (eax1_.fDecayTime_sec != eax1_d_.fDecayTime_sec); +} + +void EaxReverbEffect::v1_defer_damping(float damping) +{ + eax1_d_.fDamping = damping; + eax1_dirty_flags_.flDamping = (eax1_.fDamping != eax1_d_.fDamping); +} + +void EaxReverbEffect::v1_defer_all(const EAX_REVERBPROPERTIES& lReverb) +{ + v1_defer_environment(lReverb.environment); + v1_defer_volume(lReverb.fVolume); + v1_defer_decay_time(lReverb.fDecayTime_sec); + v1_defer_damping(lReverb.fDamping); +} + + +void EaxReverbEffect::v1_set_efx() +{ + auto efx_props = eax_efx_reverb_presets[eax1_.environment]; + efx_props.flGain = eax1_.fVolume; + efx_props.flDecayTime = eax1_.fDecayTime_sec; + efx_props.flDecayHFRatio = clamp(eax1_.fDamping, AL_EAXREVERB_MIN_DECAY_HFRATIO, AL_EAXREVERB_MAX_DECAY_HFRATIO); + + al_effect_props_.Reverb.Density = efx_props.flDensity; + al_effect_props_.Reverb.Diffusion = efx_props.flDiffusion; + al_effect_props_.Reverb.Gain = efx_props.flGain; + al_effect_props_.Reverb.GainHF = efx_props.flGainHF; + al_effect_props_.Reverb.GainLF = efx_props.flGainLF; + al_effect_props_.Reverb.DecayTime = efx_props.flDecayTime; + al_effect_props_.Reverb.DecayHFRatio = efx_props.flDecayHFRatio; + al_effect_props_.Reverb.DecayLFRatio = efx_props.flDecayLFRatio; + al_effect_props_.Reverb.ReflectionsGain = efx_props.flReflectionsGain; + al_effect_props_.Reverb.ReflectionsDelay = efx_props.flReflectionsDelay; + al_effect_props_.Reverb.ReflectionsPan[0] = efx_props.flReflectionsPan[0]; + al_effect_props_.Reverb.ReflectionsPan[1] = efx_props.flReflectionsPan[1]; + al_effect_props_.Reverb.ReflectionsPan[2] = efx_props.flReflectionsPan[2]; + al_effect_props_.Reverb.LateReverbGain = efx_props.flLateReverbGain; + al_effect_props_.Reverb.LateReverbDelay = efx_props.flLateReverbDelay; + al_effect_props_.Reverb.LateReverbPan[0] = efx_props.flLateReverbPan[0]; + al_effect_props_.Reverb.LateReverbPan[1] = efx_props.flLateReverbPan[1]; + al_effect_props_.Reverb.LateReverbPan[2] = efx_props.flLateReverbPan[2]; + al_effect_props_.Reverb.EchoTime = efx_props.flEchoTime; + al_effect_props_.Reverb.EchoDepth = efx_props.flEchoDepth; + al_effect_props_.Reverb.ModulationTime = efx_props.flModulationTime; + al_effect_props_.Reverb.ModulationDepth = efx_props.flModulationDepth; + al_effect_props_.Reverb.HFReference = efx_props.flHFReference; + al_effect_props_.Reverb.LFReference = efx_props.flLFReference; + al_effect_props_.Reverb.RoomRolloffFactor = efx_props.flRoomRolloffFactor; + al_effect_props_.Reverb.AirAbsorptionGainHF = efx_props.flAirAbsorptionGainHF; + al_effect_props_.Reverb.DecayHFLimit = false; +} + +void EaxReverbEffect::defer_environment( + unsigned long ulEnvironment) +{ + eax_d_.ulEnvironment = ulEnvironment; + eax_dirty_flags_.ulEnvironment = (eax_.ulEnvironment != eax_d_.ulEnvironment); +} + +void EaxReverbEffect::defer_environment_size( + float flEnvironmentSize) +{ + eax_d_.flEnvironmentSize = flEnvironmentSize; + eax_dirty_flags_.flEnvironmentSize = (eax_.flEnvironmentSize != eax_d_.flEnvironmentSize); +} + +void EaxReverbEffect::defer_environment_diffusion( + float flEnvironmentDiffusion) +{ + eax_d_.flEnvironmentDiffusion = flEnvironmentDiffusion; + eax_dirty_flags_.flEnvironmentDiffusion = (eax_.flEnvironmentDiffusion != eax_d_.flEnvironmentDiffusion); +} + +void EaxReverbEffect::defer_room( + long lRoom) +{ + eax_d_.lRoom = lRoom; + eax_dirty_flags_.lRoom = (eax_.lRoom != eax_d_.lRoom); +} + +void EaxReverbEffect::defer_room_hf( + long lRoomHF) +{ + eax_d_.lRoomHF = lRoomHF; + eax_dirty_flags_.lRoomHF = (eax_.lRoomHF != eax_d_.lRoomHF); +} + +void EaxReverbEffect::defer_room_lf( + long lRoomLF) +{ + eax_d_.lRoomLF = lRoomLF; + eax_dirty_flags_.lRoomLF = (eax_.lRoomLF != eax_d_.lRoomLF); +} + +void EaxReverbEffect::defer_decay_time( + float flDecayTime) +{ + eax_d_.flDecayTime = flDecayTime; + eax_dirty_flags_.flDecayTime = (eax_.flDecayTime != eax_d_.flDecayTime); +} + +void EaxReverbEffect::defer_decay_hf_ratio( + float flDecayHFRatio) +{ + eax_d_.flDecayHFRatio = flDecayHFRatio; + eax_dirty_flags_.flDecayHFRatio = (eax_.flDecayHFRatio != eax_d_.flDecayHFRatio); +} + +void EaxReverbEffect::defer_decay_lf_ratio( + float flDecayLFRatio) +{ + eax_d_.flDecayLFRatio = flDecayLFRatio; + eax_dirty_flags_.flDecayLFRatio = (eax_.flDecayLFRatio != eax_d_.flDecayLFRatio); +} + +void EaxReverbEffect::defer_reflections( + long lReflections) +{ + eax_d_.lReflections = lReflections; + eax_dirty_flags_.lReflections = (eax_.lReflections != eax_d_.lReflections); +} + +void EaxReverbEffect::defer_reflections_delay( + float flReflectionsDelay) +{ + eax_d_.flReflectionsDelay = flReflectionsDelay; + eax_dirty_flags_.flReflectionsDelay = (eax_.flReflectionsDelay != eax_d_.flReflectionsDelay); +} + +void EaxReverbEffect::defer_reflections_pan( + const EAXVECTOR& vReflectionsPan) +{ + eax_d_.vReflectionsPan = vReflectionsPan; + eax_dirty_flags_.vReflectionsPan = (eax_.vReflectionsPan != eax_d_.vReflectionsPan); +} + +void EaxReverbEffect::defer_reverb( + long lReverb) +{ + eax_d_.lReverb = lReverb; + eax_dirty_flags_.lReverb = (eax_.lReverb != eax_d_.lReverb); +} + +void EaxReverbEffect::defer_reverb_delay( + float flReverbDelay) +{ + eax_d_.flReverbDelay = flReverbDelay; + eax_dirty_flags_.flReverbDelay = (eax_.flReverbDelay != eax_d_.flReverbDelay); +} + +void EaxReverbEffect::defer_reverb_pan( + const EAXVECTOR& vReverbPan) +{ + eax_d_.vReverbPan = vReverbPan; + eax_dirty_flags_.vReverbPan = (eax_.vReverbPan != eax_d_.vReverbPan); +} + +void EaxReverbEffect::defer_echo_time( + float flEchoTime) +{ + eax_d_.flEchoTime = flEchoTime; + eax_dirty_flags_.flEchoTime = (eax_.flEchoTime != eax_d_.flEchoTime); +} + +void EaxReverbEffect::defer_echo_depth( + float flEchoDepth) +{ + eax_d_.flEchoDepth = flEchoDepth; + eax_dirty_flags_.flEchoDepth = (eax_.flEchoDepth != eax_d_.flEchoDepth); +} + +void EaxReverbEffect::defer_modulation_time( + float flModulationTime) +{ + eax_d_.flModulationTime = flModulationTime; + eax_dirty_flags_.flModulationTime = (eax_.flModulationTime != eax_d_.flModulationTime); +} + +void EaxReverbEffect::defer_modulation_depth( + float flModulationDepth) +{ + eax_d_.flModulationDepth = flModulationDepth; + eax_dirty_flags_.flModulationDepth = (eax_.flModulationDepth != eax_d_.flModulationDepth); +} + +void EaxReverbEffect::defer_air_absorbtion_hf( + float flAirAbsorptionHF) +{ + eax_d_.flAirAbsorptionHF = flAirAbsorptionHF; + eax_dirty_flags_.flAirAbsorptionHF = (eax_.flAirAbsorptionHF != eax_d_.flAirAbsorptionHF); +} + +void EaxReverbEffect::defer_hf_reference( + float flHFReference) +{ + eax_d_.flHFReference = flHFReference; + eax_dirty_flags_.flHFReference = (eax_.flHFReference != eax_d_.flHFReference); +} + +void EaxReverbEffect::defer_lf_reference( + float flLFReference) +{ + eax_d_.flLFReference = flLFReference; + eax_dirty_flags_.flLFReference = (eax_.flLFReference != eax_d_.flLFReference); +} + +void EaxReverbEffect::defer_room_rolloff_factor( + float flRoomRolloffFactor) +{ + eax_d_.flRoomRolloffFactor = flRoomRolloffFactor; + eax_dirty_flags_.flRoomRolloffFactor = (eax_.flRoomRolloffFactor != eax_d_.flRoomRolloffFactor); +} + +void EaxReverbEffect::defer_flags( + unsigned long ulFlags) +{ + eax_d_.ulFlags = ulFlags; + eax_dirty_flags_.ulFlags = (eax_.ulFlags != eax_d_.ulFlags); +} + +void EaxReverbEffect::defer_all( + const EAX20LISTENERPROPERTIES& listener) +{ + defer_room(listener.lRoom); + defer_room_hf(listener.lRoomHF); + defer_room_rolloff_factor(listener.flRoomRolloffFactor); + defer_decay_time(listener.flDecayTime); + defer_decay_hf_ratio(listener.flDecayHFRatio); + defer_reflections(listener.lReflections); + defer_reflections_delay(listener.flReflectionsDelay); + defer_reverb(listener.lReverb); + defer_reverb_delay(listener.flReverbDelay); + defer_environment(listener.dwEnvironment); + defer_environment_size(listener.flEnvironmentSize); + defer_environment_diffusion(listener.flEnvironmentDiffusion); + defer_air_absorbtion_hf(listener.flAirAbsorptionHF); + defer_flags(listener.dwFlags); +} + +void EaxReverbEffect::defer_all( + const EAXREVERBPROPERTIES& lReverb) +{ + defer_environment(lReverb.ulEnvironment); + defer_environment_size(lReverb.flEnvironmentSize); + defer_environment_diffusion(lReverb.flEnvironmentDiffusion); + defer_room(lReverb.lRoom); + defer_room_hf(lReverb.lRoomHF); + defer_room_lf(lReverb.lRoomLF); + defer_decay_time(lReverb.flDecayTime); + defer_decay_hf_ratio(lReverb.flDecayHFRatio); + defer_decay_lf_ratio(lReverb.flDecayLFRatio); + defer_reflections(lReverb.lReflections); + defer_reflections_delay(lReverb.flReflectionsDelay); + defer_reflections_pan(lReverb.vReflectionsPan); + defer_reverb(lReverb.lReverb); + defer_reverb_delay(lReverb.flReverbDelay); + defer_reverb_pan(lReverb.vReverbPan); + defer_echo_time(lReverb.flEchoTime); + defer_echo_depth(lReverb.flEchoDepth); + defer_modulation_time(lReverb.flModulationTime); + defer_modulation_depth(lReverb.flModulationDepth); + defer_air_absorbtion_hf(lReverb.flAirAbsorptionHF); + defer_hf_reference(lReverb.flHFReference); + defer_lf_reference(lReverb.flLFReference); + defer_room_rolloff_factor(lReverb.flRoomRolloffFactor); + defer_flags(lReverb.ulFlags); +} + + +void EaxReverbEffect::v1_defer_environment(const EaxEaxCall& eax_call) +{ + const auto& environment = eax_call.get_value(); + + validate_environment(environment, 1, true); + + const auto& reverb_preset = EAX1REVERB_PRESETS[environment]; + v1_defer_all(reverb_preset); +} + +void EaxReverbEffect::v1_defer_volume(const EaxEaxCall& eax_call) +{ + const auto& volume = eax_call.get_value(); + + v1_validate_volume(volume); + v1_defer_volume(volume); +} + +void EaxReverbEffect::v1_defer_decay_time(const EaxEaxCall& eax_call) +{ + const auto& decay_time = eax_call.get_value(); + + v1_validate_decay_time(decay_time); + v1_defer_decay_time(decay_time); +} + +void EaxReverbEffect::v1_defer_damping(const EaxEaxCall& eax_call) +{ + const auto& damping = eax_call.get_value(); + + v1_validate_damping(damping); + v1_defer_damping(damping); +} + +void EaxReverbEffect::v1_defer_all(const EaxEaxCall& eax_call) +{ + const auto& reverb_all = eax_call.get_value(); + + v1_validate_all(reverb_all); + v1_defer_all(reverb_all); +} + + +void EaxReverbEffect::defer_environment( + const EaxEaxCall& eax_call) +{ + const auto& ulEnvironment = + eax_call.get_value(); + + validate_environment(ulEnvironment, eax_call.get_version(), true); + + if (eax_d_.ulEnvironment == ulEnvironment) + { + return; + } + + const auto& reverb_preset = EAXREVERB_PRESETS[ulEnvironment]; + + defer_all(reverb_preset); +} + +void EaxReverbEffect::defer_environment_size( + const EaxEaxCall& eax_call) +{ + const auto& flEnvironmentSize = + eax_call.get_value(); + + validate_environment_size(flEnvironmentSize); + + if (eax_d_.flEnvironmentSize == flEnvironmentSize) + { + return; + } + + const auto scale = flEnvironmentSize / eax_d_.flEnvironmentSize; + + defer_environment_size(flEnvironmentSize); + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) + { + const auto flDecayTime = clamp( + scale * eax_d_.flDecayTime, + EAXREVERB_MINDECAYTIME, + EAXREVERB_MAXDECAYTIME); + + defer_decay_time(flDecayTime); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSSCALE) != 0) + { + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) + { + const auto lReflections = clamp( + eax_d_.lReflections - static_cast(gain_to_level_mb(scale)), + EAXREVERB_MINREFLECTIONS, + EAXREVERB_MAXREFLECTIONS); + + defer_reflections(lReflections); + } + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) + { + const auto flReflectionsDelay = clamp( + eax_d_.flReflectionsDelay * scale, + EAXREVERB_MINREFLECTIONSDELAY, + EAXREVERB_MAXREFLECTIONSDELAY); + + defer_reflections_delay(flReflectionsDelay); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REVERBSCALE) != 0) + { + const auto log_scalar = ((eax_d_.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F; + + const auto lReverb = clamp( + eax_d_.lReverb - static_cast(std::log10(scale) * log_scalar), + EAXREVERB_MINREVERB, + EAXREVERB_MAXREVERB); + + defer_reverb(lReverb); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REVERBDELAYSCALE) != 0) + { + const auto flReverbDelay = clamp( + scale * eax_d_.flReverbDelay, + EAXREVERB_MINREVERBDELAY, + EAXREVERB_MAXREVERBDELAY); + + defer_reverb_delay(flReverbDelay); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_ECHOTIMESCALE) != 0) + { + const auto flEchoTime = clamp( + eax_d_.flEchoTime * scale, + EAXREVERB_MINECHOTIME, + EAXREVERB_MAXECHOTIME); + + defer_echo_time(flEchoTime); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_MODULATIONTIMESCALE) != 0) + { + const auto flModulationTime = clamp( + scale * eax_d_.flModulationTime, + EAXREVERB_MINMODULATIONTIME, + EAXREVERB_MAXMODULATIONTIME); + + defer_modulation_time(flModulationTime); + } +} + +void EaxReverbEffect::defer_environment_diffusion( + const EaxEaxCall& eax_call) +{ + const auto& flEnvironmentDiffusion = + eax_call.get_value(); + + validate_environment_diffusion(flEnvironmentDiffusion); + defer_environment_diffusion(flEnvironmentDiffusion); +} + +void EaxReverbEffect::defer_room( + const EaxEaxCall& eax_call) +{ + const auto& lRoom = + eax_call.get_value(); + + validate_room(lRoom); + defer_room(lRoom); +} + +void EaxReverbEffect::defer_room_hf( + const EaxEaxCall& eax_call) +{ + const auto& lRoomHF = + eax_call.get_value(); + + validate_room_hf(lRoomHF); + defer_room_hf(lRoomHF); +} + +void EaxReverbEffect::defer_room_lf( + const EaxEaxCall& eax_call) +{ + const auto& lRoomLF = + eax_call.get_value(); + + validate_room_lf(lRoomLF); + defer_room_lf(lRoomLF); +} + +void EaxReverbEffect::defer_decay_time( + const EaxEaxCall& eax_call) +{ + const auto& flDecayTime = + eax_call.get_value(); + + validate_decay_time(flDecayTime); + defer_decay_time(flDecayTime); +} + +void EaxReverbEffect::defer_decay_hf_ratio( + const EaxEaxCall& eax_call) +{ + const auto& flDecayHFRatio = + eax_call.get_value(); + + validate_decay_hf_ratio(flDecayHFRatio); + defer_decay_hf_ratio(flDecayHFRatio); +} + +void EaxReverbEffect::defer_decay_lf_ratio( + const EaxEaxCall& eax_call) +{ + const auto& flDecayLFRatio = + eax_call.get_value(); + + validate_decay_lf_ratio(flDecayLFRatio); + defer_decay_lf_ratio(flDecayLFRatio); +} + +void EaxReverbEffect::defer_reflections( + const EaxEaxCall& eax_call) +{ + const auto& lReflections = + eax_call.get_value(); + + validate_reflections(lReflections); + defer_reflections(lReflections); +} + +void EaxReverbEffect::defer_reflections_delay( + const EaxEaxCall& eax_call) +{ + const auto& flReflectionsDelay = + eax_call.get_value(); + + validate_reflections_delay(flReflectionsDelay); + defer_reflections_delay(flReflectionsDelay); +} + +void EaxReverbEffect::defer_reflections_pan( + const EaxEaxCall& eax_call) +{ + const auto& vReflectionsPan = + eax_call.get_value(); + + validate_reflections_pan(vReflectionsPan); + defer_reflections_pan(vReflectionsPan); +} + +void EaxReverbEffect::defer_reverb( + const EaxEaxCall& eax_call) +{ + const auto& lReverb = + eax_call.get_value(); + + validate_reverb(lReverb); + defer_reverb(lReverb); +} + +void EaxReverbEffect::defer_reverb_delay( + const EaxEaxCall& eax_call) +{ + const auto& flReverbDelay = + eax_call.get_value(); + + validate_reverb_delay(flReverbDelay); + defer_reverb_delay(flReverbDelay); +} + +void EaxReverbEffect::defer_reverb_pan( + const EaxEaxCall& eax_call) +{ + const auto& vReverbPan = + eax_call.get_value(); + + validate_reverb_pan(vReverbPan); + defer_reverb_pan(vReverbPan); +} + +void EaxReverbEffect::defer_echo_time( + const EaxEaxCall& eax_call) +{ + const auto& flEchoTime = + eax_call.get_value(); + + validate_echo_time(flEchoTime); + defer_echo_time(flEchoTime); +} + +void EaxReverbEffect::defer_echo_depth( + const EaxEaxCall& eax_call) +{ + const auto& flEchoDepth = + eax_call.get_value(); + + validate_echo_depth(flEchoDepth); + defer_echo_depth(flEchoDepth); +} + +void EaxReverbEffect::defer_modulation_time( + const EaxEaxCall& eax_call) +{ + const auto& flModulationTime = + eax_call.get_value(); + + validate_modulation_time(flModulationTime); + defer_modulation_time(flModulationTime); +} + +void EaxReverbEffect::defer_modulation_depth( + const EaxEaxCall& eax_call) +{ + const auto& flModulationDepth = + eax_call.get_value(); + + validate_modulation_depth(flModulationDepth); + defer_modulation_depth(flModulationDepth); +} + +void EaxReverbEffect::defer_air_absorbtion_hf( + const EaxEaxCall& eax_call) +{ + const auto& air_absorbtion_hf = + eax_call.get_value(); + + validate_air_absorbtion_hf(air_absorbtion_hf); + defer_air_absorbtion_hf(air_absorbtion_hf); +} + +void EaxReverbEffect::defer_hf_reference( + const EaxEaxCall& eax_call) +{ + const auto& flHFReference = + eax_call.get_value(); + + validate_hf_reference(flHFReference); + defer_hf_reference(flHFReference); +} + +void EaxReverbEffect::defer_lf_reference( + const EaxEaxCall& eax_call) +{ + const auto& flLFReference = + eax_call.get_value(); + + validate_lf_reference(flLFReference); + defer_lf_reference(flLFReference); +} + +void EaxReverbEffect::defer_room_rolloff_factor( + const EaxEaxCall& eax_call) +{ + const auto& flRoomRolloffFactor = + eax_call.get_value(); + + validate_room_rolloff_factor(flRoomRolloffFactor); + defer_room_rolloff_factor(flRoomRolloffFactor); +} + +void EaxReverbEffect::defer_flags( + const EaxEaxCall& eax_call) +{ + const auto& ulFlags = + eax_call.get_value(); + + validate_flags(ulFlags); + defer_flags(ulFlags); +} + +void EaxReverbEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto eax_version = eax_call.get_version(); + + if (eax_version == 2) + { + const auto& listener = + eax_call.get_value(); + + validate_all(listener, eax_version); + defer_all(listener); + } + else + { + const auto& reverb_all = + eax_call.get_value(); + + validate_all(reverb_all, eax_version); + defer_all(reverb_all); + } +} + + +void EaxReverbEffect::v1_defer(const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case DSPROPERTY_EAX_ALL: return v1_defer_all(eax_call); + case DSPROPERTY_EAX_ENVIRONMENT: return v1_defer_environment(eax_call); + case DSPROPERTY_EAX_VOLUME: return v1_defer_volume(eax_call); + case DSPROPERTY_EAX_DECAYTIME: return v1_defer_decay_time(eax_call); + case DSPROPERTY_EAX_DAMPING: return v1_defer_damping(eax_call); + default: eax_fail("Unsupported property id."); + } +} + +// [[nodiscard]] +bool EaxReverbEffect::apply_deferred() +{ + bool ret{false}; + + if(unlikely(eax1_dirty_flags_ != Eax1ReverbEffectDirtyFlags{})) + { + eax1_ = eax1_d_; + + v1_set_efx(); + + eax1_dirty_flags_ = Eax1ReverbEffectDirtyFlags{}; + + ret = true; + } + + if(eax_dirty_flags_ == EaxReverbEffectDirtyFlags{}) + return ret; + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulEnvironment) + { + } + + if (eax_dirty_flags_.flEnvironmentSize) + { + set_efx_density_from_environment_size(); + } + + if (eax_dirty_flags_.flEnvironmentDiffusion) + { + set_efx_diffusion(); + } + + if (eax_dirty_flags_.lRoom) + { + set_efx_gain(); + } + + if (eax_dirty_flags_.lRoomHF) + { + set_efx_gain_hf(); + } + + if (eax_dirty_flags_.lRoomLF) + { + set_efx_gain_lf(); + } + + if (eax_dirty_flags_.flDecayTime) + { + set_efx_decay_time(); + } + + if (eax_dirty_flags_.flDecayHFRatio) + { + set_efx_decay_hf_ratio(); + } + + if (eax_dirty_flags_.flDecayLFRatio) + { + set_efx_decay_lf_ratio(); + } + + if (eax_dirty_flags_.lReflections) + { + set_efx_reflections_gain(); + } + + if (eax_dirty_flags_.flReflectionsDelay) + { + set_efx_reflections_delay(); + } + + if (eax_dirty_flags_.vReflectionsPan) + { + set_efx_reflections_pan(); + } + + if (eax_dirty_flags_.lReverb) + { + set_efx_late_reverb_gain(); + } + + if (eax_dirty_flags_.flReverbDelay) + { + set_efx_late_reverb_delay(); + } + + if (eax_dirty_flags_.vReverbPan) + { + set_efx_late_reverb_pan(); + } + + if (eax_dirty_flags_.flEchoTime) + { + set_efx_echo_time(); + } + + if (eax_dirty_flags_.flEchoDepth) + { + set_efx_echo_depth(); + } + + if (eax_dirty_flags_.flModulationTime) + { + set_efx_modulation_time(); + } + + if (eax_dirty_flags_.flModulationDepth) + { + set_efx_modulation_depth(); + } + + if (eax_dirty_flags_.flAirAbsorptionHF) + { + set_efx_air_absorption_gain_hf(); + } + + if (eax_dirty_flags_.flHFReference) + { + set_efx_hf_reference(); + } + + if (eax_dirty_flags_.flLFReference) + { + set_efx_lf_reference(); + } + + if (eax_dirty_flags_.flRoomRolloffFactor) + { + set_efx_room_rolloff_factor(); + } + + if (eax_dirty_flags_.ulFlags) + { + set_efx_flags(); + } + + eax_dirty_flags_ = EaxReverbEffectDirtyFlags{}; + + return true; +} + +void EaxReverbEffect::set(const EaxEaxCall& eax_call) +{ + if(eax_call.get_version() == 1) + v1_defer(eax_call); + else switch(eax_call.get_property_id()) + { + case EAXREVERB_NONE: + break; + + case EAXREVERB_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXREVERB_ENVIRONMENT: + defer_environment(eax_call); + break; + + case EAXREVERB_ENVIRONMENTSIZE: + defer_environment_size(eax_call); + break; + + case EAXREVERB_ENVIRONMENTDIFFUSION: + defer_environment_diffusion(eax_call); + break; + + case EAXREVERB_ROOM: + defer_room(eax_call); + break; + + case EAXREVERB_ROOMHF: + defer_room_hf(eax_call); + break; + + case EAXREVERB_ROOMLF: + defer_room_lf(eax_call); + break; + + case EAXREVERB_DECAYTIME: + defer_decay_time(eax_call); + break; + + case EAXREVERB_DECAYHFRATIO: + defer_decay_hf_ratio(eax_call); + break; + + case EAXREVERB_DECAYLFRATIO: + defer_decay_lf_ratio(eax_call); + break; + + case EAXREVERB_REFLECTIONS: + defer_reflections(eax_call); + break; + + case EAXREVERB_REFLECTIONSDELAY: + defer_reflections_delay(eax_call); + break; + + case EAXREVERB_REFLECTIONSPAN: + defer_reflections_pan(eax_call); + break; + + case EAXREVERB_REVERB: + defer_reverb(eax_call); + break; + + case EAXREVERB_REVERBDELAY: + defer_reverb_delay(eax_call); + break; + + case EAXREVERB_REVERBPAN: + defer_reverb_pan(eax_call); + break; + + case EAXREVERB_ECHOTIME: + defer_echo_time(eax_call); + break; + + case EAXREVERB_ECHODEPTH: + defer_echo_depth(eax_call); + break; + + case EAXREVERB_MODULATIONTIME: + defer_modulation_time(eax_call); + break; + + case EAXREVERB_MODULATIONDEPTH: + defer_modulation_depth(eax_call); + break; + + case EAXREVERB_AIRABSORPTIONHF: + defer_air_absorbtion_hf(eax_call); + break; + + case EAXREVERB_HFREFERENCE: + defer_hf_reference(eax_call); + break; + + case EAXREVERB_LFREFERENCE: + defer_lf_reference(eax_call); + break; + + case EAXREVERB_ROOMROLLOFFFACTOR: + defer_room_rolloff_factor(eax_call); + break; + + case EAXREVERB_FLAGS: + defer_flags(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +const EFXEAXREVERBPROPERTIES eax_efx_reverb_presets[EAX1_ENVIRONMENT_COUNT] = +{ + EFX_REVERB_PRESET_GENERIC, + EFX_REVERB_PRESET_PADDEDCELL, + EFX_REVERB_PRESET_ROOM, + EFX_REVERB_PRESET_BATHROOM, + EFX_REVERB_PRESET_LIVINGROOM, + EFX_REVERB_PRESET_STONEROOM, + EFX_REVERB_PRESET_AUDITORIUM, + EFX_REVERB_PRESET_CONCERTHALL, + EFX_REVERB_PRESET_CAVE, + EFX_REVERB_PRESET_ARENA, + EFX_REVERB_PRESET_HANGAR, + EFX_REVERB_PRESET_CARPETEDHALLWAY, + EFX_REVERB_PRESET_HALLWAY, + EFX_REVERB_PRESET_STONECORRIDOR, + EFX_REVERB_PRESET_ALLEY, + EFX_REVERB_PRESET_FOREST, + EFX_REVERB_PRESET_CITY, + EFX_REVERB_PRESET_MOUNTAINS, + EFX_REVERB_PRESET_QUARRY, + EFX_REVERB_PRESET_PLAIN, + EFX_REVERB_PRESET_PARKINGLOT, + EFX_REVERB_PRESET_SEWERPIPE, + EFX_REVERB_PRESET_UNDERWATER, + EFX_REVERB_PRESET_DRUGGED, + EFX_REVERB_PRESET_DIZZY, + EFX_REVERB_PRESET_PSYCHOTIC, +}; // EFXEAXREVERBPROPERTIES + +} // namespace + +EaxEffectUPtr eax_create_eax_reverb_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/effects/vmorpher.cpp b/modules/openal-soft/al/effects/vmorpher.cpp new file mode 100644 index 0000000..8c0b3ad --- /dev/null +++ b/modules/openal-soft/al/effects/vmorpher.cpp @@ -0,0 +1,786 @@ + +#include "config.h" + +#include + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "aloptional.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include + +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + + +namespace { + +al::optional PhenomeFromEnum(ALenum val) +{ +#define HANDLE_PHENOME(x) case AL_VOCAL_MORPHER_PHONEME_ ## x: \ + return al::make_optional(VMorpherPhenome::x) + switch(val) + { + HANDLE_PHENOME(A); + HANDLE_PHENOME(E); + HANDLE_PHENOME(I); + HANDLE_PHENOME(O); + HANDLE_PHENOME(U); + HANDLE_PHENOME(AA); + HANDLE_PHENOME(AE); + HANDLE_PHENOME(AH); + HANDLE_PHENOME(AO); + HANDLE_PHENOME(EH); + HANDLE_PHENOME(ER); + HANDLE_PHENOME(IH); + HANDLE_PHENOME(IY); + HANDLE_PHENOME(UH); + HANDLE_PHENOME(UW); + HANDLE_PHENOME(B); + HANDLE_PHENOME(D); + HANDLE_PHENOME(F); + HANDLE_PHENOME(G); + HANDLE_PHENOME(J); + HANDLE_PHENOME(K); + HANDLE_PHENOME(L); + HANDLE_PHENOME(M); + HANDLE_PHENOME(N); + HANDLE_PHENOME(P); + HANDLE_PHENOME(R); + HANDLE_PHENOME(S); + HANDLE_PHENOME(T); + HANDLE_PHENOME(V); + HANDLE_PHENOME(Z); + } + return al::nullopt; +#undef HANDLE_PHENOME +} +ALenum EnumFromPhenome(VMorpherPhenome phenome) +{ +#define HANDLE_PHENOME(x) case VMorpherPhenome::x: return AL_VOCAL_MORPHER_PHONEME_ ## x + switch(phenome) + { + HANDLE_PHENOME(A); + HANDLE_PHENOME(E); + HANDLE_PHENOME(I); + HANDLE_PHENOME(O); + HANDLE_PHENOME(U); + HANDLE_PHENOME(AA); + HANDLE_PHENOME(AE); + HANDLE_PHENOME(AH); + HANDLE_PHENOME(AO); + HANDLE_PHENOME(EH); + HANDLE_PHENOME(ER); + HANDLE_PHENOME(IH); + HANDLE_PHENOME(IY); + HANDLE_PHENOME(UH); + HANDLE_PHENOME(UW); + HANDLE_PHENOME(B); + HANDLE_PHENOME(D); + HANDLE_PHENOME(F); + HANDLE_PHENOME(G); + HANDLE_PHENOME(J); + HANDLE_PHENOME(K); + HANDLE_PHENOME(L); + HANDLE_PHENOME(M); + HANDLE_PHENOME(N); + HANDLE_PHENOME(P); + HANDLE_PHENOME(R); + HANDLE_PHENOME(S); + HANDLE_PHENOME(T); + HANDLE_PHENOME(V); + HANDLE_PHENOME(Z); + } + throw std::runtime_error{"Invalid phenome: "+std::to_string(static_cast(phenome))}; +#undef HANDLE_PHENOME +} + +al::optional WaveformFromEmum(ALenum value) +{ + switch(value) + { + case AL_VOCAL_MORPHER_WAVEFORM_SINUSOID: return al::make_optional(VMorpherWaveform::Sinusoid); + case AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE: return al::make_optional(VMorpherWaveform::Triangle); + case AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH: return al::make_optional(VMorpherWaveform::Sawtooth); + } + return al::nullopt; +} +ALenum EnumFromWaveform(VMorpherWaveform type) +{ + switch(type) + { + case VMorpherWaveform::Sinusoid: return AL_VOCAL_MORPHER_WAVEFORM_SINUSOID; + 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))}; +} + +void Vmorpher_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_VOCAL_MORPHER_PHONEMEA: + if(auto phenomeopt = PhenomeFromEnum(val)) + props->Vmorpher.PhonemeA = *phenomeopt; + else + throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: 0x%04x", val}; + break; + + case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: + if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING)) + throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a coarse tuning out of range"}; + props->Vmorpher.PhonemeACoarseTuning = val; + break; + + case AL_VOCAL_MORPHER_PHONEMEB: + if(auto phenomeopt = PhenomeFromEnum(val)) + props->Vmorpher.PhonemeB = *phenomeopt; + else + throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: 0x%04x", val}; + break; + + case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: + if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING)) + throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b coarse tuning out of range"}; + props->Vmorpher.PhonemeBCoarseTuning = val; + break; + + case AL_VOCAL_MORPHER_WAVEFORM: + if(auto formopt = WaveformFromEmum(val)) + props->Vmorpher.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}; + } +} +void Vmorpher_setParamiv(EffectProps*, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", + param}; +} +void Vmorpher_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_VOCAL_MORPHER_RATE: + if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE)) + throw effect_exception{AL_INVALID_VALUE, "Vocal morpher rate out of range"}; + props->Vmorpher.Rate = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x", + param}; + } +} +void Vmorpher_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Vmorpher_setParamf(props, param, vals[0]); } + +void Vmorpher_getParami(const EffectProps *props, ALenum param, int* val) +{ + switch(param) + { + case AL_VOCAL_MORPHER_PHONEMEA: + *val = EnumFromPhenome(props->Vmorpher.PhonemeA); + break; + + case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: + *val = props->Vmorpher.PhonemeACoarseTuning; + break; + + case AL_VOCAL_MORPHER_PHONEMEB: + *val = EnumFromPhenome(props->Vmorpher.PhonemeB); + break; + + case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: + *val = props->Vmorpher.PhonemeBCoarseTuning; + break; + + case AL_VOCAL_MORPHER_WAVEFORM: + *val = EnumFromWaveform(props->Vmorpher.Waveform); + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", + param}; + } +} +void Vmorpher_getParamiv(const EffectProps*, ALenum param, int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", + param}; +} +void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_VOCAL_MORPHER_RATE: + *val = props->Vmorpher.Rate; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x", + param}; + } +} +void Vmorpher_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Vmorpher_getParamf(props, param, vals); } + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Vmorpher.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE; + props.Vmorpher.PhonemeA = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA); + props.Vmorpher.PhonemeB = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEB); + props.Vmorpher.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING; + props.Vmorpher.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING; + props.Vmorpher.Waveform = *WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM); + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Vmorpher); + +const EffectProps VmorpherEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxVocalMorpherEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxVocalMorpherEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeA : 1; + EaxVocalMorpherEffectDirtyFlagsValue lPhonemeACoarseTuning : 1; + EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeB : 1; + EaxVocalMorpherEffectDirtyFlagsValue lPhonemeBCoarseTuning : 1; + EaxVocalMorpherEffectDirtyFlagsValue ulWaveform : 1; + EaxVocalMorpherEffectDirtyFlagsValue flRate : 1; +}; // EaxPitchShifterEffectDirtyFlags + + +class EaxVocalMorpherEffect final : + public EaxEffect +{ +public: + EaxVocalMorpherEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXVOCALMORPHERPROPERTIES eax_{}; + EAXVOCALMORPHERPROPERTIES eax_d_{}; + EaxVocalMorpherEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_phoneme_a(); + void set_efx_phoneme_a_coarse_tuning(); + void set_efx_phoneme_b(); + void set_efx_phoneme_b_coarse_tuning(); + void set_efx_waveform(); + void set_efx_rate(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_phoneme_a(unsigned long ulPhonemeA); + void validate_phoneme_a_coarse_tuning(long lPhonemeACoarseTuning); + void validate_phoneme_b(unsigned long ulPhonemeB); + void validate_phoneme_b_coarse_tuning(long lPhonemeBCoarseTuning); + void validate_waveform(unsigned long ulWaveform); + void validate_rate(float flRate); + void validate_all(const EAXVOCALMORPHERPROPERTIES& all); + + void defer_phoneme_a(unsigned long ulPhonemeA); + void defer_phoneme_a_coarse_tuning(long lPhonemeACoarseTuning); + void defer_phoneme_b(unsigned long ulPhonemeB); + void defer_phoneme_b_coarse_tuning(long lPhonemeBCoarseTuning); + void defer_waveform(unsigned long ulWaveform); + void defer_rate(float flRate); + void defer_all(const EAXVOCALMORPHERPROPERTIES& all); + + void defer_phoneme_a(const EaxEaxCall& eax_call); + void defer_phoneme_a_coarse_tuning(const EaxEaxCall& eax_call); + void defer_phoneme_b(const EaxEaxCall& eax_call); + void defer_phoneme_b_coarse_tuning(const EaxEaxCall& eax_call); + void defer_waveform(const EaxEaxCall& eax_call); + void defer_rate(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxVocalMorpherEffect + + +class EaxVocalMorpherEffectException : + public EaxException +{ +public: + explicit EaxVocalMorpherEffectException( + const char* message) + : + EaxException{"EAX_VOCAL_MORPHER_EFFECT", message} + { + } +}; // EaxVocalMorpherEffectException + + +EaxVocalMorpherEffect::EaxVocalMorpherEffect() + : EaxEffect{AL_EFFECT_VOCAL_MORPHER} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxVocalMorpherEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxVocalMorpherEffect::set_eax_defaults() +{ + eax_.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA; + eax_.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING; + eax_.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB; + eax_.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING; + eax_.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM; + eax_.flRate = EAXVOCALMORPHER_DEFAULTRATE; + + eax_d_ = eax_; +} + +void EaxVocalMorpherEffect::set_efx_phoneme_a() +{ + const auto phoneme_a = clamp( + static_cast(eax_.ulPhonemeA), + AL_VOCAL_MORPHER_MIN_PHONEMEA, + AL_VOCAL_MORPHER_MAX_PHONEMEA); + + const auto efx_phoneme_a = PhenomeFromEnum(phoneme_a); + assert(efx_phoneme_a.has_value()); + al_effect_props_.Vmorpher.PhonemeA = *efx_phoneme_a; +} + +void EaxVocalMorpherEffect::set_efx_phoneme_a_coarse_tuning() +{ + const auto phoneme_a_coarse_tuning = clamp( + static_cast(eax_.lPhonemeACoarseTuning), + AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING, + AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING); + + al_effect_props_.Vmorpher.PhonemeACoarseTuning = phoneme_a_coarse_tuning; +} + +void EaxVocalMorpherEffect::set_efx_phoneme_b() +{ + const auto phoneme_b = clamp( + static_cast(eax_.ulPhonemeB), + AL_VOCAL_MORPHER_MIN_PHONEMEB, + AL_VOCAL_MORPHER_MAX_PHONEMEB); + + const auto efx_phoneme_b = PhenomeFromEnum(phoneme_b); + assert(efx_phoneme_b.has_value()); + al_effect_props_.Vmorpher.PhonemeB = *efx_phoneme_b; +} + +void EaxVocalMorpherEffect::set_efx_phoneme_b_coarse_tuning() +{ + const auto phoneme_b_coarse_tuning = clamp( + static_cast(eax_.lPhonemeBCoarseTuning), + AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING, + AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING); + + al_effect_props_.Vmorpher.PhonemeBCoarseTuning = phoneme_b_coarse_tuning; +} + +void EaxVocalMorpherEffect::set_efx_waveform() +{ + const auto waveform = clamp( + static_cast(eax_.ulWaveform), + AL_VOCAL_MORPHER_MIN_WAVEFORM, + AL_VOCAL_MORPHER_MAX_WAVEFORM); + + const auto wfx_waveform = WaveformFromEmum(waveform); + assert(wfx_waveform.has_value()); + al_effect_props_.Vmorpher.Waveform = *wfx_waveform; +} + +void EaxVocalMorpherEffect::set_efx_rate() +{ + const auto rate = clamp( + eax_.flRate, + AL_VOCAL_MORPHER_MIN_RATE, + AL_VOCAL_MORPHER_MAX_RATE); + + al_effect_props_.Vmorpher.Rate = rate; +} + +void EaxVocalMorpherEffect::set_efx_defaults() +{ + set_efx_phoneme_a(); + set_efx_phoneme_a_coarse_tuning(); + set_efx_phoneme_b(); + set_efx_phoneme_b_coarse_tuning(); + set_efx_waveform(); + set_efx_rate(); +} + +void EaxVocalMorpherEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXVOCALMORPHER_NONE: + break; + + case EAXVOCALMORPHER_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXVOCALMORPHER_PHONEMEA: + eax_call.set_value(eax_.ulPhonemeA); + break; + + case EAXVOCALMORPHER_PHONEMEACOARSETUNING: + eax_call.set_value(eax_.lPhonemeACoarseTuning); + break; + + case EAXVOCALMORPHER_PHONEMEB: + eax_call.set_value(eax_.ulPhonemeB); + break; + + case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: + eax_call.set_value(eax_.lPhonemeBCoarseTuning); + break; + + case EAXVOCALMORPHER_WAVEFORM: + eax_call.set_value(eax_.ulWaveform); + break; + + case EAXVOCALMORPHER_RATE: + eax_call.set_value(eax_.flRate); + break; + + default: + throw EaxVocalMorpherEffectException{"Unsupported property id."}; + } +} + +void EaxVocalMorpherEffect::validate_phoneme_a( + unsigned long ulPhonemeA) +{ + eax_validate_range( + "Phoneme A", + ulPhonemeA, + EAXVOCALMORPHER_MINPHONEMEA, + EAXVOCALMORPHER_MAXPHONEMEA); +} + +void EaxVocalMorpherEffect::validate_phoneme_a_coarse_tuning( + long lPhonemeACoarseTuning) +{ + eax_validate_range( + "Phoneme A Coarse Tuning", + lPhonemeACoarseTuning, + EAXVOCALMORPHER_MINPHONEMEACOARSETUNING, + EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING); +} + +void EaxVocalMorpherEffect::validate_phoneme_b( + unsigned long ulPhonemeB) +{ + eax_validate_range( + "Phoneme B", + ulPhonemeB, + EAXVOCALMORPHER_MINPHONEMEB, + EAXVOCALMORPHER_MAXPHONEMEB); +} + +void EaxVocalMorpherEffect::validate_phoneme_b_coarse_tuning( + long lPhonemeBCoarseTuning) +{ + eax_validate_range( + "Phoneme B Coarse Tuning", + lPhonemeBCoarseTuning, + EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING, + EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING); +} + +void EaxVocalMorpherEffect::validate_waveform( + unsigned long ulWaveform) +{ + eax_validate_range( + "Waveform", + ulWaveform, + EAXVOCALMORPHER_MINWAVEFORM, + EAXVOCALMORPHER_MAXWAVEFORM); +} + +void EaxVocalMorpherEffect::validate_rate( + float flRate) +{ + eax_validate_range( + "Rate", + flRate, + EAXVOCALMORPHER_MINRATE, + EAXVOCALMORPHER_MAXRATE); +} + +void EaxVocalMorpherEffect::validate_all( + const EAXVOCALMORPHERPROPERTIES& all) +{ + validate_phoneme_a(all.ulPhonemeA); + validate_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning); + validate_phoneme_b(all.ulPhonemeB); + validate_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning); + validate_waveform(all.ulWaveform); + validate_rate(all.flRate); +} + +void EaxVocalMorpherEffect::defer_phoneme_a( + unsigned long ulPhonemeA) +{ + eax_d_.ulPhonemeA = ulPhonemeA; + eax_dirty_flags_.ulPhonemeA = (eax_.ulPhonemeA != eax_d_.ulPhonemeA); +} + +void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning( + long lPhonemeACoarseTuning) +{ + eax_d_.lPhonemeACoarseTuning = lPhonemeACoarseTuning; + eax_dirty_flags_.lPhonemeACoarseTuning = (eax_.lPhonemeACoarseTuning != eax_d_.lPhonemeACoarseTuning); +} + +void EaxVocalMorpherEffect::defer_phoneme_b( + unsigned long ulPhonemeB) +{ + eax_d_.ulPhonemeB = ulPhonemeB; + eax_dirty_flags_.ulPhonemeB = (eax_.ulPhonemeB != eax_d_.ulPhonemeB); +} + +void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning( + long lPhonemeBCoarseTuning) +{ + eax_d_.lPhonemeBCoarseTuning = lPhonemeBCoarseTuning; + eax_dirty_flags_.lPhonemeBCoarseTuning = (eax_.lPhonemeBCoarseTuning != eax_d_.lPhonemeBCoarseTuning); +} + +void EaxVocalMorpherEffect::defer_waveform( + unsigned long ulWaveform) +{ + eax_d_.ulWaveform = ulWaveform; + eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); +} + +void EaxVocalMorpherEffect::defer_rate( + float flRate) +{ + eax_d_.flRate = flRate; + eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate); +} + +void EaxVocalMorpherEffect::defer_all( + const EAXVOCALMORPHERPROPERTIES& all) +{ + defer_phoneme_a(all.ulPhonemeA); + defer_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning); + defer_phoneme_b(all.ulPhonemeB); + defer_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning); + defer_waveform(all.ulWaveform); + defer_rate(all.flRate); +} + +void EaxVocalMorpherEffect::defer_phoneme_a( + const EaxEaxCall& eax_call) +{ + const auto& phoneme_a = eax_call.get_value(); + + validate_phoneme_a(phoneme_a); + defer_phoneme_a(phoneme_a); +} + +void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning( + const EaxEaxCall& eax_call) +{ + const auto& phoneme_a_coarse_tuning = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeACoarseTuning) + >(); + + validate_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning); + defer_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning); +} + +void EaxVocalMorpherEffect::defer_phoneme_b( + const EaxEaxCall& eax_call) +{ + const auto& phoneme_b = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::ulPhonemeB) + >(); + + validate_phoneme_b(phoneme_b); + defer_phoneme_b(phoneme_b); +} + +void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning( + const EaxEaxCall& eax_call) +{ + const auto& phoneme_b_coarse_tuning = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeBCoarseTuning) + >(); + + validate_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning); + defer_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning); +} + +void EaxVocalMorpherEffect::defer_waveform( + const EaxEaxCall& eax_call) +{ + const auto& waveform = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::ulWaveform) + >(); + + validate_waveform(waveform); + defer_waveform(waveform); +} + +void EaxVocalMorpherEffect::defer_rate( + const EaxEaxCall& eax_call) +{ + const auto& rate = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::flRate) + >(); + + validate_rate(rate); + defer_rate(rate); +} + +void EaxVocalMorpherEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = eax_call.get_value< + EaxVocalMorpherEffectException, + const EAXVOCALMORPHERPROPERTIES + >(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxVocalMorpherEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxVocalMorpherEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulPhonemeA) + { + set_efx_phoneme_a(); + } + + if (eax_dirty_flags_.lPhonemeACoarseTuning) + { + set_efx_phoneme_a_coarse_tuning(); + } + + if (eax_dirty_flags_.ulPhonemeB) + { + set_efx_phoneme_b(); + } + + if (eax_dirty_flags_.lPhonemeBCoarseTuning) + { + set_efx_phoneme_b_coarse_tuning(); + } + + if (eax_dirty_flags_.ulWaveform) + { + set_efx_waveform(); + } + + if (eax_dirty_flags_.flRate) + { + set_efx_rate(); + } + + eax_dirty_flags_ = EaxVocalMorpherEffectDirtyFlags{}; + + return true; +} + +void EaxVocalMorpherEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXVOCALMORPHER_NONE: + break; + + case EAXVOCALMORPHER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXVOCALMORPHER_PHONEMEA: + defer_phoneme_a(eax_call); + break; + + case EAXVOCALMORPHER_PHONEMEACOARSETUNING: + defer_phoneme_a_coarse_tuning(eax_call); + break; + + case EAXVOCALMORPHER_PHONEMEB: + defer_phoneme_b(eax_call); + break; + + case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: + defer_phoneme_b_coarse_tuning(eax_call); + break; + + case EAXVOCALMORPHER_WAVEFORM: + defer_waveform(eax_call); + break; + + case EAXVOCALMORPHER_RATE: + defer_rate(eax_call); + break; + + default: + throw EaxVocalMorpherEffectException{"Unsupported property id."}; + } +} + +} // namespace + + +EaxEffectUPtr eax_create_eax_vocal_morpher_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/OpenAL32/alError.cpp b/modules/openal-soft/al/error.cpp similarity index 71% rename from modules/openal-soft/OpenAL32/alError.cpp rename to modules/openal-soft/al/error.cpp index 324f4c1..9067101 100644 --- a/modules/openal-soft/OpenAL32/alError.cpp +++ b/modules/openal-soft/al/error.cpp @@ -20,23 +20,32 @@ #include "config.h" +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#include #include #include #include +#include +#include -#ifdef HAVE_WINDOWS_H -#define WIN32_LEAN_AND_MEAN -#include -#endif +#include "AL/al.h" +#include "AL/alc.h" + +#include "alc/context.h" +#include "almalloc.h" +#include "core/except.h" +#include "core/logging.h" +#include "opthelpers.h" +#include "vector.h" -#include "alMain.h" -#include "alcontext.h" -#include "alError.h" -#include "alexcpt.h" -ALboolean TrapALError = AL_FALSE; +bool TrapALError{false}; -void alSetError(ALCcontext *context, ALenum errorCode, const char *msg, ...) +void ALCcontext::setError(ALenum errorCode, const char *msg, ...) { auto message = al::vector(256); @@ -54,9 +63,9 @@ void alSetError(ALCcontext *context, ALenum errorCode, const char *msg, ...) if(msglen >= 0) msg = message.data(); else msg = ""; - msglen = static_cast(strlen(msg)); - WARN("Error generated on context %p, code 0x%04x, \"%s\"\n", context, errorCode, msg); + WARN("Error generated on context %p, code 0x%04x, \"%s\"\n", + decltype(std::declval()){this}, errorCode, msg); if(TrapALError) { #ifdef _WIN32 @@ -69,24 +78,16 @@ void alSetError(ALCcontext *context, ALenum errorCode, const char *msg, ...) } ALenum curerr{AL_NO_ERROR}; - context->LastError.compare_exchange_strong(curerr, errorCode); - if((context->EnabledEvts.load(std::memory_order_relaxed)&EventType_Error)) - { - std::lock_guard _{context->EventCbLock}; - ALbitfieldSOFT enabledevts{context->EnabledEvts.load(std::memory_order_relaxed)}; - if((enabledevts&EventType_Error) && context->EventCb) - (*context->EventCb)(AL_EVENT_TYPE_ERROR_SOFT, 0, errorCode, msglen, msg, - context->EventParam); - } + mLastError.compare_exchange_strong(curerr, errorCode); } AL_API ALenum AL_APIENTRY alGetError(void) START_API_FUNC { ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) + if(unlikely(!context)) { - constexpr ALenum deferror{AL_INVALID_OPERATION}; + static constexpr ALenum deferror{AL_INVALID_OPERATION}; WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror); if(TrapALError) { @@ -100,6 +101,6 @@ START_API_FUNC return deferror; } - return context->LastError.exchange(AL_NO_ERROR); + return context->mLastError.exchange(AL_NO_ERROR); } END_API_FUNC diff --git a/modules/openal-soft/al/event.cpp b/modules/openal-soft/al/event.cpp new file mode 100644 index 0000000..e5923c4 --- /dev/null +++ b/modules/openal-soft/al/event.cpp @@ -0,0 +1,220 @@ + +#include "config.h" + +#include "event.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" + +#include "albyte.h" +#include "alc/context.h" +#include "alc/effects/base.h" +#include "alc/inprogext.h" +#include "almalloc.h" +#include "core/async_event.h" +#include "core/except.h" +#include "core/logging.h" +#include "core/voice_change.h" +#include "opthelpers.h" +#include "ringbuffer.h" +#include "threads.h" + + +static int EventThread(ALCcontext *context) +{ + RingBuffer *ring{context->mAsyncEvents.get()}; + bool quitnow{false}; + while(likely(!quitnow)) + { + auto evt_data = ring->getReadVector().first; + if(evt_data.len == 0) + { + context->mEventSem.wait(); + continue; + } + + std::lock_guard _{context->mEventCbLock}; + do { + auto *evt_ptr = reinterpret_cast(evt_data.buf); + evt_data.buf += sizeof(AsyncEvent); + evt_data.len -= 1; + + AsyncEvent evt{*evt_ptr}; + al::destroy_at(evt_ptr); + ring->readAdvance(1); + + quitnow = evt.EnumType == AsyncEvent::KillThread; + if(unlikely(quitnow)) break; + + if(evt.EnumType == AsyncEvent::ReleaseEffectState) + { + evt.u.mEffectState->release(); + continue; + } + + uint enabledevts{context->mEnabledEvts.load(std::memory_order_acquire)}; + if(!context->mEventCb) continue; + + if(evt.EnumType == AsyncEvent::SourceStateChange) + { + if(!(enabledevts&AsyncEvent::SourceStateChange)) + continue; + ALuint state{}; + std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)}; + msg += " state has changed to "; + switch(evt.u.srcstate.state) + { + case AsyncEvent::SrcState::Reset: + msg += "AL_INITIAL"; + state = AL_INITIAL; + break; + case AsyncEvent::SrcState::Stop: + msg += "AL_STOPPED"; + state = AL_STOPPED; + break; + case AsyncEvent::SrcState::Play: + msg += "AL_PLAYING"; + state = AL_PLAYING; + break; + case AsyncEvent::SrcState::Pause: + msg += "AL_PAUSED"; + state = AL_PAUSED; + break; + } + context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id, + state, static_cast(msg.length()), msg.c_str(), context->mEventParam); + } + else if(evt.EnumType == AsyncEvent::BufferCompleted) + { + if(!(enabledevts&AsyncEvent::BufferCompleted)) + continue; + std::string msg{std::to_string(evt.u.bufcomp.count)}; + if(evt.u.bufcomp.count == 1) msg += " buffer completed"; + else msg += " buffers completed"; + context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.u.bufcomp.id, + evt.u.bufcomp.count, static_cast(msg.length()), msg.c_str(), + context->mEventParam); + } + else if(evt.EnumType == AsyncEvent::Disconnected) + { + if(!(enabledevts&AsyncEvent::Disconnected)) + continue; + context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, + static_cast(strlen(evt.u.disconnect.msg)), evt.u.disconnect.msg, + context->mEventParam); + } + } while(evt_data.len != 0); + } + return 0; +} + +void StartEventThrd(ALCcontext *ctx) +{ + try { + ctx->mEventThread = std::thread{EventThread, ctx}; + } + catch(std::exception& e) { + ERR("Failed to start event thread: %s\n", e.what()); + } + catch(...) { + ERR("Failed to start event thread! Expect problems.\n"); + } +} + +void StopEventThrd(ALCcontext *ctx) +{ + RingBuffer *ring{ctx->mAsyncEvents.get()}; + auto evt_data = ring->getWriteVector().first; + if(evt_data.len == 0) + { + do { + std::this_thread::yield(); + evt_data = ring->getWriteVector().first; + } while(evt_data.len == 0); + } + al::construct_at(reinterpret_cast(evt_data.buf), AsyncEvent::KillThread); + ring->writeAdvance(1); + + ctx->mEventSem.post(); + if(ctx->mEventThread.joinable()) + ctx->mEventThread.join(); +} + +AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if(unlikely(!context)) return; + + if(count < 0) context->setError(AL_INVALID_VALUE, "Controlling %d events", count); + if(count <= 0) return; + if(!types) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer"); + + uint flags{0}; + const ALenum *types_end = types+count; + auto bad_type = std::find_if_not(types, types_end, + [&flags](ALenum type) noexcept -> bool + { + if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT) + flags |= AsyncEvent::BufferCompleted; + else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT) + flags |= AsyncEvent::SourceStateChange; + else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT) + flags |= AsyncEvent::Disconnected; + else + return false; + return true; + } + ); + if(bad_type != types_end) + SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid event type 0x%04x", *bad_type); + + if(enable) + { + uint enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)}; + while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts|flags, + std::memory_order_acq_rel, std::memory_order_acquire) == 0) + { + /* enabledevts is (re-)filled with the current value on failure, so + * just try again. + */ + } + } + else + { + uint enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)}; + while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts&~flags, + std::memory_order_acq_rel, std::memory_order_acquire) == 0) + { + } + /* Wait to ensure the event handler sees the changed flags before + * returning. + */ + std::lock_guard _{context->mEventCbLock}; + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if(unlikely(!context)) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mEventCbLock}; + context->mEventCb = callback; + context->mEventParam = userParam; +} +END_API_FUNC diff --git a/modules/openal-soft/al/event.h b/modules/openal-soft/al/event.h new file mode 100644 index 0000000..83513c5 --- /dev/null +++ b/modules/openal-soft/al/event.h @@ -0,0 +1,9 @@ +#ifndef AL_EVENT_H +#define AL_EVENT_H + +struct ALCcontext; + +void StartEventThrd(ALCcontext *ctx); +void StopEventThrd(ALCcontext *ctx); + +#endif diff --git a/modules/openal-soft/OpenAL32/alExtension.cpp b/modules/openal-soft/al/extension.cpp similarity index 83% rename from modules/openal-soft/OpenAL32/alExtension.cpp rename to modules/openal-soft/al/extension.cpp index dda2a62..5dda2a8 100644 --- a/modules/openal-soft/OpenAL32/alExtension.cpp +++ b/modules/openal-soft/al/extension.cpp @@ -20,33 +20,33 @@ #include "config.h" +#include #include #include -#include #include "AL/al.h" #include "AL/alc.h" -#include "alMain.h" -#include "alcontext.h" -#include "alError.h" -#include "alexcpt.h" +#include "alc/context.h" +#include "alstring.h" +#include "core/except.h" +#include "opthelpers.h" + AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName) START_API_FUNC { ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return AL_FALSE; + if(unlikely(!context)) return AL_FALSE; if(!extName) - SETERR_RETURN(context.get(), AL_INVALID_VALUE, AL_FALSE, "NULL pointer"); + SETERR_RETURN(context, AL_INVALID_VALUE, AL_FALSE, "NULL pointer"); size_t len{strlen(extName)}; - const char *ptr{context->ExtensionList}; + const char *ptr{context->mExtensionList}; while(ptr && *ptr) { - if(strncasecmp(ptr, extName, len) == 0 && - (ptr[len] == '\0' || isspace(ptr[len]))) + if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len]))) return AL_TRUE; if((ptr=strchr(ptr, ' ')) != nullptr) diff --git a/modules/openal-soft/al/filter.cpp b/modules/openal-soft/al/filter.cpp new file mode 100644 index 0000000..f4b0ac9 --- /dev/null +++ b/modules/openal-soft/al/filter.cpp @@ -0,0 +1,717 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "filter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/efx.h" + +#include "albit.h" +#include "alc/context.h" +#include "alc/device.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "core/except.h" +#include "opthelpers.h" +#include "vector.h" + + +namespace { + +class filter_exception final : public al::base_exception { + ALenum mErrorCode; + +public: +#ifdef __USE_MINGW_ANSI_STDIO + [[gnu::format(gnu_printf, 3, 4)]] +#else + [[gnu::format(printf, 3, 4)]] +#endif + filter_exception(ALenum code, const char *msg, ...) : mErrorCode{code} + { + std::va_list args; + va_start(args, msg); + setMessage(msg, args); + va_end(args); + } + ALenum errorCode() const noexcept { return mErrorCode; } +}; + + +#define DEFINE_ALFILTER_VTABLE(T) \ +const ALfilter::Vtable T##_vtable = { \ + T##_setParami, T##_setParamiv, T##_setParamf, T##_setParamfv, \ + T##_getParami, T##_getParamiv, T##_getParamf, T##_getParamfv, \ +} + +void ALlowpass_setParami(ALfilter*, ALenum param, int) +{ throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } +void ALlowpass_setParamiv(ALfilter*, ALenum param, const int*) +{ + throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", + param}; +} +void ALlowpass_setParamf(ALfilter *filter, ALenum param, float val) +{ + switch(param) + { + case AL_LOWPASS_GAIN: + if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN)) + throw filter_exception{AL_INVALID_VALUE, "Low-pass gain %f out of range", val}; + filter->Gain = val; + break; + + case AL_LOWPASS_GAINHF: + if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF)) + throw filter_exception{AL_INVALID_VALUE, "Low-pass gainhf %f out of range", val}; + filter->GainHF = val; + break; + + default: + throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; + } +} +void ALlowpass_setParamfv(ALfilter *filter, ALenum param, const float *vals) +{ ALlowpass_setParamf(filter, param, vals[0]); } + +void ALlowpass_getParami(const ALfilter*, ALenum param, int*) +{ throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } +void ALlowpass_getParamiv(const ALfilter*, ALenum param, int*) +{ + throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", + param}; +} +void ALlowpass_getParamf(const ALfilter *filter, ALenum param, float *val) +{ + switch(param) + { + case AL_LOWPASS_GAIN: + *val = filter->Gain; + break; + + case AL_LOWPASS_GAINHF: + *val = filter->GainHF; + break; + + default: + throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; + } +} +void ALlowpass_getParamfv(const ALfilter *filter, ALenum param, float *vals) +{ ALlowpass_getParamf(filter, param, vals); } + +DEFINE_ALFILTER_VTABLE(ALlowpass); + + +void ALhighpass_setParami(ALfilter*, ALenum param, int) +{ throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } +void ALhighpass_setParamiv(ALfilter*, ALenum param, const int*) +{ + throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", + param}; +} +void ALhighpass_setParamf(ALfilter *filter, ALenum param, float val) +{ + switch(param) + { + case AL_HIGHPASS_GAIN: + if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN)) + throw filter_exception{AL_INVALID_VALUE, "High-pass gain %f out of range", val}; + filter->Gain = val; + break; + + case AL_HIGHPASS_GAINLF: + if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF)) + throw filter_exception{AL_INVALID_VALUE, "High-pass gainlf %f out of range", val}; + filter->GainLF = val; + break; + + default: + throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; + } +} +void ALhighpass_setParamfv(ALfilter *filter, ALenum param, const float *vals) +{ ALhighpass_setParamf(filter, param, vals[0]); } + +void ALhighpass_getParami(const ALfilter*, ALenum param, int*) +{ throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } +void ALhighpass_getParamiv(const ALfilter*, ALenum param, int*) +{ + throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", + param}; +} +void ALhighpass_getParamf(const ALfilter *filter, ALenum param, float *val) +{ + switch(param) + { + case AL_HIGHPASS_GAIN: + *val = filter->Gain; + break; + + case AL_HIGHPASS_GAINLF: + *val = filter->GainLF; + break; + + default: + throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; + } +} +void ALhighpass_getParamfv(const ALfilter *filter, ALenum param, float *vals) +{ ALhighpass_getParamf(filter, param, vals); } + +DEFINE_ALFILTER_VTABLE(ALhighpass); + + +void ALbandpass_setParami(ALfilter*, ALenum param, int) +{ throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } +void ALbandpass_setParamiv(ALfilter*, ALenum param, const int*) +{ + throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", + param}; +} +void ALbandpass_setParamf(ALfilter *filter, ALenum param, float val) +{ + switch(param) + { + case AL_BANDPASS_GAIN: + if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN)) + throw filter_exception{AL_INVALID_VALUE, "Band-pass gain %f out of range", val}; + filter->Gain = val; + break; + + case AL_BANDPASS_GAINHF: + if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF)) + throw filter_exception{AL_INVALID_VALUE, "Band-pass gainhf %f out of range", val}; + filter->GainHF = val; + break; + + case AL_BANDPASS_GAINLF: + if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF)) + throw filter_exception{AL_INVALID_VALUE, "Band-pass gainlf %f out of range", val}; + filter->GainLF = val; + break; + + default: + throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; + } +} +void ALbandpass_setParamfv(ALfilter *filter, ALenum param, const float *vals) +{ ALbandpass_setParamf(filter, param, vals[0]); } + +void ALbandpass_getParami(const ALfilter*, ALenum param, int*) +{ throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } +void ALbandpass_getParamiv(const ALfilter*, ALenum param, int*) +{ + throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", + param}; +} +void ALbandpass_getParamf(const ALfilter *filter, ALenum param, float *val) +{ + switch(param) + { + case AL_BANDPASS_GAIN: + *val = filter->Gain; + break; + + case AL_BANDPASS_GAINHF: + *val = filter->GainHF; + break; + + case AL_BANDPASS_GAINLF: + *val = filter->GainLF; + break; + + default: + throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; + } +} +void ALbandpass_getParamfv(const ALfilter *filter, ALenum param, float *vals) +{ ALbandpass_getParamf(filter, param, vals); } + +DEFINE_ALFILTER_VTABLE(ALbandpass); + + +void ALnullfilter_setParami(ALfilter*, ALenum param, int) +{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void ALnullfilter_setParamiv(ALfilter*, ALenum param, const int*) +{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void ALnullfilter_setParamf(ALfilter*, ALenum param, float) +{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void ALnullfilter_setParamfv(ALfilter*, ALenum param, const float*) +{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } + +void ALnullfilter_getParami(const ALfilter*, ALenum param, int*) +{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void ALnullfilter_getParamiv(const ALfilter*, ALenum param, int*) +{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void ALnullfilter_getParamf(const ALfilter*, ALenum param, float*) +{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void ALnullfilter_getParamfv(const ALfilter*, ALenum param, float*) +{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } + +DEFINE_ALFILTER_VTABLE(ALnullfilter); + + +void InitFilterParams(ALfilter *filter, ALenum type) +{ + if(type == AL_FILTER_LOWPASS) + { + filter->Gain = AL_LOWPASS_DEFAULT_GAIN; + filter->GainHF = AL_LOWPASS_DEFAULT_GAINHF; + filter->HFReference = LOWPASSFREQREF; + filter->GainLF = 1.0f; + filter->LFReference = HIGHPASSFREQREF; + filter->vtab = &ALlowpass_vtable; + } + else if(type == AL_FILTER_HIGHPASS) + { + filter->Gain = AL_HIGHPASS_DEFAULT_GAIN; + filter->GainHF = 1.0f; + filter->HFReference = LOWPASSFREQREF; + filter->GainLF = AL_HIGHPASS_DEFAULT_GAINLF; + filter->LFReference = HIGHPASSFREQREF; + filter->vtab = &ALhighpass_vtable; + } + else if(type == AL_FILTER_BANDPASS) + { + filter->Gain = AL_BANDPASS_DEFAULT_GAIN; + filter->GainHF = AL_BANDPASS_DEFAULT_GAINHF; + filter->HFReference = LOWPASSFREQREF; + filter->GainLF = AL_BANDPASS_DEFAULT_GAINLF; + filter->LFReference = HIGHPASSFREQREF; + filter->vtab = &ALbandpass_vtable; + } + else + { + filter->Gain = 1.0f; + filter->GainHF = 1.0f; + filter->HFReference = LOWPASSFREQREF; + filter->GainLF = 1.0f; + filter->LFReference = HIGHPASSFREQREF; + filter->vtab = &ALnullfilter_vtable; + } + filter->type = type; +} + +bool EnsureFilters(ALCdevice *device, size_t needed) +{ + size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), size_t{0}, + [](size_t cur, const FilterSubList &sublist) noexcept -> size_t + { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; + + while(needed > count) + { + if UNLIKELY(device->FilterList.size() >= 1<<25) + return false; + + device->FilterList.emplace_back(); + auto sublist = device->FilterList.end() - 1; + sublist->FreeMask = ~0_u64; + sublist->Filters = static_cast(al_calloc(alignof(ALfilter), sizeof(ALfilter)*64)); + if UNLIKELY(!sublist->Filters) + { + device->FilterList.pop_back(); + return false; + } + count += 64; + } + return true; +} + + +ALfilter *AllocFilter(ALCdevice *device) +{ + auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(), + [](const FilterSubList &entry) noexcept -> bool + { return entry.FreeMask != 0; }); + auto lidx = static_cast(std::distance(device->FilterList.begin(), sublist)); + auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); + ASSUME(slidx < 64); + + ALfilter *filter{al::construct_at(sublist->Filters + slidx)}; + InitFilterParams(filter, AL_FILTER_NULL); + + /* Add 1 to avoid filter ID 0. */ + filter->id = ((lidx<<6) | slidx) + 1; + + sublist->FreeMask &= ~(1_u64 << slidx); + + return filter; +} + +void FreeFilter(ALCdevice *device, ALfilter *filter) +{ + const ALuint id{filter->id - 1}; + const size_t lidx{id >> 6}; + const ALuint slidx{id & 0x3f}; + + al::destroy_at(filter); + + device->FilterList[lidx].FreeMask |= 1_u64 << slidx; +} + + +inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) +{ + const size_t lidx{(id-1) >> 6}; + const ALuint slidx{(id-1) & 0x3f}; + + if UNLIKELY(lidx >= device->FilterList.size()) + return nullptr; + FilterSubList &sublist = device->FilterList[lidx]; + if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + return nullptr; + return sublist.Filters + slidx; +} + +} // namespace + +AL_API void AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Generating %d filters", n); + if UNLIKELY(n <= 0) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->FilterLock}; + if(!EnsureFilters(device, static_cast(n))) + { + context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, (n==1)?"":"s"); + return; + } + + if LIKELY(n == 1) + { + /* Special handling for the easy and normal case. */ + ALfilter *filter{AllocFilter(device)}; + if(filter) filters[0] = filter->id; + } + else + { + /* Store the allocated buffer IDs in a separate local list, to avoid + * modifying the user storage in case of failure. + */ + al::vector ids; + ids.reserve(static_cast(n)); + do { + ALfilter *filter{AllocFilter(device)}; + ids.emplace_back(filter->id); + } while(--n); + std::copy(ids.begin(), ids.end(), filters); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Deleting %d filters", n); + if UNLIKELY(n <= 0) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->FilterLock}; + + /* First try to find any filters that are invalid. */ + auto validate_filter = [device](const ALuint fid) -> bool + { return !fid || LookupFilter(device, fid) != nullptr; }; + + const ALuint *filters_end = filters + n; + auto invflt = std::find_if_not(filters, filters_end, validate_filter); + if UNLIKELY(invflt != filters_end) + { + context->setError(AL_INVALID_NAME, "Invalid filter ID %u", *invflt); + return; + } + + /* All good. Delete non-0 filter IDs. */ + auto delete_filter = [device](const ALuint fid) -> void + { + ALfilter *filter{fid ? LookupFilter(device, fid) : nullptr}; + if(filter) FreeFilter(device, filter); + }; + std::for_each(filters, filters_end, delete_filter); +} +END_API_FUNC + +AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if LIKELY(context) + { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->FilterLock}; + if(!filter || LookupFilter(device, filter)) + return AL_TRUE; + } + return AL_FALSE; +} +END_API_FUNC + + +AL_API void AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->FilterLock}; + + ALfilter *alfilt{LookupFilter(device, filter)}; + if UNLIKELY(!alfilt) + context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); + else + { + if(param == AL_FILTER_TYPE) + { + if(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS + || value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS) + InitFilterParams(alfilt, value); + else + context->setError(AL_INVALID_VALUE, "Invalid filter type 0x%04x", value); + } + else try + { + /* Call the appropriate handler */ + alfilt->setParami(param, value); + } + catch(filter_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *values) +START_API_FUNC +{ + switch(param) + { + case AL_FILTER_TYPE: + alFilteri(filter, param, values[0]); + return; + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->FilterLock}; + + ALfilter *alfilt{LookupFilter(device, filter)}; + if UNLIKELY(!alfilt) + context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); + else try + { + /* Call the appropriate handler */ + alfilt->setParamiv(param, values); + } + catch(filter_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->FilterLock}; + + ALfilter *alfilt{LookupFilter(device, filter)}; + if UNLIKELY(!alfilt) + context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); + else try + { + /* Call the appropriate handler */ + alfilt->setParamf(param, value); + } + catch(filter_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *values) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->FilterLock}; + + ALfilter *alfilt{LookupFilter(device, filter)}; + if UNLIKELY(!alfilt) + context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); + else try + { + /* Call the appropriate handler */ + alfilt->setParamfv(param, values); + } + catch(filter_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->FilterLock}; + + const ALfilter *alfilt{LookupFilter(device, filter)}; + if UNLIKELY(!alfilt) + context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); + else + { + if(param == AL_FILTER_TYPE) + *value = alfilt->type; + else try + { + /* Call the appropriate handler */ + alfilt->getParami(param, value); + } + catch(filter_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *values) +START_API_FUNC +{ + switch(param) + { + case AL_FILTER_TYPE: + alGetFilteri(filter, param, values); + return; + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->FilterLock}; + + const ALfilter *alfilt{LookupFilter(device, filter)}; + if UNLIKELY(!alfilt) + context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); + else try + { + /* Call the appropriate handler */ + alfilt->getParamiv(param, values); + } + catch(filter_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->FilterLock}; + + const ALfilter *alfilt{LookupFilter(device, filter)}; + if UNLIKELY(!alfilt) + context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); + else try + { + /* Call the appropriate handler */ + alfilt->getParamf(param, value); + } + catch(filter_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *values) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard _{device->FilterLock}; + + const ALfilter *alfilt{LookupFilter(device, filter)}; + if UNLIKELY(!alfilt) + context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); + else try + { + /* Call the appropriate handler */ + alfilt->getParamfv(param, values); + } + catch(filter_exception &e) { + context->setError(e.errorCode(), "%s", e.what()); + } +} +END_API_FUNC + + +FilterSubList::~FilterSubList() +{ + uint64_t usemask{~FreeMask}; + while(usemask) + { + const int idx{al::countr_zero(usemask)}; + al::destroy_at(Filters+idx); + usemask &= ~(1_u64 << idx); + } + FreeMask = ~usemask; + al_free(Filters); + Filters = nullptr; +} diff --git a/modules/openal-soft/al/filter.h b/modules/openal-soft/al/filter.h new file mode 100644 index 0000000..65a9e30 --- /dev/null +++ b/modules/openal-soft/al/filter.h @@ -0,0 +1,52 @@ +#ifndef AL_FILTER_H +#define AL_FILTER_H + + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "almalloc.h" + +#define LOWPASSFREQREF 5000.0f +#define HIGHPASSFREQREF 250.0f + + +struct ALfilter { + ALenum type{AL_FILTER_NULL}; + + float Gain{1.0f}; + float GainHF{1.0f}; + float HFReference{LOWPASSFREQREF}; + float GainLF{1.0f}; + float LFReference{HIGHPASSFREQREF}; + + struct Vtable { + void (*const setParami )(ALfilter *filter, ALenum param, int val); + void (*const setParamiv)(ALfilter *filter, ALenum param, const int *vals); + void (*const setParamf )(ALfilter *filter, ALenum param, float val); + void (*const setParamfv)(ALfilter *filter, ALenum param, const float *vals); + + void (*const getParami )(const ALfilter *filter, ALenum param, int *val); + void (*const getParamiv)(const ALfilter *filter, ALenum param, int *vals); + void (*const getParamf )(const ALfilter *filter, ALenum param, float *val); + void (*const getParamfv)(const ALfilter *filter, ALenum param, float *vals); + }; + const Vtable *vtab{nullptr}; + + /* Self ID */ + ALuint id{0}; + + void setParami(ALenum param, int value) { vtab->setParami(this, param, value); } + void setParamiv(ALenum param, const int *values) { vtab->setParamiv(this, param, values); } + void setParamf(ALenum param, float value) { vtab->setParamf(this, param, value); } + void setParamfv(ALenum param, const float *values) { vtab->setParamfv(this, param, values); } + void getParami(ALenum param, int *value) const { vtab->getParami(this, param, value); } + void getParamiv(ALenum param, int *values) const { vtab->getParamiv(this, param, values); } + void getParamf(ALenum param, float *value) const { vtab->getParamf(this, param, value); } + void getParamfv(ALenum param, float *values) const { vtab->getParamfv(this, param, values); } + + DISABLE_ALLOC() +}; + +#endif diff --git a/modules/openal-soft/OpenAL32/alListener.cpp b/modules/openal-soft/al/listener.cpp similarity index 53% rename from modules/openal-soft/OpenAL32/alListener.cpp rename to modules/openal-soft/al/listener.cpp index 232aba3..9484d9b 100644 --- a/modules/openal-soft/OpenAL32/alListener.cpp +++ b/modules/openal-soft/al/listener.cpp @@ -20,93 +20,128 @@ #include "config.h" +#include "listener.h" + #include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/efx.h" -#include "alMain.h" -#include "alcontext.h" -#include "alu.h" -#include "alError.h" -#include "alListener.h" -#include "alSource.h" -#include "alexcpt.h" +#include "alc/context.h" +#include "almalloc.h" +#include "atomic.h" +#include "core/except.h" +#include "opthelpers.h" -#define DO_UPDATEPROPS() do { \ - if(!context->DeferUpdates.load(std::memory_order_acquire)) \ - UpdateListenerProps(context.get()); \ - else \ - listener.PropsClean.clear(std::memory_order_release); \ -} while(0) +namespace { + +inline void UpdateProps(ALCcontext *context) +{ + if(!context->mDeferUpdates) + { + UpdateContextProps(context); + return; + } + context->mPropsDirty = true; +} -AL_API ALvoid AL_APIENTRY alListenerf(ALenum param, ALfloat value) +#ifdef ALSOFT_EAX +inline void CommitAndUpdateProps(ALCcontext *context) +{ + if(!context->mDeferUpdates) + { + if(context->has_eax()) + { + context->mHoldUpdates.store(true, std::memory_order_release); + while((context->mUpdateCount.load(std::memory_order_acquire)&1) != 0) { + /* busy-wait */ + } + + context->eax_commit_and_update_sources(); + } + UpdateContextProps(context); + context->mHoldUpdates.store(false, std::memory_order_release); + return; + } + context->mPropsDirty = true; +} + +#else + +inline void CommitAndUpdateProps(ALCcontext *context) +{ UpdateProps(context); } +#endif + +} // namespace + +AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value) START_API_FUNC { ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; + if UNLIKELY(!context) return; - ALlistener &listener = context->Listener; - std::lock_guard _{context->PropLock}; + ALlistener &listener = context->mListener; + std::lock_guard _{context->mPropLock}; switch(param) { case AL_GAIN: if(!(value >= 0.0f && std::isfinite(value))) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Listener gain out of range"); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener gain out of range"); listener.Gain = value; - DO_UPDATEPROPS(); + UpdateProps(context.get()); break; case AL_METERS_PER_UNIT: if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT)) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, - "Listener meters per unit out of range"); - context->MetersPerUnit = value; - if(!context->DeferUpdates.load(std::memory_order_acquire)) - UpdateContextProps(context.get()); - else - context->PropsClean.clear(std::memory_order_release); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener meters per unit out of range"); + listener.mMetersPerUnit = value; + UpdateProps(context.get()); break; default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid listener float property"); + context->setError(AL_INVALID_ENUM, "Invalid listener float property"); } } END_API_FUNC -AL_API ALvoid AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) +AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) START_API_FUNC { ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; + if UNLIKELY(!context) return; - ALlistener &listener = context->Listener; - std::lock_guard _{context->PropLock}; + ALlistener &listener = context->mListener; + std::lock_guard _{context->mPropLock}; switch(param) { case AL_POSITION: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Listener position out of range"); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener position out of range"); listener.Position[0] = value1; listener.Position[1] = value2; listener.Position[2] = value3; - DO_UPDATEPROPS(); + CommitAndUpdateProps(context.get()); break; case AL_VELOCITY: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Listener velocity out of range"); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener velocity out of range"); listener.Velocity[0] = value1; listener.Velocity[1] = value2; listener.Velocity[2] = value3; - DO_UPDATEPROPS(); + CommitAndUpdateProps(context.get()); break; default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid listener 3-float property"); + context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property"); } } END_API_FUNC -AL_API ALvoid AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values) +AL_API void AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values) START_API_FUNC { if(values) @@ -126,17 +161,17 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; + if UNLIKELY(!context) return; - ALlistener &listener = context->Listener; - std::lock_guard _{context->PropLock}; - if(!values) SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "NULL pointer"); + ALlistener &listener = context->mListener; + std::lock_guard _{context->mPropLock}; + if(!values) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer"); switch(param) { case AL_ORIENTATION: if(!(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) && std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5]))) - SETERR_RETURN(context.get(), AL_INVALID_VALUE,, "Listener orientation out of range"); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener orientation out of range"); /* AT then UP */ listener.OrientAt[0] = values[0]; listener.OrientAt[1] = values[1]; @@ -144,27 +179,27 @@ START_API_FUNC listener.OrientUp[0] = values[3]; listener.OrientUp[1] = values[4]; listener.OrientUp[2] = values[5]; - DO_UPDATEPROPS(); + CommitAndUpdateProps(context.get()); break; default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid listener float-vector property"); + context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property"); } } END_API_FUNC -AL_API ALvoid AL_APIENTRY alListeneri(ALenum param, ALint UNUSED(value)) +AL_API void AL_APIENTRY alListeneri(ALenum param, ALint /*value*/) START_API_FUNC { ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; + if UNLIKELY(!context) return; - std::lock_guard _{context->PropLock}; + std::lock_guard _{context->mPropLock}; switch(param) { default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid listener integer property"); + context->setError(AL_INVALID_ENUM, "Invalid listener integer property"); } } END_API_FUNC @@ -181,13 +216,13 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; + if UNLIKELY(!context) return; - std::lock_guard _{context->PropLock}; + std::lock_guard _{context->mPropLock}; switch(param) { default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid listener 3-integer property"); + context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property"); } } END_API_FUNC @@ -218,30 +253,30 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; + if UNLIKELY(!context) return; - std::lock_guard _{context->PropLock}; + std::lock_guard _{context->mPropLock}; if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); + context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid listener integer-vector property"); + context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property"); } } END_API_FUNC -AL_API ALvoid AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value) +AL_API void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value) START_API_FUNC { ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; + if UNLIKELY(!context) return; - ALlistener &listener = context->Listener; - std::lock_guard _{context->PropLock}; + ALlistener &listener = context->mListener; + std::lock_guard _{context->mPropLock}; if(!value) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); + context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { case AL_GAIN: @@ -249,25 +284,25 @@ START_API_FUNC break; case AL_METERS_PER_UNIT: - *value = context->MetersPerUnit; + *value = listener.mMetersPerUnit; break; default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid listener float property"); + context->setError(AL_INVALID_ENUM, "Invalid listener float property"); } } END_API_FUNC -AL_API ALvoid AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) +AL_API void AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) START_API_FUNC { ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; + if UNLIKELY(!context) return; - ALlistener &listener = context->Listener; - std::lock_guard _{context->PropLock}; + ALlistener &listener = context->mListener; + std::lock_guard _{context->mPropLock}; if(!value1 || !value2 || !value3) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); + context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { case AL_POSITION: @@ -283,12 +318,12 @@ START_API_FUNC break; default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid listener 3-float property"); + context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property"); } } END_API_FUNC -AL_API ALvoid AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values) +AL_API void AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values) START_API_FUNC { switch(param) @@ -305,12 +340,12 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; + if UNLIKELY(!context) return; - ALlistener &listener = context->Listener; - std::lock_guard _{context->PropLock}; + ALlistener &listener = context->mListener; + std::lock_guard _{context->mPropLock}; if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); + context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { case AL_ORIENTATION: @@ -324,25 +359,25 @@ START_API_FUNC break; default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid listener float-vector property"); + context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property"); } } END_API_FUNC -AL_API ALvoid AL_APIENTRY alGetListeneri(ALenum param, ALint *value) +AL_API void AL_APIENTRY alGetListeneri(ALenum param, ALint *value) START_API_FUNC { ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; + if UNLIKELY(!context) return; - std::lock_guard _{context->PropLock}; + std::lock_guard _{context->mPropLock}; if(!value) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); + context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid listener integer property"); + context->setError(AL_INVALID_ENUM, "Invalid listener integer property"); } } END_API_FUNC @@ -351,12 +386,12 @@ AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *valu START_API_FUNC { ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; + if UNLIKELY(!context) return; - ALlistener &listener = context->Listener; - std::lock_guard _{context->PropLock}; + ALlistener &listener = context->mListener; + std::lock_guard _{context->mPropLock}; if(!value1 || !value2 || !value3) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); + context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { case AL_POSITION: @@ -372,7 +407,7 @@ START_API_FUNC break; default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid listener 3-integer property"); + context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property"); } } END_API_FUNC @@ -389,12 +424,12 @@ START_API_FUNC } ContextRef context{GetContextRef()}; - if(UNLIKELY(!context)) return; + if UNLIKELY(!context) return; - ALlistener &listener = context->Listener; - std::lock_guard _{context->PropLock}; + ALlistener &listener = context->mListener; + std::lock_guard _{context->mPropLock}; if(!values) - alSetError(context.get(), AL_INVALID_VALUE, "NULL pointer"); + context->setError(AL_INVALID_VALUE, "NULL pointer"); else switch(param) { case AL_ORIENTATION: @@ -408,42 +443,7 @@ START_API_FUNC break; default: - alSetError(context.get(), AL_INVALID_ENUM, "Invalid listener integer-vector property"); + context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property"); } } END_API_FUNC - - -void UpdateListenerProps(ALCcontext *context) -{ - /* Get an unused proprty container, or allocate a new one as needed. */ - ALlistenerProps *props{context->FreeListenerProps.load(std::memory_order_acquire)}; - if(!props) - props = static_cast(al_calloc(16, sizeof(*props))); - else - { - ALlistenerProps *next; - do { - next = props->next.load(std::memory_order_relaxed); - } while(context->FreeListenerProps.compare_exchange_weak(props, next, - std::memory_order_seq_cst, std::memory_order_acquire) == 0); - } - - /* Copy in current property values. */ - ALlistener &listener = context->Listener; - props->Position = listener.Position; - props->Velocity = listener.Velocity; - props->OrientAt = listener.OrientAt; - props->OrientUp = listener.OrientUp; - props->Gain = listener.Gain; - - /* Set the new container for updating internal parameters. */ - props = listener.Update.exchange(props, std::memory_order_acq_rel); - if(props) - { - /* If there was an unused update container, put it back in the - * freelist. - */ - AtomicReplaceHead(context->FreeListenerProps, props); - } -} diff --git a/modules/openal-soft/al/listener.h b/modules/openal-soft/al/listener.h new file mode 100644 index 0000000..8153287 --- /dev/null +++ b/modules/openal-soft/al/listener.h @@ -0,0 +1,24 @@ +#ifndef AL_LISTENER_H +#define AL_LISTENER_H + +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/efx.h" + +#include "almalloc.h" + + +struct ALlistener { + std::array Position{{0.0f, 0.0f, 0.0f}}; + std::array Velocity{{0.0f, 0.0f, 0.0f}}; + std::array OrientAt{{0.0f, 0.0f, -1.0f}}; + std::array OrientUp{{0.0f, 1.0f, 0.0f}}; + float Gain{1.0f}; + float mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT}; + + DISABLE_ALLOC() +}; + +#endif diff --git a/modules/openal-soft/al/source.cpp b/modules/openal-soft/al/source.cpp new file mode 100644 index 0000000..d2ccfe0 --- /dev/null +++ b/modules/openal-soft/al/source.cpp @@ -0,0 +1,6068 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "source.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" +#include "AL/efx.h" + +#include "albit.h" +#include "alc/alu.h" +#include "alc/backends/base.h" +#include "alc/context.h" +#include "alc/device.h" +#include "alc/inprogext.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "aloptional.h" +#include "alspan.h" +#include "atomic.h" +#include "auxeffectslot.h" +#include "buffer.h" +#include "core/ambidefs.h" +#include "core/bformatdec.h" +#include "core/except.h" +#include "core/filters/nfc.h" +#include "core/filters/splitter.h" +#include "core/logging.h" +#include "core/voice_change.h" +#include "event.h" +#include "filter.h" +#include "opthelpers.h" +#include "ringbuffer.h" +#include "threads.h" + +#ifdef ALSOFT_EAX +#include "eax_exception.h" +#endif // ALSOFT_EAX + +namespace { + +using namespace std::placeholders; +using std::chrono::nanoseconds; + +Voice *GetSourceVoice(ALsource *source, ALCcontext *context) +{ + auto voicelist = context->getVoicesSpan(); + ALuint idx{source->VoiceIdx}; + if(idx < voicelist.size()) + { + ALuint sid{source->id}; + Voice *voice = voicelist[idx]; + if(voice->mSourceID.load(std::memory_order_acquire) == sid) + return voice; + } + source->VoiceIdx = INVALID_VOICE_IDX; + return nullptr; +} + + +void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context) +{ + /* Get an unused property container, or allocate a new one as needed. */ + VoicePropsItem *props{context->mFreeVoiceProps.load(std::memory_order_acquire)}; + if(!props) + { + context->allocVoiceProps(); + props = context->mFreeVoiceProps.load(std::memory_order_acquire); + } + VoicePropsItem *next; + do { + next = props->next.load(std::memory_order_relaxed); + } while(unlikely(context->mFreeVoiceProps.compare_exchange_weak(props, next, + std::memory_order_acq_rel, std::memory_order_acquire) == false)); + + props->Pitch = source->Pitch; + props->Gain = source->Gain; + props->OuterGain = source->OuterGain; + props->MinGain = source->MinGain; + props->MaxGain = source->MaxGain; + props->InnerAngle = source->InnerAngle; + props->OuterAngle = source->OuterAngle; + props->RefDistance = source->RefDistance; + props->MaxDistance = source->MaxDistance; + props->RolloffFactor = source->RolloffFactor +#ifdef ALSOFT_EAX + + source->RolloffFactor2 +#endif + ; + props->Position = source->Position; + props->Velocity = source->Velocity; + props->Direction = source->Direction; + props->OrientAt = source->OrientAt; + props->OrientUp = source->OrientUp; + props->HeadRelative = source->HeadRelative; + props->mDistanceModel = source->mDistanceModel; + props->mResampler = source->mResampler; + props->DirectChannels = source->DirectChannels; + props->mSpatializeMode = source->mSpatialize; + + props->DryGainHFAuto = source->DryGainHFAuto; + props->WetGainAuto = source->WetGainAuto; + props->WetGainHFAuto = source->WetGainHFAuto; + props->OuterGainHF = source->OuterGainHF; + + props->AirAbsorptionFactor = source->AirAbsorptionFactor; + props->RoomRolloffFactor = source->RoomRolloffFactor; + props->DopplerFactor = source->DopplerFactor; + + props->StereoPan = source->StereoPan; + + props->Radius = source->Radius; + props->EnhWidth = source->EnhWidth; + + props->Direct.Gain = source->Direct.Gain; + props->Direct.GainHF = source->Direct.GainHF; + props->Direct.HFReference = source->Direct.HFReference; + props->Direct.GainLF = source->Direct.GainLF; + props->Direct.LFReference = source->Direct.LFReference; + + auto copy_send = [](const ALsource::SendData &srcsend) noexcept -> VoiceProps::SendData + { + VoiceProps::SendData ret{}; + ret.Slot = srcsend.Slot ? &srcsend.Slot->mSlot : nullptr; + ret.Gain = srcsend.Gain; + ret.GainHF = srcsend.GainHF; + ret.HFReference = srcsend.HFReference; + ret.GainLF = srcsend.GainLF; + ret.LFReference = srcsend.LFReference; + return ret; + }; + std::transform(source->Send.cbegin(), source->Send.cend(), props->Send, copy_send); + if(!props->Send[0].Slot && context->mDefaultSlot) + props->Send[0].Slot = &context->mDefaultSlot->mSlot; + + /* Set the new container for updating internal parameters. */ + props = voice->mUpdate.exchange(props, std::memory_order_acq_rel); + if(props) + { + /* If there was an unused update container, put it back in the + * freelist. + */ + AtomicReplaceHead(context->mFreeVoiceProps, props); + } +} + +/* GetSourceSampleOffset + * + * Gets the current read offset for the given Source, in 32.32 fixed-point + * samples. The offset is relative to the start of the queue (not the start of + * the current buffer). + */ +int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime) +{ + ALCdevice *device{context->mALDevice.get()}; + const VoiceBufferItem *Current{}; + uint64_t readPos{}; + ALuint refcount; + Voice *voice; + + do { + refcount = device->waitForMix(); + *clocktime = GetDeviceClockTime(device); + voice = GetSourceVoice(Source, context); + if(voice) + { + Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); + + readPos = uint64_t{voice->mPosition.load(std::memory_order_relaxed)} << 32; + readPos |= uint64_t{voice->mPositionFrac.load(std::memory_order_relaxed)} << + (32-MixerFracBits); + } + std::atomic_thread_fence(std::memory_order_acquire); + } while(refcount != device->MixCount.load(std::memory_order_relaxed)); + + if(!voice) + return 0; + + for(auto &item : Source->mQueue) + { + if(&item == Current) break; + readPos += uint64_t{item.mSampleLen} << 32; + } + return static_cast(minu64(readPos, 0x7fffffffffffffff_u64)); +} + +/* GetSourceSecOffset + * + * Gets the current read offset for the given Source, in seconds. The offset is + * relative to the start of the queue (not the start of the current buffer). + */ +double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime) +{ + ALCdevice *device{context->mALDevice.get()}; + const VoiceBufferItem *Current{}; + uint64_t readPos{}; + ALuint refcount; + Voice *voice; + + do { + refcount = device->waitForMix(); + *clocktime = GetDeviceClockTime(device); + voice = GetSourceVoice(Source, context); + if(voice) + { + Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); + + readPos = uint64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits; + readPos |= voice->mPositionFrac.load(std::memory_order_relaxed); + } + std::atomic_thread_fence(std::memory_order_acquire); + } while(refcount != device->MixCount.load(std::memory_order_relaxed)); + + if(!voice) + return 0.0f; + + const ALbuffer *BufferFmt{nullptr}; + auto BufferList = Source->mQueue.cbegin(); + while(BufferList != Source->mQueue.cend() && std::addressof(*BufferList) != Current) + { + if(!BufferFmt) BufferFmt = BufferList->mBuffer; + readPos += uint64_t{BufferList->mSampleLen} << MixerFracBits; + ++BufferList; + } + while(BufferList != Source->mQueue.cend() && !BufferFmt) + { + BufferFmt = BufferList->mBuffer; + ++BufferList; + } + ASSUME(BufferFmt != nullptr); + + return static_cast(readPos) / double{MixerFracOne} / BufferFmt->mSampleRate; +} + +/* GetSourceOffset + * + * Gets the current read offset for the given Source, in the appropriate format + * (Bytes, Samples or Seconds). The offset is relative to the start of the + * queue (not the start of the current buffer). + */ +double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) +{ + ALCdevice *device{context->mALDevice.get()}; + const VoiceBufferItem *Current{}; + ALuint readPos{}; + ALuint readPosFrac{}; + ALuint refcount; + Voice *voice; + + do { + refcount = device->waitForMix(); + voice = GetSourceVoice(Source, context); + if(voice) + { + Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); + + readPos = voice->mPosition.load(std::memory_order_relaxed); + readPosFrac = voice->mPositionFrac.load(std::memory_order_relaxed); + } + std::atomic_thread_fence(std::memory_order_acquire); + } while(refcount != device->MixCount.load(std::memory_order_relaxed)); + + if(!voice) + return 0.0; + + const ALbuffer *BufferFmt{nullptr}; + auto BufferList = Source->mQueue.cbegin(); + while(BufferList != Source->mQueue.cend() && std::addressof(*BufferList) != Current) + { + if(!BufferFmt) BufferFmt = BufferList->mBuffer; + readPos += BufferList->mSampleLen; + ++BufferList; + } + while(BufferList != Source->mQueue.cend() && !BufferFmt) + { + BufferFmt = BufferList->mBuffer; + ++BufferList; + } + ASSUME(BufferFmt != nullptr); + + double offset{}; + switch(name) + { + case AL_SEC_OFFSET: + offset = (readPos + readPosFrac/double{MixerFracOne}) / BufferFmt->mSampleRate; + break; + + case AL_SAMPLE_OFFSET: + offset = readPos + readPosFrac/double{MixerFracOne}; + break; + + case AL_BYTE_OFFSET: + if(BufferFmt->OriginalType == UserFmtIMA4) + { + ALuint FrameBlockSize{BufferFmt->OriginalAlign}; + ALuint align{(BufferFmt->OriginalAlign-1)/2 + 4}; + ALuint BlockSize{align * BufferFmt->channelsFromFmt()}; + + /* Round down to nearest ADPCM block */ + offset = static_cast(readPos / FrameBlockSize * BlockSize); + } + else if(BufferFmt->OriginalType == UserFmtMSADPCM) + { + ALuint FrameBlockSize{BufferFmt->OriginalAlign}; + ALuint align{(FrameBlockSize-2)/2 + 7}; + ALuint BlockSize{align * BufferFmt->channelsFromFmt()}; + + /* Round down to nearest ADPCM block */ + offset = static_cast(readPos / FrameBlockSize * BlockSize); + } + else + { + const ALuint FrameSize{BufferFmt->frameSizeFromFmt()}; + offset = static_cast(readPos * FrameSize); + } + break; + } + return offset; +} + +/* GetSourceLength + * + * Gets the length of the given Source's buffer queue, in the appropriate + * format (Bytes, Samples or Seconds). + */ +double GetSourceLength(const ALsource *source, ALenum name) +{ + uint64_t length{0}; + const ALbuffer *BufferFmt{nullptr}; + for(auto &listitem : source->mQueue) + { + if(!BufferFmt) + BufferFmt = listitem.mBuffer; + length += listitem.mSampleLen; + } + if(length == 0) + return 0.0; + + ASSUME(BufferFmt != nullptr); + switch(name) + { + case AL_SEC_LENGTH_SOFT: + return static_cast(length) / BufferFmt->mSampleRate; + + case AL_SAMPLE_LENGTH_SOFT: + return static_cast(length); + + case AL_BYTE_LENGTH_SOFT: + if(BufferFmt->OriginalType == UserFmtIMA4) + { + ALuint FrameBlockSize{BufferFmt->OriginalAlign}; + ALuint align{(BufferFmt->OriginalAlign-1)/2 + 4}; + ALuint BlockSize{align * BufferFmt->channelsFromFmt()}; + + /* Round down to nearest ADPCM block */ + return static_cast(length / FrameBlockSize) * BlockSize; + } + else if(BufferFmt->OriginalType == UserFmtMSADPCM) + { + ALuint FrameBlockSize{BufferFmt->OriginalAlign}; + ALuint align{(FrameBlockSize-2)/2 + 7}; + ALuint BlockSize{align * BufferFmt->channelsFromFmt()}; + + /* Round down to nearest ADPCM block */ + return static_cast(length / FrameBlockSize) * BlockSize; + } + return static_cast(length) * BufferFmt->frameSizeFromFmt(); + } + return 0.0; +} + + +struct VoicePos { + ALuint pos, frac; + ALbufferQueueItem *bufferitem; +}; + +/** + * GetSampleOffset + * + * Retrieves the voice position, fixed-point fraction, and bufferlist item + * using the givem offset type and offset. If the offset is out of range, + * returns an empty optional. + */ +al::optional GetSampleOffset(al::deque &BufferList, ALenum OffsetType, + double Offset) +{ + /* Find the first valid Buffer in the Queue */ + const ALbuffer *BufferFmt{nullptr}; + for(auto &item : BufferList) + { + BufferFmt = item.mBuffer; + if(BufferFmt) break; + } + if(!BufferFmt || BufferFmt->mCallback) + return al::nullopt; + + /* Get sample frame offset */ + ALuint offset{0u}, frac{0u}; + double dbloff, dblfrac; + switch(OffsetType) + { + case AL_SEC_OFFSET: + dblfrac = std::modf(Offset*BufferFmt->mSampleRate, &dbloff); + offset = static_cast(mind(dbloff, std::numeric_limits::max())); + frac = static_cast(mind(dblfrac*MixerFracOne, MixerFracOne-1.0)); + break; + + case AL_SAMPLE_OFFSET: + dblfrac = std::modf(Offset, &dbloff); + offset = static_cast(mind(dbloff, std::numeric_limits::max())); + frac = static_cast(mind(dblfrac*MixerFracOne, MixerFracOne-1.0)); + break; + + case AL_BYTE_OFFSET: + /* Determine the ByteOffset (and ensure it is block aligned) */ + offset = static_cast(Offset); + if(BufferFmt->OriginalType == UserFmtIMA4) + { + const ALuint align{(BufferFmt->OriginalAlign-1)/2 + 4}; + offset /= align * BufferFmt->channelsFromFmt(); + offset *= BufferFmt->OriginalAlign; + } + else if(BufferFmt->OriginalType == UserFmtMSADPCM) + { + const ALuint align{(BufferFmt->OriginalAlign-2)/2 + 7}; + offset /= align * BufferFmt->channelsFromFmt(); + offset *= BufferFmt->OriginalAlign; + } + else + offset /= BufferFmt->frameSizeFromFmt(); + frac = 0; + break; + } + + /* Find the bufferlist item this offset belongs to. */ + ALuint totalBufferLen{0u}; + for(auto &item : BufferList) + { + if(totalBufferLen > offset) + break; + if(item.mSampleLen > offset-totalBufferLen) + { + /* Offset is in this buffer */ + return VoicePos{offset-totalBufferLen, frac, &item}; + } + totalBufferLen += item.mSampleLen; + } + + /* Offset is out of range of the queue */ + return al::nullopt; +} + + +void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, ALCcontext *context, + ALCdevice *device) +{ + voice->mLoopBuffer.store(source->Looping ? &source->mQueue.front() : nullptr, + std::memory_order_relaxed); + + ALbuffer *buffer{BufferList->mBuffer}; + voice->mFrequency = buffer->mSampleRate; + voice->mFmtChannels = + (buffer->mChannels == FmtStereo && source->mStereoMode == SourceStereo::Enhanced) ? + FmtSuperStereo : buffer->mChannels; + voice->mFmtType = buffer->mType; + voice->mFrameStep = buffer->channelsFromFmt(); + voice->mFrameSize = buffer->frameSizeFromFmt(); + voice->mAmbiLayout = IsUHJ(voice->mFmtChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout; + voice->mAmbiScaling = IsUHJ(voice->mFmtChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling; + voice->mAmbiOrder = (voice->mFmtChannels == FmtSuperStereo) ? 1 : buffer->mAmbiOrder; + + if(buffer->mCallback) voice->mFlags.set(VoiceIsCallback); + else if(source->SourceType == AL_STATIC) voice->mFlags.set(VoiceIsStatic); + voice->mNumCallbackSamples = 0; + + voice->prepare(device); + + source->mPropsDirty = false; + UpdateSourceProps(source, voice, context); + + voice->mSourceID.store(source->id, std::memory_order_release); +} + + +VoiceChange *GetVoiceChanger(ALCcontext *ctx) +{ + VoiceChange *vchg{ctx->mVoiceChangeTail}; + if UNLIKELY(vchg == ctx->mCurrentVoiceChange.load(std::memory_order_acquire)) + { + ctx->allocVoiceChanges(); + vchg = ctx->mVoiceChangeTail; + } + + ctx->mVoiceChangeTail = vchg->mNext.exchange(nullptr, std::memory_order_relaxed); + + return vchg; +} + +void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail) +{ + ALCdevice *device{ctx->mALDevice.get()}; + + VoiceChange *oldhead{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)}; + while(VoiceChange *next{oldhead->mNext.load(std::memory_order_relaxed)}) + oldhead = next; + oldhead->mNext.store(tail, std::memory_order_release); + + const bool connected{device->Connected.load(std::memory_order_acquire)}; + device->waitForMix(); + if UNLIKELY(!connected) + { + if(ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) + { + /* If the device is disconnected and voices are stopped, just + * ignore all pending changes. + */ + VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)}; + while(VoiceChange *next{cur->mNext.load(std::memory_order_acquire)}) + { + cur = next; + if(Voice *voice{cur->mVoice}) + voice->mSourceID.store(0, std::memory_order_relaxed); + } + ctx->mCurrentVoiceChange.store(cur, std::memory_order_release); + } + } +} + + +bool SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALCcontext *context, + ALCdevice *device) +{ + /* First, get a free voice to start at the new offset. */ + auto voicelist = context->getVoicesSpan(); + Voice *newvoice{}; + ALuint vidx{0}; + for(Voice *voice : voicelist) + { + if(voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped + && voice->mSourceID.load(std::memory_order_relaxed) == 0u + && voice->mPendingChange.load(std::memory_order_relaxed) == false) + { + newvoice = voice; + break; + } + ++vidx; + } + if(unlikely(!newvoice)) + { + auto &allvoices = *context->mVoices.load(std::memory_order_relaxed); + if(allvoices.size() == voicelist.size()) + context->allocVoices(1); + context->mActiveVoiceCount.fetch_add(1, std::memory_order_release); + voicelist = context->getVoicesSpan(); + + vidx = 0; + for(Voice *voice : voicelist) + { + if(voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped + && voice->mSourceID.load(std::memory_order_relaxed) == 0u + && voice->mPendingChange.load(std::memory_order_relaxed) == false) + { + newvoice = voice; + break; + } + ++vidx; + } + ASSUME(newvoice != nullptr); + } + + /* Initialize the new voice and set its starting offset. + * TODO: It might be better to have the VoiceChange processing copy the old + * voice's mixing parameters (and pending update) insead of initializing it + * all here. This would just need to set the minimum properties to link the + * voice to the source and its position-dependent properties (including the + * fading flag). + */ + newvoice->mPlayState.store(Voice::Pending, std::memory_order_relaxed); + newvoice->mPosition.store(vpos.pos, std::memory_order_relaxed); + newvoice->mPositionFrac.store(vpos.frac, std::memory_order_relaxed); + newvoice->mCurrentBuffer.store(vpos.bufferitem, std::memory_order_relaxed); + newvoice->mFlags.reset(); + if(vpos.pos > 0 || vpos.frac > 0 || vpos.bufferitem != &source->mQueue.front()) + newvoice->mFlags.set(VoiceIsFading); + InitVoice(newvoice, source, vpos.bufferitem, context, device); + source->VoiceIdx = vidx; + + /* Set the old voice as having a pending change, and send it off with the + * new one with a new offset voice change. + */ + oldvoice->mPendingChange.store(true, std::memory_order_relaxed); + + VoiceChange *vchg{GetVoiceChanger(context)}; + vchg->mOldVoice = oldvoice; + vchg->mVoice = newvoice; + vchg->mSourceID = source->id; + vchg->mState = VChangeState::Restart; + SendVoiceChanges(context, vchg); + + /* If the old voice still has a sourceID, it's still active and the change- + * over will work on the next update. + */ + if LIKELY(oldvoice->mSourceID.load(std::memory_order_acquire) != 0u) + return true; + + /* Otherwise, if the new voice's state is not pending, the change-over + * already happened. + */ + if(newvoice->mPlayState.load(std::memory_order_acquire) != Voice::Pending) + return true; + + /* Otherwise, wait for any current mix to finish and check one last time. */ + device->waitForMix(); + if(newvoice->mPlayState.load(std::memory_order_acquire) != Voice::Pending) + return true; + /* The change-over failed because the old voice stopped before the new + * voice could start at the new offset. Let go of the new voice and have + * the caller store the source offset since it's stopped. + */ + newvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); + newvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); + newvoice->mSourceID.store(0u, std::memory_order_relaxed); + newvoice->mPlayState.store(Voice::Stopped, std::memory_order_relaxed); + return false; +} + + +/** + * Returns if the last known state for the source was playing or paused. Does + * not sync with the mixer voice. + */ +inline bool IsPlayingOrPaused(ALsource *source) +{ return source->state == AL_PLAYING || source->state == AL_PAUSED; } + +/** + * Returns an updated source state using the matching voice's status (or lack + * thereof). + */ +inline ALenum GetSourceState(ALsource *source, Voice *voice) +{ + if(!voice && source->state == AL_PLAYING) + source->state = AL_STOPPED; + return source->state; +} + + +bool EnsureSources(ALCcontext *context, size_t needed) +{ + size_t count{std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(), + size_t{0}, + [](size_t cur, const SourceSubList &sublist) noexcept -> size_t + { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; + + while(needed > count) + { + if UNLIKELY(context->mSourceList.size() >= 1<<25) + return false; + + context->mSourceList.emplace_back(); + auto sublist = context->mSourceList.end() - 1; + sublist->FreeMask = ~0_u64; + sublist->Sources = static_cast(al_calloc(alignof(ALsource), sizeof(ALsource)*64)); + if UNLIKELY(!sublist->Sources) + { + context->mSourceList.pop_back(); + return false; + } + count += 64; + } + return true; +} + +ALsource *AllocSource(ALCcontext *context) +{ + auto sublist = std::find_if(context->mSourceList.begin(), context->mSourceList.end(), + [](const SourceSubList &entry) noexcept -> bool + { return entry.FreeMask != 0; }); + auto lidx = static_cast(std::distance(context->mSourceList.begin(), sublist)); + auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); + ASSUME(slidx < 64); + + ALsource *source{al::construct_at(sublist->Sources + slidx)}; + + /* Add 1 to avoid source ID 0. */ + source->id = ((lidx<<6) | slidx) + 1; + + context->mNumSources += 1; + sublist->FreeMask &= ~(1_u64 << slidx); + + return source; +} + +void FreeSource(ALCcontext *context, ALsource *source) +{ + const ALuint id{source->id - 1}; + const size_t lidx{id >> 6}; + const ALuint slidx{id & 0x3f}; + + if(Voice *voice{GetSourceVoice(source, context)}) + { + VoiceChange *vchg{GetVoiceChanger(context)}; + + voice->mPendingChange.store(true, std::memory_order_relaxed); + vchg->mVoice = voice; + vchg->mSourceID = source->id; + vchg->mState = VChangeState::Stop; + + SendVoiceChanges(context, vchg); + } + + al::destroy_at(source); + + context->mSourceList[lidx].FreeMask |= 1_u64 << slidx; + context->mNumSources--; +} + + +inline ALsource *LookupSource(ALCcontext *context, ALuint id) noexcept +{ + const size_t lidx{(id-1) >> 6}; + const ALuint slidx{(id-1) & 0x3f}; + + if UNLIKELY(lidx >= context->mSourceList.size()) + return nullptr; + SourceSubList &sublist{context->mSourceList[lidx]}; + if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + return nullptr; + return sublist.Sources + slidx; +} + +inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) noexcept +{ + const size_t lidx{(id-1) >> 6}; + const ALuint slidx{(id-1) & 0x3f}; + + if UNLIKELY(lidx >= device->BufferList.size()) + return nullptr; + BufferSubList &sublist = device->BufferList[lidx]; + if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + return nullptr; + return sublist.Buffers + slidx; +} + +inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) noexcept +{ + const size_t lidx{(id-1) >> 6}; + const ALuint slidx{(id-1) & 0x3f}; + + if UNLIKELY(lidx >= device->FilterList.size()) + return nullptr; + FilterSubList &sublist = device->FilterList[lidx]; + if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + return nullptr; + return sublist.Filters + slidx; +} + +inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept +{ + const size_t lidx{(id-1) >> 6}; + const ALuint slidx{(id-1) & 0x3f}; + + if UNLIKELY(lidx >= context->mEffectSlotList.size()) + return nullptr; + EffectSlotSubList &sublist{context->mEffectSlotList[lidx]}; + if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + return nullptr; + return sublist.EffectSlots + slidx; +} + + +al::optional StereoModeFromEnum(ALenum mode) +{ + switch(mode) + { + case AL_NORMAL_SOFT: return al::make_optional(SourceStereo::Normal); + case AL_SUPER_STEREO_SOFT: return al::make_optional(SourceStereo::Enhanced); + } + WARN("Unsupported stereo mode: 0x%04x\n", mode); + return al::nullopt; +} +ALenum EnumFromStereoMode(SourceStereo mode) +{ + switch(mode) + { + case SourceStereo::Normal: return AL_NORMAL_SOFT; + case SourceStereo::Enhanced: return AL_SUPER_STEREO_SOFT; + } + throw std::runtime_error{"Invalid SourceStereo: "+std::to_string(int(mode))}; +} + +al::optional SpatializeModeFromEnum(ALenum mode) +{ + switch(mode) + { + case AL_FALSE: return al::make_optional(SpatializeMode::Off); + case AL_TRUE: return al::make_optional(SpatializeMode::On); + case AL_AUTO_SOFT: return al::make_optional(SpatializeMode::Auto); + } + WARN("Unsupported spatialize mode: 0x%04x\n", mode); + return al::nullopt; +} +ALenum EnumFromSpatializeMode(SpatializeMode mode) +{ + switch(mode) + { + case SpatializeMode::Off: return AL_FALSE; + case SpatializeMode::On: return AL_TRUE; + case SpatializeMode::Auto: return AL_AUTO_SOFT; + } + throw std::runtime_error{"Invalid SpatializeMode: "+std::to_string(int(mode))}; +} + +al::optional DirectModeFromEnum(ALenum mode) +{ + switch(mode) + { + case AL_FALSE: return al::make_optional(DirectMode::Off); + case AL_DROP_UNMATCHED_SOFT: return al::make_optional(DirectMode::DropMismatch); + case AL_REMIX_UNMATCHED_SOFT: return al::make_optional(DirectMode::RemixMismatch); + } + WARN("Unsupported direct mode: 0x%04x\n", mode); + return al::nullopt; +} +ALenum EnumFromDirectMode(DirectMode mode) +{ + switch(mode) + { + case DirectMode::Off: return AL_FALSE; + 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))}; +} + +al::optional DistanceModelFromALenum(ALenum model) +{ + switch(model) + { + case AL_NONE: return al::make_optional(DistanceModel::Disable); + case AL_INVERSE_DISTANCE: return al::make_optional(DistanceModel::Inverse); + case AL_INVERSE_DISTANCE_CLAMPED: return al::make_optional(DistanceModel::InverseClamped); + case AL_LINEAR_DISTANCE: return al::make_optional(DistanceModel::Linear); + case AL_LINEAR_DISTANCE_CLAMPED: return al::make_optional(DistanceModel::LinearClamped); + case AL_EXPONENT_DISTANCE: return al::make_optional(DistanceModel::Exponent); + case AL_EXPONENT_DISTANCE_CLAMPED: return al::make_optional(DistanceModel::ExponentClamped); + } + return al::nullopt; +} +ALenum ALenumFromDistanceModel(DistanceModel model) +{ + switch(model) + { + case DistanceModel::Disable: return AL_NONE; + case DistanceModel::Inverse: return AL_INVERSE_DISTANCE; + case DistanceModel::InverseClamped: return AL_INVERSE_DISTANCE_CLAMPED; + case DistanceModel::Linear: return AL_LINEAR_DISTANCE; + case DistanceModel::LinearClamped: return AL_LINEAR_DISTANCE_CLAMPED; + 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))}; +} + +enum SourceProp : ALenum { + srcPitch = AL_PITCH, + srcGain = AL_GAIN, + srcMinGain = AL_MIN_GAIN, + srcMaxGain = AL_MAX_GAIN, + srcMaxDistance = AL_MAX_DISTANCE, + srcRolloffFactor = AL_ROLLOFF_FACTOR, + srcDopplerFactor = AL_DOPPLER_FACTOR, + srcConeOuterGain = AL_CONE_OUTER_GAIN, + srcSecOffset = AL_SEC_OFFSET, + srcSampleOffset = AL_SAMPLE_OFFSET, + srcByteOffset = AL_BYTE_OFFSET, + srcConeInnerAngle = AL_CONE_INNER_ANGLE, + srcConeOuterAngle = AL_CONE_OUTER_ANGLE, + srcRefDistance = AL_REFERENCE_DISTANCE, + + srcPosition = AL_POSITION, + srcVelocity = AL_VELOCITY, + srcDirection = AL_DIRECTION, + + srcSourceRelative = AL_SOURCE_RELATIVE, + srcLooping = AL_LOOPING, + srcBuffer = AL_BUFFER, + srcSourceState = AL_SOURCE_STATE, + srcBuffersQueued = AL_BUFFERS_QUEUED, + srcBuffersProcessed = AL_BUFFERS_PROCESSED, + srcSourceType = AL_SOURCE_TYPE, + + /* ALC_EXT_EFX */ + srcConeOuterGainHF = AL_CONE_OUTER_GAINHF, + srcAirAbsorptionFactor = AL_AIR_ABSORPTION_FACTOR, + srcRoomRolloffFactor = AL_ROOM_ROLLOFF_FACTOR, + srcDirectFilterGainHFAuto = AL_DIRECT_FILTER_GAINHF_AUTO, + srcAuxSendFilterGainAuto = AL_AUXILIARY_SEND_FILTER_GAIN_AUTO, + srcAuxSendFilterGainHFAuto = AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO, + srcDirectFilter = AL_DIRECT_FILTER, + srcAuxSendFilter = AL_AUXILIARY_SEND_FILTER, + + /* AL_SOFT_direct_channels */ + srcDirectChannelsSOFT = AL_DIRECT_CHANNELS_SOFT, + + /* AL_EXT_source_distance_model */ + srcDistanceModel = AL_DISTANCE_MODEL, + + /* AL_SOFT_source_latency */ + srcSampleOffsetLatencySOFT = AL_SAMPLE_OFFSET_LATENCY_SOFT, + srcSecOffsetLatencySOFT = AL_SEC_OFFSET_LATENCY_SOFT, + + /* AL_EXT_STEREO_ANGLES */ + srcAngles = AL_STEREO_ANGLES, + + /* AL_EXT_SOURCE_RADIUS */ + srcRadius = AL_SOURCE_RADIUS, + + /* AL_EXT_BFORMAT */ + srcOrientation = AL_ORIENTATION, + + /* AL_SOFT_source_length */ + srcByteLength = AL_BYTE_LENGTH_SOFT, + srcSampleLength = AL_SAMPLE_LENGTH_SOFT, + srcSecLength = AL_SEC_LENGTH_SOFT, + + /* AL_SOFT_source_resampler */ + srcResampler = AL_SOURCE_RESAMPLER_SOFT, + + /* AL_SOFT_source_spatialize */ + srcSpatialize = AL_SOURCE_SPATIALIZE_SOFT, + + /* ALC_SOFT_device_clock */ + srcSampleOffsetClockSOFT = AL_SAMPLE_OFFSET_CLOCK_SOFT, + srcSecOffsetClockSOFT = AL_SEC_OFFSET_CLOCK_SOFT, + + /* AL_SOFT_UHJ */ + srcStereoMode = AL_STEREO_MODE_SOFT, + srcSuperStereoWidth = AL_SUPER_STEREO_WIDTH_SOFT, +}; + + +constexpr size_t MaxValues{6u}; + +ALuint FloatValsByProp(ALenum prop) +{ + switch(static_cast(prop)) + { + case AL_PITCH: + case AL_GAIN: + case AL_MIN_GAIN: + case AL_MAX_GAIN: + case AL_MAX_DISTANCE: + case AL_ROLLOFF_FACTOR: + case AL_DOPPLER_FACTOR: + case AL_CONE_OUTER_GAIN: + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + case AL_CONE_INNER_ANGLE: + case AL_CONE_OUTER_ANGLE: + case AL_REFERENCE_DISTANCE: + case AL_CONE_OUTER_GAINHF: + case AL_AIR_ABSORPTION_FACTOR: + case AL_ROOM_ROLLOFF_FACTOR: + case AL_DIRECT_FILTER_GAINHF_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + case AL_DIRECT_CHANNELS_SOFT: + case AL_DISTANCE_MODEL: + case AL_SOURCE_RELATIVE: + case AL_LOOPING: + case AL_SOURCE_STATE: + case AL_BUFFERS_QUEUED: + case AL_BUFFERS_PROCESSED: + case AL_SOURCE_TYPE: + case AL_SOURCE_RADIUS: + case AL_SOURCE_RESAMPLER_SOFT: + case AL_SOURCE_SPATIALIZE_SOFT: + case AL_BYTE_LENGTH_SOFT: + case AL_SAMPLE_LENGTH_SOFT: + case AL_SEC_LENGTH_SOFT: + case AL_STEREO_MODE_SOFT: + case AL_SUPER_STEREO_WIDTH_SOFT: + return 1; + + case AL_STEREO_ANGLES: + return 2; + + case AL_POSITION: + case AL_VELOCITY: + case AL_DIRECTION: + return 3; + + case AL_ORIENTATION: + return 6; + + case AL_SEC_OFFSET_LATENCY_SOFT: + case AL_SEC_OFFSET_CLOCK_SOFT: + break; /* Double only */ + + case AL_BUFFER: + case AL_DIRECT_FILTER: + case AL_AUXILIARY_SEND_FILTER: + break; /* i/i64 only */ + case AL_SAMPLE_OFFSET_LATENCY_SOFT: + case AL_SAMPLE_OFFSET_CLOCK_SOFT: + break; /* i64 only */ + } + return 0; +} +ALuint DoubleValsByProp(ALenum prop) +{ + switch(static_cast(prop)) + { + case AL_PITCH: + case AL_GAIN: + case AL_MIN_GAIN: + case AL_MAX_GAIN: + case AL_MAX_DISTANCE: + case AL_ROLLOFF_FACTOR: + case AL_DOPPLER_FACTOR: + case AL_CONE_OUTER_GAIN: + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + case AL_CONE_INNER_ANGLE: + case AL_CONE_OUTER_ANGLE: + case AL_REFERENCE_DISTANCE: + case AL_CONE_OUTER_GAINHF: + case AL_AIR_ABSORPTION_FACTOR: + case AL_ROOM_ROLLOFF_FACTOR: + case AL_DIRECT_FILTER_GAINHF_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + case AL_DIRECT_CHANNELS_SOFT: + case AL_DISTANCE_MODEL: + case AL_SOURCE_RELATIVE: + case AL_LOOPING: + case AL_SOURCE_STATE: + case AL_BUFFERS_QUEUED: + case AL_BUFFERS_PROCESSED: + case AL_SOURCE_TYPE: + case AL_SOURCE_RADIUS: + case AL_SOURCE_RESAMPLER_SOFT: + case AL_SOURCE_SPATIALIZE_SOFT: + case AL_BYTE_LENGTH_SOFT: + case AL_SAMPLE_LENGTH_SOFT: + case AL_SEC_LENGTH_SOFT: + case AL_STEREO_MODE_SOFT: + case AL_SUPER_STEREO_WIDTH_SOFT: + return 1; + + case AL_SEC_OFFSET_LATENCY_SOFT: + case AL_SEC_OFFSET_CLOCK_SOFT: + case AL_STEREO_ANGLES: + return 2; + + case AL_POSITION: + case AL_VELOCITY: + case AL_DIRECTION: + return 3; + + case AL_ORIENTATION: + return 6; + + case AL_BUFFER: + case AL_DIRECT_FILTER: + case AL_AUXILIARY_SEND_FILTER: + break; /* i/i64 only */ + case AL_SAMPLE_OFFSET_LATENCY_SOFT: + case AL_SAMPLE_OFFSET_CLOCK_SOFT: + break; /* i64 only */ + } + return 0; +} + + +void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); +void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); +void SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); + +#define CHECKSIZE(v, s) do { \ + if LIKELY((v).size() == (s) || (v).size() == MaxValues) break; \ + Context->setError(AL_INVALID_ENUM, \ + "Property 0x%04x expects %d value(s), got %zu", prop, (s), \ + (v).size()); \ + return; \ +} while(0) +#define CHECKVAL(x) do { \ + if LIKELY(x) break; \ + Context->setError(AL_INVALID_VALUE, "Value out of range"); \ + return; \ +} while(0) + +void UpdateSourceProps(ALsource *source, ALCcontext *context) +{ + if(!context->mDeferUpdates) + { + if(Voice *voice{GetSourceVoice(source, context)}) + { + UpdateSourceProps(source, voice, context); + return; + } + } + source->mPropsDirty = true; +} +#ifdef ALSOFT_EAX +void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) +{ + if(!context->mDeferUpdates) + { + if(source->eax_is_initialized()) + source->eax_commit(); + if(Voice *voice{GetSourceVoice(source, context)}) + { + UpdateSourceProps(source, voice, context); + return; + } + } + source->mPropsDirty = true; +} + +#else + +inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) +{ UpdateSourceProps(source, context); } +#endif + + +void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, + const al::span values) +{ + int ival; + + switch(prop) + { + case AL_SEC_LENGTH_SOFT: + case AL_SEC_OFFSET_LATENCY_SOFT: + case AL_SEC_OFFSET_CLOCK_SOFT: + /* Query only */ + SETERR_RETURN(Context, AL_INVALID_OPERATION,, + "Setting read-only source property 0x%04x", prop); + + case AL_PITCH: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f); + + Source->Pitch = values[0]; + return UpdateSourceProps(Source, Context); + + case AL_CONE_INNER_ANGLE: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f && values[0] <= 360.0f); + + Source->InnerAngle = values[0]; + return CommitAndUpdateSourceProps(Source, Context); + + case AL_CONE_OUTER_ANGLE: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f && values[0] <= 360.0f); + + Source->OuterAngle = values[0]; + return CommitAndUpdateSourceProps(Source, Context); + + case AL_GAIN: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f); + + Source->Gain = values[0]; + return UpdateSourceProps(Source, Context); + + case AL_MAX_DISTANCE: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f); + + Source->MaxDistance = values[0]; + return CommitAndUpdateSourceProps(Source, Context); + + case AL_ROLLOFF_FACTOR: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f); + + Source->RolloffFactor = values[0]; + return CommitAndUpdateSourceProps(Source, Context); + + case AL_REFERENCE_DISTANCE: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f); + + Source->RefDistance = values[0]; + return CommitAndUpdateSourceProps(Source, Context); + + case AL_MIN_GAIN: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f); + + Source->MinGain = values[0]; + return UpdateSourceProps(Source, Context); + + case AL_MAX_GAIN: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f); + + Source->MaxGain = values[0]; + return UpdateSourceProps(Source, Context); + + case AL_CONE_OUTER_GAIN: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f); + + Source->OuterGain = values[0]; + return UpdateSourceProps(Source, Context); + + case AL_CONE_OUTER_GAINHF: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f); + + Source->OuterGainHF = values[0]; + return UpdateSourceProps(Source, Context); + + case AL_AIR_ABSORPTION_FACTOR: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f && values[0] <= 10.0f); + + Source->AirAbsorptionFactor = values[0]; + return UpdateSourceProps(Source, Context); + + case AL_ROOM_ROLLOFF_FACTOR: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f && values[0] <= 10.0f); + + Source->RoomRolloffFactor = values[0]; + return UpdateSourceProps(Source, Context); + + case AL_DOPPLER_FACTOR: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f); + + Source->DopplerFactor = values[0]; + return UpdateSourceProps(Source, Context); + + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f); + + if(Voice *voice{GetSourceVoice(Source, Context)}) + { + auto vpos = GetSampleOffset(Source->mQueue, prop, values[0]); + if(!vpos) SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid offset"); + + if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mALDevice.get())) + return; + } + Source->OffsetType = prop; + Source->Offset = values[0]; + return; + + case AL_SOURCE_RADIUS: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f && std::isfinite(values[0])); + + Source->Radius = values[0]; + return UpdateSourceProps(Source, Context); + + case AL_SUPER_STEREO_WIDTH_SOFT: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f); + + Source->EnhWidth = values[0]; + return UpdateSourceProps(Source, Context); + + case AL_STEREO_ANGLES: + CHECKSIZE(values, 2); + CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1])); + + Source->StereoPan[0] = values[0]; + Source->StereoPan[1] = values[1]; + return UpdateSourceProps(Source, Context); + + + case AL_POSITION: + CHECKSIZE(values, 3); + CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); + + Source->Position[0] = values[0]; + Source->Position[1] = values[1]; + Source->Position[2] = values[2]; + return CommitAndUpdateSourceProps(Source, Context); + + case AL_VELOCITY: + CHECKSIZE(values, 3); + CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); + + Source->Velocity[0] = values[0]; + Source->Velocity[1] = values[1]; + Source->Velocity[2] = values[2]; + return CommitAndUpdateSourceProps(Source, Context); + + case AL_DIRECTION: + CHECKSIZE(values, 3); + CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); + + Source->Direction[0] = values[0]; + Source->Direction[1] = values[1]; + Source->Direction[2] = values[2]; + return CommitAndUpdateSourceProps(Source, Context); + + case AL_ORIENTATION: + CHECKSIZE(values, 6); + CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) + && std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5])); + + Source->OrientAt[0] = values[0]; + Source->OrientAt[1] = values[1]; + Source->OrientAt[2] = values[2]; + Source->OrientUp[0] = values[3]; + Source->OrientUp[1] = values[4]; + Source->OrientUp[2] = values[5]; + return UpdateSourceProps(Source, Context); + + + case AL_SOURCE_RELATIVE: + case AL_LOOPING: + case AL_SOURCE_STATE: + case AL_SOURCE_TYPE: + case AL_DISTANCE_MODEL: + case AL_DIRECT_FILTER_GAINHF_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + case AL_DIRECT_CHANNELS_SOFT: + case AL_SOURCE_RESAMPLER_SOFT: + case AL_SOURCE_SPATIALIZE_SOFT: + case AL_BYTE_LENGTH_SOFT: + case AL_SAMPLE_LENGTH_SOFT: + case AL_STEREO_MODE_SOFT: + CHECKSIZE(values, 1); + ival = static_cast(values[0]); + return SetSourceiv(Source, Context, prop, {&ival, 1u}); + + case AL_BUFFERS_QUEUED: + case AL_BUFFERS_PROCESSED: + CHECKSIZE(values, 1); + ival = static_cast(static_cast(values[0])); + return SetSourceiv(Source, Context, prop, {&ival, 1u}); + + case AL_BUFFER: + case AL_DIRECT_FILTER: + case AL_AUXILIARY_SEND_FILTER: + case AL_SAMPLE_OFFSET_LATENCY_SOFT: + case AL_SAMPLE_OFFSET_CLOCK_SOFT: + break; + } + + ERR("Unexpected property: 0x%04x\n", prop); + Context->setError(AL_INVALID_ENUM, "Invalid source float property 0x%04x", prop); + return; +} + +void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, + const al::span values) +{ + ALCdevice *device{Context->mALDevice.get()}; + ALeffectslot *slot{nullptr}; + al::deque oldlist; + std::unique_lock slotlock; + float fvals[6]; + + switch(prop) + { + case AL_SOURCE_STATE: + case AL_SOURCE_TYPE: + case AL_BUFFERS_QUEUED: + case AL_BUFFERS_PROCESSED: + case AL_BYTE_LENGTH_SOFT: + case AL_SAMPLE_LENGTH_SOFT: + /* Query only */ + SETERR_RETURN(Context, AL_INVALID_OPERATION,, + "Setting read-only source property 0x%04x", prop); + + case AL_SOURCE_RELATIVE: + CHECKSIZE(values, 1); + CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->HeadRelative = values[0] != AL_FALSE; + return CommitAndUpdateSourceProps(Source, Context); + + case AL_LOOPING: + CHECKSIZE(values, 1); + CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->Looping = values[0] != AL_FALSE; + if(Voice *voice{GetSourceVoice(Source, Context)}) + { + if(Source->Looping) + voice->mLoopBuffer.store(&Source->mQueue.front(), std::memory_order_release); + else + voice->mLoopBuffer.store(nullptr, std::memory_order_release); + + /* If the source is playing, wait for the current mix to finish to + * ensure it isn't currently looping back or reaching the end. + */ + device->waitForMix(); + } + return; + + case AL_BUFFER: + CHECKSIZE(values, 1); + { + const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; + if(state == AL_PLAYING || state == AL_PAUSED) + SETERR_RETURN(Context, AL_INVALID_OPERATION,, + "Setting buffer on playing or paused source %u", Source->id); + } + if(values[0]) + { + std::lock_guard _{device->BufferLock}; + ALbuffer *buffer{LookupBuffer(device, static_cast(values[0]))}; + if(!buffer) + SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid buffer ID %u", + static_cast(values[0])); + if(buffer->MappedAccess && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) + SETERR_RETURN(Context, AL_INVALID_OPERATION,, + "Setting non-persistently mapped buffer %u", buffer->id); + if(buffer->mCallback && ReadRef(buffer->ref) != 0) + SETERR_RETURN(Context, AL_INVALID_OPERATION,, + "Setting already-set callback buffer %u", buffer->id); + + /* Add the selected buffer to a one-item queue */ + al::deque newlist; + newlist.emplace_back(); + newlist.back().mCallback = buffer->mCallback; + newlist.back().mUserData = buffer->mUserData; + newlist.back().mSampleLen = buffer->mSampleLen; + newlist.back().mLoopStart = buffer->mLoopStart; + newlist.back().mLoopEnd = buffer->mLoopEnd; + newlist.back().mSamples = buffer->mData.data(); + newlist.back().mBuffer = buffer; + IncrementRef(buffer->ref); + + /* Source is now Static */ + Source->SourceType = AL_STATIC; + Source->mQueue.swap(oldlist); + Source->mQueue.swap(newlist); + } + else + { + /* Source is now Undetermined */ + Source->SourceType = AL_UNDETERMINED; + Source->mQueue.swap(oldlist); + } + + /* Delete all elements in the previous queue */ + for(auto &item : oldlist) + { + if(ALbuffer *buffer{item.mBuffer}) + DecrementRef(buffer->ref); + } + return; + + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0); + + if(Voice *voice{GetSourceVoice(Source, Context)}) + { + auto vpos = GetSampleOffset(Source->mQueue, prop, values[0]); + if(!vpos) SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid source offset"); + + if(SetVoiceOffset(voice, *vpos, Source, Context, device)) + return; + } + Source->OffsetType = prop; + Source->Offset = values[0]; + return; + + case AL_DIRECT_FILTER: + CHECKSIZE(values, 1); + if(values[0]) + { + std::lock_guard _{device->FilterLock}; + ALfilter *filter{LookupFilter(device, static_cast(values[0]))}; + if(!filter) + SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid filter ID %u", + static_cast(values[0])); + Source->Direct.Gain = filter->Gain; + Source->Direct.GainHF = filter->GainHF; + Source->Direct.HFReference = filter->HFReference; + Source->Direct.GainLF = filter->GainLF; + Source->Direct.LFReference = filter->LFReference; + } + else + { + Source->Direct.Gain = 1.0f; + Source->Direct.GainHF = 1.0f; + Source->Direct.HFReference = LOWPASSFREQREF; + Source->Direct.GainLF = 1.0f; + Source->Direct.LFReference = HIGHPASSFREQREF; + } + return UpdateSourceProps(Source, Context); + + case AL_DIRECT_FILTER_GAINHF_AUTO: + CHECKSIZE(values, 1); + CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->DryGainHFAuto = values[0] != AL_FALSE; + return UpdateSourceProps(Source, Context); + + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + CHECKSIZE(values, 1); + CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->WetGainAuto = values[0] != AL_FALSE; + return UpdateSourceProps(Source, Context); + + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + CHECKSIZE(values, 1); + CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->WetGainHFAuto = values[0] != AL_FALSE; + return UpdateSourceProps(Source, Context); + + case AL_DIRECT_CHANNELS_SOFT: + CHECKSIZE(values, 1); + if(auto mode = DirectModeFromEnum(values[0])) + { + Source->DirectChannels = *mode; + return UpdateSourceProps(Source, Context); + } + Context->setError(AL_INVALID_VALUE, "Unsupported AL_DIRECT_CHANNELS_SOFT: 0x%04x\n", + values[0]); + return; + + case AL_DISTANCE_MODEL: + CHECKSIZE(values, 1); + if(auto model = DistanceModelFromALenum(values[0])) + { + Source->mDistanceModel = *model; + if(Context->mSourceDistanceModel) + UpdateSourceProps(Source, Context); + return; + } + Context->setError(AL_INVALID_VALUE, "Distance model out of range: 0x%04x", values[0]); + return; + + case AL_SOURCE_RESAMPLER_SOFT: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0 && values[0] <= static_cast(Resampler::Max)); + + Source->mResampler = static_cast(values[0]); + return UpdateSourceProps(Source, Context); + + case AL_SOURCE_SPATIALIZE_SOFT: + CHECKSIZE(values, 1); + if(auto mode = SpatializeModeFromEnum(values[0])) + { + Source->mSpatialize = *mode; + return UpdateSourceProps(Source, Context); + } + Context->setError(AL_INVALID_VALUE, "Unsupported AL_SOURCE_SPATIALIZE_SOFT: 0x%04x\n", + values[0]); + return; + + case AL_STEREO_MODE_SOFT: + CHECKSIZE(values, 1); + { + const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; + if(state == AL_PLAYING || state == AL_PAUSED) + SETERR_RETURN(Context, AL_INVALID_OPERATION,, + "Modifying stereo mode on playing or paused source %u", Source->id); + } + if(auto mode = StereoModeFromEnum(values[0])) + { + Source->mStereoMode = *mode; + return; + } + Context->setError(AL_INVALID_VALUE, "Unsupported AL_STEREO_MODE_SOFT: 0x%04x\n", + values[0]); + return; + + case AL_AUXILIARY_SEND_FILTER: + CHECKSIZE(values, 3); + slotlock = std::unique_lock{Context->mEffectSlotLock}; + if(values[0] && (slot=LookupEffectSlot(Context, static_cast(values[0]))) == nullptr) + SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid effect ID %u", values[0]); + if(static_cast(values[1]) >= device->NumAuxSends) + SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid send %u", values[1]); + + if(values[2]) + { + std::lock_guard _{device->FilterLock}; + ALfilter *filter{LookupFilter(device, static_cast(values[2]))}; + if(!filter) + SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid filter ID %u", values[2]); + + auto &send = Source->Send[static_cast(values[1])]; + send.Gain = filter->Gain; + send.GainHF = filter->GainHF; + send.HFReference = filter->HFReference; + send.GainLF = filter->GainLF; + send.LFReference = filter->LFReference; + } + else + { + /* Disable filter */ + auto &send = Source->Send[static_cast(values[1])]; + send.Gain = 1.0f; + send.GainHF = 1.0f; + send.HFReference = LOWPASSFREQREF; + send.GainLF = 1.0f; + send.LFReference = HIGHPASSFREQREF; + } + + if(slot != Source->Send[static_cast(values[1])].Slot && IsPlayingOrPaused(Source)) + { + /* Add refcount on the new slot, and release the previous slot */ + if(slot) IncrementRef(slot->ref); + if(auto *oldslot = Source->Send[static_cast(values[1])].Slot) + DecrementRef(oldslot->ref); + Source->Send[static_cast(values[1])].Slot = slot; + + /* We must force an update if the auxiliary slot changed on an + * active source, in case the slot is about to be deleted. + */ + Voice *voice{GetSourceVoice(Source, Context)}; + if(voice) UpdateSourceProps(Source, voice, Context); + else Source->mPropsDirty = true; + } + else + { + if(slot) IncrementRef(slot->ref); + if(auto *oldslot = Source->Send[static_cast(values[1])].Slot) + DecrementRef(oldslot->ref); + Source->Send[static_cast(values[1])].Slot = slot; + UpdateSourceProps(Source, Context); + } + return; + + + /* 1x float */ + case AL_CONE_INNER_ANGLE: + case AL_CONE_OUTER_ANGLE: + case AL_PITCH: + case AL_GAIN: + case AL_MIN_GAIN: + case AL_MAX_GAIN: + case AL_REFERENCE_DISTANCE: + case AL_ROLLOFF_FACTOR: + case AL_CONE_OUTER_GAIN: + case AL_MAX_DISTANCE: + case AL_DOPPLER_FACTOR: + case AL_CONE_OUTER_GAINHF: + case AL_AIR_ABSORPTION_FACTOR: + case AL_ROOM_ROLLOFF_FACTOR: + case AL_SOURCE_RADIUS: + case AL_SEC_LENGTH_SOFT: + case AL_SUPER_STEREO_WIDTH_SOFT: + CHECKSIZE(values, 1); + fvals[0] = static_cast(values[0]); + return SetSourcefv(Source, Context, prop, {fvals, 1u}); + + /* 3x float */ + case AL_POSITION: + case AL_VELOCITY: + case AL_DIRECTION: + CHECKSIZE(values, 3); + fvals[0] = static_cast(values[0]); + fvals[1] = static_cast(values[1]); + fvals[2] = static_cast(values[2]); + return SetSourcefv(Source, Context, prop, {fvals, 3u}); + + /* 6x float */ + case AL_ORIENTATION: + CHECKSIZE(values, 6); + fvals[0] = static_cast(values[0]); + fvals[1] = static_cast(values[1]); + fvals[2] = static_cast(values[2]); + fvals[3] = static_cast(values[3]); + fvals[4] = static_cast(values[4]); + fvals[5] = static_cast(values[5]); + return SetSourcefv(Source, Context, prop, {fvals, 6u}); + + case AL_SAMPLE_OFFSET_LATENCY_SOFT: + case AL_SEC_OFFSET_LATENCY_SOFT: + case AL_SEC_OFFSET_CLOCK_SOFT: + case AL_SAMPLE_OFFSET_CLOCK_SOFT: + case AL_STEREO_ANGLES: + break; + } + + ERR("Unexpected property: 0x%04x\n", prop); + Context->setError(AL_INVALID_ENUM, "Invalid source integer property 0x%04x", prop); + return; +} + +void SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, + const al::span values) +{ + float fvals[MaxValues]; + int ivals[MaxValues]; + + switch(prop) + { + case AL_SOURCE_TYPE: + case AL_BUFFERS_QUEUED: + case AL_BUFFERS_PROCESSED: + case AL_SOURCE_STATE: + case AL_BYTE_LENGTH_SOFT: + case AL_SAMPLE_LENGTH_SOFT: + case AL_SAMPLE_OFFSET_LATENCY_SOFT: + case AL_SAMPLE_OFFSET_CLOCK_SOFT: + /* Query only */ + SETERR_RETURN(Context, AL_INVALID_OPERATION,, + "Setting read-only source property 0x%04x", prop); + + /* 1x int */ + case AL_SOURCE_RELATIVE: + case AL_LOOPING: + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + case AL_DIRECT_FILTER_GAINHF_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + case AL_DIRECT_CHANNELS_SOFT: + case AL_DISTANCE_MODEL: + case AL_SOURCE_RESAMPLER_SOFT: + case AL_SOURCE_SPATIALIZE_SOFT: + case AL_STEREO_MODE_SOFT: + CHECKSIZE(values, 1); + CHECKVAL(values[0] <= INT_MAX && values[0] >= INT_MIN); + + ivals[0] = static_cast(values[0]); + return SetSourceiv(Source, Context, prop, {ivals, 1u}); + + /* 1x uint */ + case AL_BUFFER: + case AL_DIRECT_FILTER: + CHECKSIZE(values, 1); + CHECKVAL(values[0] <= UINT_MAX && values[0] >= 0); + + ivals[0] = static_cast(values[0]); + return SetSourceiv(Source, Context, prop, {ivals, 1u}); + + /* 3x uint */ + case AL_AUXILIARY_SEND_FILTER: + CHECKSIZE(values, 3); + CHECKVAL(values[0] <= UINT_MAX && values[0] >= 0 && values[1] <= UINT_MAX && values[1] >= 0 + && values[2] <= UINT_MAX && values[2] >= 0); + + ivals[0] = static_cast(values[0]); + ivals[1] = static_cast(values[1]); + ivals[2] = static_cast(values[2]); + return SetSourceiv(Source, Context, prop, {ivals, 3u}); + + /* 1x float */ + case AL_CONE_INNER_ANGLE: + case AL_CONE_OUTER_ANGLE: + case AL_PITCH: + case AL_GAIN: + case AL_MIN_GAIN: + case AL_MAX_GAIN: + case AL_REFERENCE_DISTANCE: + case AL_ROLLOFF_FACTOR: + case AL_CONE_OUTER_GAIN: + case AL_MAX_DISTANCE: + case AL_DOPPLER_FACTOR: + case AL_CONE_OUTER_GAINHF: + case AL_AIR_ABSORPTION_FACTOR: + case AL_ROOM_ROLLOFF_FACTOR: + case AL_SOURCE_RADIUS: + case AL_SEC_LENGTH_SOFT: + case AL_SUPER_STEREO_WIDTH_SOFT: + CHECKSIZE(values, 1); + fvals[0] = static_cast(values[0]); + return SetSourcefv(Source, Context, prop, {fvals, 1u}); + + /* 3x float */ + case AL_POSITION: + case AL_VELOCITY: + case AL_DIRECTION: + CHECKSIZE(values, 3); + fvals[0] = static_cast(values[0]); + fvals[1] = static_cast(values[1]); + fvals[2] = static_cast(values[2]); + return SetSourcefv(Source, Context, prop, {fvals, 3u}); + + /* 6x float */ + case AL_ORIENTATION: + CHECKSIZE(values, 6); + fvals[0] = static_cast(values[0]); + fvals[1] = static_cast(values[1]); + fvals[2] = static_cast(values[2]); + fvals[3] = static_cast(values[3]); + fvals[4] = static_cast(values[4]); + fvals[5] = static_cast(values[5]); + return SetSourcefv(Source, Context, prop, {fvals, 6u}); + + case AL_SEC_OFFSET_LATENCY_SOFT: + case AL_SEC_OFFSET_CLOCK_SOFT: + case AL_STEREO_ANGLES: + break; + } + + ERR("Unexpected property: 0x%04x\n", prop); + Context->setError(AL_INVALID_ENUM, "Invalid source integer64 property 0x%04x", prop); + return; +} + +#undef CHECKVAL +#undef CHECKSIZE + +#define CHECKSIZE(v, s) do { \ + if LIKELY((v).size() == (s) || (v).size() == MaxValues) break; \ + Context->setError(AL_INVALID_ENUM, \ + "Property 0x%04x expects %d value(s), got %zu", prop, (s), \ + (v).size()); \ + return false; \ +} while(0) + +bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); +bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); +bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); + +bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values) +{ + ALCdevice *device{Context->mALDevice.get()}; + ClockLatency clocktime; + nanoseconds srcclock; + int ivals[MaxValues]; + bool err; + + switch(prop) + { + case AL_GAIN: + CHECKSIZE(values, 1); + values[0] = Source->Gain; + return true; + + case AL_PITCH: + CHECKSIZE(values, 1); + values[0] = Source->Pitch; + return true; + + case AL_MAX_DISTANCE: + CHECKSIZE(values, 1); + values[0] = Source->MaxDistance; + return true; + + case AL_ROLLOFF_FACTOR: + CHECKSIZE(values, 1); + values[0] = Source->RolloffFactor; + return true; + + case AL_REFERENCE_DISTANCE: + CHECKSIZE(values, 1); + values[0] = Source->RefDistance; + return true; + + case AL_CONE_INNER_ANGLE: + CHECKSIZE(values, 1); + values[0] = Source->InnerAngle; + return true; + + case AL_CONE_OUTER_ANGLE: + CHECKSIZE(values, 1); + values[0] = Source->OuterAngle; + return true; + + case AL_MIN_GAIN: + CHECKSIZE(values, 1); + values[0] = Source->MinGain; + return true; + + case AL_MAX_GAIN: + CHECKSIZE(values, 1); + values[0] = Source->MaxGain; + return true; + + case AL_CONE_OUTER_GAIN: + CHECKSIZE(values, 1); + values[0] = Source->OuterGain; + return true; + + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + CHECKSIZE(values, 1); + values[0] = GetSourceOffset(Source, prop, Context); + return true; + + case AL_CONE_OUTER_GAINHF: + CHECKSIZE(values, 1); + values[0] = Source->OuterGainHF; + return true; + + case AL_AIR_ABSORPTION_FACTOR: + CHECKSIZE(values, 1); + values[0] = Source->AirAbsorptionFactor; + return true; + + case AL_ROOM_ROLLOFF_FACTOR: + CHECKSIZE(values, 1); + values[0] = Source->RoomRolloffFactor; + return true; + + case AL_DOPPLER_FACTOR: + CHECKSIZE(values, 1); + values[0] = Source->DopplerFactor; + return true; + + case AL_SOURCE_RADIUS: + CHECKSIZE(values, 1); + values[0] = Source->Radius; + return true; + + case AL_SUPER_STEREO_WIDTH_SOFT: + CHECKSIZE(values, 1); + values[0] = Source->EnhWidth; + return true; + + case AL_BYTE_LENGTH_SOFT: + case AL_SAMPLE_LENGTH_SOFT: + case AL_SEC_LENGTH_SOFT: + CHECKSIZE(values, 1); + values[0] = GetSourceLength(Source, prop); + return true; + + case AL_STEREO_ANGLES: + CHECKSIZE(values, 2); + values[0] = Source->StereoPan[0]; + values[1] = Source->StereoPan[1]; + return true; + + case AL_SEC_OFFSET_LATENCY_SOFT: + CHECKSIZE(values, 2); + /* Get the source offset with the clock time first. Then get the clock + * time with the device latency. Order is important. + */ + values[0] = GetSourceSecOffset(Source, Context, &srcclock); + { + std::lock_guard _{device->StateLock}; + clocktime = GetClockLatency(device, device->Backend.get()); + } + if(srcclock == clocktime.ClockTime) + values[1] = static_cast(clocktime.Latency.count()) / 1000000000.0; + else + { + /* If the clock time incremented, reduce the latency by that much + * since it's that much closer to the source offset it got earlier. + */ + const nanoseconds diff{clocktime.ClockTime - srcclock}; + const nanoseconds latency{clocktime.Latency - std::min(clocktime.Latency, diff)}; + values[1] = static_cast(latency.count()) / 1000000000.0; + } + return true; + + case AL_SEC_OFFSET_CLOCK_SOFT: + CHECKSIZE(values, 2); + values[0] = GetSourceSecOffset(Source, Context, &srcclock); + values[1] = static_cast(srcclock.count()) / 1000000000.0; + return true; + + case AL_POSITION: + CHECKSIZE(values, 3); + values[0] = Source->Position[0]; + values[1] = Source->Position[1]; + values[2] = Source->Position[2]; + return true; + + case AL_VELOCITY: + CHECKSIZE(values, 3); + values[0] = Source->Velocity[0]; + values[1] = Source->Velocity[1]; + values[2] = Source->Velocity[2]; + return true; + + case AL_DIRECTION: + CHECKSIZE(values, 3); + values[0] = Source->Direction[0]; + values[1] = Source->Direction[1]; + values[2] = Source->Direction[2]; + return true; + + case AL_ORIENTATION: + CHECKSIZE(values, 6); + values[0] = Source->OrientAt[0]; + values[1] = Source->OrientAt[1]; + values[2] = Source->OrientAt[2]; + values[3] = Source->OrientUp[0]; + values[4] = Source->OrientUp[1]; + values[5] = Source->OrientUp[2]; + return true; + + /* 1x int */ + case AL_SOURCE_RELATIVE: + case AL_LOOPING: + case AL_SOURCE_STATE: + case AL_BUFFERS_QUEUED: + case AL_BUFFERS_PROCESSED: + case AL_SOURCE_TYPE: + case AL_DIRECT_FILTER_GAINHF_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + case AL_DIRECT_CHANNELS_SOFT: + case AL_DISTANCE_MODEL: + case AL_SOURCE_RESAMPLER_SOFT: + case AL_SOURCE_SPATIALIZE_SOFT: + case AL_STEREO_MODE_SOFT: + CHECKSIZE(values, 1); + if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) + values[0] = static_cast(ivals[0]); + return err; + + case AL_BUFFER: + case AL_DIRECT_FILTER: + case AL_AUXILIARY_SEND_FILTER: + case AL_SAMPLE_OFFSET_LATENCY_SOFT: + case AL_SAMPLE_OFFSET_CLOCK_SOFT: + break; + } + + ERR("Unexpected property: 0x%04x\n", prop); + Context->setError(AL_INVALID_ENUM, "Invalid source double property 0x%04x", prop); + return false; +} + +bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values) +{ + double dvals[MaxValues]; + bool err; + + switch(prop) + { + case AL_SOURCE_RELATIVE: + CHECKSIZE(values, 1); + values[0] = Source->HeadRelative; + return true; + + case AL_LOOPING: + CHECKSIZE(values, 1); + values[0] = Source->Looping; + return true; + + case AL_BUFFER: + CHECKSIZE(values, 1); + { + ALbufferQueueItem *BufferList{(Source->SourceType == AL_STATIC) + ? &Source->mQueue.front() : nullptr}; + ALbuffer *buffer{BufferList ? BufferList->mBuffer : nullptr}; + values[0] = buffer ? static_cast(buffer->id) : 0; + } + return true; + + case AL_SOURCE_STATE: + CHECKSIZE(values, 1); + values[0] = GetSourceState(Source, GetSourceVoice(Source, Context)); + return true; + + case AL_BUFFERS_QUEUED: + CHECKSIZE(values, 1); + values[0] = static_cast(Source->mQueue.size()); + return true; + + case AL_BUFFERS_PROCESSED: + CHECKSIZE(values, 1); + if(Source->Looping || Source->SourceType != AL_STREAMING) + { + /* Buffers on a looping source are in a perpetual state of PENDING, + * so don't report any as PROCESSED + */ + values[0] = 0; + } + else + { + int played{0}; + if(Source->state != AL_INITIAL) + { + const VoiceBufferItem *Current{nullptr}; + if(Voice *voice{GetSourceVoice(Source, Context)}) + Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); + for(auto &item : Source->mQueue) + { + if(&item == Current) + break; + ++played; + } + } + values[0] = played; + } + return true; + + case AL_SOURCE_TYPE: + CHECKSIZE(values, 1); + values[0] = Source->SourceType; + return true; + + case AL_DIRECT_FILTER_GAINHF_AUTO: + CHECKSIZE(values, 1); + values[0] = Source->DryGainHFAuto; + return true; + + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + CHECKSIZE(values, 1); + values[0] = Source->WetGainAuto; + return true; + + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + CHECKSIZE(values, 1); + values[0] = Source->WetGainHFAuto; + return true; + + case AL_DIRECT_CHANNELS_SOFT: + CHECKSIZE(values, 1); + values[0] = EnumFromDirectMode(Source->DirectChannels); + return true; + + case AL_DISTANCE_MODEL: + CHECKSIZE(values, 1); + values[0] = ALenumFromDistanceModel(Source->mDistanceModel); + return true; + + case AL_BYTE_LENGTH_SOFT: + case AL_SAMPLE_LENGTH_SOFT: + case AL_SEC_LENGTH_SOFT: + CHECKSIZE(values, 1); + values[0] = static_cast(mind(GetSourceLength(Source, prop), + std::numeric_limits::max())); + return true; + + case AL_SOURCE_RESAMPLER_SOFT: + CHECKSIZE(values, 1); + values[0] = static_cast(Source->mResampler); + return true; + + case AL_SOURCE_SPATIALIZE_SOFT: + CHECKSIZE(values, 1); + values[0] = EnumFromSpatializeMode(Source->mSpatialize); + return true; + + case AL_STEREO_MODE_SOFT: + CHECKSIZE(values, 1); + values[0] = EnumFromStereoMode(Source->mStereoMode); + return true; + + /* 1x float/double */ + case AL_CONE_INNER_ANGLE: + case AL_CONE_OUTER_ANGLE: + case AL_PITCH: + case AL_GAIN: + case AL_MIN_GAIN: + case AL_MAX_GAIN: + case AL_REFERENCE_DISTANCE: + case AL_ROLLOFF_FACTOR: + case AL_CONE_OUTER_GAIN: + case AL_MAX_DISTANCE: + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + case AL_DOPPLER_FACTOR: + case AL_AIR_ABSORPTION_FACTOR: + case AL_ROOM_ROLLOFF_FACTOR: + case AL_CONE_OUTER_GAINHF: + case AL_SOURCE_RADIUS: + case AL_SUPER_STEREO_WIDTH_SOFT: + CHECKSIZE(values, 1); + if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false) + values[0] = static_cast(dvals[0]); + return err; + + /* 3x float/double */ + case AL_POSITION: + case AL_VELOCITY: + case AL_DIRECTION: + CHECKSIZE(values, 3); + if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false) + { + values[0] = static_cast(dvals[0]); + values[1] = static_cast(dvals[1]); + values[2] = static_cast(dvals[2]); + } + return err; + + /* 6x float/double */ + case AL_ORIENTATION: + CHECKSIZE(values, 6); + if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false) + { + values[0] = static_cast(dvals[0]); + values[1] = static_cast(dvals[1]); + values[2] = static_cast(dvals[2]); + values[3] = static_cast(dvals[3]); + values[4] = static_cast(dvals[4]); + values[5] = static_cast(dvals[5]); + } + return err; + + case AL_SAMPLE_OFFSET_LATENCY_SOFT: + case AL_SAMPLE_OFFSET_CLOCK_SOFT: + break; /* i64 only */ + case AL_SEC_OFFSET_LATENCY_SOFT: + case AL_SEC_OFFSET_CLOCK_SOFT: + break; /* Double only */ + case AL_STEREO_ANGLES: + break; /* Float/double only */ + + case AL_DIRECT_FILTER: + case AL_AUXILIARY_SEND_FILTER: + break; /* ??? */ + } + + ERR("Unexpected property: 0x%04x\n", prop); + Context->setError(AL_INVALID_ENUM, "Invalid source integer property 0x%04x", prop); + return false; +} + +bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values) +{ + ALCdevice *device{Context->mALDevice.get()}; + ClockLatency clocktime; + nanoseconds srcclock; + double dvals[MaxValues]; + int ivals[MaxValues]; + bool err; + + switch(prop) + { + case AL_BYTE_LENGTH_SOFT: + case AL_SAMPLE_LENGTH_SOFT: + case AL_SEC_LENGTH_SOFT: + CHECKSIZE(values, 1); + values[0] = static_cast(GetSourceLength(Source, prop)); + return true; + + case AL_SAMPLE_OFFSET_LATENCY_SOFT: + CHECKSIZE(values, 2); + /* Get the source offset with the clock time first. Then get the clock + * time with the device latency. Order is important. + */ + values[0] = GetSourceSampleOffset(Source, Context, &srcclock); + { + std::lock_guard _{device->StateLock}; + clocktime = GetClockLatency(device, device->Backend.get()); + } + if(srcclock == clocktime.ClockTime) + values[1] = clocktime.Latency.count(); + else + { + /* If the clock time incremented, reduce the latency by that much + * since it's that much closer to the source offset it got earlier. + */ + const nanoseconds diff{clocktime.ClockTime - srcclock}; + values[1] = nanoseconds{clocktime.Latency - std::min(clocktime.Latency, diff)}.count(); + } + return true; + + case AL_SAMPLE_OFFSET_CLOCK_SOFT: + CHECKSIZE(values, 2); + values[0] = GetSourceSampleOffset(Source, Context, &srcclock); + values[1] = srcclock.count(); + return true; + + /* 1x float/double */ + case AL_CONE_INNER_ANGLE: + case AL_CONE_OUTER_ANGLE: + case AL_PITCH: + case AL_GAIN: + case AL_MIN_GAIN: + case AL_MAX_GAIN: + case AL_REFERENCE_DISTANCE: + case AL_ROLLOFF_FACTOR: + case AL_CONE_OUTER_GAIN: + case AL_MAX_DISTANCE: + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + case AL_DOPPLER_FACTOR: + case AL_AIR_ABSORPTION_FACTOR: + case AL_ROOM_ROLLOFF_FACTOR: + case AL_CONE_OUTER_GAINHF: + case AL_SOURCE_RADIUS: + case AL_SUPER_STEREO_WIDTH_SOFT: + CHECKSIZE(values, 1); + if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false) + values[0] = static_cast(dvals[0]); + return err; + + /* 3x float/double */ + case AL_POSITION: + case AL_VELOCITY: + case AL_DIRECTION: + CHECKSIZE(values, 3); + if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false) + { + values[0] = static_cast(dvals[0]); + values[1] = static_cast(dvals[1]); + values[2] = static_cast(dvals[2]); + } + return err; + + /* 6x float/double */ + case AL_ORIENTATION: + CHECKSIZE(values, 6); + if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false) + { + values[0] = static_cast(dvals[0]); + values[1] = static_cast(dvals[1]); + values[2] = static_cast(dvals[2]); + values[3] = static_cast(dvals[3]); + values[4] = static_cast(dvals[4]); + values[5] = static_cast(dvals[5]); + } + return err; + + /* 1x int */ + case AL_SOURCE_RELATIVE: + case AL_LOOPING: + case AL_SOURCE_STATE: + case AL_BUFFERS_QUEUED: + case AL_BUFFERS_PROCESSED: + case AL_SOURCE_TYPE: + case AL_DIRECT_FILTER_GAINHF_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + case AL_DIRECT_CHANNELS_SOFT: + case AL_DISTANCE_MODEL: + case AL_SOURCE_RESAMPLER_SOFT: + case AL_SOURCE_SPATIALIZE_SOFT: + case AL_STEREO_MODE_SOFT: + CHECKSIZE(values, 1); + if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) + values[0] = ivals[0]; + return err; + + /* 1x uint */ + case AL_BUFFER: + case AL_DIRECT_FILTER: + CHECKSIZE(values, 1); + if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) + values[0] = static_cast(ivals[0]); + return err; + + /* 3x uint */ + case AL_AUXILIARY_SEND_FILTER: + CHECKSIZE(values, 3); + if((err=GetSourceiv(Source, Context, prop, {ivals, 3u})) != false) + { + values[0] = static_cast(ivals[0]); + values[1] = static_cast(ivals[1]); + values[2] = static_cast(ivals[2]); + } + return err; + + case AL_SEC_OFFSET_LATENCY_SOFT: + case AL_SEC_OFFSET_CLOCK_SOFT: + break; /* Double only */ + case AL_STEREO_ANGLES: + break; /* Float/double only */ + } + + ERR("Unexpected property: 0x%04x\n", prop); + Context->setError(AL_INVALID_ENUM, "Invalid source integer64 property 0x%04x", prop); + return false; +} + +} // namespace + +AL_API void AL_APIENTRY alGenSources(ALsizei n, ALuint *sources) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Generating %d sources", n); + if UNLIKELY(n <= 0) return; + +#ifdef ALSOFT_EAX + const bool has_eax{context->has_eax()}; + std::unique_lock proplock{}; + if(has_eax) + proplock = std::unique_lock{context->mPropLock}; +#endif + std::unique_lock srclock{context->mSourceLock}; + ALCdevice *device{context->mALDevice.get()}; + if(static_cast(n) > device->SourcesMax-context->mNumSources) + { + context->setError(AL_OUT_OF_MEMORY, "Exceeding %u source limit (%u + %d)", + device->SourcesMax, context->mNumSources, n); + return; + } + if(!EnsureSources(context.get(), static_cast(n))) + { + context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d source%s", n, (n==1)?"":"s"); + return; + } + + if(n == 1) + { + ALsource *source{AllocSource(context.get())}; + sources[0] = source->id; + +#ifdef ALSOFT_EAX + if(has_eax) + source->eax_initialize(context.get()); +#endif // ALSOFT_EAX + } + else + { +#ifdef ALSOFT_EAX + auto eax_sources = al::vector{}; + if(has_eax) + eax_sources.reserve(static_cast(n)); +#endif // ALSOFT_EAX + + al::vector ids; + ids.reserve(static_cast(n)); + do { + ALsource *source{AllocSource(context.get())}; + ids.emplace_back(source->id); + +#ifdef ALSOFT_EAX + if(has_eax) + eax_sources.emplace_back(source); +#endif // ALSOFT_EAX + } while(--n); + std::copy(ids.cbegin(), ids.cend(), sources); + +#ifdef ALSOFT_EAX + for(auto& eax_source : eax_sources) + eax_source->eax_initialize(context.get()); +#endif // ALSOFT_EAX + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + SETERR_RETURN(context, AL_INVALID_VALUE,, "Deleting %d sources", n); + + std::lock_guard _{context->mSourceLock}; + + /* Check that all Sources are valid */ + auto validate_source = [&context](const ALuint sid) -> bool + { return LookupSource(context.get(), sid) != nullptr; }; + + const ALuint *sources_end = sources + n; + auto invsrc = std::find_if_not(sources, sources_end, validate_source); + if UNLIKELY(invsrc != sources_end) + { + context->setError(AL_INVALID_NAME, "Invalid source ID %u", *invsrc); + return; + } + + /* All good. Delete source IDs. */ + auto delete_source = [&context](const ALuint sid) -> void + { + ALsource *src{LookupSource(context.get(), sid)}; + if(src) FreeSource(context.get(), src); + }; + std::for_each(sources, sources_end, delete_source); +} +END_API_FUNC + +AL_API ALboolean AL_APIENTRY alIsSource(ALuint source) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if LIKELY(context) + { + std::lock_guard _{context->mSourceLock}; + if(LookupSource(context.get(), source) != nullptr) + return AL_TRUE; + } + return AL_FALSE; +} +END_API_FUNC + + +AL_API void AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mSourceLock}; + ALsource *Source = LookupSource(context.get(), source); + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else + SetSourcefv(Source, context.get(), static_cast(param), {&value, 1u}); +} +END_API_FUNC + +AL_API void AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mSourceLock}; + ALsource *Source = LookupSource(context.get(), source); + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else + { + const float fvals[3]{ value1, value2, value3 }; + SetSourcefv(Source, context.get(), static_cast(param), fvals); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mSourceLock}; + ALsource *Source = LookupSource(context.get(), source); + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + SetSourcefv(Source, context.get(), static_cast(param), {values, MaxValues}); +} +END_API_FUNC + + +AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mSourceLock}; + ALsource *Source = LookupSource(context.get(), source); + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else + { + const float fval[1]{static_cast(value)}; + SetSourcefv(Source, context.get(), static_cast(param), fval); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mSourceLock}; + ALsource *Source = LookupSource(context.get(), source); + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else + { + const float fvals[3]{static_cast(value1), static_cast(value2), + static_cast(value3)}; + SetSourcefv(Source, context.get(), static_cast(param), fvals); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mSourceLock}; + ALsource *Source = LookupSource(context.get(), source); + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + { + const ALuint count{DoubleValsByProp(param)}; + float fvals[MaxValues]; + for(ALuint i{0};i < count;i++) + fvals[i] = static_cast(values[i]); + SetSourcefv(Source, context.get(), static_cast(param), {fvals, count}); + } +} +END_API_FUNC + + +AL_API void AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mSourceLock}; + ALsource *Source = LookupSource(context.get(), source); + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else + SetSourceiv(Source, context.get(), static_cast(param), {&value, 1u}); +} +END_API_FUNC + +AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mSourceLock}; + ALsource *Source = LookupSource(context.get(), source); + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else + { + const int ivals[3]{ value1, value2, value3 }; + SetSourceiv(Source, context.get(), static_cast(param), ivals); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mSourceLock}; + ALsource *Source = LookupSource(context.get(), source); + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + SetSourceiv(Source, context.get(), static_cast(param), {values, MaxValues}); +} +END_API_FUNC + + +AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else + SetSourcei64v(Source, context.get(), static_cast(param), {&value, 1u}); +} +END_API_FUNC + +AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else + { + const int64_t i64vals[3]{ value1, value2, value3 }; + SetSourcei64v(Source, context.get(), static_cast(param), i64vals); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + std::lock_guard __{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + SetSourcei64v(Source, context.get(), static_cast(param), {values, MaxValues}); +} +END_API_FUNC + + +AL_API void AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!value) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + { + double dval[1]; + if(GetSourcedv(Source, context.get(), static_cast(param), dval)) + *value = static_cast(dval[0]); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!(value1 && value2 && value3)) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + { + double dvals[3]; + if(GetSourcedv(Source, context.get(), static_cast(param), dvals)) + { + *value1 = static_cast(dvals[0]); + *value2 = static_cast(dvals[1]); + *value3 = static_cast(dvals[2]); + } + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + { + const ALuint count{FloatValsByProp(param)}; + double dvals[MaxValues]; + if(GetSourcedv(Source, context.get(), static_cast(param), {dvals, count})) + { + for(ALuint i{0};i < count;i++) + values[i] = static_cast(dvals[i]); + } + } +} +END_API_FUNC + + +AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!value) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + GetSourcedv(Source, context.get(), static_cast(param), {value, 1u}); +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!(value1 && value2 && value3)) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + { + double dvals[3]; + if(GetSourcedv(Source, context.get(), static_cast(param), dvals)) + { + *value1 = dvals[0]; + *value2 = dvals[1]; + *value3 = dvals[2]; + } + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + GetSourcedv(Source, context.get(), static_cast(param), {values, MaxValues}); +} +END_API_FUNC + + +AL_API void AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!value) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + GetSourceiv(Source, context.get(), static_cast(param), {value, 1u}); +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!(value1 && value2 && value3)) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + { + int ivals[3]; + if(GetSourceiv(Source, context.get(), static_cast(param), ivals)) + { + *value1 = ivals[0]; + *value2 = ivals[1]; + *value3 = ivals[2]; + } + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + GetSourceiv(Source, context.get(), static_cast(param), {values, MaxValues}); +} +END_API_FUNC + + +AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!value) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + GetSourcei64v(Source, context.get(), static_cast(param), {value, 1u}); +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!(value1 && value2 && value3)) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + { + int64_t i64vals[3]; + if(GetSourcei64v(Source, context.get(), static_cast(param), i64vals)) + { + *value1 = i64vals[0]; + *value2 = i64vals[1]; + *value3 = i64vals[2]; + } + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mSourceLock}; + ALsource *Source{LookupSource(context.get(), source)}; + if UNLIKELY(!Source) + context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); + else if UNLIKELY(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else + GetSourcei64v(Source, context.get(), static_cast(param), {values, MaxValues}); +} +END_API_FUNC + + +AL_API void AL_APIENTRY alSourcePlay(ALuint source) +START_API_FUNC +{ alSourcePlayv(1, &source); } +END_API_FUNC + +AL_API void AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Playing %d sources", n); + if UNLIKELY(n <= 0) return; + + al::vector extra_sources; + std::array source_storage; + al::span srchandles; + if LIKELY(static_cast(n) <= source_storage.size()) + srchandles = {source_storage.data(), static_cast(n)}; + else + { + extra_sources.resize(static_cast(n)); + srchandles = {extra_sources.data(), extra_sources.size()}; + } + + std::lock_guard _{context->mSourceLock}; + for(auto &srchdl : srchandles) + { + srchdl = LookupSource(context.get(), *sources); + if(!srchdl) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources); + ++sources; + } + + ALCdevice *device{context->mALDevice.get()}; + /* If the device is disconnected, and voices stop on disconnect, go right + * to stopped. + */ + if UNLIKELY(!device->Connected.load(std::memory_order_acquire)) + { + if(context->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) + { + for(ALsource *source : srchandles) + { + /* TODO: Send state change event? */ + source->Offset = 0.0; + source->OffsetType = AL_NONE; + source->state = AL_STOPPED; + } + return; + } + } + + /* Count the number of reusable voices. */ + auto voicelist = context->getVoicesSpan(); + size_t free_voices{0}; + for(const Voice *voice : voicelist) + { + free_voices += (voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped + && voice->mSourceID.load(std::memory_order_relaxed) == 0u + && voice->mPendingChange.load(std::memory_order_relaxed) == false); + if(free_voices == srchandles.size()) + break; + } + if UNLIKELY(srchandles.size() != free_voices) + { + const size_t inc_amount{srchandles.size() - free_voices}; + auto &allvoices = *context->mVoices.load(std::memory_order_relaxed); + if(inc_amount > allvoices.size() - voicelist.size()) + { + /* Increase the number of voices to handle the request. */ + context->allocVoices(inc_amount - (allvoices.size() - voicelist.size())); + } + context->mActiveVoiceCount.fetch_add(inc_amount, std::memory_order_release); + voicelist = context->getVoicesSpan(); + } + + auto voiceiter = voicelist.begin(); + ALuint vidx{0}; + VoiceChange *tail{}, *cur{}; + for(ALsource *source : srchandles) + { + /* Check that there is a queue containing at least one valid, non zero + * length buffer. + */ + auto BufferList = source->mQueue.begin(); + for(;BufferList != source->mQueue.end();++BufferList) + { + if(BufferList->mSampleLen != 0 || BufferList->mCallback) + break; + } + + /* If there's nothing to play, go right to stopped. */ + if UNLIKELY(BufferList == source->mQueue.end()) + { + /* NOTE: A source without any playable buffers should not have a + * Voice since it shouldn't be in a playing or paused state. So + * there's no need to look up its voice and clear the source. + */ + source->Offset = 0.0; + source->OffsetType = AL_NONE; + source->state = AL_STOPPED; + continue; + } + + if(!cur) + cur = tail = GetVoiceChanger(context.get()); + else + { + cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed); + cur = cur->mNext.load(std::memory_order_relaxed); + } + + Voice *voice{GetSourceVoice(source, context.get())}; + switch(GetSourceState(source, voice)) + { + case AL_PAUSED: + /* A source that's paused simply resumes. If there's no voice, it + * was lost from a disconnect, so just start over with a new one. + */ + cur->mOldVoice = nullptr; + if(!voice) break; + cur->mVoice = voice; + cur->mSourceID = source->id; + cur->mState = VChangeState::Play; + source->state = AL_PLAYING; +#ifdef ALSOFT_EAX + if(source->eax_is_initialized()) + source->eax_commit(); +#endif // ALSOFT_EAX + continue; + + case AL_PLAYING: + /* A source that's already playing is restarted from the beginning. + * Stop the current voice and start a new one so it properly cross- + * fades back to the beginning. + */ + if(voice) + voice->mPendingChange.store(true, std::memory_order_relaxed); + cur->mOldVoice = voice; + voice = nullptr; + break; + + default: + assert(voice == nullptr); + cur->mOldVoice = nullptr; +#ifdef ALSOFT_EAX + if(source->eax_is_initialized()) + source->eax_commit(); +#endif // ALSOFT_EAX + break; + } + + /* Find the next unused voice to play this source with. */ + for(;voiceiter != voicelist.end();++voiceiter,++vidx) + { + Voice *v{*voiceiter}; + if(v->mPlayState.load(std::memory_order_acquire) == Voice::Stopped + && v->mSourceID.load(std::memory_order_relaxed) == 0u + && v->mPendingChange.load(std::memory_order_relaxed) == false) + { + voice = v; + break; + } + } + ASSUME(voice != nullptr); + + voice->mPosition.store(0u, std::memory_order_relaxed); + voice->mPositionFrac.store(0, std::memory_order_relaxed); + voice->mCurrentBuffer.store(&source->mQueue.front(), std::memory_order_relaxed); + voice->mFlags.reset(); + /* A source that's not playing or paused has any offset applied when it + * starts playing. + */ + if(const ALenum offsettype{source->OffsetType}) + { + const double offset{source->Offset}; + source->OffsetType = AL_NONE; + source->Offset = 0.0; + if(auto vpos = GetSampleOffset(source->mQueue, offsettype, offset)) + { + voice->mPosition.store(vpos->pos, std::memory_order_relaxed); + voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed); + voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_relaxed); + if(vpos->pos!=0 || vpos->frac!=0 || vpos->bufferitem!=&source->mQueue.front()) + voice->mFlags.set(VoiceIsFading); + } + } + InitVoice(voice, source, std::addressof(*BufferList), context.get(), device); + + source->VoiceIdx = vidx; + source->state = AL_PLAYING; + + cur->mVoice = voice; + cur->mSourceID = source->id; + cur->mState = VChangeState::Play; + } + if LIKELY(tail) + SendVoiceChanges(context.get(), tail); +} +END_API_FUNC + + +AL_API void AL_APIENTRY alSourcePause(ALuint source) +START_API_FUNC +{ alSourcePausev(1, &source); } +END_API_FUNC + +AL_API void AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Pausing %d sources", n); + if UNLIKELY(n <= 0) return; + + al::vector extra_sources; + std::array source_storage; + al::span srchandles; + if LIKELY(static_cast(n) <= source_storage.size()) + srchandles = {source_storage.data(), static_cast(n)}; + else + { + extra_sources.resize(static_cast(n)); + srchandles = {extra_sources.data(), extra_sources.size()}; + } + + std::lock_guard _{context->mSourceLock}; + for(auto &srchdl : srchandles) + { + srchdl = LookupSource(context.get(), *sources); + if(!srchdl) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources); + ++sources; + } + + /* Pausing has to be done in two steps. First, for each source that's + * detected to be playing, chamge the voice (asynchronously) to + * stopping/paused. + */ + VoiceChange *tail{}, *cur{}; + for(ALsource *source : srchandles) + { + Voice *voice{GetSourceVoice(source, context.get())}; + if(GetSourceState(source, voice) == AL_PLAYING) + { + if(!cur) + cur = tail = GetVoiceChanger(context.get()); + else + { + cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed); + cur = cur->mNext.load(std::memory_order_relaxed); + } + cur->mVoice = voice; + cur->mSourceID = source->id; + cur->mState = VChangeState::Pause; + } + } + if LIKELY(tail) + { + SendVoiceChanges(context.get(), tail); + /* Second, now that the voice changes have been sent, because it's + * possible that the voice stopped after it was detected playing and + * before the voice got paused, recheck that the source is still + * considered playing and set it to paused if so. + */ + for(ALsource *source : srchandles) + { + Voice *voice{GetSourceVoice(source, context.get())}; + if(GetSourceState(source, voice) == AL_PLAYING) + source->state = AL_PAUSED; + } + } +} +END_API_FUNC + + +AL_API void AL_APIENTRY alSourceStop(ALuint source) +START_API_FUNC +{ alSourceStopv(1, &source); } +END_API_FUNC + +AL_API void AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Stopping %d sources", n); + if UNLIKELY(n <= 0) return; + + al::vector extra_sources; + std::array source_storage; + al::span srchandles; + if LIKELY(static_cast(n) <= source_storage.size()) + srchandles = {source_storage.data(), static_cast(n)}; + else + { + extra_sources.resize(static_cast(n)); + srchandles = {extra_sources.data(), extra_sources.size()}; + } + + std::lock_guard _{context->mSourceLock}; + for(auto &srchdl : srchandles) + { + srchdl = LookupSource(context.get(), *sources); + if(!srchdl) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources); + ++sources; + } + + VoiceChange *tail{}, *cur{}; + for(ALsource *source : srchandles) + { + if(Voice *voice{GetSourceVoice(source, context.get())}) + { + if(!cur) + cur = tail = GetVoiceChanger(context.get()); + else + { + cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed); + cur = cur->mNext.load(std::memory_order_relaxed); + } + voice->mPendingChange.store(true, std::memory_order_relaxed); + cur->mVoice = voice; + cur->mSourceID = source->id; + cur->mState = VChangeState::Stop; + source->state = AL_STOPPED; + } + source->Offset = 0.0; + source->OffsetType = AL_NONE; + source->VoiceIdx = INVALID_VOICE_IDX; + } + if LIKELY(tail) + SendVoiceChanges(context.get(), tail); +} +END_API_FUNC + + +AL_API void AL_APIENTRY alSourceRewind(ALuint source) +START_API_FUNC +{ alSourceRewindv(1, &source); } +END_API_FUNC + +AL_API void AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Rewinding %d sources", n); + if UNLIKELY(n <= 0) return; + + al::vector extra_sources; + std::array source_storage; + al::span srchandles; + if LIKELY(static_cast(n) <= source_storage.size()) + srchandles = {source_storage.data(), static_cast(n)}; + else + { + extra_sources.resize(static_cast(n)); + srchandles = {extra_sources.data(), extra_sources.size()}; + } + + std::lock_guard _{context->mSourceLock}; + for(auto &srchdl : srchandles) + { + srchdl = LookupSource(context.get(), *sources); + if(!srchdl) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources); + ++sources; + } + + VoiceChange *tail{}, *cur{}; + for(ALsource *source : srchandles) + { + Voice *voice{GetSourceVoice(source, context.get())}; + if(source->state != AL_INITIAL) + { + if(!cur) + cur = tail = GetVoiceChanger(context.get()); + else + { + cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed); + cur = cur->mNext.load(std::memory_order_relaxed); + } + if(voice) + voice->mPendingChange.store(true, std::memory_order_relaxed); + cur->mVoice = voice; + cur->mSourceID = source->id; + cur->mState = VChangeState::Reset; + source->state = AL_INITIAL; + } + source->Offset = 0.0; + source->OffsetType = AL_NONE; + source->VoiceIdx = INVALID_VOICE_IDX; + } + if LIKELY(tail) + SendVoiceChanges(context.get(), tail); +} +END_API_FUNC + + +AL_API void AL_APIENTRY alSourceQueueBuffers(ALuint src, ALsizei nb, const ALuint *buffers) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(nb < 0) + context->setError(AL_INVALID_VALUE, "Queueing %d buffers", nb); + if UNLIKELY(nb <= 0) return; + + std::lock_guard _{context->mSourceLock}; + ALsource *source{LookupSource(context.get(),src)}; + if UNLIKELY(!source) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", src); + + /* Can't queue on a Static Source */ + if UNLIKELY(source->SourceType == AL_STATIC) + SETERR_RETURN(context, AL_INVALID_OPERATION,, "Queueing onto static source %u", src); + + /* Check for a valid Buffer, for its frequency and format */ + ALCdevice *device{context->mALDevice.get()}; + ALbuffer *BufferFmt{nullptr}; + for(auto &item : source->mQueue) + { + BufferFmt = item.mBuffer; + if(BufferFmt) break; + } + + std::unique_lock buflock{device->BufferLock}; + const size_t NewListStart{source->mQueue.size()}; + ALbufferQueueItem *BufferList{nullptr}; + for(ALsizei i{0};i < nb;i++) + { + bool fmt_mismatch{false}; + ALbuffer *buffer{nullptr}; + if(buffers[i] && (buffer=LookupBuffer(device, buffers[i])) == nullptr) + { + context->setError(AL_INVALID_NAME, "Queueing invalid buffer ID %u", buffers[i]); + goto buffer_error; + } + if(buffer && buffer->mCallback) + { + context->setError(AL_INVALID_OPERATION, "Queueing callback buffer %u", buffers[i]); + goto buffer_error; + } + + source->mQueue.emplace_back(); + if(!BufferList) + BufferList = &source->mQueue.back(); + else + { + auto &item = source->mQueue.back(); + BufferList->mNext.store(&item, std::memory_order_relaxed); + BufferList = &item; + } + if(!buffer) continue; + BufferList->mSampleLen = buffer->mSampleLen; + BufferList->mLoopEnd = buffer->mSampleLen; + BufferList->mSamples = buffer->mData.data(); + BufferList->mBuffer = buffer; + IncrementRef(buffer->ref); + + if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) + { + context->setError(AL_INVALID_OPERATION, "Queueing non-persistently mapped buffer %u", + buffer->id); + goto buffer_error; + } + + if(BufferFmt == nullptr) + BufferFmt = buffer; + else + { + fmt_mismatch |= BufferFmt->mSampleRate != buffer->mSampleRate; + fmt_mismatch |= BufferFmt->mChannels != buffer->mChannels; + if(BufferFmt->isBFormat()) + { + fmt_mismatch |= BufferFmt->mAmbiLayout != buffer->mAmbiLayout; + fmt_mismatch |= BufferFmt->mAmbiScaling != buffer->mAmbiScaling; + } + fmt_mismatch |= BufferFmt->mAmbiOrder != buffer->mAmbiOrder; + fmt_mismatch |= BufferFmt->OriginalType != buffer->OriginalType; + } + if UNLIKELY(fmt_mismatch) + { + context->setError(AL_INVALID_OPERATION, "Queueing buffer with mismatched format"); + + buffer_error: + /* A buffer failed (invalid ID or format), so unlock and release + * each buffer we had. + */ + auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart); + for(;iter != source->mQueue.end();++iter) + { + if(ALbuffer *buf{iter->mBuffer}) + DecrementRef(buf->ref); + } + source->mQueue.resize(NewListStart); + return; + } + } + /* All buffers good. */ + buflock.unlock(); + + /* Source is now streaming */ + source->SourceType = AL_STREAMING; + + if(NewListStart != 0) + { + auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart); + (iter-1)->mNext.store(std::addressof(*iter), std::memory_order_release); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alSourceUnqueueBuffers(ALuint src, ALsizei nb, ALuint *buffers) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if UNLIKELY(nb < 0) + context->setError(AL_INVALID_VALUE, "Unqueueing %d buffers", nb); + if UNLIKELY(nb <= 0) return; + + std::lock_guard _{context->mSourceLock}; + ALsource *source{LookupSource(context.get(),src)}; + if UNLIKELY(!source) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", src); + + if UNLIKELY(source->SourceType != AL_STREAMING) + SETERR_RETURN(context, AL_INVALID_VALUE,, "Unqueueing from a non-streaming source %u", + src); + if UNLIKELY(source->Looping) + SETERR_RETURN(context, AL_INVALID_VALUE,, "Unqueueing from looping source %u", src); + + /* Make sure enough buffers have been processed to unqueue. */ + uint processed{0u}; + if LIKELY(source->state != AL_INITIAL) + { + VoiceBufferItem *Current{nullptr}; + if(Voice *voice{GetSourceVoice(source, context.get())}) + Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); + for(auto &item : source->mQueue) + { + if(&item == Current) + break; + ++processed; + } + } + if UNLIKELY(processed < static_cast(nb)) + SETERR_RETURN(context, AL_INVALID_VALUE,, "Unqueueing %d buffer%s (only %u processed)", + nb, (nb==1)?"":"s", processed); + + do { + auto &head = source->mQueue.front(); + if(ALbuffer *buffer{head.mBuffer}) + { + *(buffers++) = buffer->id; + DecrementRef(buffer->ref); + } + else + *(buffers++) = 0; + source->mQueue.pop_front(); + } while(--nb); +} +END_API_FUNC + + +AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint, ALsizei, const ALuint*) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + context->setError(AL_INVALID_OPERATION, "alSourceQueueBufferLayersSOFT not supported"); +} +END_API_FUNC + + +ALsource::ALsource() +{ + Direct.Gain = 1.0f; + Direct.GainHF = 1.0f; + Direct.HFReference = LOWPASSFREQREF; + Direct.GainLF = 1.0f; + Direct.LFReference = HIGHPASSFREQREF; + for(auto &send : Send) + { + send.Slot = nullptr; + send.Gain = 1.0f; + send.GainHF = 1.0f; + send.HFReference = LOWPASSFREQREF; + send.GainLF = 1.0f; + send.LFReference = HIGHPASSFREQREF; + } +} + +ALsource::~ALsource() +{ + for(auto &item : mQueue) + { + if(ALbuffer *buffer{item.mBuffer}) + DecrementRef(buffer->ref); + } + + auto clear_send = [](ALsource::SendData &send) -> void + { if(send.Slot) DecrementRef(send.Slot->ref); }; + std::for_each(Send.begin(), Send.end(), clear_send); +} + +void UpdateAllSourceProps(ALCcontext *context) +{ + std::lock_guard _{context->mSourceLock}; +#ifdef ALSOFT_EAX + if(context->has_eax()) + { + /* If EAX is enabled, we need to go through and commit all sources' EAX + * changes, along with updating its voice, if any. + */ + for(auto &sublist : context->mSourceList) + { + uint64_t usemask{~sublist.FreeMask}; + while(usemask) + { + const int idx{al::countr_zero(usemask)}; + usemask &= ~(1_u64 << idx); + + ALsource *source{sublist.Sources + idx}; + source->eax_commit(); + + if(Voice *voice{GetSourceVoice(source, context)}) + { + if(std::exchange(source->mPropsDirty, false)) + UpdateSourceProps(source, voice, context); + } + } + } + } + else +#endif + { + auto voicelist = context->getVoicesSpan(); + ALuint vidx{0u}; + for(Voice *voice : voicelist) + { + ALuint sid{voice->mSourceID.load(std::memory_order_acquire)}; + ALsource *source = sid ? LookupSource(context, sid) : nullptr; + if(source && source->VoiceIdx == vidx) + { + if(std::exchange(source->mPropsDirty, false)) + UpdateSourceProps(source, voice, context); + } + ++vidx; + } + } +} + +SourceSubList::~SourceSubList() +{ + uint64_t usemask{~FreeMask}; + while(usemask) + { + const int idx{al::countr_zero(usemask)}; + usemask &= ~(1_u64 << idx); + al::destroy_at(Sources+idx); + } + FreeMask = ~usemask; + al_free(Sources); + Sources = nullptr; +} + + +#ifdef ALSOFT_EAX +class EaxSourceException : + public EaxException +{ +public: + explicit EaxSourceException( + const char* message) + : + EaxException{"EAX_SOURCE", message} + { + } +}; // EaxSourceException + + +class EaxSourceActiveFxSlotsException : + public EaxException +{ +public: + explicit EaxSourceActiveFxSlotsException( + const char* message) + : + EaxException{"EAX_SOURCE_ACTIVE_FX_SLOTS", message} + { + } +}; // EaxSourceActiveFxSlotsException + + +class EaxSourceSendException : + public EaxException +{ +public: + explicit EaxSourceSendException( + const char* message) + : + EaxException{"EAX_SOURCE_SEND", message} + { + } +}; // EaxSourceSendException + + +void EaxUpdateSourceVoice(ALsource *source, ALCcontext *context) +{ + if(Voice *voice{GetSourceVoice(source, context)}) + { + if(std::exchange(source->mPropsDirty, false)) + UpdateSourceProps(source, voice, context); + } +} + + +void ALsource::eax_initialize(ALCcontext *context) noexcept +{ + assert(context); + eax_al_context_ = context; + eax_set_defaults(); + eax_initialize_fx_slots(); + + eax_d_ = eax_; +} + +void ALsource::eax_update_filters() +{ + eax_update_filters_internal(); +} + +void ALsource::eax_update(EaxContextSharedDirtyFlags) +{ + /* NOTE: EaxContextSharedDirtyFlags only has one flag (primary_fx_slot_id), + * which must be true for this to be called. + */ + if(eax_uses_primary_id_) + eax_update_primary_fx_slot_id(); +} + +void ALsource::eax_commit_and_update() +{ + eax_apply_deferred(); + EaxUpdateSourceVoice(this, eax_al_context_); +} + +ALsource* ALsource::eax_lookup_source( + ALCcontext& al_context, + ALuint source_id) noexcept +{ + return LookupSource(&al_context, source_id); +} + +[[noreturn]] +void ALsource::eax_fail( + const char* message) +{ + throw EaxSourceException{message}; +} + +void ALsource::eax_set_source_defaults() noexcept +{ + eax1_.fMix = EAX_REVERBMIX_USEDISTANCE; + + eax_.source.lDirect = EAXSOURCE_DEFAULTDIRECT; + eax_.source.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF; + eax_.source.lRoom = EAXSOURCE_DEFAULTROOM; + eax_.source.lRoomHF = EAXSOURCE_DEFAULTROOMHF; + eax_.source.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION; + eax_.source.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO; + eax_.source.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; + eax_.source.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; + eax_.source.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; + eax_.source.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; + eax_.source.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; + eax_.source.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO; + eax_.source.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF; + eax_.source.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR; + eax_.source.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR; + eax_.source.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR; + eax_.source.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR; + eax_.source.ulFlags = EAXSOURCE_DEFAULTFLAGS; + eax_.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; +} + +void ALsource::eax_set_active_fx_slots_defaults() noexcept +{ + eax_.active_fx_slots = EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; +} + +void ALsource::eax_set_send_defaults(EAXSOURCEALLSENDPROPERTIES& eax_send) noexcept +{ + eax_send.guidReceivingFXSlotID = EAX_NULL_GUID; + eax_send.lSend = EAXSOURCE_DEFAULTSEND; + eax_send.lSendHF = EAXSOURCE_DEFAULTSENDHF; + eax_send.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; + eax_send.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; + eax_send.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; + eax_send.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; + eax_send.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; + eax_send.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO; +} + +void ALsource::eax_set_sends_defaults() noexcept +{ + for (auto& eax_send : eax_.sends) + { + eax_set_send_defaults(eax_send); + } +} + +void ALsource::eax_set_speaker_levels_defaults() noexcept +{ + std::fill(eax_.speaker_levels.begin(), eax_.speaker_levels.end(), EAXSOURCE_DEFAULTSPEAKERLEVEL); +} + +void ALsource::eax_set_defaults() noexcept +{ + eax_set_source_defaults(); + eax_set_active_fx_slots_defaults(); + eax_set_sends_defaults(); + eax_set_speaker_levels_defaults(); +} + +float ALsource::eax_calculate_dst_occlusion_mb( + long src_occlusion_mb, + float path_ratio, + float lf_ratio) noexcept +{ + const auto ratio_1 = path_ratio + lf_ratio - 1.0F; + const auto ratio_2 = path_ratio * lf_ratio; + const auto ratio = (ratio_2 > ratio_1) ? ratio_2 : ratio_1; + const auto dst_occlustion_mb = static_cast(src_occlusion_mb) * ratio; + + return dst_occlustion_mb; +} + +EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept +{ + auto gain_mb = + static_cast(eax_.source.lDirect) + + + (static_cast(eax_.source.lObstruction) * eax_.source.flObstructionLFRatio) + + + eax_calculate_dst_occlusion_mb( + eax_.source.lOcclusion, + eax_.source.flOcclusionDirectRatio, + eax_.source.flOcclusionLFRatio); + + auto gain_hf_mb = + static_cast(eax_.source.lDirectHF) + + + static_cast(eax_.source.lObstruction) + + + (static_cast(eax_.source.lOcclusion) * eax_.source.flOcclusionDirectRatio); + + for (auto i = std::size_t{}; i < EAX_MAX_FXSLOTS; ++i) + { + if (eax_active_fx_slots_[i]) + { + const auto& send = eax_.sends[i]; + + gain_mb += eax_calculate_dst_occlusion_mb( + send.lOcclusion, + send.flOcclusionDirectRatio, + send.flOcclusionLFRatio); + + gain_hf_mb += static_cast(send.lOcclusion) * send.flOcclusionDirectRatio; + } + } + + const auto al_low_pass_param = EaxAlLowPassParam + { + level_mb_to_gain(gain_mb), + minf(level_mb_to_gain(gain_hf_mb), 1.0f) + }; + + return al_low_pass_param; +} + +EaxAlLowPassParam ALsource::eax_create_room_filter_param( + const ALeffectslot& fx_slot, + const EAXSOURCEALLSENDPROPERTIES& send) const noexcept +{ + const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot(); + + const auto gain_mb = + static_cast( + eax_.source.lRoom + + send.lSend) + + + eax_calculate_dst_occlusion_mb( + eax_.source.lOcclusion, + eax_.source.flOcclusionRoomRatio, + eax_.source.flOcclusionLFRatio + ) + + + eax_calculate_dst_occlusion_mb( + send.lOcclusion, + send.flOcclusionRoomRatio, + send.flOcclusionLFRatio + ) + + + (static_cast(eax_.source.lExclusion) * eax_.source.flExclusionLFRatio) + + (static_cast(send.lExclusion) * send.flExclusionLFRatio) + + + 0.0F; + + const auto gain_hf_mb = + static_cast( + eax_.source.lRoomHF + + send.lSendHF) + + + (static_cast(fx_slot_eax.lOcclusion + eax_.source.lOcclusion) * eax_.source.flOcclusionRoomRatio) + + (static_cast(send.lOcclusion) * send.flOcclusionRoomRatio) + + + static_cast( + eax_.source.lExclusion + + send.lExclusion) + + + 0.0F; + + const auto al_low_pass_param = EaxAlLowPassParam + { + level_mb_to_gain(gain_mb), + minf(level_mb_to_gain(gain_hf_mb), 1.0f) + }; + + return al_low_pass_param; +} + +void ALsource::eax_set_fx_slots() +{ + eax_uses_primary_id_ = false; + eax_has_active_fx_slots_ = false; + eax_active_fx_slots_.fill(false); + + for(const auto& eax_active_fx_slot_id : eax_.active_fx_slots.guidActiveFXSlots) + { + auto fx_slot_index = EaxFxSlotIndex{}; + + if(eax_active_fx_slot_id == EAX_PrimaryFXSlotID) + { + eax_uses_primary_id_ = true; + fx_slot_index = eax_al_context_->eax_get_primary_fx_slot_index(); + } + else + { + fx_slot_index = eax_active_fx_slot_id; + } + + if(fx_slot_index.has_value()) + { + eax_has_active_fx_slots_ = true; + eax_active_fx_slots_[*fx_slot_index] = true; + } + } + + for(auto i = 0u;i < eax_active_fx_slots_.size();++i) + { + if(!eax_active_fx_slots_[i]) + eax_set_al_source_send(nullptr, i, EaxAlLowPassParam{1.0f, 1.0f}); + } +} + +void ALsource::eax_initialize_fx_slots() +{ + eax_set_fx_slots(); + eax_update_filters_internal(); +} + +void ALsource::eax_update_direct_filter_internal() +{ + const auto& direct_param = eax_create_direct_filter_param(); + + Direct.Gain = direct_param.gain; + Direct.GainHF = direct_param.gain_hf; + Direct.HFReference = LOWPASSFREQREF; + Direct.GainLF = 1.0f; + Direct.LFReference = HIGHPASSFREQREF; + mPropsDirty = true; +} + +void ALsource::eax_update_room_filters_internal() +{ + if (!eax_has_active_fx_slots_) + { + return; + } + + for (auto i = 0u; i < EAX_MAX_FXSLOTS; ++i) + { + if (eax_active_fx_slots_[i]) + { + auto& fx_slot = eax_al_context_->eax_get_fx_slot(static_cast(i)); + const auto& send = eax_.sends[i]; + const auto& room_param = eax_create_room_filter_param(fx_slot, send); + + eax_set_al_source_send(&fx_slot, i, room_param); + } + } +} + +void ALsource::eax_update_filters_internal() +{ + eax_update_direct_filter_internal(); + eax_update_room_filters_internal(); +} + +void ALsource::eax_update_primary_fx_slot_id() +{ + const auto& previous_primary_fx_slot_index = eax_al_context_->eax_get_previous_primary_fx_slot_index(); + const auto& primary_fx_slot_index = eax_al_context_->eax_get_primary_fx_slot_index(); + + if (previous_primary_fx_slot_index == primary_fx_slot_index) + { + return; + } + + if (previous_primary_fx_slot_index.has_value()) + { + const auto fx_slot_index = previous_primary_fx_slot_index.value(); + eax_active_fx_slots_[fx_slot_index] = false; + + eax_set_al_source_send(nullptr, fx_slot_index, EaxAlLowPassParam{1.0f, 1.0f}); + } + + if (primary_fx_slot_index.has_value()) + { + const auto fx_slot_index = primary_fx_slot_index.value(); + eax_active_fx_slots_[fx_slot_index] = true; + + auto& fx_slot = eax_al_context_->eax_get_fx_slot(fx_slot_index); + const auto& send = eax_.sends[fx_slot_index]; + const auto& room_param = eax_create_room_filter_param(fx_slot, send); + + eax_set_al_source_send(&fx_slot, fx_slot_index, room_param); + } + + eax_has_active_fx_slots_ = std::any_of( + eax_active_fx_slots_.cbegin(), + eax_active_fx_slots_.cend(), + [](const auto& item) + { + return item; + } + ); +} + +void ALsource::eax_defer_active_fx_slots( + const EaxEaxCall& eax_call) +{ + const auto active_fx_slots_span = + eax_call.get_values(); + + const auto fx_slot_count = active_fx_slots_span.size(); + + if (fx_slot_count <= 0 || fx_slot_count > EAX_MAX_FXSLOTS) + { + throw EaxSourceActiveFxSlotsException{"Count out of range."}; + } + + for (auto i = std::size_t{}; i < fx_slot_count; ++i) + { + const auto& fx_slot_guid = active_fx_slots_span[i]; + + if (fx_slot_guid != EAX_NULL_GUID && + fx_slot_guid != EAX_PrimaryFXSlotID && + fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot0 && + fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot0 && + fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot1 && + fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot1 && + fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot2 && + fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot2 && + fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot3 && + fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot3) + { + throw EaxSourceActiveFxSlotsException{"Unsupported GUID."}; + } + } + + for (auto i = std::size_t{}; i < fx_slot_count; ++i) + { + eax_d_.active_fx_slots.guidActiveFXSlots[i] = active_fx_slots_span[i]; + } + + for (auto i = fx_slot_count; i < EAX_MAX_FXSLOTS; ++i) + { + eax_d_.active_fx_slots.guidActiveFXSlots[i] = EAX_NULL_GUID; + } + + eax_are_active_fx_slots_dirty_ = (eax_d_.active_fx_slots != eax_.active_fx_slots); +} + + +const char* ALsource::eax_get_exclusion_name() noexcept +{ + return "Exclusion"; +} + +const char* ALsource::eax_get_exclusion_lf_ratio_name() noexcept +{ + return "Exclusion LF Ratio"; +} + +const char* ALsource::eax_get_occlusion_name() noexcept +{ + return "Occlusion"; +} + +const char* ALsource::eax_get_occlusion_lf_ratio_name() noexcept +{ + return "Occlusion LF Ratio"; +} + +const char* ALsource::eax_get_occlusion_direct_ratio_name() noexcept +{ + return "Occlusion Direct Ratio"; +} + +const char* ALsource::eax_get_occlusion_room_ratio_name() noexcept +{ + return "Occlusion Room Ratio"; +} + +void ALsource::eax1_validate_reverb_mix(float reverb_mix) +{ + if (reverb_mix == EAX_REVERBMIX_USEDISTANCE) + return; + + eax_validate_range("Reverb Mix", reverb_mix, EAX_BUFFER_MINREVERBMIX, EAX_BUFFER_MAXREVERBMIX); +} + +void ALsource::eax_validate_send_receiving_fx_slot_guid( + const GUID& guidReceivingFXSlotID) +{ + if (guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot0 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot0 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot1 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot1 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot2 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot2 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot3 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot3) + { + throw EaxSourceSendException{"Unsupported receiving FX slot GUID."}; + } +} + +void ALsource::eax_validate_send_send( + long lSend) +{ + eax_validate_range( + "Send", + lSend, + EAXSOURCE_MINSEND, + EAXSOURCE_MAXSEND); +} + +void ALsource::eax_validate_send_send_hf( + long lSendHF) +{ + eax_validate_range( + "Send HF", + lSendHF, + EAXSOURCE_MINSENDHF, + EAXSOURCE_MAXSENDHF); +} + +void ALsource::eax_validate_send_occlusion( + long lOcclusion) +{ + eax_validate_range( + eax_get_occlusion_name(), + lOcclusion, + EAXSOURCE_MINOCCLUSION, + EAXSOURCE_MAXOCCLUSION); +} + +void ALsource::eax_validate_send_occlusion_lf_ratio( + float flOcclusionLFRatio) +{ + eax_validate_range( + eax_get_occlusion_lf_ratio_name(), + flOcclusionLFRatio, + EAXSOURCE_MINOCCLUSIONLFRATIO, + EAXSOURCE_MAXOCCLUSIONLFRATIO); +} + +void ALsource::eax_validate_send_occlusion_room_ratio( + float flOcclusionRoomRatio) +{ + eax_validate_range( + eax_get_occlusion_room_ratio_name(), + flOcclusionRoomRatio, + EAXSOURCE_MINOCCLUSIONROOMRATIO, + EAXSOURCE_MAXOCCLUSIONROOMRATIO); +} + +void ALsource::eax_validate_send_occlusion_direct_ratio( + float flOcclusionDirectRatio) +{ + eax_validate_range( + eax_get_occlusion_direct_ratio_name(), + flOcclusionDirectRatio, + EAXSOURCE_MINOCCLUSIONDIRECTRATIO, + EAXSOURCE_MAXOCCLUSIONDIRECTRATIO); +} + +void ALsource::eax_validate_send_exclusion( + long lExclusion) +{ + eax_validate_range( + eax_get_exclusion_name(), + lExclusion, + EAXSOURCE_MINEXCLUSION, + EAXSOURCE_MAXEXCLUSION); +} + +void ALsource::eax_validate_send_exclusion_lf_ratio( + float flExclusionLFRatio) +{ + eax_validate_range( + eax_get_exclusion_lf_ratio_name(), + flExclusionLFRatio, + EAXSOURCE_MINEXCLUSIONLFRATIO, + EAXSOURCE_MAXEXCLUSIONLFRATIO); +} + +void ALsource::eax_validate_send( + const EAXSOURCESENDPROPERTIES& all) +{ + eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID); + eax_validate_send_send(all.lSend); + eax_validate_send_send_hf(all.lSendHF); +} + +void ALsource::eax_validate_send_exclusion_all( + const EAXSOURCEEXCLUSIONSENDPROPERTIES& all) +{ + eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID); + eax_validate_send_exclusion(all.lExclusion); + eax_validate_send_exclusion_lf_ratio(all.flExclusionLFRatio); +} + +void ALsource::eax_validate_send_occlusion_all( + const EAXSOURCEOCCLUSIONSENDPROPERTIES& all) +{ + eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID); + eax_validate_send_occlusion(all.lOcclusion); + eax_validate_send_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_validate_send_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_validate_send_occlusion_direct_ratio(all.flOcclusionDirectRatio); +} + +void ALsource::eax_validate_send_all( + const EAXSOURCEALLSENDPROPERTIES& all) +{ + eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID); + eax_validate_send_send(all.lSend); + eax_validate_send_send_hf(all.lSendHF); + eax_validate_send_occlusion(all.lOcclusion); + eax_validate_send_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_validate_send_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_validate_send_occlusion_direct_ratio(all.flOcclusionDirectRatio); + eax_validate_send_exclusion(all.lExclusion); + eax_validate_send_exclusion_lf_ratio(all.flExclusionLFRatio); +} + +EaxFxSlotIndexValue ALsource::eax_get_send_index( + const GUID& send_guid) +{ + if (false) + { + } + else if (send_guid == EAXPROPERTYID_EAX40_FXSlot0 || send_guid == EAXPROPERTYID_EAX50_FXSlot0) + { + return 0; + } + else if (send_guid == EAXPROPERTYID_EAX40_FXSlot1 || send_guid == EAXPROPERTYID_EAX50_FXSlot1) + { + return 1; + } + else if (send_guid == EAXPROPERTYID_EAX40_FXSlot2 || send_guid == EAXPROPERTYID_EAX50_FXSlot2) + { + return 2; + } + else if (send_guid == EAXPROPERTYID_EAX40_FXSlot3 || send_guid == EAXPROPERTYID_EAX50_FXSlot3) + { + return 3; + } + else + { + throw EaxSourceSendException{"Unsupported receiving FX slot GUID."}; + } +} + +void ALsource::eax_defer_send_send( + long lSend, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].lSend = lSend; + + eax_sends_dirty_flags_.sends[index].lSend = + (eax_.sends[index].lSend != eax_d_.sends[index].lSend); +} + +void ALsource::eax_defer_send_send_hf( + long lSendHF, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].lSendHF = lSendHF; + + eax_sends_dirty_flags_.sends[index].lSendHF = + (eax_.sends[index].lSendHF != eax_d_.sends[index].lSendHF); +} + +void ALsource::eax_defer_send_occlusion( + long lOcclusion, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].lOcclusion = lOcclusion; + + eax_sends_dirty_flags_.sends[index].lOcclusion = + (eax_.sends[index].lOcclusion != eax_d_.sends[index].lOcclusion); +} + +void ALsource::eax_defer_send_occlusion_lf_ratio( + float flOcclusionLFRatio, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].flOcclusionLFRatio = flOcclusionLFRatio; + + eax_sends_dirty_flags_.sends[index].flOcclusionLFRatio = + (eax_.sends[index].flOcclusionLFRatio != eax_d_.sends[index].flOcclusionLFRatio); +} + +void ALsource::eax_defer_send_occlusion_room_ratio( + float flOcclusionRoomRatio, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].flOcclusionRoomRatio = flOcclusionRoomRatio; + + eax_sends_dirty_flags_.sends[index].flOcclusionRoomRatio = + (eax_.sends[index].flOcclusionRoomRatio != eax_d_.sends[index].flOcclusionRoomRatio); +} + +void ALsource::eax_defer_send_occlusion_direct_ratio( + float flOcclusionDirectRatio, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].flOcclusionDirectRatio = flOcclusionDirectRatio; + + eax_sends_dirty_flags_.sends[index].flOcclusionDirectRatio = + (eax_.sends[index].flOcclusionDirectRatio != eax_d_.sends[index].flOcclusionDirectRatio); +} + +void ALsource::eax_defer_send_exclusion( + long lExclusion, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].lExclusion = lExclusion; + + eax_sends_dirty_flags_.sends[index].lExclusion = + (eax_.sends[index].lExclusion != eax_d_.sends[index].lExclusion); +} + +void ALsource::eax_defer_send_exclusion_lf_ratio( + float flExclusionLFRatio, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].flExclusionLFRatio = flExclusionLFRatio; + + eax_sends_dirty_flags_.sends[index].flExclusionLFRatio = + (eax_.sends[index].flExclusionLFRatio != eax_d_.sends[index].flExclusionLFRatio); +} + +void ALsource::eax_defer_send( + const EAXSOURCESENDPROPERTIES& all, + EaxFxSlotIndexValue index) +{ + eax_defer_send_send(all.lSend, index); + eax_defer_send_send_hf(all.lSendHF, index); +} + +void ALsource::eax_defer_send_exclusion_all( + const EAXSOURCEEXCLUSIONSENDPROPERTIES& all, + EaxFxSlotIndexValue index) +{ + eax_defer_send_exclusion(all.lExclusion, index); + eax_defer_send_exclusion_lf_ratio(all.flExclusionLFRatio, index); +} + +void ALsource::eax_defer_send_occlusion_all( + const EAXSOURCEOCCLUSIONSENDPROPERTIES& all, + EaxFxSlotIndexValue index) +{ + eax_defer_send_occlusion(all.lOcclusion, index); + eax_defer_send_occlusion_lf_ratio(all.flOcclusionLFRatio, index); + eax_defer_send_occlusion_room_ratio(all.flOcclusionRoomRatio, index); + eax_defer_send_occlusion_direct_ratio(all.flOcclusionDirectRatio, index); +} + +void ALsource::eax_defer_send_all( + const EAXSOURCEALLSENDPROPERTIES& all, + EaxFxSlotIndexValue index) +{ + eax_defer_send_send(all.lSend, index); + eax_defer_send_send_hf(all.lSendHF, index); + eax_defer_send_occlusion(all.lOcclusion, index); + eax_defer_send_occlusion_lf_ratio(all.flOcclusionLFRatio, index); + eax_defer_send_occlusion_room_ratio(all.flOcclusionRoomRatio, index); + eax_defer_send_occlusion_direct_ratio(all.flOcclusionDirectRatio, index); + eax_defer_send_exclusion(all.lExclusion, index); + eax_defer_send_exclusion_lf_ratio(all.flExclusionLFRatio, index); +} + +void ALsource::eax_defer_send( + const EaxEaxCall& eax_call) +{ + const auto eax_all_span = + eax_call.get_values(); + + const auto count = eax_all_span.size(); + + if (count <= 0 || count > EAX_MAX_FXSLOTS) + { + throw EaxSourceSendException{"Send count out of range."}; + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + eax_validate_send(all); + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID); + eax_defer_send(all, send_index); + } +} + +void ALsource::eax_defer_send_exclusion_all( + const EaxEaxCall& eax_call) +{ + const auto eax_all_span = + eax_call.get_values(); + + const auto count = eax_all_span.size(); + + if (count <= 0 || count > EAX_MAX_FXSLOTS) + { + throw EaxSourceSendException{"Send exclusion all count out of range."}; + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + eax_validate_send_exclusion_all(all); + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID); + eax_defer_send_exclusion_all(all, send_index); + } +} + +void ALsource::eax_defer_send_occlusion_all( + const EaxEaxCall& eax_call) +{ + const auto eax_all_span = + eax_call.get_values(); + + const auto count = eax_all_span.size(); + + if (count <= 0 || count > EAX_MAX_FXSLOTS) + { + throw EaxSourceSendException{"Send occlusion all count out of range."}; + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + eax_validate_send_occlusion_all(all); + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID); + eax_defer_send_occlusion_all(all, send_index); + } +} + +void ALsource::eax_defer_send_all( + const EaxEaxCall& eax_call) +{ + const auto eax_all_span = + eax_call.get_values(); + + const auto count = eax_all_span.size(); + + if (count <= 0 || count > EAX_MAX_FXSLOTS) + { + throw EaxSourceSendException{"Send all count out of range."}; + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + eax_validate_send_all(all); + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID); + eax_defer_send_all(all, send_index); + } +} + + +void ALsource::eax_validate_source_direct( + long direct) +{ + eax_validate_range( + "Direct", + direct, + EAXSOURCE_MINDIRECT, + EAXSOURCE_MAXDIRECT); +} + +void ALsource::eax_validate_source_direct_hf( + long direct_hf) +{ + eax_validate_range( + "Direct HF", + direct_hf, + EAXSOURCE_MINDIRECTHF, + EAXSOURCE_MAXDIRECTHF); +} + +void ALsource::eax_validate_source_room( + long room) +{ + eax_validate_range( + "Room", + room, + EAXSOURCE_MINROOM, + EAXSOURCE_MAXROOM); +} + +void ALsource::eax_validate_source_room_hf( + long room_hf) +{ + eax_validate_range( + "Room HF", + room_hf, + EAXSOURCE_MINROOMHF, + EAXSOURCE_MAXROOMHF); +} + +void ALsource::eax_validate_source_obstruction( + long obstruction) +{ + eax_validate_range( + "Obstruction", + obstruction, + EAXSOURCE_MINOBSTRUCTION, + EAXSOURCE_MAXOBSTRUCTION); +} + +void ALsource::eax_validate_source_obstruction_lf_ratio( + float obstruction_lf_ratio) +{ + eax_validate_range( + "Obstruction LF Ratio", + obstruction_lf_ratio, + EAXSOURCE_MINOBSTRUCTIONLFRATIO, + EAXSOURCE_MAXOBSTRUCTIONLFRATIO); +} + +void ALsource::eax_validate_source_occlusion( + long occlusion) +{ + eax_validate_range( + eax_get_occlusion_name(), + occlusion, + EAXSOURCE_MINOCCLUSION, + EAXSOURCE_MAXOCCLUSION); +} + +void ALsource::eax_validate_source_occlusion_lf_ratio( + float occlusion_lf_ratio) +{ + eax_validate_range( + eax_get_occlusion_lf_ratio_name(), + occlusion_lf_ratio, + EAXSOURCE_MINOCCLUSIONLFRATIO, + EAXSOURCE_MAXOCCLUSIONLFRATIO); +} + +void ALsource::eax_validate_source_occlusion_room_ratio( + float occlusion_room_ratio) +{ + eax_validate_range( + eax_get_occlusion_room_ratio_name(), + occlusion_room_ratio, + EAXSOURCE_MINOCCLUSIONROOMRATIO, + EAXSOURCE_MAXOCCLUSIONROOMRATIO); +} + +void ALsource::eax_validate_source_occlusion_direct_ratio( + float occlusion_direct_ratio) +{ + eax_validate_range( + eax_get_occlusion_direct_ratio_name(), + occlusion_direct_ratio, + EAXSOURCE_MINOCCLUSIONDIRECTRATIO, + EAXSOURCE_MAXOCCLUSIONDIRECTRATIO); +} + +void ALsource::eax_validate_source_exclusion( + long exclusion) +{ + eax_validate_range( + eax_get_exclusion_name(), + exclusion, + EAXSOURCE_MINEXCLUSION, + EAXSOURCE_MAXEXCLUSION); +} + +void ALsource::eax_validate_source_exclusion_lf_ratio( + float exclusion_lf_ratio) +{ + eax_validate_range( + eax_get_exclusion_lf_ratio_name(), + exclusion_lf_ratio, + EAXSOURCE_MINEXCLUSIONLFRATIO, + EAXSOURCE_MAXEXCLUSIONLFRATIO); +} + +void ALsource::eax_validate_source_outside_volume_hf( + long outside_volume_hf) +{ + eax_validate_range( + "Outside Volume HF", + outside_volume_hf, + EAXSOURCE_MINOUTSIDEVOLUMEHF, + EAXSOURCE_MAXOUTSIDEVOLUMEHF); +} + +void ALsource::eax_validate_source_doppler_factor( + float doppler_factor) +{ + eax_validate_range( + "Doppler Factor", + doppler_factor, + EAXSOURCE_MINDOPPLERFACTOR, + EAXSOURCE_MAXDOPPLERFACTOR); +} + +void ALsource::eax_validate_source_rolloff_factor( + float rolloff_factor) +{ + eax_validate_range( + "Rolloff Factor", + rolloff_factor, + EAXSOURCE_MINROLLOFFFACTOR, + EAXSOURCE_MAXROLLOFFFACTOR); +} + +void ALsource::eax_validate_source_room_rolloff_factor( + float room_rolloff_factor) +{ + eax_validate_range( + "Room Rolloff Factor", + room_rolloff_factor, + EAXSOURCE_MINROOMROLLOFFFACTOR, + EAXSOURCE_MAXROOMROLLOFFFACTOR); +} + +void ALsource::eax_validate_source_air_absorption_factor( + float air_absorption_factor) +{ + eax_validate_range( + "Air Absorption Factor", + air_absorption_factor, + EAXSOURCE_MINAIRABSORPTIONFACTOR, + EAXSOURCE_MAXAIRABSORPTIONFACTOR); +} + +void ALsource::eax_validate_source_flags( + unsigned long flags, + int eax_version) +{ + eax_validate_range( + "Flags", + flags, + 0UL, + ~((eax_version == 5) ? EAX50SOURCEFLAGS_RESERVED : EAX20SOURCEFLAGS_RESERVED)); +} + +void ALsource::eax_validate_source_macro_fx_factor( + float macro_fx_factor) +{ + eax_validate_range( + "Macro FX Factor", + macro_fx_factor, + EAXSOURCE_MINMACROFXFACTOR, + EAXSOURCE_MAXMACROFXFACTOR); +} + +void ALsource::eax_validate_source_2d_all( + const EAXSOURCE2DPROPERTIES& all, + int eax_version) +{ + eax_validate_source_direct(all.lDirect); + eax_validate_source_direct_hf(all.lDirectHF); + eax_validate_source_room(all.lRoom); + eax_validate_source_room_hf(all.lRoomHF); + eax_validate_source_flags(all.ulFlags, eax_version); +} + +void ALsource::eax_validate_source_obstruction_all( + const EAXOBSTRUCTIONPROPERTIES& all) +{ + eax_validate_source_obstruction(all.lObstruction); + eax_validate_source_obstruction_lf_ratio(all.flObstructionLFRatio); +} + +void ALsource::eax_validate_source_exclusion_all( + const EAXEXCLUSIONPROPERTIES& all) +{ + eax_validate_source_exclusion(all.lExclusion); + eax_validate_source_exclusion_lf_ratio(all.flExclusionLFRatio); +} + +void ALsource::eax_validate_source_occlusion_all( + const EAXOCCLUSIONPROPERTIES& all) +{ + eax_validate_source_occlusion(all.lOcclusion); + eax_validate_source_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_validate_source_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_validate_source_occlusion_direct_ratio(all.flOcclusionDirectRatio); +} + +void ALsource::eax_validate_source_all( + const EAX20BUFFERPROPERTIES& all, + int eax_version) +{ + eax_validate_source_direct(all.lDirect); + eax_validate_source_direct_hf(all.lDirectHF); + eax_validate_source_room(all.lRoom); + eax_validate_source_room_hf(all.lRoomHF); + eax_validate_source_obstruction(all.lObstruction); + eax_validate_source_obstruction_lf_ratio(all.flObstructionLFRatio); + eax_validate_source_occlusion(all.lOcclusion); + eax_validate_source_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_validate_source_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_validate_source_outside_volume_hf(all.lOutsideVolumeHF); + eax_validate_source_room_rolloff_factor(all.flRoomRolloffFactor); + eax_validate_source_air_absorption_factor(all.flAirAbsorptionFactor); + eax_validate_source_flags(all.dwFlags, eax_version); +} + +void ALsource::eax_validate_source_all( + const EAX30SOURCEPROPERTIES& all, + int eax_version) +{ + eax_validate_source_direct(all.lDirect); + eax_validate_source_direct_hf(all.lDirectHF); + eax_validate_source_room(all.lRoom); + eax_validate_source_room_hf(all.lRoomHF); + eax_validate_source_obstruction(all.lObstruction); + eax_validate_source_obstruction_lf_ratio(all.flObstructionLFRatio); + eax_validate_source_occlusion(all.lOcclusion); + eax_validate_source_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_validate_source_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_validate_source_occlusion_direct_ratio(all.flOcclusionDirectRatio); + eax_validate_source_exclusion(all.lExclusion); + eax_validate_source_exclusion_lf_ratio(all.flExclusionLFRatio); + eax_validate_source_outside_volume_hf(all.lOutsideVolumeHF); + eax_validate_source_doppler_factor(all.flDopplerFactor); + eax_validate_source_rolloff_factor(all.flRolloffFactor); + eax_validate_source_room_rolloff_factor(all.flRoomRolloffFactor); + eax_validate_source_air_absorption_factor(all.flAirAbsorptionFactor); + eax_validate_source_flags(all.ulFlags, eax_version); +} + +void ALsource::eax_validate_source_all( + const EAX50SOURCEPROPERTIES& all, + int eax_version) +{ + eax_validate_source_all(static_cast(all), eax_version); + eax_validate_source_macro_fx_factor(all.flMacroFXFactor); +} + +void ALsource::eax_validate_source_speaker_id( + long speaker_id) +{ + eax_validate_range( + "Speaker Id", + speaker_id, + static_cast(EAXSPEAKER_FRONT_LEFT), + static_cast(EAXSPEAKER_LOW_FREQUENCY)); +} + +void ALsource::eax_validate_source_speaker_level( + long speaker_level) +{ + eax_validate_range( + "Speaker Level", + speaker_level, + EAXSOURCE_MINSPEAKERLEVEL, + EAXSOURCE_MAXSPEAKERLEVEL); +} + +void ALsource::eax_validate_source_speaker_level_all( + const EAXSPEAKERLEVELPROPERTIES& all) +{ + eax_validate_source_speaker_id(all.lSpeakerID); + eax_validate_source_speaker_level(all.lLevel); +} + +void ALsource::eax_defer_source_direct( + long lDirect) +{ + eax_d_.source.lDirect = lDirect; + eax_source_dirty_filter_flags_.lDirect = (eax_.source.lDirect != eax_d_.source.lDirect); +} + +void ALsource::eax_defer_source_direct_hf( + long lDirectHF) +{ + eax_d_.source.lDirectHF = lDirectHF; + eax_source_dirty_filter_flags_.lDirectHF = (eax_.source.lDirectHF != eax_d_.source.lDirectHF); +} + +void ALsource::eax_defer_source_room( + long lRoom) +{ + eax_d_.source.lRoom = lRoom; + eax_source_dirty_filter_flags_.lRoom = (eax_.source.lRoom != eax_d_.source.lRoom); +} + +void ALsource::eax_defer_source_room_hf( + long lRoomHF) +{ + eax_d_.source.lRoomHF = lRoomHF; + eax_source_dirty_filter_flags_.lRoomHF = (eax_.source.lRoomHF != eax_d_.source.lRoomHF); +} + +void ALsource::eax_defer_source_obstruction( + long lObstruction) +{ + eax_d_.source.lObstruction = lObstruction; + eax_source_dirty_filter_flags_.lObstruction = (eax_.source.lObstruction != eax_d_.source.lObstruction); +} + +void ALsource::eax_defer_source_obstruction_lf_ratio( + float flObstructionLFRatio) +{ + eax_d_.source.flObstructionLFRatio = flObstructionLFRatio; + eax_source_dirty_filter_flags_.flObstructionLFRatio = (eax_.source.flObstructionLFRatio != eax_d_.source.flObstructionLFRatio); +} + +void ALsource::eax_defer_source_occlusion( + long lOcclusion) +{ + eax_d_.source.lOcclusion = lOcclusion; + eax_source_dirty_filter_flags_.lOcclusion = (eax_.source.lOcclusion != eax_d_.source.lOcclusion); +} + +void ALsource::eax_defer_source_occlusion_lf_ratio( + float flOcclusionLFRatio) +{ + eax_d_.source.flOcclusionLFRatio = flOcclusionLFRatio; + eax_source_dirty_filter_flags_.flOcclusionLFRatio = (eax_.source.flOcclusionLFRatio != eax_d_.source.flOcclusionLFRatio); +} + +void ALsource::eax_defer_source_occlusion_room_ratio( + float flOcclusionRoomRatio) +{ + eax_d_.source.flOcclusionRoomRatio = flOcclusionRoomRatio; + eax_source_dirty_filter_flags_.flOcclusionRoomRatio = (eax_.source.flOcclusionRoomRatio != eax_d_.source.flOcclusionRoomRatio); +} + +void ALsource::eax_defer_source_occlusion_direct_ratio( + float flOcclusionDirectRatio) +{ + eax_d_.source.flOcclusionDirectRatio = flOcclusionDirectRatio; + eax_source_dirty_filter_flags_.flOcclusionDirectRatio = (eax_.source.flOcclusionDirectRatio != eax_d_.source.flOcclusionDirectRatio); +} + +void ALsource::eax_defer_source_exclusion( + long lExclusion) +{ + eax_d_.source.lExclusion = lExclusion; + eax_source_dirty_filter_flags_.lExclusion = (eax_.source.lExclusion != eax_d_.source.lExclusion); +} + +void ALsource::eax_defer_source_exclusion_lf_ratio( + float flExclusionLFRatio) +{ + eax_d_.source.flExclusionLFRatio = flExclusionLFRatio; + eax_source_dirty_filter_flags_.flExclusionLFRatio = (eax_.source.flExclusionLFRatio != eax_d_.source.flExclusionLFRatio); +} + +void ALsource::eax_defer_source_outside_volume_hf( + long lOutsideVolumeHF) +{ + eax_d_.source.lOutsideVolumeHF = lOutsideVolumeHF; + eax_source_dirty_misc_flags_.lOutsideVolumeHF = (eax_.source.lOutsideVolumeHF != eax_d_.source.lOutsideVolumeHF); +} + +void ALsource::eax_defer_source_doppler_factor( + float flDopplerFactor) +{ + eax_d_.source.flDopplerFactor = flDopplerFactor; + eax_source_dirty_misc_flags_.flDopplerFactor = (eax_.source.flDopplerFactor != eax_d_.source.flDopplerFactor); +} + +void ALsource::eax_defer_source_rolloff_factor( + float flRolloffFactor) +{ + eax_d_.source.flRolloffFactor = flRolloffFactor; + eax_source_dirty_misc_flags_.flRolloffFactor = (eax_.source.flRolloffFactor != eax_d_.source.flRolloffFactor); +} + +void ALsource::eax_defer_source_room_rolloff_factor( + float flRoomRolloffFactor) +{ + eax_d_.source.flRoomRolloffFactor = flRoomRolloffFactor; + eax_source_dirty_misc_flags_.flRoomRolloffFactor = (eax_.source.flRoomRolloffFactor != eax_d_.source.flRoomRolloffFactor); +} + +void ALsource::eax_defer_source_air_absorption_factor( + float flAirAbsorptionFactor) +{ + eax_d_.source.flAirAbsorptionFactor = flAirAbsorptionFactor; + eax_source_dirty_misc_flags_.flAirAbsorptionFactor = (eax_.source.flAirAbsorptionFactor != eax_d_.source.flAirAbsorptionFactor); +} + +void ALsource::eax_defer_source_flags( + unsigned long ulFlags) +{ + eax_d_.source.ulFlags = ulFlags; + eax_source_dirty_misc_flags_.ulFlags = (eax_.source.ulFlags != eax_d_.source.ulFlags); +} + +void ALsource::eax_defer_source_macro_fx_factor( + float flMacroFXFactor) +{ + eax_d_.source.flMacroFXFactor = flMacroFXFactor; + eax_source_dirty_misc_flags_.flMacroFXFactor = (eax_.source.flMacroFXFactor != eax_d_.source.flMacroFXFactor); +} + +void ALsource::eax_defer_source_2d_all( + const EAXSOURCE2DPROPERTIES& all) +{ + eax_defer_source_direct(all.lDirect); + eax_defer_source_direct_hf(all.lDirectHF); + eax_defer_source_room(all.lRoom); + eax_defer_source_room_hf(all.lRoomHF); + eax_defer_source_flags(all.ulFlags); +} + +void ALsource::eax_defer_source_obstruction_all( + const EAXOBSTRUCTIONPROPERTIES& all) +{ + eax_defer_source_obstruction(all.lObstruction); + eax_defer_source_obstruction_lf_ratio(all.flObstructionLFRatio); +} + +void ALsource::eax_defer_source_exclusion_all( + const EAXEXCLUSIONPROPERTIES& all) +{ + eax_defer_source_exclusion(all.lExclusion); + eax_defer_source_exclusion_lf_ratio(all.flExclusionLFRatio); +} + +void ALsource::eax_defer_source_occlusion_all( + const EAXOCCLUSIONPROPERTIES& all) +{ + eax_defer_source_occlusion(all.lOcclusion); + eax_defer_source_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_defer_source_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_defer_source_occlusion_direct_ratio(all.flOcclusionDirectRatio); +} + +void ALsource::eax_defer_source_all( + const EAX20BUFFERPROPERTIES& all) +{ + eax_defer_source_direct(all.lDirect); + eax_defer_source_direct_hf(all.lDirectHF); + eax_defer_source_room(all.lRoom); + eax_defer_source_room_hf(all.lRoomHF); + eax_defer_source_obstruction(all.lObstruction); + eax_defer_source_obstruction_lf_ratio(all.flObstructionLFRatio); + eax_defer_source_occlusion(all.lOcclusion); + eax_defer_source_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_defer_source_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_defer_source_outside_volume_hf(all.lOutsideVolumeHF); + eax_defer_source_room_rolloff_factor(all.flRoomRolloffFactor); + eax_defer_source_air_absorption_factor(all.flAirAbsorptionFactor); + eax_defer_source_flags(all.dwFlags); +} + +void ALsource::eax_defer_source_all( + const EAX30SOURCEPROPERTIES& all) +{ + eax_defer_source_direct(all.lDirect); + eax_defer_source_direct_hf(all.lDirectHF); + eax_defer_source_room(all.lRoom); + eax_defer_source_room_hf(all.lRoomHF); + eax_defer_source_obstruction(all.lObstruction); + eax_defer_source_obstruction_lf_ratio(all.flObstructionLFRatio); + eax_defer_source_occlusion(all.lOcclusion); + eax_defer_source_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_defer_source_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_defer_source_occlusion_direct_ratio(all.flOcclusionDirectRatio); + eax_defer_source_exclusion(all.lExclusion); + eax_defer_source_exclusion_lf_ratio(all.flExclusionLFRatio); + eax_defer_source_outside_volume_hf(all.lOutsideVolumeHF); + eax_defer_source_doppler_factor(all.flDopplerFactor); + eax_defer_source_rolloff_factor(all.flRolloffFactor); + eax_defer_source_room_rolloff_factor(all.flRoomRolloffFactor); + eax_defer_source_air_absorption_factor(all.flAirAbsorptionFactor); + eax_defer_source_flags(all.ulFlags); +} + +void ALsource::eax_defer_source_all( + const EAX50SOURCEPROPERTIES& all) +{ + eax_defer_source_all(static_cast(all)); + eax_defer_source_macro_fx_factor(all.flMacroFXFactor); +} + +void ALsource::eax_defer_source_speaker_level_all( + const EAXSPEAKERLEVELPROPERTIES& all) +{ + const auto speaker_index = static_cast(all.lSpeakerID - 1); + auto& speaker_level_d = eax_d_.speaker_levels[speaker_index]; + const auto& speaker_level = eax_.speaker_levels[speaker_index]; + + if (speaker_level != speaker_level_d) + { + eax_source_dirty_misc_flags_.speaker_levels = true; + } +} + +void ALsource::eax1_set_efx() +{ + const auto primary_fx_slot_index = eax_al_context_->eax_get_primary_fx_slot_index(); + + if (!primary_fx_slot_index.has_value()) + return; + + WetGainAuto = (eax1_.fMix == EAX_REVERBMIX_USEDISTANCE); + const auto filter_gain = (WetGainAuto ? 1.0F : eax1_.fMix); + auto& fx_slot = eax_al_context_->eax_get_fx_slot(*primary_fx_slot_index); + eax_set_al_source_send(&fx_slot, *primary_fx_slot_index, EaxAlLowPassParam{filter_gain, 1.0F}); + mPropsDirty = true; +} + +void ALsource::eax1_set_reverb_mix(const EaxEaxCall& eax_call) +{ + const auto reverb_mix = eax_call.get_value(); + eax1_validate_reverb_mix(reverb_mix); + + if (eax1_.fMix == reverb_mix) + return; + + eax1_.fMix = reverb_mix; + eax1_set_efx(); +} + +void ALsource::eax_defer_source_direct( + const EaxEaxCall& eax_call) +{ + const auto direct = + eax_call.get_value(); + + eax_validate_source_direct(direct); + eax_defer_source_direct(direct); +} + +void ALsource::eax_defer_source_direct_hf( + const EaxEaxCall& eax_call) +{ + const auto direct_hf = + eax_call.get_value(); + + eax_validate_source_direct_hf(direct_hf); + eax_defer_source_direct_hf(direct_hf); +} + +void ALsource::eax_defer_source_room( + const EaxEaxCall& eax_call) +{ + const auto room = + eax_call.get_value(); + + eax_validate_source_room(room); + eax_defer_source_room(room); +} + +void ALsource::eax_defer_source_room_hf( + const EaxEaxCall& eax_call) +{ + const auto room_hf = + eax_call.get_value(); + + eax_validate_source_room_hf(room_hf); + eax_defer_source_room_hf(room_hf); +} + +void ALsource::eax_defer_source_obstruction( + const EaxEaxCall& eax_call) +{ + const auto obstruction = + eax_call.get_value(); + + eax_validate_source_obstruction(obstruction); + eax_defer_source_obstruction(obstruction); +} + +void ALsource::eax_defer_source_obstruction_lf_ratio( + const EaxEaxCall& eax_call) +{ + const auto obstruction_lf_ratio = + eax_call.get_value(); + + eax_validate_source_obstruction_lf_ratio(obstruction_lf_ratio); + eax_defer_source_obstruction_lf_ratio(obstruction_lf_ratio); +} + +void ALsource::eax_defer_source_occlusion( + const EaxEaxCall& eax_call) +{ + const auto occlusion = + eax_call.get_value(); + + eax_validate_source_occlusion(occlusion); + eax_defer_source_occlusion(occlusion); +} + +void ALsource::eax_defer_source_occlusion_lf_ratio( + const EaxEaxCall& eax_call) +{ + const auto occlusion_lf_ratio = + eax_call.get_value(); + + eax_validate_source_occlusion_lf_ratio(occlusion_lf_ratio); + eax_defer_source_occlusion_lf_ratio(occlusion_lf_ratio); +} + +void ALsource::eax_defer_source_occlusion_room_ratio( + const EaxEaxCall& eax_call) +{ + const auto occlusion_room_ratio = + eax_call.get_value(); + + eax_validate_source_occlusion_room_ratio(occlusion_room_ratio); + eax_defer_source_occlusion_room_ratio(occlusion_room_ratio); +} + +void ALsource::eax_defer_source_occlusion_direct_ratio( + const EaxEaxCall& eax_call) +{ + const auto occlusion_direct_ratio = + eax_call.get_value(); + + eax_validate_source_occlusion_direct_ratio(occlusion_direct_ratio); + eax_defer_source_occlusion_direct_ratio(occlusion_direct_ratio); +} + +void ALsource::eax_defer_source_exclusion( + const EaxEaxCall& eax_call) +{ + const auto exclusion = + eax_call.get_value(); + + eax_validate_source_exclusion(exclusion); + eax_defer_source_exclusion(exclusion); +} + +void ALsource::eax_defer_source_exclusion_lf_ratio( + const EaxEaxCall& eax_call) +{ + const auto exclusion_lf_ratio = + eax_call.get_value(); + + eax_validate_source_exclusion_lf_ratio(exclusion_lf_ratio); + eax_defer_source_exclusion_lf_ratio(exclusion_lf_ratio); +} + +void ALsource::eax_defer_source_outside_volume_hf( + const EaxEaxCall& eax_call) +{ + const auto outside_volume_hf = + eax_call.get_value(); + + eax_validate_source_outside_volume_hf(outside_volume_hf); + eax_defer_source_outside_volume_hf(outside_volume_hf); +} + +void ALsource::eax_defer_source_doppler_factor( + const EaxEaxCall& eax_call) +{ + const auto doppler_factor = + eax_call.get_value(); + + eax_validate_source_doppler_factor(doppler_factor); + eax_defer_source_doppler_factor(doppler_factor); +} + +void ALsource::eax_defer_source_rolloff_factor( + const EaxEaxCall& eax_call) +{ + const auto rolloff_factor = + eax_call.get_value(); + + eax_validate_source_rolloff_factor(rolloff_factor); + eax_defer_source_rolloff_factor(rolloff_factor); +} + +void ALsource::eax_defer_source_room_rolloff_factor( + const EaxEaxCall& eax_call) +{ + const auto room_rolloff_factor = + eax_call.get_value(); + + eax_validate_source_room_rolloff_factor(room_rolloff_factor); + eax_defer_source_room_rolloff_factor(room_rolloff_factor); +} + +void ALsource::eax_defer_source_air_absorption_factor( + const EaxEaxCall& eax_call) +{ + const auto air_absorption_factor = + eax_call.get_value(); + + eax_validate_source_air_absorption_factor(air_absorption_factor); + eax_defer_source_air_absorption_factor(air_absorption_factor); +} + +void ALsource::eax_defer_source_flags( + const EaxEaxCall& eax_call) +{ + const auto flags = + eax_call.get_value(); + + eax_validate_source_flags(flags, eax_call.get_version()); + eax_defer_source_flags(flags); +} + +void ALsource::eax_defer_source_macro_fx_factor( + const EaxEaxCall& eax_call) +{ + const auto macro_fx_factor = + eax_call.get_value(); + + eax_validate_source_macro_fx_factor(macro_fx_factor); + eax_defer_source_macro_fx_factor(macro_fx_factor); +} + +void ALsource::eax_defer_source_2d_all( + const EaxEaxCall& eax_call) +{ + const auto all = eax_call.get_value(); + + eax_validate_source_2d_all(all, eax_call.get_version()); + eax_defer_source_2d_all(all); +} + +void ALsource::eax_defer_source_obstruction_all( + const EaxEaxCall& eax_call) +{ + const auto all = eax_call.get_value(); + + eax_validate_source_obstruction_all(all); + eax_defer_source_obstruction_all(all); +} + +void ALsource::eax_defer_source_exclusion_all( + const EaxEaxCall& eax_call) +{ + const auto all = eax_call.get_value(); + + eax_validate_source_exclusion_all(all); + eax_defer_source_exclusion_all(all); +} + +void ALsource::eax_defer_source_occlusion_all( + const EaxEaxCall& eax_call) +{ + const auto all = eax_call.get_value(); + + eax_validate_source_occlusion_all(all); + eax_defer_source_occlusion_all(all); +} + +void ALsource::eax_defer_source_all( + const EaxEaxCall& eax_call) +{ + const auto eax_version = eax_call.get_version(); + + if (eax_version == 2) + { + const auto all = eax_call.get_value(); + + eax_validate_source_all(all, eax_version); + eax_defer_source_all(all); + } + else if (eax_version < 5) + { + const auto all = eax_call.get_value(); + + eax_validate_source_all(all, eax_version); + eax_defer_source_all(all); + } + else + { + const auto all = eax_call.get_value(); + + eax_validate_source_all(all, eax_version); + eax_defer_source_all(all); + } +} + +void ALsource::eax_defer_source_speaker_level_all( + const EaxEaxCall& eax_call) +{ + const auto speaker_level_properties = eax_call.get_value(); + + eax_validate_source_speaker_level_all(speaker_level_properties); + eax_defer_source_speaker_level_all(speaker_level_properties); +} + +void ALsource::eax_set_outside_volume_hf() +{ + const auto efx_gain_hf = clamp( + level_mb_to_gain(static_cast(eax_.source.lOutsideVolumeHF)), + AL_MIN_CONE_OUTER_GAINHF, + AL_MAX_CONE_OUTER_GAINHF + ); + + OuterGainHF = efx_gain_hf; +} + +void ALsource::eax_set_doppler_factor() +{ + DopplerFactor = eax_.source.flDopplerFactor; +} + +void ALsource::eax_set_rolloff_factor() +{ + RolloffFactor2 = eax_.source.flRolloffFactor; +} + +void ALsource::eax_set_room_rolloff_factor() +{ + RoomRolloffFactor = eax_.source.flRoomRolloffFactor; +} + +void ALsource::eax_set_air_absorption_factor() +{ + AirAbsorptionFactor = eax_.source.flAirAbsorptionFactor; +} + +void ALsource::eax_set_direct_hf_auto_flag() +{ + const auto is_enable = (eax_.source.ulFlags & EAXSOURCEFLAGS_DIRECTHFAUTO) != 0; + + DryGainHFAuto = is_enable; +} + +void ALsource::eax_set_room_auto_flag() +{ + const auto is_enable = (eax_.source.ulFlags & EAXSOURCEFLAGS_ROOMAUTO) != 0; + + WetGainAuto = is_enable; +} + +void ALsource::eax_set_room_hf_auto_flag() +{ + const auto is_enable = (eax_.source.ulFlags & EAXSOURCEFLAGS_ROOMHFAUTO) != 0; + + WetGainHFAuto = is_enable; +} + +void ALsource::eax_set_flags() +{ + eax_set_direct_hf_auto_flag(); + eax_set_room_auto_flag(); + eax_set_room_hf_auto_flag(); + eax_set_speaker_levels(); +} + +void ALsource::eax_set_macro_fx_factor() +{ + // TODO +} + +void ALsource::eax_set_speaker_levels() +{ + // TODO +} + +void ALsource::eax1_set(const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case DSPROPERTY_EAXBUFFER_ALL: + case DSPROPERTY_EAXBUFFER_REVERBMIX: + eax1_set_reverb_mix(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +void ALsource::eax_apply_deferred() +{ + if (!eax_are_active_fx_slots_dirty_ && + eax_sends_dirty_flags_ == EaxSourceSendsDirtyFlags{} && + eax_source_dirty_filter_flags_ == EaxSourceSourceFilterDirtyFlags{} && + eax_source_dirty_misc_flags_ == EaxSourceSourceMiscDirtyFlags{}) + { + return; + } + + eax_ = eax_d_; + + if (eax_are_active_fx_slots_dirty_) + { + eax_are_active_fx_slots_dirty_ = false; + eax_set_fx_slots(); + eax_update_filters_internal(); + } + else if (eax_has_active_fx_slots_) + { + if (eax_source_dirty_filter_flags_ != EaxSourceSourceFilterDirtyFlags{}) + { + eax_update_filters_internal(); + } + else if (eax_sends_dirty_flags_ != EaxSourceSendsDirtyFlags{}) + { + for (auto i = std::size_t{}; i < EAX_MAX_FXSLOTS; ++i) + { + if (eax_active_fx_slots_[i]) + { + if (eax_sends_dirty_flags_.sends[i] != EaxSourceSendDirtyFlags{}) + { + eax_update_filters_internal(); + break; + } + } + } + } + } + + if (eax_source_dirty_misc_flags_ != EaxSourceSourceMiscDirtyFlags{}) + { + if (eax_source_dirty_misc_flags_.lOutsideVolumeHF) + { + eax_set_outside_volume_hf(); + } + + if (eax_source_dirty_misc_flags_.flDopplerFactor) + { + eax_set_doppler_factor(); + } + + if (eax_source_dirty_misc_flags_.flRolloffFactor) + { + eax_set_rolloff_factor(); + } + + if (eax_source_dirty_misc_flags_.flRoomRolloffFactor) + { + eax_set_room_rolloff_factor(); + } + + if (eax_source_dirty_misc_flags_.flAirAbsorptionFactor) + { + eax_set_air_absorption_factor(); + } + + if (eax_source_dirty_misc_flags_.ulFlags) + { + eax_set_flags(); + } + + if (eax_source_dirty_misc_flags_.flMacroFXFactor) + { + eax_set_macro_fx_factor(); + } + + mPropsDirty = true; + + eax_source_dirty_misc_flags_ = EaxSourceSourceMiscDirtyFlags{}; + } + + eax_sends_dirty_flags_ = EaxSourceSendsDirtyFlags{}; + eax_source_dirty_filter_flags_ = EaxSourceSourceFilterDirtyFlags{}; +} + +void ALsource::eax_set( + const EaxEaxCall& eax_call) +{ + if (eax_call.get_version() == 1) + { + eax1_set(eax_call); + return; + } + + switch (eax_call.get_property_id()) + { + case EAXSOURCE_NONE: + break; + + case EAXSOURCE_ALLPARAMETERS: + eax_defer_source_all(eax_call); + break; + + case EAXSOURCE_OBSTRUCTIONPARAMETERS: + eax_defer_source_obstruction_all(eax_call); + break; + + case EAXSOURCE_OCCLUSIONPARAMETERS: + eax_defer_source_occlusion_all(eax_call); + break; + + case EAXSOURCE_EXCLUSIONPARAMETERS: + eax_defer_source_exclusion_all(eax_call); + break; + + case EAXSOURCE_DIRECT: + eax_defer_source_direct(eax_call); + break; + + case EAXSOURCE_DIRECTHF: + eax_defer_source_direct_hf(eax_call); + break; + + case EAXSOURCE_ROOM: + eax_defer_source_room(eax_call); + break; + + case EAXSOURCE_ROOMHF: + eax_defer_source_room_hf(eax_call); + break; + + case EAXSOURCE_OBSTRUCTION: + eax_defer_source_obstruction(eax_call); + break; + + case EAXSOURCE_OBSTRUCTIONLFRATIO: + eax_defer_source_obstruction_lf_ratio(eax_call); + break; + + case EAXSOURCE_OCCLUSION: + eax_defer_source_occlusion(eax_call); + break; + + case EAXSOURCE_OCCLUSIONLFRATIO: + eax_defer_source_occlusion_lf_ratio(eax_call); + break; + + case EAXSOURCE_OCCLUSIONROOMRATIO: + eax_defer_source_occlusion_room_ratio(eax_call); + break; + + case EAXSOURCE_OCCLUSIONDIRECTRATIO: + eax_defer_source_occlusion_direct_ratio(eax_call); + break; + + case EAXSOURCE_EXCLUSION: + eax_defer_source_exclusion(eax_call); + break; + + case EAXSOURCE_EXCLUSIONLFRATIO: + eax_defer_source_exclusion_lf_ratio(eax_call); + break; + + case EAXSOURCE_OUTSIDEVOLUMEHF: + eax_defer_source_outside_volume_hf(eax_call); + break; + + case EAXSOURCE_DOPPLERFACTOR: + eax_defer_source_doppler_factor(eax_call); + break; + + case EAXSOURCE_ROLLOFFFACTOR: + eax_defer_source_rolloff_factor(eax_call); + break; + + case EAXSOURCE_ROOMROLLOFFFACTOR: + eax_defer_source_room_rolloff_factor(eax_call); + break; + + case EAXSOURCE_AIRABSORPTIONFACTOR: + eax_defer_source_air_absorption_factor(eax_call); + break; + + case EAXSOURCE_FLAGS: + eax_defer_source_flags(eax_call); + break; + + case EAXSOURCE_SENDPARAMETERS: + eax_defer_send(eax_call); + break; + + case EAXSOURCE_ALLSENDPARAMETERS: + eax_defer_send_all(eax_call); + break; + + case EAXSOURCE_OCCLUSIONSENDPARAMETERS: + eax_defer_send_occlusion_all(eax_call); + break; + + case EAXSOURCE_EXCLUSIONSENDPARAMETERS: + eax_defer_send_exclusion_all(eax_call); + break; + + case EAXSOURCE_ACTIVEFXSLOTID: + eax_defer_active_fx_slots(eax_call); + break; + + case EAXSOURCE_MACROFXFACTOR: + eax_defer_source_macro_fx_factor(eax_call); + break; + + case EAXSOURCE_SPEAKERLEVELS: + eax_defer_source_speaker_level_all(eax_call); + break; + + case EAXSOURCE_ALL2DPARAMETERS: + eax_defer_source_2d_all(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +const GUID& ALsource::eax_get_send_fx_slot_guid( + int eax_version, + EaxFxSlotIndexValue fx_slot_index) +{ + switch (eax_version) + { + case 4: + switch (fx_slot_index) + { + case 0: + return EAXPROPERTYID_EAX40_FXSlot0; + + case 1: + return EAXPROPERTYID_EAX40_FXSlot1; + + case 2: + return EAXPROPERTYID_EAX40_FXSlot2; + + case 3: + return EAXPROPERTYID_EAX40_FXSlot3; + + default: + eax_fail("FX slot index out of range."); + } + + case 5: + switch (fx_slot_index) + { + case 0: + return EAXPROPERTYID_EAX50_FXSlot0; + + case 1: + return EAXPROPERTYID_EAX50_FXSlot1; + + case 2: + return EAXPROPERTYID_EAX50_FXSlot2; + + case 3: + return EAXPROPERTYID_EAX50_FXSlot3; + + default: + eax_fail("FX slot index out of range."); + } + + default: + eax_fail("Unsupported EAX version."); + } +} + +void ALsource::eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCESENDPROPERTIES& dst_send) +{ + dst_send.lSend = src_send.lSend; + dst_send.lSendHF = src_send.lSendHF; +} + +void ALsource::eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCEALLSENDPROPERTIES& dst_send) +{ + dst_send = src_send; +} + +void ALsource::eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCEOCCLUSIONSENDPROPERTIES& dst_send) +{ + dst_send.lOcclusion = src_send.lOcclusion; + dst_send.flOcclusionLFRatio = src_send.flOcclusionLFRatio; + dst_send.flOcclusionRoomRatio = src_send.flOcclusionRoomRatio; + dst_send.flOcclusionDirectRatio = src_send.flOcclusionDirectRatio; +} + +void ALsource::eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCEEXCLUSIONSENDPROPERTIES& dst_send) +{ + dst_send.lExclusion = src_send.lExclusion; + dst_send.flExclusionLFRatio = src_send.flExclusionLFRatio; +} + +void ALsource::eax1_get(const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case DSPROPERTY_EAXBUFFER_ALL: + case DSPROPERTY_EAXBUFFER_REVERBMIX: + eax_call.set_value(eax1_); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +void ALsource::eax_api_get_source_all_v2( + const EaxEaxCall& eax_call) +{ + auto eax_2_all = EAX20BUFFERPROPERTIES{}; + eax_2_all.lDirect = eax_.source.lDirect; + eax_2_all.lDirectHF = eax_.source.lDirectHF; + eax_2_all.lRoom = eax_.source.lRoom; + eax_2_all.lRoomHF = eax_.source.lRoomHF; + eax_2_all.flRoomRolloffFactor = eax_.source.flRoomRolloffFactor; + eax_2_all.lObstruction = eax_.source.lObstruction; + eax_2_all.flObstructionLFRatio = eax_.source.flObstructionLFRatio; + eax_2_all.lOcclusion = eax_.source.lOcclusion; + eax_2_all.flOcclusionLFRatio = eax_.source.flOcclusionLFRatio; + eax_2_all.flOcclusionRoomRatio = eax_.source.flOcclusionRoomRatio; + eax_2_all.lOutsideVolumeHF = eax_.source.lOutsideVolumeHF; + eax_2_all.flAirAbsorptionFactor = eax_.source.flAirAbsorptionFactor; + eax_2_all.dwFlags = eax_.source.ulFlags; + + eax_call.set_value(eax_2_all); +} + +void ALsource::eax_api_get_source_all_v3( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(static_cast(eax_.source)); +} + +void ALsource::eax_api_get_source_all_v5( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_.source); +} + +void ALsource::eax_api_get_source_all( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_version()) + { + case 2: + eax_api_get_source_all_v2(eax_call); + break; + + case 3: + case 4: + eax_api_get_source_all_v3(eax_call); + break; + + case 5: + eax_api_get_source_all_v5(eax_call); + break; + + default: + eax_fail("Unsupported EAX version."); + } +} + +void ALsource::eax_api_get_source_all_obstruction( + const EaxEaxCall& eax_call) +{ + auto eax_obstruction_all = EAXOBSTRUCTIONPROPERTIES{}; + eax_obstruction_all.lObstruction = eax_.source.lObstruction; + eax_obstruction_all.flObstructionLFRatio = eax_.source.flObstructionLFRatio; + + eax_call.set_value(eax_obstruction_all); +} + +void ALsource::eax_api_get_source_all_occlusion( + const EaxEaxCall& eax_call) +{ + auto eax_occlusion_all = EAXOCCLUSIONPROPERTIES{}; + eax_occlusion_all.lOcclusion = eax_.source.lOcclusion; + eax_occlusion_all.flOcclusionLFRatio = eax_.source.flOcclusionLFRatio; + eax_occlusion_all.flOcclusionRoomRatio = eax_.source.flOcclusionRoomRatio; + eax_occlusion_all.flOcclusionDirectRatio = eax_.source.flOcclusionDirectRatio; + + eax_call.set_value(eax_occlusion_all); +} + +void ALsource::eax_api_get_source_all_exclusion( + const EaxEaxCall& eax_call) +{ + auto eax_exclusion_all = EAXEXCLUSIONPROPERTIES{}; + eax_exclusion_all.lExclusion = eax_.source.lExclusion; + eax_exclusion_all.flExclusionLFRatio = eax_.source.flExclusionLFRatio; + + eax_call.set_value(eax_exclusion_all); +} + +void ALsource::eax_api_get_source_active_fx_slot_id( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_version()) + { + case 4: + { + const auto& active_fx_slots = reinterpret_cast(eax_.active_fx_slots); + eax_call.set_value(active_fx_slots); + } + break; + + case 5: + { + const auto& active_fx_slots = reinterpret_cast(eax_.active_fx_slots); + eax_call.set_value(active_fx_slots); + } + break; + + default: + eax_fail("Unsupported EAX version."); + } +} + +void ALsource::eax_api_get_source_all_2d( + const EaxEaxCall& eax_call) +{ + auto eax_2d_all = EAXSOURCE2DPROPERTIES{}; + eax_2d_all.lDirect = eax_.source.lDirect; + eax_2d_all.lDirectHF = eax_.source.lDirectHF; + eax_2d_all.lRoom = eax_.source.lRoom; + eax_2d_all.lRoomHF = eax_.source.lRoomHF; + eax_2d_all.ulFlags = eax_.source.ulFlags; + + eax_call.set_value(eax_2d_all); +} + +void ALsource::eax_api_get_source_speaker_level_all( + const EaxEaxCall& eax_call) +{ + auto& all = eax_call.get_value(); + + eax_validate_source_speaker_id(all.lSpeakerID); + const auto speaker_index = static_cast(all.lSpeakerID - 1); + all.lLevel = eax_.speaker_levels[speaker_index]; +} + +void ALsource::eax_get( + const EaxEaxCall& eax_call) +{ + if (eax_call.get_version() == 1) + { + eax1_get(eax_call); + return; + } + + switch (eax_call.get_property_id()) + { + case EAXSOURCE_NONE: + break; + + case EAXSOURCE_ALLPARAMETERS: + eax_api_get_source_all(eax_call); + break; + + case EAXSOURCE_OBSTRUCTIONPARAMETERS: + eax_api_get_source_all_obstruction(eax_call); + break; + + case EAXSOURCE_OCCLUSIONPARAMETERS: + eax_api_get_source_all_occlusion(eax_call); + break; + + case EAXSOURCE_EXCLUSIONPARAMETERS: + eax_api_get_source_all_exclusion(eax_call); + break; + + case EAXSOURCE_DIRECT: + eax_call.set_value(eax_.source.lDirect); + break; + + case EAXSOURCE_DIRECTHF: + eax_call.set_value(eax_.source.lDirectHF); + break; + + case EAXSOURCE_ROOM: + eax_call.set_value(eax_.source.lRoom); + break; + + case EAXSOURCE_ROOMHF: + eax_call.set_value(eax_.source.lRoomHF); + break; + + case EAXSOURCE_OBSTRUCTION: + eax_call.set_value(eax_.source.lObstruction); + break; + + case EAXSOURCE_OBSTRUCTIONLFRATIO: + eax_call.set_value(eax_.source.flObstructionLFRatio); + break; + + case EAXSOURCE_OCCLUSION: + eax_call.set_value(eax_.source.lOcclusion); + break; + + case EAXSOURCE_OCCLUSIONLFRATIO: + eax_call.set_value(eax_.source.flOcclusionLFRatio); + break; + + case EAXSOURCE_OCCLUSIONROOMRATIO: + eax_call.set_value(eax_.source.flOcclusionRoomRatio); + break; + + case EAXSOURCE_OCCLUSIONDIRECTRATIO: + eax_call.set_value(eax_.source.flOcclusionDirectRatio); + break; + + case EAXSOURCE_EXCLUSION: + eax_call.set_value(eax_.source.lExclusion); + break; + + case EAXSOURCE_EXCLUSIONLFRATIO: + eax_call.set_value(eax_.source.flExclusionLFRatio); + break; + + case EAXSOURCE_OUTSIDEVOLUMEHF: + eax_call.set_value(eax_.source.lOutsideVolumeHF); + break; + + case EAXSOURCE_DOPPLERFACTOR: + eax_call.set_value(eax_.source.flDopplerFactor); + break; + + case EAXSOURCE_ROLLOFFFACTOR: + eax_call.set_value(eax_.source.flRolloffFactor); + break; + + case EAXSOURCE_ROOMROLLOFFFACTOR: + eax_call.set_value(eax_.source.flRoomRolloffFactor); + break; + + case EAXSOURCE_AIRABSORPTIONFACTOR: + eax_call.set_value(eax_.source.flAirAbsorptionFactor); + break; + + case EAXSOURCE_FLAGS: + eax_call.set_value(eax_.source.ulFlags); + break; + + case EAXSOURCE_SENDPARAMETERS: + eax_api_get_send_properties(eax_call); + break; + + case EAXSOURCE_ALLSENDPARAMETERS: + eax_api_get_send_properties(eax_call); + break; + + case EAXSOURCE_OCCLUSIONSENDPARAMETERS: + eax_api_get_send_properties(eax_call); + break; + + case EAXSOURCE_EXCLUSIONSENDPARAMETERS: + eax_api_get_send_properties(eax_call); + break; + + case EAXSOURCE_ACTIVEFXSLOTID: + eax_api_get_source_active_fx_slot_id(eax_call); + break; + + case EAXSOURCE_MACROFXFACTOR: + eax_call.set_value(eax_.source.flMacroFXFactor); + break; + + case EAXSOURCE_SPEAKERLEVELS: + eax_api_get_source_speaker_level_all(eax_call); + break; + + case EAXSOURCE_ALL2DPARAMETERS: + eax_api_get_source_all_2d(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +void ALsource::eax_set_al_source_send( + ALeffectslot *slot, + size_t sendidx, + const EaxAlLowPassParam &filter) +{ + if(sendidx >= EAX_MAX_FXSLOTS) + return; + + auto &send = Send[sendidx]; + send.Gain = filter.gain; + send.GainHF = filter.gain_hf; + send.HFReference = LOWPASSFREQREF; + send.GainLF = 1.0f; + send.LFReference = HIGHPASSFREQREF; + + if(slot) IncrementRef(slot->ref); + if(auto *oldslot = send.Slot) + DecrementRef(oldslot->ref); + send.Slot = slot; + + mPropsDirty = true; +} + +#endif // ALSOFT_EAX diff --git a/modules/openal-soft/al/source.h b/modules/openal-soft/al/source.h new file mode 100644 index 0000000..6db6bfa --- /dev/null +++ b/modules/openal-soft/al/source.h @@ -0,0 +1,809 @@ +#ifndef AL_SOURCE_H +#define AL_SOURCE_H + +#include +#include +#include +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" + +#include "alc/alu.h" +#include "alc/context.h" +#include "alc/inprogext.h" +#include "aldeque.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "atomic.h" +#include "core/voice.h" +#include "vector.h" + +#ifdef ALSOFT_EAX +#include "eax_eax_call.h" +#include "eax_fx_slot_index.h" +#include "eax_utils.h" +#endif // ALSOFT_EAX + +struct ALbuffer; +struct ALeffectslot; + + +enum class SourceStereo : bool { + Normal = AL_NORMAL_SOFT, + Enhanced = AL_SUPER_STEREO_SOFT +}; + +#define DEFAULT_SENDS 2 + +#define INVALID_VOICE_IDX static_cast(-1) + +struct ALbufferQueueItem : public VoiceBufferItem { + ALbuffer *mBuffer{nullptr}; + + DISABLE_ALLOC() +}; + + +#ifdef ALSOFT_EAX +using EaxSourceSourceFilterDirtyFlagsValue = std::uint_least16_t; + +struct EaxSourceSourceFilterDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxSourceSourceFilterDirtyFlagsValue lDirect : 1; + EaxSourceSourceFilterDirtyFlagsValue lDirectHF : 1; + EaxSourceSourceFilterDirtyFlagsValue lRoom : 1; + EaxSourceSourceFilterDirtyFlagsValue lRoomHF : 1; + EaxSourceSourceFilterDirtyFlagsValue lObstruction : 1; + EaxSourceSourceFilterDirtyFlagsValue flObstructionLFRatio : 1; + EaxSourceSourceFilterDirtyFlagsValue lOcclusion : 1; + EaxSourceSourceFilterDirtyFlagsValue flOcclusionLFRatio : 1; + EaxSourceSourceFilterDirtyFlagsValue flOcclusionRoomRatio : 1; + EaxSourceSourceFilterDirtyFlagsValue flOcclusionDirectRatio : 1; + EaxSourceSourceFilterDirtyFlagsValue lExclusion : 1; + EaxSourceSourceFilterDirtyFlagsValue flExclusionLFRatio : 1; +}; // EaxSourceSourceFilterDirtyFlags + + +using EaxSourceSourceMiscDirtyFlagsValue = std::uint_least8_t; + +struct EaxSourceSourceMiscDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxSourceSourceMiscDirtyFlagsValue lOutsideVolumeHF : 1; + EaxSourceSourceMiscDirtyFlagsValue flDopplerFactor : 1; + EaxSourceSourceMiscDirtyFlagsValue flRolloffFactor : 1; + EaxSourceSourceMiscDirtyFlagsValue flRoomRolloffFactor : 1; + EaxSourceSourceMiscDirtyFlagsValue flAirAbsorptionFactor : 1; + EaxSourceSourceMiscDirtyFlagsValue ulFlags : 1; + EaxSourceSourceMiscDirtyFlagsValue flMacroFXFactor : 1; + EaxSourceSourceMiscDirtyFlagsValue speaker_levels : 1; +}; // EaxSourceSourceMiscDirtyFlags + + +using EaxSourceSendDirtyFlagsValue = std::uint_least8_t; + +struct EaxSourceSendDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxSourceSendDirtyFlagsValue lSend : 1; + EaxSourceSendDirtyFlagsValue lSendHF : 1; + EaxSourceSendDirtyFlagsValue lOcclusion : 1; + EaxSourceSendDirtyFlagsValue flOcclusionLFRatio : 1; + EaxSourceSendDirtyFlagsValue flOcclusionRoomRatio : 1; + EaxSourceSendDirtyFlagsValue flOcclusionDirectRatio : 1; + EaxSourceSendDirtyFlagsValue lExclusion : 1; + EaxSourceSendDirtyFlagsValue flExclusionLFRatio : 1; +}; // EaxSourceSendDirtyFlags + + +struct EaxSourceSendsDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxSourceSendDirtyFlags sends[EAX_MAX_FXSLOTS]; +}; // EaxSourceSendsDirtyFlags +#endif // ALSOFT_EAX + +struct ALsource { + /** Source properties. */ + float Pitch{1.0f}; + float Gain{1.0f}; + float OuterGain{0.0f}; + float MinGain{0.0f}; + float MaxGain{1.0f}; + float InnerAngle{360.0f}; + float OuterAngle{360.0f}; + float RefDistance{1.0f}; + float MaxDistance{std::numeric_limits::max()}; + float RolloffFactor{1.0f}; +#ifdef ALSOFT_EAX + // For EAXSOURCE_ROLLOFFFACTOR, which is distinct from and added to + // AL_ROLLOFF_FACTOR + float RolloffFactor2{0.0f}; +#endif + std::array Position{{0.0f, 0.0f, 0.0f}}; + std::array Velocity{{0.0f, 0.0f, 0.0f}}; + std::array Direction{{0.0f, 0.0f, 0.0f}}; + std::array OrientAt{{0.0f, 0.0f, -1.0f}}; + std::array OrientUp{{0.0f, 1.0f, 0.0f}}; + bool HeadRelative{false}; + bool Looping{false}; + DistanceModel mDistanceModel{DistanceModel::Default}; + Resampler mResampler{ResamplerDefault}; + DirectMode DirectChannels{DirectMode::Off}; + SpatializeMode mSpatialize{SpatializeMode::Auto}; + SourceStereo mStereoMode{SourceStereo::Normal}; + + bool DryGainHFAuto{true}; + bool WetGainAuto{true}; + bool WetGainHFAuto{true}; + float OuterGainHF{1.0f}; + + float AirAbsorptionFactor{0.0f}; + float RoomRolloffFactor{0.0f}; + float DopplerFactor{1.0f}; + + /* NOTE: Stereo pan angles are specified in radians, counter-clockwise + * rather than clockwise. + */ + std::array StereoPan{{al::numbers::pi_v/6.0f, -al::numbers::pi_v/6.0f}}; + + float Radius{0.0f}; + float EnhWidth{0.593f}; + + /** Direct filter and auxiliary send info. */ + struct { + float Gain; + float GainHF; + float HFReference; + float GainLF; + float LFReference; + } Direct; + struct SendData { + ALeffectslot *Slot; + float Gain; + float GainHF; + float HFReference; + float GainLF; + float LFReference; + }; + std::array Send; + + /** + * Last user-specified offset, and the offset type (bytes, samples, or + * seconds). + */ + double Offset{0.0}; + ALenum OffsetType{AL_NONE}; + + /** Source type (static, streaming, or undetermined) */ + ALenum SourceType{AL_UNDETERMINED}; + + /** Source state (initial, playing, paused, or stopped) */ + ALenum state{AL_INITIAL}; + + /** Source Buffer Queue head. */ + al::deque mQueue; + + bool mPropsDirty{true}; + + /* Index into the context's Voices array. Lazily updated, only checked and + * reset when looking up the voice. + */ + ALuint VoiceIdx{INVALID_VOICE_IDX}; + + /** Self ID */ + ALuint id{0}; + + + ALsource(); + ~ALsource(); + + ALsource(const ALsource&) = delete; + ALsource& operator=(const ALsource&) = delete; + + DISABLE_ALLOC() + +#ifdef ALSOFT_EAX +public: + void eax_initialize(ALCcontext *context) noexcept; + + + void eax_dispatch(const EaxEaxCall& eax_call) + { eax_call.is_get() ? eax_get(eax_call) : eax_set(eax_call); } + + + void eax_update_filters(); + + void eax_update( + EaxContextSharedDirtyFlags dirty_flags); + + void eax_commit() { eax_apply_deferred(); } + void eax_commit_and_update(); + + bool eax_is_initialized() const noexcept { return eax_al_context_; } + + + static ALsource* eax_lookup_source( + ALCcontext& al_context, + ALuint source_id) noexcept; + + +private: + static constexpr auto eax_max_speakers = 9; + + + using EaxActiveFxSlots = std::array; + using EaxSpeakerLevels = std::array; + + struct Eax + { + using Sends = std::array; + + EAX50ACTIVEFXSLOTS active_fx_slots{}; + EAX50SOURCEPROPERTIES source{}; + Sends sends{}; + EaxSpeakerLevels speaker_levels{}; + }; // Eax + + + bool eax_uses_primary_id_{}; + bool eax_has_active_fx_slots_{}; + bool eax_are_active_fx_slots_dirty_{}; + + ALCcontext* eax_al_context_{}; + + EAXBUFFER_REVERBPROPERTIES eax1_{}; + Eax eax_{}; + Eax eax_d_{}; + EaxActiveFxSlots eax_active_fx_slots_{}; + + EaxSourceSendsDirtyFlags eax_sends_dirty_flags_{}; + EaxSourceSourceFilterDirtyFlags eax_source_dirty_filter_flags_{}; + EaxSourceSourceMiscDirtyFlags eax_source_dirty_misc_flags_{}; + + + [[noreturn]] + static void eax_fail( + const char* message); + + + void eax_set_source_defaults() noexcept; + void eax_set_active_fx_slots_defaults() noexcept; + void eax_set_send_defaults(EAXSOURCEALLSENDPROPERTIES& eax_send) noexcept; + void eax_set_sends_defaults() noexcept; + void eax_set_speaker_levels_defaults() noexcept; + void eax_set_defaults() noexcept; + + + static float eax_calculate_dst_occlusion_mb( + long src_occlusion_mb, + float path_ratio, + float lf_ratio) noexcept; + + EaxAlLowPassParam eax_create_direct_filter_param() const noexcept; + + EaxAlLowPassParam eax_create_room_filter_param( + const ALeffectslot& fx_slot, + const EAXSOURCEALLSENDPROPERTIES& send) const noexcept; + + void eax_set_fx_slots(); + + void eax_initialize_fx_slots(); + + void eax_update_direct_filter_internal(); + + void eax_update_room_filters_internal(); + + void eax_update_filters_internal(); + + void eax_update_primary_fx_slot_id(); + + + void eax_defer_active_fx_slots( + const EaxEaxCall& eax_call); + + + static const char* eax_get_exclusion_name() noexcept; + + static const char* eax_get_exclusion_lf_ratio_name() noexcept; + + + static const char* eax_get_occlusion_name() noexcept; + + static const char* eax_get_occlusion_lf_ratio_name() noexcept; + + static const char* eax_get_occlusion_direct_ratio_name() noexcept; + + static const char* eax_get_occlusion_room_ratio_name() noexcept; + + + static void eax1_validate_reverb_mix(float reverb_mix); + + static void eax_validate_send_receiving_fx_slot_guid( + const GUID& guidReceivingFXSlotID); + + static void eax_validate_send_send( + long lSend); + + static void eax_validate_send_send_hf( + long lSendHF); + + static void eax_validate_send_occlusion( + long lOcclusion); + + static void eax_validate_send_occlusion_lf_ratio( + float flOcclusionLFRatio); + + static void eax_validate_send_occlusion_room_ratio( + float flOcclusionRoomRatio); + + static void eax_validate_send_occlusion_direct_ratio( + float flOcclusionDirectRatio); + + static void eax_validate_send_exclusion( + long lExclusion); + + static void eax_validate_send_exclusion_lf_ratio( + float flExclusionLFRatio); + + static void eax_validate_send( + const EAXSOURCESENDPROPERTIES& all); + + static void eax_validate_send_exclusion_all( + const EAXSOURCEEXCLUSIONSENDPROPERTIES& all); + + static void eax_validate_send_occlusion_all( + const EAXSOURCEOCCLUSIONSENDPROPERTIES& all); + + static void eax_validate_send_all( + const EAXSOURCEALLSENDPROPERTIES& all); + + + static EaxFxSlotIndexValue eax_get_send_index( + const GUID& send_guid); + + + void eax_defer_send_send( + long lSend, + EaxFxSlotIndexValue index); + + void eax_defer_send_send_hf( + long lSendHF, + EaxFxSlotIndexValue index); + + void eax_defer_send_occlusion( + long lOcclusion, + EaxFxSlotIndexValue index); + + void eax_defer_send_occlusion_lf_ratio( + float flOcclusionLFRatio, + EaxFxSlotIndexValue index); + + void eax_defer_send_occlusion_room_ratio( + float flOcclusionRoomRatio, + EaxFxSlotIndexValue index); + + void eax_defer_send_occlusion_direct_ratio( + float flOcclusionDirectRatio, + EaxFxSlotIndexValue index); + + void eax_defer_send_exclusion( + long lExclusion, + EaxFxSlotIndexValue index); + + void eax_defer_send_exclusion_lf_ratio( + float flExclusionLFRatio, + EaxFxSlotIndexValue index); + + void eax_defer_send( + const EAXSOURCESENDPROPERTIES& all, + EaxFxSlotIndexValue index); + + void eax_defer_send_exclusion_all( + const EAXSOURCEEXCLUSIONSENDPROPERTIES& all, + EaxFxSlotIndexValue index); + + void eax_defer_send_occlusion_all( + const EAXSOURCEOCCLUSIONSENDPROPERTIES& all, + EaxFxSlotIndexValue index); + + void eax_defer_send_all( + const EAXSOURCEALLSENDPROPERTIES& all, + EaxFxSlotIndexValue index); + + + void eax_defer_send( + const EaxEaxCall& eax_call); + + void eax_defer_send_exclusion_all( + const EaxEaxCall& eax_call); + + void eax_defer_send_occlusion_all( + const EaxEaxCall& eax_call); + + void eax_defer_send_all( + const EaxEaxCall& eax_call); + + + static void eax_validate_source_direct( + long direct); + + static void eax_validate_source_direct_hf( + long direct_hf); + + static void eax_validate_source_room( + long room); + + static void eax_validate_source_room_hf( + long room_hf); + + static void eax_validate_source_obstruction( + long obstruction); + + static void eax_validate_source_obstruction_lf_ratio( + float obstruction_lf_ratio); + + static void eax_validate_source_occlusion( + long occlusion); + + static void eax_validate_source_occlusion_lf_ratio( + float occlusion_lf_ratio); + + static void eax_validate_source_occlusion_room_ratio( + float occlusion_room_ratio); + + static void eax_validate_source_occlusion_direct_ratio( + float occlusion_direct_ratio); + + static void eax_validate_source_exclusion( + long exclusion); + + static void eax_validate_source_exclusion_lf_ratio( + float exclusion_lf_ratio); + + static void eax_validate_source_outside_volume_hf( + long outside_volume_hf); + + static void eax_validate_source_doppler_factor( + float doppler_factor); + + static void eax_validate_source_rolloff_factor( + float rolloff_factor); + + static void eax_validate_source_room_rolloff_factor( + float room_rolloff_factor); + + static void eax_validate_source_air_absorption_factor( + float air_absorption_factor); + + static void eax_validate_source_flags( + unsigned long flags, + int eax_version); + + static void eax_validate_source_macro_fx_factor( + float macro_fx_factor); + + static void eax_validate_source_2d_all( + const EAXSOURCE2DPROPERTIES& all, + int eax_version); + + static void eax_validate_source_obstruction_all( + const EAXOBSTRUCTIONPROPERTIES& all); + + static void eax_validate_source_exclusion_all( + const EAXEXCLUSIONPROPERTIES& all); + + static void eax_validate_source_occlusion_all( + const EAXOCCLUSIONPROPERTIES& all); + + static void eax_validate_source_all( + const EAX20BUFFERPROPERTIES& all, + int eax_version); + + static void eax_validate_source_all( + const EAX30SOURCEPROPERTIES& all, + int eax_version); + + static void eax_validate_source_all( + const EAX50SOURCEPROPERTIES& all, + int eax_version); + + static void eax_validate_source_speaker_id( + long speaker_id); + + static void eax_validate_source_speaker_level( + long speaker_level); + + static void eax_validate_source_speaker_level_all( + const EAXSPEAKERLEVELPROPERTIES& all); + + + void eax_defer_source_direct( + long lDirect); + + void eax_defer_source_direct_hf( + long lDirectHF); + + void eax_defer_source_room( + long lRoom); + + void eax_defer_source_room_hf( + long lRoomHF); + + void eax_defer_source_obstruction( + long lObstruction); + + void eax_defer_source_obstruction_lf_ratio( + float flObstructionLFRatio); + + void eax_defer_source_occlusion( + long lOcclusion); + + void eax_defer_source_occlusion_lf_ratio( + float flOcclusionLFRatio); + + void eax_defer_source_occlusion_room_ratio( + float flOcclusionRoomRatio); + + void eax_defer_source_occlusion_direct_ratio( + float flOcclusionDirectRatio); + + void eax_defer_source_exclusion( + long lExclusion); + + void eax_defer_source_exclusion_lf_ratio( + float flExclusionLFRatio); + + void eax_defer_source_outside_volume_hf( + long lOutsideVolumeHF); + + void eax_defer_source_doppler_factor( + float flDopplerFactor); + + void eax_defer_source_rolloff_factor( + float flRolloffFactor); + + void eax_defer_source_room_rolloff_factor( + float flRoomRolloffFactor); + + void eax_defer_source_air_absorption_factor( + float flAirAbsorptionFactor); + + void eax_defer_source_flags( + unsigned long ulFlags); + + void eax_defer_source_macro_fx_factor( + float flMacroFXFactor); + + void eax_defer_source_2d_all( + const EAXSOURCE2DPROPERTIES& all); + + void eax_defer_source_obstruction_all( + const EAXOBSTRUCTIONPROPERTIES& all); + + void eax_defer_source_exclusion_all( + const EAXEXCLUSIONPROPERTIES& all); + + void eax_defer_source_occlusion_all( + const EAXOCCLUSIONPROPERTIES& all); + + void eax_defer_source_all( + const EAX20BUFFERPROPERTIES& all); + + void eax_defer_source_all( + const EAX30SOURCEPROPERTIES& all); + + void eax_defer_source_all( + const EAX50SOURCEPROPERTIES& all); + + void eax_defer_source_speaker_level_all( + const EAXSPEAKERLEVELPROPERTIES& all); + + + void eax_defer_source_direct( + const EaxEaxCall& eax_call); + + void eax_defer_source_direct_hf( + const EaxEaxCall& eax_call); + + void eax_defer_source_room( + const EaxEaxCall& eax_call); + + void eax_defer_source_room_hf( + const EaxEaxCall& eax_call); + + void eax_defer_source_obstruction( + const EaxEaxCall& eax_call); + + void eax_defer_source_obstruction_lf_ratio( + const EaxEaxCall& eax_call); + + void eax_defer_source_occlusion( + const EaxEaxCall& eax_call); + + void eax_defer_source_occlusion_lf_ratio( + const EaxEaxCall& eax_call); + + void eax_defer_source_occlusion_room_ratio( + const EaxEaxCall& eax_call); + + void eax_defer_source_occlusion_direct_ratio( + const EaxEaxCall& eax_call); + + void eax_defer_source_exclusion( + const EaxEaxCall& eax_call); + + void eax_defer_source_exclusion_lf_ratio( + const EaxEaxCall& eax_call); + + void eax_defer_source_outside_volume_hf( + const EaxEaxCall& eax_call); + + void eax_defer_source_doppler_factor( + const EaxEaxCall& eax_call); + + void eax_defer_source_rolloff_factor( + const EaxEaxCall& eax_call); + + void eax_defer_source_room_rolloff_factor( + const EaxEaxCall& eax_call); + + void eax_defer_source_air_absorption_factor( + const EaxEaxCall& eax_call); + + void eax_defer_source_flags( + const EaxEaxCall& eax_call); + + void eax_defer_source_macro_fx_factor( + const EaxEaxCall& eax_call); + + void eax_defer_source_2d_all( + const EaxEaxCall& eax_call); + + void eax_defer_source_obstruction_all( + const EaxEaxCall& eax_call); + + void eax_defer_source_exclusion_all( + const EaxEaxCall& eax_call); + + void eax_defer_source_occlusion_all( + const EaxEaxCall& eax_call); + + void eax_defer_source_all( + const EaxEaxCall& eax_call); + + void eax_defer_source_speaker_level_all( + const EaxEaxCall& eax_call); + + + void eax_set_outside_volume_hf(); + + void eax_set_doppler_factor(); + + void eax_set_rolloff_factor(); + + void eax_set_room_rolloff_factor(); + + void eax_set_air_absorption_factor(); + + + void eax_set_direct_hf_auto_flag(); + + void eax_set_room_auto_flag(); + + void eax_set_room_hf_auto_flag(); + + void eax_set_flags(); + + + void eax_set_macro_fx_factor(); + + void eax_set_speaker_levels(); + + + void eax1_set_efx(); + void eax1_set_reverb_mix(const EaxEaxCall& eax_call); + void eax1_set(const EaxEaxCall& eax_call); + + void eax_apply_deferred(); + + void eax_set( + const EaxEaxCall& eax_call); + + + static const GUID& eax_get_send_fx_slot_guid( + int eax_version, + EaxFxSlotIndexValue fx_slot_index); + + static void eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCESENDPROPERTIES& dst_send); + + static void eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCEALLSENDPROPERTIES& dst_send); + + static void eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCEOCCLUSIONSENDPROPERTIES& dst_send); + + static void eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCEEXCLUSIONSENDPROPERTIES& dst_send); + + template< + typename TException, + typename TSrcSend + > + void eax_api_get_send_properties( + const EaxEaxCall& eax_call) const + { + const auto eax_version = eax_call.get_version(); + const auto dst_sends = eax_call.get_values(); + const auto send_count = dst_sends.size(); + + for (auto fx_slot_index = EaxFxSlotIndexValue{}; fx_slot_index < send_count; ++fx_slot_index) + { + auto& dst_send = dst_sends[fx_slot_index]; + const auto& src_send = eax_.sends[fx_slot_index]; + + eax_copy_send(src_send, dst_send); + + dst_send.guidReceivingFXSlotID = eax_get_send_fx_slot_guid(eax_version, fx_slot_index); + } + } + + + void eax1_get(const EaxEaxCall& eax_call); + + void eax_api_get_source_all_v2( + const EaxEaxCall& eax_call); + + void eax_api_get_source_all_v3( + const EaxEaxCall& eax_call); + + void eax_api_get_source_all_v5( + const EaxEaxCall& eax_call); + + void eax_api_get_source_all( + const EaxEaxCall& eax_call); + + void eax_api_get_source_all_obstruction( + const EaxEaxCall& eax_call); + + void eax_api_get_source_all_occlusion( + const EaxEaxCall& eax_call); + + void eax_api_get_source_all_exclusion( + const EaxEaxCall& eax_call); + + void eax_api_get_source_active_fx_slot_id( + const EaxEaxCall& eax_call); + + void eax_api_get_source_all_2d( + const EaxEaxCall& eax_call); + + void eax_api_get_source_speaker_level_all( + const EaxEaxCall& eax_call); + + void eax_get( + const EaxEaxCall& eax_call); + + + // `alSource3i(source, AL_AUXILIARY_SEND_FILTER, ...)` + void eax_set_al_source_send(ALeffectslot *slot, size_t sendidx, + const EaxAlLowPassParam &filter); +#endif // ALSOFT_EAX +}; + +void UpdateAllSourceProps(ALCcontext *context); + +#endif diff --git a/modules/openal-soft/al/state.cpp b/modules/openal-soft/al/state.cpp new file mode 100644 index 0000000..8142890 --- /dev/null +++ b/modules/openal-soft/al/state.cpp @@ -0,0 +1,962 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2000 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "version.h" + +#include +#include +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "alc/alu.h" +#include "alc/context.h" +#include "alc/inprogext.h" +#include "alnumeric.h" +#include "aloptional.h" +#include "atomic.h" +#include "core/context.h" +#include "core/except.h" +#include "core/mixer/defs.h" +#include "core/voice.h" +#include "intrusive_ptr.h" +#include "opthelpers.h" +#include "strutils.h" + +#ifdef ALSOFT_EAX +#include "alc/device.h" + +#include "eax_globals.h" +#include "eax_x_ram.h" +#endif // ALSOFT_EAX + + +namespace { + +constexpr ALchar alVendor[] = "OpenAL Community"; +constexpr ALchar alVersion[] = "1.1 ALSOFT " ALSOFT_VERSION; +constexpr ALchar alRenderer[] = "OpenAL Soft"; + +// Error Messages +constexpr ALchar alNoError[] = "No Error"; +constexpr ALchar alErrInvalidName[] = "Invalid Name"; +constexpr ALchar alErrInvalidEnum[] = "Invalid Enum"; +constexpr ALchar alErrInvalidValue[] = "Invalid Value"; +constexpr ALchar alErrInvalidOp[] = "Invalid Operation"; +constexpr ALchar alErrOutOfMemory[] = "Out of Memory"; + +/* Resampler strings */ +template struct ResamplerName { }; +template<> struct ResamplerName +{ static constexpr const ALchar *Get() noexcept { return "Nearest"; } }; +template<> struct ResamplerName +{ static constexpr const ALchar *Get() noexcept { return "Linear"; } }; +template<> struct ResamplerName +{ static constexpr const ALchar *Get() noexcept { return "Cubic"; } }; +template<> struct ResamplerName +{ static constexpr const ALchar *Get() noexcept { return "11th order Sinc (fast)"; } }; +template<> struct ResamplerName +{ static constexpr const ALchar *Get() noexcept { return "11th order Sinc"; } }; +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"; } }; + +const ALchar *GetResamplerName(const Resampler rtype) +{ +#define HANDLE_RESAMPLER(r) case r: return ResamplerName::Get() + switch(rtype) + { + HANDLE_RESAMPLER(Resampler::Point); + HANDLE_RESAMPLER(Resampler::Linear); + HANDLE_RESAMPLER(Resampler::Cubic); + HANDLE_RESAMPLER(Resampler::FastBSinc12); + HANDLE_RESAMPLER(Resampler::BSinc12); + HANDLE_RESAMPLER(Resampler::FastBSinc24); + HANDLE_RESAMPLER(Resampler::BSinc24); + } +#undef HANDLE_RESAMPLER + /* Should never get here. */ + throw std::runtime_error{"Unexpected resampler index"}; +} + +al::optional DistanceModelFromALenum(ALenum model) +{ + switch(model) + { + case AL_NONE: return al::make_optional(DistanceModel::Disable); + case AL_INVERSE_DISTANCE: return al::make_optional(DistanceModel::Inverse); + case AL_INVERSE_DISTANCE_CLAMPED: return al::make_optional(DistanceModel::InverseClamped); + case AL_LINEAR_DISTANCE: return al::make_optional(DistanceModel::Linear); + case AL_LINEAR_DISTANCE_CLAMPED: return al::make_optional(DistanceModel::LinearClamped); + case AL_EXPONENT_DISTANCE: return al::make_optional(DistanceModel::Exponent); + case AL_EXPONENT_DISTANCE_CLAMPED: return al::make_optional(DistanceModel::ExponentClamped); + } + return al::nullopt; +} +ALenum ALenumFromDistanceModel(DistanceModel model) +{ + switch(model) + { + case DistanceModel::Disable: return AL_NONE; + case DistanceModel::Inverse: return AL_INVERSE_DISTANCE; + case DistanceModel::InverseClamped: return AL_INVERSE_DISTANCE_CLAMPED; + case DistanceModel::Linear: return AL_LINEAR_DISTANCE; + case DistanceModel::LinearClamped: return AL_LINEAR_DISTANCE_CLAMPED; + 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))}; +} + +} // namespace + +/* WARNING: Non-standard export! Not part of any extension, or exposed in the + * alcFunctions list. + */ +AL_API const ALchar* AL_APIENTRY alsoft_get_version(void) +START_API_FUNC +{ + static const auto spoof = al::getenv("ALSOFT_SPOOF_VERSION"); + if(spoof) return spoof->c_str(); + return ALSOFT_VERSION; +} +END_API_FUNC + +#define DO_UPDATEPROPS() do { \ + if(!context->mDeferUpdates) \ + UpdateContextProps(context.get()); \ + else \ + context->mPropsDirty = true; \ +} while(0) + + +AL_API void AL_APIENTRY alEnable(ALenum capability) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + switch(capability) + { + case AL_SOURCE_DISTANCE_MODEL: + { + std::lock_guard _{context->mPropLock}; + context->mSourceDistanceModel = true; + DO_UPDATEPROPS(); + } + break; + + case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: + context->setError(AL_INVALID_OPERATION, "Re-enabling AL_STOP_SOURCES_ON_DISCONNECT_SOFT not yet supported"); + break; + + default: + context->setError(AL_INVALID_VALUE, "Invalid enable property 0x%04x", capability); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alDisable(ALenum capability) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + switch(capability) + { + case AL_SOURCE_DISTANCE_MODEL: + { + std::lock_guard _{context->mPropLock}; + context->mSourceDistanceModel = false; + DO_UPDATEPROPS(); + } + break; + + case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: + context->mStopVoicesOnDisconnect = false; + break; + + default: + context->setError(AL_INVALID_VALUE, "Invalid disable property 0x%04x", capability); + } +} +END_API_FUNC + +AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return AL_FALSE; + + std::lock_guard _{context->mPropLock}; + ALboolean value{AL_FALSE}; + switch(capability) + { + case AL_SOURCE_DISTANCE_MODEL: + value = context->mSourceDistanceModel ? AL_TRUE : AL_FALSE; + break; + + case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: + value = context->mStopVoicesOnDisconnect ? AL_TRUE : AL_FALSE; + break; + + default: + context->setError(AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability); + } + + return value; +} +END_API_FUNC + +AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum pname) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return AL_FALSE; + + std::lock_guard _{context->mPropLock}; + ALboolean value{AL_FALSE}; + switch(pname) + { + case AL_DOPPLER_FACTOR: + if(context->mDopplerFactor != 0.0f) + value = AL_TRUE; + break; + + case AL_DOPPLER_VELOCITY: + if(context->mDopplerVelocity != 0.0f) + value = AL_TRUE; + break; + + case AL_DISTANCE_MODEL: + if(context->mDistanceModel == DistanceModel::Default) + value = AL_TRUE; + break; + + case AL_SPEED_OF_SOUND: + if(context->mSpeedOfSound != 0.0f) + value = AL_TRUE; + break; + + case AL_DEFERRED_UPDATES_SOFT: + if(context->mDeferUpdates) + value = AL_TRUE; + break; + + case AL_GAIN_LIMIT_SOFT: + if(GainMixMax/context->mGainBoost != 0.0f) + value = AL_TRUE; + break; + + case AL_NUM_RESAMPLERS_SOFT: + /* Always non-0. */ + value = AL_TRUE; + break; + + case AL_DEFAULT_RESAMPLER_SOFT: + value = static_cast(ResamplerDefault) ? AL_TRUE : AL_FALSE; + break; + + default: + context->setError(AL_INVALID_VALUE, "Invalid boolean property 0x%04x", pname); + } + + return value; +} +END_API_FUNC + +AL_API ALdouble AL_APIENTRY alGetDouble(ALenum pname) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return 0.0; + + std::lock_guard _{context->mPropLock}; + ALdouble value{0.0}; + switch(pname) + { + case AL_DOPPLER_FACTOR: + value = context->mDopplerFactor; + break; + + case AL_DOPPLER_VELOCITY: + value = context->mDopplerVelocity; + break; + + case AL_DISTANCE_MODEL: + value = static_cast(ALenumFromDistanceModel(context->mDistanceModel)); + break; + + case AL_SPEED_OF_SOUND: + value = context->mSpeedOfSound; + break; + + case AL_DEFERRED_UPDATES_SOFT: + if(context->mDeferUpdates) + value = static_cast(AL_TRUE); + break; + + case AL_GAIN_LIMIT_SOFT: + value = ALdouble{GainMixMax}/context->mGainBoost; + break; + + case AL_NUM_RESAMPLERS_SOFT: + value = static_cast(Resampler::Max) + 1.0; + break; + + case AL_DEFAULT_RESAMPLER_SOFT: + value = static_cast(ResamplerDefault); + break; + + default: + context->setError(AL_INVALID_VALUE, "Invalid double property 0x%04x", pname); + } + + return value; +} +END_API_FUNC + +AL_API ALfloat AL_APIENTRY alGetFloat(ALenum pname) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return 0.0f; + + std::lock_guard _{context->mPropLock}; + ALfloat value{0.0f}; + switch(pname) + { + case AL_DOPPLER_FACTOR: + value = context->mDopplerFactor; + break; + + case AL_DOPPLER_VELOCITY: + value = context->mDopplerVelocity; + break; + + case AL_DISTANCE_MODEL: + value = static_cast(ALenumFromDistanceModel(context->mDistanceModel)); + break; + + case AL_SPEED_OF_SOUND: + value = context->mSpeedOfSound; + break; + + case AL_DEFERRED_UPDATES_SOFT: + if(context->mDeferUpdates) + value = static_cast(AL_TRUE); + break; + + case AL_GAIN_LIMIT_SOFT: + value = GainMixMax/context->mGainBoost; + break; + + case AL_NUM_RESAMPLERS_SOFT: + value = static_cast(Resampler::Max) + 1.0f; + break; + + case AL_DEFAULT_RESAMPLER_SOFT: + value = static_cast(ResamplerDefault); + break; + + default: + context->setError(AL_INVALID_VALUE, "Invalid float property 0x%04x", pname); + } + + return value; +} +END_API_FUNC + +AL_API ALint AL_APIENTRY alGetInteger(ALenum pname) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return 0; + + std::lock_guard _{context->mPropLock}; + ALint value{0}; + switch(pname) + { + case AL_DOPPLER_FACTOR: + value = static_cast(context->mDopplerFactor); + break; + + case AL_DOPPLER_VELOCITY: + value = static_cast(context->mDopplerVelocity); + break; + + case AL_DISTANCE_MODEL: + value = ALenumFromDistanceModel(context->mDistanceModel); + break; + + case AL_SPEED_OF_SOUND: + value = static_cast(context->mSpeedOfSound); + break; + + case AL_DEFERRED_UPDATES_SOFT: + if(context->mDeferUpdates) + value = AL_TRUE; + break; + + case AL_GAIN_LIMIT_SOFT: + value = static_cast(GainMixMax/context->mGainBoost); + break; + + case AL_NUM_RESAMPLERS_SOFT: + value = static_cast(Resampler::Max) + 1; + break; + + case AL_DEFAULT_RESAMPLER_SOFT: + value = static_cast(ResamplerDefault); + break; + +#ifdef ALSOFT_EAX + +#define EAX_ERROR "[alGetInteger] EAX not enabled." + + case AL_EAX_RAM_SIZE: + if (eax_g_is_enabled) + { + value = eax_x_ram_max_size; + } + else + { + context->setError(AL_INVALID_VALUE, EAX_ERROR); + } + + break; + + case AL_EAX_RAM_FREE: + if (eax_g_is_enabled) + { + auto device = context->mALDevice.get(); + std::lock_guard device_lock{device->BufferLock}; + + value = static_cast(device->eax_x_ram_free_size); + } + else + { + context->setError(AL_INVALID_VALUE, EAX_ERROR); + } + + break; + +#undef EAX_ERROR + +#endif // ALSOFT_EAX + + default: + context->setError(AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname); + } + + return value; +} +END_API_FUNC + +AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return 0_i64; + + std::lock_guard _{context->mPropLock}; + ALint64SOFT value{0}; + switch(pname) + { + case AL_DOPPLER_FACTOR: + value = static_cast(context->mDopplerFactor); + break; + + case AL_DOPPLER_VELOCITY: + value = static_cast(context->mDopplerVelocity); + break; + + case AL_DISTANCE_MODEL: + value = ALenumFromDistanceModel(context->mDistanceModel); + break; + + case AL_SPEED_OF_SOUND: + value = static_cast(context->mSpeedOfSound); + break; + + case AL_DEFERRED_UPDATES_SOFT: + if(context->mDeferUpdates) + value = AL_TRUE; + break; + + case AL_GAIN_LIMIT_SOFT: + value = static_cast(GainMixMax/context->mGainBoost); + break; + + case AL_NUM_RESAMPLERS_SOFT: + value = static_cast(Resampler::Max) + 1; + break; + + case AL_DEFAULT_RESAMPLER_SOFT: + value = static_cast(ResamplerDefault); + break; + + default: + context->setError(AL_INVALID_VALUE, "Invalid integer64 property 0x%04x", pname); + } + + return value; +} +END_API_FUNC + +AL_API ALvoid* AL_APIENTRY alGetPointerSOFT(ALenum pname) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return nullptr; + + std::lock_guard _{context->mPropLock}; + void *value{nullptr}; + switch(pname) + { + case AL_EVENT_CALLBACK_FUNCTION_SOFT: + value = reinterpret_cast(context->mEventCb); + break; + + case AL_EVENT_CALLBACK_USER_PARAM_SOFT: + value = context->mEventParam; + break; + + default: + context->setError(AL_INVALID_VALUE, "Invalid pointer property 0x%04x", pname); + } + + return value; +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetBooleanv(ALenum pname, ALboolean *values) +START_API_FUNC +{ + if(values) + { + switch(pname) + { + case AL_DOPPLER_FACTOR: + case AL_DOPPLER_VELOCITY: + case AL_DISTANCE_MODEL: + case AL_SPEED_OF_SOUND: + case AL_DEFERRED_UPDATES_SOFT: + case AL_GAIN_LIMIT_SOFT: + case AL_NUM_RESAMPLERS_SOFT: + case AL_DEFAULT_RESAMPLER_SOFT: + values[0] = alGetBoolean(pname); + return; + } + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(pname) + { + default: + context->setError(AL_INVALID_VALUE, "Invalid boolean-vector property 0x%04x", pname); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetDoublev(ALenum pname, ALdouble *values) +START_API_FUNC +{ + if(values) + { + switch(pname) + { + case AL_DOPPLER_FACTOR: + case AL_DOPPLER_VELOCITY: + case AL_DISTANCE_MODEL: + case AL_SPEED_OF_SOUND: + case AL_DEFERRED_UPDATES_SOFT: + case AL_GAIN_LIMIT_SOFT: + case AL_NUM_RESAMPLERS_SOFT: + case AL_DEFAULT_RESAMPLER_SOFT: + values[0] = alGetDouble(pname); + return; + } + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(pname) + { + default: + context->setError(AL_INVALID_VALUE, "Invalid double-vector property 0x%04x", pname); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetFloatv(ALenum pname, ALfloat *values) +START_API_FUNC +{ + if(values) + { + switch(pname) + { + case AL_DOPPLER_FACTOR: + case AL_DOPPLER_VELOCITY: + case AL_DISTANCE_MODEL: + case AL_SPEED_OF_SOUND: + case AL_DEFERRED_UPDATES_SOFT: + case AL_GAIN_LIMIT_SOFT: + case AL_NUM_RESAMPLERS_SOFT: + case AL_DEFAULT_RESAMPLER_SOFT: + values[0] = alGetFloat(pname); + return; + } + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(pname) + { + default: + context->setError(AL_INVALID_VALUE, "Invalid float-vector property 0x%04x", pname); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetIntegerv(ALenum pname, ALint *values) +START_API_FUNC +{ + if(values) + { + switch(pname) + { + case AL_DOPPLER_FACTOR: + case AL_DOPPLER_VELOCITY: + case AL_DISTANCE_MODEL: + case AL_SPEED_OF_SOUND: + case AL_DEFERRED_UPDATES_SOFT: + case AL_GAIN_LIMIT_SOFT: + case AL_NUM_RESAMPLERS_SOFT: + case AL_DEFAULT_RESAMPLER_SOFT: + values[0] = alGetInteger(pname); + return; + } + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(pname) + { + default: + context->setError(AL_INVALID_VALUE, "Invalid integer-vector property 0x%04x", pname); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) +START_API_FUNC +{ + if(values) + { + switch(pname) + { + case AL_DOPPLER_FACTOR: + case AL_DOPPLER_VELOCITY: + case AL_DISTANCE_MODEL: + case AL_SPEED_OF_SOUND: + case AL_DEFERRED_UPDATES_SOFT: + case AL_GAIN_LIMIT_SOFT: + case AL_NUM_RESAMPLERS_SOFT: + case AL_DEFAULT_RESAMPLER_SOFT: + values[0] = alGetInteger64SOFT(pname); + return; + } + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(pname) + { + default: + context->setError(AL_INVALID_VALUE, "Invalid integer64-vector property 0x%04x", pname); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, ALvoid **values) +START_API_FUNC +{ + if(values) + { + switch(pname) + { + case AL_EVENT_CALLBACK_FUNCTION_SOFT: + case AL_EVENT_CALLBACK_USER_PARAM_SOFT: + values[0] = alGetPointerSOFT(pname); + return; + } + } + + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if(!values) + context->setError(AL_INVALID_VALUE, "NULL pointer"); + else switch(pname) + { + default: + context->setError(AL_INVALID_VALUE, "Invalid pointer-vector property 0x%04x", pname); + } +} +END_API_FUNC + +AL_API const ALchar* AL_APIENTRY alGetString(ALenum pname) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return nullptr; + + const ALchar *value{nullptr}; + switch(pname) + { + case AL_VENDOR: + value = alVendor; + break; + + case AL_VERSION: + value = alVersion; + break; + + case AL_RENDERER: + value = alRenderer; + break; + + case AL_EXTENSIONS: + value = context->mExtensionList; + break; + + case AL_NO_ERROR: + value = alNoError; + break; + + case AL_INVALID_NAME: + value = alErrInvalidName; + break; + + case AL_INVALID_ENUM: + value = alErrInvalidEnum; + break; + + case AL_INVALID_VALUE: + value = alErrInvalidValue; + break; + + case AL_INVALID_OPERATION: + value = alErrInvalidOp; + break; + + case AL_OUT_OF_MEMORY: + value = alErrOutOfMemory; + break; + + default: + context->setError(AL_INVALID_VALUE, "Invalid string property 0x%04x", pname); + } + return value; +} +END_API_FUNC + +AL_API void AL_APIENTRY alDopplerFactor(ALfloat value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if(!(value >= 0.0f && std::isfinite(value))) + context->setError(AL_INVALID_VALUE, "Doppler factor %f out of range", value); + else + { + std::lock_guard _{context->mPropLock}; + context->mDopplerFactor = value; + DO_UPDATEPROPS(); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if(!(value >= 0.0f && std::isfinite(value))) + context->setError(AL_INVALID_VALUE, "Doppler velocity %f out of range", value); + else + { + std::lock_guard _{context->mPropLock}; + context->mDopplerVelocity = value; + DO_UPDATEPROPS(); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alSpeedOfSound(ALfloat value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if(!(value > 0.0f && std::isfinite(value))) + context->setError(AL_INVALID_VALUE, "Speed of sound %f out of range", value); + else + { + std::lock_guard _{context->mPropLock}; + context->mSpeedOfSound = value; + DO_UPDATEPROPS(); + } +} +END_API_FUNC + +AL_API void AL_APIENTRY alDistanceModel(ALenum value) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if(auto model = DistanceModelFromALenum(value)) + { + std::lock_guard _{context->mPropLock}; + context->mDistanceModel = *model; + if(!context->mSourceDistanceModel) + DO_UPDATEPROPS(); + } + else + context->setError(AL_INVALID_VALUE, "Distance model 0x%04x out of range", value); +} +END_API_FUNC + + +AL_API void AL_APIENTRY alDeferUpdatesSOFT(void) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + context->deferUpdates(); +} +END_API_FUNC + +AL_API void AL_APIENTRY alProcessUpdatesSOFT(void) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard _{context->mPropLock}; + context->processUpdates(); +} +END_API_FUNC + + +AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return nullptr; + + const ALchar *value{nullptr}; + switch(pname) + { + case AL_RESAMPLER_NAME_SOFT: + if(index < 0 || index > static_cast(Resampler::Max)) + context->setError(AL_INVALID_VALUE, "Resampler name index %d out of range", index); + else + value = GetResamplerName(static_cast(index)); + break; + + default: + context->setError(AL_INVALID_VALUE, "Invalid string indexed property"); + } + return value; +} +END_API_FUNC + + +void UpdateContextProps(ALCcontext *context) +{ + /* Get an unused proprty container, or allocate a new one as needed. */ + ContextProps *props{context->mFreeContextProps.load(std::memory_order_acquire)}; + if(!props) + props = new ContextProps{}; + else + { + ContextProps *next; + do { + next = props->next.load(std::memory_order_relaxed); + } while(context->mFreeContextProps.compare_exchange_weak(props, next, + std::memory_order_seq_cst, std::memory_order_acquire) == 0); + } + + /* Copy in current property values. */ + ALlistener &listener = context->mListener; + props->Position = listener.Position; + props->Velocity = listener.Velocity; + props->OrientAt = listener.OrientAt; + props->OrientUp = listener.OrientUp; + props->Gain = listener.Gain; + props->MetersPerUnit = listener.mMetersPerUnit; + + props->DopplerFactor = context->mDopplerFactor; + props->DopplerVelocity = context->mDopplerVelocity; + props->SpeedOfSound = context->mSpeedOfSound; + + props->SourceDistanceModel = context->mSourceDistanceModel; + props->mDistanceModel = context->mDistanceModel; + + /* Set the new container for updating internal parameters. */ + props = context->mParams.ContextUpdate.exchange(props, std::memory_order_acq_rel); + if(props) + { + /* If there was an unused update container, put it back in the + * freelist. + */ + AtomicReplaceHead(context->mFreeContextProps, props); + } +} diff --git a/modules/openal-soft/alsoftrc.sample b/modules/openal-soft/alsoftrc.sample index 95d4ff8..7333899 100644 --- a/modules/openal-soft/alsoftrc.sample +++ b/modules/openal-soft/alsoftrc.sample @@ -14,10 +14,17 @@ # block, while ALSA options would be in the [alsa/Name of Device] block. # Options marked as "(global)" are not influenced by the device. # -# The system-wide settings can be put in /etc/openal/alsoft.conf and user- -# specific override settings in $HOME/.alsoftrc. +# The system-wide settings can be put in /etc/xdg/alsoft.conf (as determined by +# the XDG_CONFIG_DIRS env var list, /etc/xdg being the default if unset) and +# user-specific override settings in $HOME/.config/alsoft.conf (as determined +# by the XDG_CONFIG_HOME env var). +# # For Windows, these settings should go into $AppData\alsoft.ini # +# An additional configuration file (alsoft.ini on Windows, alsoft.conf on other +# OSs) can be placed alongside the process executable for app-specific config +# settings. +# # Option and block names are case-senstive. The supplied values are only hints # and may not be honored (though generally it'll try to get as close as # possible). Note: options that are left unset may default to app- or system- @@ -48,10 +55,10 @@ ## channels: # Sets the output channel configuration. If left unspecified, one will try to # be detected from the system, and defaulting to stereo. The available values -# are: mono, stereo, quad, surround51, surround51rear, surround61, surround71, -# ambi1, ambi2, ambi3. Note that the ambi* configurations provide ambisonic -# channels of the given order (using ACN ordering and SN3D normalization by -# default), which need to be decoded to play correctly on speakers. +# are: mono, stereo, quad, surround51, surround61, surround71, ambi1, ambi2, +# ambi3. Note that the ambi* configurations provide ambisonic channels of the +# given order (using ACN ordering and SN3D normalization by default), which +# need to be decoded to play correctly on speakers. #channels = ## sample-type: @@ -102,19 +109,39 @@ ## ambi-format: # Specifies the channel order and normalization for the "ambi*" set of channel -# configurations. Valid settings are: fuma, ambix (or acn+sn3d), acn+n3d +# configurations. Valid settings are: fuma, acn+fuma, ambix (or acn+sn3d), or +# acn+n3d #ambi-format = ambix ## hrtf: # Controls HRTF processing. These filters provide better spatialization of -# sounds while using headphones, but do require a bit more CPU power. The -# default filters will only work with 44100hz or 48000hz stereo output. While +# sounds while using headphones, but do require a bit more CPU power. While # HRTF is used, the cf_level option is ignored. Setting this to auto (default) # will allow HRTF to be used when headphones are detected or the app requests # it, while setting true or false will forcefully enable or disable HRTF # respectively. #hrtf = auto +## hrtf-mode: +# Specifies the rendering mode for HRTF processing. Setting the mode to full +# (default) applies a unique HRIR filter to each source given its relative +# location, providing the clearest directional response at the cost of the +# highest CPU usage. Setting the mode to ambi1, ambi2, or ambi3 will instead +# mix to a first-, second-, or third-order ambisonic buffer respectively, then +# decode that buffer with HRTF filters. Ambi1 has the lowest CPU usage, +# replacing the per-source HRIR filter for a simple 4-channel panning mix, but +# retains full 3D placement at the cost of a more diffuse response. Ambi2 and +# ambi3 increasingly improve the directional clarity, at the cost of more CPU +# usage (still less than "full", given some number of active sources). +#hrtf-mode = full + +## hrtf-size: +# Specifies the impulse response size, in samples, for the HRTF filter. Larger +# values increase the filter quality, while smaller values reduce processing +# cost. A value of 0 (default) uses the full filter size in the dataset, and +# the default dataset has a filter size of 32 samples at 44.1khz. +#hrtf-size = 0 + ## default-hrtf: # Specifies the default HRTF to use. When multiple HRTFs are available, this # determines the preferred one to use if none are specifically requested. Note @@ -148,24 +175,33 @@ #cf_level = 0 ## resampler: (global) -# Selects the resampler used when mixing sources. Valid values are: +# Selects the default resampler used when mixing sources. Valid values are: # point - nearest sample, no interpolation # linear - extrapolates samples using a linear slope between samples # cubic - extrapolates samples using a Catmull-Rom spline # bsinc12 - extrapolates samples using a band-limited Sinc filter (varying # between 12 and 24 points, with anti-aliasing) +# fast_bsinc12 - same as bsinc12, except without interpolation between down- +# sampling scales # bsinc24 - extrapolates samples using a band-limited Sinc filter (varying # between 24 and 48 points, with anti-aliasing) +# fast_bsinc24 - same as bsinc24, except without interpolation between down- +# sampling scales #resampler = linear ## rt-prio: (global) -# Sets real-time priority for the mixing thread. Not all drivers may use this -# (eg. PortAudio) as they already control the priority of the mixing thread. -# 0 and negative values will disable it. Note that this may constitute a -# security risk since a real-time priority thread can indefinitely block -# normal-priority threads if it fails to wait. As such, the default is -# disabled. -#rt-prio = 0 +# Sets the real-time priority value for the mixing thread. Not all drivers may +# use this (eg. PortAudio) as those APIs already control the priority of the +# mixing thread. 0 and negative values will disable real-time priority. Note +# that this may constitute a security risk since a real-time priority thread +# can indefinitely block normal-priority threads if it fails to wait. Disable +# this if it turns out to be a problem. +#rt-prio = 1 + +## rt-time-limit: (global) +# On non-Windows systems, allows reducing the process's RLIMIT_RTTIME resource +# as necessary for acquiring real-time priority from RTKit. +#rt-time-limit = true ## sources: # Sets the maximum number of allocatable sources. Lower values may help for @@ -182,7 +218,7 @@ ## sends: # Limits the number of auxiliary sends allowed per source. Setting this higher # than the default has no effect. -#sends = 16 +#sends = 6 ## front-stablizer: # Applies filters to "stablize" front sound imaging. A psychoacoustic method @@ -223,7 +259,7 @@ # help for apps that try to use effects which are too CPU intensive for the # system to handle. Available effects are: eaxreverb,reverb,autowah,chorus, # compressor,distortion,echo,equalizer,flanger,modulator,dedicated,pshifter, -# fshifter +# fshifter,vmorpher. #excludefx = ## default-reverb: (global) @@ -255,11 +291,8 @@ ## hq-mode: # Enables a high-quality ambisonic decoder. This mode is capable of frequency- # dependent processing, creating a better reproduction of 3D sound rendering -# over surround sound speakers. Enabling this also requires specifying decoder -# configuration files for the appropriate speaker configuration you intend to -# use (see the quad, surround51, etc options below). Currently, up to third- -# order decoding is supported. -hq-mode = false +# over surround sound speakers. +#hq-mode = true ## distance-comp: # Enables compensation for the speakers' relative distances to the listener. @@ -267,7 +300,7 @@ hq-mode = false # behave as though they are all equidistant, which is important for proper # playback of 3D sound rendering. Requires the proper distances to be # specified in the decoder configuration file. -distance-comp = true +#distance-comp = true ## nfc: # Enables near-field control filters. This simulates and compensates for low- @@ -276,7 +309,7 @@ distance-comp = true # may be stronger or weaker than intended if the application doesn't use or # specify an appropriate unit scale, or if incorrect speaker distances are set # in the decoder configuration file. -nfc = false +#nfc = false ## nfc-ref-delay # Specifies the reference delay value for ambisonic output when NFC filters @@ -287,29 +320,29 @@ nfc = false # designed for higher-order ambisonics, this also applies to first-order # output. When left unset, normal output is created with no near-field # simulation. Requires the nfc option to also be enabled. -nfc-ref-delay = +#nfc-ref-delay = ## quad: # Decoder configuration file for Quadraphonic channel output. See # docs/ambdec.txt for a description of the file format. -quad = +#quad = ## surround51: # Decoder configuration file for 5.1 Surround (Side and Rear) channel output. # See docs/ambdec.txt for a description of the file format. -surround51 = +#surround51 = ## surround61: # Decoder configuration file for 6.1 Surround channel output. See # docs/ambdec.txt for a description of the file format. -surround61 = +#surround61 = ## surround71: # Decoder configuration file for 7.1 Surround channel output. See # docs/ambdec.txt for a description of the file format. Note: This can be used # to enable 3D7.1 with the appropriate configuration and speaker placement, # see docs/3D7.1.txt. -surround71 = +#surround71 = ## ## Reverb effect stuff (includes EAX reverb) @@ -323,6 +356,21 @@ surround71 = # value of 0 means no change. #boost = 0 +## +## PipeWire backend stuff +## +[pipewire] + +## assume-audio: (global) +# Causes the backend to succeed initialization even if PipeWire reports no +# audio support. Currently, audio support is detected by the presence of audio +# source or sink nodes, although this can cause false negatives in cases where +# device availability during library initialization is spotty. Future versions +# of PipeWire are expected to have a more robust method to test audio support, +# but in the mean time this can be set to true to assume PipeWire has audio +# support even when no nodes may be reported at initialization time. +#assume-audio = false + ## ## PulseAudio backend stuff ## @@ -378,6 +426,13 @@ surround71 = # case-sensitive. #device-prefix- = +## custom-devices: (global) +# Specifies a list of enumerated playback devices and the ALSA devices they +# refer to. The list pattern is "Display Name=ALSA device;...". The display +# names will be returned for device enumeration, and the ALSA device is the +# device name to open for each enumerated device. +#custom-devices = + ## capture: (global) # Sets the device name for the default capture device. #capture = default @@ -395,6 +450,13 @@ surround71 = # capture-prefix-NVidia-0). The card id is case-sensitive. #capture-prefix- = +## custom-captures: (global) +# Specifies a list of enumerated capture devices and the ALSA devices they +# refer to. The list pattern is "Display Name=ALSA device;...". The display +# names will be returned for device enumeration, and the ALSA device is the +# device name to open for each enumerated device. +#custom-captures = + ## mmap: # Sets whether to try using mmap mode (helps reduce latencies and CPU # consumption). If mmap isn't available, it will automatically fall back to @@ -442,10 +504,29 @@ surround71 = [jack] ## spawn-server: (global) -# Attempts to autospawn a JACK server whenever needed (initializing the -# backend, opening devices, etc). +# Attempts to autospawn a JACK server when initializing. #spawn-server = false +## custom-devices: (global) +# Specifies a list of enumerated devices and the ports they connect to. The +# list pattern is "Display Name=ports regex;Display Name=ports regex;...". The +# display names will be returned for device enumeration, and the ports regex +# is the regular expression to identify the target ports on the server (as +# given by the jack_get_ports function) for each enumerated device. +#custom-devices = + +## rt-mix: +# Renders samples directly in the real-time processing callback. This allows +# for lower latency and less overall CPU utilization, but can increase the +# risk of underruns when increasing the amount of work the mixer needs to do. +#rt-mix = true + +## connect-ports: +# Attempts to automatically connect the client ports to physical server ports. +# Client ports that fail to connect will leave the remaining channels +# unconnected and silent (the device format won't change to accommodate). +#connect-ports = true + ## buffer-size: # Sets the update buffer size, in samples, that the backend will keep buffered # to handle the server's real-time processing requests. This value must be a @@ -453,6 +534,7 @@ surround71 = # less than JACK's buffer update size, it will be clamped. This option may # be useful in case the server's update size is too small and doesn't give the # mixer time to keep enough audio available for the processing requests. +# Ignored when rt-mix is true. #buffer-size = 0 ## @@ -500,3 +582,30 @@ surround71 = # Creates AMB format files using first-order ambisonics instead of a standard # single- or multi-channel .wav file. #bformat = false + +## +## EAX extensions stuff +## +[eax] + +## enable: (global) +# Sets whether to enable EAX extensions or not. +#enable = true + +## +## Per-game compatibility options (these should only be set in per-game config +## files, *NOT* system- or user-level!) +## +[game_compat] + +## reverse-x: (global) +# Reverses the local X (left-right) position of 3D sound sources. +#reverse-x = false + +## reverse-y: (global) +# Reverses the local Y (up-down) position of 3D sound sources. +#reverse-y = false + +## reverse-z: (global) +# Reverses the local Z (front-back) position of 3D sound sources. +#reverse-z = false diff --git a/modules/openal-soft/appveyor.yml b/modules/openal-soft/appveyor.yml index 010a2da..c468d4e 100644 --- a/modules/openal-soft/appveyor.yml +++ b/modules/openal-soft/appveyor.yml @@ -1,19 +1,21 @@ -version: 1.19.1.{build} +version: 1.22.0.{build} environment: + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + GEN: "Visual Studio 15 2017" matrix: - - GEN: "Visual Studio 14 2015" + - ARCH: Win32 CFG: Release - - GEN: "Visual Studio 14 2015 Win64" + - ARCH: x64 CFG: Release -install: - # Remove the VS Xamarin targets to reduce AppVeyor specific noise in build - # logs. See also http://help.appveyor.com/discussions/problems/4569 - - del "C:\Program Files (x86)\MSBuild\14.0\Microsoft.Common.targets\ImportAfter\Xamarin.Common.targets" +after_build: +- 7z a ..\soft_oal.zip "%APPVEYOR_BUILD_FOLDER%\build\%CFG%\soft_oal.dll" "%APPVEYOR_BUILD_FOLDER%\README.md" "%APPVEYOR_BUILD_FOLDER%\COPYING" + +artifacts: +- path: soft_oal.zip build_script: - cd build - - cmake -G"%GEN%" -DALSOFT_BUILD_ROUTER=ON -DALSOFT_REQUIRE_WINMM=ON -DALSOFT_REQUIRE_DSOUND=ON -DALSOFT_REQUIRE_WASAPI=ON -DALSOFT_EMBED_HRTF_DATA=YES .. + - cmake -G "%GEN%" -A %ARCH% -DALSOFT_BUILD_ROUTER=ON -DALSOFT_REQUIRE_WINMM=ON -DALSOFT_REQUIRE_DSOUND=ON -DALSOFT_REQUIRE_WASAPI=ON -DALSOFT_EMBED_HRTF_DATA=YES .. - cmake --build . --config %CFG% --clean-first - diff --git a/modules/openal-soft/cmake/CheckFileOffsetBits.c b/modules/openal-soft/cmake/CheckFileOffsetBits.c deleted file mode 100644 index de98296..0000000 --- a/modules/openal-soft/cmake/CheckFileOffsetBits.c +++ /dev/null @@ -1,9 +0,0 @@ -#include - -#define KB ((off_t)(1024)) -#define MB ((off_t)(KB*1024)) -#define GB ((off_t)(MB*1024)) -int tb[((GB+GB+GB) > GB) ? 1 : -1]; - -int main() -{ return 0; } diff --git a/modules/openal-soft/cmake/CheckFileOffsetBits.cmake b/modules/openal-soft/cmake/CheckFileOffsetBits.cmake deleted file mode 100644 index 1dc154e..0000000 --- a/modules/openal-soft/cmake/CheckFileOffsetBits.cmake +++ /dev/null @@ -1,39 +0,0 @@ -# - Check if the _FILE_OFFSET_BITS macro is needed for large files -# CHECK_FILE_OFFSET_BITS() -# -# The following variables may be set before calling this macro to -# modify the way the check is run: -# -# CMAKE_REQUIRED_FLAGS = string of compile command line flags -# CMAKE_REQUIRED_DEFINITIONS = list of macros to define (-DFOO=bar) -# CMAKE_REQUIRED_INCLUDES = list of include directories -# Copyright (c) 2009, Chris Robinson -# -# Redistribution and use is allowed according to the terms of the LGPL license. - - -MACRO(CHECK_FILE_OFFSET_BITS) - - IF(NOT DEFINED _FILE_OFFSET_BITS) - MESSAGE(STATUS "Checking _FILE_OFFSET_BITS for large files") - TRY_COMPILE(__WITHOUT_FILE_OFFSET_BITS_64 - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CheckFileOffsetBits.c - COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}) - IF(NOT __WITHOUT_FILE_OFFSET_BITS_64) - TRY_COMPILE(__WITH_FILE_OFFSET_BITS_64 - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CheckFileOffsetBits.c - COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} -D_FILE_OFFSET_BITS=64) - ENDIF(NOT __WITHOUT_FILE_OFFSET_BITS_64) - - IF(NOT __WITHOUT_FILE_OFFSET_BITS_64 AND __WITH_FILE_OFFSET_BITS_64) - SET(_FILE_OFFSET_BITS 64 CACHE INTERNAL "_FILE_OFFSET_BITS macro needed for large files") - MESSAGE(STATUS "Checking _FILE_OFFSET_BITS for large files - 64") - ELSE(NOT __WITHOUT_FILE_OFFSET_BITS_64 AND __WITH_FILE_OFFSET_BITS_64) - SET(_FILE_OFFSET_BITS "" CACHE INTERNAL "_FILE_OFFSET_BITS macro needed for large files") - MESSAGE(STATUS "Checking _FILE_OFFSET_BITS for large files - not needed") - ENDIF(NOT __WITHOUT_FILE_OFFSET_BITS_64 AND __WITH_FILE_OFFSET_BITS_64) - ENDIF(NOT DEFINED _FILE_OFFSET_BITS) - -ENDMACRO(CHECK_FILE_OFFSET_BITS) \ No newline at end of file diff --git a/modules/openal-soft/cmake/CheckSharedFunctionExists.cmake b/modules/openal-soft/cmake/CheckSharedFunctionExists.cmake deleted file mode 100644 index c691fa9..0000000 --- a/modules/openal-soft/cmake/CheckSharedFunctionExists.cmake +++ /dev/null @@ -1,92 +0,0 @@ -# - Check if a symbol exists as a function, variable, or macro -# CHECK_SYMBOL_EXISTS( ) -# -# Check that the is available after including given header -# and store the result in a . Specify the list -# of files in one argument as a semicolon-separated list. -# -# If the header files define the symbol as a macro it is considered -# available and assumed to work. If the header files declare the -# symbol as a function or variable then the symbol must also be -# available for linking. If the symbol is a type or enum value -# it will not be recognized (consider using CheckTypeSize or -# CheckCSourceCompiles). -# -# The following variables may be set before calling this macro to -# modify the way the check is run: -# -# CMAKE_REQUIRED_FLAGS = string of compile command line flags -# CMAKE_REQUIRED_DEFINITIONS = list of macros to define (-DFOO=bar) -# CMAKE_REQUIRED_INCLUDES = list of include directories -# CMAKE_REQUIRED_LIBRARIES = list of libraries to link - -#============================================================================= -# Copyright 2003-2011 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -MACRO(CHECK_SHARED_FUNCTION_EXISTS SYMBOL FILES LIBRARY LOCATION VARIABLE) - IF(NOT DEFINED "${VARIABLE}" OR "x${${VARIABLE}}" STREQUAL "x${VARIABLE}") - SET(CMAKE_CONFIGURABLE_FILE_CONTENT "/* */\n") - SET(MACRO_CHECK_SYMBOL_EXISTS_FLAGS ${CMAKE_REQUIRED_FLAGS}) - IF(CMAKE_REQUIRED_LIBRARIES) - SET(CHECK_SYMBOL_EXISTS_LIBS - "-DLINK_LIBRARIES:STRING=${CMAKE_REQUIRED_LIBRARIES};${LIBRARY}") - ELSE(CMAKE_REQUIRED_LIBRARIES) - SET(CHECK_SYMBOL_EXISTS_LIBS - "-DLINK_LIBRARIES:STRING=${LIBRARY}") - ENDIF(CMAKE_REQUIRED_LIBRARIES) - IF(CMAKE_REQUIRED_INCLUDES) - SET(CMAKE_SYMBOL_EXISTS_INCLUDES - "-DINCLUDE_DIRECTORIES:STRING=${CMAKE_REQUIRED_INCLUDES}") - ELSE(CMAKE_REQUIRED_INCLUDES) - SET(CMAKE_SYMBOL_EXISTS_INCLUDES) - ENDIF(CMAKE_REQUIRED_INCLUDES) - FOREACH(FILE ${FILES}) - SET(CMAKE_CONFIGURABLE_FILE_CONTENT - "${CMAKE_CONFIGURABLE_FILE_CONTENT}#include <${FILE}>\n") - ENDFOREACH(FILE) - SET(CMAKE_CONFIGURABLE_FILE_CONTENT - "${CMAKE_CONFIGURABLE_FILE_CONTENT}\nvoid cmakeRequireSymbol(int dummy,...){(void)dummy;}\nint main()\n{\n cmakeRequireSymbol(0,&${SYMBOL});\n return 0;\n}\n") - - CONFIGURE_FILE("${CMAKE_ROOT}/Modules/CMakeConfigurableFile.in" - "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckSymbolExists.c" @ONLY) - - MESSAGE(STATUS "Looking for ${SYMBOL} in ${LIBRARY}") - TRY_COMPILE(${VARIABLE} - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckSymbolExists.c - COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} - CMAKE_FLAGS - -DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_SYMBOL_EXISTS_FLAGS} - -DLINK_DIRECTORIES:STRING=${LOCATION} - "${CHECK_SYMBOL_EXISTS_LIBS}" - "${CMAKE_SYMBOL_EXISTS_INCLUDES}" - OUTPUT_VARIABLE OUTPUT) - IF(${VARIABLE}) - MESSAGE(STATUS "Looking for ${SYMBOL} in ${LIBRARY} - found") - SET(${VARIABLE} 1 CACHE INTERNAL "Have symbol ${SYMBOL} in ${LIBRARY}") - FILE(APPEND ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log - "Determining if the ${SYMBOL} " - "exist in ${LIBRARY} passed with the following output:\n" - "${OUTPUT}\nFile ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckSymbolExists.c:\n" - "${CMAKE_CONFIGURABLE_FILE_CONTENT}\n") - ELSE(${VARIABLE}) - MESSAGE(STATUS "Looking for ${SYMBOL} in ${LIBRARY} - not found.") - SET(${VARIABLE} "" CACHE INTERNAL "Have symbol ${SYMBOL} in ${LIBRARY}") - FILE(APPEND ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log - "Determining if the ${SYMBOL} " - "exist in ${LIBRARY} failed with the following output:\n" - "${OUTPUT}\nFile ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckSymbolExists.c:\n" - "${CMAKE_CONFIGURABLE_FILE_CONTENT}\n") - ENDIF(${VARIABLE}) - ENDIF(NOT DEFINED "${VARIABLE}" OR "x${${VARIABLE}}" STREQUAL "x${VARIABLE}") -ENDMACRO(CHECK_SHARED_FUNCTION_EXISTS) diff --git a/modules/openal-soft/cmake/FindDSound.cmake b/modules/openal-soft/cmake/FindDSound.cmake deleted file mode 100644 index 4078deb..0000000 --- a/modules/openal-soft/cmake/FindDSound.cmake +++ /dev/null @@ -1,41 +0,0 @@ -# - Find DirectSound includes and libraries -# -# DSOUND_FOUND - True if DSOUND_INCLUDE_DIR & DSOUND_LIBRARY are found -# DSOUND_LIBRARIES - Set when DSOUND_LIBRARY is found -# DSOUND_INCLUDE_DIRS - Set when DSOUND_INCLUDE_DIR is found -# -# DSOUND_INCLUDE_DIR - where to find dsound.h, etc. -# DSOUND_LIBRARY - the dsound library -# - -if (WIN32) - include(FindWindowsSDK) - if (WINDOWSSDK_FOUND) - get_windowssdk_library_dirs(${WINDOWSSDK_PREFERRED_DIR} WINSDK_LIB_DIRS) - get_windowssdk_include_dirs(${WINDOWSSDK_PREFERRED_DIR} WINSDK_INCLUDE_DIRS) - endif() -endif() - -# DSOUND_INCLUDE_DIR -find_path(DSOUND_INCLUDE_DIR - NAMES "dsound.h" - PATHS "${DXSDK_DIR}" ${WINSDK_INCLUDE_DIRS} - PATH_SUFFIXES include - DOC "The DirectSound include directory") - -# DSOUND_LIBRARY -find_library(DSOUND_LIBRARY - NAMES dsound - PATHS "${DXSDK_DIR}" ${WINSDK_LIB_DIRS} - PATH_SUFFIXES lib lib/x86 lib/x64 - DOC "The DirectSound library") - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(DSound REQUIRED_VARS DSOUND_LIBRARY DSOUND_INCLUDE_DIR) - -if(DSOUND_FOUND) - set(DSOUND_LIBRARIES ${DSOUND_LIBRARY}) - set(DSOUND_INCLUDE_DIRS ${DSOUND_INCLUDE_DIR}) -endif() - -mark_as_advanced(DSOUND_INCLUDE_DIR DSOUND_LIBRARY) diff --git a/modules/openal-soft/cmake/FindFFmpeg.cmake b/modules/openal-soft/cmake/FindFFmpeg.cmake index c489c2c..60ca68f 100644 --- a/modules/openal-soft/cmake/FindFFmpeg.cmake +++ b/modules/openal-soft/cmake/FindFFmpeg.cmake @@ -84,17 +84,9 @@ macro(find_component _component _pkgconfig _library _header) if(EXISTS "${${_component}_INCLUDE_DIRS}/${_ver_header}") file(STRINGS "${${_component}_INCLUDE_DIRS}/${_ver_header}" version_str REGEX "^#define[\t ]+LIB${_component}_VERSION_M.*") - foreach(_str "${version_str}") - if(NOT version_maj) - string(REGEX REPLACE "^.*LIB${_component}_VERSION_MAJOR[\t ]+([0-9]*).*$" "\\1" version_maj "${_str}") - endif() - if(NOT version_min) - string(REGEX REPLACE "^.*LIB${_component}_VERSION_MINOR[\t ]+([0-9]*).*$" "\\1" version_min "${_str}") - endif() - if(NOT version_mic) - string(REGEX REPLACE "^.*LIB${_component}_VERSION_MICRO[\t ]+([0-9]*).*$" "\\1" version_mic "${_str}") - endif() - endforeach() + string(REGEX REPLACE "^.*LIB${_component}_VERSION_MAJOR[\t ]+([0-9]*).*$" "\\1" version_maj "${version_str}") + string(REGEX REPLACE "^.*LIB${_component}_VERSION_MINOR[\t ]+([0-9]*).*$" "\\1" version_min "${version_str}") + string(REGEX REPLACE "^.*LIB${_component}_VERSION_MICRO[\t ]+([0-9]*).*$" "\\1" version_mic "${version_str}") unset(version_str) set(${_component}_VERSION "${version_maj}.${version_min}.${version_mic}" CACHE STRING "The ${_component} version number.") diff --git a/modules/openal-soft/cmake/FindMySOFA.cmake b/modules/openal-soft/cmake/FindMySOFA.cmake index a1d5744..7d485c3 100644 --- a/modules/openal-soft/cmake/FindMySOFA.cmake +++ b/modules/openal-soft/cmake/FindMySOFA.cmake @@ -56,7 +56,7 @@ find_library(MYSOFA_M_LIBRARY NAMES m # handle the QUIETLY and REQUIRED arguments and set MYSOFA_FOUND to TRUE if # all listed variables are TRUE include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(MYSOFA REQUIRED_VARS MYSOFA_LIBRARY MYSOFA_INCLUDE_DIR ZLIB_FOUND) +find_package_handle_standard_args(MySOFA REQUIRED_VARS MYSOFA_LIBRARY MYSOFA_INCLUDE_DIR ZLIB_FOUND) if(MYSOFA_FOUND) set(MYSOFA_INCLUDE_DIRS ${MYSOFA_INCLUDE_DIR}) diff --git a/modules/openal-soft/cmake/FindOboe.cmake b/modules/openal-soft/cmake/FindOboe.cmake new file mode 100644 index 0000000..bf12c12 --- /dev/null +++ b/modules/openal-soft/cmake/FindOboe.cmake @@ -0,0 +1,31 @@ +# - Find Oboe +# Find the Oboe library +# +# This module defines the following variable: +# OBOE_FOUND - True if Oboe was found +# +# This module defines the following target: +# oboe::oboe - Import target for linking Oboe to a project +# + +find_path(OBOE_INCLUDE_DIR NAMES oboe/Oboe.h + DOC "The Oboe include directory" +) + +find_library(OBOE_LIBRARY NAMES oboe + DOC "The Oboe library" +) + +# handle the QUIETLY and REQUIRED arguments and set OBOE_FOUND to TRUE if +# all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Oboe REQUIRED_VARS OBOE_LIBRARY OBOE_INCLUDE_DIR) + +if(OBOE_FOUND) + add_library(oboe::oboe UNKNOWN IMPORTED) + set_target_properties(oboe::oboe PROPERTIES + IMPORTED_LOCATION ${OBOE_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES ${OBOE_INCLUDE_DIR}) +endif() + +mark_as_advanced(OBOE_INCLUDE_DIR OBOE_LIBRARY) diff --git a/modules/openal-soft/cmake/FindOpenSL.cmake b/modules/openal-soft/cmake/FindOpenSL.cmake new file mode 100644 index 0000000..0042874 --- /dev/null +++ b/modules/openal-soft/cmake/FindOpenSL.cmake @@ -0,0 +1,63 @@ +# - Find OpenSL +# Find the OpenSL libraries +# +# This module defines the following variables and targets: +# OPENSL_FOUND - True if OPENSL was found +# OpenSL::OpenSLES - The OpenSLES target +# + +#============================================================================= +# Copyright 2009-2011 Kitware, Inc. +# Copyright 2009-2011 Philip Lowman +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * The names of Kitware, Inc., the Insight Consortium, or the names of +# any consortium members, or of any contributors, may not be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + +find_path(OPENSL_INCLUDE_DIR NAMES SLES/OpenSLES.h + DOC "The OpenSL include directory") +find_path(OPENSL_ANDROID_INCLUDE_DIR NAMES SLES/OpenSLES_Android.h + DOC "The OpenSL Android include directory") + +find_library(OPENSL_LIBRARY NAMES OpenSLES + DOC "The OpenSL library") + +# handle the QUIETLY and REQUIRED arguments and set OPENSL_FOUND to TRUE if +# all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(OpenSL REQUIRED_VARS OPENSL_LIBRARY OPENSL_INCLUDE_DIR + OPENSL_ANDROID_INCLUDE_DIR) + +if(OPENSL_FOUND) + add_library(OpenSL::OpenSLES UNKNOWN IMPORTED) + set_target_properties(OpenSL::OpenSLES PROPERTIES + IMPORTED_LOCATION ${OPENSL_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES ${OPENSL_INCLUDE_DIR} + INTERFACE_INCLUDE_DIRECTORIES ${OPENSL_ANDROID_INCLUDE_DIR}) +endif() + +mark_as_advanced(OPENSL_INCLUDE_DIR OPENSL_ANDROID_INCLUDE_DIR OPENSL_LIBRARY) diff --git a/modules/openal-soft/cmake/FindQSA.cmake b/modules/openal-soft/cmake/FindQSA.cmake deleted file mode 100644 index 0ad1fd4..0000000 --- a/modules/openal-soft/cmake/FindQSA.cmake +++ /dev/null @@ -1,34 +0,0 @@ -# - Find QSA includes and libraries -# -# QSA_FOUND - True if QSA_INCLUDE_DIR & QSA_LIBRARY are found -# QSA_LIBRARIES - Set when QSA_LIBRARY is found -# QSA_INCLUDE_DIRS - Set when QSA_INCLUDE_DIR is found -# -# QSA_INCLUDE_DIR - where to find sys/asoundlib.h, etc. -# QSA_LIBRARY - the asound library -# - -# Only check for QSA on QNX, because it conflicts with ALSA. -if("${CMAKE_C_PLATFORM_ID}" STREQUAL "QNX") - find_path(QSA_INCLUDE_DIR - NAMES sys/asoundlib.h - DOC "The QSA include directory" - ) - - find_library(QSA_LIBRARY - NAMES asound - DOC "The QSA library" - ) -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(QSA - REQUIRED_VARS QSA_LIBRARY QSA_INCLUDE_DIR -) - -if(QSA_FOUND) - set(QSA_LIBRARIES ${QSA_LIBRARY}) - set(QSA_INCLUDE_DIRS ${QSA_INCLUDE_DIR}) -endif() - -mark_as_advanced(QSA_INCLUDE_DIR QSA_LIBRARY) diff --git a/modules/openal-soft/cmake/FindSDL_sound.cmake b/modules/openal-soft/cmake/FindSDL_sound.cmake deleted file mode 100644 index 5557b55..0000000 --- a/modules/openal-soft/cmake/FindSDL_sound.cmake +++ /dev/null @@ -1,429 +0,0 @@ -# - Locates the SDL_sound library -# -# This module depends on SDL being found and -# must be called AFTER FindSDL.cmake or FindSDL2.cmake is called. -# -# This module defines -# SDL_SOUND_INCLUDE_DIR, where to find SDL_sound.h -# SDL_SOUND_FOUND, if false, do not try to link to SDL_sound -# SDL_SOUND_LIBRARIES, this contains the list of libraries that you need -# to link against. This is a read-only variable and is marked INTERNAL. -# SDL_SOUND_EXTRAS, this is an optional variable for you to add your own -# flags to SDL_SOUND_LIBRARIES. This is prepended to SDL_SOUND_LIBRARIES. -# This is available mostly for cases this module failed to anticipate for -# and you must add additional flags. This is marked as ADVANCED. -# SDL_SOUND_VERSION_STRING, human-readable string containing the version of SDL_sound -# -# This module also defines (but you shouldn't need to use directly) -# SDL_SOUND_LIBRARY, the name of just the SDL_sound library you would link -# against. Use SDL_SOUND_LIBRARIES for you link instructions and not this one. -# And might define the following as needed -# MIKMOD_LIBRARY -# MODPLUG_LIBRARY -# OGG_LIBRARY -# VORBIS_LIBRARY -# SMPEG_LIBRARY -# FLAC_LIBRARY -# SPEEX_LIBRARY -# -# Typically, you should not use these variables directly, and you should use -# SDL_SOUND_LIBRARIES which contains SDL_SOUND_LIBRARY and the other audio libraries -# (if needed) to successfully compile on your system. -# -# Created by Eric Wing. -# This module is a bit more complicated than the other FindSDL* family modules. -# The reason is that SDL_sound can be compiled in a large variety of different ways -# which are independent of platform. SDL_sound may dynamically link against other 3rd -# party libraries to get additional codec support, such as Ogg Vorbis, SMPEG, ModPlug, -# MikMod, FLAC, Speex, and potentially others. -# Under some circumstances which I don't fully understand, -# there seems to be a requirement -# that dependent libraries of libraries you use must also be explicitly -# linked against in order to successfully compile. SDL_sound does not currently -# have any system in place to know how it was compiled. -# So this CMake module does the hard work in trying to discover which 3rd party -# libraries are required for building (if any). -# This module uses a brute force approach to create a test program that uses SDL_sound, -# and then tries to build it. If the build fails, it parses the error output for -# known symbol names to figure out which libraries are needed. -# -# Responds to the $SDLDIR and $SDLSOUNDDIR environmental variable that would -# correspond to the ./configure --prefix=$SDLDIR used in building SDL. -# -# On OSX, this will prefer the Framework version (if found) over others. -# People will have to manually change the cache values of -# SDL_LIBRARY or SDL2_LIBRARY to override this selection or set the CMake -# environment CMAKE_INCLUDE_PATH to modify the search paths. - -#============================================================================= -# Copyright 2005-2009 Kitware, Inc. -# Copyright 2012 Benjamin Eikel -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -set(SDL_SOUND_EXTRAS "" CACHE STRING "SDL_sound extra flags") -mark_as_advanced(SDL_SOUND_EXTRAS) - -# Find SDL_sound.h -find_path(SDL_SOUND_INCLUDE_DIR SDL_sound.h - HINTS - ENV SDLSOUNDDIR - ENV SDLDIR - PATH_SUFFIXES SDL SDL12 SDL11 -) - -find_library(SDL_SOUND_LIBRARY - NAMES SDL_sound - HINTS - ENV SDLSOUNDDIR - ENV SDLDIR -) - -if(SDL2_FOUND OR SDL_FOUND) - if(SDL_SOUND_INCLUDE_DIR AND SDL_SOUND_LIBRARY) - # CMake is giving me problems using TRY_COMPILE with the CMAKE_FLAGS - # for the :STRING syntax if I have multiple values contained in a - # single variable. This is a problem for the SDL2_LIBRARY variable - # because it does just that. When I feed this variable to the command, - # only the first value gets the appropriate modifier (e.g. -I) and - # the rest get dropped. - # To get multiple single variables to work, I must separate them with a "\;" - # I could go back and modify the FindSDL2.cmake module, but that's kind of painful. - # The solution would be to try something like: - # set(SDL2_TRY_COMPILE_LIBRARY_LIST "${SDL2_TRY_COMPILE_LIBRARY_LIST}\;${CMAKE_THREAD_LIBS_INIT}") - # Instead, it was suggested on the mailing list to write a temporary CMakeLists.txt - # with a temporary test project and invoke that with TRY_COMPILE. - # See message thread "Figuring out dependencies for a library in order to build" - # 2005-07-16 - # try_compile( - # MY_RESULT - # ${CMAKE_BINARY_DIR} - # ${PROJECT_SOURCE_DIR}/DetermineSoundLibs.c - # CMAKE_FLAGS - # -DINCLUDE_DIRECTORIES:STRING=${SDL2_INCLUDE_DIR}\;${SDL_SOUND_INCLUDE_DIR} - # -DLINK_LIBRARIES:STRING=${SDL_SOUND_LIBRARY}\;${SDL2_LIBRARY} - # OUTPUT_VARIABLE MY_OUTPUT - # ) - - # To minimize external dependencies, create a sdlsound test program - # which will be used to figure out if additional link dependencies are - # required for the link phase. - file(WRITE ${PROJECT_BINARY_DIR}/CMakeTmp/DetermineSoundLibs.c - "#include \"SDL_sound.h\" - #include \"SDL.h\" - int main(int argc, char* argv[]) - { - Sound_AudioInfo desired; - Sound_Sample* sample; - - SDL_Init(0); - Sound_Init(); - - /* This doesn't actually have to work, but Init() is a no-op - * for some of the decoders, so this should force more symbols - * to be pulled in. - */ - sample = Sound_NewSampleFromFile(argv[1], &desired, 4096); - - Sound_Quit(); - SDL_Quit(); - return 0; - }" - ) - - # Calling - # target_link_libraries(DetermineSoundLibs "${SDL_SOUND_LIBRARY} ${SDL2_LIBRARY}) - # causes problems when SDL2_LIBRARY looks like - # /Library/Frameworks/SDL2.framework;-framework Cocoa - # The ;-framework Cocoa seems to be confusing CMake once the OS X - # framework support was added. I was told that breaking up the list - # would fix the problem. - set(TMP_LIBS "") - if(SDL2_FOUND) - set(SDL_SOUND_LIBRARIES_TMP ${SDL_SOUND_LIBRARY} ${SDL2_LIBRARY}) - foreach(lib ${SDL_SOUND_LIBRARY} ${SDL2_LIBRARY}) - set(TMP_LIBS "${TMP_LIBS} \"${lib}\"") - endforeach() - set(TMP_INCLUDE_DIRS ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR}) - else() - set(SDL_SOUND_LIBRARIES_TMP ${SDL_SOUND_LIBRARY} ${SDL_LIBRARY}) - foreach(lib ${SDL_SOUND_LIBRARY} ${SDL_LIBRARY}) - set(TMP_LIBS "${TMP_LIBS} \"${lib}\"") - endforeach() - set(TMP_INCLUDE_DIRS ${SDL_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR}) - endif() - - # Keep trying to build a temp project until we find all missing libs. - set(TRY_AGAIN TRUE) - WHILE(TRY_AGAIN) - set(TRY_AGAIN FALSE) - # message("TMP_TRY_LIBS ${TMP_TRY_LIBS}") - - # Write the CMakeLists.txt and test project - # Weird, this is still sketchy. If I don't quote the variables - # in the TARGET_LINK_LIBRARIES, I seem to loose everything - # in the SDL2_LIBRARY string after the "-framework". - # But if I quote the stuff in INCLUDE_DIRECTORIES, it doesn't work. - file(WRITE ${PROJECT_BINARY_DIR}/CMakeTmp/CMakeLists.txt - "cmake_minimum_required(VERSION 2.8) - project(DetermineSoundLibs C) - include_directories(${TMP_INCLUDE_DIRS}) - add_executable(DetermineSoundLibs DetermineSoundLibs.c) - target_link_libraries(DetermineSoundLibs ${TMP_LIBS})" - ) - - try_compile( - MY_RESULT - ${PROJECT_BINARY_DIR}/CMakeTmp - ${PROJECT_BINARY_DIR}/CMakeTmp - DetermineSoundLibs - OUTPUT_VARIABLE MY_OUTPUT - ) - # message("${MY_RESULT}") - # message(${MY_OUTPUT}) - - if(NOT MY_RESULT) - # I expect that MPGLIB, VOC, WAV, AIFF, and SHN are compiled in statically. - # I think Timidity is also compiled in statically. - # I've never had to explcitly link against Quicktime, so I'll skip that for now. - - # Find libmath - if("${MY_OUTPUT}" MATCHES "cos@@GLIBC") - find_library(MATH_LIBRARY NAMES m) - if(MATH_LIBRARY) - set(SDL_SOUND_LIBRARIES_TMP ${SDL_SOUND_LIBRARIES_TMP} ${MATH_LIBRARY}) - set(TMP_LIBS "${SDL_SOUND_LIBRARIES_TMP} \"${MATH_LIBRARY}\"") - set(TRY_AGAIN TRUE) - endif(MATH_LIBRARY) - endif("${MY_OUTPUT}" MATCHES "cos@@GLIBC") - - # Find MikMod - if("${MY_OUTPUT}" MATCHES "MikMod_") - find_library(MIKMOD_LIBRARY - NAMES libmikmod-coreaudio mikmod - PATHS - ENV MIKMODDIR - ENV SDLSOUNDDIR - ENV SDLDIR - /sw - /opt/local - /opt/csw - /opt - PATH_SUFFIXES lib - ) - if(MIKMOD_LIBRARY) - set(SDL_SOUND_LIBRARIES_TMP ${SDL_SOUND_LIBRARIES_TMP} ${MIKMOD_LIBRARY}) - set(TMP_LIBS "${SDL_SOUND_LIBRARIES_TMP} \"${MIKMOD_LIBRARY}\"") - set(TRY_AGAIN TRUE) - endif(MIKMOD_LIBRARY) - endif("${MY_OUTPUT}" MATCHES "MikMod_") - - # Find ModPlug - if("${MY_OUTPUT}" MATCHES "MODPLUG_") - find_library(MODPLUG_LIBRARY - NAMES modplug - PATHS - ENV MODPLUGDIR - ENV SDLSOUNDDIR - ENV SDLDIR - /sw - /opt/local - /opt/csw - /opt - PATH_SUFFIXES lib - ) - if(MODPLUG_LIBRARY) - set(SDL_SOUND_LIBRARIES_TMP ${SDL_SOUND_LIBRARIES_TMP} ${MODPLUG_LIBRARY}) - set(TMP_LIBS "${SDL_SOUND_LIBRARIES_TMP} \"${MODPLUG_LIBRARY}\"") - set(TRY_AGAIN TRUE) - endif() - endif() - - # Find Ogg and Vorbis - if("${MY_OUTPUT}" MATCHES "ov_") - find_library(VORBISFILE_LIBRARY - NAMES vorbisfile VorbisFile VORBISFILE - PATHS - ENV VORBISDIR - ENV OGGDIR - ENV SDLSOUNDDIR - ENV SDLDIR - /sw - /opt/local - /opt/csw - /opt - PATH_SUFFIXES lib - ) - if(VORBISFILE_LIBRARY) - set(SDL_SOUND_LIBRARIES_TMP ${SDL_SOUND_LIBRARIES_TMP} ${VORBISFILE_LIBRARY}) - set(TMP_LIBS "${SDL_SOUND_LIBRARIES_TMP} \"${VORBISFILE_LIBRARY}\"") - set(TRY_AGAIN TRUE) - endif() - - find_library(VORBIS_LIBRARY - NAMES vorbis Vorbis VORBIS - PATHS - ENV OGGDIR - ENV VORBISDIR - ENV SDLSOUNDDIR - ENV SDLDIR - /sw - /opt/local - /opt/csw - /opt - PATH_SUFFIXES lib - ) - if(VORBIS_LIBRARY) - set(SDL_SOUND_LIBRARIES_TMP ${SDL_SOUND_LIBRARIES_TMP} ${VORBIS_LIBRARY}) - set(TMP_LIBS "${SDL_SOUND_LIBRARIES_TMP} \"${VORBIS_LIBRARY}\"") - set(TRY_AGAIN TRUE) - endif() - - find_library(OGG_LIBRARY - NAMES ogg Ogg OGG - PATHS - ENV OGGDIR - ENV VORBISDIR - ENV SDLSOUNDDIR - ENV SDLDIR - /sw - /opt/local - /opt/csw - /opt - PATH_SUFFIXES lib - ) - if(OGG_LIBRARY) - set(SDL_SOUND_LIBRARIES_TMP ${SDL_SOUND_LIBRARIES_TMP} ${OGG_LIBRARY}) - set(TMP_LIBS "${SDL_SOUND_LIBRARIES_TMP} \"${OGG_LIBRARY}\"") - set(TRY_AGAIN TRUE) - endif() - endif() - - # Find SMPEG - if("${MY_OUTPUT}" MATCHES "SMPEG_") - find_library(SMPEG_LIBRARY - NAMES smpeg SMPEG Smpeg SMpeg - PATHS - ENV SMPEGDIR - ENV SDLSOUNDDIR - ENV SDLDIR - /sw - /opt/local - /opt/csw - /opt - PATH_SUFFIXES lib - ) - if(SMPEG_LIBRARY) - set(SDL_SOUND_LIBRARIES_TMP ${SDL_SOUND_LIBRARIES_TMP} ${SMPEG_LIBRARY}) - set(TMP_LIBS "${SDL_SOUND_LIBRARIES_TMP} \"${SMPEG_LIBRARY}\"") - set(TRY_AGAIN TRUE) - endif() - endif() - - - # Find FLAC - if("${MY_OUTPUT}" MATCHES "FLAC_") - find_library(FLAC_LIBRARY - NAMES flac FLAC - PATHS - ENV FLACDIR - ENV SDLSOUNDDIR - ENV SDLDIR - /sw - /opt/local - /opt/csw - /opt - PATH_SUFFIXES lib - ) - if(FLAC_LIBRARY) - set(SDL_SOUND_LIBRARIES_TMP ${SDL_SOUND_LIBRARIES_TMP} ${FLAC_LIBRARY}) - set(TMP_LIBS "${SDL_SOUND_LIBRARIES_TMP} \"${FLAC_LIBRARY}\"") - set(TRY_AGAIN TRUE) - endif() - endif() - - - # Hmmm...Speex seems to depend on Ogg. This might be a problem if - # the TRY_COMPILE attempt gets blocked at SPEEX before it can pull - # in the Ogg symbols. I'm not sure if I should duplicate the ogg stuff - # above for here or if two ogg entries will screw up things. - if("${MY_OUTPUT}" MATCHES "speex_") - find_library(SPEEX_LIBRARY - NAMES speex SPEEX - PATHS - ENV SPEEXDIR - ENV SDLSOUNDDIR - ENV SDLDIR - /sw - /opt/local - /opt/csw - /opt - PATH_SUFFIXES lib - ) - if(SPEEX_LIBRARY) - set(SDL_SOUND_LIBRARIES_TMP ${SDL_SOUND_LIBRARIES_TMP} ${SPEEX_LIBRARY}) - set(TMP_LIBS "${SDL_SOUND_LIBRARIES_TMP} \"${SPEEX_LIBRARY}\"") - set(TRY_AGAIN TRUE) - endif() - - # Find OGG (needed for Speex) - # We might have already found Ogg for Vorbis, so skip it if so. - if(NOT OGG_LIBRARY) - find_library(OGG_LIBRARY - NAMES ogg Ogg OGG - PATHS - ENV OGGDIR - ENV VORBISDIR - ENV SPEEXDIR - ENV SDLSOUNDDIR - ENV SDLDIR - /sw - /opt/local - /opt/csw - /opt - PATH_SUFFIXES lib - ) - if(OGG_LIBRARY) - set(SDL_SOUND_LIBRARIES_TMP ${SDL_SOUND_LIBRARIES_TMP} ${OGG_LIBRARY}) - set(TMP_LIBS "${SDL_SOUND_LIBRARIES_TMP} \"${OGG_LIBRARY}\"") - set(TRY_AGAIN TRUE) - endif() - endif() - endif() - endif() - ENDWHILE() - unset(TMP_INCLUDE_DIRS) - unset(TMP_LIBS) - - set(SDL_SOUND_LIBRARIES ${SDL_SOUND_EXTRAS} ${SDL_SOUND_LIBRARIES_TMP} CACHE INTERNAL "SDL_sound and dependent libraries") - endif() -endif() - -if(SDL_SOUND_INCLUDE_DIR AND EXISTS "${SDL_SOUND_INCLUDE_DIR}/SDL_sound.h") - file(STRINGS "${SDL_SOUND_INCLUDE_DIR}/SDL_sound.h" SDL_SOUND_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SOUND_VER_MAJOR[ \t]+[0-9]+$") - file(STRINGS "${SDL_SOUND_INCLUDE_DIR}/SDL_sound.h" SDL_SOUND_VERSION_MINOR_LINE REGEX "^#define[ \t]+SOUND_VER_MINOR[ \t]+[0-9]+$") - file(STRINGS "${SDL_SOUND_INCLUDE_DIR}/SDL_sound.h" SDL_SOUND_VERSION_PATCH_LINE REGEX "^#define[ \t]+SOUND_VER_PATCH[ \t]+[0-9]+$") - string(REGEX REPLACE "^#define[ \t]+SOUND_VER_MAJOR[ \t]+([0-9]+)$" "\\1" SDL_SOUND_VERSION_MAJOR "${SDL_SOUND_VERSION_MAJOR_LINE}") - string(REGEX REPLACE "^#define[ \t]+SOUND_VER_MINOR[ \t]+([0-9]+)$" "\\1" SDL_SOUND_VERSION_MINOR "${SDL_SOUND_VERSION_MINOR_LINE}") - string(REGEX REPLACE "^#define[ \t]+SOUND_VER_PATCH[ \t]+([0-9]+)$" "\\1" SDL_SOUND_VERSION_PATCH "${SDL_SOUND_VERSION_PATCH_LINE}") - set(SDL_SOUND_VERSION_STRING ${SDL_SOUND_VERSION_MAJOR}.${SDL_SOUND_VERSION_MINOR}.${SDL_SOUND_VERSION_PATCH}) - unset(SDL_SOUND_VERSION_MAJOR_LINE) - unset(SDL_SOUND_VERSION_MINOR_LINE) - unset(SDL_SOUND_VERSION_PATCH_LINE) - unset(SDL_SOUND_VERSION_MAJOR) - unset(SDL_SOUND_VERSION_MINOR) - unset(SDL_SOUND_VERSION_PATCH) -endif() - -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL_sound - REQUIRED_VARS SDL_SOUND_LIBRARIES SDL_SOUND_INCLUDE_DIR - VERSION_VAR SDL_SOUND_VERSION_STRING) diff --git a/modules/openal-soft/cmake/FindSndFile.cmake b/modules/openal-soft/cmake/FindSndFile.cmake new file mode 100644 index 0000000..b931d3c --- /dev/null +++ b/modules/openal-soft/cmake/FindSndFile.cmake @@ -0,0 +1,25 @@ +# - Try to find SndFile +# Once done this will define +# +# SNDFILE_FOUND - system has SndFile +# SndFile::SndFile - the SndFile target +# + +find_path(SNDFILE_INCLUDE_DIR NAMES sndfile.h) + +find_library(SNDFILE_LIBRARY NAMES sndfile sndfile-1) + +# handle the QUIETLY and REQUIRED arguments and set SNDFILE_FOUND to TRUE if +# all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SndFile DEFAULT_MSG SNDFILE_LIBRARY SNDFILE_INCLUDE_DIR) + +if(SNDFILE_FOUND) + add_library(SndFile::SndFile UNKNOWN IMPORTED) + set_target_properties(SndFile::SndFile PROPERTIES + IMPORTED_LOCATION ${SNDFILE_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES ${SNDFILE_INCLUDE_DIR}) +endif() + +# show the SNDFILE_INCLUDE_DIR and SNDFILE_LIBRARY variables only in the advanced view +mark_as_advanced(SNDFILE_INCLUDE_DIR SNDFILE_LIBRARY) diff --git a/modules/openal-soft/cmake/FindWindowsSDK.cmake b/modules/openal-soft/cmake/FindWindowsSDK.cmake deleted file mode 100644 index 007c9b1..0000000 --- a/modules/openal-soft/cmake/FindWindowsSDK.cmake +++ /dev/null @@ -1,630 +0,0 @@ -# - Find the Windows SDK aka Platform SDK -# -# Relevant Wikipedia article: http://en.wikipedia.org/wiki/Microsoft_Windows_SDK -# -# Pass "COMPONENTS tools" to ignore Visual Studio version checks: in case -# you just want the tool binaries to run, rather than the libraries and headers -# for compiling. -# -# Variables: -# WINDOWSSDK_FOUND - if any version of the windows or platform SDK was found that is usable with the current version of visual studio -# WINDOWSSDK_LATEST_DIR -# WINDOWSSDK_LATEST_NAME -# WINDOWSSDK_FOUND_PREFERENCE - if we found an entry indicating a "preferred" SDK listed for this visual studio version -# WINDOWSSDK_PREFERRED_DIR -# WINDOWSSDK_PREFERRED_NAME -# -# WINDOWSSDK_DIRS - contains no duplicates, ordered most recent first. -# WINDOWSSDK_PREFERRED_FIRST_DIRS - contains no duplicates, ordered with preferred first, followed by the rest in descending recency -# -# Functions: -# windowssdk_name_lookup( ) - Find the name corresponding with the SDK directory you pass in, or -# NOTFOUND if not recognized. Your directory must be one of WINDOWSSDK_DIRS for this to work. -# -# windowssdk_build_lookup( ) - Find the build version number corresponding with the SDK directory you pass in, or -# NOTFOUND if not recognized. Your directory must be one of WINDOWSSDK_DIRS for this to work. -# -# get_windowssdk_from_component( ) - Given a library or include dir, -# find the Windows SDK root dir corresponding to it, or NOTFOUND if unrecognized. -# -# get_windowssdk_library_dirs( ) - Find the architecture-appropriate -# library directories corresponding to the SDK directory you pass in (or NOTFOUND if none) -# -# get_windowssdk_library_dirs_multiple( ...) - Find the architecture-appropriate -# library directories corresponding to the SDK directories you pass in, in order, skipping those not found. NOTFOUND if none at all. -# Good for passing WINDOWSSDK_DIRS or WINDOWSSDK_DIRS to if you really just want a file and don't care where from. -# -# get_windowssdk_include_dirs( ) - Find the -# include directories corresponding to the SDK directory you pass in (or NOTFOUND if none) -# -# get_windowssdk_include_dirs_multiple( ...) - Find the -# include directories corresponding to the SDK directories you pass in, in order, skipping those not found. NOTFOUND if none at all. -# Good for passing WINDOWSSDK_DIRS or WINDOWSSDK_DIRS to if you really just want a file and don't care where from. -# -# Requires these CMake modules: -# FindPackageHandleStandardArgs (known included with CMake >=2.6.2) -# -# Original Author: -# 2012 Ryan Pavlik -# http://academic.cleardefinition.com -# Iowa State University HCI Graduate Program/VRAC -# -# Copyright Iowa State University 2012. -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_1_0.txt or copy at -# http://www.boost.org/LICENSE_1_0.txt) - -set(_preferred_sdk_dirs) # pre-output -set(_win_sdk_dirs) # pre-output -set(_win_sdk_versanddirs) # pre-output -set(_win_sdk_buildsanddirs) # pre-output -set(_winsdk_vistaonly) # search parameters -set(_winsdk_kits) # search parameters - - -set(_WINDOWSSDK_ANNOUNCE OFF) -if(NOT WINDOWSSDK_FOUND AND (NOT WindowsSDK_FIND_QUIETLY)) - set(_WINDOWSSDK_ANNOUNCE ON) -endif() -macro(_winsdk_announce) - if(_WINSDK_ANNOUNCE) - message(STATUS ${ARGN}) - endif() -endmacro() - -set(_winsdk_win10vers - 10.0.17763.0 # Windows 10 Version 1809 - 10.0.17134.0 # Windows 10 Version 1803 (April 2018 Update) - 10.0.16299.0 # Windows 10 Version 1709 (Fall Creators Update) - 10.0.15063.0 # Windows 10 Version 1703 (Creators Update) - 10.0.14393.0 # Redstone aka Win10 1607 "Anniversary Update" - 10.0.10586.0 # TH2 aka Win10 1511 - 10.0.10240.0 # Win10 RTM - 10.0.10150.0 # just ucrt - 10.0.10056.0 -) - -if(WindowsSDK_FIND_COMPONENTS MATCHES "tools") - set(_WINDOWSSDK_IGNOREMSVC ON) - _winsdk_announce("Checking for tools from Windows/Platform SDKs...") -else() - set(_WINDOWSSDK_IGNOREMSVC OFF) - _winsdk_announce("Checking for Windows/Platform SDKs...") -endif() - -# Appends to the three main pre-output lists used only if the path exists -# and is not already in the list. -function(_winsdk_conditional_append _vername _build _path) - if(("${_path}" MATCHES "registry") OR (NOT EXISTS "${_path}")) - # Path invalid - do not add - return() - endif() - list(FIND _win_sdk_dirs "${_path}" _win_sdk_idx) - if(_win_sdk_idx GREATER -1) - # Path already in list - do not add - return() - endif() - _winsdk_announce( " - ${_vername}, Build ${_build} @ ${_path}") - # Not yet in the list, so we'll add it - list(APPEND _win_sdk_dirs "${_path}") - set(_win_sdk_dirs "${_win_sdk_dirs}" CACHE INTERNAL "" FORCE) - list(APPEND - _win_sdk_versanddirs - "${_vername}" - "${_path}") - set(_win_sdk_versanddirs "${_win_sdk_versanddirs}" CACHE INTERNAL "" FORCE) - list(APPEND - _win_sdk_buildsanddirs - "${_build}" - "${_path}") - set(_win_sdk_buildsanddirs "${_win_sdk_buildsanddirs}" CACHE INTERNAL "" FORCE) -endfunction() - -# Appends to the "preferred SDK" lists only if the path exists -function(_winsdk_conditional_append_preferred _info _path) - if(("${_path}" MATCHES "registry") OR (NOT EXISTS "${_path}")) - # Path invalid - do not add - return() - endif() - - get_filename_component(_path "${_path}" ABSOLUTE) - - list(FIND _win_sdk_preferred_sdk_dirs "${_path}" _win_sdk_idx) - if(_win_sdk_idx GREATER -1) - # Path already in list - do not add - return() - endif() - _winsdk_announce( " - Found \"preferred\" SDK ${_info} @ ${_path}") - # Not yet in the list, so we'll add it - list(APPEND _win_sdk_preferred_sdk_dirs "${_path}") - set(_win_sdk_preferred_sdk_dirs "${_win_sdk_dirs}" CACHE INTERNAL "" FORCE) - - # Just in case we somehow missed it: - _winsdk_conditional_append("${_info}" "" "${_path}") -endfunction() - -# Given a version like v7.0A, looks for an SDK in the registry under "Microsoft SDKs". -# If the given version might be in both HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows -# and HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots aka "Windows Kits", -# use this macro first, since these registry keys usually have more information. -# -# Pass a "default" build number as an extra argument in case we can't find it. -function(_winsdk_check_microsoft_sdks_registry _winsdkver) - set(SDKKEY "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\${_winsdkver}") - get_filename_component(_sdkdir - "[${SDKKEY};InstallationFolder]" - ABSOLUTE) - - set(_sdkname "Windows SDK ${_winsdkver}") - - # Default build number passed as extra argument - set(_build ${ARGN}) - # See if the registry holds a Microsoft-mutilated, err, designated, product name - # (just using get_filename_component to execute the registry lookup) - get_filename_component(_sdkproductname - "[${SDKKEY};ProductName]" - NAME) - if(NOT "${_sdkproductname}" MATCHES "registry") - # Got a product name - set(_sdkname "${_sdkname} (${_sdkproductname})") - endif() - - # try for a version to augment our name - # (just using get_filename_component to execute the registry lookup) - get_filename_component(_sdkver - "[${SDKKEY};ProductVersion]" - NAME) - if(NOT "${_sdkver}" MATCHES "registry" AND NOT MATCHES) - # Got a version - if(NOT "${_sdkver}" MATCHES "\\.\\.") - # and it's not an invalid one with two dots in it: - # use to override the default build - set(_build ${_sdkver}) - if(NOT "${_sdkname}" MATCHES "${_sdkver}") - # Got a version that's not already in the name, let's use it to improve our name. - set(_sdkname "${_sdkname} (${_sdkver})") - endif() - endif() - endif() - _winsdk_conditional_append("${_sdkname}" "${_build}" "${_sdkdir}") -endfunction() - -# Given a name for identification purposes, the build number, and a key (technically a "value name") -# corresponding to a Windows SDK packaged as a "Windows Kit", look for it -# in HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots -# Note that the key or "value name" tends to be something weird like KitsRoot81 - -# no easy way to predict, just have to observe them in the wild. -# Doesn't hurt to also try _winsdk_check_microsoft_sdks_registry for these: -# sometimes you get keys in both parts of the registry (in the wow64 portion especially), -# and the non-"Windows Kits" location is often more descriptive. -function(_winsdk_check_windows_kits_registry _winkit_name _winkit_build _winkit_key) - get_filename_component(_sdkdir - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots;${_winkit_key}]" - ABSOLUTE) - _winsdk_conditional_append("${_winkit_name}" "${_winkit_build}" "${_sdkdir}") -endfunction() - -# Given a name for identification purposes and the build number -# corresponding to a Windows 10 SDK packaged as a "Windows Kit", look for it -# in HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots -# Doesn't hurt to also try _winsdk_check_microsoft_sdks_registry for these: -# sometimes you get keys in both parts of the registry (in the wow64 portion especially), -# and the non-"Windows Kits" location is often more descriptive. -function(_winsdk_check_win10_kits _winkit_build) - get_filename_component(_sdkdir - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots;KitsRoot10]" - ABSOLUTE) - if(("${_sdkdir}" MATCHES "registry") OR (NOT EXISTS "${_sdkdir}")) - return() # not found - endif() - if(EXISTS "${_sdkdir}/Include/${_winkit_build}/um") - _winsdk_conditional_append("Windows Kits 10 (Build ${_winkit_build})" "${_winkit_build}" "${_sdkdir}") - endif() -endfunction() - -# Given a name for indentification purposes, the build number, and the associated package GUID, -# look in the registry under both HKLM and HKCU in \\SOFTWARE\\Microsoft\\MicrosoftSDK\\InstalledSDKs\\ -# for that guid and the SDK it points to. -function(_winsdk_check_platformsdk_registry _platformsdkname _build _platformsdkguid) - foreach(_winsdk_hive HKEY_LOCAL_MACHINE HKEY_CURRENT_USER) - get_filename_component(_sdkdir - "[${_winsdk_hive}\\SOFTWARE\\Microsoft\\MicrosoftSDK\\InstalledSDKs\\${_platformsdkguid};Install Dir]" - ABSOLUTE) - _winsdk_conditional_append("${_platformsdkname} (${_build})" "${_build}" "${_sdkdir}") - endforeach() -endfunction() - -### -# Detect toolchain information: to know whether it's OK to use Vista+ only SDKs -### -set(_winsdk_vistaonly_ok OFF) -if(MSVC AND NOT _WINDOWSSDK_IGNOREMSVC) - # VC 10 and older has broad target support - if(MSVC_VERSION LESS 1700) - # VC 11 by default targets Vista and later only, so we can add a few more SDKs that (might?) only work on vista+ - elseif("${CMAKE_VS_PLATFORM_TOOLSET}" MATCHES "_xp") - # This is the XP-compatible v110+ toolset - elseif("${CMAKE_VS_PLATFORM_TOOLSET}" STREQUAL "v100" OR "${CMAKE_VS_PLATFORM_TOOLSET}" STREQUAL "v90") - # This is the VS2010/VS2008 toolset - else() - # OK, we're VC11 or newer and not using a backlevel or XP-compatible toolset. - # These versions have no XP (and possibly Vista pre-SP1) support - set(_winsdk_vistaonly_ok ON) - if(_WINDOWSSDK_ANNOUNCE AND NOT _WINDOWSSDK_VISTAONLY_PESTERED) - set(_WINDOWSSDK_VISTAONLY_PESTERED ON CACHE INTERNAL "" FORCE) - message(STATUS "FindWindowsSDK: Detected Visual Studio 2012 or newer, not using the _xp toolset variant: including SDK versions that drop XP support in search!") - endif() - endif() -endif() -if(_WINDOWSSDK_IGNOREMSVC) - set(_winsdk_vistaonly_ok ON) -endif() - -### -# MSVC version checks - keeps messy conditionals in one place -# (messy because of _WINDOWSSDK_IGNOREMSVC) -### -set(_winsdk_msvc_greater_1200 OFF) -if(_WINDOWSSDK_IGNOREMSVC OR (MSVC AND (MSVC_VERSION GREATER 1200))) - set(_winsdk_msvc_greater_1200 ON) -endif() -# Newer than VS .NET/VS Toolkit 2003 -set(_winsdk_msvc_greater_1310 OFF) -if(_WINDOWSSDK_IGNOREMSVC OR (MSVC AND (MSVC_VERSION GREATER 1310))) - set(_winsdk_msvc_greater_1310 ON) -endif() - -# VS2005/2008 -set(_winsdk_msvc_less_1600 OFF) -if(_WINDOWSSDK_IGNOREMSVC OR (MSVC AND (MSVC_VERSION LESS 1600))) - set(_winsdk_msvc_less_1600 ON) -endif() - -# VS2013+ -set(_winsdk_msvc_not_less_1800 OFF) -if(_WINDOWSSDK_IGNOREMSVC OR (MSVC AND (NOT MSVC_VERSION LESS 1800))) - set(_winsdk_msvc_not_less_1800 ON) -endif() - -### -# START body of find module -### -if(_winsdk_msvc_greater_1310) # Newer than VS .NET/VS Toolkit 2003 - ### - # Look for "preferred" SDKs - ### - - # Environment variable for SDK dir - if(EXISTS "$ENV{WindowsSDKDir}" AND (NOT "$ENV{WindowsSDKDir}" STREQUAL "")) - _winsdk_conditional_append_preferred("WindowsSDKDir environment variable" "$ENV{WindowsSDKDir}") - endif() - - if(_winsdk_msvc_less_1600) - # Per-user current Windows SDK for VS2005/2008 - get_filename_component(_sdkdir - "[HKEY_CURRENT_USER\\Software\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]" - ABSOLUTE) - _winsdk_conditional_append_preferred("Per-user current Windows SDK" "${_sdkdir}") - - # System-wide current Windows SDK for VS2005/2008 - get_filename_component(_sdkdir - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]" - ABSOLUTE) - _winsdk_conditional_append_preferred("System-wide current Windows SDK" "${_sdkdir}") - endif() - - ### - # Begin the massive list of SDK searching! - ### - if(_winsdk_vistaonly_ok AND _winsdk_msvc_not_less_1800) - # These require at least Visual Studio 2013 (VC12) - - _winsdk_check_microsoft_sdks_registry(v10.0A) - - # Windows Software Development Kit (SDK) for Windows 10 - # Several different versions living in the same directory - if nothing else we can assume RTM (10240) - _winsdk_check_microsoft_sdks_registry(v10.0 10.0.10240.0) - foreach(_win10build ${_winsdk_win10vers}) - _winsdk_check_win10_kits(${_win10build}) - endforeach() - endif() # vista-only and 2013+ - - # Included in Visual Studio 2013 - # Includes the v120_xp toolset - _winsdk_check_microsoft_sdks_registry(v8.1A 8.1.51636) - - if(_winsdk_vistaonly_ok AND _winsdk_msvc_not_less_1800) - # Windows Software Development Kit (SDK) for Windows 8.1 - # http://msdn.microsoft.com/en-gb/windows/desktop/bg162891 - _winsdk_check_microsoft_sdks_registry(v8.1 8.1.25984.0) - _winsdk_check_windows_kits_registry("Windows Kits 8.1" 8.1.25984.0 KitsRoot81) - endif() # vista-only and 2013+ - - if(_winsdk_vistaonly_ok) - # Included in Visual Studio 2012 - _winsdk_check_microsoft_sdks_registry(v8.0A 8.0.50727) - - # Microsoft Windows SDK for Windows 8 and .NET Framework 4.5 - # This is the first version to also include the DirectX SDK - # http://msdn.microsoft.com/en-US/windows/desktop/hh852363.aspx - _winsdk_check_microsoft_sdks_registry(v8.0 6.2.9200.16384) - _winsdk_check_windows_kits_registry("Windows Kits 8.0" 6.2.9200.16384 KitsRoot) - endif() # vista-only - - # Included with VS 2012 Update 1 or later - # Introduces v110_xp toolset - _winsdk_check_microsoft_sdks_registry(v7.1A 7.1.51106) - if(_winsdk_vistaonly_ok) - # Microsoft Windows SDK for Windows 7 and .NET Framework 4 - # http://www.microsoft.com/downloads/en/details.aspx?FamilyID=6b6c21d2-2006-4afa-9702-529fa782d63b - _winsdk_check_microsoft_sdks_registry(v7.1 7.1.7600.0.30514) - endif() # vista-only - - # Included with VS 2010 - _winsdk_check_microsoft_sdks_registry(v7.0A 6.1.7600.16385) - - # Windows SDK for Windows 7 and .NET Framework 3.5 SP1 - # Works with VC9 - # http://www.microsoft.com/en-us/download/details.aspx?id=18950 - _winsdk_check_microsoft_sdks_registry(v7.0 6.1.7600.16385) - - # Two versions call themselves "v6.1": - # Older: - # Windows Vista Update & .NET 3.0 SDK - # http://www.microsoft.com/en-us/download/details.aspx?id=14477 - - # Newer: - # Windows Server 2008 & .NET 3.5 SDK - # may have broken VS9SP1? they recommend v7.0 instead, or a KB... - # http://www.microsoft.com/en-us/download/details.aspx?id=24826 - _winsdk_check_microsoft_sdks_registry(v6.1 6.1.6000.16384.10) - - # Included in VS 2008 - _winsdk_check_microsoft_sdks_registry(v6.0A 6.1.6723.1) - - # Microsoft Windows Software Development Kit for Windows Vista and .NET Framework 3.0 Runtime Components - # http://blogs.msdn.com/b/stanley/archive/2006/11/08/microsoft-windows-software-development-kit-for-windows-vista-and-net-framework-3-0-runtime-components.aspx - _winsdk_check_microsoft_sdks_registry(v6.0 6.0.6000.16384) -endif() - -# Let's not forget the Platform SDKs, which sometimes are useful! -if(_winsdk_msvc_greater_1200) - _winsdk_check_platformsdk_registry("Microsoft Platform SDK for Windows Server 2003 R2" "5.2.3790.2075.51" "D2FF9F89-8AA2-4373-8A31-C838BF4DBBE1") - _winsdk_check_platformsdk_registry("Microsoft Platform SDK for Windows Server 2003 SP1" "5.2.3790.1830.15" "8F9E5EF3-A9A5-491B-A889-C58EFFECE8B3") -endif() -### -# Finally, look for "preferred" SDKs -### -if(_winsdk_msvc_greater_1310) # Newer than VS .NET/VS Toolkit 2003 - - - # Environment variable for SDK dir - if(EXISTS "$ENV{WindowsSDKDir}" AND (NOT "$ENV{WindowsSDKDir}" STREQUAL "")) - _winsdk_conditional_append_preferred("WindowsSDKDir environment variable" "$ENV{WindowsSDKDir}") - endif() - - if(_winsdk_msvc_less_1600) - # Per-user current Windows SDK for VS2005/2008 - get_filename_component(_sdkdir - "[HKEY_CURRENT_USER\\Software\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]" - ABSOLUTE) - _winsdk_conditional_append_preferred("Per-user current Windows SDK" "${_sdkdir}") - - # System-wide current Windows SDK for VS2005/2008 - get_filename_component(_sdkdir - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]" - ABSOLUTE) - _winsdk_conditional_append_preferred("System-wide current Windows SDK" "${_sdkdir}") - endif() -endif() - - -function(windowssdk_name_lookup _dir _outvar) - list(FIND _win_sdk_versanddirs "${_dir}" _diridx) - math(EXPR _idx "${_diridx} - 1") - if(${_idx} GREATER -1) - list(GET _win_sdk_versanddirs ${_idx} _ret) - else() - set(_ret "NOTFOUND") - endif() - set(${_outvar} "${_ret}" PARENT_SCOPE) -endfunction() - -function(windowssdk_build_lookup _dir _outvar) - list(FIND _win_sdk_buildsanddirs "${_dir}" _diridx) - math(EXPR _idx "${_diridx} - 1") - if(${_idx} GREATER -1) - list(GET _win_sdk_buildsanddirs ${_idx} _ret) - else() - set(_ret "NOTFOUND") - endif() - set(${_outvar} "${_ret}" PARENT_SCOPE) -endfunction() - -# If we found something... -if(_win_sdk_dirs) - list(GET _win_sdk_dirs 0 WINDOWSSDK_LATEST_DIR) - windowssdk_name_lookup("${WINDOWSSDK_LATEST_DIR}" - WINDOWSSDK_LATEST_NAME) - set(WINDOWSSDK_DIRS ${_win_sdk_dirs}) - - # Fallback, in case no preference found. - set(WINDOWSSDK_PREFERRED_DIR "${WINDOWSSDK_LATEST_DIR}") - set(WINDOWSSDK_PREFERRED_NAME "${WINDOWSSDK_LATEST_NAME}") - set(WINDOWSSDK_PREFERRED_FIRST_DIRS ${WINDOWSSDK_DIRS}) - set(WINDOWSSDK_FOUND_PREFERENCE OFF) -endif() - -# If we found indications of a user preference... -if(_win_sdk_preferred_sdk_dirs) - list(GET _win_sdk_preferred_sdk_dirs 0 WINDOWSSDK_PREFERRED_DIR) - windowssdk_name_lookup("${WINDOWSSDK_PREFERRED_DIR}" - WINDOWSSDK_PREFERRED_NAME) - set(WINDOWSSDK_PREFERRED_FIRST_DIRS - ${_win_sdk_preferred_sdk_dirs} - ${_win_sdk_dirs}) - list(REMOVE_DUPLICATES WINDOWSSDK_PREFERRED_FIRST_DIRS) - set(WINDOWSSDK_FOUND_PREFERENCE ON) -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(WindowsSDK - "No compatible version of the Windows SDK or Platform SDK found." - WINDOWSSDK_DIRS) - -if(WINDOWSSDK_FOUND) - # Internal: Architecture-appropriate library directory names. - if("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "ARM") - if(CMAKE_SIZEOF_VOID_P MATCHES "8") - # Only supported in Win10 SDK and up. - set(_winsdk_arch8 arm64) # what the WDK for Win8+ calls this architecture - else() - set(_winsdk_archbare /arm) # what the architecture used to be called in oldest SDKs - set(_winsdk_arch arm) # what the architecture used to be called - set(_winsdk_arch8 arm) # what the WDK for Win8+ calls this architecture - endif() - else() - if(CMAKE_SIZEOF_VOID_P MATCHES "8") - set(_winsdk_archbare /x64) # what the architecture used to be called in oldest SDKs - set(_winsdk_arch amd64) # what the architecture used to be called - set(_winsdk_arch8 x64) # what the WDK for Win8+ calls this architecture - else() - set(_winsdk_archbare ) # what the architecture used to be called in oldest SDKs - set(_winsdk_arch i386) # what the architecture used to be called - set(_winsdk_arch8 x86) # what the WDK for Win8+ calls this architecture - endif() - endif() - - function(get_windowssdk_from_component _component _var) - get_filename_component(_component "${_component}" ABSOLUTE) - file(TO_CMAKE_PATH "${_component}" _component) - foreach(_sdkdir ${WINDOWSSDK_DIRS}) - get_filename_component(_sdkdir "${_sdkdir}" ABSOLUTE) - string(LENGTH "${_sdkdir}" _sdklen) - file(RELATIVE_PATH _rel "${_sdkdir}" "${_component}") - # If we don't have any "parent directory" items... - if(NOT "${_rel}" MATCHES "[.][.]") - set(${_var} "${_sdkdir}" PARENT_SCOPE) - return() - endif() - endforeach() - # Fail. - set(${_var} "NOTFOUND" PARENT_SCOPE) - endfunction() - function(get_windowssdk_library_dirs _winsdk_dir _var) - set(_dirs) - set(_suffixes - "lib${_winsdk_archbare}" # SDKs like 7.1A - "lib/${_winsdk_arch}" # just because some SDKs have x86 dir and root dir - "lib/w2k/${_winsdk_arch}" # Win2k min requirement - "lib/wxp/${_winsdk_arch}" # WinXP min requirement - "lib/wnet/${_winsdk_arch}" # Win Server 2003 min requirement - "lib/wlh/${_winsdk_arch}" - "lib/wlh/um/${_winsdk_arch8}" # Win Vista ("Long Horn") min requirement - "lib/win7/${_winsdk_arch}" - "lib/win7/um/${_winsdk_arch8}" # Win 7 min requirement - ) - foreach(_ver - wlh # Win Vista ("Long Horn") min requirement - win7 # Win 7 min requirement - win8 # Win 8 min requirement - winv6.3 # Win 8.1 min requirement - ) - - list(APPEND _suffixes - "lib/${_ver}/${_winsdk_arch}" - "lib/${_ver}/um/${_winsdk_arch8}" - "lib/${_ver}/km/${_winsdk_arch8}" - ) - endforeach() - - # Look for WDF libraries in Win10+ SDK - foreach(_mode umdf kmdf) - file(GLOB _wdfdirs RELATIVE "${_winsdk_dir}" "${_winsdk_dir}/lib/wdf/${_mode}/${_winsdk_arch8}/*") - if(_wdfdirs) - list(APPEND _suffixes ${_wdfdirs}) - endif() - endforeach() - - # Look in each Win10+ SDK version for the components - foreach(_win10ver ${_winsdk_win10vers}) - foreach(_component um km ucrt mmos) - list(APPEND _suffixes "lib/${_win10ver}/${_component}/${_winsdk_arch8}") - endforeach() - endforeach() - - foreach(_suffix ${_suffixes}) - # Check to see if a library actually exists here. - file(GLOB _libs "${_winsdk_dir}/${_suffix}/*.lib") - if(_libs) - list(APPEND _dirs "${_winsdk_dir}/${_suffix}") - endif() - endforeach() - if("${_dirs}" STREQUAL "") - set(_dirs NOTFOUND) - else() - list(REMOVE_DUPLICATES _dirs) - endif() - set(${_var} ${_dirs} PARENT_SCOPE) - endfunction() - function(get_windowssdk_include_dirs _winsdk_dir _var) - set(_dirs) - - set(_subdirs shared um winrt km wdf mmos ucrt) - set(_suffixes Include) - - foreach(_dir ${_subdirs}) - list(APPEND _suffixes "Include/${_dir}") - endforeach() - - foreach(_ver ${_winsdk_win10vers}) - foreach(_dir ${_subdirs}) - list(APPEND _suffixes "Include/${_ver}/${_dir}") - endforeach() - endforeach() - - foreach(_suffix ${_suffixes}) - # Check to see if a header file actually exists here. - file(GLOB _headers "${_winsdk_dir}/${_suffix}/*.h") - if(_headers) - list(APPEND _dirs "${_winsdk_dir}/${_suffix}") - endif() - endforeach() - if("${_dirs}" STREQUAL "") - set(_dirs NOTFOUND) - else() - list(REMOVE_DUPLICATES _dirs) - endif() - set(${_var} ${_dirs} PARENT_SCOPE) - endfunction() - function(get_windowssdk_library_dirs_multiple _var) - set(_dirs) - foreach(_sdkdir ${ARGN}) - get_windowssdk_library_dirs("${_sdkdir}" _current_sdk_libdirs) - if(_current_sdk_libdirs) - list(APPEND _dirs ${_current_sdk_libdirs}) - endif() - endforeach() - if("${_dirs}" STREQUAL "") - set(_dirs NOTFOUND) - else() - list(REMOVE_DUPLICATES _dirs) - endif() - set(${_var} ${_dirs} PARENT_SCOPE) - endfunction() - function(get_windowssdk_include_dirs_multiple _var) - set(_dirs) - foreach(_sdkdir ${ARGN}) - get_windowssdk_include_dirs("${_sdkdir}" _current_sdk_incdirs) - if(_current_sdk_libdirs) - list(APPEND _dirs ${_current_sdk_incdirs}) - endif() - endforeach() - if("${_dirs}" STREQUAL "") - set(_dirs NOTFOUND) - else() - list(REMOVE_DUPLICATES _dirs) - endif() - set(${_var} ${_dirs} PARENT_SCOPE) - endfunction() -endif() diff --git a/modules/openal-soft/cmake/bin2h.script.cmake b/modules/openal-soft/cmake/bin2h.script.cmake new file mode 100644 index 0000000..1438fde --- /dev/null +++ b/modules/openal-soft/cmake/bin2h.script.cmake @@ -0,0 +1,13 @@ +# Read the input file into 'indata', converting each byte to a pair of hex +# characters +file(READ "${INPUT_FILE}" indata HEX) + +# For each pair of characters, indent them and prepend the 0x prefix, and +# append a comma separateor. +# TODO: Prettify this. Should group a number of bytes per line instead of one +# per line. +string(REGEX REPLACE "(..)" " 0x\\1,\n" output "${indata}") + +# Write the list of hex chars to the output file in a const byte array +file(WRITE "${OUTPUT_FILE}" + "const unsigned char ${VARIABLE_NAME}[] = {\n${output}};\n") diff --git a/modules/openal-soft/common/albit.h b/modules/openal-soft/common/albit.h new file mode 100644 index 0000000..ad59620 --- /dev/null +++ b/modules/openal-soft/common/albit.h @@ -0,0 +1,155 @@ +#ifndef AL_BIT_H +#define AL_BIT_H + +#include +#include +#include +#if !defined(__GNUC__) && (defined(_WIN32) || defined(_WIN64)) +#include +#endif + +namespace al { + +#ifdef __BYTE_ORDER__ +enum class endian { + little = __ORDER_LITTLE_ENDIAN__, + big = __ORDER_BIG_ENDIAN__, + native = __BYTE_ORDER__ +}; + +#else + +/* This doesn't support mixed-endian. */ +namespace detail_ { +constexpr bool IsLittleEndian() noexcept +{ + static_assert(sizeof(char) < sizeof(int), "char is too big"); + + constexpr int test_val{1}; + return static_cast(test_val) ? true : false; +} +} // namespace detail_ + +enum class endian { + big = 0, + little = 1, + native = detail_::IsLittleEndian() ? little : big +}; +#endif + + +/* Define popcount (population count/count 1 bits) and countr_zero (count + * trailing zero bits, starting from the lsb) methods, for various integer + * types. + */ +#ifdef __GNUC__ + +namespace detail_ { + inline int popcount(unsigned long long val) noexcept { return __builtin_popcountll(val); } + inline int popcount(unsigned long val) noexcept { return __builtin_popcountl(val); } + inline int popcount(unsigned int val) noexcept { return __builtin_popcount(val); } + + inline int countr_zero(unsigned long long val) noexcept { return __builtin_ctzll(val); } + inline int countr_zero(unsigned long val) noexcept { return __builtin_ctzl(val); } + inline int countr_zero(unsigned int val) noexcept { return __builtin_ctz(val); } +} // namespace detail_ + +template +inline std::enable_if_t::value && std::is_unsigned::value, +int> popcount(T v) noexcept { return detail_::popcount(v); } + +template +inline std::enable_if_t::value && std::is_unsigned::value, +int> countr_zero(T val) noexcept +{ return val ? detail_::countr_zero(val) : std::numeric_limits::digits; } + +#else + +/* There be black magics here. The popcount method is derived from + * https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + * while the ctz-utilizing-popcount algorithm is shown here + * http://www.hackersdelight.org/hdcodetxt/ntz.c.txt + * as the ntz2 variant. These likely aren't the most efficient methods, but + * they're good enough if the GCC built-ins aren't available. + */ +namespace detail_ { + template::digits> + struct fast_utype { }; + template + struct fast_utype { using type = std::uint_fast8_t; }; + template + struct fast_utype { using type = std::uint_fast16_t; }; + template + struct fast_utype { using type = std::uint_fast32_t; }; + template + struct fast_utype { using type = std::uint_fast64_t; }; + + template + constexpr T repbits(unsigned char bits) noexcept + { + T ret{bits}; + for(size_t i{1};i < sizeof(T);++i) + ret = (ret<<8) | bits; + return ret; + } +} // namespace detail_ + +template +constexpr std::enable_if_t::value && std::is_unsigned::value, +int> popcount(T val) noexcept +{ + using fast_type = typename detail_::fast_utype::type; + constexpr fast_type b01010101{detail_::repbits(0x55)}; + constexpr fast_type b00110011{detail_::repbits(0x33)}; + constexpr fast_type b00001111{detail_::repbits(0x0f)}; + constexpr fast_type b00000001{detail_::repbits(0x01)}; + + fast_type v{fast_type{val} - ((fast_type{val} >> 1) & b01010101)}; + v = (v & b00110011) + ((v >> 2) & b00110011); + v = (v + (v >> 4)) & b00001111; + return static_cast(((v * b00000001) >> ((sizeof(T)-1)*8)) & 0xff); +} + +#ifdef _WIN32 + +template +inline std::enable_if_t::value && std::is_unsigned::value + && std::numeric_limits::digits <= 32, +int> countr_zero(T v) +{ + unsigned long idx{std::numeric_limits::digits}; + _BitScanForward(&idx, static_cast(v)); + return static_cast(idx); +} + +template +inline std::enable_if_t::value && std::is_unsigned::value + && 32 < std::numeric_limits::digits && std::numeric_limits::digits <= 64, +int> countr_zero(T v) +{ + unsigned long idx{std::numeric_limits::digits}; +#ifdef _WIN64 + _BitScanForward64(&idx, v); +#else + if(!_BitScanForward(&idx, static_cast(v))) + { + if(_BitScanForward(&idx, static_cast(v>>32))) + idx += 32; + } +#endif /* _WIN64 */ + return static_cast(idx); +} + +#else + +template +constexpr std::enable_if_t::value && std::is_unsigned::value, +int> countr_zero(T value) +{ return popcount(static_cast(~value & (value - 1))); } + +#endif +#endif + +} // namespace al + +#endif /* AL_BIT_H */ diff --git a/modules/openal-soft/common/albyte.h b/modules/openal-soft/common/albyte.h new file mode 100644 index 0000000..be58686 --- /dev/null +++ b/modules/openal-soft/common/albyte.h @@ -0,0 +1,17 @@ +#ifndef AL_BYTE_H +#define AL_BYTE_H + +#include +#include +#include +#include + +using uint = unsigned int; + +namespace al { + +using byte = unsigned char; + +} // namespace al + +#endif /* AL_BYTE_H */ diff --git a/modules/openal-soft/common/alcomplex.cpp b/modules/openal-soft/common/alcomplex.cpp index ace1b43..126e2c0 100644 --- a/modules/openal-soft/common/alcomplex.cpp +++ b/modules/openal-soft/common/alcomplex.cpp @@ -3,74 +3,164 @@ #include "alcomplex.h" +#include +#include #include +#include +#include + +#include "albit.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "opthelpers.h" + namespace { -constexpr double Pi{3.141592653589793238462643383279502884}; +using ushort = unsigned short; +using ushort2 = std::pair; -} // namespace +/* Because std::array doesn't have constexpr non-const accessors in C++14. */ +template +struct our_array { + T mData[N]; +}; -void complex_fft(std::complex *FFTBuffer, int FFTSize, double Sign) +constexpr size_t BitReverseCounter(size_t log2_size) noexcept { - /* Bit-reversal permutation applied to a sequence of FFTSize items */ - for(int i{1};i < FFTSize-1;i++) + /* Some magic math that calculates the number of swaps needed for a + * sequence of bit-reversed indices when index < reversed_index. + */ + return (1u<<(log2_size-1)) - (1u<<((log2_size-1u)/2u)); +} + +template +constexpr auto GetBitReverser() noexcept +{ + static_assert(N <= sizeof(ushort)*8, "Too many bits for the bit-reversal table."); + + our_array ret{}; + const size_t fftsize{1u << N}; + size_t ret_i{0}; + + /* Bit-reversal permutation applied to a sequence of fftsize items. */ + for(size_t idx{1u};idx < fftsize-1;++idx) { - int j{0}; - for(int mask{1};mask < FFTSize;mask <<= 1) + size_t revidx{0u}, imask{idx}; + for(size_t i{0};i < N;++i) { - if((i&mask) != 0) - j++; - j <<= 1; + revidx = (revidx<<1) | (imask&1); + imask >>= 1; } - j >>= 1; - if(i < j) - std::swap(FFTBuffer[i], FFTBuffer[j]); + if(idx < revidx) + { + ret.mData[ret_i].first = static_cast(idx); + ret.mData[ret_i].second = static_cast(revidx); + ++ret_i; + } } + assert(ret_i == al::size(ret.mData)); + return ret; +} + +/* These bit-reversal swap tables support up to 10-bit indices (1024 elements), + * which is the largest used by OpenAL Soft's filters and effects. Larger FFT + * requests, used by some utilities where performance is less important, will + * use a slower table-less path. + */ +constexpr auto BitReverser2 = GetBitReverser<2>(); +constexpr auto BitReverser3 = GetBitReverser<3>(); +constexpr auto BitReverser4 = GetBitReverser<4>(); +constexpr auto BitReverser5 = GetBitReverser<5>(); +constexpr auto BitReverser6 = GetBitReverser<6>(); +constexpr auto BitReverser7 = GetBitReverser<7>(); +constexpr auto BitReverser8 = GetBitReverser<8>(); +constexpr auto BitReverser9 = GetBitReverser<9>(); +constexpr auto BitReverser10 = GetBitReverser<10>(); +constexpr al::span gBitReverses[11]{ + {}, {}, + BitReverser2.mData, + BitReverser3.mData, + BitReverser4.mData, + BitReverser5.mData, + BitReverser6.mData, + BitReverser7.mData, + BitReverser8.mData, + BitReverser9.mData, + BitReverser10.mData +}; + +} // namespace - /* Iterative form of Danielson–Lanczos lemma */ - int step{2}; - for(int i{1};i < FFTSize;i<<=1, step<<=1) +void complex_fft(const al::span> buffer, const double sign) +{ + const size_t fftsize{buffer.size()}; + /* Get the number of bits used for indexing. Simplifies bit-reversal and + * the main loop count. + */ + const size_t log2_size{static_cast(al::countr_zero(fftsize))}; + + if(unlikely(log2_size >= al::size(gBitReverses))) { - int step2{step >> 1}; - double arg{Pi / step2}; + for(size_t idx{1u};idx < fftsize-1;++idx) + { + size_t revidx{0u}, imask{idx}; + for(size_t i{0};i < log2_size;++i) + { + revidx = (revidx<<1) | (imask&1); + imask >>= 1; + } - std::complex w{std::cos(arg), std::sin(arg)*Sign}; + if(idx < revidx) + std::swap(buffer[idx], buffer[revidx]); + } + } + else for(auto &rev : gBitReverses[log2_size]) + std::swap(buffer[rev.first], buffer[rev.second]); + + /* Iterative form of Danielson-Lanczos lemma */ + const double pi{al::numbers::pi * sign}; + size_t step2{1u}; + for(size_t i{0};i < log2_size;++i) + { + const double arg{pi / static_cast(step2)}; + + /* TODO: Would std::polar(1.0, arg) be any better? */ + const std::complex w{std::cos(arg), std::sin(arg)}; std::complex u{1.0, 0.0}; - for(int j{0};j < step2;j++) + const size_t step{step2 << 1}; + for(size_t j{0};j < step2;j++) { - for(int k{j};k < FFTSize;k+=step) + for(size_t k{j};k < fftsize;k+=step) { - std::complex temp{FFTBuffer[k+step2] * u}; - FFTBuffer[k+step2] = FFTBuffer[k] - temp; - FFTBuffer[k] += temp; + std::complex temp{buffer[k+step2] * u}; + buffer[k+step2] = buffer[k] - temp; + buffer[k] += temp; } u *= w; } + + step2 <<= 1; } } -void complex_hilbert(std::complex *Buffer, int size) +void complex_hilbert(const al::span> buffer) { - const double inverse_size = 1.0/static_cast(size); - - for(int i{0};i < size;i++) - Buffer[i].imag(0.0); - - complex_fft(Buffer, size, 1.0); + inverse_fft(buffer); - int todo{size>>1}; - int i{0}; + const double inverse_size = 1.0/static_cast(buffer.size()); + auto bufiter = buffer.begin(); + const auto halfiter = bufiter + (buffer.size()>>1); - Buffer[i++] *= inverse_size; - while(i < todo) - Buffer[i++] *= 2.0*inverse_size; - Buffer[i++] *= inverse_size; + *bufiter *= inverse_size; ++bufiter; + bufiter = std::transform(bufiter, halfiter, bufiter, + [inverse_size](const std::complex &c) -> std::complex + { return c * (2.0*inverse_size); }); + *bufiter *= inverse_size; ++bufiter; - for(;i < size;i++) - Buffer[i] = std::complex{}; + std::fill(bufiter, buffer.end(), std::complex{}); - complex_fft(Buffer, size, -1.0); + forward_fft(buffer); } diff --git a/modules/openal-soft/common/alcomplex.h b/modules/openal-soft/common/alcomplex.h index 554886c..23b8114 100644 --- a/modules/openal-soft/common/alcomplex.h +++ b/modules/openal-soft/common/alcomplex.h @@ -3,22 +3,36 @@ #include +#include "alspan.h" + /** * Iterative implementation of 2-radix FFT (In-place algorithm). Sign = -1 is - * FFT and 1 is iFFT (inverse). Fills FFTBuffer[0...FFTSize-1] with the - * Discrete Fourier Transform (DFT) of the time domain data stored in - * FFTBuffer[0...FFTSize-1]. FFTBuffer is an array of complex numbers, FFTSize - * MUST BE power of two. + * FFT and 1 is inverse FFT. Applies the Discrete Fourier Transform (DFT) to + * the data supplied in the buffer, which MUST BE power of two. + */ +void complex_fft(const al::span> buffer, const double sign); + +/** + * Calculate the frequency-domain response of the time-domain signal in the + * provided buffer, which MUST BE power of two. + */ +inline void forward_fft(const al::span> buffer) +{ complex_fft(buffer, -1.0); } + +/** + * Calculate the time-domain signal of the frequency-domain response in the + * provided buffer, which MUST BE power of two. */ -void complex_fft(std::complex *FFTBuffer, int FFTSize, double Sign); +inline void inverse_fft(const al::span> buffer) +{ complex_fft(buffer, 1.0); } /** * Calculate the complex helical sequence (discrete-time analytical signal) of * the given input using the discrete Hilbert transform (In-place algorithm). - * Fills Buffer[0...size-1] with the discrete-time analytical signal stored in - * Buffer[0...size-1]. Buffer is an array of complex numbers, size MUST BE - * power of two. + * Fills the buffer with the discrete-time analytical signal stored in the + * buffer. The buffer is an array of complex numbers and MUST BE power of two, + * and the imaginary components should be cleared to 0. */ -void complex_hilbert(std::complex *Buffer, int size); +void complex_hilbert(const al::span> buffer); #endif /* ALCOMPLEX_H */ diff --git a/modules/openal-soft/common/aldeque.h b/modules/openal-soft/common/aldeque.h new file mode 100644 index 0000000..3f99bf0 --- /dev/null +++ b/modules/openal-soft/common/aldeque.h @@ -0,0 +1,16 @@ +#ifndef ALDEQUE_H +#define ALDEQUE_H + +#include + +#include "almalloc.h" + + +namespace al { + +template +using deque = std::deque>; + +} // namespace al + +#endif /* ALDEQUE_H */ diff --git a/modules/openal-soft/common/alexcpt.h b/modules/openal-soft/common/alexcpt.h deleted file mode 100644 index ff09bab..0000000 --- a/modules/openal-soft/common/alexcpt.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef ALEXCPT_H -#define ALEXCPT_H - -#include -#include - -#include "AL/alc.h" - - -#ifdef __GNUC__ -#define ALEXCPT_FORMAT(x, y, z) __attribute__((format(x, (y), (z)))) -#else -#define ALEXCPT_FORMAT(x, y, z) -#endif - - -namespace al { - -class backend_exception final : public std::exception { - std::string mMessage; - ALCenum mErrorCode; - -public: - backend_exception(ALCenum code, const char *msg, ...) ALEXCPT_FORMAT(printf, 3,4); - - const char *what() const noexcept override { return mMessage.c_str(); } - ALCenum errorCode() const noexcept { return mErrorCode; } -}; - -} // namespace al - -#define START_API_FUNC try - -#define END_API_FUNC catch(...) { std::terminate(); } - -#endif /* ALEXCPT_H */ diff --git a/modules/openal-soft/common/alfstream.cpp b/modules/openal-soft/common/alfstream.cpp new file mode 100644 index 0000000..3beda83 --- /dev/null +++ b/modules/openal-soft/common/alfstream.cpp @@ -0,0 +1,150 @@ + +#include "config.h" + +#include "alfstream.h" + +#include "strutils.h" + +#ifdef _WIN32 + +namespace al { + +auto filebuf::underflow() -> int_type +{ + if(mFile != INVALID_HANDLE_VALUE && gptr() == egptr()) + { + // Read in the next chunk of data, and set the pointers on success + DWORD got{}; + if(ReadFile(mFile, mBuffer.data(), static_cast(mBuffer.size()), &got, nullptr)) + setg(mBuffer.data(), mBuffer.data(), mBuffer.data()+got); + } + if(gptr() == egptr()) + return traits_type::eof(); + return traits_type::to_int_type(*gptr()); +} + +auto filebuf::seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) -> pos_type +{ + if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in)) + return traits_type::eof(); + + LARGE_INTEGER fpos{}; + switch(whence) + { + case std::ios_base::beg: + fpos.QuadPart = offset; + if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN)) + return traits_type::eof(); + break; + + case std::ios_base::cur: + // If the offset remains in the current buffer range, just + // update the pointer. + if((offset >= 0 && offset < off_type(egptr()-gptr())) || + (offset < 0 && -offset <= off_type(gptr()-eback()))) + { + // Get the current file offset to report the correct read + // offset. + fpos.QuadPart = 0; + if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT)) + return traits_type::eof(); + setg(eback(), gptr()+offset, egptr()); + return fpos.QuadPart - off_type(egptr()-gptr()); + } + // Need to offset for the file offset being at egptr() while + // the requested offset is relative to gptr(). + offset -= off_type(egptr()-gptr()); + fpos.QuadPart = offset; + if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT)) + return traits_type::eof(); + break; + + case std::ios_base::end: + fpos.QuadPart = offset; + if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_END)) + return traits_type::eof(); + break; + + default: + return traits_type::eof(); + } + setg(nullptr, nullptr, nullptr); + return fpos.QuadPart; +} + +auto filebuf::seekpos(pos_type pos, std::ios_base::openmode mode) -> pos_type +{ + // Simplified version of seekoff + if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in)) + return traits_type::eof(); + + LARGE_INTEGER fpos{}; + fpos.QuadPart = pos; + if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN)) + return traits_type::eof(); + + setg(nullptr, nullptr, nullptr); + return fpos.QuadPart; +} + +filebuf::~filebuf() +{ close(); } + +bool filebuf::open(const wchar_t *filename, std::ios_base::openmode mode) +{ + if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) + return false; + HANDLE f{CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, nullptr)}; + if(f == INVALID_HANDLE_VALUE) return false; + + if(mFile != INVALID_HANDLE_VALUE) + CloseHandle(mFile); + mFile = f; + + setg(nullptr, nullptr, nullptr); + return true; +} +bool filebuf::open(const char *filename, std::ios_base::openmode mode) +{ + std::wstring wname{utf8_to_wstr(filename)}; + return open(wname.c_str(), mode); +} + +void filebuf::close() +{ + if(mFile != INVALID_HANDLE_VALUE) + CloseHandle(mFile); + mFile = INVALID_HANDLE_VALUE; +} + + +ifstream::ifstream(const wchar_t *filename, std::ios_base::openmode mode) + : std::istream{nullptr} +{ + init(&mStreamBuf); + + // Set the failbit if the file failed to open. + if((mode&std::ios_base::out) || !mStreamBuf.open(filename, mode|std::ios_base::in)) + clear(failbit); +} + +ifstream::ifstream(const char *filename, std::ios_base::openmode mode) + : std::istream{nullptr} +{ + init(&mStreamBuf); + + // Set the failbit if the file failed to open. + if((mode&std::ios_base::out) || !mStreamBuf.open(filename, mode|std::ios_base::in)) + clear(failbit); +} + +/* This is only here to ensure the compiler doesn't define an implicit + * destructor, which it tries to automatically inline and subsequently complain + * it can't inline without excessive code growth. + */ +ifstream::~ifstream() { } + +} // namespace al + +#endif diff --git a/modules/openal-soft/common/alfstream.h b/modules/openal-soft/common/alfstream.h new file mode 100644 index 0000000..353fd2d --- /dev/null +++ b/modules/openal-soft/common/alfstream.h @@ -0,0 +1,74 @@ +#ifndef AL_FSTREAM_H +#define AL_FSTREAM_H + +#ifdef _WIN32 + +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include +#include + + +namespace al { + +// Windows' std::ifstream fails with non-ANSI paths since the standard only +// specifies names using const char* (or std::string). MSVC has a non-standard +// extension using const wchar_t* (or std::wstring?) to handle Unicode paths, +// but not all Windows compilers support it. So we have to make our own istream +// that accepts UTF-8 paths and forwards to Unicode-aware I/O functions. +class filebuf final : public std::streambuf { + std::array mBuffer; + HANDLE mFile{INVALID_HANDLE_VALUE}; + + int_type underflow() override; + pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override; + pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override; + +public: + filebuf() = default; + ~filebuf() override; + + bool open(const wchar_t *filename, std::ios_base::openmode mode); + bool open(const char *filename, std::ios_base::openmode mode); + + bool is_open() const noexcept { return mFile != INVALID_HANDLE_VALUE; } + + void close(); +}; + +// Inherit from std::istream to use our custom streambuf +class ifstream final : public std::istream { + filebuf mStreamBuf; + +public: + ifstream(const wchar_t *filename, std::ios_base::openmode mode = std::ios_base::in); + ifstream(const std::wstring &filename, std::ios_base::openmode mode = std::ios_base::in) + : ifstream(filename.c_str(), mode) { } + ifstream(const char *filename, std::ios_base::openmode mode = std::ios_base::in); + ifstream(const std::string &filename, std::ios_base::openmode mode = std::ios_base::in) + : ifstream(filename.c_str(), mode) { } + ~ifstream() override; + + bool is_open() const noexcept { return mStreamBuf.is_open(); } + + void close() { mStreamBuf.close(); } +}; + +} // namespace al + +#else /* _WIN32 */ + +#include + +namespace al { + +using filebuf = std::filebuf; +using ifstream = std::ifstream; + +} // namespace al + +#endif /* _WIN32 */ + +#endif /* AL_FSTREAM_H */ diff --git a/modules/openal-soft/common/almalloc.cpp b/modules/openal-soft/common/almalloc.cpp index 35b9500..ad1dc6b 100644 --- a/modules/openal-soft/common/almalloc.cpp +++ b/modules/openal-soft/common/almalloc.cpp @@ -3,106 +3,59 @@ #include "almalloc.h" +#include +#include #include #include +#include #ifdef HAVE_MALLOC_H #include #endif -#ifdef HAVE_WINDOWS_H -#include -#else -#include -#endif - - -#ifdef __GNUC__ -#define LIKELY(x) __builtin_expect(!!(x), !0) -#define UNLIKELY(x) __builtin_expect(!!(x), 0) -#else -#define LIKELY(x) (!!(x)) -#define UNLIKELY(x) (!!(x)) -#endif void *al_malloc(size_t alignment, size_t size) { -#if defined(HAVE_ALIGNED_ALLOC) - size = (size+(alignment-1))&~(alignment-1); - return aligned_alloc(alignment, size); -#elif defined(HAVE_POSIX_MEMALIGN) - void *ret; + assert((alignment & (alignment-1)) == 0); + alignment = std::max(alignment, alignof(std::max_align_t)); + +#if defined(HAVE_POSIX_MEMALIGN) + void *ret{}; if(posix_memalign(&ret, alignment, size) == 0) return ret; return nullptr; #elif defined(HAVE__ALIGNED_MALLOC) return _aligned_malloc(size, alignment); #else - char *ret = static_cast(malloc(size+alignment)); - if(ret != nullptr) + size_t total_size{size + alignment-1 + sizeof(void*)}; + void *base{std::malloc(total_size)}; + if(base != nullptr) { - *(ret++) = 0x00; - while(((ptrdiff_t)ret&(alignment-1)) != 0) - *(ret++) = 0x55; + void *aligned_ptr{static_cast(base) + sizeof(void*)}; + total_size -= sizeof(void*); + + std::align(alignment, size, aligned_ptr, total_size); + *(static_cast(aligned_ptr)-1) = base; + base = aligned_ptr; } - return ret; + return base; #endif } void *al_calloc(size_t alignment, size_t size) { - void *ret = al_malloc(alignment, size); - if(ret) memset(ret, 0, size); + void *ret{al_malloc(alignment, size)}; + if(ret) std::memset(ret, 0, size); return ret; } void al_free(void *ptr) noexcept { -#if defined(HAVE_ALIGNED_ALLOC) || defined(HAVE_POSIX_MEMALIGN) - free(ptr); +#if defined(HAVE_POSIX_MEMALIGN) + std::free(ptr); #elif defined(HAVE__ALIGNED_MALLOC) _aligned_free(ptr); #else if(ptr != nullptr) - { - char *finder = static_cast(ptr); - do { - --finder; - } while(*finder == 0x55); - free(finder); - } -#endif -} - -size_t al_get_page_size() noexcept -{ - static size_t psize = 0; - if(UNLIKELY(!psize)) - { -#ifdef HAVE_SYSCONF -#if defined(_SC_PAGESIZE) - if(!psize) psize = sysconf(_SC_PAGESIZE); -#elif defined(_SC_PAGE_SIZE) - if(!psize) psize = sysconf(_SC_PAGE_SIZE); -#endif -#endif /* HAVE_SYSCONF */ -#ifdef _WIN32 - if(!psize) - { - SYSTEM_INFO sysinfo{}; - GetSystemInfo(&sysinfo); - psize = sysinfo.dwPageSize; - } -#endif - if(!psize) psize = DEF_ALIGN; - } - return psize; -} - -int al_is_sane_alignment_allocator() noexcept -{ -#if defined(HAVE_ALIGNED_ALLOC) || defined(HAVE_POSIX_MEMALIGN) || defined(HAVE__ALIGNED_MALLOC) - return 1; -#else - return 0; + std::free(*(static_cast(ptr) - 1)); #endif } diff --git a/modules/openal-soft/common/almalloc.h b/modules/openal-soft/common/almalloc.h index 406c2d3..711d02f 100644 --- a/modules/openal-soft/common/almalloc.h +++ b/modules/openal-soft/common/almalloc.h @@ -1,27 +1,30 @@ #ifndef AL_MALLOC_H #define AL_MALLOC_H -#include - -#include -#include #include +#include +#include +#include +#include +#include +#include +#include -/* Minimum alignment required by posix_memalign. */ -#define DEF_ALIGN sizeof(void*) +#include "pragmadefs.h" + +void al_free(void *ptr) noexcept; +[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]] void *al_malloc(size_t alignment, size_t size); +[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]] void *al_calloc(size_t alignment, size_t size); -void al_free(void *ptr) noexcept; -size_t al_get_page_size(void) noexcept; -/** - * Returns non-0 if the allocation function has direct alignment handling. - * Otherwise, the standard malloc is used with an over-allocation and pointer - * offset strategy. - */ -int al_is_sane_alignment_allocator(void) noexcept; +#define DISABLE_ALLOC() \ + void *operator new(size_t) = delete; \ + void *operator new[](size_t) = delete; \ + void operator delete(void*) noexcept = delete; \ + void operator delete[](void*) noexcept = delete; #define DEF_NEWDEL(T) \ void *operator new(size_t size) \ @@ -30,115 +33,257 @@ int al_is_sane_alignment_allocator(void) noexcept; if(!ret) throw std::bad_alloc(); \ return ret; \ } \ - void operator delete(void *block) noexcept { al_free(block); } + void *operator new[](size_t size) { return operator new(size); } \ + void operator delete(void *block) noexcept { al_free(block); } \ + void operator delete[](void *block) noexcept { operator delete(block); } #define DEF_PLACE_NEWDEL() \ void *operator new(size_t /*size*/, void *ptr) noexcept { return ptr; } \ + void *operator new[](size_t /*size*/, void *ptr) noexcept { return ptr; } \ + void operator delete(void *block, void*) noexcept { al_free(block); } \ + void operator delete(void *block) noexcept { al_free(block); } \ + void operator delete[](void *block, void*) noexcept { al_free(block); } \ + void operator delete[](void *block) noexcept { al_free(block); } + +enum FamCount : size_t { }; + +#define DEF_FAM_NEWDEL(T, FamMem) \ + static constexpr size_t Sizeof(size_t count) noexcept \ + { \ + return std::max(decltype(FamMem)::Sizeof(count, offsetof(T, FamMem)), \ + sizeof(T)); \ + } \ + \ + void *operator new(size_t /*size*/, FamCount count) \ + { \ + if(void *ret{al_malloc(alignof(T), T::Sizeof(count))}) \ + return ret; \ + throw std::bad_alloc(); \ + } \ + void *operator new[](size_t /*size*/) = delete; \ + void operator delete(void *block, FamCount) { al_free(block); } \ void operator delete(void *block) noexcept { al_free(block); } \ - void operator delete(void* /*block*/, void* /*ptr*/) noexcept { } + void operator delete[](void* /*block*/) = delete; + namespace al { -template -struct allocator : public std::allocator { - using size_type = size_t; +template +struct allocator { + using value_type = T; + using reference = T&; + using const_reference = const T&; using pointer = T*; using const_pointer = const T*; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using is_always_equal = std::true_type; template struct rebind { - using other = allocator; + using other = allocator; }; - pointer allocate(size_type n, const void* = nullptr) + constexpr explicit allocator() noexcept = default; + template + constexpr explicit allocator(const allocator&) noexcept { } + + T *allocate(std::size_t n) { - if(n > std::numeric_limits::max() / sizeof(T)) - throw std::bad_alloc(); + if(n > std::numeric_limits::max()/sizeof(T)) throw std::bad_alloc(); + if(auto p = al_malloc(alignment, n*sizeof(T))) return static_cast(p); + throw std::bad_alloc(); + } + void deallocate(T *p, std::size_t) noexcept { al_free(p); } +}; +template +constexpr bool operator==(const allocator&, const allocator&) noexcept { return true; } +template +constexpr bool operator!=(const allocator&, const allocator&) noexcept { return false; } + + +template +constexpr T* construct_at(T *ptr, Args&& ...args) + noexcept(std::is_nothrow_constructible::value) +{ return ::new(static_cast(ptr)) T{std::forward(args)...}; } + +/* At least VS 2015 complains that 'ptr' is unused when the given type's + * destructor is trivial (a no-op). So disable that warning for this call. + */ +DIAGNOSTIC_PUSH +msc_pragma(warning(disable : 4100)) +template +constexpr std::enable_if_t::value> +destroy_at(T *ptr) noexcept(std::is_nothrow_destructible::value) +{ ptr->~T(); } +DIAGNOSTIC_POP +template +constexpr std::enable_if_t::value> +destroy_at(T *ptr) noexcept(std::is_nothrow_destructible>::value) +{ + for(auto &elem : *ptr) + al::destroy_at(std::addressof(elem)); +} - void *ret{al_malloc(alignment, n*sizeof(T))}; - if(!ret) throw std::bad_alloc(); - return static_cast(ret); +template +constexpr void destroy(T first, T end) noexcept(noexcept(al::destroy_at(std::addressof(*first)))) +{ + while(first != end) + { + al::destroy_at(std::addressof(*first)); + ++first; } +} - void deallocate(pointer p, size_type) - { al_free(p); } +template +constexpr std::enable_if_t::value,T> +destroy_n(T first, N count) noexcept(noexcept(al::destroy_at(std::addressof(*first)))) +{ + if(count != 0) + { + do { + al::destroy_at(std::addressof(*first)); + ++first; + } while(--count); + } + return first; +} - allocator() : std::allocator() { } - allocator(const allocator &a) : std::allocator(a) { } - template - allocator(const allocator &a) : std::allocator(a) - { } -}; -template -inline T* assume_aligned(T *ptr) noexcept +template +inline std::enable_if_t::value, +T> uninitialized_default_construct_n(T first, N count) { - static_assert((alignment & (alignment-1)) == 0, "alignment must be a power of 2"); -#ifdef __GNUC__ - return static_cast(__builtin_assume_aligned(ptr, alignment)); -#elif defined(_MSC_VER) - auto ptrval = reinterpret_cast(ptr); - if((ptrval&(alignment-1)) != 0) __assume(0); - return reinterpret_cast(ptrval); -#else - return ptr; -#endif + using ValueT = typename std::iterator_traits::value_type; + T current{first}; + if(count != 0) + { + try { + do { + ::new(static_cast(std::addressof(*current))) ValueT; + ++current; + } while(--count); + } + catch(...) { + al::destroy(first, current); + throw; + } + } + return current; } -/* std::make_unique was added with C++14, so until we rely on that, make our - * own version. + +/* Storage for flexible array data. This is trivially destructible if type T is + * trivially destructible. */ -template -std::unique_ptr make_unique(ArgsT&&...args) -{ return std::unique_ptr{new T{std::forward(args)...}}; } +template::value> +struct FlexArrayStorage { + const size_t mSize; + union { + char mDummy; + alignas(alignment) T mArray[1]; + }; + + static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept + { + const size_t len{sizeof(T)*count}; + return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base; + } + + FlexArrayStorage(size_t size) : mSize{size} + { al::uninitialized_default_construct_n(mArray, mSize); } + ~FlexArrayStorage() = default; + + FlexArrayStorage(const FlexArrayStorage&) = delete; + FlexArrayStorage& operator=(const FlexArrayStorage&) = delete; +}; + +template +struct FlexArrayStorage { + const size_t mSize; + union { + char mDummy; + alignas(alignment) T mArray[1]; + }; + + static constexpr size_t Sizeof(size_t count, size_t base) noexcept + { + const size_t len{sizeof(T)*count}; + return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base; + } + FlexArrayStorage(size_t size) : mSize{size} + { al::uninitialized_default_construct_n(mArray, mSize); } + ~FlexArrayStorage() { al::destroy_n(mArray, mSize); } + + FlexArrayStorage(const FlexArrayStorage&) = delete; + FlexArrayStorage& operator=(const FlexArrayStorage&) = delete; +}; /* A flexible array type. Used either standalone or at the end of a parent * struct, with placement new, to have a run-time-sized array that's embedded * with its size. */ -template +template struct FlexArray { - const size_t mSize; - alignas(alignment) T mArray[]; + using element_type = T; + using value_type = std::remove_cv_t; + using index_type = size_t; + using difference_type = ptrdiff_t; - static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept - { - return base + - std::max(offsetof(FlexArray, mArray) + sizeof(T)*count, sizeof(FlexArray)); - } + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; - FlexArray(size_t size) : mSize{size} - { new (mArray) T[mSize]; } - ~FlexArray() + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + using Storage_t_ = FlexArrayStorage; + + Storage_t_ mStore; + + static constexpr index_type Sizeof(index_type count, index_type base=0u) noexcept + { return Storage_t_::Sizeof(count, base); } + static std::unique_ptr Create(index_type count) { - for(size_t i{0u};i < mSize;++i) - mArray[i].~T(); + void *ptr{al_calloc(alignof(FlexArray), Sizeof(count))}; + return std::unique_ptr{al::construct_at(static_cast(ptr), count)}; } - FlexArray(const FlexArray&) = delete; - FlexArray& operator=(const FlexArray&) = delete; + FlexArray(index_type size) : mStore{size} { } + ~FlexArray() = default; + + index_type size() const noexcept { return mStore.mSize; } + bool empty() const noexcept { return mStore.mSize == 0; } - size_t size() const noexcept { return mSize; } + pointer data() noexcept { return mStore.mArray; } + const_pointer data() const noexcept { return mStore.mArray; } - T *data() noexcept { return mArray; } - const T *data() const noexcept { return mArray; } + reference operator[](index_type i) noexcept { return mStore.mArray[i]; } + const_reference operator[](index_type i) const noexcept { return mStore.mArray[i]; } - T& operator[](size_t i) noexcept { return mArray[i]; } - const T& operator[](size_t i) const noexcept { return mArray[i]; } + reference front() noexcept { return mStore.mArray[0]; } + const_reference front() const noexcept { return mStore.mArray[0]; } - T& front() noexcept { return mArray[0]; } - const T& front() const noexcept { return mArray[0]; } + reference back() noexcept { return mStore.mArray[mStore.mSize-1]; } + const_reference back() const noexcept { return mStore.mArray[mStore.mSize-1]; } - T& back() noexcept { return mArray[mSize-1]; } - const T& back() const noexcept { return mArray[mSize-1]; } + iterator begin() noexcept { return mStore.mArray; } + const_iterator begin() const noexcept { return mStore.mArray; } + const_iterator cbegin() const noexcept { return mStore.mArray; } + iterator end() noexcept { return mStore.mArray + mStore.mSize; } + const_iterator end() const noexcept { return mStore.mArray + mStore.mSize; } + const_iterator cend() const noexcept { return mStore.mArray + mStore.mSize; } - T *begin() noexcept { return mArray; } - const T *begin() const noexcept { return mArray; } - const T *cbegin() const noexcept { return mArray; } - T *end() noexcept { return mArray + mSize; } - const T *end() const noexcept { return mArray + mSize; } - const T *cend() const noexcept { return mArray + mSize; } + reverse_iterator rbegin() noexcept { return end(); } + const_reverse_iterator rbegin() const noexcept { return end(); } + const_reverse_iterator crbegin() const noexcept { return cend(); } + reverse_iterator rend() noexcept { return begin(); } + const_reverse_iterator rend() const noexcept { return begin(); } + const_reverse_iterator crend() const noexcept { return cbegin(); } DEF_PLACE_NEWDEL() }; diff --git a/modules/openal-soft/common/alnumbers.h b/modules/openal-soft/common/alnumbers.h new file mode 100644 index 0000000..37a5541 --- /dev/null +++ b/modules/openal-soft/common/alnumbers.h @@ -0,0 +1,36 @@ +#ifndef COMMON_ALNUMBERS_H +#define COMMON_ALNUMBERS_H + +#include + +namespace al { + +namespace numbers { + +namespace detail_ { + template + using as_fp = std::enable_if_t::value, T>; +} // detail_ + +template +static constexpr auto pi_v = detail_::as_fp(3.141592653589793238462643383279502884L); + +template +static constexpr auto inv_pi_v = detail_::as_fp(0.318309886183790671537767526745028724L); + +template +static constexpr auto sqrt2_v = detail_::as_fp(1.414213562373095048801688724209698079L); + +template +static constexpr auto sqrt3_v = detail_::as_fp(1.732050807568877293527446341505872367L); + +static constexpr auto pi = pi_v; +static constexpr auto inv_pi = inv_pi_v; +static constexpr auto sqrt2 = sqrt2_v; +static constexpr auto sqrt3 = sqrt3_v; + +} // namespace numbers + +} // namespace al + +#endif /* COMMON_ALNUMBERS_H */ diff --git a/modules/openal-soft/common/alnumeric.h b/modules/openal-soft/common/alnumeric.h index e97c40e..9e7a7f0 100644 --- a/modules/openal-soft/common/alnumeric.h +++ b/modules/openal-soft/common/alnumeric.h @@ -1,7 +1,10 @@ #ifndef AL_NUMERIC_H #define AL_NUMERIC_H -#include +#include +#include +#include +#include #ifdef HAVE_INTRIN_H #include #endif @@ -66,6 +69,19 @@ constexpr inline size_t clampz(size_t val, size_t min, size_t max) noexcept { return minz(max, maxz(min, val)); } +constexpr inline float lerpf(float val1, float val2, float mu) noexcept +{ return val1 + (val2-val1)*mu; } +constexpr inline float cubic(float val1, float val2, float val3, float val4, float mu) noexcept +{ + const float mu2{mu*mu}, mu3{mu2*mu}; + const float a0{-0.5f*mu3 + mu2 + -0.5f*mu}; + const float a1{ 1.5f*mu3 + -2.5f*mu2 + 1.0f}; + const float a2{-1.5f*mu3 + 2.0f*mu2 + 0.5f*mu}; + const float a3{ 0.5f*mu3 + -0.5f*mu2}; + return val1*a0 + val2*a1 + val3*a2 + val4*a3; +} + + /** Find the next power-of-2 for non-power-of-2 numbers. */ inline uint32_t NextPowerOf2(uint32_t value) noexcept { @@ -89,109 +105,6 @@ inline size_t RoundUp(size_t value, size_t r) noexcept } -/* Define CTZ macros (count trailing zeros), and POPCNT macros (population - * count/count 1 bits), for 32- and 64-bit integers. The CTZ macros' results - * are *UNDEFINED* if the value is 0. - */ -#ifdef __GNUC__ - -#define POPCNT32 __builtin_popcount -#define CTZ32 __builtin_ctz -#if SIZEOF_LONG == 8 -#define POPCNT64 __builtin_popcountl -#define CTZ64 __builtin_ctzl -#else -#define POPCNT64 __builtin_popcountll -#define CTZ64 __builtin_ctzll -#endif - -#elif defined(HAVE_BITSCANFORWARD64_INTRINSIC) - -inline int msvc64_popcnt32(uint32_t v) -{ return (int)__popcnt(v); } -#define POPCNT32 msvc64_popcnt32 -inline int msvc64_ctz32(uint32_t v) -{ - unsigned long idx = 32; - _BitScanForward(&idx, v); - return (int)idx; -} -#define CTZ32 msvc64_ctz32 - -inline int msvc64_popcnt64(uint64_t v) -{ return (int)__popcnt64(v); } -#define POPCNT64 msvc64_popcnt64 -inline int msvc64_ctz64(uint64_t v) -{ - unsigned long idx = 64; - _BitScanForward64(&idx, v); - return (int)idx; -} -#define CTZ64 msvc64_ctz64 - -#elif defined(HAVE_BITSCANFORWARD_INTRINSIC) - -inline int msvc_popcnt32(uint32_t v) -{ return (int)__popcnt(v); } -#define POPCNT32 msvc_popcnt32 -inline int msvc_ctz32(uint32_t v) -{ - unsigned long idx = 32; - _BitScanForward(&idx, v); - return (int)idx; -} -#define CTZ32 msvc_ctz32 - -inline int msvc_popcnt64(uint64_t v) -{ return (int)(__popcnt((uint32_t)v) + __popcnt((uint32_t)(v>>32))); } -#define POPCNT64 msvc_popcnt64 -inline int msvc_ctz64(uint64_t v) -{ - unsigned long idx = 64; - if(!_BitScanForward(&idx, (uint32_t)(v&0xffffffff))) - { - if(_BitScanForward(&idx, (uint32_t)(v>>32))) - idx += 32; - } - return (int)idx; -} -#define CTZ64 msvc_ctz64 - -#else - -/* There be black magics here. The popcnt method is derived from - * https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel - * while the ctz-utilizing-popcnt algorithm is shown here - * http://www.hackersdelight.org/hdcodetxt/ntz.c.txt - * as the ntz2 variant. These likely aren't the most efficient methods, but - * they're good enough if the GCC or MSVC intrinsics aren't available. - */ -inline int fallback_popcnt32(uint32_t v) -{ - v = v - ((v >> 1) & 0x55555555u); - v = (v & 0x33333333u) + ((v >> 2) & 0x33333333u); - v = (v + (v >> 4)) & 0x0f0f0f0fu; - return (int)((v * 0x01010101u) >> 24); -} -#define POPCNT32 fallback_popcnt32 -inline int fallback_ctz32(uint32_t value) -{ return fallback_popcnt32(~value & (value - 1)); } -#define CTZ32 fallback_ctz32 - -inline int fallback_popcnt64(uint64_t v) -{ - v = v - ((v >> 1) & 0x5555555555555555_u64); - v = (v & 0x3333333333333333_u64) + ((v >> 2) & 0x3333333333333333_u64); - v = (v + (v >> 4)) & 0x0f0f0f0f0f0f0f0f_u64; - return (int)((v * 0x0101010101010101_u64) >> 56); -} -#define POPCNT64 fallback_popcnt64 -inline int fallback_ctz64(uint64_t value) -{ return fallback_popcnt64(~value & (value - 1)); } -#define CTZ64 fallback_ctz64 -#endif - - /** * Fast float-to-int conversion. No particular rounding mode is assumed; the * IEEE-754 default is round-to-nearest with ties-to-even, though an app could @@ -225,6 +138,8 @@ inline int fastf2i(float f) noexcept return static_cast(f); #endif } +inline unsigned int fastf2u(float f) noexcept +{ return static_cast(fastf2i(f)); } /** Converts float-to-int using standard behavior (truncation). */ inline int float2int(float f) noexcept @@ -232,8 +147,9 @@ inline int float2int(float f) noexcept #if defined(HAVE_SSE_INTRINSICS) return _mm_cvtt_ss2si(_mm_set_ss(f)); -#elif ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \ - !defined(__SSE_MATH__)) || (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0) +#elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0) \ + || ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \ + && !defined(__SSE_MATH__)) int sign, shift, mant; union { float f; @@ -245,11 +161,11 @@ inline int float2int(float f) noexcept shift = ((conv.i>>23)&0xff) - (127+23); /* Over/underflow */ - if(UNLIKELY(shift >= 31 || shift < -23)) + if UNLIKELY(shift >= 31 || shift < -23) return 0; mant = (conv.i&0x7fffff) | 0x800000; - if(LIKELY(shift < 0)) + if LIKELY(shift < 0) return (mant >> -shift) * sign; return (mant << shift) * sign; @@ -258,6 +174,43 @@ inline int float2int(float f) noexcept return static_cast(f); #endif } +inline unsigned int float2uint(float f) noexcept +{ return static_cast(float2int(f)); } + +/** Converts double-to-int using standard behavior (truncation). */ +inline int double2int(double d) noexcept +{ +#if defined(HAVE_SSE_INTRINSICS) + return _mm_cvttsd_si32(_mm_set_sd(d)); + +#elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2) \ + || ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \ + && !defined(__SSE2_MATH__)) + int sign, shift; + int64_t mant; + union { + double d; + int64_t i64; + } conv; + + conv.d = d; + sign = (conv.i64 >> 63) | 1; + shift = ((conv.i64 >> 52) & 0x7ff) - (1023 + 52); + + /* Over/underflow */ + if UNLIKELY(shift >= 63 || shift < -52) + return 0; + + mant = (conv.i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64; + if LIKELY(shift < 0) + return (int)(mant >> -shift) * sign; + return (int)(mant << shift) * sign; + +#else + + return static_cast(d); +#endif +} /** * Rounds a float to the nearest integral value, according to the current @@ -266,19 +219,25 @@ inline int float2int(float f) noexcept */ inline float fast_roundf(float f) noexcept { -#if (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \ - !defined(__SSE_MATH__) +#if (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \ + && !defined(__SSE_MATH__) float out; __asm__ __volatile__("frndint" : "=t"(out) : "0"(f)); return out; +#elif (defined(__GNUC__) || defined(__clang__)) && defined(__aarch64__) + + float out; + __asm__ volatile("frintx %s0, %s1" : "=w"(out) : "w"(f)); + return out; + #else /* Integral limit, where sub-integral precision is not available for * floats. */ - static constexpr float ilim[2] = { + static const float ilim[2]{ 8388608.0f /* 0x1.0p+23 */, -8388608.0f /* -0x1.0p+23 */ }; @@ -292,7 +251,7 @@ inline float fast_roundf(float f) noexcept sign = (conv.i>>31)&0x01; expo = (conv.i>>23)&0xff; - if(UNLIKELY(expo >= 150/*+23*/)) + if UNLIKELY(expo >= 150/*+23*/) { /* An exponent (base-2) of 23 or higher is incapable of sub-integral * precision, so it's already an integral value. We don't need to worry @@ -314,4 +273,27 @@ inline float fast_roundf(float f) noexcept #endif } + +template +constexpr const T& clamp(const T& value, const T& min_value, const T& max_value) noexcept +{ + return std::min(std::max(value, min_value), max_value); +} + +// Converts level (mB) to gain. +inline float level_mb_to_gain(float x) +{ + if(x <= -10'000.0f) + return 0.0f; + return std::pow(10.0f, x / 2'000.0f); +} + +// Converts gain to level (mB). +inline float gain_to_level_mb(float x) +{ + if (x <= 0.0f) + return -10'000.0f; + return maxf(std::log10(x) * 2'000.0f, -10'000.0f); +} + #endif /* AL_NUMERIC_H */ diff --git a/modules/openal-soft/common/aloptional.h b/modules/openal-soft/common/aloptional.h new file mode 100644 index 0000000..6180d16 --- /dev/null +++ b/modules/openal-soft/common/aloptional.h @@ -0,0 +1,353 @@ +#ifndef AL_OPTIONAL_H +#define AL_OPTIONAL_H + +#include +#include +#include + +#include "almalloc.h" + +namespace al { + +struct nullopt_t { }; +struct in_place_t { }; + +constexpr nullopt_t nullopt{}; +constexpr in_place_t in_place{}; + +#define NOEXCEPT_AS(...) noexcept(noexcept(__VA_ARGS__)) + +namespace detail_ { +/* Base storage struct for an optional. Defines a trivial destructor, for types + * that can be trivially destructed. + */ +template::value> +struct optstore_base { + bool mHasValue{false}; + union { + char mDummy{}; + T mValue; + }; + + constexpr optstore_base() noexcept { } + template + constexpr explicit optstore_base(in_place_t, Args&& ...args) + noexcept(std::is_nothrow_constructible::value) + : mHasValue{true}, mValue{std::forward(args)...} + { } + ~optstore_base() = default; +}; + +/* Specialization needing a non-trivial destructor. */ +template +struct optstore_base { + bool mHasValue{false}; + union { + char mDummy{}; + T mValue; + }; + + constexpr optstore_base() noexcept { } + template + constexpr explicit optstore_base(in_place_t, Args&& ...args) + noexcept(std::is_nothrow_constructible::value) + : mHasValue{true}, mValue{std::forward(args)...} + { } + ~optstore_base() { if(mHasValue) al::destroy_at(std::addressof(mValue)); } +}; + +/* Next level of storage, which defines helpers to construct and destruct the + * stored object. + */ +template +struct optstore_helper : public optstore_base { + using optstore_base::optstore_base; + + template + constexpr void construct(Args&& ...args) noexcept(std::is_nothrow_constructible::value) + { + al::construct_at(std::addressof(this->mValue), std::forward(args)...); + this->mHasValue = true; + } + + constexpr void reset() noexcept + { + if(this->mHasValue) + al::destroy_at(std::addressof(this->mValue)); + this->mHasValue = false; + } + + constexpr void assign(const optstore_helper &rhs) + noexcept(std::is_nothrow_copy_constructible::value + && std::is_nothrow_copy_assignable::value) + { + if(!rhs.mHasValue) + this->reset(); + else if(this->mHasValue) + this->mValue = rhs.mValue; + else + this->construct(rhs.mValue); + } + + constexpr void assign(optstore_helper&& rhs) + noexcept(std::is_nothrow_move_constructible::value + && std::is_nothrow_move_assignable::value) + { + if(!rhs.mHasValue) + this->reset(); + else if(this->mHasValue) + this->mValue = std::move(rhs.mValue); + else + this->construct(std::move(rhs.mValue)); + } +}; + +/* Define copy and move constructors and assignment operators, which may or may + * not be trivial. + */ +template::value, + bool trivial_move = std::is_trivially_move_constructible::value, + /* Trivial assignment is dependent on trivial construction+destruction. */ + bool = trivial_copy && std::is_trivially_copy_assignable::value + && std::is_trivially_destructible::value, + bool = trivial_move && std::is_trivially_move_assignable::value + && std::is_trivially_destructible::value> +struct optional_storage; + +/* Some versions of GCC have issues with 'this' in the following noexcept(...) + * statements, so this macro is a workaround. + */ +#define _this std::declval() + +/* Completely trivial. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage&) = default; + constexpr optional_storage(optional_storage&&) = default; + constexpr optional_storage& operator=(const optional_storage&) = default; + constexpr optional_storage& operator=(optional_storage&&) = default; +}; + +/* Non-trivial move assignment. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage&) = default; + constexpr optional_storage(optional_storage&&) = default; + constexpr optional_storage& operator=(const optional_storage&) = default; + constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) + { this->assign(std::move(rhs)); return *this; } +}; + +/* Non-trivial move construction. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage&) = default; + constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) + { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } + constexpr optional_storage& operator=(const optional_storage&) = default; + constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) + { this->assign(std::move(rhs)); return *this; } +}; + +/* Non-trivial copy assignment. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage&) = default; + constexpr optional_storage(optional_storage&&) = default; + constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) + { this->assign(rhs); return *this; } + constexpr optional_storage& operator=(optional_storage&&) = default; +}; + +/* Non-trivial copy construction. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) + { if(rhs.mHasValue) this->construct(rhs.mValue); } + constexpr optional_storage(optional_storage&&) = default; + constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) + { this->assign(rhs); return *this; } + constexpr optional_storage& operator=(optional_storage&&) = default; +}; + +/* Non-trivial assignment. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage&) = default; + constexpr optional_storage(optional_storage&&) = default; + constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) + { this->assign(rhs); return *this; } + constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) + { this->assign(std::move(rhs)); return *this; } +}; + +/* Non-trivial assignment, non-trivial move construction. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage&) = default; + constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) + { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } + constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) + { this->assign(rhs); return *this; } + constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) + { this->assign(std::move(rhs)); return *this; } +}; + +/* Non-trivial assignment, non-trivial copy construction. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) + { if(rhs.mHasValue) this->construct(rhs.mValue); } + constexpr optional_storage(optional_storage&&) = default; + constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) + { this->assign(rhs); return *this; } + constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) + { this->assign(std::move(rhs)); return *this; } +}; + +/* Completely non-trivial. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) + { if(rhs.mHasValue) this->construct(rhs.mValue); } + constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) + { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } + constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) + { this->assign(rhs); return *this; } + constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) + { this->assign(std::move(rhs)); return *this; } +}; + +#undef _this + +} // namespace detail_ + +#define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true + +template +class optional { + using storage_t = detail_::optional_storage; + + storage_t mStore{}; + +public: + using value_type = T; + + constexpr optional() = default; + constexpr optional(const optional&) = default; + constexpr optional(optional&&) = default; + constexpr optional(nullopt_t) noexcept { } + template + constexpr explicit optional(in_place_t, Args&& ...args) + NOEXCEPT_AS(storage_t{al::in_place, std::forward(args)...}) + : mStore{al::in_place, std::forward(args)...} + { } + template::value + && !std::is_same, al::in_place_t>::value + && !std::is_same, optional>::value + && std::is_convertible::value)> + constexpr optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward(rhs)}) + : mStore{al::in_place, std::forward(rhs)} + { } + template::value + && !std::is_same, al::in_place_t>::value + && !std::is_same, optional>::value + && !std::is_convertible::value)> + constexpr explicit optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward(rhs)}) + : mStore{al::in_place, std::forward(rhs)} + { } + ~optional() = default; + + constexpr optional& operator=(const optional&) = default; + constexpr optional& operator=(optional&&) = default; + constexpr optional& operator=(nullopt_t) noexcept { mStore.reset(); return *this; } + template + constexpr std::enable_if_t::value + && std::is_assignable::value + && !std::is_same, optional>::value + && (!std::is_same, T>::value || !std::is_scalar::value), + optional&> operator=(U&& rhs) + { + if(mStore.mHasValue) + mStore.mValue = std::forward(rhs); + else + mStore.construct(std::forward(rhs)); + return *this; + } + + constexpr const T* operator->() const { return std::addressof(mStore.mValue); } + constexpr T* operator->() { return std::addressof(mStore.mValue); } + constexpr const T& operator*() const& { return mStore.mValue; } + constexpr T& operator*() & { return mStore.mValue; } + constexpr const T&& operator*() const&& { return std::move(mStore.mValue); } + constexpr T&& operator*() && { return std::move(mStore.mValue); } + + constexpr explicit operator bool() const noexcept { return mStore.mHasValue; } + constexpr bool has_value() const noexcept { return mStore.mHasValue; } + + constexpr T& value() & { return mStore.mValue; } + constexpr const T& value() const& { return mStore.mValue; } + constexpr T&& value() && { return std::move(mStore.mValue); } + constexpr const T&& value() const&& { return std::move(mStore.mValue); } + + template + constexpr T value_or(U&& defval) const& + { return bool{*this} ? **this : static_cast(std::forward(defval)); } + template + constexpr T value_or(U&& defval) && + { return bool{*this} ? std::move(**this) : static_cast(std::forward(defval)); } + + template + constexpr T& emplace(Args&& ...args) + { + mStore.reset(); + mStore.construct(std::forward(args)...); + return mStore.mValue; + } + template + constexpr std::enable_if_t&, Args&&...>::value, + T&> emplace(std::initializer_list il, Args&& ...args) + { + mStore.reset(); + mStore.construct(il, std::forward(args)...); + return mStore.mValue; + } + + constexpr void reset() noexcept { mStore.reset(); } +}; + +template +constexpr optional> make_optional(T&& arg) +{ return optional>{in_place, std::forward(arg)}; } + +template +constexpr optional make_optional(Args&& ...args) +{ return optional{in_place, std::forward(args)...}; } + +template +constexpr optional make_optional(std::initializer_list il, Args&& ...args) +{ return optional{in_place, il, std::forward(args)...}; } + +#undef REQUIRES +#undef NOEXCEPT_AS +} // namespace al + +#endif /* AL_OPTIONAL_H */ diff --git a/modules/openal-soft/common/alspan.h b/modules/openal-soft/common/alspan.h index 9682317..4a0e043 100644 --- a/modules/openal-soft/common/alspan.h +++ b/modules/openal-soft/common/alspan.h @@ -1,37 +1,29 @@ #ifndef AL_SPAN_H #define AL_SPAN_H -#include - #include -#include +#include #include +#include +#include namespace al { template -constexpr auto size(T &cont) -> decltype(cont.size()) -{ return cont.size(); } - -template -constexpr auto size(const T &cont) -> decltype(cont.size()) +constexpr auto size(const T &cont) noexcept(noexcept(cont.size())) -> decltype(cont.size()) { return cont.size(); } template -constexpr size_t size(T (&)[N]) noexcept +constexpr size_t size(const T (&)[N]) noexcept { return N; } -template -constexpr size_t size(std::initializer_list list) noexcept -{ return list.size(); } - template -constexpr auto data(T &cont) -> decltype(cont.data()) +constexpr auto data(T &cont) noexcept(noexcept(cont.data())) -> decltype(cont.data()) { return cont.data(); } template -constexpr auto data(const T &cont) -> decltype(cont.data()) +constexpr auto data(const T &cont) noexcept(noexcept(cont.data())) -> decltype(cont.data()) { return cont.data(); } template @@ -43,11 +35,154 @@ constexpr const T* data(std::initializer_list list) noexcept { return list.begin(); } -template +constexpr size_t dynamic_extent{static_cast(-1)}; + +template +class span; + +namespace detail_ { + template + struct make_void { using type = void; }; + template + using void_t = typename make_void::type; + + template + struct is_span_ : std::false_type { }; + template + struct is_span_> : std::true_type { }; + template + using is_span = is_span_>; + + template + struct is_std_array_ : std::false_type { }; + template + struct is_std_array_> : std::true_type { }; + template + using is_std_array = is_std_array_>; + + template + struct has_size_and_data : std::false_type { }; + template + struct has_size_and_data())), decltype(al::data(std::declval()))>> + : std::true_type { }; +} // namespace detail_ + +#define REQUIRES(...) bool rt_=true, std::enable_if_t = true +#define IS_VALID_CONTAINER(C) \ + !detail_::is_span::value && !detail_::is_std_array::value && \ + !std::is_array::value && detail_::has_size_and_data::value && \ + std::is_convertible()))>(*)[],element_type(*)[]>::value + +template class span { public: using element_type = T; - using value_type = typename std::remove_cv::type; + using value_type = std::remove_cv_t; + using index_type = size_t; + using difference_type = ptrdiff_t; + + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + static constexpr size_t extent{E}; + + template + constexpr span() noexcept { } + constexpr span(pointer ptr, index_type /*count*/) : mData{ptr} { } + constexpr span(pointer first, pointer /*last*/) : mData{first} { } + constexpr span(element_type (&arr)[E]) noexcept : span{al::data(arr), al::size(arr)} { } + constexpr span(std::array &arr) noexcept : span{al::data(arr), al::size(arr)} { } + template::value)> + constexpr span(const std::array &arr) noexcept + : span{al::data(arr), al::size(arr)} + { } + template + constexpr span(U &cont) : span{al::data(cont), al::size(cont)} { } + template + constexpr span(const U &cont) : span{al::data(cont), al::size(cont)} { } + template::value + && std::is_convertible::value)> + constexpr span(const span &span_) noexcept : span{al::data(span_), al::size(span_)} { } + constexpr span(const span&) noexcept = default; + + constexpr span& operator=(const span &rhs) noexcept = default; + + constexpr reference front() const { return *mData; } + constexpr reference back() const { return *(mData+E-1); } + constexpr reference operator[](index_type idx) const { return mData[idx]; } + constexpr pointer data() const noexcept { return mData; } + + constexpr index_type size() const noexcept { return E; } + constexpr index_type size_bytes() const noexcept { return E * sizeof(value_type); } + constexpr bool empty() const noexcept { return E == 0; } + + constexpr iterator begin() const noexcept { return mData; } + constexpr iterator end() const noexcept { return mData+E; } + constexpr const_iterator cbegin() const noexcept { return mData; } + constexpr const_iterator cend() const noexcept { return mData+E; } + + constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; } + constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; } + constexpr const_reverse_iterator crbegin() const noexcept + { return const_reverse_iterator{cend()}; } + constexpr const_reverse_iterator crend() const noexcept + { return const_reverse_iterator{cbegin()}; } + + template + constexpr span first() const + { + static_assert(E >= C, "New size exceeds original capacity"); + return span{mData, C}; + } + + template + constexpr span last() const + { + static_assert(E >= C, "New size exceeds original capacity"); + return span{mData+(E-C), C}; + } + + template + constexpr auto subspan() const -> std::enable_if_t> + { + static_assert(E >= O, "Offset exceeds extent"); + static_assert(E-O >= C, "New size exceeds original capacity"); + return span{mData+O, C}; + } + + template + constexpr auto subspan() const -> std::enable_if_t> + { + static_assert(E >= O, "Offset exceeds extent"); + return span{mData+O, E-O}; + } + + /* NOTE: Can't declare objects of a specialized template class prior to + * defining the specialization. As a result, these methods need to be + * defined later. + */ + constexpr span first(size_t count) const; + constexpr span last(size_t count) const; + constexpr span subspan(size_t offset, + size_t count=dynamic_extent) const; + +private: + pointer mData{nullptr}; +}; + +template +class span { +public: + using element_type = T; + using value_type = std::remove_cv_t; using index_type = size_t; using difference_type = ptrdiff_t; @@ -61,47 +196,112 @@ public: using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; + static constexpr size_t extent{dynamic_extent}; + constexpr span() noexcept = default; - constexpr span(pointer ptr, index_type count) : mData{ptr}, mCount{count} { } - constexpr span(pointer first, pointer last) : mData{first}, mCount{std::distance(first, last)} { } + constexpr span(pointer ptr, index_type count) : mData{ptr}, mDataEnd{ptr+count} { } + constexpr span(pointer first, pointer last) : mData{first}, mDataEnd{last} { } template constexpr span(element_type (&arr)[N]) noexcept : span{al::data(arr), al::size(arr)} { } template constexpr span(std::array &arr) noexcept : span{al::data(arr), al::size(arr)} { } - template - constexpr span(const std::array &arr) noexcept : span{al::data(arr), al::size(arr)} { } - template + template::value)> + constexpr span(const std::array &arr) noexcept + : span{al::data(arr), al::size(arr)} + { } + template constexpr span(U &cont) : span{al::data(cont), al::size(cont)} { } - template + template constexpr span(const U &cont) : span{al::data(cont), al::size(cont)} { } + template::value || extent != N) + && std::is_convertible::value)> + constexpr span(const span &span_) noexcept : span{al::data(span_), al::size(span_)} { } constexpr span(const span&) noexcept = default; - span& operator=(const span &rhs) noexcept = default; + constexpr span& operator=(const span &rhs) noexcept = default; - constexpr reference front() const { return mData[0]; } - constexpr reference back() const { return mData[mCount-1]; } + constexpr reference front() const { return *mData; } + constexpr reference back() const { return *(mDataEnd-1); } constexpr reference operator[](index_type idx) const { return mData[idx]; } constexpr pointer data() const noexcept { return mData; } - constexpr index_type size() const noexcept { return mCount; } - constexpr index_type size_bytes() const noexcept { return mCount * sizeof(value_type); } - constexpr bool empty() const noexcept { return mCount == 0; } + constexpr index_type size() const noexcept { return static_cast(mDataEnd-mData); } + constexpr index_type size_bytes() const noexcept + { return static_cast(mDataEnd-mData) * sizeof(value_type); } + constexpr bool empty() const noexcept { return mData == mDataEnd; } constexpr iterator begin() const noexcept { return mData; } - constexpr iterator end() const noexcept { return mData + mCount; } + constexpr iterator end() const noexcept { return mDataEnd; } constexpr const_iterator cbegin() const noexcept { return mData; } - constexpr const_iterator cend() const noexcept { return mData + mCount; } + constexpr const_iterator cend() const noexcept { return mDataEnd; } + + constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; } + constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; } + constexpr const_reverse_iterator crbegin() const noexcept + { return const_reverse_iterator{cend()}; } + constexpr const_reverse_iterator crend() const noexcept + { return const_reverse_iterator{cbegin()}; } + + template + constexpr span first() const + { return span{mData, C}; } + + constexpr span first(size_t count) const + { return (count >= size()) ? *this : span{mData, mData+count}; } - constexpr reverse_iterator rbegin() const noexcept { return end(); } - constexpr reverse_iterator rend() const noexcept { return begin(); } - constexpr const_reverse_iterator crbegin() const noexcept { return cend(); } - constexpr const_reverse_iterator crend() const noexcept { return cbegin(); } + template + constexpr span last() const + { return span{mDataEnd-C, C}; } + + constexpr span last(size_t count) const + { return (count >= size()) ? *this : span{mDataEnd-count, mDataEnd}; } + + template + constexpr auto subspan() const -> std::enable_if_t> + { return span{mData+O, C}; } + + template + constexpr auto subspan() const -> std::enable_if_t> + { return span{mData+O, mDataEnd}; } + + constexpr span subspan(size_t offset, size_t count=dynamic_extent) const + { + return (offset > size()) ? span{} : + (count >= size()-offset) ? span{mData+offset, mDataEnd} : + span{mData+offset, mData+offset+count}; + } private: pointer mData{nullptr}; - index_type mCount{0u}; + pointer mDataEnd{nullptr}; }; +template +constexpr inline auto span::first(size_t count) const -> span +{ + return (count >= size()) ? span{mData, extent} : + span{mData, count}; +} + +template +constexpr inline auto span::last(size_t count) const -> span +{ + return (count >= size()) ? span{mData, extent} : + span{mData+extent-count, count}; +} + +template +constexpr inline auto span::subspan(size_t offset, size_t count) const + -> span +{ + return (offset > size()) ? span{} : + (count >= size()-offset) ? span{mData+offset, mData+extent} : + span{mData+offset, mData+offset+count}; +} + +#undef IS_VALID_CONTAINER +#undef REQUIRES + } // namespace al #endif /* AL_SPAN_H */ diff --git a/modules/openal-soft/common/alstring.cpp b/modules/openal-soft/common/alstring.cpp new file mode 100644 index 0000000..4a84be1 --- /dev/null +++ b/modules/openal-soft/common/alstring.cpp @@ -0,0 +1,45 @@ + +#include "config.h" + +#include "alstring.h" + +#include +#include + + +namespace { + +int to_upper(const char ch) +{ + using char8_traits = std::char_traits; + return std::toupper(char8_traits::to_int_type(ch)); +} + +} // namespace + +namespace al { + +int strcasecmp(const char *str0, const char *str1) noexcept +{ + do { + const int diff{to_upper(*str0) - to_upper(*str1)}; + if(diff < 0) return -1; + if(diff > 0) return 1; + } while(*(str0++) && *(str1++)); + return 0; +} + +int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept +{ + if(len > 0) + { + do { + const int diff{to_upper(*str0) - to_upper(*str1)}; + if(diff < 0) return -1; + if(diff > 0) return 1; + } while(--len && *(str0++) && *(str1++)); + } + return 0; +} + +} // namespace al diff --git a/modules/openal-soft/common/alstring.h b/modules/openal-soft/common/alstring.h new file mode 100644 index 0000000..f5127ae --- /dev/null +++ b/modules/openal-soft/common/alstring.h @@ -0,0 +1,17 @@ +#ifndef AL_STRING_H +#define AL_STRING_H + +#include + + +namespace al { + +/* These would be better served by using a string_view-like span/view with + * case-insensitive char traits. + */ +int strcasecmp(const char *str0, const char *str1) noexcept; +int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept; + +} // namespace al + +#endif /* AL_STRING_H */ diff --git a/modules/openal-soft/common/atomic.h b/modules/openal-soft/common/atomic.h index e34f35b..5e9b04c 100644 --- a/modules/openal-soft/common/atomic.h +++ b/modules/openal-soft/common/atomic.h @@ -6,14 +6,14 @@ using RefCount = std::atomic; -inline void InitRef(RefCount *ptr, unsigned int value) -{ ptr->store(value, std::memory_order_relaxed); } -inline unsigned int ReadRef(RefCount *ptr) -{ return ptr->load(std::memory_order_acquire); } -inline unsigned int IncrementRef(RefCount *ptr) -{ return ptr->fetch_add(1u, std::memory_order_acq_rel)+1u; } -inline unsigned int DecrementRef(RefCount *ptr) -{ return ptr->fetch_sub(1u, std::memory_order_acq_rel)-1u; } +inline void InitRef(RefCount &ref, unsigned int value) +{ ref.store(value, std::memory_order_relaxed); } +inline unsigned int ReadRef(RefCount &ref) +{ return ref.load(std::memory_order_acquire); } +inline unsigned int IncrementRef(RefCount &ref) +{ return ref.fetch_add(1u, std::memory_order_acq_rel)+1u; } +inline unsigned int DecrementRef(RefCount &ref) +{ return ref.fetch_sub(1u, std::memory_order_acq_rel)-1u; } /* WARNING: A livelock is theoretically possible if another thread keeps diff --git a/modules/openal-soft/common/comptr.h b/modules/openal-soft/common/comptr.h new file mode 100644 index 0000000..3dc574e --- /dev/null +++ b/modules/openal-soft/common/comptr.h @@ -0,0 +1,68 @@ +#ifndef COMMON_COMPTR_H +#define COMMON_COMPTR_H + +#include +#include + +#include "opthelpers.h" + + +template +class ComPtr { + T *mPtr{nullptr}; + +public: + ComPtr() noexcept = default; + ComPtr(const ComPtr &rhs) : mPtr{rhs.mPtr} { if(mPtr) mPtr->AddRef(); } + ComPtr(ComPtr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; } + ComPtr(std::nullptr_t) noexcept { } + explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { } + ~ComPtr() { if(mPtr) mPtr->Release(); } + + ComPtr& operator=(const ComPtr &rhs) + { + if(!rhs.mPtr) + { + if(mPtr) + mPtr->Release(); + mPtr = nullptr; + } + else + { + rhs.mPtr->AddRef(); + try { + if(mPtr) + mPtr->Release(); + mPtr = rhs.mPtr; + } + catch(...) { + rhs.mPtr->Release(); + throw; + } + } + return *this; + } + ComPtr& operator=(ComPtr&& rhs) + { + if(likely(&rhs != this)) + { + if(mPtr) mPtr->Release(); + mPtr = std::exchange(rhs.mPtr, nullptr); + } + return *this; + } + + explicit operator bool() const noexcept { return mPtr != nullptr; } + + T& operator*() const noexcept { return *mPtr; } + T* operator->() const noexcept { return mPtr; } + T* get() const noexcept { return mPtr; } + T** getPtr() noexcept { return &mPtr; } + + T* release() noexcept { return std::exchange(mPtr, nullptr); } + + void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); } + void swap(ComPtr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); } +}; + +#endif diff --git a/modules/openal-soft/common/dynload.cpp b/modules/openal-soft/common/dynload.cpp new file mode 100644 index 0000000..f1c2a7e --- /dev/null +++ b/modules/openal-soft/common/dynload.cpp @@ -0,0 +1,44 @@ + +#include "config.h" + +#include "dynload.h" + +#include "strutils.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include + +void *LoadLib(const char *name) +{ + std::wstring wname{utf8_to_wstr(name)}; + return LoadLibraryW(wname.c_str()); +} +void CloseLib(void *handle) +{ FreeLibrary(static_cast(handle)); } +void *GetSymbol(void *handle, const char *name) +{ return reinterpret_cast(GetProcAddress(static_cast(handle), name)); } + +#elif defined(HAVE_DLFCN_H) + +#include + +void *LoadLib(const char *name) +{ + dlerror(); + void *handle{dlopen(name, RTLD_NOW)}; + const char *err{dlerror()}; + if(err) handle = nullptr; + return handle; +} +void CloseLib(void *handle) +{ dlclose(handle); } +void *GetSymbol(void *handle, const char *name) +{ + dlerror(); + void *sym{dlsym(handle, name)}; + const char *err{dlerror()}; + if(err) sym = nullptr; + return sym; +} +#endif diff --git a/modules/openal-soft/common/dynload.h b/modules/openal-soft/common/dynload.h new file mode 100644 index 0000000..bd9e86f --- /dev/null +++ b/modules/openal-soft/common/dynload.h @@ -0,0 +1,14 @@ +#ifndef AL_DYNLOAD_H +#define AL_DYNLOAD_H + +#if defined(_WIN32) || defined(HAVE_DLFCN_H) + +#define HAVE_DYNLOAD + +void *LoadLib(const char *name); +void CloseLib(void *handle); +void *GetSymbol(void *handle, const char *name); + +#endif + +#endif /* AL_DYNLOAD_H */ diff --git a/modules/openal-soft/common/intrusive_ptr.h b/modules/openal-soft/common/intrusive_ptr.h new file mode 100644 index 0000000..9e206a6 --- /dev/null +++ b/modules/openal-soft/common/intrusive_ptr.h @@ -0,0 +1,120 @@ +#ifndef INTRUSIVE_PTR_H +#define INTRUSIVE_PTR_H + +#include + +#include "atomic.h" +#include "opthelpers.h" + + +namespace al { + +template +class intrusive_ref { + RefCount mRef{1u}; + +public: + unsigned int add_ref() noexcept { return IncrementRef(mRef); } + unsigned int release() noexcept + { + auto ref = DecrementRef(mRef); + if UNLIKELY(ref == 0) + delete static_cast(this); + return ref; + } + + /** + * Release only if doing so would not bring the object to 0 references and + * delete it. Returns false if the object could not be released. + * + * NOTE: The caller is responsible for handling a failed release, as it + * means the object has no other references and needs to be be deleted + * somehow. + */ + bool releaseIfNoDelete() noexcept + { + auto val = mRef.load(std::memory_order_acquire); + while(val > 1 && !mRef.compare_exchange_strong(val, val-1, std::memory_order_acq_rel)) + { + /* val was updated with the current value on failure, so just try + * again. + */ + } + + return val >= 2; + } +}; + + +template +class intrusive_ptr { + T *mPtr{nullptr}; + +public: + intrusive_ptr() noexcept = default; + intrusive_ptr(const intrusive_ptr &rhs) noexcept : mPtr{rhs.mPtr} + { if(mPtr) mPtr->add_ref(); } + intrusive_ptr(intrusive_ptr&& rhs) noexcept : mPtr{rhs.mPtr} + { rhs.mPtr = nullptr; } + intrusive_ptr(std::nullptr_t) noexcept { } + explicit intrusive_ptr(T *ptr) noexcept : mPtr{ptr} { } + ~intrusive_ptr() { if(mPtr) mPtr->release(); } + + intrusive_ptr& operator=(const intrusive_ptr &rhs) noexcept + { + static_assert(noexcept(std::declval()->release()), "release must be noexcept"); + + if(rhs.mPtr) rhs.mPtr->add_ref(); + if(mPtr) mPtr->release(); + mPtr = rhs.mPtr; + return *this; + } + intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept + { + if(likely(&rhs != this)) + { + if(mPtr) mPtr->release(); + mPtr = std::exchange(rhs.mPtr, nullptr); + } + return *this; + } + + explicit operator bool() const noexcept { return mPtr != nullptr; } + + T& operator*() const noexcept { return *mPtr; } + T* operator->() const noexcept { return mPtr; } + T* get() const noexcept { return mPtr; } + + void reset(T *ptr=nullptr) noexcept + { + if(mPtr) + mPtr->release(); + mPtr = ptr; + } + + T* release() noexcept { return std::exchange(mPtr, nullptr); } + + void swap(intrusive_ptr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); } + void swap(intrusive_ptr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); } +}; + +#define AL_DECL_OP(op) \ +template \ +inline bool operator op(const intrusive_ptr &lhs, const T *rhs) noexcept \ +{ return lhs.get() op rhs; } \ +template \ +inline bool operator op(const T *lhs, const intrusive_ptr &rhs) noexcept \ +{ return lhs op rhs.get(); } + +AL_DECL_OP(==) +AL_DECL_OP(!=) +AL_DECL_OP(<=) +AL_DECL_OP(>=) +AL_DECL_OP(<) +AL_DECL_OP(>) + +#undef AL_DECL_OP + +} // namespace al + +#endif /* INTRUSIVE_PTR_H */ diff --git a/modules/openal-soft/common/math_defs.h b/modules/openal-soft/common/math_defs.h deleted file mode 100644 index 9749bd5..0000000 --- a/modules/openal-soft/common/math_defs.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef AL_MATH_DEFS_H -#define AL_MATH_DEFS_H - -#include - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -constexpr inline float Deg2Rad(float x) noexcept { return x * static_cast(M_PI/180.0); } -constexpr inline float Rad2Deg(float x) noexcept { return x * static_cast(180.0/M_PI); } - -namespace al { - -template -struct MathDefs { }; - -template<> -struct MathDefs { - static constexpr inline float Pi() noexcept { return 3.14159265358979323846f; } - static constexpr inline float Tau() noexcept { return 3.14159265358979323846f * 2.0f; } - static constexpr inline float Sqrt3() noexcept { return 1.73205080756887719318f; } -}; - -template<> -struct MathDefs { - static constexpr inline double Pi() noexcept { return 3.14159265358979323846; } - static constexpr inline double Tau() noexcept { return 3.14159265358979323846 * 2.0; } - static constexpr inline double Sqrt3() noexcept { return 1.73205080756887719318; } -}; - -} // namespace al - -#endif /* AL_MATH_DEFS_H */ diff --git a/modules/openal-soft/common/opthelpers.h b/modules/openal-soft/common/opthelpers.h index c83229f..e46c0f3 100644 --- a/modules/openal-soft/common/opthelpers.h +++ b/modules/openal-soft/common/opthelpers.h @@ -1,6 +1,10 @@ #ifndef OPTHELPERS_H #define OPTHELPERS_H +#include +#include + + #ifdef __has_builtin #define HAS_BUILTIN __has_builtin #else @@ -8,32 +12,76 @@ #endif #ifdef __GNUC__ -/* LIKELY optimizes the case where the condition is true. The condition is not - * required to be true, but it can result in more optimal code for the true - * path at the expense of a less optimal false path. +#define force_inline [[gnu::always_inline]] +#elif defined(_MSC_VER) +#define force_inline __forceinline +#else +#define force_inline inline +#endif + +#if defined(__GNUC__) || HAS_BUILTIN(__builtin_expect) +/* likely() optimizes for the case where the condition is true. The condition + * is not required to be true, but it can result in more optimal code for the + * true path at the expense of a less optimal false path. */ -#define LIKELY(x) __builtin_expect(!!(x), !false) -/* The opposite of LIKELY, optimizing the case where the condition is false. */ -#define UNLIKELY(x) __builtin_expect(!!(x), false) +template +force_inline constexpr bool likely(T&& expr) noexcept +{ return __builtin_expect(static_cast(std::forward(expr)), true); } +/* The opposite of likely(), optimizing for the case where the condition is + * false. + */ +template +force_inline constexpr bool unlikely(T&& expr) noexcept +{ return __builtin_expect(static_cast(std::forward(expr)), false); } + +#else + +template +force_inline constexpr bool likely(T&& expr) noexcept +{ return static_cast(std::forward(expr)); } +template +force_inline constexpr bool unlikely(T&& expr) noexcept +{ return static_cast(std::forward(expr)); } +#endif +#define LIKELY(x) (likely(x)) +#define UNLIKELY(x) (unlikely(x)) + +#if HAS_BUILTIN(__builtin_assume) /* Unlike LIKELY, ASSUME requires the condition to be true or else it invokes * undefined behavior. It's essentially an assert without actually checking the * condition at run-time, allowing for stronger optimizations than LIKELY. */ -#if HAS_BUILTIN(__builtin_assume) #define ASSUME __builtin_assume +#elif defined(_MSC_VER) +#define ASSUME __assume +#elif defined(__GNUC__) +#define ASSUME(x) do { if(x) break; __builtin_unreachable(); } while(0) #else -#define ASSUME(x) do { if(!(x)) __builtin_unreachable(); } while(0) +#define ASSUME(x) ((void)0) #endif -#else /* __GNUC__ */ +namespace al { -#define LIKELY(x) (!!(x)) -#define UNLIKELY(x) (!!(x)) -#ifdef _MSC_VER -#define ASSUME __assume +template +force_inline constexpr auto assume_aligned(T *ptr) noexcept +{ +#ifdef __cpp_lib_assume_aligned + return std::assume_aligned(ptr); +#elif defined(__clang__) || (defined(__GNUC__) && !defined(__ICC)) + return static_cast(__builtin_assume_aligned(ptr, alignment)); +#elif defined(_MSC_VER) + constexpr std::size_t alignment_mask{(1<(ptr)&alignment_mask) == 0) + return ptr; + __assume(0); +#elif defined(__ICC) + __assume_aligned(ptr, alignment); + return ptr; #else -#define ASSUME(x) ((void)0) -#endif /* _MSC_VER */ -#endif /* __GNUC__ */ + return ptr; +#endif +} + +} // namespace al #endif /* OPTHELPERS_H */ diff --git a/modules/openal-soft/common/phase_shifter.h b/modules/openal-soft/common/phase_shifter.h new file mode 100644 index 0000000..ace92c9 --- /dev/null +++ b/modules/openal-soft/common/phase_shifter.h @@ -0,0 +1,314 @@ +#ifndef PHASE_SHIFTER_H +#define PHASE_SHIFTER_H + +#ifdef HAVE_SSE_INTRINSICS +#include +#elif defined(HAVE_NEON) +#include +#endif + +#include +#include + +#include "alcomplex.h" +#include "alspan.h" + + +/* Implements a wide-band +90 degree phase-shift. Note that this should be + * given one sample less of a delay (FilterSize/2 - 1) compared to the direct + * signal delay (FilterSize/2) to properly align. + */ +template +struct PhaseShifterT { + static_assert(FilterSize >= 16, "FilterSize needs to be at least 16"); + static_assert((FilterSize&(FilterSize-1)) == 0, "FilterSize needs to be power-of-two"); + + alignas(16) std::array mCoeffs{}; + + /* Some notes on this filter construction. + * + * A wide-band phase-shift filter needs a delay to maintain linearity. A + * dirac impulse in the center of a time-domain buffer represents a filter + * passing all frequencies through as-is with a pure delay. Converting that + * to the frequency domain, adjusting the phase of each frequency bin by + * +90 degrees, then converting back to the time domain, results in a FIR + * filter that applies a +90 degree wide-band phase-shift. + * + * A particularly notable aspect of the time-domain filter response is that + * every other coefficient is 0. This allows doubling the effective size of + * the filter, by storing only the non-0 coefficients and double-stepping + * over the input to apply it. + * + * Additionally, the resulting filter is independent of the sample rate. + * The same filter can be applied regardless of the device's sample rate + * and achieve the same effect. + */ + PhaseShifterT() + { + using complex_d = std::complex; + constexpr size_t fft_size{FilterSize}; + constexpr size_t half_size{fft_size / 2}; + + auto fftBuffer = std::make_unique(fft_size); + std::fill_n(fftBuffer.get(), fft_size, complex_d{}); + fftBuffer[half_size] = 1.0; + + forward_fft({fftBuffer.get(), fft_size}); + for(size_t i{0};i < half_size+1;++i) + fftBuffer[i] = complex_d{-fftBuffer[i].imag(), fftBuffer[i].real()}; + for(size_t i{half_size+1};i < fft_size;++i) + fftBuffer[i] = std::conj(fftBuffer[fft_size - i]); + inverse_fft({fftBuffer.get(), fft_size}); + + auto fftiter = fftBuffer.get() + half_size + (FilterSize/2 - 1); + for(float &coeff : mCoeffs) + { + coeff = static_cast(fftiter->real() / double{fft_size}); + fftiter -= 2; + } + } + + void process(al::span dst, const float *RESTRICT src) const; + void processAccum(al::span dst, const float *RESTRICT src) const; + +private: +#if defined(HAVE_NEON) + /* There doesn't seem to be NEON intrinsics to do this kind of stipple + * shuffling, so there's two custom methods for it. + */ + static auto shuffle_2020(float32x4_t a, float32x4_t b) + { + float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 0))}; + ret = vsetq_lane_f32(vgetq_lane_f32(a, 2), ret, 1); + ret = vsetq_lane_f32(vgetq_lane_f32(b, 0), ret, 2); + ret = vsetq_lane_f32(vgetq_lane_f32(b, 2), ret, 3); + return ret; + } + static auto shuffle_3131(float32x4_t a, float32x4_t b) + { + float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 1))}; + ret = vsetq_lane_f32(vgetq_lane_f32(a, 3), ret, 1); + ret = vsetq_lane_f32(vgetq_lane_f32(b, 1), ret, 2); + ret = vsetq_lane_f32(vgetq_lane_f32(b, 3), ret, 3); + return ret; + } + static auto unpacklo(float32x4_t a, float32x4_t b) + { + float32x2x2_t result{vzip_f32(vget_low_f32(a), vget_low_f32(b))}; + 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]); + } + static auto load4(float32_t a, float32_t b, float32_t c, float32_t d) + { + float32x4_t ret{vmovq_n_f32(a)}; + ret = vsetq_lane_f32(b, ret, 1); + ret = vsetq_lane_f32(c, ret, 2); + ret = vsetq_lane_f32(d, ret, 3); + return ret; + } +#endif +}; + +template +inline void PhaseShifterT::process(al::span dst, const float *RESTRICT src) const +{ +#ifdef HAVE_SSE_INTRINSICS + if(size_t todo{dst.size()>>1}) + { + auto *out = reinterpret_cast<__m64*>(dst.data()); + do { + __m128 r04{_mm_setzero_ps()}; + __m128 r14{_mm_setzero_ps()}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; + const __m128 s0{_mm_loadu_ps(&src[j*2])}; + const __m128 s1{_mm_loadu_ps(&src[j*2 + 4])}; + + __m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))}; + r04 = _mm_add_ps(r04, _mm_mul_ps(s, coeffs)); + + s = _mm_shuffle_ps(s0, s1, _MM_SHUFFLE(3, 1, 3, 1)); + r14 = _mm_add_ps(r14, _mm_mul_ps(s, coeffs)); + } + src += 2; + + __m128 r4{_mm_add_ps(_mm_unpackhi_ps(r04, r14), _mm_unpacklo_ps(r04, r14))}; + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + + _mm_storel_pi(out, r4); + ++out; + } while(--todo); + } + if((dst.size()&1)) + { + __m128 r4{_mm_setzero_ps()}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; + const __m128 s{_mm_setr_ps(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])}; + r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); + } + r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + + dst.back() = _mm_cvtss_f32(r4); + } + +#elif defined(HAVE_NEON) + + size_t pos{0}; + if(size_t todo{dst.size()>>1}) + { + do { + float32x4_t r04{vdupq_n_f32(0.0f)}; + float32x4_t r14{vdupq_n_f32(0.0f)}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; + const float32x4_t s0{vld1q_f32(&src[j*2])}; + const float32x4_t s1{vld1q_f32(&src[j*2 + 4])}; + + r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs); + r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs); + } + src += 2; + + float32x4_t r4{vaddq_f32(unpackhi(r04, r14), unpacklo(r04, r14))}; + float32x2_t r2{vadd_f32(vget_low_f32(r4), vget_high_f32(r4))}; + + vst1_f32(&dst[pos], r2); + pos += 2; + } while(--todo); + } + if((dst.size()&1)) + { + float32x4_t r4{vdupq_n_f32(0.0f)}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; + const float32x4_t s{load4(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])}; + r4 = vmlaq_f32(r4, s, coeffs); + } + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + dst[pos] = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + } + +#else + + for(float &output : dst) + { + float ret{0.0f}; + for(size_t j{0};j < mCoeffs.size();++j) + ret += src[j*2] * mCoeffs[j]; + + output = ret; + ++src; + } +#endif +} + +template +inline void PhaseShifterT::processAccum(al::span dst, const float *RESTRICT src) const +{ +#ifdef HAVE_SSE_INTRINSICS + if(size_t todo{dst.size()>>1}) + { + auto *out = reinterpret_cast<__m64*>(dst.data()); + do { + __m128 r04{_mm_setzero_ps()}; + __m128 r14{_mm_setzero_ps()}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; + const __m128 s0{_mm_loadu_ps(&src[j*2])}; + const __m128 s1{_mm_loadu_ps(&src[j*2 + 4])}; + + __m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))}; + r04 = _mm_add_ps(r04, _mm_mul_ps(s, coeffs)); + + s = _mm_shuffle_ps(s0, s1, _MM_SHUFFLE(3, 1, 3, 1)); + r14 = _mm_add_ps(r14, _mm_mul_ps(s, coeffs)); + } + src += 2; + + __m128 r4{_mm_add_ps(_mm_unpackhi_ps(r04, r14), _mm_unpacklo_ps(r04, r14))}; + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + + _mm_storel_pi(out, _mm_add_ps(_mm_loadl_pi(_mm_undefined_ps(), out), r4)); + ++out; + } while(--todo); + } + if((dst.size()&1)) + { + __m128 r4{_mm_setzero_ps()}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; + const __m128 s{_mm_setr_ps(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])}; + r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); + } + r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + + dst.back() += _mm_cvtss_f32(r4); + } + +#elif defined(HAVE_NEON) + + size_t pos{0}; + if(size_t todo{dst.size()>>1}) + { + do { + float32x4_t r04{vdupq_n_f32(0.0f)}; + float32x4_t r14{vdupq_n_f32(0.0f)}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; + const float32x4_t s0{vld1q_f32(&src[j*2])}; + const float32x4_t s1{vld1q_f32(&src[j*2 + 4])}; + + r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs); + r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs); + } + src += 2; + + float32x4_t r4{vaddq_f32(unpackhi(r04, r14), unpacklo(r04, r14))}; + float32x2_t r2{vadd_f32(vget_low_f32(r4), vget_high_f32(r4))}; + + vst1_f32(&dst[pos], vadd_f32(vld1_f32(&dst[pos]), r2)); + pos += 2; + } while(--todo); + } + if((dst.size()&1)) + { + float32x4_t r4{vdupq_n_f32(0.0f)}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; + const float32x4_t s{load4(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])}; + r4 = vmlaq_f32(r4, s, coeffs); + } + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + dst[pos] += vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + } + +#else + + for(float &output : dst) + { + float ret{0.0f}; + for(size_t j{0};j < mCoeffs.size();++j) + ret += src[j*2] * mCoeffs[j]; + + output += ret; + ++src; + } +#endif +} + +#endif /* PHASE_SHIFTER_H */ diff --git a/modules/openal-soft/common/polyphase_resampler.cpp b/modules/openal-soft/common/polyphase_resampler.cpp new file mode 100644 index 0000000..bb8f69a --- /dev/null +++ b/modules/openal-soft/common/polyphase_resampler.cpp @@ -0,0 +1,222 @@ + +#include "polyphase_resampler.h" + +#include +#include + +#include "alnumbers.h" +#include "opthelpers.h" + + +namespace { + +constexpr double Epsilon{1e-9}; + +using uint = unsigned int; + +/* This is the normalized cardinal sine (sinc) function. + * + * sinc(x) = { 1, x = 0 + * { sin(pi x) / (pi x), otherwise. + */ +double Sinc(const double x) +{ + if(unlikely(std::abs(x) < Epsilon)) + return 1.0; + return std::sin(al::numbers::pi*x) / (al::numbers::pi*x); +} + +/* The zero-order modified Bessel function of the first kind, used for the + * Kaiser window. + * + * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k) + * = sum_{k=0}^inf ((x / 2)^k / k!)^2 + */ +constexpr double BesselI_0(const double x) +{ + // Start at k=1 since k=0 is trivial. + const double x2{x/2.0}; + double term{1.0}; + double sum{1.0}; + int k{1}; + + // Let the integration converge until the term of the sum is no longer + // significant. + double last_sum{}; + do { + const double y{x2 / k}; + ++k; + last_sum = sum; + term *= y * y; + sum += term; + } while(sum != last_sum); + return sum; +} + +/* Calculate a Kaiser window from the given beta value and a normalized k + * [-1, 1]. + * + * w(k) = { I_0(B sqrt(1 - k^2)) / I_0(B), -1 <= k <= 1 + * { 0, elsewhere. + * + * Where k can be calculated as: + * + * k = i / l, where -l <= i <= l. + * + * or: + * + * k = 2 i / M - 1, where 0 <= i <= M. + */ +double Kaiser(const double b, const double k) +{ + if(!(k >= -1.0 && k <= 1.0)) + return 0.0; + return BesselI_0(b * std::sqrt(1.0 - k*k)) / BesselI_0(b); +} + +// Calculates the greatest common divisor of a and b. +constexpr uint Gcd(uint x, uint y) +{ + while(y > 0) + { + const uint z{y}; + y = x % y; + x = z; + } + return x; +} + +/* Calculates the size (order) of the Kaiser window. Rejection is in dB and + * the transition width is normalized frequency (0.5 is nyquist). + * + * M = { ceil((r - 7.95) / (2.285 2 pi f_t)), r > 21 + * { ceil(5.79 / 2 pi f_t), r <= 21. + * + */ +constexpr uint CalcKaiserOrder(const double rejection, const double transition) +{ + const double w_t{2.0 * al::numbers::pi * transition}; + if LIKELY(rejection > 21.0) + return static_cast(std::ceil((rejection - 7.95) / (2.285 * w_t))); + return static_cast(std::ceil(5.79 / w_t)); +} + +// Calculates the beta value of the Kaiser window. Rejection is in dB. +constexpr double CalcKaiserBeta(const double rejection) +{ + if LIKELY(rejection > 50.0) + return 0.1102 * (rejection - 8.7); + if(rejection >= 21.0) + return (0.5842 * std::pow(rejection - 21.0, 0.4)) + + (0.07886 * (rejection - 21.0)); + return 0.0; +} + +/* Calculates a point on the Kaiser-windowed sinc filter for the given half- + * width, beta, gain, and cutoff. The point is specified in non-normalized + * samples, from 0 to M, where M = (2 l + 1). + * + * w(k) 2 p f_t sinc(2 f_t x) + * + * x -- centered sample index (i - l) + * k -- normalized and centered window index (x / l) + * w(k) -- window function (Kaiser) + * p -- gain compensation factor when sampling + * f_t -- normalized center frequency (or cutoff; 0.5 is nyquist) + */ +double SincFilter(const uint l, const double b, const double gain, const double cutoff, + const uint i) +{ + const double x{static_cast(i) - l}; + return Kaiser(b, x / l) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x); +} + +} // namespace + +// Calculate the resampling metrics and build the Kaiser-windowed sinc filter +// that's used to cut frequencies above the destination nyquist. +void PPhaseResampler::init(const uint srcRate, const uint dstRate) +{ + const uint gcd{Gcd(srcRate, dstRate)}; + 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. + */ + double cutoff, width; + if(mP > mQ) + { + cutoff = 0.475 / mP; + width = 0.05 / mP; + } + else + { + cutoff = 0.475 / mQ; + width = 0.05 / mQ; + } + // A rejection of -180 dB is used for the stop band. Round up when + // calculating the left offset to avoid increasing the transition width. + const uint l{(CalcKaiserOrder(180.0, width)+1) / 2}; + const double beta{CalcKaiserBeta(180.0)}; + mM = l*2 + 1; + mL = l; + mF.resize(mM); + for(uint i{0};i < mM;i++) + mF[i] = SincFilter(l, beta, mP, cutoff, i); +} + +// Perform the upsample-filter-downsample resampling operation using a +// polyphase filter implementation. +void PPhaseResampler::process(const uint inN, const double *in, const uint outN, double *out) +{ + if UNLIKELY(outN == 0) + return; + + // Handle in-place operation. + std::vector workspace; + double *work{out}; + if UNLIKELY(work == in) + { + workspace.resize(outN); + work = workspace.data(); + } + + // Resample the input. + const uint p{mP}, q{mQ}, m{mM}, l{mL}; + const double *f{mF.data()}; + for(uint i{0};i < outN;i++) + { + // Input starts at l to compensate for the filter delay. This will + // drop any build-up from the first half of the filter. + size_t j_f{(l + q*i) % p}; + size_t j_s{(l + q*i) / p}; + + // Only take input when 0 <= j_s < inN. + double r{0.0}; + if LIKELY(j_f < m) + { + size_t filt_len{(m-j_f+p-1) / p}; + if LIKELY(j_s+1 > inN) + { + size_t skip{std::min(j_s+1 - inN, filt_len)}; + j_f += p*skip; + j_s -= skip; + filt_len -= skip; + } + if(size_t todo{std::min(j_s+1, filt_len)}) + { + do { + r += f[j_f] * in[j_s]; + j_f += p; + --j_s; + } while(--todo); + } + } + work[i] = r; + } + // Clean up after in-place operation. + if(work != out) + std::copy_n(work, outN, out); +} diff --git a/modules/openal-soft/common/polyphase_resampler.h b/modules/openal-soft/common/polyphase_resampler.h new file mode 100644 index 0000000..e9833d1 --- /dev/null +++ b/modules/openal-soft/common/polyphase_resampler.h @@ -0,0 +1,45 @@ +#ifndef POLYPHASE_RESAMPLER_H +#define POLYPHASE_RESAMPLER_H + +#include + + +/* This is a polyphase sinc-filtered resampler. It is built for very high + * quality results, rather than real-time performance. + * + * Upsample Downsample + * + * p/q = 3/2 p/q = 3/5 + * + * M-+-+-+-> M-+-+-+-> + * -------------------+ ---------------------+ + * p s * f f f f|f| | p s * f f f f f | + * | 0 * 0 0 0|0|0 | | 0 * 0 0 0 0|0| | + * v 0 * 0 0|0|0 0 | v 0 * 0 0 0|0|0 | + * s * f|f|f f f | s * f f|f|f f | + * 0 * |0|0 0 0 0 | 0 * 0|0|0 0 0 | + * --------+=+--------+ 0 * |0|0 0 0 0 | + * d . d .|d|. d . d ----------+=+--------+ + * d . . . .|d|. . . . + * q-> + * q-+-+-+-> + * + * P_f(i,j) = q i mod p + pj + * P_s(i,j) = floor(q i / p) - j + * d[i=0..N-1] = sum_{j=0}^{floor((M - 1) / p)} { + * { f[P_f(i,j)] s[P_s(i,j)], P_f(i,j) < M + * { 0, P_f(i,j) >= M. } + */ + +struct PPhaseResampler { + using uint = unsigned int; + + void init(const uint srcRate, const uint dstRate); + void process(const uint inN, const double *in, const uint outN, double *out); + +private: + uint mP, mQ, mM, mL; + std::vector mF; +}; + +#endif /* POLYPHASE_RESAMPLER_H */ diff --git a/modules/openal-soft/common/pragmadefs.h b/modules/openal-soft/common/pragmadefs.h new file mode 100644 index 0000000..9f0a711 --- /dev/null +++ b/modules/openal-soft/common/pragmadefs.h @@ -0,0 +1,21 @@ +#ifndef PRAGMADEFS_H +#define PRAGMADEFS_H + +#if defined(_MSC_VER) +#define DIAGNOSTIC_PUSH __pragma(warning(push)) +#define DIAGNOSTIC_POP __pragma(warning(pop)) +#define std_pragma(...) +#define msc_pragma __pragma +#else +#if defined(__GNUC__) || defined(__clang__) +#define DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") +#define DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#else +#define DIAGNOSTIC_PUSH +#define DIAGNOSTIC_POP +#endif +#define std_pragma _Pragma +#define msc_pragma(...) +#endif + +#endif /* PRAGMADEFS_H */ diff --git a/modules/openal-soft/Alc/ringbuffer.cpp b/modules/openal-soft/common/ringbuffer.cpp similarity index 70% rename from modules/openal-soft/Alc/ringbuffer.cpp rename to modules/openal-soft/common/ringbuffer.cpp index 386341a..0aec1d4 100644 --- a/modules/openal-soft/Alc/ringbuffer.cpp +++ b/modules/openal-soft/common/ringbuffer.cpp @@ -20,20 +20,16 @@ #include "config.h" -#include -#include -#include +#include "ringbuffer.h" #include +#include +#include -#include "ringbuffer.h" -#include "atomic.h" -#include "threads.h" #include "almalloc.h" -#include "compat.h" -RingBufferPtr CreateRingBuffer(size_t sz, size_t elem_sz, int limit_writes) +RingBufferPtr RingBuffer::Create(size_t sz, size_t elem_sz, int limit_writes) { size_t power_of_two{0u}; if(sz > 0) @@ -49,9 +45,11 @@ RingBufferPtr CreateRingBuffer(size_t sz, size_t elem_sz, int limit_writes) #endif } ++power_of_two; - if(power_of_two < sz) return nullptr; + if(power_of_two <= sz || power_of_two > std::numeric_limits::max()/elem_sz) + throw std::overflow_error{"Ring buffer size overflow"}; - RingBufferPtr rb{new (al_malloc(16, sizeof(*rb) + power_of_two*elem_sz)) RingBuffer()}; + const size_t bufbytes{power_of_two * elem_sz}; + RingBufferPtr rb{new(FamCount(bufbytes)) RingBuffer{bufbytes}}; rb->mWriteSize = limit_writes ? sz : (power_of_two-1); rb->mSizeMask = power_of_two - 1; rb->mElemSize = elem_sz; @@ -63,22 +61,7 @@ void RingBuffer::reset() noexcept { mWritePtr.store(0, std::memory_order_relaxed); mReadPtr.store(0, std::memory_order_relaxed); - std::fill_n(mBuffer, (mSizeMask+1)*mElemSize, 0); -} - - -size_t RingBuffer::readSpace() const noexcept -{ - size_t w = mWritePtr.load(std::memory_order_acquire); - size_t r = mReadPtr.load(std::memory_order_acquire); - return (w-r) & mSizeMask; -} - -size_t RingBuffer::writeSpace() const noexcept -{ - size_t w = mWritePtr.load(std::memory_order_acquire); - size_t r = mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask; - return (r-w-1) & mSizeMask; + std::fill_n(mBuffer.begin(), (mSizeMask+1)*mElemSize, al::byte{}); } @@ -103,11 +86,12 @@ size_t RingBuffer::read(void *dest, size_t cnt) noexcept n2 = 0; } - memcpy(dest, mBuffer + read_ptr*mElemSize, n1*mElemSize); + auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize, + static_cast(dest)); read_ptr += n1; if(n2 > 0) { - memcpy(static_cast(dest) + n1*mElemSize, mBuffer, n2*mElemSize); + std::copy_n(mBuffer.begin(), n2*mElemSize, outiter); read_ptr += n2; } mReadPtr.store(read_ptr, std::memory_order_release); @@ -135,9 +119,10 @@ size_t RingBuffer::peek(void *dest, size_t cnt) const noexcept n2 = 0; } - memcpy(dest, mBuffer + read_ptr*mElemSize, n1*mElemSize); + auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize, + static_cast(dest)); if(n2 > 0) - memcpy(static_cast(dest) + n1*mElemSize, mBuffer, n2*mElemSize); + std::copy_n(mBuffer.begin(), n2*mElemSize, outiter); return to_read; } @@ -162,11 +147,12 @@ size_t RingBuffer::write(const void *src, size_t cnt) noexcept n2 = 0; } - memcpy(mBuffer + write_ptr*mElemSize, src, n1*mElemSize); + auto srcbytes = static_cast(src); + std::copy_n(srcbytes, n1*mElemSize, mBuffer.begin() + write_ptr*mElemSize); write_ptr += n1; if(n2 > 0) { - memcpy(mBuffer, static_cast(src) + n1*mElemSize, n2*mElemSize); + std::copy_n(srcbytes + n1*mElemSize, n2*mElemSize, mBuffer.begin()); write_ptr += n2; } mWritePtr.store(write_ptr, std::memory_order_release); @@ -174,20 +160,9 @@ size_t RingBuffer::write(const void *src, size_t cnt) noexcept } -void RingBuffer::readAdvance(size_t cnt) noexcept -{ - mReadPtr.fetch_add(cnt, std::memory_order_acq_rel); -} - -void RingBuffer::writeAdvance(size_t cnt) noexcept -{ - mWritePtr.fetch_add(cnt, std::memory_order_acq_rel); -} - - -ll_ringbuffer_data_pair RingBuffer::getReadVector() const noexcept +auto RingBuffer::getReadVector() const noexcept -> DataPair { - ll_ringbuffer_data_pair ret; + DataPair ret; size_t w{mWritePtr.load(std::memory_order_acquire)}; size_t r{mReadPtr.load(std::memory_order_acquire)}; @@ -200,15 +175,15 @@ ll_ringbuffer_data_pair RingBuffer::getReadVector() const noexcept { /* Two part vector: the rest of the buffer after the current read ptr, * plus some from the start of the buffer. */ - ret.first.buf = const_cast(&mBuffer[r*mElemSize]); + ret.first.buf = const_cast(mBuffer.data() + r*mElemSize); ret.first.len = mSizeMask+1 - r; - ret.second.buf = const_cast(mBuffer); + ret.second.buf = const_cast(mBuffer.data()); ret.second.len = cnt2 & mSizeMask; } else { /* Single part vector: just the rest of the buffer */ - ret.first.buf = const_cast(&mBuffer[r*mElemSize]); + ret.first.buf = const_cast(mBuffer.data() + r*mElemSize); ret.first.len = free_cnt; ret.second.buf = nullptr; ret.second.len = 0; @@ -217,9 +192,9 @@ ll_ringbuffer_data_pair RingBuffer::getReadVector() const noexcept return ret; } -ll_ringbuffer_data_pair RingBuffer::getWriteVector() const noexcept +auto RingBuffer::getWriteVector() const noexcept -> DataPair { - ll_ringbuffer_data_pair ret; + DataPair ret; size_t w{mWritePtr.load(std::memory_order_acquire)}; size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask}; @@ -232,14 +207,14 @@ ll_ringbuffer_data_pair RingBuffer::getWriteVector() const noexcept { /* Two part vector: the rest of the buffer after the current write ptr, * plus some from the start of the buffer. */ - ret.first.buf = const_cast(&mBuffer[w*mElemSize]); + ret.first.buf = const_cast(mBuffer.data() + w*mElemSize); ret.first.len = mSizeMask+1 - w; - ret.second.buf = const_cast(mBuffer); + ret.second.buf = const_cast(mBuffer.data()); ret.second.len = cnt2 & mSizeMask; } else { - ret.first.buf = const_cast(&mBuffer[w*mElemSize]); + ret.first.buf = const_cast(mBuffer.data() + w*mElemSize); ret.first.len = free_cnt; ret.second.buf = nullptr; ret.second.len = 0; diff --git a/modules/openal-soft/Alc/ringbuffer.h b/modules/openal-soft/common/ringbuffer.h similarity index 61% rename from modules/openal-soft/Alc/ringbuffer.h rename to modules/openal-soft/common/ringbuffer.h index 311477c..2a3797b 100644 --- a/modules/openal-soft/Alc/ringbuffer.h +++ b/modules/openal-soft/common/ringbuffer.h @@ -1,12 +1,12 @@ #ifndef RINGBUFFER_H #define RINGBUFFER_H -#include - #include #include +#include #include +#include "albyte.h" #include "almalloc.h" @@ -16,21 +16,25 @@ * single-consumer/single-provider operation. */ -struct ll_ringbuffer_data { - char *buf; - size_t len; -}; -using ll_ringbuffer_data_pair = std::pair; - - struct RingBuffer { +private: std::atomic mWritePtr{0u}; std::atomic mReadPtr{0u}; size_t mWriteSize{0u}; size_t mSizeMask{0u}; size_t mElemSize{0u}; - alignas(16) char mBuffer[]; + al::FlexArray mBuffer; + +public: + struct Data { + al::byte *buf; + size_t len; + }; + using DataPair = std::pair; + + + RingBuffer(const size_t count) : mBuffer{count} { } /** Reset the read and write pointers to zero. This is not thread safe. */ void reset() noexcept; @@ -40,19 +44,25 @@ struct RingBuffer { * hold the current readable data. If the readable data is in one segment * the second segment has zero length. */ - ll_ringbuffer_data_pair getReadVector() const noexcept; + DataPair getReadVector() const noexcept; /** * The non-copying data writer. Returns two ringbuffer data pointers that * hold the current writeable data. If the writeable data is in one segment * the second segment has zero length. */ - ll_ringbuffer_data_pair getWriteVector() const noexcept; + DataPair getWriteVector() const noexcept; /** * Return the number of elements available for reading. This is the number * of elements in front of the read pointer and behind the write pointer. */ - size_t readSpace() const noexcept; + size_t readSpace() const noexcept + { + const size_t w{mWritePtr.load(std::memory_order_acquire)}; + const size_t r{mReadPtr.load(std::memory_order_acquire)}; + return (w-r) & mSizeMask; + } + /** * The copying data reader. Copy at most `cnt' elements into `dest'. * Returns the actual number of elements copied. @@ -64,31 +74,42 @@ struct RingBuffer { */ size_t peek(void *dest, size_t cnt) const noexcept; /** Advance the read pointer `cnt' places. */ - void readAdvance(size_t cnt) noexcept; + void readAdvance(size_t cnt) noexcept + { mReadPtr.fetch_add(cnt, std::memory_order_acq_rel); } + /** * Return the number of elements available for writing. This is the number * of elements in front of the write pointer and behind the read pointer. */ - size_t writeSpace() const noexcept; + size_t writeSpace() const noexcept + { + const size_t w{mWritePtr.load(std::memory_order_acquire)}; + const size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask}; + return (r-w-1) & mSizeMask; + } + /** * The copying data writer. Copy at most `cnt' elements from `src'. Returns * the actual number of elements copied. */ size_t write(const void *src, size_t cnt) noexcept; /** Advance the write pointer `cnt' places. */ - void writeAdvance(size_t cnt) noexcept; + void writeAdvance(size_t cnt) noexcept + { mWritePtr.fetch_add(cnt, std::memory_order_acq_rel); } - DEF_PLACE_NEWDEL() -}; -using RingBufferPtr = std::unique_ptr; + size_t getElemSize() const noexcept { return mElemSize; } + /** + * Create a new ringbuffer to hold at least `sz' elements of `elem_sz' + * bytes. The number of elements is rounded up to the next power of two + * (even if it is already a power of two, to ensure the requested amount + * can be written). + */ + static std::unique_ptr Create(size_t sz, size_t elem_sz, int limit_writes); -/** - * Create a new ringbuffer to hold at least `sz' elements of `elem_sz' bytes. - * The number of elements is rounded up to the next power of two (even if it is - * already a power of two, to ensure the requested amount can be written). - */ -RingBufferPtr CreateRingBuffer(size_t sz, size_t elem_sz, int limit_writes); + DEF_FAM_NEWDEL(RingBuffer, mBuffer) +}; +using RingBufferPtr = std::unique_ptr; #endif /* RINGBUFFER_H */ diff --git a/modules/openal-soft/common/strutils.cpp b/modules/openal-soft/common/strutils.cpp new file mode 100644 index 0000000..18c4947 --- /dev/null +++ b/modules/openal-soft/common/strutils.cpp @@ -0,0 +1,64 @@ + +#include "config.h" + +#include "strutils.h" + +#include + + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include + +std::string wstr_to_utf8(const WCHAR *wstr) +{ + std::string ret; + + int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr); + if(len > 0) + { + ret.resize(len); + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &ret[0], len, nullptr, nullptr); + ret.pop_back(); + } + + return ret; +} + +std::wstring utf8_to_wstr(const char *str) +{ + std::wstring ret; + + int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0); + if(len > 0) + { + ret.resize(len); + MultiByteToWideChar(CP_UTF8, 0, str, -1, &ret[0], len); + ret.pop_back(); + } + + return ret; +} +#endif + +namespace al { + +al::optional getenv(const char *envname) +{ + const char *str{std::getenv(envname)}; + if(str && str[0] != '\0') + return al::make_optional(str); + return al::nullopt; +} + +#ifdef _WIN32 +al::optional getenv(const WCHAR *envname) +{ + const WCHAR *str{_wgetenv(envname)}; + if(str && str[0] != L'\0') + return al::make_optional(str); + return al::nullopt; +} +#endif + +} // namespace al diff --git a/modules/openal-soft/common/strutils.h b/modules/openal-soft/common/strutils.h new file mode 100644 index 0000000..0c7a0e2 --- /dev/null +++ b/modules/openal-soft/common/strutils.h @@ -0,0 +1,24 @@ +#ifndef AL_STRUTILS_H +#define AL_STRUTILS_H + +#include + +#include "aloptional.h" + +#ifdef _WIN32 +#include + +std::string wstr_to_utf8(const wchar_t *wstr); +std::wstring utf8_to_wstr(const char *str); +#endif + +namespace al { + +al::optional getenv(const char *envname); +#ifdef _WIN32 +al::optional getenv(const wchar_t *envname); +#endif + +} // namespace al + +#endif /* AL_STRUTILS_H */ diff --git a/modules/openal-soft/common/threads.cpp b/modules/openal-soft/common/threads.cpp index 45f7324..c782dc3 100644 --- a/modules/openal-soft/common/threads.cpp +++ b/modules/openal-soft/common/threads.cpp @@ -20,13 +20,17 @@ #include "config.h" +#include "opthelpers.h" #include "threads.h" -#include #include #ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include + +#include void althrd_setname(const char *name) { @@ -42,7 +46,7 @@ void althrd_setname(const char *name) #pragma pack(pop) info.dwType = 0x1000; info.szName = name; - info.dwThreadID = -1; + info.dwThreadID = ~DWORD{0}; info.dwFlags = 0; __try { @@ -72,47 +76,61 @@ semaphore::~semaphore() void semaphore::post() { - if(!ReleaseSemaphore(mSem, 1, nullptr)) + if UNLIKELY(!ReleaseSemaphore(static_cast(mSem), 1, nullptr)) throw std::system_error(std::make_error_code(std::errc::value_too_large)); } void semaphore::wait() noexcept -{ WaitForSingleObject(mSem, INFINITE); } +{ WaitForSingleObject(static_cast(mSem), INFINITE); } bool semaphore::try_wait() noexcept -{ return WaitForSingleObject(mSem, 0) == WAIT_OBJECT_0; } +{ return WaitForSingleObject(static_cast(mSem), 0) == WAIT_OBJECT_0; } } // namespace al #else -#include #include #ifdef HAVE_PTHREAD_NP_H #include #endif +#include + +namespace { + +using setname_t1 = int(*)(const char*); +using setname_t2 = int(*)(pthread_t, const char*); +using setname_t3 = int(*)(pthread_t, const char*, void*); + +void setname_caller(setname_t1 func, const char *name) +{ func(name); } + +void setname_caller(setname_t2 func, const char *name) +{ func(pthread_self(), name); } + +void setname_caller(setname_t3 func, const char *name) +{ func(pthread_self(), "%s", static_cast(const_cast(name))); } + +} // namespace void althrd_setname(const char *name) { -#if defined(HAVE_PTHREAD_SETNAME_NP) -#if defined(PTHREAD_SETNAME_NP_ONE_PARAM) - pthread_setname_np(name); -#elif defined(PTHREAD_SETNAME_NP_THREE_PARAMS) - pthread_setname_np(pthread_self(), "%s", (void*)name); -#else - pthread_setname_np(pthread_self(), name); -#endif -#elif defined(HAVE_PTHREAD_SET_NAME_NP) - pthread_set_name_np(pthread_self(), name); -#else - (void)name; +#if defined(HAVE_PTHREAD_SET_NAME_NP) + setname_caller(pthread_set_name_np, name); +#elif defined(HAVE_PTHREAD_SETNAME_NP) + setname_caller(pthread_setname_np, name); #endif + /* Avoid unused function/parameter warnings. */ + std::ignore = name; + std::ignore = static_cast(&setname_caller); + std::ignore = static_cast(&setname_caller); + std::ignore = static_cast(&setname_caller); } -namespace al { - #ifdef __APPLE__ +namespace al { + semaphore::semaphore(unsigned int initial) { mSem = dispatch_semaphore_create(initial); @@ -132,8 +150,14 @@ void semaphore::wait() noexcept bool semaphore::try_wait() noexcept { return dispatch_semaphore_wait(mSem, DISPATCH_TIME_NOW) == 0; } +} // namespace al + #else /* !__APPLE__ */ +#include + +namespace al { + semaphore::semaphore(unsigned int initial) { if(sem_init(&mSem, 0, initial) != 0) @@ -158,8 +182,8 @@ void semaphore::wait() noexcept bool semaphore::try_wait() noexcept { return sem_trywait(&mSem) == 0; } -#endif /* __APPLE__ */ - } // namespace al +#endif /* __APPLE__ */ + #endif /* _WIN32 */ diff --git a/modules/openal-soft/common/threads.h b/modules/openal-soft/common/threads.h index 8a64039..1cdb5d8 100644 --- a/modules/openal-soft/common/threads.h +++ b/modules/openal-soft/common/threads.h @@ -1,10 +1,6 @@ #ifndef AL_THREADS_H #define AL_THREADS_H -#include - -#include - #if defined(__GNUC__) && defined(__i386__) /* force_align_arg_pointer is required for proper function arguments aligning * when SSE code is used. Some systems (Windows, QNX) do not guarantee our @@ -15,12 +11,9 @@ #define FORCE_ALIGN #endif -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#elif defined(__APPLE__) +#if defined(__APPLE__) #include -#else +#elif !defined(_WIN32) #include #endif @@ -30,7 +23,7 @@ namespace al { class semaphore { #ifdef _WIN32 - using native_type = HANDLE; + using native_type = void*; #elif defined(__APPLE__) using native_type = dispatch_semaphore_t; #else diff --git a/modules/openal-soft/common/vecmat.h b/modules/openal-soft/common/vecmat.h index ab407b1..78fd806 100644 --- a/modules/openal-soft/common/vecmat.h +++ b/modules/openal-soft/common/vecmat.h @@ -1,36 +1,32 @@ #ifndef COMMON_VECMAT_H #define COMMON_VECMAT_H -#include #include +#include +#include #include -#include -#include "math_defs.h" +#include "alspan.h" + namespace alu { -class Vector { - alignas(16) std::array mVals{}; +template +class VectorR { + static_assert(std::is_floating_point::value, "Must use floating-point types"); + alignas(16) std::array mVals; public: - constexpr Vector() noexcept = default; - constexpr Vector(float a, float b, float c, float d) noexcept - : mVals{{a, b, c, d}} - { } - Vector(const Vector &rhs) noexcept - { std::copy(rhs.mVals.begin(), rhs.mVals.end(), mVals.begin()); } + constexpr VectorR() noexcept = default; + constexpr VectorR(const VectorR&) noexcept = default; + constexpr VectorR(T a, T b, T c, T d) noexcept : mVals{{a, b, c, d}} { } - Vector& operator=(const Vector &rhs) noexcept - { - std::copy(rhs.mVals.begin(), rhs.mVals.end(), mVals.begin()); - return *this; - } + constexpr VectorR& operator=(const VectorR&) noexcept = default; - float& operator[](size_t idx) noexcept { return mVals[idx]; } - constexpr const float& operator[](size_t idx) const noexcept { return mVals[idx]; } + T& operator[](size_t idx) noexcept { return mVals[idx]; } + constexpr const T& operator[](size_t idx) const noexcept { return mVals[idx]; } - Vector& operator+=(const Vector &rhs) noexcept + VectorR& operator+=(const VectorR &rhs) noexcept { mVals[0] += rhs.mVals[0]; mVals[1] += rhs.mVals[1]; @@ -39,64 +35,92 @@ public: return *this; } - float normalize() + VectorR operator-(const VectorR &rhs) const noexcept + { + const VectorR ret{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1], + mVals[2] - rhs.mVals[2], mVals[3] - rhs.mVals[3]}; + return ret; + } + + T normalize(T limit = std::numeric_limits::epsilon()) { - const float length{std::sqrt(mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2])}; - if(length > std::numeric_limits::epsilon()) + limit = std::max(limit, std::numeric_limits::epsilon()); + const T length_sqr{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]}; + if(length_sqr > limit*limit) { - float inv_length = 1.0f/length; + const T length{std::sqrt(length_sqr)}; + T inv_length{T{1}/length}; mVals[0] *= inv_length; mVals[1] *= inv_length; mVals[2] *= inv_length; return length; } - mVals[0] = mVals[1] = mVals[2] = 0.0f; - return 0.0f; + mVals[0] = mVals[1] = mVals[2] = T{0}; + return T{0}; } + + constexpr VectorR cross_product(const alu::VectorR &rhs) const + { + return VectorR{ + (*this)[1]*rhs[2] - (*this)[2]*rhs[1], + (*this)[2]*rhs[0] - (*this)[0]*rhs[2], + (*this)[0]*rhs[1] - (*this)[1]*rhs[0], + T{0}}; + } + + constexpr T dot_product(const alu::VectorR &rhs) const + { return (*this)[0]*rhs[0] + (*this)[1]*rhs[1] + (*this)[2]*rhs[2]; } }; +using Vector = VectorR; -class Matrix { - alignas(16) std::array,4> mVals{}; +template +class MatrixR { + static_assert(std::is_floating_point::value, "Must use floating-point types"); + alignas(16) std::array mVals; public: - constexpr Matrix() noexcept = default; - constexpr Matrix(float aa, float ab, float ac, float ad, - float ba, float bb, float bc, float bd, - float ca, float cb, float cc, float cd, - float da, float db, float dc, float dd) noexcept - : mVals{{{{aa, ab, ac, ad}}, {{ba, bb, bc, bd}}, {{ca, cb, cc, cd}}, {{da, db, dc, dd}}}} + constexpr MatrixR() noexcept = default; + constexpr MatrixR(const MatrixR&) noexcept = default; + constexpr MatrixR(T aa, T ab, T ac, T ad, + T ba, T bb, T bc, T bd, + T ca, T cb, T cc, T cd, + T da, T db, T dc, T dd) noexcept + : mVals{{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd}} { } - Matrix(const Matrix &rhs) noexcept - { std::copy(rhs.mVals.begin(), rhs.mVals.end(), mVals.begin()); } - - Matrix& operator=(const Matrix &rhs) noexcept - { - std::copy(rhs.mVals.begin(), rhs.mVals.end(), mVals.begin()); - return *this; - } - std::array& operator[](size_t idx) noexcept { return mVals[idx]; } - constexpr const std::array& operator[](size_t idx) const noexcept { return mVals[idx]; } + constexpr MatrixR& operator=(const MatrixR&) noexcept = default; - void setRow(size_t idx, float a, float b, float c, float d) noexcept - { - mVals[idx][0] = a; - mVals[idx][1] = b; - mVals[idx][2] = c; - mVals[idx][3] = d; - } + auto operator[](size_t idx) noexcept { return al::span{&mVals[idx*4], 4}; } + constexpr auto operator[](size_t idx) const noexcept + { return al::span{&mVals[idx*4], 4}; } - static const Matrix &Identity() noexcept + static constexpr MatrixR Identity() noexcept { - static constexpr Matrix identity{ - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - return identity; + return MatrixR{ + T{1}, T{0}, T{0}, T{0}, + T{0}, T{1}, T{0}, T{0}, + T{0}, T{0}, T{1}, T{0}, + T{0}, T{0}, T{0}, T{1}}; } }; +using Matrix = MatrixR; + +template +inline VectorR operator*(const MatrixR &mtx, const VectorR &vec) noexcept +{ + return VectorR{ + vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0], + vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1], + vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2], + vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]}; +} + +template +inline VectorR cast_to(const VectorR &vec) noexcept +{ + return VectorR{static_cast(vec[0]), static_cast(vec[1]), + static_cast(vec[2]), static_cast(vec[3])}; +} } // namespace alu diff --git a/modules/openal-soft/Alc/vector.h b/modules/openal-soft/common/vector.h similarity index 80% rename from modules/openal-soft/Alc/vector.h rename to modules/openal-soft/common/vector.h index a7df54f..1b69d6a 100644 --- a/modules/openal-soft/Alc/vector.h +++ b/modules/openal-soft/common/vector.h @@ -7,7 +7,7 @@ namespace al { -template +template using vector = std::vector>; } // namespace al diff --git a/modules/openal-soft/common/win_main_utf8.h b/modules/openal-soft/common/win_main_utf8.h index 242d3b8..e40c215 100644 --- a/modules/openal-soft/common/win_main_utf8.h +++ b/modules/openal-soft/common/win_main_utf8.h @@ -13,10 +13,23 @@ #define WIN32_LEAN_AND_MEAN #include #include +#include + +#ifdef __cplusplus +#include + +#define STATIC_CAST(...) static_cast<__VA_ARGS__> +#define REINTERPRET_CAST(...) reinterpret_cast<__VA_ARGS__> + +#else + +#define STATIC_CAST(...) (__VA_ARGS__) +#define REINTERPRET_CAST(...) (__VA_ARGS__) +#endif static FILE *my_fopen(const char *fname, const char *mode) { - WCHAR *wname=NULL, *wmode=NULL; + wchar_t *wname=NULL, *wmode=NULL; int namelen, modelen; FILE *file = NULL; errno_t err; @@ -30,7 +43,12 @@ static FILE *my_fopen(const char *fname, const char *mode) return NULL; } - wname = (WCHAR*)calloc(sizeof(WCHAR), namelen+modelen); +#ifdef __cplusplus + auto strbuf = std::make_unique(static_cast(namelen+modelen)); + wname = strbuf.get(); +#else + wname = (wchar_t*)calloc(sizeof(wchar_t), (size_t)(namelen+modelen)); +#endif wmode = wname + namelen; MultiByteToWideChar(CP_UTF8, 0, fname, -1, wname, namelen); MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, modelen); @@ -42,56 +60,58 @@ static FILE *my_fopen(const char *fname, const char *mode) file = NULL; } +#ifndef __cplusplus free(wname); - +#endif return file; } #define fopen my_fopen -static char **arglist; -static void cleanup_arglist(void) -{ - free(arglist); -} +/* SDL overrides main and provides UTF-8 args for us. */ +#if !defined(SDL_MAIN_NEEDED) && !defined(SDL_MAIN_AVAILABLE) +int my_main(int, char**); +#define main my_main -static void GetUnicodeArgs(int *argc, char ***argv) +#ifdef __cplusplus +extern "C" +#endif +int wmain(int argc, wchar_t **wargv) { + char **argv; size_t total; - wchar_t **args; - int nargs, i; - - args = CommandLineToArgvW(GetCommandLineW(), &nargs); - if(!args) - { - fprintf(stderr, "Failed to get command line args: %ld\n", GetLastError()); - exit(EXIT_FAILURE); - } + int i; - total = sizeof(**argv) * nargs; - for(i = 0;i < nargs;i++) - total += WideCharToMultiByte(CP_UTF8, 0, args[i], -1, NULL, 0, NULL, NULL); + total = sizeof(*argv) * STATIC_CAST(size_t)(argc); + for(i = 0;i < argc;i++) + total += STATIC_CAST(size_t)(WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, NULL, 0, NULL, + NULL)); - atexit(cleanup_arglist); - arglist = *argv = (char**)calloc(1, total); - (*argv)[0] = (char*)(*argv + nargs); - for(i = 0;i < nargs-1;i++) +#ifdef __cplusplus + auto argbuf = std::make_unique(total); + argv = reinterpret_cast(argbuf.get()); +#else + argv = (char**)calloc(1, total); +#endif + argv[0] = REINTERPRET_CAST(char*)(argv + argc); + for(i = 0;i < argc-1;i++) { - int len = WideCharToMultiByte(CP_UTF8, 0, args[i], -1, (*argv)[i], 65535, NULL, NULL); - (*argv)[i+1] = (*argv)[i] + len; + int len = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, argv[i], 65535, NULL, NULL); + argv[i+1] = argv[i] + len; } - WideCharToMultiByte(CP_UTF8, 0, args[i], -1, (*argv)[i], 65535, NULL, NULL); - *argc = nargs; - - LocalFree(args); -} -#define GET_UNICODE_ARGS(argc, argv) GetUnicodeArgs(argc, argv) + WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, argv[i], 65535, NULL, NULL); +#ifdef __cplusplus + return main(argc, argv); #else + i = main(argc, argv); -/* Do nothing. */ -#define GET_UNICODE_ARGS(argc, argv) - + free(argv); + return i; #endif +} +#endif /* !defined(SDL_MAIN_NEEDED) && !defined(SDL_MAIN_AVAILABLE) */ + +#endif /* _WIN32 */ #endif /* WIN_MAIN_UTF8_H */ diff --git a/modules/openal-soft/config.h.in b/modules/openal-soft/config.h.in index cf46274..416b87d 100644 --- a/modules/openal-soft/config.h.in +++ b/modules/openal-soft/config.h.in @@ -1,22 +1,9 @@ -/* API declaration export attribute */ -#define AL_API ${EXPORT_DECL} -#define ALC_API ${EXPORT_DECL} - -/* Define any available alignment declaration */ -#define ALIGN(x) ${ALIGN_DECL} - -/* Define a restrict macro for non-aliased pointers */ -#define RESTRICT ${RESTRICT_DECL} +/* Define if deprecated EAX extensions are enabled */ +#cmakedefine ALSOFT_EAX /* Define if HRTF data is embedded in the library */ #cmakedefine ALSOFT_EMBED_HRTF_DATA -/* Define if we have the sysconf function */ -#cmakedefine HAVE_SYSCONF - -/* Define if we have the C11 aligned_alloc function */ -#cmakedefine HAVE_ALIGNED_ALLOC - /* Define if we have the posix_memalign function */ #cmakedefine HAVE_POSIX_MEMALIGN @@ -29,6 +16,9 @@ /* Define if we have the getopt function */ #cmakedefine HAVE_GETOPT +/* Define if we have DBus/RTKit */ +#cmakedefine HAVE_RTKIT + /* Define if we have SSE CPU extensions */ #cmakedefine HAVE_SSE #cmakedefine HAVE_SSE2 @@ -44,15 +34,15 @@ /* 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 QSA backend */ -#cmakedefine HAVE_QSA - /* Define if we have the WASAPI backend */ #cmakedefine HAVE_WASAPI @@ -77,33 +67,15 @@ /* 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 the stat function */ -#cmakedefine HAVE_STAT - -/* Define to the size of a long int type */ -#cmakedefine SIZEOF_LONG ${SIZEOF_LONG} - -/* Define to the size of a long long int type */ -#cmakedefine SIZEOF_LONG_LONG ${SIZEOF_LONG_LONG} - -/* Define if we have GCC's destructor attribute */ -#cmakedefine HAVE_GCC_DESTRUCTOR - -/* Define if we have GCC's format attribute */ -#cmakedefine HAVE_GCC_FORMAT - -/* Define if we have stdint.h */ -#cmakedefine HAVE_STDINT_H - -/* Define if we have windows.h */ -#cmakedefine HAVE_WINDOWS_H - /* Define if we have dlfcn.h */ #cmakedefine HAVE_DLFCN_H @@ -113,48 +85,24 @@ /* Define if we have malloc.h */ #cmakedefine HAVE_MALLOC_H -/* Define if we have dirent.h */ -#cmakedefine HAVE_DIRENT_H - -/* Define if we have strings.h */ -#cmakedefine HAVE_STRINGS_H - /* Define if we have cpuid.h */ #cmakedefine HAVE_CPUID_H /* Define if we have intrin.h */ #cmakedefine HAVE_INTRIN_H -/* Define if we have sys/sysconf.h */ -#cmakedefine HAVE_SYS_SYSCONF_H - /* Define if we have guiddef.h */ #cmakedefine HAVE_GUIDDEF_H /* Define if we have initguid.h */ #cmakedefine HAVE_INITGUID_H -/* Define if we have ieeefp.h */ -#cmakedefine HAVE_IEEEFP_H - -/* Define if we have float.h */ -#cmakedefine HAVE_FLOAT_H - -/* Define if we have fenv.h */ -#cmakedefine HAVE_FENV_H - /* Define if we have GCC's __get_cpuid() */ #cmakedefine HAVE_GCC_GET_CPUID /* Define if we have the __cpuid() intrinsic */ #cmakedefine HAVE_CPUID_INTRINSIC -/* Define if we have the _BitScanForward64() intrinsic */ -#cmakedefine HAVE_BITSCANFORWARD64_INTRINSIC - -/* Define if we have the _BitScanForward() intrinsic */ -#cmakedefine HAVE_BITSCANFORWARD_INTRINSIC - /* Define if we have SSE intrinsics */ #cmakedefine HAVE_SSE_INTRINSICS @@ -164,11 +112,5 @@ /* Define if we have pthread_setname_np() */ #cmakedefine HAVE_PTHREAD_SETNAME_NP -/* Define if pthread_setname_np() only accepts one parameter */ -#cmakedefine PTHREAD_SETNAME_NP_ONE_PARAM - -/* Define if pthread_setname_np() accepts three parameters */ -#cmakedefine PTHREAD_SETNAME_NP_THREE_PARAMS - /* Define if we have pthread_set_name_np() */ #cmakedefine HAVE_PTHREAD_SET_NAME_NP diff --git a/modules/openal-soft/Alc/ambdec.cpp b/modules/openal-soft/core/ambdec.cpp similarity index 53% rename from modules/openal-soft/Alc/ambdec.cpp rename to modules/openal-soft/core/ambdec.cpp index 0991cfc..0df22bc 100644 --- a/modules/openal-soft/Alc/ambdec.cpp +++ b/modules/openal-soft/core/ambdec.cpp @@ -3,17 +3,15 @@ #include "ambdec.h" -#include -#include #include - -#include -#include -#include +#include +#include +#include #include +#include -#include "logging.h" -#include "compat.h" +#include "alfstream.h" +#include "core/logging.h" namespace { @@ -67,9 +65,11 @@ bool is_at_end(const std::string &buffer, std::size_t endpos) } -bool load_ambdec_speakers(al::vector &spkrs, const std::size_t num_speakers, std::istream &f, std::string &buffer) +al::optional load_ambdec_speakers(AmbDecConf::SpeakerConf *spkrs, + const std::size_t num_speakers, std::istream &f, std::string &buffer) { - while(spkrs.size() < num_speakers) + size_t cur_speaker{0}; + while(cur_speaker < num_speakers) { std::istringstream istr{buffer}; @@ -77,18 +77,14 @@ bool load_ambdec_speakers(al::vector &spkrs, const std: if(cmd.empty()) { if(!read_clipped_line(f, buffer)) - { - ERR("Unexpected end of file\n"); - return false; - } + return al::make_optional("Unexpected end of file"); continue; } if(cmd == "add_spkr") { - spkrs.emplace_back(); - AmbDecConf::SpeakerConf &spkr = spkrs.back(); - const size_t spkr_num{spkrs.size()}; + AmbDecConf::SpeakerConf &spkr = spkrs[cur_speaker++]; + const size_t spkr_num{cur_speaker}; istr >> spkr.Name; if(istr.fail()) WARN("Name not specified for speaker %zu\n", spkr_num); @@ -102,25 +98,20 @@ bool load_ambdec_speakers(al::vector &spkrs, const std: if(istr.fail()) TRACE("Connection not specified for speaker %zu\n", spkr_num); } else - { - ERR("Unexpected speakers command: %s\n", cmd.c_str()); - return false; - } + return al::make_optional("Unexpected speakers command: "+cmd); istr.clear(); const auto endpos = static_cast(istr.tellg()); if(!is_at_end(buffer, endpos)) - { - ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); - return false; - } + return al::make_optional("Extra junk on line: " + buffer.substr(endpos)); buffer.clear(); } - return true; + return al::nullopt; } -bool load_ambdec_matrix(float (&gains)[MAX_AMBI_ORDER+1], al::vector &matrix, const std::size_t maxrow, std::istream &f, std::string &buffer) +al::optional load_ambdec_matrix(float (&gains)[MaxAmbiOrder+1], + AmbDecConf::CoeffArray *matrix, const std::size_t maxrow, std::istream &f, std::string &buffer) { bool gotgains{false}; std::size_t cur{0u}; @@ -132,10 +123,7 @@ bool load_ambdec_matrix(float (&gains)[MAX_AMBI_ORDER+1], al::vector("Unexpected end of file"); continue; } @@ -148,11 +136,8 @@ bool load_ambdec_matrix(float (&gains)[MAX_AMBI_ORDER+1], al::vector> value; if(istr.fail()) break; if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk on gain %zu: %s\n", curgain+1, - buffer.c_str()+static_cast(istr.tellg())); - return false; - } + return al::make_optional("Extra junk on gain "+std::to_string(curgain+1)+": "+ + buffer.substr(static_cast(istr.tellg()))); if(curgain < size(gains)) gains[curgain++] = value; } @@ -161,8 +146,7 @@ bool load_ambdec_matrix(float (&gains)[MAX_AMBI_ORDER+1], al::vector> value; if(istr.fail()) break; if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk on matrix element %zux%zu: %s\n", curidx, - matrix.size(), buffer.c_str()+static_cast(istr.tellg())); - matrix.pop_back(); - return false; - } + return al::make_optional("Extra junk on matrix element "+ + std::to_string(curidx)+"x"+std::to_string(cur-1)+": "+ + buffer.substr(static_cast(istr.tellg()))); if(curidx < mtxrow.size()) mtxrow[curidx++] = value; } std::fill(mtxrow.begin()+curidx, mtxrow.end(), 0.0f); - cur++; } else - { - ERR("Unexpected matrix command: %s\n", cmd.c_str()); - return false; - } + return al::make_optional("Unexpected matrix command: "+cmd); istr.clear(); const auto endpos = static_cast(istr.tellg()); if(!is_at_end(buffer, endpos)) - { - ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); - return false; - } + return al::make_optional("Extra junk on line: " + buffer.substr(endpos)); buffer.clear(); } if(!gotgains) - { - ERR("Matrix order_gain not specified\n"); - return false; - } - - return true; + return al::make_optional("Matrix order_gain not specified"); + return al::nullopt; } } // namespace -int AmbDecConf::load(const char *fname) noexcept +AmbDecConf::~AmbDecConf() = default; + + +al::optional AmbDecConf::load(const char *fname) noexcept { al::ifstream f{fname}; if(!f.is_open()) - { - ERR("Failed to open: %s\n", fname); - return 0; - } + return al::make_optional("Failed to open file"); - std::size_t num_speakers{0u}; + bool speakers_loaded{false}; + bool matrix_loaded{false}; + bool lfmatrix_loaded{false}; std::string buffer; while(read_clipped_line(f, buffer)) { @@ -226,65 +198,58 @@ int AmbDecConf::load(const char *fname) noexcept std::string command{read_word(istr)}; if(command.empty()) - { - ERR("Malformed line: %s\n", buffer.c_str()); - return 0; - } + return al::make_optional("Malformed line: "+buffer); if(command == "/description") - istr >> Description; + readline(istr, Description); else if(command == "/version") { istr >> Version; if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk after version: %s\n", - buffer.c_str()+static_cast(istr.tellg())); - return 0; - } + return al::make_optional("Extra junk after version: " + + buffer.substr(static_cast(istr.tellg()))); if(Version != 3) - { - ERR("Unsupported version: %u\n", Version); - return 0; - } + return al::make_optional("Unsupported version: "+std::to_string(Version)); } else if(command == "/dec/chan_mask") { + if(ChanMask) + return al::make_optional("Duplicate chan_mask definition"); + istr >> std::hex >> ChanMask >> std::dec; if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk after mask: %s\n", - buffer.c_str()+static_cast(istr.tellg())); - return 0; - } + return al::make_optional("Extra junk after mask: " + + buffer.substr(static_cast(istr.tellg()))); + + if(!ChanMask) + return al::make_optional("Invalid chan_mask: "+std::to_string(ChanMask)); } else if(command == "/dec/freq_bands") { + if(FreqBands) + return al::make_optional("Duplicate freq_bands"); + istr >> FreqBands; if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk after freq_bands: %s\n", - buffer.c_str()+static_cast(istr.tellg())); - return 0; - } + return al::make_optional("Extra junk after freq_bands: " + + buffer.substr(static_cast(istr.tellg()))); + if(FreqBands != 1 && FreqBands != 2) - { - ERR("Invalid freq_bands value: %u\n", FreqBands); - return 0; - } + return al::make_optional("Invalid freq_bands: "+std::to_string(FreqBands)); } else if(command == "/dec/speakers") { - istr >> num_speakers; + if(NumSpeakers) + return al::make_optional("Duplicate speakers"); + + istr >> NumSpeakers; if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk after speakers: %s\n", - buffer.c_str()+static_cast(istr.tellg())); - return 0; - } - Speakers.reserve(num_speakers); - LFMatrix.reserve(num_speakers); - HFMatrix.reserve(num_speakers); + return al::make_optional("Extra junk after speakers: " + + buffer.substr(static_cast(istr.tellg()))); + + if(!NumSpeakers) + return al::make_optional("Invalid speakers: "+std::to_string(NumSpeakers)); + Speakers = std::make_unique(NumSpeakers); } else if(command == "/dec/coeff_scale") { @@ -293,30 +258,21 @@ int AmbDecConf::load(const char *fname) noexcept else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D; else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa; else - { - ERR("Unsupported coeff scale: %s\n", scale.c_str()); - return 0; - } + return al::make_optional("Unexpected coeff_scale: "+scale); } else if(command == "/opt/xover_freq") { istr >> XOverFreq; if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk after xover_freq: %s\n", - buffer.c_str()+static_cast(istr.tellg())); - return 0; - } + return al::make_optional("Extra junk after xover_freq: " + + buffer.substr(static_cast(istr.tellg()))); } else if(command == "/opt/xover_ratio") { istr >> XOverRatio; if(!istr.eof() && !std::isspace(istr.peek())) - { - ERR("Extra junk after xover_ratio: %s\n", - buffer.c_str()+static_cast(istr.tellg())); - return 0; - } + return al::make_optional("Extra junk after xover_ratio: " + + buffer.substr(static_cast(istr.tellg()))); } else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp" || command == "/opt/delay_comp" || command == "/opt/level_comp") @@ -326,111 +282,97 @@ int AmbDecConf::load(const char *fname) noexcept } else if(command == "/speakers/{") { + if(!NumSpeakers) + return al::make_optional("Speakers defined without a count"); + const auto endpos = static_cast(istr.tellg()); if(!is_at_end(buffer, endpos)) - { - ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); - return 0; - } + return al::make_optional("Extra junk on line: " + buffer.substr(endpos)); buffer.clear(); - if(!load_ambdec_speakers(Speakers, num_speakers, f, buffer)) - return 0; + if(auto err = load_ambdec_speakers(Speakers.get(), NumSpeakers, f, buffer)) + return err; + speakers_loaded = true; if(!read_clipped_line(f, buffer)) - { - ERR("Unexpected end of file\n"); - return 0; - } + return al::make_optional("Unexpected end of file"); std::istringstream istr2{buffer}; std::string endmark{read_word(istr2)}; if(endmark != "/}") - { - ERR("Expected /} after speaker definitions, got %s\n", endmark.c_str()); - return 0; - } + return al::make_optional("Expected /} after speaker definitions, got "+endmark); istr.swap(istr2); } else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{") { + if(!NumSpeakers) + return al::make_optional("Matrix defined without a count"); const auto endpos = static_cast(istr.tellg()); if(!is_at_end(buffer, endpos)) + return al::make_optional("Extra junk on line: " + buffer.substr(endpos)); + buffer.clear(); + + if(!Matrix) { - ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); - return 0; + Matrix = std::make_unique(NumSpeakers * FreqBands); + LFMatrix = Matrix.get(); + HFMatrix = LFMatrix + NumSpeakers*(FreqBands-1); } - buffer.clear(); if(FreqBands == 1) { if(command != "/matrix/{") - { - ERR("Unexpected \"%s\" type for a single-band decoder\n", command.c_str()); - return 0; - } - if(!load_ambdec_matrix(HFOrderGain, HFMatrix, num_speakers, f, buffer)) - return 0; + return al::make_optional( + "Unexpected \""+command+"\" type for a single-band decoder"); + if(auto err = load_ambdec_matrix(HFOrderGain, HFMatrix, NumSpeakers, f, buffer)) + return err; + matrix_loaded = true; } else { if(command == "/lfmatrix/{") { - if(!load_ambdec_matrix(LFOrderGain, LFMatrix, num_speakers, f, buffer)) - return 0; + if(auto err=load_ambdec_matrix(LFOrderGain, LFMatrix, NumSpeakers, f, buffer)) + return err; + lfmatrix_loaded = true; } else if(command == "/hfmatrix/{") { - if(!load_ambdec_matrix(HFOrderGain, HFMatrix, num_speakers, f, buffer)) - return 0; + if(auto err=load_ambdec_matrix(HFOrderGain, HFMatrix, NumSpeakers, f, buffer)) + return err; + matrix_loaded = true; } else - { - ERR("Unexpected \"%s\" type for a dual-band decoder\n", command.c_str()); - return 0; - } + return al::make_optional( + "Unexpected \""+command+"\" type for a dual-band decoder"); } if(!read_clipped_line(f, buffer)) - { - ERR("Unexpected end of file\n"); - return 0; - } + return al::make_optional("Unexpected end of file"); std::istringstream istr2{buffer}; std::string endmark{read_word(istr2)}; if(endmark != "/}") - { - ERR("Expected /} after matrix definitions, got %s\n", endmark.c_str()); - return 0; - } + return al::make_optional("Expected /} after matrix definitions, got "+endmark); istr.swap(istr2); } else if(command == "/end") { const auto endpos = static_cast(istr.tellg()); if(!is_at_end(buffer, endpos)) - { - ERR("Unexpected junk on end: %s\n", buffer.c_str()+endpos); - return 0; - } + return al::make_optional("Extra junk on end: " + buffer.substr(endpos)); + + if(!speakers_loaded || !matrix_loaded || (FreqBands == 2 && !lfmatrix_loaded)) + return al::make_optional("No decoder defined"); - return 1; + return al::nullopt; } else - { - ERR("Unexpected command: %s\n", command.c_str()); - return 0; - } + return al::make_optional("Unexpected command: " + command); istr.clear(); const auto endpos = static_cast(istr.tellg()); if(!is_at_end(buffer, endpos)) - { - ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); - return 0; - } + return al::make_optional("Extra junk on line: " + buffer.substr(endpos)); buffer.clear(); } - ERR("Unexpected end of file\n"); - - return 0; + return al::make_optional("Unexpected end of file"); } diff --git a/modules/openal-soft/Alc/ambdec.h b/modules/openal-soft/core/ambdec.h similarity index 54% rename from modules/openal-soft/Alc/ambdec.h rename to modules/openal-soft/core/ambdec.h index ff7b71e..e1bcde2 100644 --- a/modules/openal-soft/Alc/ambdec.h +++ b/modules/openal-soft/core/ambdec.h @@ -1,11 +1,12 @@ -#ifndef AMBDEC_H -#define AMBDEC_H +#ifndef CORE_AMBDEC_H +#define CORE_AMBDEC_H #include +#include #include -#include "ambidefs.h" -#include "vector.h" +#include "aloptional.h" +#include "core/ambidefs.h" /* Helpers to read .ambdec configuration files. */ @@ -32,17 +33,22 @@ struct AmbDecConf { float Elevation{0.0f}; std::string Connection; }; - al::vector Speakers; + size_t NumSpeakers{0}; + std::unique_ptr Speakers; + + using CoeffArray = std::array; + std::unique_ptr Matrix; - using CoeffArray = std::array; /* Unused when FreqBands == 1 */ - float LFOrderGain[MAX_AMBI_ORDER+1]{}; - al::vector LFMatrix; + float LFOrderGain[MaxAmbiOrder+1]{}; + CoeffArray *LFMatrix; + + float HFOrderGain[MaxAmbiOrder+1]{}; + CoeffArray *HFMatrix; - float HFOrderGain[MAX_AMBI_ORDER+1]{}; - al::vector HFMatrix; + ~AmbDecConf(); - int load(const char *fname) noexcept; + al::optional load(const char *fname) noexcept; }; -#endif /* AMBDEC_H */ +#endif /* CORE_AMBDEC_H */ diff --git a/modules/openal-soft/core/ambidefs.cpp b/modules/openal-soft/core/ambidefs.cpp new file mode 100644 index 0000000..2725748 --- /dev/null +++ b/modules/openal-soft/core/ambidefs.cpp @@ -0,0 +1,44 @@ + +#include "config.h" + +#include "ambidefs.h" + +#include + + +namespace { + +constexpr std::array Ambi3DDecoderHFScale{{ + 1.00000000e+00f, 1.00000000e+00f +}}; +constexpr std::array Ambi3DDecoderHFScale2O{{ + 7.45355990e-01f, 1.00000000e+00f, 1.00000000e+00f +}}; +constexpr std::array Ambi3DDecoderHFScale3O{{ + 5.89792205e-01f, 8.79693856e-01f, 1.00000000e+00f, 1.00000000e+00f +}}; + +inline auto& GetDecoderHFScales(uint order) noexcept +{ + if(order >= 3) return Ambi3DDecoderHFScale3O; + if(order == 2) return Ambi3DDecoderHFScale2O; + return Ambi3DDecoderHFScale; +} + +} // namespace + +auto AmbiScale::GetHFOrderScales(const uint in_order, const uint out_order) noexcept + -> std::array +{ + std::array ret{}; + + assert(out_order >= in_order); + + const auto &target = GetDecoderHFScales(out_order); + const auto &input = GetDecoderHFScales(in_order); + + for(size_t i{0};i < in_order+1;++i) + ret[i] = input[i] / target[i]; + + return ret; +} diff --git a/modules/openal-soft/core/ambidefs.h b/modules/openal-soft/core/ambidefs.h new file mode 100644 index 0000000..82a1a4e --- /dev/null +++ b/modules/openal-soft/core/ambidefs.h @@ -0,0 +1,187 @@ +#ifndef CORE_AMBIDEFS_H +#define CORE_AMBIDEFS_H + +#include +#include +#include + +using uint = unsigned int; + +/* The maximum number of Ambisonics channels. For a given order (o), the size + * needed will be (o+1)**2, thus zero-order has 1, first-order has 4, second- + * order has 9, third-order has 16, and fourth-order has 25. + */ +constexpr uint8_t MaxAmbiOrder{3}; +constexpr inline size_t AmbiChannelsFromOrder(size_t order) noexcept +{ return (order+1) * (order+1); } +constexpr size_t MaxAmbiChannels{AmbiChannelsFromOrder(MaxAmbiOrder)}; + +/* A bitmask of ambisonic channels for 0 to 4th order. This only specifies up + * to 4th order, which is the highest order a 32-bit mask value can specify (a + * 64-bit mask could handle up to 7th order). + */ +constexpr uint Ambi0OrderMask{0x00000001}; +constexpr uint Ambi1OrderMask{0x0000000f}; +constexpr uint Ambi2OrderMask{0x000001ff}; +constexpr uint Ambi3OrderMask{0x0000ffff}; +constexpr uint Ambi4OrderMask{0x01ffffff}; + +/* A bitmask of ambisonic channels with height information. If none of these + * channels are used/needed, there's no height (e.g. with most surround sound + * speaker setups). This is ACN ordering, with bit 0 being ACN 0, etc. + */ +constexpr uint AmbiPeriphonicMask{0xfe7ce4}; + +/* The maximum number of ambisonic channels for 2D (non-periphonic) + * representation. This is 2 per each order above zero-order, plus 1 for zero- + * order. Or simply, o*2 + 1. + */ +constexpr inline size_t Ambi2DChannelsFromOrder(size_t order) noexcept +{ return order*2 + 1; } +constexpr size_t MaxAmbi2DChannels{Ambi2DChannelsFromOrder(MaxAmbiOrder)}; + + +/* NOTE: These are scale factors as applied to Ambisonics content. Decoder + * coefficients should be divided by these values to get proper scalings. + */ +struct AmbiScale { + static auto& FromN3D() noexcept + { + static constexpr const std::array ret{{ + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f + }}; + return ret; + } + static auto& FromSN3D() noexcept + { + static constexpr const std::array ret{{ + 1.000000000f, /* ACN 0, sqrt(1) */ + 1.732050808f, /* ACN 1, sqrt(3) */ + 1.732050808f, /* ACN 2, sqrt(3) */ + 1.732050808f, /* ACN 3, sqrt(3) */ + 2.236067978f, /* ACN 4, sqrt(5) */ + 2.236067978f, /* ACN 5, sqrt(5) */ + 2.236067978f, /* ACN 6, sqrt(5) */ + 2.236067978f, /* ACN 7, sqrt(5) */ + 2.236067978f, /* ACN 8, sqrt(5) */ + 2.645751311f, /* ACN 9, sqrt(7) */ + 2.645751311f, /* ACN 10, sqrt(7) */ + 2.645751311f, /* ACN 11, sqrt(7) */ + 2.645751311f, /* ACN 12, sqrt(7) */ + 2.645751311f, /* ACN 13, sqrt(7) */ + 2.645751311f, /* ACN 14, sqrt(7) */ + 2.645751311f, /* ACN 15, sqrt(7) */ + }}; + return ret; + } + static auto& FromFuMa() noexcept + { + static constexpr const std::array ret{{ + 1.414213562f, /* ACN 0 (W), sqrt(2) */ + 1.732050808f, /* ACN 1 (Y), sqrt(3) */ + 1.732050808f, /* ACN 2 (Z), sqrt(3) */ + 1.732050808f, /* ACN 3 (X), sqrt(3) */ + 1.936491673f, /* ACN 4 (V), sqrt(15)/2 */ + 1.936491673f, /* ACN 5 (T), sqrt(15)/2 */ + 2.236067978f, /* ACN 6 (R), sqrt(5) */ + 1.936491673f, /* ACN 7 (S), sqrt(15)/2 */ + 1.936491673f, /* ACN 8 (U), sqrt(15)/2 */ + 2.091650066f, /* ACN 9 (Q), sqrt(35/8) */ + 1.972026594f, /* ACN 10 (O), sqrt(35)/3 */ + 2.231093404f, /* ACN 11 (M), sqrt(224/45) */ + 2.645751311f, /* ACN 12 (K), sqrt(7) */ + 2.231093404f, /* ACN 13 (L), sqrt(224/45) */ + 1.972026594f, /* ACN 14 (N), sqrt(35)/3 */ + 2.091650066f, /* ACN 15 (P), sqrt(35/8) */ + }}; + return ret; + } + static auto& FromUHJ() noexcept + { + static constexpr const std::array ret{{ + 1.000000000f, /* ACN 0 (W), sqrt(1) */ + 1.224744871f, /* ACN 1 (Y), sqrt(3/2) */ + 1.224744871f, /* ACN 2 (Z), sqrt(3/2) */ + 1.224744871f, /* ACN 3 (X), sqrt(3/2) */ + /* Higher orders not relevant for UHJ. */ + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, + }}; + return ret; + } + + /* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */ + static std::array GetHFOrderScales(const uint in_order, + const uint out_order) noexcept; +}; + +struct AmbiIndex { + static auto& FromFuMa() noexcept + { + static constexpr const std::array ret{{ + 0, /* W */ + 3, /* X */ + 1, /* Y */ + 2, /* Z */ + 6, /* R */ + 7, /* S */ + 5, /* T */ + 8, /* U */ + 4, /* V */ + 12, /* K */ + 13, /* L */ + 11, /* M */ + 14, /* N */ + 10, /* O */ + 15, /* P */ + 9, /* Q */ + }}; + return ret; + } + static auto& FromFuMa2D() noexcept + { + static constexpr const std::array ret{{ + 0, /* W */ + 3, /* X */ + 1, /* Y */ + 8, /* U */ + 4, /* V */ + 15, /* P */ + 9, /* Q */ + }}; + return ret; + } + + static auto& FromACN() noexcept + { + static constexpr const std::array ret{{ + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15 + }}; + return ret; + } + static auto& FromACN2D() noexcept + { + static constexpr const std::array ret{{ + 0, 1,3, 4,8, 9,15 + }}; + return ret; + } + + static auto& OrderFromChannel() noexcept + { + static constexpr const std::array ret{{ + 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3, + }}; + return ret; + } + static auto& OrderFrom2DChannel() noexcept + { + static constexpr const std::array ret{{ + 0, 1,1, 2,2, 3,3, + }}; + return ret; + } +}; + +#endif /* CORE_AMBIDEFS_H */ diff --git a/modules/openal-soft/core/async_event.h b/modules/openal-soft/core/async_event.h new file mode 100644 index 0000000..750f38c --- /dev/null +++ b/modules/openal-soft/core/async_event.h @@ -0,0 +1,55 @@ +#ifndef CORE_EVENT_H +#define CORE_EVENT_H + +#include "almalloc.h" + +struct EffectState; + +using uint = unsigned int; + + +struct AsyncEvent { + enum : uint { + /* End event thread processing. */ + KillThread = 0, + + /* User event types. */ + SourceStateChange = 1<<0, + BufferCompleted = 1<<1, + Disconnected = 1<<2, + + /* Internal events. */ + ReleaseEffectState = 65536, + }; + + enum class SrcState { + Reset, + Stop, + Play, + Pause + }; + + uint EnumType{0u}; + union { + char dummy; + struct { + uint id; + SrcState state; + } srcstate; + struct { + uint id; + uint count; + } bufcomp; + struct { + char msg[244]; + } disconnect; + EffectState *mEffectState; + } u{}; + + AsyncEvent() noexcept = default; + constexpr AsyncEvent(uint type) noexcept : EnumType{type} { } + + DISABLE_ALLOC() +}; + +#endif diff --git a/modules/openal-soft/core/bformatdec.cpp b/modules/openal-soft/core/bformatdec.cpp new file mode 100644 index 0000000..606093c --- /dev/null +++ b/modules/openal-soft/core/bformatdec.cpp @@ -0,0 +1,192 @@ + +#include "config.h" + +#include "bformatdec.h" + +#include +#include +#include +#include + +#include "almalloc.h" +#include "alnumbers.h" +#include "filters/splitter.h" +#include "front_stablizer.h" +#include "mixer.h" +#include "opthelpers.h" + + +BFormatDec::BFormatDec(const size_t inchans, const al::span coeffs, + const al::span coeffslf, const float xover_f0norm, + std::unique_ptr stablizer) + : mStablizer{std::move(stablizer)}, mDualBand{!coeffslf.empty()}, mChannelDec{inchans} +{ + if(!mDualBand) + { + for(size_t j{0};j < mChannelDec.size();++j) + { + float *outcoeffs{mChannelDec[j].mGains.Single}; + for(const ChannelDec &incoeffs : coeffs) + *(outcoeffs++) = incoeffs[j]; + } + } + else + { + mChannelDec[0].mXOver.init(xover_f0norm); + for(size_t j{1};j < mChannelDec.size();++j) + mChannelDec[j].mXOver = mChannelDec[0].mXOver; + + for(size_t j{0};j < mChannelDec.size();++j) + { + float *outcoeffs{mChannelDec[j].mGains.Dual[sHFBand]}; + for(const ChannelDec &incoeffs : coeffs) + *(outcoeffs++) = incoeffs[j]; + + outcoeffs = mChannelDec[j].mGains.Dual[sLFBand]; + for(const ChannelDec &incoeffs : coeffslf) + *(outcoeffs++) = incoeffs[j]; + } + } +} + + +void BFormatDec::process(const al::span OutBuffer, + const FloatBufferLine *InSamples, const size_t SamplesToDo) +{ + ASSUME(SamplesToDo > 0); + + if(mDualBand) + { + const al::span hfSamples{mSamples[sHFBand].data(), SamplesToDo}; + const al::span lfSamples{mSamples[sLFBand].data(), SamplesToDo}; + for(auto &chandec : mChannelDec) + { + chandec.mXOver.process({InSamples->data(), SamplesToDo}, hfSamples.data(), + lfSamples.data()); + MixSamples(hfSamples, OutBuffer, chandec.mGains.Dual[sHFBand], + chandec.mGains.Dual[sHFBand], 0, 0); + MixSamples(lfSamples, OutBuffer, chandec.mGains.Dual[sLFBand], + chandec.mGains.Dual[sLFBand], 0, 0); + ++InSamples; + } + } + else + { + for(auto &chandec : mChannelDec) + { + MixSamples({InSamples->data(), SamplesToDo}, OutBuffer, chandec.mGains.Single, + chandec.mGains.Single, 0, 0); + ++InSamples; + } + } +} + +void BFormatDec::processStablize(const al::span OutBuffer, + const FloatBufferLine *InSamples, const size_t lidx, const size_t ridx, const size_t cidx, + const size_t SamplesToDo) +{ + ASSUME(SamplesToDo > 0); + + /* Move the existing direct L/R signal out so it doesn't get processed by + * the stablizer. Add a delay to it so it stays aligned with the stablizer + * delay. + */ + float *RESTRICT mid{al::assume_aligned<16>(mStablizer->MidDirect.data())}; + float *RESTRICT side{al::assume_aligned<16>(mStablizer->Side.data())}; + for(size_t i{0};i < SamplesToDo;++i) + { + mid[FrontStablizer::DelayLength+i] = OutBuffer[lidx][i] + OutBuffer[ridx][i]; + side[FrontStablizer::DelayLength+i] = OutBuffer[lidx][i] - OutBuffer[ridx][i]; + } + std::fill_n(OutBuffer[lidx].begin(), SamplesToDo, 0.0f); + std::fill_n(OutBuffer[ridx].begin(), SamplesToDo, 0.0f); + + /* Decode the B-Format input to OutBuffer. */ + process(OutBuffer, InSamples, SamplesToDo); + + /* Apply a delay to all channels, except the front-left and front-right, so + * they maintain correct timing. + */ + const size_t NumChannels{OutBuffer.size()}; + for(size_t i{0u};i < NumChannels;i++) + { + if(i == lidx || i == ridx) + continue; + + auto &DelayBuf = mStablizer->DelayBuf[i]; + auto buffer_end = OutBuffer[i].begin() + SamplesToDo; + if LIKELY(SamplesToDo >= FrontStablizer::DelayLength) + { + auto delay_end = std::rotate(OutBuffer[i].begin(), + buffer_end - FrontStablizer::DelayLength, buffer_end); + std::swap_ranges(OutBuffer[i].begin(), delay_end, DelayBuf.begin()); + } + else + { + auto delay_start = std::swap_ranges(OutBuffer[i].begin(), buffer_end, + DelayBuf.begin()); + std::rotate(DelayBuf.begin(), delay_start, DelayBuf.end()); + } + } + + /* Include the side signal for what was just decoded. */ + for(size_t i{0};i < SamplesToDo;++i) + side[FrontStablizer::DelayLength+i] += OutBuffer[lidx][i] - OutBuffer[ridx][i]; + + /* Combine the delayed mid signal with the decoded mid signal. */ + float *tmpbuf{mStablizer->TempBuf.data()}; + auto tmpiter = std::copy(mStablizer->MidDelay.cbegin(), mStablizer->MidDelay.cend(), tmpbuf); + for(size_t i{0};i < SamplesToDo;++i,++tmpiter) + *tmpiter = OutBuffer[lidx][i] + OutBuffer[ridx][i]; + /* Save the newest samples for next time. */ + std::copy_n(tmpbuf+SamplesToDo, mStablizer->MidDelay.size(), mStablizer->MidDelay.begin()); + + /* Apply an all-pass on the signal in reverse. The future samples are + * included with the all-pass to reduce the error in the output samples + * (the smaller the delay, the more error is introduced). + */ + mStablizer->MidFilter.applyAllpassRev({tmpbuf, SamplesToDo+FrontStablizer::DelayLength}); + + /* Now apply the band-splitter, combining its phase shift with the reversed + * phase shift, restoring the original phase on the split signal. + */ + mStablizer->MidFilter.process({tmpbuf, SamplesToDo}, mStablizer->MidHF.data(), + mStablizer->MidLF.data()); + + /* This pans the separate low- and high-frequency signals between being on + * the center channel and the left+right channels. The low-frequency signal + * is panned 1/3rd toward center and the high-frequency signal is panned + * 1/4th toward center. These values can be tweaked. + */ + const float cos_lf{std::cos(1.0f/3.0f * (al::numbers::pi_v*0.5f))}; + const float cos_hf{std::cos(1.0f/4.0f * (al::numbers::pi_v*0.5f))}; + const float sin_lf{std::sin(1.0f/3.0f * (al::numbers::pi_v*0.5f))}; + const float sin_hf{std::sin(1.0f/4.0f * (al::numbers::pi_v*0.5f))}; + for(size_t i{0};i < SamplesToDo;i++) + { + const float m{mStablizer->MidLF[i]*cos_lf + mStablizer->MidHF[i]*cos_hf + mid[i]}; + const float c{mStablizer->MidLF[i]*sin_lf + mStablizer->MidHF[i]*sin_hf}; + const float s{side[i]}; + + /* The generated center channel signal adds to the existing signal, + * while the modified left and right channels replace. + */ + OutBuffer[lidx][i] = (m + s) * 0.5f; + OutBuffer[ridx][i] = (m - s) * 0.5f; + OutBuffer[cidx][i] += c * 0.5f; + } + /* Move the delayed mid/side samples to the front for next time. */ + auto mid_end = mStablizer->MidDirect.cbegin() + SamplesToDo; + std::copy(mid_end, mid_end+FrontStablizer::DelayLength, mStablizer->MidDirect.begin()); + auto side_end = mStablizer->Side.cbegin() + SamplesToDo; + std::copy(side_end, side_end+FrontStablizer::DelayLength, mStablizer->Side.begin()); +} + + +std::unique_ptr BFormatDec::Create(const size_t inchans, + const al::span coeffs, const al::span coeffslf, + const float xover_f0norm, std::unique_ptr stablizer) +{ + return std::make_unique(inchans, coeffs, coeffslf, xover_f0norm, + std::move(stablizer)); +} diff --git a/modules/openal-soft/core/bformatdec.h b/modules/openal-soft/core/bformatdec.h new file mode 100644 index 0000000..7a27a5a --- /dev/null +++ b/modules/openal-soft/core/bformatdec.h @@ -0,0 +1,71 @@ +#ifndef CORE_BFORMATDEC_H +#define CORE_BFORMATDEC_H + +#include +#include +#include + +#include "almalloc.h" +#include "alspan.h" +#include "ambidefs.h" +#include "bufferline.h" +#include "devformat.h" +#include "filters/splitter.h" +#include "vector.h" + +struct FrontStablizer; + + +using ChannelDec = std::array; + +class BFormatDec { + static constexpr size_t sHFBand{0}; + static constexpr size_t sLFBand{1}; + static constexpr size_t sNumBands{2}; + + struct ChannelDecoder { + union MatrixU { + float Dual[sNumBands][MAX_OUTPUT_CHANNELS]; + float Single[MAX_OUTPUT_CHANNELS]; + } mGains{}; + + /* NOTE: BandSplitter filter is unused with single-band decoding. */ + BandSplitter mXOver; + }; + + alignas(16) std::array mSamples; + + const std::unique_ptr mStablizer; + const bool mDualBand{false}; + + /* TODO: This should ideally be a FlexArray, since ChannelDecoder is rather + * small and only a few are needed (3, 4, 5, 7, typically). But that can + * only be used in a standard layout struct, and a std::unique_ptr member + * (mStablizer) causes GCC and Clang to warn it's not. + */ + al::vector mChannelDec; + +public: + BFormatDec(const size_t inchans, const al::span coeffs, + const al::span coeffslf, const float xover_f0norm, + std::unique_ptr stablizer); + + bool hasStablizer() const noexcept { return mStablizer != nullptr; } + + /* Decodes the ambisonic input to the given output channels. */ + void process(const al::span OutBuffer, const FloatBufferLine *InSamples, + const size_t SamplesToDo); + + /* Decodes the ambisonic input to the given output channels with stablization. */ + void processStablize(const al::span OutBuffer, + const FloatBufferLine *InSamples, const size_t lidx, const size_t ridx, const size_t cidx, + const size_t SamplesToDo); + + static std::unique_ptr Create(const size_t inchans, + const al::span coeffs, const al::span coeffslf, + const float xover_f0norm, std::unique_ptr stablizer); + + DEF_NEWDEL(BFormatDec) +}; + +#endif /* CORE_BFORMATDEC_H */ diff --git a/modules/openal-soft/Alc/bs2b.cpp b/modules/openal-soft/core/bs2b.cpp similarity index 64% rename from modules/openal-soft/Alc/bs2b.cpp rename to modules/openal-soft/core/bs2b.cpp index 2d1b96a..303bf9b 100644 --- a/modules/openal-soft/Alc/bs2b.cpp +++ b/modules/openal-soft/core/bs2b.cpp @@ -23,19 +23,19 @@ #include "config.h" -#include -#include #include +#include +#include +#include "alnumbers.h" #include "bs2b.h" -#include "math_defs.h" /* Set up all data. */ static void init(struct bs2b *bs2b) { float Fc_lo, Fc_hi; - float G_lo, G_hi; + float G_lo, G_hi; float x, g; switch(bs2b->level) @@ -91,11 +91,11 @@ static void init(struct bs2b *bs2b) * $d = 1 / 2 / pi / $fc; * $x = exp(-1 / $d); */ - x = std::exp(-al::MathDefs::Tau() * Fc_lo / bs2b->srate); + x = std::exp(-al::numbers::pi_v*2.0f*Fc_lo/static_cast(bs2b->srate)); bs2b->b1_lo = x; bs2b->a0_lo = G_lo * (1.0f - x) * g; - x = std::exp(-al::MathDefs::Tau() * Fc_hi / bs2b->srate); + x = std::exp(-al::numbers::pi_v*2.0f*Fc_hi/static_cast(bs2b->srate)); bs2b->b1_hi = x; bs2b->a0_hi = (1.0f - G_hi * (1.0f - x)) * g; bs2b->a1_hi = -x * g; @@ -127,60 +127,55 @@ int bs2b_get_srate(struct bs2b *bs2b) void bs2b_clear(struct bs2b *bs2b) { - std::fill(std::begin(bs2b->last_sample), std::end(bs2b->last_sample), bs2b::t_last_sample{}); + std::fill(std::begin(bs2b->history), std::end(bs2b->history), bs2b::t_last_sample{}); } /* bs2b_clear */ -void bs2b_cross_feed(struct bs2b *bs2b, float *RESTRICT Left, float *RESTRICT Right, int SamplesToDo) +void bs2b_cross_feed(struct bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo) { + const float a0_lo{bs2b->a0_lo}; + const float b1_lo{bs2b->b1_lo}; + const float a0_hi{bs2b->a0_hi}; + const float a1_hi{bs2b->a1_hi}; + const float b1_hi{bs2b->b1_hi}; float lsamples[128][2]; float rsamples[128][2]; - int base; - for(base = 0;base < SamplesToDo;) + for(size_t base{0};base < SamplesToDo;) { - int todo = std::min(128, SamplesToDo-base); - int i; + const size_t todo{std::min(128, SamplesToDo-base)}; /* Process left input */ - lsamples[0][0] = bs2b->a0_lo*Left[0] + - bs2b->b1_lo*bs2b->last_sample[0].lo; - lsamples[0][1] = bs2b->a0_hi*Left[0] + - bs2b->a1_hi*bs2b->last_sample[0].asis + - bs2b->b1_hi*bs2b->last_sample[0].hi; - for(i = 1;i < todo;i++) + float z_lo{bs2b->history[0].lo}; + float z_hi{bs2b->history[0].hi}; + for(size_t i{0};i < todo;i++) { - lsamples[i][0] = bs2b->a0_lo*Left[i] + - bs2b->b1_lo*lsamples[i-1][0]; - lsamples[i][1] = bs2b->a0_hi*Left[i] + - bs2b->a1_hi*Left[i-1] + - bs2b->b1_hi*lsamples[i-1][1]; + lsamples[i][0] = a0_lo*Left[i] + z_lo; + z_lo = b1_lo*lsamples[i][0]; + + lsamples[i][1] = a0_hi*Left[i] + z_hi; + z_hi = a1_hi*Left[i] + b1_hi*lsamples[i][1]; } - bs2b->last_sample[0].asis = Left[i-1]; - bs2b->last_sample[0].lo = lsamples[i-1][0]; - bs2b->last_sample[0].hi = lsamples[i-1][1]; + bs2b->history[0].lo = z_lo; + bs2b->history[0].hi = z_hi; /* Process right input */ - rsamples[0][0] = bs2b->a0_lo*Right[0] + - bs2b->b1_lo*bs2b->last_sample[1].lo; - rsamples[0][1] = bs2b->a0_hi*Right[0] + - bs2b->a1_hi*bs2b->last_sample[1].asis + - bs2b->b1_hi*bs2b->last_sample[1].hi; - for(i = 1;i < todo;i++) + z_lo = bs2b->history[1].lo; + z_hi = bs2b->history[1].hi; + for(size_t i{0};i < todo;i++) { - rsamples[i][0] = bs2b->a0_lo*Right[i] + - bs2b->b1_lo*rsamples[i-1][0]; - rsamples[i][1] = bs2b->a0_hi*Right[i] + - bs2b->a1_hi*Right[i-1] + - bs2b->b1_hi*rsamples[i-1][1]; + rsamples[i][0] = a0_lo*Right[i] + z_lo; + z_lo = b1_lo*rsamples[i][0]; + + rsamples[i][1] = a0_hi*Right[i] + z_hi; + z_hi = a1_hi*Right[i] + b1_hi*rsamples[i][1]; } - bs2b->last_sample[1].asis = Right[i-1]; - bs2b->last_sample[1].lo = rsamples[i-1][0]; - bs2b->last_sample[1].hi = rsamples[i-1][1]; + bs2b->history[1].lo = z_lo; + bs2b->history[1].hi = z_hi; /* Crossfeed */ - for(i = 0;i < todo;i++) + for(size_t i{0};i < todo;i++) *(Left++) = lsamples[i][1] + rsamples[i][0]; - for(i = 0;i < todo;i++) + for(size_t i{0};i < todo;i++) *(Right++) = rsamples[i][1] + lsamples[i][0]; base += todo; diff --git a/modules/openal-soft/Alc/bs2b.h b/modules/openal-soft/core/bs2b.h similarity index 91% rename from modules/openal-soft/Alc/bs2b.h rename to modules/openal-soft/core/bs2b.h index e235e76..4d0b9dd 100644 --- a/modules/openal-soft/Alc/bs2b.h +++ b/modules/openal-soft/core/bs2b.h @@ -21,8 +21,8 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef BS2B_H -#define BS2B_H +#ifndef CORE_BS2B_H +#define CORE_BS2B_H #include "almalloc.h" @@ -57,14 +57,13 @@ struct bs2b { float a1_hi; float b1_hi; - /* Buffer of last filtered sample. + /* Buffer of filter history * [0] - first channel, [1] - second channel */ struct t_last_sample { - float asis; float lo; float hi; - } last_sample[2]; + } history[2]; DEF_NEWDEL(bs2b) }; @@ -85,6 +84,6 @@ int bs2b_get_srate(bs2b *bs2b); /* Clear buffer */ void bs2b_clear(bs2b *bs2b); -void bs2b_cross_feed(bs2b *bs2b, float *RESTRICT Left, float *RESTRICT Right, int SamplesToDo); +void bs2b_cross_feed(bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo); -#endif /* BS2B_H */ +#endif /* CORE_BS2B_H */ diff --git a/modules/openal-soft/core/bsinc_defs.h b/modules/openal-soft/core/bsinc_defs.h new file mode 100644 index 0000000..f295823 --- /dev/null +++ b/modules/openal-soft/core/bsinc_defs.h @@ -0,0 +1,10 @@ +#ifndef CORE_BSINC_DEFS_H +#define CORE_BSINC_DEFS_H + +/* The number of distinct scale and phase intervals within the filter table. */ +constexpr unsigned int BSincScaleBits{4}; +constexpr unsigned int BSincScaleCount{1 << BSincScaleBits}; +constexpr unsigned int BSincPhaseBits{5}; +constexpr unsigned int BSincPhaseCount{1 << BSincPhaseBits}; + +#endif /* CORE_BSINC_DEFS_H */ diff --git a/modules/openal-soft/core/bsinc_tables.cpp b/modules/openal-soft/core/bsinc_tables.cpp new file mode 100644 index 0000000..a81167d --- /dev/null +++ b/modules/openal-soft/core/bsinc_tables.cpp @@ -0,0 +1,295 @@ + +#include "bsinc_tables.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "alnumbers.h" +#include "core/mixer/defs.h" + + +namespace { + +using uint = unsigned int; + + +/* This is the normalized cardinal sine (sinc) function. + * + * sinc(x) = { 1, x = 0 + * { sin(pi x) / (pi x), otherwise. + */ +constexpr double Sinc(const double x) +{ + constexpr double epsilon{std::numeric_limits::epsilon()}; + if(!(x > epsilon || x < -epsilon)) + return 1.0; + return std::sin(al::numbers::pi*x) / (al::numbers::pi*x); +} + +/* The zero-order modified Bessel function of the first kind, used for the + * Kaiser window. + * + * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k) + * = sum_{k=0}^inf ((x / 2)^k / k!)^2 + */ +constexpr double BesselI_0(const double x) noexcept +{ + /* Start at k=1 since k=0 is trivial. */ + const double x2{x / 2.0}; + double term{1.0}; + double sum{1.0}; + double last_sum{}; + int k{1}; + + /* Let the integration converge until the term of the sum is no longer + * significant. + */ + do { + const double y{x2 / k}; + ++k; + last_sum = sum; + term *= y * y; + sum += term; + } while(sum != last_sum); + + return sum; +} + +/* Calculate a Kaiser window from the given beta value and a normalized k + * [-1, 1]. + * + * w(k) = { I_0(B sqrt(1 - k^2)) / I_0(B), -1 <= k <= 1 + * { 0, elsewhere. + * + * Where k can be calculated as: + * + * k = i / l, where -l <= i <= l. + * + * or: + * + * k = 2 i / M - 1, where 0 <= i <= M. + */ +constexpr double Kaiser(const double beta, const double k, const double besseli_0_beta) +{ + if(!(k >= -1.0 && k <= 1.0)) + return 0.0; + return BesselI_0(beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; +} + +/* Calculates the (normalized frequency) transition width of the Kaiser window. + * Rejection is in dB. + */ +constexpr double CalcKaiserWidth(const double rejection, const uint order) noexcept +{ + if(rejection > 21.19) + return (rejection - 7.95) / (2.285 * al::numbers::pi*2.0 * order); + /* This enforces a minimum rejection of just above 21.18dB */ + return 5.79 / (al::numbers::pi*2.0 * order); +} + +/* Calculates the beta value of the Kaiser window. Rejection is in dB. */ +constexpr double CalcKaiserBeta(const double rejection) +{ + if(rejection > 50.0) + return 0.1102 * (rejection-8.7); + else if(rejection >= 21.0) + return (0.5842 * std::pow(rejection-21.0, 0.4)) + (0.07886 * (rejection-21.0)); + return 0.0; +} + + +struct BSincHeader { + double width{}; + double beta{}; + double scaleBase{}; + double scaleRange{}; + double besseli_0_beta{}; + + uint a[BSincScaleCount]{}; + uint total_size{}; + + constexpr BSincHeader(uint Rejection, uint Order) noexcept + { + width = CalcKaiserWidth(Rejection, Order); + beta = CalcKaiserBeta(Rejection); + scaleBase = width / 2.0; + scaleRange = 1.0 - scaleBase; + besseli_0_beta = BesselI_0(beta); + + uint num_points{Order+1}; + for(uint si{0};si < BSincScaleCount;++si) + { + const double scale{scaleBase + (scaleRange * (si+1) / BSincScaleCount)}; + const uint a_{std::min(static_cast(num_points / 2.0 / scale), num_points)}; + const uint m{2 * a_}; + + a[si] = a_; + total_size += 4 * BSincPhaseCount * ((m+3) & ~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. + */ +constexpr BSincHeader bsinc12_hdr{60, 11}; +constexpr BSincHeader bsinc24_hdr{60, 23}; + + +/* NOTE: GCC 5 has an issue with BSincHeader objects being in an anonymous + * namespace while also being used as non-type template parameters. + */ +#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 6 + +/* The number of sample points is double the a value (rounded up to a multiple + * of 4), and scale index 0 includes the doubling for downsampling. bsinc24 is + * currently the highest quality filter, and will use the most sample points. + */ +constexpr uint BSincPointsMax{(bsinc24_hdr.a[0]*2 + 3) & ~3u}; +static_assert(BSincPointsMax <= MaxResamplerPadding, "MaxResamplerPadding is too small"); + +template +struct BSincFilterArray { + alignas(16) std::array mTable; + const BSincHeader &hdr; + + BSincFilterArray(const BSincHeader &hdr_) : hdr{hdr_} + { +#else +template +struct BSincFilterArray { + alignas(16) std::array mTable{}; + + BSincFilterArray() + { + constexpr uint BSincPointsMax{(hdr.a[0]*2 + 3) & ~3u}; + static_assert(BSincPointsMax <= MaxResamplerPadding, "MaxResamplerPadding is too small"); +#endif + using filter_type = double[BSincPhaseCount+1][BSincPointsMax]; + auto filter = std::make_unique(BSincScaleCount); + + /* 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{hdr.scaleBase + (hdr.scaleRange * (si+1) / BSincScaleCount)}; + const double cutoff{scale - (hdr.scaleBase * std::max(1.0, scale*2.0))}; + const auto a = static_cast(hdr.a[si]); + const double l{a - 1.0/BSincPhaseCount}; + + /* Do one extra phase index so that the phase delta has a proper + * target for its last index. + */ + for(uint pi{0};pi <= BSincPhaseCount;++pi) + { + const double phase{std::floor(l) + (pi/double{BSincPhaseCount})}; + + for(uint i{0};i < m;++i) + { + const double x{i - phase}; + filter[si][pi][o+i] = Kaiser(hdr.beta, x/l, hdr.besseli_0_beta) * cutoff * + Sinc(cutoff*x); + } + } + } + + 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}; + + /* Write out each phase index's filter and phase delta for this + * quality scale. + */ + for(size_t pi{0};pi < BSincPhaseCount;++pi) + { + for(size_t i{0};i < m;++i) + mTable[idx++] = static_cast(filter[si][pi][o+i]); + + /* Linear interpolation between phases is simplified by pre- + * calculating the delta (b - a) in: x = a + f (b - a) + */ + for(size_t i{0};i < m;++i) + { + const double phDelta{filter[si][pi+1][o+i] - filter[si][pi][o+i]}; + mTable[idx++] = static_cast(phDelta); + } + } + /* Calculate and write out each phase index's filter quality scale + * deltas. The last scale index doesn't have any scale or scale- + * phase deltas. + */ + if(si == BSincScaleCount-1) + { + for(size_t i{0};i < BSincPhaseCount*m*2;++i) + mTable[idx++] = 0.0f; + } + else for(size_t pi{0};pi < BSincPhaseCount;++pi) + { + /* Linear interpolation between scales is also simplified. + * + * Given a difference in the number of points between scales, + * the destination points will be 0, thus: x = a + f (-a) + */ + for(size_t i{0};i < m;++i) + { + const double scDelta{filter[si+1][pi][o+i] - filter[si][pi][o+i]}; + mTable[idx++] = static_cast(scDelta); + } + + /* This last simplification is done to complete the bilinear + * equation for the combination of phase and scale. + */ + for(size_t i{0};i < m;++i) + { + const double spDelta{(filter[si+1][pi+1][o+i] - filter[si+1][pi][o+i]) - + (filter[si][pi+1][o+i] - filter[si][pi][o+i])}; + mTable[idx++] = static_cast(spDelta); + } + } + } + assert(idx == hdr.total_size); + } + + constexpr const BSincHeader &getHeader() const noexcept { return hdr; } + constexpr const float *getTable() const noexcept { return &mTable.front(); } +}; + +#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 6 +const BSincFilterArray bsinc12_filter{bsinc12_hdr}; +const BSincFilterArray bsinc24_filter{bsinc24_hdr}; +#else +const BSincFilterArray bsinc12_filter{}; +const BSincFilterArray bsinc24_filter{}; +#endif + +template +constexpr BSincTable GenerateBSincTable(const T &filter) +{ + BSincTable ret{}; + const BSincHeader &hdr = filter.getHeader(); + ret.scaleBase = static_cast(hdr.scaleBase); + ret.scaleRange = static_cast(1.0 / hdr.scaleRange); + for(size_t i{0};i < BSincScaleCount;++i) + ret.m[i] = ((hdr.a[i]*2) + 3) & ~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; + ret.Tab = filter.getTable(); + return ret; +} + +} // namespace + +const BSincTable bsinc12{GenerateBSincTable(bsinc12_filter)}; +const BSincTable bsinc24{GenerateBSincTable(bsinc24_filter)}; diff --git a/modules/openal-soft/core/bsinc_tables.h b/modules/openal-soft/core/bsinc_tables.h new file mode 100644 index 0000000..f52cda6 --- /dev/null +++ b/modules/openal-soft/core/bsinc_tables.h @@ -0,0 +1,17 @@ +#ifndef CORE_BSINC_TABLES_H +#define CORE_BSINC_TABLES_H + +#include "bsinc_defs.h" + + +struct BSincTable { + float scaleBase, scaleRange; + unsigned int m[BSincScaleCount]; + unsigned int filterOffset[BSincScaleCount]; + const float *Tab; +}; + +extern const BSincTable bsinc12; +extern const BSincTable bsinc24; + +#endif /* CORE_BSINC_TABLES_H */ diff --git a/modules/openal-soft/core/buffer_storage.cpp b/modules/openal-soft/core/buffer_storage.cpp new file mode 100644 index 0000000..1c80e7e --- /dev/null +++ b/modules/openal-soft/core/buffer_storage.cpp @@ -0,0 +1,42 @@ + +#include "config.h" + +#include "buffer_storage.h" + +#include + + +uint BytesFromFmt(FmtType type) noexcept +{ + switch(type) + { + case FmtUByte: return sizeof(uint8_t); + case FmtShort: return sizeof(int16_t); + case FmtFloat: return sizeof(float); + case FmtDouble: return sizeof(double); + case FmtMulaw: return sizeof(uint8_t); + case FmtAlaw: return sizeof(uint8_t); + } + return 0; +} + +uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept +{ + switch(chans) + { + case FmtMono: return 1; + case FmtStereo: return 2; + case FmtRear: return 2; + case FmtQuad: return 4; + case FmtX51: return 6; + case FmtX61: return 7; + case FmtX71: return 8; + case FmtBFormat2D: return (ambiorder*2) + 1; + case FmtBFormat3D: return (ambiorder+1) * (ambiorder+1); + case FmtUHJ2: return 2; + case FmtUHJ3: return 3; + case FmtUHJ4: return 4; + case FmtSuperStereo: return 2; + } + return 0; +} diff --git a/modules/openal-soft/core/buffer_storage.h b/modules/openal-soft/core/buffer_storage.h new file mode 100644 index 0000000..ec93468 --- /dev/null +++ b/modules/openal-soft/core/buffer_storage.h @@ -0,0 +1,99 @@ +#ifndef CORE_BUFFER_STORAGE_H +#define CORE_BUFFER_STORAGE_H + +#include + +#include "albyte.h" +#include "alnumeric.h" +#include "ambidefs.h" + + +using uint = unsigned int; + +/* Storable formats */ +enum FmtType : unsigned char { + FmtUByte, + FmtShort, + FmtFloat, + FmtDouble, + FmtMulaw, + FmtAlaw, +}; +enum FmtChannels : unsigned char { + FmtMono, + FmtStereo, + FmtRear, + FmtQuad, + FmtX51, /* (WFX order) */ + FmtX61, /* (WFX order) */ + FmtX71, /* (WFX order) */ + FmtBFormat2D, + FmtBFormat3D, + FmtUHJ2, /* 2-channel UHJ, aka "BHJ", stereo-compatible */ + FmtUHJ3, /* 3-channel UHJ, aka "THJ" */ + FmtUHJ4, /* 4-channel UHJ, aka "PHJ" */ + FmtSuperStereo, /* Stereo processed with Super Stereo. */ +}; + +enum class AmbiLayout : unsigned char { + FuMa, + ACN, +}; +enum class AmbiScaling : unsigned char { + FuMa, + SN3D, + N3D, + UHJ, +}; + +uint BytesFromFmt(FmtType type) noexcept; +uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept; +inline uint FrameSizeFromFmt(FmtChannels chans, FmtType type, uint ambiorder) noexcept +{ return ChannelsFromFmt(chans, ambiorder) * BytesFromFmt(type); } + +constexpr bool IsBFormat(FmtChannels chans) noexcept +{ return chans == FmtBFormat2D || chans == FmtBFormat3D; } + +/* Super Stereo is considered part of the UHJ family here, since it goes + * through similar processing as UHJ, both result in a B-Format signal, and + * needs the same consideration as BHJ (three channel result with only two + * channel input). + */ +constexpr bool IsUHJ(FmtChannels chans) noexcept +{ return chans == FmtUHJ2 || chans == FmtUHJ3 || chans == FmtUHJ4 || chans == FmtSuperStereo; } + +/** Ambisonic formats are either B-Format or UHJ formats. */ +constexpr bool IsAmbisonic(FmtChannels chans) noexcept +{ return IsBFormat(chans) || IsUHJ(chans); } + +constexpr bool Is2DAmbisonic(FmtChannels chans) noexcept +{ + return chans == FmtBFormat2D || chans == FmtUHJ2 || chans == FmtUHJ3 + || chans == FmtSuperStereo; +} + + +using CallbackType = int(*)(void*, void*, int); + +struct BufferStorage { + CallbackType mCallback{nullptr}; + void *mUserData{nullptr}; + + uint mSampleRate{0u}; + FmtChannels mChannels{FmtMono}; + FmtType mType{FmtShort}; + uint mSampleLen{0u}; + + AmbiLayout mAmbiLayout{AmbiLayout::FuMa}; + AmbiScaling mAmbiScaling{AmbiScaling::FuMa}; + uint mAmbiOrder{0u}; + + inline uint bytesFromFmt() const noexcept { return BytesFromFmt(mType); } + inline uint channelsFromFmt() const noexcept + { return ChannelsFromFmt(mChannels, mAmbiOrder); } + inline uint frameSizeFromFmt() const noexcept { return channelsFromFmt() * bytesFromFmt(); } + + inline bool isBFormat() const noexcept { return IsBFormat(mChannels); } +}; + +#endif /* CORE_BUFFER_STORAGE_H */ diff --git a/modules/openal-soft/core/bufferline.h b/modules/openal-soft/core/bufferline.h new file mode 100644 index 0000000..8b445f3 --- /dev/null +++ b/modules/openal-soft/core/bufferline.h @@ -0,0 +1,17 @@ +#ifndef CORE_BUFFERLINE_H +#define CORE_BUFFERLINE_H + +#include + +#include "alspan.h" + +/* Size for temporary storage of buffer data, in floats. Larger values need + * more memory and are harder on cache, while smaller values may need more + * iterations for mixing. + */ +constexpr int BufferLineSize{1024}; + +using FloatBufferLine = std::array; +using FloatBufferSpan = al::span; + +#endif /* CORE_BUFFERLINE_H */ diff --git a/modules/openal-soft/core/context.cpp b/modules/openal-soft/core/context.cpp new file mode 100644 index 0000000..39fd852 --- /dev/null +++ b/modules/openal-soft/core/context.cpp @@ -0,0 +1,138 @@ + +#include "config.h" + +#include + +#include "async_event.h" +#include "context.h" +#include "device.h" +#include "effectslot.h" +#include "logging.h" +#include "ringbuffer.h" +#include "voice.h" +#include "voice_change.h" + + +ContextBase::ContextBase(DeviceBase *device) : mDevice{device} +{ } + +ContextBase::~ContextBase() +{ + size_t count{0}; + ContextProps *cprops{mParams.ContextUpdate.exchange(nullptr, std::memory_order_relaxed)}; + if(cprops) + { + ++count; + delete cprops; + } + cprops = mFreeContextProps.exchange(nullptr, std::memory_order_acquire); + while(cprops) + { + std::unique_ptr old{cprops}; + cprops = old->next.load(std::memory_order_relaxed); + ++count; + } + TRACE("Freed %zu context property object%s\n", count, (count==1)?"":"s"); + + count = 0; + EffectSlotProps *eprops{mFreeEffectslotProps.exchange(nullptr, std::memory_order_acquire)}; + while(eprops) + { + std::unique_ptr old{eprops}; + eprops = old->next.load(std::memory_order_relaxed); + ++count; + } + TRACE("Freed %zu AuxiliaryEffectSlot property object%s\n", count, (count==1)?"":"s"); + + if(EffectSlotArray *curarray{mActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed)}) + { + al::destroy_n(curarray->end(), curarray->size()); + delete curarray; + } + + delete mVoices.exchange(nullptr, std::memory_order_relaxed); + + if(mAsyncEvents) + { + count = 0; + auto evt_vec = mAsyncEvents->getReadVector(); + if(evt_vec.first.len > 0) + { + al::destroy_n(reinterpret_cast(evt_vec.first.buf), evt_vec.first.len); + count += evt_vec.first.len; + } + if(evt_vec.second.len > 0) + { + al::destroy_n(reinterpret_cast(evt_vec.second.buf), evt_vec.second.len); + count += evt_vec.second.len; + } + if(count > 0) + TRACE("Destructed %zu orphaned event%s\n", count, (count==1)?"":"s"); + mAsyncEvents->readAdvance(count); + } +} + + +void ContextBase::allocVoiceChanges() +{ + constexpr size_t clustersize{128}; + + VoiceChangeCluster cluster{std::make_unique(clustersize)}; + for(size_t i{1};i < clustersize;++i) + cluster[i-1].mNext.store(std::addressof(cluster[i]), std::memory_order_relaxed); + cluster[clustersize-1].mNext.store(mVoiceChangeTail, std::memory_order_relaxed); + + mVoiceChangeClusters.emplace_back(std::move(cluster)); + mVoiceChangeTail = mVoiceChangeClusters.back().get(); +} + +void ContextBase::allocVoiceProps() +{ + constexpr size_t clustersize{32}; + + TRACE("Increasing allocated voice properties to %zu\n", + (mVoicePropClusters.size()+1) * clustersize); + + VoicePropsCluster cluster{std::make_unique(clustersize)}; + for(size_t i{1};i < clustersize;++i) + cluster[i-1].next.store(std::addressof(cluster[i]), std::memory_order_relaxed); + mVoicePropClusters.emplace_back(std::move(cluster)); + + VoicePropsItem *oldhead{mFreeVoiceProps.load(std::memory_order_acquire)}; + do { + mVoicePropClusters.back()[clustersize-1].next.store(oldhead, std::memory_order_relaxed); + } while(mFreeVoiceProps.compare_exchange_weak(oldhead, mVoicePropClusters.back().get(), + std::memory_order_acq_rel, std::memory_order_acquire) == false); +} + +void ContextBase::allocVoices(size_t addcount) +{ + constexpr size_t clustersize{32}; + /* Convert element count to cluster count. */ + addcount = (addcount+(clustersize-1)) / clustersize; + + if(addcount >= std::numeric_limits::max()/clustersize - mVoiceClusters.size()) + throw std::runtime_error{"Allocating too many voices"}; + const size_t totalcount{(mVoiceClusters.size()+addcount) * clustersize}; + TRACE("Increasing allocated voices to %zu\n", totalcount); + + auto newarray = VoiceArray::Create(totalcount); + while(addcount) + { + mVoiceClusters.emplace_back(std::make_unique(clustersize)); + --addcount; + } + + auto voice_iter = newarray->begin(); + for(VoiceCluster &cluster : mVoiceClusters) + { + for(size_t i{0};i < clustersize;++i) + *(voice_iter++) = &cluster[i]; + } + + if(auto *oldvoices = mVoices.exchange(newarray.release(), std::memory_order_acq_rel)) + { + mDevice->waitForMix(); + delete oldvoices; + } +} diff --git a/modules/openal-soft/core/context.h b/modules/openal-soft/core/context.h new file mode 100644 index 0000000..f576862 --- /dev/null +++ b/modules/openal-soft/core/context.h @@ -0,0 +1,172 @@ +#ifndef CORE_CONTEXT_H +#define CORE_CONTEXT_H + +#include +#include +#include +#include +#include + +#include "almalloc.h" +#include "alspan.h" +#include "atomic.h" +#include "bufferline.h" +#include "threads.h" +#include "vecmat.h" +#include "vector.h" + +struct DeviceBase; +struct EffectSlot; +struct EffectSlotProps; +struct RingBuffer; +struct Voice; +struct VoiceChange; +struct VoicePropsItem; + +using uint = unsigned int; + + +constexpr float SpeedOfSoundMetersPerSec{343.3f}; + +constexpr float AirAbsorbGainHF{0.99426f}; /* -0.05dB */ + +enum class DistanceModel : unsigned char { + Disable, + Inverse, InverseClamped, + Linear, LinearClamped, + Exponent, ExponentClamped, + + Default = InverseClamped +}; + + +struct WetBuffer { + bool mInUse; + al::FlexArray mBuffer; + + WetBuffer(size_t count) : mBuffer{count} { } + + DEF_FAM_NEWDEL(WetBuffer, mBuffer) +}; +using WetBufferPtr = std::unique_ptr; + + +struct ContextProps { + std::array Position; + std::array Velocity; + std::array OrientAt; + std::array OrientUp; + float Gain; + float MetersPerUnit; + float AirAbsorptionGainHF; + + float DopplerFactor; + float DopplerVelocity; + float SpeedOfSound; + bool SourceDistanceModel; + DistanceModel mDistanceModel; + + std::atomic next; + + DEF_NEWDEL(ContextProps) +}; + +struct ContextParams { + /* Pointer to the most recent property values that are awaiting an update. */ + std::atomic ContextUpdate{nullptr}; + + alu::Vector Position{}; + alu::Matrix Matrix{alu::Matrix::Identity()}; + alu::Vector Velocity{}; + + float Gain{1.0f}; + float MetersPerUnit{1.0f}; + float AirAbsorptionGainHF{AirAbsorbGainHF}; + + float DopplerFactor{1.0f}; + float SpeedOfSound{SpeedOfSoundMetersPerSec}; /* in units per sec! */ + + bool SourceDistanceModel{false}; + DistanceModel mDistanceModel{}; +}; + +struct ContextBase { + DeviceBase *const mDevice; + + /* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit + * indicates if updates are currently happening). + */ + RefCount mUpdateCount{0u}; + std::atomic mHoldUpdates{false}; + std::atomic mStopVoicesOnDisconnect{true}; + + float mGainBoost{1.0f}; + + /* Linked lists of unused property containers, free to use for future + * updates. + */ + std::atomic mFreeContextProps{nullptr}; + std::atomic mFreeVoiceProps{nullptr}; + std::atomic mFreeEffectslotProps{nullptr}; + + /* The voice change tail is the beginning of the "free" elements, up to and + * *excluding* the current. If tail==current, there's no free elements and + * new ones need to be allocated. The current voice change is the element + * last processed, and any after are pending. + */ + VoiceChange *mVoiceChangeTail{}; + std::atomic mCurrentVoiceChange{}; + + void allocVoiceChanges(); + void allocVoiceProps(); + + + ContextParams mParams; + + using VoiceArray = al::FlexArray; + std::atomic mVoices{}; + std::atomic mActiveVoiceCount{}; + + void allocVoices(size_t addcount); + al::span getVoicesSpan() const noexcept + { + return {mVoices.load(std::memory_order_relaxed)->data(), + mActiveVoiceCount.load(std::memory_order_relaxed)}; + } + al::span getVoicesSpanAcquired() const noexcept + { + return {mVoices.load(std::memory_order_acquire)->data(), + mActiveVoiceCount.load(std::memory_order_acquire)}; + } + + + using EffectSlotArray = al::FlexArray; + std::atomic mActiveAuxSlots{nullptr}; + + std::thread mEventThread; + al::semaphore mEventSem; + std::unique_ptr mAsyncEvents; + std::atomic mEnabledEvts{0u}; + + /* Asynchronous voice change actions are processed as a linked list of + * VoiceChange objects by the mixer, which is atomically appended to. + * However, to avoid allocating each object individually, they're allocated + * in clusters that are stored in a vector for easy automatic cleanup. + */ + using VoiceChangeCluster = std::unique_ptr; + al::vector mVoiceChangeClusters; + + using VoiceCluster = std::unique_ptr; + al::vector mVoiceClusters; + + using VoicePropsCluster = std::unique_ptr; + al::vector mVoicePropClusters; + + + ContextBase(DeviceBase *device); + ContextBase(const ContextBase&) = delete; + ContextBase& operator=(const ContextBase&) = delete; + ~ContextBase(); +}; + +#endif /* CORE_CONTEXT_H */ diff --git a/modules/openal-soft/core/converter.cpp b/modules/openal-soft/core/converter.cpp new file mode 100644 index 0000000..6a06b46 --- /dev/null +++ b/modules/openal-soft/core/converter.cpp @@ -0,0 +1,371 @@ + +#include "config.h" + +#include "converter.h" + +#include +#include +#include +#include +#include + +#include "albit.h" +#include "albyte.h" +#include "alnumeric.h" +#include "fpu_ctrl.h" + +struct CTag; +struct CopyTag; + + +namespace { + +constexpr uint MaxPitch{10}; + +static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!"); +static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize, + "MaxPitch and/or BufferLineSize are too large for MixerFracBits!"); + +/* Base template left undefined. Should be marked =delete, but Clang 3.8.1 + * chokes on that given the inline specializations. + */ +template +inline float LoadSample(DevFmtType_t val) noexcept; + +template<> inline float LoadSample(DevFmtType_t val) noexcept +{ return val * (1.0f/128.0f); } +template<> inline float LoadSample(DevFmtType_t val) noexcept +{ return val * (1.0f/32768.0f); } +template<> inline float LoadSample(DevFmtType_t val) noexcept +{ return static_cast(val) * (1.0f/2147483648.0f); } +template<> inline float LoadSample(DevFmtType_t val) noexcept +{ return val; } + +template<> inline float LoadSample(DevFmtType_t val) noexcept +{ return LoadSample(static_cast(val - 128)); } +template<> inline float LoadSample(DevFmtType_t val) noexcept +{ return LoadSample(static_cast(val - 32768)); } +template<> inline float LoadSample(DevFmtType_t val) noexcept +{ return LoadSample(static_cast(val - 2147483648u)); } + + +template +inline void LoadSampleArray(float *RESTRICT dst, const void *src, const size_t srcstep, + const size_t samples) noexcept +{ + const DevFmtType_t *ssrc = static_cast*>(src); + for(size_t i{0u};i < samples;i++) + dst[i] = LoadSample(ssrc[i*srcstep]); +} + +void LoadSamples(float *dst, const void *src, const size_t srcstep, const DevFmtType srctype, + const size_t samples) noexcept +{ +#define HANDLE_FMT(T) \ + case T: LoadSampleArray(dst, src, srcstep, samples); break + switch(srctype) + { + HANDLE_FMT(DevFmtByte); + HANDLE_FMT(DevFmtUByte); + HANDLE_FMT(DevFmtShort); + HANDLE_FMT(DevFmtUShort); + HANDLE_FMT(DevFmtInt); + HANDLE_FMT(DevFmtUInt); + HANDLE_FMT(DevFmtFloat); + } +#undef HANDLE_FMT +} + + +template +inline DevFmtType_t StoreSample(float) noexcept; + +template<> inline float StoreSample(float val) noexcept +{ return val; } +template<> inline int32_t StoreSample(float val) noexcept +{ return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); } +template<> inline int16_t StoreSample(float val) noexcept +{ return static_cast(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); } +template<> inline int8_t StoreSample(float val) noexcept +{ return static_cast(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); } + +/* Define unsigned output variations. */ +template<> inline uint32_t StoreSample(float val) noexcept +{ return static_cast(StoreSample(val)) + 2147483648u; } +template<> inline uint16_t StoreSample(float val) noexcept +{ return static_cast(StoreSample(val) + 32768); } +template<> inline uint8_t StoreSample(float val) noexcept +{ return static_cast(StoreSample(val) + 128); } + +template +inline void StoreSampleArray(void *dst, const float *RESTRICT src, const size_t dststep, + const size_t samples) noexcept +{ + DevFmtType_t *sdst = static_cast*>(dst); + for(size_t i{0u};i < samples;i++) + sdst[i*dststep] = StoreSample(src[i]); +} + + +void StoreSamples(void *dst, const float *src, const size_t dststep, const DevFmtType dsttype, + const size_t samples) noexcept +{ +#define HANDLE_FMT(T) \ + case T: StoreSampleArray(dst, src, dststep, samples); break + switch(dsttype) + { + HANDLE_FMT(DevFmtByte); + HANDLE_FMT(DevFmtUByte); + HANDLE_FMT(DevFmtShort); + HANDLE_FMT(DevFmtUShort); + HANDLE_FMT(DevFmtInt); + HANDLE_FMT(DevFmtUInt); + HANDLE_FMT(DevFmtFloat); + } +#undef HANDLE_FMT +} + + +template +void Mono2Stereo(float *RESTRICT dst, const void *src, const size_t frames) noexcept +{ + const DevFmtType_t *ssrc = static_cast*>(src); + for(size_t i{0u};i < frames;i++) + dst[i*2 + 1] = dst[i*2 + 0] = LoadSample(ssrc[i]) * 0.707106781187f; +} + +template +void Multi2Mono(uint chanmask, const size_t step, const float scale, float *RESTRICT dst, + const void *src, const size_t frames) noexcept +{ + const DevFmtType_t *ssrc = static_cast*>(src); + std::fill_n(dst, frames, 0.0f); + for(size_t c{0};chanmask;++c) + { + if LIKELY((chanmask&1)) + { + for(size_t i{0u};i < frames;i++) + dst[i] += LoadSample(ssrc[i*step + c]); + } + chanmask >>= 1; + } + for(size_t i{0u};i < frames;i++) + dst[i] *= scale; +} + +} // namespace + +SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans, + uint srcRate, uint dstRate, Resampler resampler) +{ + if(numchans < 1 || srcRate < 1 || dstRate < 1) + return nullptr; + + SampleConverterPtr converter{new(FamCount(numchans)) SampleConverter{numchans}}; + converter->mSrcType = srcType; + converter->mDstType = dstType; + converter->mSrcTypeSize = BytesFromDevFmt(srcType); + converter->mDstTypeSize = BytesFromDevFmt(dstType); + + converter->mSrcPrepCount = 0; + converter->mFracOffset = 0; + + /* Have to set the mixer FPU mode since that's what the resampler code expects. */ + FPUCtl mixer_mode{}; + auto step = static_cast( + mind(srcRate*double{MixerFracOne}/dstRate + 0.5, MaxPitch*MixerFracOne)); + converter->mIncrement = maxu(step, 1); + if(converter->mIncrement == MixerFracOne) + converter->mResample = Resample_; + else + converter->mResample = PrepareResampler(resampler, converter->mIncrement, + &converter->mState); + + return converter; +} + +uint SampleConverter::availableOut(uint srcframes) const +{ + int prepcount{mSrcPrepCount}; + if(prepcount < 0) + { + /* Negative prepcount means we need to skip that many input samples. */ + if(static_cast(-prepcount) >= srcframes) + return 0; + srcframes -= static_cast(-prepcount); + prepcount = 0; + } + + if(srcframes < 1) + { + /* No output samples if there's no input samples. */ + return 0; + } + + if(prepcount < MaxResamplerPadding + && static_cast(MaxResamplerPadding - prepcount) >= srcframes) + { + /* Not enough input samples to generate an output sample. */ + return 0; + } + + auto DataSize64 = static_cast(prepcount); + DataSize64 += srcframes; + DataSize64 -= MaxResamplerPadding; + DataSize64 <<= MixerFracBits; + DataSize64 -= mFracOffset; + + /* If we have a full prep, we can generate at least one sample. */ + return static_cast(clampu64((DataSize64 + mIncrement-1)/mIncrement, 1, + std::numeric_limits::max())); +} + +uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint dstframes) +{ + const uint SrcFrameSize{static_cast(mChan.size()) * mSrcTypeSize}; + const uint DstFrameSize{static_cast(mChan.size()) * mDstTypeSize}; + const uint increment{mIncrement}; + auto SamplesIn = static_cast(*src); + uint NumSrcSamples{*srcframes}; + + FPUCtl mixer_mode{}; + uint pos{0}; + while(pos < dstframes && NumSrcSamples > 0) + { + int prepcount{mSrcPrepCount}; + if(prepcount < 0) + { + /* Negative prepcount means we need to skip that many input samples. */ + if(static_cast(-prepcount) >= NumSrcSamples) + { + mSrcPrepCount = static_cast(NumSrcSamples) + prepcount; + NumSrcSamples = 0; + break; + } + SamplesIn += SrcFrameSize*static_cast(-prepcount); + NumSrcSamples -= static_cast(-prepcount); + mSrcPrepCount = 0; + continue; + } + const uint toread{minu(NumSrcSamples, BufferLineSize - MaxResamplerPadding)}; + + if(prepcount < MaxResamplerPadding + && static_cast(MaxResamplerPadding - prepcount) >= toread) + { + /* Not enough input samples to generate an output sample. Store + * what we're given for later. + */ + for(size_t chan{0u};chan < mChan.size();chan++) + LoadSamples(&mChan[chan].PrevSamples[prepcount], SamplesIn + mSrcTypeSize*chan, + mChan.size(), mSrcType, toread); + + mSrcPrepCount = prepcount + static_cast(toread); + NumSrcSamples = 0; + break; + } + + float *RESTRICT SrcData{mSrcSamples}; + float *RESTRICT DstData{mDstSamples}; + uint DataPosFrac{mFracOffset}; + auto DataSize64 = static_cast(prepcount); + DataSize64 += toread; + DataSize64 -= MaxResamplerPadding; + DataSize64 <<= MixerFracBits; + DataSize64 -= DataPosFrac; + + /* If we have a full prep, we can generate at least one sample. */ + auto DstSize = static_cast( + clampu64((DataSize64 + increment-1)/increment, 1, BufferLineSize)); + DstSize = minu(DstSize, dstframes-pos); + + for(size_t chan{0u};chan < mChan.size();chan++) + { + const al::byte *SrcSamples{SamplesIn + mSrcTypeSize*chan}; + al::byte *DstSamples = static_cast(dst) + mDstTypeSize*chan; + + /* Load the previous samples into the source data first, then the + * new samples from the input buffer. + */ + std::copy_n(mChan[chan].PrevSamples, prepcount, SrcData); + LoadSamples(SrcData + prepcount, SrcSamples, mChan.size(), mSrcType, toread); + + /* Store as many prep samples for next time as possible, given the + * number of output samples being generated. + */ + uint SrcDataEnd{(DstSize*increment + DataPosFrac)>>MixerFracBits}; + if(SrcDataEnd >= static_cast(prepcount)+toread) + std::fill(std::begin(mChan[chan].PrevSamples), + std::end(mChan[chan].PrevSamples), 0.0f); + else + { + const size_t len{minz(al::size(mChan[chan].PrevSamples), + static_cast(prepcount)+toread-SrcDataEnd)}; + std::copy_n(SrcData+SrcDataEnd, len, mChan[chan].PrevSamples); + std::fill(std::begin(mChan[chan].PrevSamples)+len, + std::end(mChan[chan].PrevSamples), 0.0f); + } + + /* Now resample, and store the result in the output buffer. */ + const float *ResampledData{mResample(&mState, SrcData+(MaxResamplerPadding>>1), + DataPosFrac, increment, {DstData, DstSize})}; + + StoreSamples(DstSamples, ResampledData, mChan.size(), mDstType, DstSize); + } + + /* Update the number of prep samples still available, as well as the + * fractional offset. + */ + DataPosFrac += increment*DstSize; + mSrcPrepCount = mini(prepcount + static_cast(toread - (DataPosFrac>>MixerFracBits)), + MaxResamplerPadding); + mFracOffset = DataPosFrac & MixerFracMask; + + /* Update the src and dst pointers in case there's still more to do. */ + SamplesIn += SrcFrameSize*(DataPosFrac>>MixerFracBits); + NumSrcSamples -= minu(NumSrcSamples, (DataPosFrac>>MixerFracBits)); + + dst = static_cast(dst) + DstFrameSize*DstSize; + pos += DstSize; + } + + *src = SamplesIn; + *srcframes = NumSrcSamples; + + return pos; +} + + +void ChannelConverter::convert(const void *src, float *dst, uint frames) const +{ + if(mDstChans == DevFmtMono) + { + const float scale{std::sqrt(1.0f / static_cast(al::popcount(mChanMask)))}; + switch(mSrcType) + { +#define HANDLE_FMT(T) case T: Multi2Mono(mChanMask, mSrcStep, scale, dst, src, frames); break + HANDLE_FMT(DevFmtByte); + HANDLE_FMT(DevFmtUByte); + HANDLE_FMT(DevFmtShort); + HANDLE_FMT(DevFmtUShort); + HANDLE_FMT(DevFmtInt); + HANDLE_FMT(DevFmtUInt); + HANDLE_FMT(DevFmtFloat); +#undef HANDLE_FMT + } + } + else if(mChanMask == 0x1 && mDstChans == DevFmtStereo) + { + switch(mSrcType) + { +#define HANDLE_FMT(T) case T: Mono2Stereo(dst, src, frames); break + HANDLE_FMT(DevFmtByte); + HANDLE_FMT(DevFmtUByte); + HANDLE_FMT(DevFmtShort); + HANDLE_FMT(DevFmtUShort); + HANDLE_FMT(DevFmtInt); + HANDLE_FMT(DevFmtUInt); + HANDLE_FMT(DevFmtFloat); +#undef HANDLE_FMT + } + } +} diff --git a/modules/openal-soft/core/converter.h b/modules/openal-soft/core/converter.h new file mode 100644 index 0000000..2d22ae3 --- /dev/null +++ b/modules/openal-soft/core/converter.h @@ -0,0 +1,59 @@ +#ifndef CORE_CONVERTER_H +#define CORE_CONVERTER_H + +#include +#include + +#include "almalloc.h" +#include "devformat.h" +#include "mixer/defs.h" + +using uint = unsigned int; + + +struct SampleConverter { + DevFmtType mSrcType{}; + DevFmtType mDstType{}; + uint mSrcTypeSize{}; + uint mDstTypeSize{}; + + int mSrcPrepCount{}; + + uint mFracOffset{}; + uint mIncrement{}; + InterpState mState{}; + ResamplerFunc mResample{}; + + alignas(16) float mSrcSamples[BufferLineSize]{}; + alignas(16) float mDstSamples[BufferLineSize]{}; + + struct ChanSamples { + alignas(16) float PrevSamples[MaxResamplerPadding]; + }; + al::FlexArray mChan; + + SampleConverter(size_t numchans) : mChan{numchans} { } + + uint convert(const void **src, uint *srcframes, void *dst, uint dstframes); + uint availableOut(uint srcframes) const; + + DEF_FAM_NEWDEL(SampleConverter, mChan) +}; +using SampleConverterPtr = std::unique_ptr; + +SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans, + uint srcRate, uint dstRate, Resampler resampler); + + +struct ChannelConverter { + DevFmtType mSrcType{}; + uint mSrcStep{}; + uint mChanMask{}; + DevFmtChannels mDstChans{}; + + bool is_active() const noexcept { return mChanMask != 0; } + + void convert(const void *src, float *dst, uint frames) const; +}; + +#endif /* CORE_CONVERTER_H */ diff --git a/modules/openal-soft/core/cpu_caps.cpp b/modules/openal-soft/core/cpu_caps.cpp new file mode 100644 index 0000000..8211b33 --- /dev/null +++ b/modules/openal-soft/core/cpu_caps.cpp @@ -0,0 +1,142 @@ + +#include "config.h" + +#include "cpu_caps.h" + +#if defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64)) +#define WIN32_LEAN_AND_MEAN +#include +#ifndef PF_ARM_NEON_INSTRUCTIONS_AVAILABLE +#define PF_ARM_NEON_INSTRUCTIONS_AVAILABLE 19 +#endif +#endif + +#ifdef HAVE_INTRIN_H +#include +#endif +#ifdef HAVE_CPUID_H +#include +#endif + +#include +#include +#include + + +int CPUCapFlags{0}; + +namespace { + +#if defined(HAVE_GCC_GET_CPUID) \ + && (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64)) +using reg_type = unsigned int; +inline std::array get_cpuid(unsigned int f) +{ + std::array ret{}; + __get_cpuid(f, &ret[0], &ret[1], &ret[2], &ret[3]); + return ret; +} +#define CAN_GET_CPUID +#elif defined(HAVE_CPUID_INTRINSIC) \ + && (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64)) +using reg_type = int; +inline std::array get_cpuid(unsigned int f) +{ + std::array ret{}; + (__cpuid)(ret.data(), f); + return ret; +} +#define CAN_GET_CPUID +#endif + +} // namespace + +al::optional GetCPUInfo() +{ + CPUInfo ret; + +#ifdef CAN_GET_CPUID + auto cpuregs = get_cpuid(0); + if(cpuregs[0] == 0) + return al::nullopt; + + const reg_type maxfunc{cpuregs[0]}; + + cpuregs = get_cpuid(0x80000000); + const reg_type maxextfunc{cpuregs[0]}; + + ret.mVendor.append(reinterpret_cast(&cpuregs[1]), 4); + ret.mVendor.append(reinterpret_cast(&cpuregs[3]), 4); + ret.mVendor.append(reinterpret_cast(&cpuregs[2]), 4); + auto iter_end = std::remove(ret.mVendor.begin(), ret.mVendor.end(), '\0'); + iter_end = std::unique(ret.mVendor.begin(), iter_end, + [](auto&& c0, auto&& c1) { return std::isspace(c0) && std::isspace(c1); }); + ret.mVendor.erase(iter_end, ret.mVendor.end()); + if(!ret.mVendor.empty() && std::isspace(ret.mVendor.back())) + ret.mVendor.pop_back(); + if(!ret.mVendor.empty() && std::isspace(ret.mVendor.front())) + ret.mVendor.erase(ret.mVendor.begin()); + + if(maxextfunc >= 0x80000004) + { + cpuregs = get_cpuid(0x80000002); + ret.mName.append(reinterpret_cast(cpuregs.data()), 16); + cpuregs = get_cpuid(0x80000003); + ret.mName.append(reinterpret_cast(cpuregs.data()), 16); + cpuregs = get_cpuid(0x80000004); + ret.mName.append(reinterpret_cast(cpuregs.data()), 16); + iter_end = std::remove(ret.mName.begin(), ret.mName.end(), '\0'); + iter_end = std::unique(ret.mName.begin(), iter_end, + [](auto&& c0, auto&& c1) { return std::isspace(c0) && std::isspace(c1); }); + ret.mName.erase(iter_end, ret.mName.end()); + if(!ret.mName.empty() && std::isspace(ret.mName.back())) + ret.mName.pop_back(); + if(!ret.mName.empty() && std::isspace(ret.mName.front())) + ret.mName.erase(ret.mName.begin()); + } + + if(maxfunc >= 1) + { + cpuregs = get_cpuid(1); + if((cpuregs[3]&(1<<25))) + ret.mCaps |= CPU_CAP_SSE; + if((ret.mCaps&CPU_CAP_SSE) && (cpuregs[3]&(1<<26))) + ret.mCaps |= CPU_CAP_SSE2; + if((ret.mCaps&CPU_CAP_SSE2) && (cpuregs[2]&(1<<0))) + ret.mCaps |= CPU_CAP_SSE3; + if((ret.mCaps&CPU_CAP_SSE3) && (cpuregs[2]&(1<<19))) + ret.mCaps |= CPU_CAP_SSE4_1; + } + +#else + + /* Assume support for whatever's supported if we can't check for it */ +#if defined(HAVE_SSE4_1) +#warning "Assuming SSE 4.1 run-time support!" + ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1; +#elif defined(HAVE_SSE3) +#warning "Assuming SSE 3 run-time support!" + ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3; +#elif defined(HAVE_SSE2) +#warning "Assuming SSE 2 run-time support!" + ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2; +#elif defined(HAVE_SSE) +#warning "Assuming SSE run-time support!" + ret.mCaps |= CPU_CAP_SSE; +#endif +#endif /* CAN_GET_CPUID */ + +#ifdef HAVE_NEON +#ifdef __ARM_NEON + ret.mCaps |= CPU_CAP_NEON; +#elif defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64)) + if(IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE)) + ret.mCaps |= CPU_CAP_NEON; +#else +#warning "Assuming NEON run-time support!" + ret.mCaps |= CPU_CAP_NEON; +#endif +#endif + + return al::make_optional(ret); +} diff --git a/modules/openal-soft/core/cpu_caps.h b/modules/openal-soft/core/cpu_caps.h new file mode 100644 index 0000000..ffd671d --- /dev/null +++ b/modules/openal-soft/core/cpu_caps.h @@ -0,0 +1,26 @@ +#ifndef CORE_CPU_CAPS_H +#define CORE_CPU_CAPS_H + +#include + +#include "aloptional.h" + + +extern int CPUCapFlags; +enum { + CPU_CAP_SSE = 1<<0, + CPU_CAP_SSE2 = 1<<1, + CPU_CAP_SSE3 = 1<<2, + CPU_CAP_SSE4_1 = 1<<3, + CPU_CAP_NEON = 1<<4, +}; + +struct CPUInfo { + std::string mVendor; + std::string mName; + int mCaps{0}; +}; + +al::optional GetCPUInfo(); + +#endif /* CORE_CPU_CAPS_H */ diff --git a/modules/openal-soft/core/dbus_wrap.cpp b/modules/openal-soft/core/dbus_wrap.cpp new file mode 100644 index 0000000..7f22170 --- /dev/null +++ b/modules/openal-soft/core/dbus_wrap.cpp @@ -0,0 +1,46 @@ + +#include "config.h" + +#include "dbus_wrap.h" + +#ifdef HAVE_DYNLOAD + +#include +#include + +#include "logging.h" + + +void *dbus_handle{nullptr}; +#define DECL_FUNC(x) decltype(p##x) p##x{}; +DBUS_FUNCTIONS(DECL_FUNC) +#undef DECL_FUNC + +void PrepareDBus() +{ + static constexpr char libname[] = "libdbus-1.so.3"; + + auto load_func = [](auto &f, const char *name) -> void + { f = reinterpret_cast>(GetSymbol(dbus_handle, name)); }; +#define LOAD_FUNC(x) do { \ + load_func(p##x, #x); \ + if(!p##x) \ + { \ + WARN("Failed to load function %s\n", #x); \ + CloseLib(dbus_handle); \ + dbus_handle = nullptr; \ + return; \ + } \ +} while(0); + + dbus_handle = LoadLib(libname); + if(!dbus_handle) + { + WARN("Failed to load %s\n", libname); + return; + } + +DBUS_FUNCTIONS(LOAD_FUNC) +#undef LOAD_FUNC +} +#endif diff --git a/modules/openal-soft/core/dbus_wrap.h b/modules/openal-soft/core/dbus_wrap.h new file mode 100644 index 0000000..09eaacf --- /dev/null +++ b/modules/openal-soft/core/dbus_wrap.h @@ -0,0 +1,87 @@ +#ifndef CORE_DBUS_WRAP_H +#define CORE_DBUS_WRAP_H + +#include + +#include + +#include "dynload.h" + +#ifdef HAVE_DYNLOAD + +#include + +#define DBUS_FUNCTIONS(MAGIC) \ +MAGIC(dbus_error_init) \ +MAGIC(dbus_error_free) \ +MAGIC(dbus_bus_get) \ +MAGIC(dbus_connection_set_exit_on_disconnect) \ +MAGIC(dbus_connection_unref) \ +MAGIC(dbus_connection_send_with_reply_and_block) \ +MAGIC(dbus_message_unref) \ +MAGIC(dbus_message_new_method_call) \ +MAGIC(dbus_message_append_args) \ +MAGIC(dbus_message_iter_init) \ +MAGIC(dbus_message_iter_next) \ +MAGIC(dbus_message_iter_recurse) \ +MAGIC(dbus_message_iter_get_arg_type) \ +MAGIC(dbus_message_iter_get_basic) \ +MAGIC(dbus_set_error_from_message) + +extern void *dbus_handle; +#define DECL_FUNC(x) extern decltype(x) *p##x; +DBUS_FUNCTIONS(DECL_FUNC) +#undef DECL_FUNC + +#ifndef IN_IDE_PARSER +#define dbus_error_init (*pdbus_error_init) +#define dbus_error_free (*pdbus_error_free) +#define dbus_bus_get (*pdbus_bus_get) +#define dbus_connection_set_exit_on_disconnect (*pdbus_connection_set_exit_on_disconnect) +#define dbus_connection_unref (*pdbus_connection_unref) +#define dbus_connection_send_with_reply_and_block (*pdbus_connection_send_with_reply_and_block) +#define dbus_message_unref (*pdbus_message_unref) +#define dbus_message_new_method_call (*pdbus_message_new_method_call) +#define dbus_message_append_args (*pdbus_message_append_args) +#define dbus_message_iter_init (*pdbus_message_iter_init) +#define dbus_message_iter_next (*pdbus_message_iter_next) +#define dbus_message_iter_recurse (*pdbus_message_iter_recurse) +#define dbus_message_iter_get_arg_type (*pdbus_message_iter_get_arg_type) +#define dbus_message_iter_get_basic (*pdbus_message_iter_get_basic) +#define dbus_set_error_from_message (*pdbus_set_error_from_message) +#endif + +void PrepareDBus(); + +inline auto HasDBus() +{ + static std::once_flag init_dbus{}; + std::call_once(init_dbus, []{ PrepareDBus(); }); + return dbus_handle; +} + +#else + +constexpr bool HasDBus() noexcept { return true; } +#endif /* HAVE_DYNLOAD */ + + +namespace dbus { + +struct Error { + Error() { dbus_error_init(&mError); } + ~Error() { dbus_error_free(&mError); } + DBusError* operator->() { return &mError; } + DBusError &get() { return mError; } +private: + DBusError mError{}; +}; + +struct ConnectionDeleter { + void operator()(DBusConnection *c) { dbus_connection_unref(c); } +}; +using ConnectionPtr = std::unique_ptr; + +} // namespace dbus + +#endif /* CORE_DBUS_WRAP_H */ diff --git a/modules/openal-soft/core/devformat.cpp b/modules/openal-soft/core/devformat.cpp new file mode 100644 index 0000000..c841b63 --- /dev/null +++ b/modules/openal-soft/core/devformat.cpp @@ -0,0 +1,63 @@ + +#include "config.h" + +#include "devformat.h" + + +uint BytesFromDevFmt(DevFmtType type) noexcept +{ + switch(type) + { + case DevFmtByte: return sizeof(int8_t); + case DevFmtUByte: return sizeof(uint8_t); + case DevFmtShort: return sizeof(int16_t); + case DevFmtUShort: return sizeof(uint16_t); + case DevFmtInt: return sizeof(int32_t); + case DevFmtUInt: return sizeof(uint32_t); + case DevFmtFloat: return sizeof(float); + } + return 0; +} +uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept +{ + switch(chans) + { + case DevFmtMono: return 1; + case DevFmtStereo: return 2; + case DevFmtQuad: return 4; + case DevFmtX51: return 6; + case DevFmtX61: return 7; + case DevFmtX71: return 8; + case DevFmtAmbi3D: return (ambiorder+1) * (ambiorder+1); + } + return 0; +} + +const char *DevFmtTypeString(DevFmtType type) noexcept +{ + 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"; + } + return "(unknown type)"; +} +const char *DevFmtChannelsString(DevFmtChannels chans) noexcept +{ + switch(chans) + { + case DevFmtMono: return "Mono"; + case DevFmtStereo: return "Stereo"; + case DevFmtQuad: return "Quadraphonic"; + case DevFmtX51: return "5.1 Surround"; + case DevFmtX61: return "6.1 Surround"; + case DevFmtX71: return "7.1 Surround"; + case DevFmtAmbi3D: return "Ambisonic 3D"; + } + return "(unknown channels)"; +} diff --git a/modules/openal-soft/core/devformat.h b/modules/openal-soft/core/devformat.h new file mode 100644 index 0000000..e6d3092 --- /dev/null +++ b/modules/openal-soft/core/devformat.h @@ -0,0 +1,103 @@ +#ifndef CORE_DEVFORMAT_H +#define CORE_DEVFORMAT_H + +#include + + +using uint = unsigned int; + +enum Channel : unsigned char { + FrontLeft = 0, + FrontRight, + FrontCenter, + LFE, + BackLeft, + BackRight, + BackCenter, + SideLeft, + SideRight, + + TopCenter, + TopFrontLeft, + TopFrontCenter, + TopFrontRight, + TopBackLeft, + TopBackCenter, + TopBackRight, + + MaxChannels +}; + + +/* Device formats */ +enum DevFmtType : unsigned char { + DevFmtByte, + DevFmtUByte, + DevFmtShort, + DevFmtUShort, + DevFmtInt, + DevFmtUInt, + DevFmtFloat, + + DevFmtTypeDefault = DevFmtFloat +}; +enum DevFmtChannels : unsigned char { + DevFmtMono, + DevFmtStereo, + DevFmtQuad, + DevFmtX51, + DevFmtX61, + DevFmtX71, + DevFmtAmbi3D, + + DevFmtChannelsDefault = DevFmtStereo +}; +#define MAX_OUTPUT_CHANNELS 16 + +/* DevFmtType traits, providing the type, etc given a DevFmtType. */ +template +struct DevFmtTypeTraits { }; + +template<> +struct DevFmtTypeTraits { using Type = int8_t; }; +template<> +struct DevFmtTypeTraits { using Type = uint8_t; }; +template<> +struct DevFmtTypeTraits { using Type = int16_t; }; +template<> +struct DevFmtTypeTraits { using Type = uint16_t; }; +template<> +struct DevFmtTypeTraits { using Type = int32_t; }; +template<> +struct DevFmtTypeTraits { using Type = uint32_t; }; +template<> +struct DevFmtTypeTraits { using Type = float; }; + +template +using DevFmtType_t = typename DevFmtTypeTraits::Type; + + +uint BytesFromDevFmt(DevFmtType type) noexcept; +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; + +enum class DevAmbiLayout : bool { + FuMa, + ACN, + + Default = ACN +}; + +enum class DevAmbiScaling : unsigned char { + FuMa, + SN3D, + N3D, + + Default = SN3D +}; + +#endif /* CORE_DEVFORMAT_H */ diff --git a/modules/openal-soft/core/device.cpp b/modules/openal-soft/core/device.cpp new file mode 100644 index 0000000..2766c5e --- /dev/null +++ b/modules/openal-soft/core/device.cpp @@ -0,0 +1,23 @@ + +#include "config.h" + +#include "bformatdec.h" +#include "bs2b.h" +#include "device.h" +#include "front_stablizer.h" +#include "hrtf.h" +#include "mastering.h" + + +al::FlexArray DeviceBase::sEmptyContextArray{0u}; + + +DeviceBase::DeviceBase(DeviceType type) : Type{type}, mContexts{&sEmptyContextArray} +{ +} + +DeviceBase::~DeviceBase() +{ + auto *oldarray = mContexts.exchange(nullptr, std::memory_order_relaxed); + if(oldarray != &sEmptyContextArray) delete oldarray; +} diff --git a/modules/openal-soft/core/device.h b/modules/openal-soft/core/device.h new file mode 100644 index 0000000..58a30f1 --- /dev/null +++ b/modules/openal-soft/core/device.h @@ -0,0 +1,310 @@ +#ifndef CORE_DEVICE_H +#define CORE_DEVICE_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "almalloc.h" +#include "alspan.h" +#include "ambidefs.h" +#include "atomic.h" +#include "bufferline.h" +#include "devformat.h" +#include "filters/nfc.h" +#include "intrusive_ptr.h" +#include "mixer/hrtfdefs.h" +#include "opthelpers.h" +#include "resampler_limits.h" +#include "uhjfilter.h" +#include "vector.h" + +class BFormatDec; +struct bs2b; +struct Compressor; +struct ContextBase; +struct DirectHrtfState; +struct HrtfStore; + +using uint = unsigned int; + + +#define MIN_OUTPUT_RATE 8000 +#define MAX_OUTPUT_RATE 192000 +#define DEFAULT_OUTPUT_RATE 44100 + +#define DEFAULT_UPDATE_SIZE 882 /* 20ms */ +#define DEFAULT_NUM_UPDATES 3 + + +enum class DeviceType : unsigned char { + Playback, + Capture, + Loopback +}; + + +enum class RenderMode : unsigned char { + Normal, + Pairwise, + Hrtf +}; + +enum class StereoEncoding : unsigned char { + Basic, + Uhj, + Hrtf, + + Default = Basic +}; + + +struct InputRemixMap { + struct TargetMix { Channel channel; float mix; }; + + Channel channel; + std::array targets; +}; + + +/* Maximum delay in samples for speaker distance compensation. */ +#define MAX_DELAY_LENGTH 1024 + +struct DistanceComp { + struct ChanData { + float Gain{1.0f}; + uint Length{0u}; /* Valid range is [0...MAX_DELAY_LENGTH). */ + float *Buffer{nullptr}; + }; + + std::array mChannels; + al::FlexArray mSamples; + + DistanceComp(size_t count) : mSamples{count} { } + + static std::unique_ptr Create(size_t numsamples) + { return std::unique_ptr{new(FamCount(numsamples)) DistanceComp{numsamples}}; } + + DEF_FAM_NEWDEL(DistanceComp, mSamples) +}; + + +struct BFChannelConfig { + float Scale; + uint Index; +}; + + +struct MixParams { + /* Coefficient channel mapping for mixing to the buffer. */ + std::array AmbiMap{}; + + al::span Buffer; +}; + +struct RealMixParams { + al::span RemixMap; + std::array ChannelIndex{}; + + al::span Buffer; +}; + +enum { + // Frequency was requested by the app or config file + FrequencyRequest, + // Channel configuration was requested by the config file + ChannelsRequest, + // Sample type was requested by the config file + SampleTypeRequest, + + // Specifies if the DSP is paused at user request + DevicePaused, + // Specifies if the device is currently running + DeviceRunning, + + // Specifies if the output plays directly on/in ears (headphones, headset, + // ear buds, etc). + DirectEar, + + DeviceFlagsCount +}; + +struct DeviceBase { + /* To avoid extraneous allocations, a 0-sized FlexArray is + * defined globally as a sharable object. + */ + static al::FlexArray sEmptyContextArray; + + std::atomic Connected{true}; + const DeviceType Type{}; + + uint Frequency{}; + uint UpdateSize{}; + uint BufferSize{}; + + DevFmtChannels FmtChans{}; + DevFmtType FmtType{}; + uint mAmbiOrder{0}; + float mXOverFreq{400.0f}; + /* For DevFmtAmbi* output only, specifies the channel order and + * normalization. + */ + DevAmbiLayout mAmbiLayout{DevAmbiLayout::Default}; + DevAmbiScaling mAmbiScale{DevAmbiScaling::Default}; + + std::string DeviceName; + + // Device flags + std::bitset Flags{}; + + uint NumAuxSends{}; + + /* Rendering mode. */ + RenderMode mRenderMode{RenderMode::Normal}; + + /* The average speaker distance as determined by the ambdec configuration, + * HRTF data set, or the NFC-HOA reference delay. Only used for NFC. + */ + float AvgSpeakerDist{0.0f}; + + /* The default NFC filter. Not used directly, but is pre-initialized with + * the control distance from AvgSpeakerDist. + */ + NfcFilter mNFCtrlFilter{}; + + uint SamplesDone{0u}; + std::chrono::nanoseconds ClockBase{0}; + std::chrono::nanoseconds FixedLatency{0}; + + /* Temp storage used for mixer processing. */ + static constexpr size_t MixerLineSize{BufferLineSize + MaxResamplerPadding + + UhjDecoder::sFilterDelay}; + static constexpr size_t MixerChannelsMax{16}; + using MixerBufferLine = std::array; + alignas(16) std::array mSampleData; + + alignas(16) float ResampledData[BufferLineSize]; + alignas(16) float FilteredData[BufferLineSize]; + union { + alignas(16) float HrtfSourceData[BufferLineSize + HrtfHistoryLength]; + alignas(16) float NfcSampleData[BufferLineSize]; + }; + + /* Persistent storage for HRTF mixing. */ + alignas(16) float2 HrtfAccumData[BufferLineSize + HrirLength]; + + /* Mixing buffer used by the Dry mix and Real output. */ + al::vector MixBuffer; + + /* The "dry" path corresponds to the main output. */ + MixParams Dry; + uint NumChannelsPerOrder[MaxAmbiOrder+1]{}; + + /* "Real" output, which will be written to the device buffer. May alias the + * dry buffer. + */ + RealMixParams RealOut; + + /* HRTF state and info */ + std::unique_ptr mHrtfState; + al::intrusive_ptr mHrtf; + uint mIrSize{0}; + + /* Ambisonic-to-UHJ encoder */ + std::unique_ptr mUhjEncoder; + + /* Ambisonic decoder for speakers */ + std::unique_ptr AmbiDecoder; + + /* Stereo-to-binaural filter */ + std::unique_ptr Bs2b; + + using PostProc = void(DeviceBase::*)(const size_t SamplesToDo); + PostProc PostProcess{nullptr}; + + std::unique_ptr Limiter; + + /* Delay buffers used to compensate for speaker distances. */ + std::unique_ptr ChannelDelays; + + /* Dithering control. */ + float DitherDepth{0.0f}; + uint DitherSeed{0u}; + + /* Running count of the mixer invocations, in 31.1 fixed point. This + * actually increments *twice* when mixing, first at the start and then at + * the end, so the bottom bit indicates if the device is currently mixing + * and the upper bits indicates how many mixes have been done. + */ + RefCount MixCount{0u}; + + // Contexts created on this device + std::atomic*> mContexts{nullptr}; + + + DeviceBase(DeviceType type); + DeviceBase(const DeviceBase&) = delete; + DeviceBase& operator=(const DeviceBase&) = delete; + ~DeviceBase(); + + uint bytesFromFmt() const noexcept { return BytesFromDevFmt(FmtType); } + uint channelsFromFmt() const noexcept { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); } + uint frameSizeFromFmt() const noexcept { return bytesFromFmt() * channelsFromFmt(); } + + uint waitForMix() const noexcept + { + uint refcount; + while((refcount=MixCount.load(std::memory_order_acquire))&1) { + } + return refcount; + } + + void ProcessHrtf(const size_t SamplesToDo); + void ProcessAmbiDec(const size_t SamplesToDo); + void ProcessAmbiDecStablized(const size_t SamplesToDo); + void ProcessUhj(const size_t SamplesToDo); + void ProcessBs2b(const size_t SamplesToDo); + + inline void postProcess(const size_t SamplesToDo) + { if LIKELY(PostProcess) (this->*PostProcess)(SamplesToDo); } + + void renderSamples(const al::span outBuffers, const uint numSamples); + void renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep); + + /* Caller must lock the device state, and the mixer must not be running. */ +#ifdef __USE_MINGW_ANSI_STDIO + [[gnu::format(gnu_printf,2,3)]] +#else + [[gnu::format(printf,2,3)]] +#endif + void handleDisconnect(const char *msg, ...); + + DISABLE_ALLOC() + +private: + uint renderSamples(const uint numSamples); +}; + + +/* Must be less than 15 characters (16 including terminating null) for + * compatibility with pthread_setname_np limitations. */ +#define MIXER_THREAD_NAME "alsoft-mixer" + +#define RECORD_THREAD_NAME "alsoft-record" + + +/** + * Returns the index for the given channel name (e.g. FrontCenter), or + * INVALID_CHANNEL_INDEX if it doesn't exist. + */ +inline uint GetChannelIdxByName(const RealMixParams &real, Channel chan) noexcept +{ return real.ChannelIndex[chan]; } +#define INVALID_CHANNEL_INDEX ~0u + +#endif /* CORE_DEVICE_H */ diff --git a/modules/openal-soft/core/effects/base.h b/modules/openal-soft/core/effects/base.h new file mode 100644 index 0000000..3094f62 --- /dev/null +++ b/modules/openal-soft/core/effects/base.h @@ -0,0 +1,205 @@ +#ifndef CORE_EFFECTS_BASE_H +#define CORE_EFFECTS_BASE_H + +#include + +#include "albyte.h" +#include "almalloc.h" +#include "alspan.h" +#include "atomic.h" +#include "core/bufferline.h" +#include "intrusive_ptr.h" + +struct BufferStorage; +struct ContextBase; +struct DeviceBase; +struct EffectSlot; +struct MixParams; +struct RealMixParams; + + +/** Target gain for the reverb decay feedback reaching the decay time. */ +constexpr float ReverbDecayGain{0.001f}; /* -60 dB */ + +constexpr float ReverbMaxReflectionsDelay{0.3f}; +constexpr float ReverbMaxLateReverbDelay{0.1f}; + +enum class ChorusWaveform { + Sinusoid, + Triangle +}; + +constexpr float ChorusMaxDelay{0.016f}; +constexpr float FlangerMaxDelay{0.004f}; + +constexpr float EchoMaxDelay{0.207f}; +constexpr float EchoMaxLRDelay{0.404f}; + +enum class FShifterDirection { + Down, + Up, + Off +}; + +enum class ModulatorWaveform { + Sinusoid, + Sawtooth, + Square +}; + +enum class VMorpherPhenome { + A, E, I, O, U, + AA, AE, AH, AO, EH, ER, IH, IY, UH, UW, + B, D, F, G, J, K, L, M, N, P, R, S, T, V, Z +}; + +enum class VMorpherWaveform { + Sinusoid, + Triangle, + Sawtooth +}; + +union EffectProps { + struct { + // Shared Reverb Properties + float Density; + float Diffusion; + float Gain; + float GainHF; + float DecayTime; + float DecayHFRatio; + float ReflectionsGain; + float ReflectionsDelay; + float LateReverbGain; + float LateReverbDelay; + float AirAbsorptionGainHF; + float RoomRolloffFactor; + bool DecayHFLimit; + + // Additional EAX Reverb Properties + float GainLF; + float DecayLFRatio; + float ReflectionsPan[3]; + float LateReverbPan[3]; + float EchoTime; + float EchoDepth; + float ModulationTime; + float ModulationDepth; + float HFReference; + float LFReference; + } Reverb; + + struct { + float AttackTime; + float ReleaseTime; + float Resonance; + float PeakGain; + } Autowah; + + struct { + ChorusWaveform Waveform; + int Phase; + float Rate; + float Depth; + float Feedback; + float Delay; + } Chorus; /* Also Flanger */ + + struct { + bool OnOff; + } Compressor; + + struct { + float Edge; + float Gain; + float LowpassCutoff; + float EQCenter; + float EQBandwidth; + } Distortion; + + struct { + float Delay; + float LRDelay; + + float Damping; + float Feedback; + + float Spread; + } Echo; + + struct { + float LowCutoff; + float LowGain; + float Mid1Center; + float Mid1Gain; + float Mid1Width; + float Mid2Center; + float Mid2Gain; + float Mid2Width; + float HighCutoff; + float HighGain; + } Equalizer; + + struct { + float Frequency; + FShifterDirection LeftDirection; + FShifterDirection RightDirection; + } Fshifter; + + struct { + float Frequency; + float HighPassCutoff; + ModulatorWaveform Waveform; + } Modulator; + + struct { + int CoarseTune; + int FineTune; + } Pshifter; + + struct { + float Rate; + VMorpherPhenome PhonemeA; + VMorpherPhenome PhonemeB; + int PhonemeACoarseTuning; + int PhonemeBCoarseTuning; + VMorpherWaveform Waveform; + } Vmorpher; + + struct { + float Gain; + } Dedicated; +}; + + +struct EffectTarget { + MixParams *Main; + RealMixParams *RealOut; +}; + +struct EffectState : public al::intrusive_ref { + struct Buffer { + const BufferStorage *storage; + al::span samples; + }; + + al::span mOutTarget; + + + virtual ~EffectState() = default; + + virtual void deviceUpdate(const DeviceBase *device, const Buffer &buffer) = 0; + virtual void update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) = 0; + virtual void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) = 0; +}; + + +struct EffectStateFactory { + virtual ~EffectStateFactory() = default; + + virtual al::intrusive_ptr create() = 0; +}; + +#endif /* CORE_EFFECTS_BASE_H */ diff --git a/modules/openal-soft/core/effectslot.cpp b/modules/openal-soft/core/effectslot.cpp new file mode 100644 index 0000000..51fb8d4 --- /dev/null +++ b/modules/openal-soft/core/effectslot.cpp @@ -0,0 +1,25 @@ + +#include "config.h" + +#include "effectslot.h" + +#include + +#include "almalloc.h" +#include "context.h" + + +EffectSlotArray *EffectSlot::CreatePtrArray(size_t count) noexcept +{ + /* Allocate space for twice as many pointers, so the mixer has scratch + * space to store a sorted list during mixing. + */ + void *ptr{al_calloc(alignof(EffectSlotArray), EffectSlotArray::Sizeof(count*2))}; + return al::construct_at(static_cast(ptr), count); +} + +EffectSlot::~EffectSlot() +{ + if(mWetBuffer) + mWetBuffer->mInUse = false; +} diff --git a/modules/openal-soft/core/effectslot.h b/modules/openal-soft/core/effectslot.h new file mode 100644 index 0000000..8b7b977 --- /dev/null +++ b/modules/openal-soft/core/effectslot.h @@ -0,0 +1,88 @@ +#ifndef CORE_EFFECTSLOT_H +#define CORE_EFFECTSLOT_H + +#include + +#include "almalloc.h" +#include "device.h" +#include "effects/base.h" +#include "intrusive_ptr.h" + +struct EffectSlot; +struct WetBuffer; + +using EffectSlotArray = al::FlexArray; + + +enum class EffectSlotType : unsigned char { + None, + Reverb, + Chorus, + Distortion, + Echo, + Flanger, + FrequencyShifter, + VocalMorpher, + PitchShifter, + RingModulator, + Autowah, + Compressor, + Equalizer, + EAXReverb, + DedicatedLFE, + DedicatedDialog, + Convolution +}; + +struct EffectSlotProps { + float Gain; + bool AuxSendAuto; + EffectSlot *Target; + + EffectSlotType Type; + EffectProps Props; + + al::intrusive_ptr State; + + std::atomic next; + + DEF_NEWDEL(EffectSlotProps) +}; + + +struct EffectSlot { + std::atomic Update{nullptr}; + + /* Wet buffer configuration is ACN channel order with N3D scaling. + * Consequently, effects that only want to work with mono input can use + * channel 0 by itself. Effects that want multichannel can process the + * ambisonics signal and make a B-Format source pan. + */ + MixParams Wet; + + float Gain{1.0f}; + bool AuxSendAuto{true}; + EffectSlot *Target{nullptr}; + + EffectSlotType EffectType{EffectSlotType::None}; + EffectProps mEffectProps{}; + EffectState *mEffectState{nullptr}; + + float RoomRolloff{0.0f}; /* Added to the source's room rolloff, not multiplied. */ + float DecayTime{0.0f}; + float DecayLFRatio{0.0f}; + float DecayHFRatio{0.0f}; + bool DecayHFLimit{false}; + float AirAbsorptionGainHF{1.0f}; + + /* Mixing buffer used by the Wet mix. */ + WetBuffer *mWetBuffer{nullptr}; + + ~EffectSlot(); + + static EffectSlotArray *CreatePtrArray(size_t count) noexcept; + + DISABLE_ALLOC() +}; + +#endif /* CORE_EFFECTSLOT_H */ diff --git a/modules/openal-soft/common/alexcpt.cpp b/modules/openal-soft/core/except.cpp similarity index 64% rename from modules/openal-soft/common/alexcpt.cpp rename to modules/openal-soft/core/except.cpp index 9e04bba..07bb410 100644 --- a/modules/openal-soft/common/alexcpt.cpp +++ b/modules/openal-soft/core/except.cpp @@ -1,7 +1,7 @@ #include "config.h" -#include "alexcpt.h" +#include "except.h" #include #include @@ -11,20 +11,21 @@ namespace al { -backend_exception::backend_exception(ALCenum code, const char *msg, ...) : mErrorCode{code} +/* Defined here to avoid inlining it. */ +base_exception::~base_exception() { } + +void base_exception::setMessage(const char* msg, std::va_list args) { - va_list args, args2; - va_start(args, msg); + std::va_list args2; va_copy(args2, args); int msglen{std::vsnprintf(nullptr, 0, msg, args)}; - if(LIKELY(msglen > 0)) + if LIKELY(msglen > 0) { mMessage.resize(static_cast(msglen)+1); std::vsnprintf(&mMessage[0], mMessage.length(), msg, args2); mMessage.pop_back(); } va_end(args2); - va_end(args); } } // namespace al diff --git a/modules/openal-soft/core/except.h b/modules/openal-soft/core/except.h new file mode 100644 index 0000000..0e28e9d --- /dev/null +++ b/modules/openal-soft/core/except.h @@ -0,0 +1,31 @@ +#ifndef CORE_EXCEPT_H +#define CORE_EXCEPT_H + +#include +#include +#include +#include + + +namespace al { + +class base_exception : public std::exception { + std::string mMessage; + +protected: + base_exception() = default; + virtual ~base_exception(); + + void setMessage(const char *msg, std::va_list args); + +public: + const char *what() const noexcept override { return mMessage.c_str(); } +}; + +} // namespace al + +#define START_API_FUNC try + +#define END_API_FUNC catch(...) { std::terminate(); } + +#endif /* CORE_EXCEPT_H */ diff --git a/modules/openal-soft/Alc/filters/biquad.cpp b/modules/openal-soft/core/filters/biquad.cpp similarity index 66% rename from modules/openal-soft/Alc/filters/biquad.cpp rename to modules/openal-soft/core/filters/biquad.cpp index e4b7bec..470b1cd 100644 --- a/modules/openal-soft/Alc/filters/biquad.cpp +++ b/modules/openal-soft/core/filters/biquad.cpp @@ -1,22 +1,23 @@ #include "config.h" -#include +#include "biquad.h" -#include "AL/alc.h" -#include "AL/al.h" +#include +#include +#include -#include "alMain.h" -#include "biquad.h" +#include "alnumbers.h" +#include "opthelpers.h" template -void BiquadFilterR::setParams(BiquadType type, Real gain, Real f0norm, Real rcpQ) +void BiquadFilterR::setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ) { // Limit gain to -100dB assert(gain > 0.00001f); - const Real w0{al::MathDefs::Tau() * f0norm}; + const Real w0{al::numbers::pi_v*2.0f * f0norm}; const Real sin_w0{std::sin(w0)}; const Real cos_w0{std::cos(w0)}; const Real alpha{sin_w0/2.0f * rcpQ}; @@ -47,7 +48,6 @@ void BiquadFilterR::setParams(BiquadType type, Real gain, Real f0norm, Rea a[2] = (gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2; break; case BiquadType::Peaking: - gain = std::sqrt(gain); b[0] = 1.0f + alpha * gain; b[1] = -2.0f * cos_w0; b[2] = 1.0f - alpha * gain; @@ -82,25 +82,23 @@ void BiquadFilterR::setParams(BiquadType type, Real gain, Real f0norm, Rea break; } - a1 = a[1] / a[0]; - a2 = a[2] / a[0]; - b0 = b[0] / a[0]; - b1 = b[1] / a[0]; - b2 = b[2] / a[0]; + mA1 = a[1] / a[0]; + mA2 = a[2] / a[0]; + mB0 = b[0] / a[0]; + mB1 = b[1] / a[0]; + mB2 = b[2] / a[0]; } template -void BiquadFilterR::process(Real *dst, const Real *src, int numsamples) +void BiquadFilterR::process(const al::span src, Real *dst) { - ASSUME(numsamples > 0); - - const Real b0{this->b0}; - const Real b1{this->b1}; - const Real b2{this->b2}; - const Real a1{this->a1}; - const Real a2{this->a2}; - Real z1{this->z1}; - Real z2{this->z2}; + const Real b0{mB0}; + const Real b1{mB1}; + const Real b2{mB2}; + const Real a1{mA1}; + const Real a2{mA2}; + Real z1{mZ1}; + Real z2{mZ2}; /* Processing loop is Transposed Direct Form II. This requires less storage * compared to Direct Form I (only two delay components, instead of a four- @@ -112,15 +110,54 @@ void BiquadFilterR::process(Real *dst, const Real *src, int numsamples) */ auto proc_sample = [b0,b1,b2,a1,a2,&z1,&z2](Real input) noexcept -> Real { - Real output = input*b0 + z1; + const Real output{input*b0 + z1}; z1 = input*b1 - output*a1 + z2; z2 = input*b2 - output*a2; return output; }; - std::transform(src, src+numsamples, dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst, proc_sample); + + mZ1 = z1; + mZ2 = z2; +} + +template +void BiquadFilterR::dualProcess(BiquadFilterR &other, const al::span src, + Real *dst) +{ + const Real b00{mB0}; + const Real b01{mB1}; + const Real b02{mB2}; + const Real a01{mA1}; + const Real a02{mA2}; + const Real b10{other.mB0}; + const Real b11{other.mB1}; + const Real b12{other.mB2}; + const Real a11{other.mA1}; + const Real a12{other.mA2}; + Real z01{mZ1}; + Real z02{mZ2}; + Real z11{other.mZ1}; + Real z12{other.mZ2}; + + auto proc_sample = [b00,b01,b02,a01,a02,b10,b11,b12,a11,a12,&z01,&z02,&z11,&z12](Real input) noexcept -> Real + { + const Real tmpout{input*b00 + z01}; + z01 = input*b01 - tmpout*a01 + z02; + z02 = input*b02 - tmpout*a02; + input = tmpout; + + const Real output{input*b10 + z11}; + z11 = input*b11 - output*a11 + z12; + z12 = input*b12 - output*a12; + return output; + }; + std::transform(src.cbegin(), src.cend(), dst, proc_sample); - this->z1 = z1; - this->z2 = z2; + mZ1 = z01; + mZ2 = z02; + other.mZ1 = z11; + other.mZ2 = z12; } template class BiquadFilterR; diff --git a/modules/openal-soft/core/filters/biquad.h b/modules/openal-soft/core/filters/biquad.h new file mode 100644 index 0000000..75a4009 --- /dev/null +++ b/modules/openal-soft/core/filters/biquad.h @@ -0,0 +1,144 @@ +#ifndef CORE_FILTERS_BIQUAD_H +#define CORE_FILTERS_BIQUAD_H + +#include +#include +#include +#include + +#include "alnumbers.h" +#include "alspan.h" + + +/* Filters implementation is based on the "Cookbook formulae for audio + * EQ biquad filter coefficients" by Robert Bristow-Johnson + * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt + */ +/* Implementation note: For the shelf and peaking filters, the specified gain + * is for the centerpoint of the transition band. This better fits EFX filter + * behavior, which expects the shelf's reference frequency to reach the given + * gain. To set the gain for the shelf or peak itself, use the square root of + * the desired linear gain (or halve the dB gain). + */ + +enum class BiquadType { + /** EFX-style low-pass filter, specifying a gain and reference frequency. */ + HighShelf, + /** EFX-style high-pass filter, specifying a gain and reference frequency. */ + LowShelf, + /** Peaking filter, specifying a gain and reference frequency. */ + Peaking, + + /** Low-pass cut-off filter, specifying a cut-off frequency. */ + LowPass, + /** High-pass cut-off filter, specifying a cut-off frequency. */ + HighPass, + /** Band-pass filter, specifying a center frequency. */ + BandPass, +}; + +template +class BiquadFilterR { + /* Last two delayed components for direct form II. */ + Real mZ1{0}, mZ2{0}; + /* Transfer function coefficients "b" (numerator) */ + Real mB0{1}, mB1{0}, mB2{0}; + /* Transfer function coefficients "a" (denominator; a0 is pre-applied). */ + Real mA1{0}, mA2{0}; + + void setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ); + + /** + * Calculates the rcpQ (i.e. 1/Q) coefficient for shelving filters, using + * the reference gain and shelf slope parameter. + * \param gain 0 < gain + * \param slope 0 < slope <= 1 + */ + static Real rcpQFromSlope(Real gain, Real slope) + { return std::sqrt((gain + Real{1}/gain)*(Real{1}/slope - Real{1}) + Real{2}); } + + /** + * Calculates the rcpQ (i.e. 1/Q) coefficient for filters, using the + * normalized reference frequency and bandwidth. + * \param f0norm 0 < f0norm < 0.5. + * \param bandwidth 0 < bandwidth + */ + static Real rcpQFromBandwidth(Real f0norm, Real bandwidth) + { + const Real w0{al::numbers::pi_v*Real{2} * f0norm}; + return 2.0f*std::sinh(std::log(Real{2})/Real{2}*bandwidth*w0/std::sin(w0)); + } + +public: + void clear() noexcept { mZ1 = mZ2 = Real{0}; } + + /** + * Sets the filter state for the specified filter type and its parameters. + * + * \param type The type of filter to apply. + * \param f0norm The normalized reference frequency (ref / sample_rate). + * This is the center point for the Shelf, Peaking, and BandPass filter + * types, or the cutoff frequency for the LowPass and HighPass filter + * types. + * \param gain The gain for the reference frequency response. Only used by + * the Shelf and Peaking filter types. + * \param slope Slope steepness of the transition band. + */ + void setParamsFromSlope(BiquadType type, Real f0norm, Real gain, Real slope) + { + gain = std::max(gain, 0.001f); /* Limit -60dB */ + setParams(type, f0norm, gain, rcpQFromSlope(gain, slope)); + } + + /** + * Sets the filter state for the specified filter type and its parameters. + * + * \param type The type of filter to apply. + * \param f0norm The normalized reference frequency (ref / sample_rate). + * This is the center point for the Shelf, Peaking, and BandPass filter + * types, or the cutoff frequency for the LowPass and HighPass filter + * types. + * \param gain The gain for the reference frequency response. Only used by + * the Shelf and Peaking filter types. + * \param bandwidth Normalized bandwidth of the transition band. + */ + void setParamsFromBandwidth(BiquadType type, Real f0norm, Real gain, Real bandwidth) + { setParams(type, f0norm, gain, rcpQFromBandwidth(f0norm, bandwidth)); } + + void copyParamsFrom(const BiquadFilterR &other) + { + mB0 = other.mB0; + mB1 = other.mB1; + mB2 = other.mB2; + mA1 = other.mA1; + mA2 = other.mA2; + } + + void process(const al::span src, Real *dst); + /** Processes this filter and the other at the same time. */ + void dualProcess(BiquadFilterR &other, const al::span src, Real *dst); + + /* Rather hacky. It's just here to support "manual" processing. */ + std::pair getComponents() const noexcept { return {mZ1, mZ2}; } + void setComponents(Real z1, Real z2) noexcept { mZ1 = z1; mZ2 = z2; } + Real processOne(const Real in, Real &z1, Real &z2) const noexcept + { + const Real out{in*mB0 + z1}; + z1 = in*mB1 - out*mA1 + z2; + z2 = in*mB2 - out*mA2; + return out; + } +}; + +template +struct DualBiquadR { + BiquadFilterR &f0, &f1; + + void process(const al::span src, Real *dst) + { f0.dualProcess(f1, src, dst); } +}; + +using BiquadFilter = BiquadFilterR; +using DualBiquad = DualBiquadR; + +#endif /* CORE_FILTERS_BIQUAD_H */ diff --git a/modules/openal-soft/Alc/filters/nfc.cpp b/modules/openal-soft/core/filters/nfc.cpp similarity index 87% rename from modules/openal-soft/Alc/filters/nfc.cpp rename to modules/openal-soft/core/filters/nfc.cpp index e607dd5..aa64c61 100644 --- a/modules/openal-soft/Alc/filters/nfc.cpp +++ b/modules/openal-soft/core/filters/nfc.cpp @@ -5,7 +5,7 @@ #include -#include "alMain.h" +#include "opthelpers.h" /* Near-field control filters are the basis for handling the near-field effect. @@ -62,25 +62,21 @@ NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept float b_00, g_0; float r; - nfc.base_gain = 1.0f; - nfc.gain = 1.0f; - - /* Calculate bass-boost coefficients. */ - r = 0.5f * w0; + /* Calculate bass-cut coefficients. */ + r = 0.5f * w1; b_00 = B[1][0] * r; g_0 = 1.0f + b_00; - nfc.gain *= g_0; - nfc.b1 = 2.0f * b_00 / g_0; + nfc.base_gain = 1.0f / g_0; + nfc.a1 = 2.0f * b_00 / g_0; - /* Calculate bass-cut coefficients. */ - r = 0.5f * w1; + /* Calculate bass-boost coefficients. */ + r = 0.5f * w0; b_00 = B[1][0] * r; g_0 = 1.0f + b_00; - nfc.base_gain /= g_0; - nfc.gain /= g_0; - nfc.a1 = 2.0f * b_00 / g_0; + nfc.gain = nfc.base_gain * g_0; + nfc.b1 = 2.0f * b_00 / g_0; return nfc; } @@ -102,29 +98,25 @@ NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept float b_10, b_11, g_1; float r; - nfc.base_gain = 1.0f; - nfc.gain = 1.0f; - - /* Calculate bass-boost coefficients. */ - r = 0.5f * w0; + /* Calculate bass-cut coefficients. */ + r = 0.5f * w1; b_10 = B[2][0] * r; b_11 = B[2][1] * r * r; g_1 = 1.0f + b_10 + b_11; - nfc.gain *= g_1; - nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; - nfc.b2 = 4.0f * b_11 / g_1; + nfc.base_gain = 1.0f / g_1; + nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; + nfc.a2 = 4.0f * b_11 / g_1; - /* Calculate bass-cut coefficients. */ - r = 0.5f * w1; + /* Calculate bass-boost coefficients. */ + r = 0.5f * w0; b_10 = B[2][0] * r; b_11 = B[2][1] * r * r; g_1 = 1.0f + b_10 + b_11; - nfc.base_gain /= g_1; - nfc.gain /= g_1; - nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; - nfc.a2 = 4.0f * b_11 / g_1; + nfc.gain = nfc.base_gain * g_1; + nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; + nfc.b2 = 4.0f * b_11 / g_1; return nfc; } @@ -149,35 +141,31 @@ NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept float b_00, g_0; float r; - nfc.base_gain = 1.0f; - nfc.gain = 1.0f; - - /* Calculate bass-boost coefficients. */ - r = 0.5f * w0; + /* Calculate bass-cut coefficients. */ + r = 0.5f * w1; b_10 = B[3][0] * r; b_11 = B[3][1] * r * r; b_00 = B[3][2] * r; g_1 = 1.0f + b_10 + b_11; g_0 = 1.0f + b_00; - nfc.gain *= g_1 * g_0; - nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; - nfc.b2 = 4.0f * b_11 / g_1; - nfc.b3 = 2.0f * b_00 / g_0; + nfc.base_gain = 1.0f / (g_1 * g_0); + nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; + nfc.a2 = 4.0f * b_11 / g_1; + nfc.a3 = 2.0f * b_00 / g_0; - /* Calculate bass-cut coefficients. */ - r = 0.5f * w1; + /* Calculate bass-boost coefficients. */ + r = 0.5f * w0; b_10 = B[3][0] * r; b_11 = B[3][1] * r * r; b_00 = B[3][2] * r; g_1 = 1.0f + b_10 + b_11; g_0 = 1.0f + b_00; - nfc.base_gain /= g_1 * g_0; - nfc.gain /= g_1 * g_0; - nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; - nfc.a2 = 4.0f * b_11 / g_1; - nfc.a3 = 2.0f * b_00 / g_0; + nfc.gain = nfc.base_gain * (g_1 * g_0); + nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; + nfc.b2 = 4.0f * b_11 / g_1; + nfc.b3 = 2.0f * b_00 / g_0; return nfc; } @@ -191,7 +179,7 @@ void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept const float g_1{1.0f + b_10 + b_11}; const float g_0{1.0f + b_00}; - nfc->gain = nfc->base_gain * g_1 * g_0; + nfc->gain = nfc->base_gain * (g_1 * g_0); nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc->b2 = 4.0f * b_11 / g_1; nfc->b3 = 2.0f * b_00 / g_0; @@ -205,11 +193,8 @@ NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept float b_00, b_01, g_0; float r; - nfc.base_gain = 1.0f; - nfc.gain = 1.0f; - - /* Calculate bass-boost coefficients. */ - r = 0.5f * w0; + /* Calculate bass-cut coefficients. */ + r = 0.5f * w1; b_10 = B[4][0] * r; b_11 = B[4][1] * r * r; b_00 = B[4][2] * r; @@ -217,14 +202,14 @@ NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept g_1 = 1.0f + b_10 + b_11; g_0 = 1.0f + b_00 + b_01; - nfc.gain *= g_1 * g_0; - nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; - nfc.b2 = 4.0f * b_11 / g_1; - nfc.b3 = (2.0f*b_00 + 4.0f*b_01) / g_0; - nfc.b4 = 4.0f * b_01 / g_0; + nfc.base_gain = 1.0f / (g_1 * g_0); + nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; + nfc.a2 = 4.0f * b_11 / g_1; + nfc.a3 = (2.0f*b_00 + 4.0f*b_01) / g_0; + nfc.a4 = 4.0f * b_01 / g_0; - /* Calculate bass-cut coefficients. */ - r = 0.5f * w1; + /* 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; @@ -232,12 +217,11 @@ NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept g_1 = 1.0f + b_10 + b_11; g_0 = 1.0f + b_00 + b_01; - nfc.base_gain /= g_1 * g_0; - nfc.gain /= g_1 * g_0; - nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; - nfc.a2 = 4.0f * b_11 / g_1; - nfc.a3 = (2.0f*b_00 + 4.0f*b_01) / g_0; - nfc.a4 = 4.0f * b_01 / g_0; + nfc.gain = nfc.base_gain * (g_1 * g_0); + nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; + nfc.b2 = 4.0f * b_11 / g_1; + nfc.b3 = (2.0f*b_00 + 4.0f*b_01) / g_0; + nfc.b4 = 4.0f * b_01 / g_0; return nfc; } @@ -252,7 +236,7 @@ void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept const float g_1{1.0f + b_10 + b_11}; const float g_0{1.0f + b_00 + b_01}; - nfc->gain = nfc->base_gain * g_1 * g_0; + nfc->gain = nfc->base_gain * (g_1 * g_0); nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc->b2 = 4.0f * b_11 / g_1; nfc->b3 = (2.0f*b_00 + 4.0f*b_01) / g_0; @@ -278,10 +262,8 @@ void NfcFilter::adjust(const float w0) noexcept } -void NfcFilter::process1(float *RESTRICT dst, const float *RESTRICT src, const int count) +void NfcFilter::process1(const al::span src, float *RESTRICT dst) { - ASSUME(count > 0); - const float gain{first.gain}; const float b1{first.b1}; const float a1{first.a1}; @@ -293,14 +275,12 @@ void NfcFilter::process1(float *RESTRICT dst, const float *RESTRICT src, const i z1 += y; return out; }; - std::transform(src, src+count, dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst, proc_sample); first.z[0] = z1; } -void NfcFilter::process2(float *RESTRICT dst, const float *RESTRICT src, const int count) +void NfcFilter::process2(const al::span src, float *RESTRICT dst) { - ASSUME(count > 0); - const float gain{second.gain}; const float b1{second.b1}; const float b2{second.b2}; @@ -316,15 +296,13 @@ void NfcFilter::process2(float *RESTRICT dst, const float *RESTRICT src, const i z1 += y; return out; }; - std::transform(src, src+count, dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst, proc_sample); second.z[0] = z1; second.z[1] = z2; } -void NfcFilter::process3(float *RESTRICT dst, const float *RESTRICT src, const int count) +void NfcFilter::process3(const al::span src, float *RESTRICT dst) { - ASSUME(count > 0); - const float gain{third.gain}; const float b1{third.b1}; const float b2{third.b2}; @@ -347,16 +325,14 @@ void NfcFilter::process3(float *RESTRICT dst, const float *RESTRICT src, const i z3 += y; return out; }; - std::transform(src, src+count, dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst, proc_sample); third.z[0] = z1; third.z[1] = z2; third.z[2] = z3; } -void NfcFilter::process4(float *RESTRICT dst, const float *RESTRICT src, const int count) +void NfcFilter::process4(const al::span src, float *RESTRICT dst) { - ASSUME(count > 0); - const float gain{fourth.gain}; const float b1{fourth.b1}; const float b2{fourth.b2}; @@ -383,7 +359,7 @@ void NfcFilter::process4(float *RESTRICT dst, const float *RESTRICT src, const i z3 += y; return out; }; - std::transform(src, src+count, dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst, proc_sample); fourth.z[0] = z1; fourth.z[1] = z2; fourth.z[2] = z3; diff --git a/modules/openal-soft/Alc/filters/nfc.h b/modules/openal-soft/core/filters/nfc.h similarity index 76% rename from modules/openal-soft/Alc/filters/nfc.h rename to modules/openal-soft/core/filters/nfc.h index b656850..33f67a5 100644 --- a/modules/openal-soft/Alc/filters/nfc.h +++ b/modules/openal-soft/core/filters/nfc.h @@ -1,5 +1,10 @@ -#ifndef FILTER_NFC_H -#define FILTER_NFC_H +#ifndef CORE_FILTERS_NFC_H +#define CORE_FILTERS_NFC_H + +#include + +#include "alspan.h" + struct NfcFilter1 { float base_gain, gain; @@ -43,16 +48,16 @@ public: void adjust(const float w0) noexcept; /* Near-field control filter for first-order ambisonic channels (1-3). */ - void process1(float *RESTRICT dst, const float *RESTRICT src, const int count); + void process1(const al::span src, float *RESTRICT dst); /* Near-field control filter for second-order ambisonic channels (4-8). */ - void process2(float *RESTRICT dst, const float *RESTRICT src, const int count); + void process2(const al::span src, float *RESTRICT dst); /* Near-field control filter for third-order ambisonic channels (9-15). */ - void process3(float *RESTRICT dst, const float *RESTRICT src, const int count); + void process3(const al::span src, float *RESTRICT dst); /* Near-field control filter for fourth-order ambisonic channels (16-24). */ - void process4(float *RESTRICT dst, const float *RESTRICT src, const int count); + void process4(const al::span src, float *RESTRICT dst); }; -#endif /* FILTER_NFC_H */ +#endif /* CORE_FILTERS_NFC_H */ diff --git a/modules/openal-soft/core/filters/splitter.cpp b/modules/openal-soft/core/filters/splitter.cpp new file mode 100644 index 0000000..e7f0375 --- /dev/null +++ b/modules/openal-soft/core/filters/splitter.cpp @@ -0,0 +1,178 @@ + +#include "config.h" + +#include "splitter.h" + +#include +#include +#include + +#include "alnumbers.h" +#include "opthelpers.h" + + +template +void BandSplitterR::init(Real f0norm) +{ + const Real w{f0norm * (al::numbers::pi_v*2)}; + const Real cw{std::cos(w)}; + if(cw > std::numeric_limits::epsilon()) + mCoeff = (std::sin(w) - 1.0f) / cw; + else + mCoeff = cw * -0.5f; + + mLpZ1 = 0.0f; + mLpZ2 = 0.0f; + mApZ1 = 0.0f; +} + +template +void BandSplitterR::process(const al::span input, Real *hpout, Real *lpout) +{ + const Real ap_coeff{mCoeff}; + const Real lp_coeff{mCoeff*0.5f + 0.5f}; + Real lp_z1{mLpZ1}; + Real lp_z2{mLpZ2}; + Real ap_z1{mApZ1}; + auto proc_sample = [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpout](const Real in) noexcept -> Real + { + /* Low-pass sample processing. */ + Real d{(in - lp_z1) * lp_coeff}; + Real lp_y{lp_z1 + d}; + lp_z1 = lp_y + d; + + d = (lp_y - lp_z2) * lp_coeff; + lp_y = lp_z2 + d; + lp_z2 = lp_y + d; + + *(lpout++) = lp_y; + + /* All-pass sample processing. */ + Real ap_y{in*ap_coeff + ap_z1}; + ap_z1 = in - ap_y*ap_coeff; + + /* High-pass generated from removing low-passed output. */ + return ap_y - lp_y; + }; + std::transform(input.cbegin(), input.cend(), hpout, proc_sample); + mLpZ1 = lp_z1; + mLpZ2 = lp_z2; + mApZ1 = ap_z1; +} + +template +void BandSplitterR::processHfScale(const al::span input, Real *RESTRICT output, + const Real hfscale) +{ + const Real ap_coeff{mCoeff}; + const Real lp_coeff{mCoeff*0.5f + 0.5f}; + Real lp_z1{mLpZ1}; + Real lp_z2{mLpZ2}; + Real ap_z1{mApZ1}; + auto proc_sample = [hfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real + { + /* Low-pass sample processing. */ + Real d{(in - lp_z1) * lp_coeff}; + Real lp_y{lp_z1 + d}; + lp_z1 = lp_y + d; + + d = (lp_y - lp_z2) * lp_coeff; + lp_y = lp_z2 + d; + lp_z2 = lp_y + d; + + /* All-pass sample processing. */ + Real ap_y{in*ap_coeff + ap_z1}; + ap_z1 = in - ap_y*ap_coeff; + + /* High-pass generated by removing the low-passed signal, which is then + * scaled and added back to the low-passed signal. + */ + return (ap_y-lp_y)*hfscale + lp_y; + }; + std::transform(input.begin(), input.end(), output, proc_sample); + mLpZ1 = lp_z1; + mLpZ2 = lp_z2; + mApZ1 = ap_z1; +} + +template +void BandSplitterR::processHfScale(const al::span samples, const Real hfscale) +{ + const Real ap_coeff{mCoeff}; + const Real lp_coeff{mCoeff*0.5f + 0.5f}; + Real lp_z1{mLpZ1}; + Real lp_z2{mLpZ2}; + Real ap_z1{mApZ1}; + auto proc_sample = [hfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real + { + /* Low-pass sample processing. */ + Real d{(in - lp_z1) * lp_coeff}; + Real lp_y{lp_z1 + d}; + lp_z1 = lp_y + d; + + d = (lp_y - lp_z2) * lp_coeff; + lp_y = lp_z2 + d; + lp_z2 = lp_y + d; + + /* All-pass sample processing. */ + Real ap_y{in*ap_coeff + ap_z1}; + ap_z1 = in - ap_y*ap_coeff; + + /* High-pass generated by removing the low-passed signal, which is then + * scaled and added back to the low-passed signal. + */ + return (ap_y-lp_y)*hfscale + lp_y; + }; + std::transform(samples.begin(), samples.end(), samples.begin(), proc_sample); + mLpZ1 = lp_z1; + mLpZ2 = lp_z2; + mApZ1 = ap_z1; +} + +template +void BandSplitterR::processScale(const al::span samples, const Real hfscale, const Real lfscale) +{ + const Real ap_coeff{mCoeff}; + const Real lp_coeff{mCoeff*0.5f + 0.5f}; + Real lp_z1{mLpZ1}; + Real lp_z2{mLpZ2}; + Real ap_z1{mApZ1}; + auto proc_sample = [hfscale,lfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real + { + Real d{(in - lp_z1) * lp_coeff}; + Real lp_y{lp_z1 + d}; + lp_z1 = lp_y + d; + + d = (lp_y - lp_z2) * lp_coeff; + lp_y = lp_z2 + d; + lp_z2 = lp_y + d; + + Real ap_y{in*ap_coeff + ap_z1}; + ap_z1 = in - ap_y*ap_coeff; + + /* Apply separate factors to the high and low frequencies. */ + return (ap_y-lp_y)*hfscale + lp_y*lfscale; + }; + std::transform(samples.begin(), samples.end(), samples.begin(), proc_sample); + mLpZ1 = lp_z1; + mLpZ2 = lp_z2; + mApZ1 = ap_z1; +} + +template +void BandSplitterR::applyAllpassRev(const al::span samples) const +{ + const Real coeff{mCoeff}; + Real z1{0.0f}; + auto proc_sample = [coeff,&z1](const Real in) noexcept -> Real + { + const Real out{in*coeff + z1}; + z1 = in - out*coeff; + return out; + }; + std::transform(samples.rbegin(), samples.rend(), samples.rbegin(), proc_sample); +} + + +template class BandSplitterR; +template class BandSplitterR; diff --git a/modules/openal-soft/core/filters/splitter.h b/modules/openal-soft/core/filters/splitter.h new file mode 100644 index 0000000..fa15bd5 --- /dev/null +++ b/modules/openal-soft/core/filters/splitter.h @@ -0,0 +1,43 @@ +#ifndef CORE_FILTERS_SPLITTER_H +#define CORE_FILTERS_SPLITTER_H + +#include + +#include "alspan.h" + + +/* Band splitter. Splits a signal into two phase-matching frequency bands. */ +template +class BandSplitterR { + Real mCoeff{0.0f}; + Real mLpZ1{0.0f}; + Real mLpZ2{0.0f}; + Real mApZ1{0.0f}; + +public: + BandSplitterR() = default; + BandSplitterR(const BandSplitterR&) = default; + BandSplitterR(Real f0norm) { init(f0norm); } + BandSplitterR& operator=(const BandSplitterR&) = default; + + void init(Real f0norm); + void clear() noexcept { mLpZ1 = mLpZ2 = mApZ1 = 0.0f; } + void process(const al::span input, Real *hpout, Real *lpout); + + void processHfScale(const al::span input, Real *output, const Real hfscale); + + void processHfScale(const al::span samples, const Real hfscale); + void processScale(const al::span samples, const Real hfscale, const Real lfscale); + + /** + * The all-pass portion of the band splitter. Applies the same phase shift + * without splitting the signal, in reverse. It starts from the back of the + * span and works toward the front, creating a phase shift of -n degrees + * instead of +n. Note that each use of this method is indepedent, it does + * not track history between calls. + */ + void applyAllpassRev(const al::span samples) const; +}; +using BandSplitter = BandSplitterR; + +#endif /* CORE_FILTERS_SPLITTER_H */ diff --git a/modules/openal-soft/core/fmt_traits.cpp b/modules/openal-soft/core/fmt_traits.cpp new file mode 100644 index 0000000..054d876 --- /dev/null +++ b/modules/openal-soft/core/fmt_traits.cpp @@ -0,0 +1,79 @@ + +#include "config.h" + +#include "fmt_traits.h" + + +namespace al { + +const int16_t muLawDecompressionTable[256] = { + -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, + -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, + -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, + -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, 0, + 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, + 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, + 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, + 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, + 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, + 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, + 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, + 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, + 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, + 876, 844, 812, 780, 748, 716, 684, 652, + 620, 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, 260, + 244, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0 +}; + +const int16_t aLawDecompressionTable[256] = { + -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, + -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, + -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, + -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, + -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944, + -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136, + -11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472, + -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568, + -344, -328, -376, -360, -280, -264, -312, -296, + -472, -456, -504, -488, -408, -392, -440, -424, + -88, -72, -120, -104, -24, -8, -56, -40, + -216, -200, -248, -232, -152, -136, -184, -168, + -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, + -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, + -688, -656, -752, -720, -560, -528, -624, -592, + -944, -912, -1008, -976, -816, -784, -880, -848, + 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, + 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, + 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, + 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, + 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, + 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, + 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, + 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, + 344, 328, 376, 360, 280, 264, 312, 296, + 472, 456, 504, 488, 408, 392, 440, 424, + 88, 72, 120, 104, 24, 8, 56, 40, + 216, 200, 248, 232, 152, 136, 184, 168, + 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, + 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, + 688, 656, 752, 720, 560, 528, 624, 592, + 944, 912, 1008, 976, 816, 784, 880, 848 +}; + +} // namespace al diff --git a/modules/openal-soft/core/fmt_traits.h b/modules/openal-soft/core/fmt_traits.h new file mode 100644 index 0000000..f797f83 --- /dev/null +++ b/modules/openal-soft/core/fmt_traits.h @@ -0,0 +1,81 @@ +#ifndef CORE_FMT_TRAITS_H +#define CORE_FMT_TRAITS_H + +#include +#include + +#include "albyte.h" +#include "buffer_storage.h" + + +namespace al { + +extern const int16_t muLawDecompressionTable[256]; +extern const int16_t aLawDecompressionTable[256]; + + +template +struct FmtTypeTraits { }; + +template<> +struct FmtTypeTraits { + using Type = uint8_t; + + template + static constexpr inline OutT to(const Type val) noexcept + { return val*OutT{1.0/128.0} - OutT{1.0}; } +}; +template<> +struct FmtTypeTraits { + using Type = int16_t; + + template + static constexpr inline OutT to(const Type val) noexcept { return val*OutT{1.0/32768.0}; } +}; +template<> +struct FmtTypeTraits { + using Type = float; + + template + static constexpr inline OutT to(const Type val) noexcept { return val; } +}; +template<> +struct FmtTypeTraits { + using Type = double; + + template + static constexpr inline OutT to(const Type val) noexcept { return static_cast(val); } +}; +template<> +struct FmtTypeTraits { + using Type = uint8_t; + + template + static constexpr inline OutT to(const Type val) noexcept + { return muLawDecompressionTable[val] * OutT{1.0/32768.0}; } +}; +template<> +struct FmtTypeTraits { + using Type = uint8_t; + + template + static constexpr inline OutT to(const Type val) noexcept + { return aLawDecompressionTable[val] * OutT{1.0/32768.0}; } +}; + + +template +inline void LoadSampleArray(DstT *RESTRICT dst, const al::byte *src, const size_t srcstep, + const size_t samples) noexcept +{ + using TypeTraits = FmtTypeTraits; + using SampleType = typename TypeTraits::Type; + + const SampleType *RESTRICT ssrc{reinterpret_cast(src)}; + for(size_t i{0u};i < samples;i++) + dst[i] = TypeTraits::template to(ssrc[i*srcstep]); +} + +} // namespace al + +#endif /* CORE_FMT_TRAITS_H */ diff --git a/modules/openal-soft/core/fpu_ctrl.cpp b/modules/openal-soft/core/fpu_ctrl.cpp new file mode 100644 index 0000000..0cf0d6e --- /dev/null +++ b/modules/openal-soft/core/fpu_ctrl.cpp @@ -0,0 +1,61 @@ + +#include "config.h" + +#include "fpu_ctrl.h" + +#ifdef HAVE_INTRIN_H +#include +#endif +#ifdef HAVE_SSE_INTRINSICS +#include +#ifndef _MM_DENORMALS_ZERO_MASK +/* Some headers seem to be missing these? */ +#define _MM_DENORMALS_ZERO_MASK 0x0040u +#define _MM_DENORMALS_ZERO_ON 0x0040u +#endif +#endif + +#include "cpu_caps.h" + + +void FPUCtl::enter() noexcept +{ + if(this->in_mode) return; + +#if defined(HAVE_SSE_INTRINSICS) + this->sse_state = _mm_getcsr(); + unsigned int sseState{this->sse_state}; + sseState &= ~(_MM_FLUSH_ZERO_MASK | _MM_DENORMALS_ZERO_MASK); + sseState |= _MM_FLUSH_ZERO_ON | _MM_DENORMALS_ZERO_ON; + _mm_setcsr(sseState); + +#elif defined(__GNUC__) && defined(HAVE_SSE) + + if((CPUCapFlags&CPU_CAP_SSE)) + { + __asm__ __volatile__("stmxcsr %0" : "=m" (*&this->sse_state)); + unsigned int sseState{this->sse_state}; + sseState |= 0x8000; /* set flush-to-zero */ + if((CPUCapFlags&CPU_CAP_SSE2)) + sseState |= 0x0040; /* set denormals-are-zero */ + __asm__ __volatile__("ldmxcsr %0" : : "m" (*&sseState)); + } +#endif + + this->in_mode = true; +} + +void FPUCtl::leave() noexcept +{ + if(!this->in_mode) return; + +#if defined(HAVE_SSE_INTRINSICS) + _mm_setcsr(this->sse_state); + +#elif defined(__GNUC__) && defined(HAVE_SSE) + + if((CPUCapFlags&CPU_CAP_SSE)) + __asm__ __volatile__("ldmxcsr %0" : : "m" (*&this->sse_state)); +#endif + this->in_mode = false; +} diff --git a/modules/openal-soft/core/fpu_ctrl.h b/modules/openal-soft/core/fpu_ctrl.h new file mode 100644 index 0000000..9554313 --- /dev/null +++ b/modules/openal-soft/core/fpu_ctrl.h @@ -0,0 +1,21 @@ +#ifndef CORE_FPU_CTRL_H +#define CORE_FPU_CTRL_H + +class FPUCtl { +#if defined(HAVE_SSE_INTRINSICS) || (defined(__GNUC__) && defined(HAVE_SSE)) + unsigned int sse_state{}; +#endif + bool in_mode{}; + +public: + FPUCtl() noexcept { enter(); in_mode = true; } + ~FPUCtl() { if(in_mode) leave(); } + + FPUCtl(const FPUCtl&) = delete; + FPUCtl& operator=(const FPUCtl&) = delete; + + void enter() noexcept; + void leave() noexcept; +}; + +#endif /* CORE_FPU_CTRL_H */ diff --git a/modules/openal-soft/core/front_stablizer.h b/modules/openal-soft/core/front_stablizer.h new file mode 100644 index 0000000..3d328a8 --- /dev/null +++ b/modules/openal-soft/core/front_stablizer.h @@ -0,0 +1,36 @@ +#ifndef CORE_FRONT_STABLIZER_H +#define CORE_FRONT_STABLIZER_H + +#include +#include + +#include "almalloc.h" +#include "bufferline.h" +#include "filters/splitter.h" + + +struct FrontStablizer { + static constexpr size_t DelayLength{256u}; + + FrontStablizer(size_t numchans) : DelayBuf{numchans} { } + + alignas(16) std::array Side{}; + alignas(16) std::array MidDirect{}; + alignas(16) std::array MidDelay{}; + + alignas(16) std::array TempBuf{}; + + BandSplitter MidFilter; + alignas(16) FloatBufferLine MidLF{}; + alignas(16) FloatBufferLine MidHF{}; + + using DelayLine = std::array; + al::FlexArray DelayBuf; + + static std::unique_ptr Create(size_t numchans) + { return std::unique_ptr{new(FamCount(numchans)) FrontStablizer{numchans}}; } + + DEF_FAM_NEWDEL(FrontStablizer, DelayBuf) +}; + +#endif /* CORE_FRONT_STABLIZER_H */ diff --git a/modules/openal-soft/core/helpers.cpp b/modules/openal-soft/core/helpers.cpp new file mode 100644 index 0000000..e4a94fe --- /dev/null +++ b/modules/openal-soft/core/helpers.cpp @@ -0,0 +1,557 @@ + +#include "config.h" + +#include "helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "almalloc.h" +#include "alfstream.h" +#include "alnumeric.h" +#include "aloptional.h" +#include "alspan.h" +#include "alstring.h" +#include "logging.h" +#include "strutils.h" +#include "vector.h" + + +/* Mixing thread piority level */ +int RTPrioLevel{1}; + +/* Allow reducing the process's RTTime limit for RTKit. */ +bool AllowRTTimeLimit{true}; + + +#ifdef _WIN32 + +#include + +const PathNamePair &GetProcBinary() +{ + static al::optional procbin; + if(procbin) return *procbin; + + auto fullpath = al::vector(256); + DWORD len{GetModuleFileNameW(nullptr, fullpath.data(), static_cast(fullpath.size()))}; + while(len == fullpath.size()) + { + fullpath.resize(fullpath.size() << 1); + len = GetModuleFileNameW(nullptr, fullpath.data(), static_cast(fullpath.size())); + } + if(len == 0) + { + ERR("Failed to get process name: error %lu\n", GetLastError()); + procbin = al::make_optional(); + return *procbin; + } + + fullpath.resize(len); + if(fullpath.back() != 0) + fullpath.push_back(0); + + auto sep = std::find(fullpath.rbegin()+1, fullpath.rend(), '\\'); + sep = std::find(fullpath.rbegin()+1, sep, '/'); + if(sep != fullpath.rend()) + { + *sep = 0; + procbin = al::make_optional(wstr_to_utf8(fullpath.data()), + wstr_to_utf8(&*sep + 1)); + } + else + procbin = al::make_optional(std::string{}, wstr_to_utf8(fullpath.data())); + + TRACE("Got binary: %s, %s\n", procbin->path.c_str(), procbin->fname.c_str()); + return *procbin; +} + +namespace { + +void DirectorySearch(const char *path, const char *ext, al::vector *const results) +{ + std::string pathstr{path}; + pathstr += "\\*"; + pathstr += ext; + TRACE("Searching %s\n", pathstr.c_str()); + + std::wstring wpath{utf8_to_wstr(pathstr.c_str())}; + WIN32_FIND_DATAW fdata; + HANDLE hdl{FindFirstFileW(wpath.c_str(), &fdata)}; + if(hdl == INVALID_HANDLE_VALUE) return; + + const auto base = results->size(); + + do { + results->emplace_back(); + std::string &str = results->back(); + str = path; + str += '\\'; + str += wstr_to_utf8(fdata.cFileName); + } while(FindNextFileW(hdl, &fdata)); + FindClose(hdl); + + const al::span newlist{results->data()+base, results->size()-base}; + std::sort(newlist.begin(), newlist.end()); + for(const auto &name : newlist) + TRACE(" got %s\n", name.c_str()); +} + +} // namespace + +al::vector SearchDataFiles(const char *ext, const char *subdir) +{ + auto is_slash = [](int c) noexcept -> int { return (c == '\\' || c == '/'); }; + + static std::mutex search_lock; + std::lock_guard _{search_lock}; + + /* If the path is absolute, use it directly. */ + al::vector results; + if(isalpha(subdir[0]) && subdir[1] == ':' && is_slash(subdir[2])) + { + std::string path{subdir}; + std::replace(path.begin(), path.end(), '/', '\\'); + DirectorySearch(path.c_str(), ext, &results); + return results; + } + if(subdir[0] == '\\' && subdir[1] == '\\' && subdir[2] == '?' && subdir[3] == '\\') + { + DirectorySearch(subdir, ext, &results); + return results; + } + + std::string path; + + /* Search the app-local directory. */ + if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH")) + { + path = wstr_to_utf8(localpath->c_str()); + if(is_slash(path.back())) + path.pop_back(); + } + else if(WCHAR *cwdbuf{_wgetcwd(nullptr, 0)}) + { + path = wstr_to_utf8(cwdbuf); + if(is_slash(path.back())) + path.pop_back(); + free(cwdbuf); + } + else + path = "."; + std::replace(path.begin(), path.end(), '/', '\\'); + DirectorySearch(path.c_str(), ext, &results); + + /* Search the local and global data dirs. */ + static const int ids[2]{ CSIDL_APPDATA, CSIDL_COMMON_APPDATA }; + for(int id : ids) + { + WCHAR buffer[MAX_PATH]; + if(SHGetSpecialFolderPathW(nullptr, buffer, id, FALSE) == FALSE) + continue; + + path = wstr_to_utf8(buffer); + if(!is_slash(path.back())) + path += '\\'; + path += subdir; + std::replace(path.begin(), path.end(), '/', '\\'); + + DirectorySearch(path.c_str(), ext, &results); + } + + return results; +} + +void SetRTPriority(void) +{ + if(RTPrioLevel > 0) + { + if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) + ERR("Failed to set priority level for thread\n"); + } +} + +#else + +#include +#include +#include +#ifdef __FreeBSD__ +#include +#endif +#ifdef __HAIKU__ +#include +#endif +#ifdef HAVE_PROC_PIDPATH +#include +#endif +#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) +#include +#include +#endif +#ifdef HAVE_RTKIT +#include +#include + +#include "dbus_wrap.h" +#include "rtkit.h" +#ifndef RLIMIT_RTTIME +#define RLIMIT_RTTIME 15 +#endif +#endif + +const PathNamePair &GetProcBinary() +{ + static al::optional procbin; + if(procbin) return *procbin; + + al::vector pathname; +#ifdef __FreeBSD__ + size_t pathlen; + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; + if(sysctl(mib, 4, nullptr, &pathlen, nullptr, 0) == -1) + WARN("Failed to sysctl kern.proc.pathname: %s\n", strerror(errno)); + else + { + pathname.resize(pathlen + 1); + sysctl(mib, 4, pathname.data(), &pathlen, nullptr, 0); + pathname.resize(pathlen); + } +#endif +#ifdef HAVE_PROC_PIDPATH + if(pathname.empty()) + { + char procpath[PROC_PIDPATHINFO_MAXSIZE]{}; + const pid_t pid{getpid()}; + if(proc_pidpath(pid, procpath, sizeof(procpath)) < 1) + ERR("proc_pidpath(%d, ...) failed: %s\n", pid, strerror(errno)); + else + pathname.insert(pathname.end(), procpath, procpath+strlen(procpath)); + } +#endif +#ifdef __HAIKU__ + if(pathname.empty()) + { + char procpath[PATH_MAX]; + if(find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, procpath, sizeof(procpath)) == B_OK) + pathname.insert(pathname.end(), procpath, procpath+strlen(procpath)); + } +#endif +#ifndef __SWITCH__ + if(pathname.empty()) + { + static const char SelfLinkNames[][32]{ + "/proc/self/exe", + "/proc/self/file", + "/proc/curproc/exe", + "/proc/curproc/file" + }; + + pathname.resize(256); + + const char *selfname{}; + ssize_t len{}; + for(const char *name : SelfLinkNames) + { + selfname = name; + len = readlink(selfname, pathname.data(), pathname.size()); + if(len >= 0 || errno != ENOENT) break; + } + + while(len > 0 && static_cast(len) == pathname.size()) + { + pathname.resize(pathname.size() << 1); + len = readlink(selfname, pathname.data(), pathname.size()); + } + if(len <= 0) + { + WARN("Failed to readlink %s: %s\n", selfname, strerror(errno)); + len = 0; + } + + pathname.resize(static_cast(len)); + } +#endif + while(!pathname.empty() && pathname.back() == 0) + pathname.pop_back(); + + auto sep = std::find(pathname.crbegin(), pathname.crend(), '/'); + if(sep != pathname.crend()) + procbin = al::make_optional(std::string(pathname.cbegin(), sep.base()-1), + std::string(sep.base(), pathname.cend())); + else + procbin = al::make_optional(std::string{}, + std::string(pathname.cbegin(), pathname.cend())); + + TRACE("Got binary: \"%s\", \"%s\"\n", procbin->path.c_str(), procbin->fname.c_str()); + return *procbin; +} + +namespace { + +void DirectorySearch(const char *path, const char *ext, al::vector *const results) +{ + TRACE("Searching %s for *%s\n", path, ext); + DIR *dir{opendir(path)}; + if(!dir) return; + + const auto base = results->size(); + const size_t extlen{strlen(ext)}; + + while(struct dirent *dirent{readdir(dir)}) + { + if(strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) + continue; + + const size_t len{strlen(dirent->d_name)}; + if(len <= extlen) continue; + if(al::strcasecmp(dirent->d_name+len-extlen, ext) != 0) + continue; + + results->emplace_back(); + std::string &str = results->back(); + str = path; + if(str.back() != '/') + str.push_back('/'); + str += dirent->d_name; + } + closedir(dir); + + const al::span newlist{results->data()+base, results->size()-base}; + std::sort(newlist.begin(), newlist.end()); + for(const auto &name : newlist) + TRACE(" got %s\n", name.c_str()); +} + +} // namespace + +al::vector SearchDataFiles(const char *ext, const char *subdir) +{ + static std::mutex search_lock; + std::lock_guard _{search_lock}; + + al::vector results; + if(subdir[0] == '/') + { + DirectorySearch(subdir, ext, &results); + return results; + } + + /* Search the app-local directory. */ + if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH")) + DirectorySearch(localpath->c_str(), ext, &results); + else + { + al::vector cwdbuf(256); + while(!getcwd(cwdbuf.data(), cwdbuf.size())) + { + if(errno != ERANGE) + { + cwdbuf.clear(); + break; + } + cwdbuf.resize(cwdbuf.size() << 1); + } + if(cwdbuf.empty()) + DirectorySearch(".", ext, &results); + else + { + DirectorySearch(cwdbuf.data(), ext, &results); + cwdbuf.clear(); + } + } + + // Search local data dir + if(auto datapath = al::getenv("XDG_DATA_HOME")) + { + std::string &path = *datapath; + if(path.back() != '/') + path += '/'; + path += subdir; + DirectorySearch(path.c_str(), ext, &results); + } + else if(auto homepath = al::getenv("HOME")) + { + std::string &path = *homepath; + if(path.back() == '/') + path.pop_back(); + path += "/.local/share/"; + path += subdir; + DirectorySearch(path.c_str(), ext, &results); + } + + // Search global data dirs + std::string datadirs{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")}; + + size_t curpos{0u}; + while(curpos < datadirs.size()) + { + size_t nextpos{datadirs.find(':', curpos)}; + + std::string path{(nextpos != std::string::npos) ? + datadirs.substr(curpos, nextpos++ - curpos) : datadirs.substr(curpos)}; + curpos = nextpos; + + if(path.empty()) continue; + if(path.back() != '/') + path += '/'; + path += subdir; + + DirectorySearch(path.c_str(), ext, &results); + } + + return results; +} + +namespace { + +bool SetRTPriorityPthread(int prio) +{ + int err{ENOTSUP}; +#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) + /* Get the min and max priority for SCHED_RR. Limit the max priority to + * half, for now, to ensure the thread can't take the highest priority and + * go rogue. + */ + int rtmin{sched_get_priority_min(SCHED_RR)}; + int rtmax{sched_get_priority_max(SCHED_RR)}; + rtmax = (rtmax-rtmin)/2 + rtmin; + + struct sched_param param{}; + param.sched_priority = clampi(prio, rtmin, rtmax); +#ifdef SCHED_RESET_ON_FORK + err = pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, ¶m); + if(err == EINVAL) +#endif + err = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); + if(err == 0) return true; + +#else + + std::ignore = prio; +#endif + WARN("pthread_setschedparam failed: %s (%d)\n", std::strerror(err), err); + return false; +} + +bool SetRTPriorityRTKit(int prio) +{ +#ifdef HAVE_RTKIT + if(!HasDBus()) + { + WARN("D-Bus not available\n"); + 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); + return false; + } + + /* Don't stupidly exit if the connection dies while doing this. */ + dbus_connection_set_exit_on_disconnect(conn.get(), false); + + auto limit_rttime = [](DBusConnection *c) -> int + { + using ulonglong = unsigned long long; + long long maxrttime{rtkit_get_rttime_usec_max(c)}; + if(maxrttime <= 0) return static_cast(std::abs(maxrttime)); + const ulonglong umaxtime{static_cast(maxrttime)}; + + struct rlimit rlim{}; + if(getrlimit(RLIMIT_RTTIME, &rlim) != 0) + return errno; + + TRACE("RTTime max: %llu (hard: %llu, soft: %llu)\n", umaxtime, ulonglong{rlim.rlim_max}, + ulonglong{rlim.rlim_cur}); + if(rlim.rlim_max > umaxtime) + { + rlim.rlim_max = static_cast(umaxtime); + rlim.rlim_cur = std::min(rlim.rlim_cur, rlim.rlim_max); + if(setrlimit(RLIMIT_RTTIME, &rlim) != 0) + return errno; + } + return 0; + }; + + int nicemin{}; + int err{rtkit_get_min_nice_level(conn.get(), &nicemin)}; + if(err == -ENOENT) + { + err = std::abs(err); + ERR("Could not query RTKit: %s (%d)\n", std::strerror(err), err); + return false; + } + int rtmax{rtkit_get_max_realtime_priority(conn.get())}; + TRACE("Maximum real-time priority: %d, minimum niceness: %d\n", rtmax, nicemin); + + if(rtmax > 0) + { + if(AllowRTTimeLimit) + { + err = limit_rttime(conn.get()); + if(err != 0) + WARN("Failed to set RLIMIT_RTTIME for RTKit: %s (%d)\n", + std::strerror(err), err); + } + + /* Limit the maximum real-time priority to half. */ + rtmax = (rtmax+1)/2; + prio = clampi(prio, 1, rtmax); + + TRACE("Making real-time with priority %d (max: %d)\n", prio, rtmax); + err = rtkit_make_realtime(conn.get(), 0, prio); + if(err == 0) return true; + + err = std::abs(err); + WARN("Failed to set real-time priority: %s (%d)\n", std::strerror(err), err); + } + /* 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 + * audio processing thread only to get a priority boost. Currently only + * Linux is known to have per-thread niceness. + */ +#ifdef __linux__ + if(nicemin < 0) + { + TRACE("Making high priority with niceness %d\n", 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::strerror(err), err); + } +#endif /* __linux__ */ + +#else + + std::ignore = prio; + WARN("D-Bus not supported\n"); +#endif + return false; +} + +} // namespace + +void SetRTPriority() +{ + if(RTPrioLevel <= 0) + return; + + if(SetRTPriorityPthread(RTPrioLevel)) + return; + if(SetRTPriorityRTKit(RTPrioLevel)) + return; +} + +#endif diff --git a/modules/openal-soft/core/helpers.h b/modules/openal-soft/core/helpers.h new file mode 100644 index 0000000..f0bfcf1 --- /dev/null +++ b/modules/openal-soft/core/helpers.h @@ -0,0 +1,18 @@ +#ifndef CORE_HELPERS_H +#define CORE_HELPERS_H + +#include + +#include "vector.h" + + +struct PathNamePair { std::string path, fname; }; +const PathNamePair &GetProcBinary(void); + +extern int RTPrioLevel; +extern bool AllowRTTimeLimit; +void SetRTPriority(void); + +al::vector SearchDataFiles(const char *match, const char *subdir); + +#endif /* CORE_HELPERS_H */ diff --git a/modules/openal-soft/core/hrtf.cpp b/modules/openal-soft/core/hrtf.cpp new file mode 100644 index 0000000..d4d6981 --- /dev/null +++ b/modules/openal-soft/core/hrtf.cpp @@ -0,0 +1,1462 @@ + +#include "config.h" + +#include "hrtf.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "albit.h" +#include "albyte.h" +#include "alfstream.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "aloptional.h" +#include "alspan.h" +#include "ambidefs.h" +#include "filters/splitter.h" +#include "helpers.h" +#include "logging.h" +#include "mixer/hrtfdefs.h" +#include "opthelpers.h" +#include "polyphase_resampler.h" +#include "vector.h" + + +namespace { + +struct HrtfEntry { + std::string mDispName; + std::string mFilename; +}; + +struct LoadedHrtf { + std::string mFilename; + std::unique_ptr mEntry; +}; + +/* Data set limits must be the same as or more flexible than those defined in + * the makemhr utility. + */ +constexpr uint MinFdCount{1}; +constexpr uint MaxFdCount{16}; + +constexpr uint MinFdDistance{50}; +constexpr uint MaxFdDistance{2500}; + +constexpr uint MinEvCount{5}; +constexpr uint MaxEvCount{181}; + +constexpr uint MinAzCount{1}; +constexpr uint MaxAzCount{255}; + +constexpr uint MaxHrirDelay{HrtfHistoryLength - 1}; + +constexpr uint HrirDelayFracBits{2}; +constexpr uint HrirDelayFracOne{1 << HrirDelayFracBits}; +constexpr uint HrirDelayFracHalf{HrirDelayFracOne >> 1}; + +static_assert(MaxHrirDelay*HrirDelayFracOne < 256, "MAX_HRIR_DELAY or DELAY_FRAC too large"); + +constexpr char magicMarker00[8]{'M','i','n','P','H','R','0','0'}; +constexpr char magicMarker01[8]{'M','i','n','P','H','R','0','1'}; +constexpr char magicMarker02[8]{'M','i','n','P','H','R','0','2'}; +constexpr char magicMarker03[8]{'M','i','n','P','H','R','0','3'}; + +/* First value for pass-through coefficients (remaining are 0), used for omni- + * directional sounds. */ +constexpr auto PassthruCoeff = static_cast(1.0/al::numbers::sqrt2); + +std::mutex LoadedHrtfLock; +al::vector LoadedHrtfs; + +std::mutex EnumeratedHrtfLock; +al::vector EnumeratedHrtfs; + + +class databuf final : public std::streambuf { + int_type underflow() override + { return traits_type::eof(); } + + pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override + { + if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) + return traits_type::eof(); + + char_type *cur; + switch(whence) + { + case std::ios_base::beg: + if(offset < 0 || offset > egptr()-eback()) + return traits_type::eof(); + cur = eback() + offset; + break; + + case std::ios_base::cur: + if((offset >= 0 && offset > egptr()-gptr()) || + (offset < 0 && -offset > gptr()-eback())) + return traits_type::eof(); + cur = gptr() + offset; + break; + + case std::ios_base::end: + if(offset > 0 || -offset > egptr()-eback()) + return traits_type::eof(); + cur = egptr() + offset; + break; + + default: + return traits_type::eof(); + } + + setg(eback(), cur, egptr()); + return cur - eback(); + } + + pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override + { + // Simplified version of seekoff + if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) + return traits_type::eof(); + + if(pos < 0 || pos > egptr()-eback()) + return traits_type::eof(); + + setg(eback(), eback() + static_cast(pos), egptr()); + return pos; + } + +public: + databuf(const char_type *start_, const char_type *end_) noexcept + { + setg(const_cast(start_), const_cast(start_), + const_cast(end_)); + } +}; + +class idstream final : public std::istream { + databuf mStreamBuf; + +public: + idstream(const char *start_, const char *end_) + : std::istream{nullptr}, mStreamBuf{start_, end_} + { init(&mStreamBuf); } +}; + + +struct IdxBlend { uint idx; float blend; }; +/* Calculate the elevation index given the polar elevation in radians. This + * will return an index between 0 and (evcount - 1). + */ +IdxBlend CalcEvIndex(uint evcount, float ev) +{ + ev = (al::numbers::pi_v*0.5f + ev) * static_cast(evcount-1) / + al::numbers::pi_v; + uint idx{float2uint(ev)}; + + return IdxBlend{minu(idx, evcount-1), ev-static_cast(idx)}; +} + +/* Calculate the azimuth index given the polar azimuth in radians. This will + * return an index between 0 and (azcount - 1). + */ +IdxBlend CalcAzIndex(uint azcount, float az) +{ + az = (al::numbers::pi_v*2.0f + az) * static_cast(azcount) / + (al::numbers::pi_v*2.0f); + uint idx{float2uint(az)}; + + return IdxBlend{idx%azcount, az-static_cast(idx)}; +} + +} // namespace + + +/* Calculates static HRIR coefficients and delays for the given polar elevation + * and azimuth in radians. The coefficients are normalized. + */ +void GetHrtfCoeffs(const HrtfStore *Hrtf, float elevation, float azimuth, float distance, + float spread, HrirArray &coeffs, const al::span delays) +{ + const float dirfact{1.0f - (al::numbers::inv_pi_v/2.0f * spread)}; + + const auto *field = Hrtf->field; + const auto *field_end = field + Hrtf->fdCount-1; + size_t ebase{0}; + while(distance < field->distance && field != field_end) + { + ebase += field->evCount; + ++field; + } + + /* Calculate the elevation indices. */ + const auto elev0 = CalcEvIndex(field->evCount, elevation); + const size_t elev1_idx{minu(elev0.idx+1, field->evCount-1)}; + const size_t ir0offset{Hrtf->elev[ebase + elev0.idx].irOffset}; + const size_t ir1offset{Hrtf->elev[ebase + elev1_idx].irOffset}; + + /* Calculate azimuth indices. */ + const auto az0 = CalcAzIndex(Hrtf->elev[ebase + elev0.idx].azCount, azimuth); + const auto az1 = CalcAzIndex(Hrtf->elev[ebase + elev1_idx].azCount, azimuth); + + /* Calculate the HRIR indices to blend. */ + const size_t idx[4]{ + ir0offset + az0.idx, + ir0offset + ((az0.idx+1) % Hrtf->elev[ebase + elev0.idx].azCount), + ir1offset + az1.idx, + ir1offset + ((az1.idx+1) % Hrtf->elev[ebase + elev1_idx].azCount) + }; + + /* Calculate bilinear blending weights, attenuated according to the + * directional panning factor. + */ + const float blend[4]{ + (1.0f-elev0.blend) * (1.0f-az0.blend) * dirfact, + (1.0f-elev0.blend) * ( az0.blend) * dirfact, + ( elev0.blend) * (1.0f-az1.blend) * dirfact, + ( elev0.blend) * ( az1.blend) * dirfact + }; + + /* Calculate the blended HRIR delays. */ + float d{Hrtf->delays[idx[0]][0]*blend[0] + Hrtf->delays[idx[1]][0]*blend[1] + + Hrtf->delays[idx[2]][0]*blend[2] + Hrtf->delays[idx[3]][0]*blend[3]}; + delays[0] = fastf2u(d * float{1.0f/HrirDelayFracOne}); + d = Hrtf->delays[idx[0]][1]*blend[0] + Hrtf->delays[idx[1]][1]*blend[1] + + Hrtf->delays[idx[2]][1]*blend[2] + Hrtf->delays[idx[3]][1]*blend[3]; + delays[1] = fastf2u(d * float{1.0f/HrirDelayFracOne}); + + /* Calculate the blended HRIR coefficients. */ + float *coeffout{al::assume_aligned<16>(&coeffs[0][0])}; + coeffout[0] = PassthruCoeff * (1.0f-dirfact); + coeffout[1] = PassthruCoeff * (1.0f-dirfact); + std::fill_n(coeffout+2, size_t{HrirLength-1}*2, 0.0f); + for(size_t c{0};c < 4;c++) + { + const float *srccoeffs{al::assume_aligned<16>(Hrtf->coeffs[idx[c]][0].data())}; + const float mult{blend[c]}; + auto blend_coeffs = [mult](const float src, const float coeff) noexcept -> float + { return src*mult + coeff; }; + std::transform(srccoeffs, srccoeffs + HrirLength*2, coeffout, coeffout, blend_coeffs); + } +} + + +std::unique_ptr DirectHrtfState::Create(size_t num_chans) +{ return std::unique_ptr{new(FamCount(num_chans)) DirectHrtfState{num_chans}}; } + +void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, + const al::span AmbiPoints, const float (*AmbiMatrix)[MaxAmbiChannels], + const float XOverFreq, const al::span AmbiOrderHFGain) +{ + using double2 = std::array; + struct ImpulseResponse { + const ConstHrirSpan hrir; + uint ldelay, rdelay; + }; + + const double xover_norm{double{XOverFreq} / Hrtf->sampleRate}; + for(size_t i{0};i < mChannels.size();++i) + { + const size_t order{AmbiIndex::OrderFromChannel()[i]}; + mChannels[i].mSplitter.init(static_cast(xover_norm)); + mChannels[i].mHfScale = AmbiOrderHFGain[order]; + } + + uint min_delay{HrtfHistoryLength*HrirDelayFracOne}, max_delay{0}; + al::vector impres; impres.reserve(AmbiPoints.size()); + auto calc_res = [Hrtf,&max_delay,&min_delay](const AngularPoint &pt) -> ImpulseResponse + { + auto &field = Hrtf->field[0]; + const auto elev0 = CalcEvIndex(field.evCount, pt.Elev.value); + const size_t elev1_idx{minu(elev0.idx+1, field.evCount-1)}; + const size_t ir0offset{Hrtf->elev[elev0.idx].irOffset}; + const size_t ir1offset{Hrtf->elev[elev1_idx].irOffset}; + + const auto az0 = CalcAzIndex(Hrtf->elev[elev0.idx].azCount, pt.Azim.value); + const auto az1 = CalcAzIndex(Hrtf->elev[elev1_idx].azCount, pt.Azim.value); + + const size_t idx[4]{ + ir0offset + az0.idx, + ir0offset + ((az0.idx+1) % Hrtf->elev[elev0.idx].azCount), + ir1offset + az1.idx, + ir1offset + ((az1.idx+1) % Hrtf->elev[elev1_idx].azCount) + }; + + const std::array blend{{ + (1.0-elev0.blend) * (1.0-az0.blend), + (1.0-elev0.blend) * ( az0.blend), + ( elev0.blend) * (1.0-az1.blend), + ( elev0.blend) * ( az1.blend) + }}; + + /* The largest blend factor serves as the closest HRIR. */ + const size_t irOffset{idx[std::max_element(blend.begin(), blend.end()) - blend.begin()]}; + ImpulseResponse res{Hrtf->coeffs[irOffset], + Hrtf->delays[irOffset][0], Hrtf->delays[irOffset][1]}; + + min_delay = minu(min_delay, minu(res.ldelay, res.rdelay)); + max_delay = maxu(max_delay, maxu(res.ldelay, res.rdelay)); + + return res; + }; + std::transform(AmbiPoints.begin(), AmbiPoints.end(), std::back_inserter(impres), calc_res); + auto hrir_delay_round = [](const uint d) noexcept -> uint + { return (d+HrirDelayFracHalf) >> HrirDelayFracBits; }; + + TRACE("Min delay: %.2f, max delay: %.2f, FIR length: %u\n", + min_delay/double{HrirDelayFracOne}, max_delay/double{HrirDelayFracOne}, irSize); + + const bool per_hrir_min{mChannels.size() > AmbiChannelsFromOrder(1)}; + auto tmpres = al::vector>(mChannels.size()); + max_delay = 0; + for(size_t c{0u};c < AmbiPoints.size();++c) + { + const ConstHrirSpan hrir{impres[c].hrir}; + const uint base_delay{per_hrir_min ? minu(impres[c].ldelay, impres[c].rdelay) : min_delay}; + const uint ldelay{hrir_delay_round(impres[c].ldelay - base_delay)}; + const uint rdelay{hrir_delay_round(impres[c].rdelay - base_delay)}; + max_delay = maxu(max_delay, maxu(impres[c].ldelay, impres[c].rdelay) - base_delay); + + for(size_t i{0u};i < mChannels.size();++i) + { + const double mult{AmbiMatrix[c][i]}; + const size_t numirs{HrirLength - maxz(ldelay, rdelay)}; + size_t lidx{ldelay}, ridx{rdelay}; + for(size_t j{0};j < numirs;++j) + { + tmpres[i][lidx++][0] += hrir[j][0] * mult; + tmpres[i][ridx++][1] += hrir[j][1] * mult; + } + } + } + impres.clear(); + + for(size_t i{0u};i < mChannels.size();++i) + { + auto copy_arr = [](const double2 &in) noexcept -> float2 + { return float2{{static_cast(in[0]), static_cast(in[1])}}; }; + std::transform(tmpres[i].cbegin(), tmpres[i].cend(), mChannels[i].mCoeffs.begin(), + copy_arr); + } + tmpres.clear(); + + const uint max_length{minu(hrir_delay_round(max_delay) + irSize, HrirLength)}; + TRACE("New max delay: %.2f, FIR length: %u\n", max_delay/double{HrirDelayFracOne}, + max_length); + mIrSize = max_length; +} + + +namespace { + +std::unique_ptr CreateHrtfStore(uint rate, ushort irSize, + const al::span fields, + const al::span elevs, const HrirArray *coeffs, + const ubyte2 *delays, const char *filename) +{ + const size_t irCount{size_t{elevs.back().azCount} + elevs.back().irOffset}; + size_t total{sizeof(HrtfStore)}; + total = RoundUp(total, alignof(HrtfStore::Field)); /* Align for field infos */ + total += sizeof(std::declval().field[0])*fields.size(); + total = RoundUp(total, alignof(HrtfStore::Elevation)); /* Align for elevation infos */ + total += sizeof(std::declval().elev[0])*elevs.size(); + total = RoundUp(total, 16); /* Align for coefficients using SIMD */ + total += sizeof(std::declval().coeffs[0])*irCount; + total += sizeof(std::declval().delays[0])*irCount; + + void *ptr{al_calloc(16, total)}; + std::unique_ptr Hrtf{al::construct_at(static_cast(ptr))}; + if(!Hrtf) + ERR("Out of memory allocating storage for %s.\n", filename); + else + { + InitRef(Hrtf->mRef, 1u); + Hrtf->sampleRate = rate; + Hrtf->irSize = irSize; + Hrtf->fdCount = static_cast(fields.size()); + + /* Set up pointers to storage following the main HRTF struct. */ + char *base = reinterpret_cast(Hrtf.get()); + size_t offset{sizeof(HrtfStore)}; + + offset = RoundUp(offset, alignof(HrtfStore::Field)); /* Align for field infos */ + auto field_ = reinterpret_cast(base + offset); + offset += sizeof(field_[0])*fields.size(); + + offset = RoundUp(offset, alignof(HrtfStore::Elevation)); /* Align for elevation infos */ + auto elev_ = reinterpret_cast(base + offset); + offset += sizeof(elev_[0])*elevs.size(); + + offset = RoundUp(offset, 16); /* Align for coefficients using SIMD */ + auto coeffs_ = reinterpret_cast(base + offset); + offset += sizeof(coeffs_[0])*irCount; + + auto delays_ = reinterpret_cast(base + offset); + offset += sizeof(delays_[0])*irCount; + + if(unlikely(offset != total)) + throw std::runtime_error{"HrtfStore allocation size mismatch"}; + + /* Copy input data to storage. */ + std::uninitialized_copy(fields.cbegin(), fields.cend(), field_); + std::uninitialized_copy(elevs.cbegin(), elevs.cend(), elev_); + std::uninitialized_copy_n(coeffs, irCount, coeffs_); + std::uninitialized_copy_n(delays, irCount, delays_); + + /* Finally, assign the storage pointers. */ + Hrtf->field = field_; + Hrtf->elev = elev_; + Hrtf->coeffs = coeffs_; + Hrtf->delays = delays_; + } + + return Hrtf; +} + +void MirrorLeftHrirs(const al::span elevs, HrirArray *coeffs, + ubyte2 *delays) +{ + for(const auto &elev : elevs) + { + const ushort evoffset{elev.irOffset}; + const ushort azcount{elev.azCount}; + for(size_t j{0};j < azcount;j++) + { + const size_t lidx{evoffset + j}; + const size_t ridx{evoffset + ((azcount-j) % azcount)}; + + const size_t irSize{coeffs[ridx].size()}; + for(size_t k{0};k < irSize;k++) + coeffs[ridx][k][1] = coeffs[lidx][k][0]; + delays[ridx][1] = delays[lidx][0]; + } + } +} + + +template +constexpr std::enable_if_t::value && num_bits < sizeof(T)*8, +T> fixsign(T value) noexcept +{ + constexpr auto signbit = static_cast(1u << (num_bits-1)); + return static_cast((value^signbit) - signbit); +} + +template +constexpr std::enable_if_t::value || num_bits == sizeof(T)*8, +T> fixsign(T value) noexcept +{ return value; } + +template +inline std::enable_if_t readle(std::istream &data) +{ + static_assert((num_bits&7) == 0, "num_bits must be a multiple of 8"); + static_assert(num_bits <= sizeof(T)*8, "num_bits is too large for the type"); + + T ret{}; + if(!data.read(reinterpret_cast(&ret), num_bits/8)) + return static_cast(EOF); + + return fixsign(ret); +} + +template +inline std::enable_if_t readle(std::istream &data) +{ + static_assert((num_bits&7) == 0, "num_bits must be a multiple of 8"); + static_assert(num_bits <= sizeof(T)*8, "num_bits is too large for the type"); + + T ret{}; + al::byte b[sizeof(T)]{}; + if(!data.read(reinterpret_cast(b), num_bits/8)) + return static_cast(EOF); + std::reverse_copy(std::begin(b), std::end(b), reinterpret_cast(&ret)); + + return fixsign(ret); +} + +template<> +inline uint8_t readle(std::istream &data) +{ return static_cast(data.get()); } + + +std::unique_ptr LoadHrtf00(std::istream &data, const char *filename) +{ + uint rate{readle(data)}; + ushort irCount{readle(data)}; + ushort irSize{readle(data)}; + ubyte evCount{readle(data)}; + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + + if(irSize < MinIrLength || irSize > HrirLength) + { + ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength); + return nullptr; + } + if(evCount < MinEvCount || evCount > MaxEvCount) + { + ERR("Unsupported elevation count: evCount=%d (%d to %d)\n", + evCount, MinEvCount, MaxEvCount); + return nullptr; + } + + auto elevs = al::vector(evCount); + for(auto &elev : elevs) + elev.irOffset = readle(data); + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + for(size_t i{1};i < evCount;i++) + { + if(elevs[i].irOffset <= elevs[i-1].irOffset) + { + ERR("Invalid evOffset: evOffset[%zu]=%d (last=%d)\n", 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); + return nullptr; + } + + for(size_t i{1};i < evCount;i++) + { + 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); + 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); + return nullptr; + } + + auto coeffs = al::vector(irCount, HrirArray{}); + auto delays = al::vector(irCount); + for(auto &hrir : coeffs) + { + for(auto &val : al::span{hrir.data(), irSize}) + val[0] = readle(data) / 32768.0f; + } + for(auto &val : delays) + val[0] = readle(data); + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + for(size_t i{0};i < irCount;i++) + { + if(delays[i][0] > MaxHrirDelay) + { + ERR("Invalid delays[%zd]: %d (%d)\n", i, delays[i][0], MaxHrirDelay); + return nullptr; + } + delays[i][0] <<= HrirDelayFracBits; + } + + /* Mirror the left ear responses to the right ear. */ + MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data()); + + const HrtfStore::Field field[1]{{0.0f, evCount}}; + return CreateHrtfStore(rate, irSize, field, {elevs.data(), elevs.size()}, coeffs.data(), + delays.data(), filename); +} + +std::unique_ptr LoadHrtf01(std::istream &data, const char *filename) +{ + uint rate{readle(data)}; + ushort irSize{readle(data)}; + ubyte evCount{readle(data)}; + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + + if(irSize < MinIrLength || irSize > HrirLength) + { + ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength); + return nullptr; + } + if(evCount < MinEvCount || evCount > MaxEvCount) + { + ERR("Unsupported elevation count: evCount=%d (%d to %d)\n", + evCount, MinEvCount, MaxEvCount); + return nullptr; + } + + auto elevs = al::vector(evCount); + for(auto &elev : elevs) + elev.azCount = readle(data); + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + for(size_t i{0};i < evCount;++i) + { + if(elevs[i].azCount < MinAzCount || elevs[i].azCount > MaxAzCount) + { + ERR("Unsupported azimuth count: azCount[%zd]=%d (%d to %d)\n", i, elevs[i].azCount, + MinAzCount, MaxAzCount); + return nullptr; + } + } + + elevs[0].irOffset = 0; + for(size_t i{1};i < evCount;i++) + elevs[i].irOffset = static_cast(elevs[i-1].irOffset + elevs[i-1].azCount); + const ushort irCount{static_cast(elevs.back().irOffset + elevs.back().azCount)}; + + auto coeffs = al::vector(irCount, HrirArray{}); + auto delays = al::vector(irCount); + for(auto &hrir : coeffs) + { + for(auto &val : al::span{hrir.data(), irSize}) + val[0] = readle(data) / 32768.0f; + } + for(auto &val : delays) + val[0] = readle(data); + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + for(size_t i{0};i < irCount;i++) + { + if(delays[i][0] > MaxHrirDelay) + { + ERR("Invalid delays[%zd]: %d (%d)\n", i, delays[i][0], MaxHrirDelay); + return nullptr; + } + delays[i][0] <<= HrirDelayFracBits; + } + + /* Mirror the left ear responses to the right ear. */ + MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data()); + + const HrtfStore::Field field[1]{{0.0f, evCount}}; + return CreateHrtfStore(rate, irSize, field, {elevs.data(), elevs.size()}, coeffs.data(), + delays.data(), filename); +} + +std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) +{ + constexpr ubyte SampleType_S16{0}; + constexpr ubyte SampleType_S24{1}; + constexpr ubyte ChanType_LeftOnly{0}; + constexpr ubyte ChanType_LeftRight{1}; + + uint rate{readle(data)}; + ubyte sampleType{readle(data)}; + ubyte channelType{readle(data)}; + ushort irSize{readle(data)}; + ubyte fdCount{readle(data)}; + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + + if(sampleType > SampleType_S24) + { + ERR("Unsupported sample type: %d\n", sampleType); + return nullptr; + } + if(channelType > ChanType_LeftRight) + { + ERR("Unsupported channel type: %d\n", channelType); + return nullptr; + } + + if(irSize < MinIrLength || irSize > HrirLength) + { + ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength); + return nullptr; + } + if(fdCount < 1 || fdCount > MaxFdCount) + { + ERR("Unsupported number of field-depths: fdCount=%d (%d to %d)\n", fdCount, MinFdCount, + MaxFdCount); + return nullptr; + } + + auto fields = al::vector(fdCount); + auto elevs = al::vector{}; + for(size_t f{0};f < fdCount;f++) + { + const ushort distance{readle(data)}; + const ubyte evCount{readle(data)}; + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + + if(distance < MinFdDistance || distance > MaxFdDistance) + { + ERR("Unsupported field distance[%zu]=%d (%d to %d millimeters)\n", f, distance, + MinFdDistance, MaxFdDistance); + return nullptr; + } + if(evCount < MinEvCount || evCount > MaxEvCount) + { + ERR("Unsupported elevation count: evCount[%zu]=%d (%d to %d)\n", f, evCount, + MinEvCount, MaxEvCount); + return nullptr; + } + + fields[f].distance = distance / 1000.0f; + 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, + fields[f-1].distance); + return nullptr; + } + + const size_t ebase{elevs.size()}; + elevs.resize(ebase + evCount); + for(auto &elev : al::span(elevs.data()+ebase, evCount)) + elev.azCount = readle(data); + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + + for(size_t e{0};e < evCount;e++) + { + 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, + elevs[ebase+e].azCount, MinAzCount, MaxAzCount); + return nullptr; + } + } + } + + elevs[0].irOffset = 0; + std::partial_sum(elevs.cbegin(), elevs.cend(), elevs.begin(), + [](const HrtfStore::Elevation &last, const HrtfStore::Elevation &cur) + -> HrtfStore::Elevation + { + return HrtfStore::Elevation{cur.azCount, + static_cast(last.azCount + last.irOffset)}; + }); + const auto irTotal = static_cast(elevs.back().azCount + elevs.back().irOffset); + + auto coeffs = al::vector(irTotal, HrirArray{}); + auto delays = al::vector(irTotal); + if(channelType == ChanType_LeftOnly) + { + if(sampleType == SampleType_S16) + { + for(auto &hrir : coeffs) + { + for(auto &val : al::span{hrir.data(), irSize}) + val[0] = readle(data) / 32768.0f; + } + } + else if(sampleType == SampleType_S24) + { + for(auto &hrir : coeffs) + { + for(auto &val : al::span{hrir.data(), irSize}) + val[0] = static_cast(readle(data)) / 8388608.0f; + } + } + for(auto &val : delays) + val[0] = readle(data); + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + for(size_t i{0};i < irTotal;++i) + { + if(delays[i][0] > MaxHrirDelay) + { + ERR("Invalid delays[%zu][0]: %d (%d)\n", i, delays[i][0], MaxHrirDelay); + return nullptr; + } + delays[i][0] <<= HrirDelayFracBits; + } + + /* Mirror the left ear responses to the right ear. */ + MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data()); + } + else if(channelType == ChanType_LeftRight) + { + if(sampleType == SampleType_S16) + { + for(auto &hrir : coeffs) + { + for(auto &val : al::span{hrir.data(), irSize}) + { + val[0] = readle(data) / 32768.0f; + val[1] = readle(data) / 32768.0f; + } + } + } + else if(sampleType == SampleType_S24) + { + for(auto &hrir : coeffs) + { + for(auto &val : al::span{hrir.data(), irSize}) + { + val[0] = static_cast(readle(data)) / 8388608.0f; + val[1] = static_cast(readle(data)) / 8388608.0f; + } + } + } + for(auto &val : delays) + { + val[0] = readle(data); + val[1] = readle(data); + } + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + + for(size_t i{0};i < irTotal;++i) + { + if(delays[i][0] > MaxHrirDelay) + { + ERR("Invalid delays[%zu][0]: %d (%d)\n", 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); + return nullptr; + } + delays[i][0] <<= HrirDelayFracBits; + delays[i][1] <<= HrirDelayFracBits; + } + } + + if(fdCount > 1) + { + auto fields_ = al::vector(fields.size()); + auto elevs_ = al::vector(elevs.size()); + auto coeffs_ = al::vector(coeffs.size()); + auto delays_ = al::vector(delays.size()); + + /* Simple reverse for the per-field elements. */ + std::reverse_copy(fields.cbegin(), fields.cend(), fields_.begin()); + + /* Each field has a group of elevations, which each have an azimuth + * count. Reverse the order of the groups, keeping the relative order + * of per-group azimuth counts. + */ + auto elevs__end = elevs_.end(); + auto copy_azs = [&elevs,&elevs__end](const ptrdiff_t ebase, const HrtfStore::Field &field) + -> ptrdiff_t + { + auto elevs_src = elevs.begin()+ebase; + elevs__end = std::copy_backward(elevs_src, elevs_src+field.evCount, elevs__end); + return ebase + field.evCount; + }; + (void)std::accumulate(fields.cbegin(), fields.cend(), ptrdiff_t{0}, copy_azs); + assert(elevs_.begin() == elevs__end); + + /* Reestablish the IR offset for each elevation index, given the new + * ordering of elevations. + */ + elevs_[0].irOffset = 0; + std::partial_sum(elevs_.cbegin(), elevs_.cend(), elevs_.begin(), + [](const HrtfStore::Elevation &last, const HrtfStore::Elevation &cur) + -> HrtfStore::Elevation + { + return HrtfStore::Elevation{cur.azCount, + static_cast(last.azCount + last.irOffset)}; + }); + + /* Reverse the order of each field's group of IRs. */ + auto coeffs_end = coeffs_.end(); + auto delays_end = delays_.end(); + auto copy_irs = [&elevs,&coeffs,&delays,&coeffs_end,&delays_end]( + const ptrdiff_t ebase, const HrtfStore::Field &field) -> ptrdiff_t + { + auto accum_az = [](int count, const HrtfStore::Elevation &elev) noexcept -> int + { return count + elev.azCount; }; + const auto elevs_mid = elevs.cbegin() + ebase; + const auto elevs_end = elevs_mid + field.evCount; + const int abase{std::accumulate(elevs.cbegin(), elevs_mid, 0, accum_az)}; + const int num_azs{std::accumulate(elevs_mid, elevs_end, 0, accum_az)}; + + coeffs_end = std::copy_backward(coeffs.cbegin() + abase, + coeffs.cbegin() + (abase+num_azs), coeffs_end); + delays_end = std::copy_backward(delays.cbegin() + abase, + delays.cbegin() + (abase+num_azs), delays_end); + + return ebase + field.evCount; + }; + (void)std::accumulate(fields.cbegin(), fields.cend(), ptrdiff_t{0}, copy_irs); + assert(coeffs_.begin() == coeffs_end); + assert(delays_.begin() == delays_end); + + fields = std::move(fields_); + elevs = std::move(elevs_); + coeffs = std::move(coeffs_); + delays = std::move(delays_); + } + + return CreateHrtfStore(rate, irSize, {fields.data(), fields.size()}, + {elevs.data(), elevs.size()}, coeffs.data(), delays.data(), filename); +} + +std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) +{ + constexpr ubyte ChanType_LeftOnly{0}; + constexpr ubyte ChanType_LeftRight{1}; + + uint rate{readle(data)}; + ubyte channelType{readle(data)}; + ushort irSize{readle(data)}; + ubyte fdCount{readle(data)}; + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + + if(channelType > ChanType_LeftRight) + { + ERR("Unsupported channel type: %d\n", channelType); + return nullptr; + } + + if(irSize < MinIrLength || irSize > HrirLength) + { + ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength); + return nullptr; + } + if(fdCount < 1 || fdCount > MaxFdCount) + { + ERR("Unsupported number of field-depths: fdCount=%d (%d to %d)\n", fdCount, MinFdCount, + MaxFdCount); + return nullptr; + } + + auto fields = al::vector(fdCount); + auto elevs = al::vector{}; + for(size_t f{0};f < fdCount;f++) + { + const ushort distance{readle(data)}; + const ubyte evCount{readle(data)}; + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + + if(distance < MinFdDistance || distance > MaxFdDistance) + { + ERR("Unsupported field distance[%zu]=%d (%d to %d millimeters)\n", f, distance, + MinFdDistance, MaxFdDistance); + return nullptr; + } + if(evCount < MinEvCount || evCount > MaxEvCount) + { + ERR("Unsupported elevation count: evCount[%zu]=%d (%d to %d)\n", f, evCount, + MinEvCount, MaxEvCount); + return nullptr; + } + + fields[f].distance = distance / 1000.0f; + 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); + return nullptr; + } + + const size_t ebase{elevs.size()}; + elevs.resize(ebase + evCount); + for(auto &elev : al::span(elevs.data()+ebase, evCount)) + elev.azCount = readle(data); + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + + for(size_t e{0};e < evCount;e++) + { + 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, + elevs[ebase+e].azCount, MinAzCount, MaxAzCount); + return nullptr; + } + } + } + + elevs[0].irOffset = 0; + std::partial_sum(elevs.cbegin(), elevs.cend(), elevs.begin(), + [](const HrtfStore::Elevation &last, const HrtfStore::Elevation &cur) + -> HrtfStore::Elevation + { + return HrtfStore::Elevation{cur.azCount, + static_cast(last.azCount + last.irOffset)}; + }); + const auto irTotal = static_cast(elevs.back().azCount + elevs.back().irOffset); + + auto coeffs = al::vector(irTotal, HrirArray{}); + auto delays = al::vector(irTotal); + if(channelType == ChanType_LeftOnly) + { + for(auto &hrir : coeffs) + { + for(auto &val : al::span{hrir.data(), irSize}) + val[0] = static_cast(readle(data)) / 8388608.0f; + } + for(auto &val : delays) + val[0] = readle(data); + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + for(size_t i{0};i < irTotal;++i) + { + if(delays[i][0] > MaxHrirDelay<{hrir.data(), irSize}) + { + val[0] = static_cast(readle(data)) / 8388608.0f; + val[1] = static_cast(readle(data)) / 8388608.0f; + } + } + for(auto &val : delays) + { + val[0] = readle(data); + val[1] = readle(data); + } + if(!data || data.eof()) + { + ERR("Failed reading %s\n", filename); + return nullptr; + } + + for(size_t i{0};i < irTotal;++i) + { + if(delays[i][0] > MaxHrirDelay< MaxHrirDelay< bool { return name == entry.mDispName; }; + auto &enum_names = EnumeratedHrtfs; + return std::find_if(enum_names.cbegin(), enum_names.cend(), match_name) != enum_names.cend(); +} + +void AddFileEntry(const std::string &filename) +{ + /* Check if this file has already been enumerated. */ + auto enum_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(), + [&filename](const HrtfEntry &entry) -> bool + { return entry.mFilename == filename; }); + if(enum_iter != EnumeratedHrtfs.cend()) + { + TRACE("Skipping duplicate file entry %s\n", filename.c_str()); + return; + } + + /* TODO: Get a human-readable name from the HRTF data (possibly coming in a + * format update). */ + size_t namepos{filename.find_last_of('/')+1}; + if(!namepos) namepos = filename.find_last_of('\\')+1; + + size_t extpos{filename.find_last_of('.')}; + if(extpos <= namepos) extpos = std::string::npos; + + const std::string basename{(extpos == std::string::npos) ? + filename.substr(namepos) : filename.substr(namepos, extpos-namepos)}; + std::string newname{basename}; + int count{1}; + while(checkName(newname)) + { + newname = basename; + newname += " #"; + newname += std::to_string(++count); + } + EnumeratedHrtfs.emplace_back(HrtfEntry{newname, filename}); + const HrtfEntry &entry = EnumeratedHrtfs.back(); + + TRACE("Adding file entry \"%s\"\n", entry.mFilename.c_str()); +} + +/* Unfortunate that we have to duplicate AddFileEntry to take a memory buffer + * for input instead of opening the given filename. + */ +void AddBuiltInEntry(const std::string &dispname, uint residx) +{ + const std::string filename{'!'+std::to_string(residx)+'_'+dispname}; + + auto enum_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(), + [&filename](const HrtfEntry &entry) -> bool + { return entry.mFilename == filename; }); + if(enum_iter != EnumeratedHrtfs.cend()) + { + TRACE("Skipping duplicate file entry %s\n", filename.c_str()); + return; + } + + /* TODO: Get a human-readable name from the HRTF data (possibly coming in a + * format update). */ + + std::string newname{dispname}; + int count{1}; + while(checkName(newname)) + { + newname = dispname; + newname += " #"; + newname += std::to_string(++count); + } + EnumeratedHrtfs.emplace_back(HrtfEntry{newname, filename}); + const HrtfEntry &entry = EnumeratedHrtfs.back(); + + TRACE("Adding built-in entry \"%s\"\n", entry.mFilename.c_str()); +} + + +#define IDR_DEFAULT_HRTF_MHR 1 + +#ifndef ALSOFT_EMBED_HRTF_DATA + +al::span GetResource(int /*name*/) +{ return {}; } + +#else + +#include "hrtf_default.h" + +al::span GetResource(int name) +{ + if(name == IDR_DEFAULT_HRTF_MHR) + return {reinterpret_cast(hrtf_default), sizeof(hrtf_default)}; + return {}; +} +#endif + +} // namespace + + +al::vector EnumerateHrtf(al::optional pathopt) +{ + std::lock_guard _{EnumeratedHrtfLock}; + EnumeratedHrtfs.clear(); + + bool usedefaults{true}; + if(pathopt) + { + const char *pathlist{pathopt->c_str()}; + while(pathlist && *pathlist) + { + const char *next, *end; + + while(isspace(*pathlist) || *pathlist == ',') + pathlist++; + if(*pathlist == '\0') + continue; + + next = strchr(pathlist, ','); + if(next) + end = next++; + else + { + end = pathlist + strlen(pathlist); + usedefaults = false; + } + + while(end != pathlist && isspace(*(end-1))) + --end; + if(end != pathlist) + { + const std::string pname{pathlist, end}; + for(const auto &fname : SearchDataFiles(".mhr", pname.c_str())) + AddFileEntry(fname); + } + + pathlist = next; + } + } + + if(usedefaults) + { + for(const auto &fname : SearchDataFiles(".mhr", "openal/hrtf")) + AddFileEntry(fname); + + if(!GetResource(IDR_DEFAULT_HRTF_MHR).empty()) + AddBuiltInEntry("Built-In HRTF", IDR_DEFAULT_HRTF_MHR); + } + + al::vector list; + list.reserve(EnumeratedHrtfs.size()); + for(auto &entry : EnumeratedHrtfs) + list.emplace_back(entry.mDispName); + + return list; +} + +HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) +{ + std::lock_guard _{EnumeratedHrtfLock}; + auto entry_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(), + [&name](const HrtfEntry &entry) -> bool { return entry.mDispName == name; }); + if(entry_iter == EnumeratedHrtfs.cend()) + return nullptr; + const std::string &fname = entry_iter->mFilename; + + std::lock_guard __{LoadedHrtfLock}; + auto hrtf_lt_fname = [](LoadedHrtf &hrtf, const std::string &filename) -> bool + { return hrtf.mFilename < filename; }; + auto handle = std::lower_bound(LoadedHrtfs.begin(), LoadedHrtfs.end(), fname, hrtf_lt_fname); + while(handle != LoadedHrtfs.end() && handle->mFilename == fname) + { + HrtfStore *hrtf{handle->mEntry.get()}; + if(hrtf && hrtf->sampleRate == devrate) + { + hrtf->add_ref(); + return HrtfStorePtr{hrtf}; + } + ++handle; + } + + std::unique_ptr stream; + int residx{}; + char ch{}; + if(sscanf(fname.c_str(), "!%d%c", &residx, &ch) == 2 && ch == '_') + { + TRACE("Loading %s...\n", fname.c_str()); + al::span res{GetResource(residx)}; + if(res.empty()) + { + ERR("Could not get resource %u, %s\n", residx, name.c_str()); + return nullptr; + } + stream = std::make_unique(res.begin(), res.end()); + } + else + { + TRACE("Loading %s...\n", fname.c_str()); + auto fstr = std::make_unique(fname.c_str(), std::ios::binary); + if(!fstr->is_open()) + { + ERR("Could not open %s\n", fname.c_str()); + return nullptr; + } + stream = std::move(fstr); + } + + std::unique_ptr hrtf; + char magic[sizeof(magicMarker03)]; + stream->read(magic, sizeof(magic)); + if(stream->gcount() < static_cast(sizeof(magicMarker03))) + ERR("%s data is too short (%zu bytes)\n", name.c_str(), stream->gcount()); + else if(memcmp(magic, magicMarker03, sizeof(magicMarker03)) == 0) + { + TRACE("Detected data set format v3\n"); + hrtf = LoadHrtf03(*stream, name.c_str()); + } + else if(memcmp(magic, magicMarker02, sizeof(magicMarker02)) == 0) + { + TRACE("Detected data set format v2\n"); + hrtf = LoadHrtf02(*stream, name.c_str()); + } + else if(memcmp(magic, magicMarker01, sizeof(magicMarker01)) == 0) + { + TRACE("Detected data set format v1\n"); + hrtf = LoadHrtf01(*stream, name.c_str()); + } + else if(memcmp(magic, magicMarker00, sizeof(magicMarker00)) == 0) + { + TRACE("Detected data set format v0\n"); + hrtf = LoadHrtf00(*stream, name.c_str()); + } + else + ERR("Invalid header in %s: \"%.8s\"\n", name.c_str(), magic); + stream.reset(); + + if(!hrtf) + { + ERR("Failed to load %s\n", name.c_str()); + return nullptr; + } + + if(hrtf->sampleRate != devrate) + { + TRACE("Resampling HRTF %s (%uhz -> %uhz)\n", name.c_str(), hrtf->sampleRate, devrate); + + /* Calculate the last elevation's index and get the total IR count. */ + const size_t lastEv{std::accumulate(hrtf->field, hrtf->field+hrtf->fdCount, size_t{0}, + [](const size_t curval, const HrtfStore::Field &field) noexcept -> size_t + { return curval + field.evCount; } + ) - 1}; + const size_t irCount{size_t{hrtf->elev[lastEv].irOffset} + hrtf->elev[lastEv].azCount}; + + /* Resample all the IRs. */ + std::array,2> inout; + PPhaseResampler rs; + rs.init(hrtf->sampleRate, devrate); + for(size_t i{0};i < irCount;++i) + { + HrirArray &coeffs = const_cast(hrtf->coeffs[i]); + for(size_t j{0};j < 2;++j) + { + std::transform(coeffs.cbegin(), coeffs.cend(), inout[0].begin(), + [j](const float2 &in) noexcept -> double { return in[j]; }); + rs.process(HrirLength, inout[0].data(), HrirLength, inout[1].data()); + for(size_t k{0};k < HrirLength;++k) + coeffs[k][j] = static_cast(inout[1][k]); + } + } + rs = {}; + + /* Scale the delays for the new sample rate. */ + float max_delay{0.0f}; + auto new_delays = al::vector(irCount); + const float rate_scale{static_cast(devrate)/static_cast(hrtf->sampleRate)}; + for(size_t i{0};i < irCount;++i) + { + for(size_t j{0};j < 2;++j) + { + const float new_delay{std::round(hrtf->delays[i][j] * rate_scale) / + float{HrirDelayFracOne}}; + max_delay = maxf(max_delay, new_delay); + new_delays[i][j] = new_delay; + } + } + + /* If the new delays exceed the max, scale it down to fit (essentially + * shrinking the head radius; not ideal but better than a per-delay + * clamp). + */ + float delay_scale{HrirDelayFracOne}; + if(max_delay > MaxHrirDelay) + { + WARN("Resampled delay exceeds max (%.2f > %d)\n", max_delay, MaxHrirDelay); + delay_scale *= float{MaxHrirDelay} / max_delay; + } + + for(size_t i{0};i < irCount;++i) + { + ubyte2 &delays = const_cast(hrtf->delays[i]); + for(size_t j{0};j < 2;++j) + delays[j] = static_cast(float2int(new_delays[i][j]*delay_scale + 0.5f)); + } + + /* Scale the IR size for the new sample rate and update the stored + * sample rate. + */ + const float newIrSize{std::round(static_cast(hrtf->irSize) * rate_scale)}; + hrtf->irSize = static_cast(minf(HrirLength, newIrSize)); + hrtf->sampleRate = devrate; + } + + TRACE("Loaded HRTF %s for sample rate %uhz, %u-sample filter\n", name.c_str(), + hrtf->sampleRate, hrtf->irSize); + handle = LoadedHrtfs.emplace(handle, LoadedHrtf{fname, std::move(hrtf)}); + + return HrtfStorePtr{handle->mEntry.get()}; +} + + +void HrtfStore::add_ref() +{ + auto ref = IncrementRef(mRef); + TRACE("HrtfStore %p increasing refcount to %u\n", decltype(std::declval()){this}, ref); +} + +void HrtfStore::release() +{ + auto ref = DecrementRef(mRef); + TRACE("HrtfStore %p decreasing refcount to %u\n", decltype(std::declval()){this}, ref); + if(ref == 0) + { + std::lock_guard _{LoadedHrtfLock}; + + /* Go through and remove all unused HRTFs. */ + auto remove_unused = [](LoadedHrtf &hrtf) -> bool + { + HrtfStore *entry{hrtf.mEntry.get()}; + if(entry && ReadRef(entry->mRef) == 0) + { + TRACE("Unloading unused HRTF %s\n", hrtf.mFilename.data()); + hrtf.mEntry = nullptr; + return true; + } + return false; + }; + auto iter = std::remove_if(LoadedHrtfs.begin(), LoadedHrtfs.end(), remove_unused); + LoadedHrtfs.erase(iter, LoadedHrtfs.end()); + } +} diff --git a/modules/openal-soft/core/hrtf.h b/modules/openal-soft/core/hrtf.h new file mode 100644 index 0000000..9cf11ef --- /dev/null +++ b/modules/openal-soft/core/hrtf.h @@ -0,0 +1,90 @@ +#ifndef CORE_HRTF_H +#define CORE_HRTF_H + +#include +#include +#include +#include + +#include "almalloc.h" +#include "aloptional.h" +#include "alspan.h" +#include "atomic.h" +#include "ambidefs.h" +#include "bufferline.h" +#include "mixer/hrtfdefs.h" +#include "intrusive_ptr.h" +#include "vector.h" + + +struct HrtfStore { + RefCount mRef; + + uint sampleRate; + uint irSize; + + struct Field { + float distance; + ubyte evCount; + }; + /* NOTE: Fields are stored *backwards*. field[0] is the farthest field, and + * field[fdCount-1] is the nearest. + */ + uint fdCount; + const Field *field; + + struct Elevation { + ushort azCount; + ushort irOffset; + }; + Elevation *elev; + const HrirArray *coeffs; + const ubyte2 *delays; + + void add_ref(); + void release(); + + DEF_PLACE_NEWDEL() +}; +using HrtfStorePtr = al::intrusive_ptr; + + +struct EvRadians { float value; }; +struct AzRadians { float value; }; +struct AngularPoint { + EvRadians Elev; + AzRadians Azim; +}; + + +struct DirectHrtfState { + std::array mTemp; + + /* HRTF filter state for dry buffer content */ + uint mIrSize{0}; + al::FlexArray mChannels; + + 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 + * high-frequency gains for the decoder. The calculated impulse responses + * are ordered and scaled according to the matrix input. + */ + void build(const HrtfStore *Hrtf, const uint irSize, + const al::span AmbiPoints, const float (*AmbiMatrix)[MaxAmbiChannels], + const float XOverFreq, const al::span AmbiOrderHFGain); + + static std::unique_ptr Create(size_t num_chans); + + DEF_FAM_NEWDEL(DirectHrtfState, mChannels) +}; + + +al::vector EnumerateHrtf(al::optional pathopt); +HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate); + +void GetHrtfCoeffs(const HrtfStore *Hrtf, float elevation, float azimuth, float distance, + float spread, HrirArray &coeffs, const al::span delays); + +#endif /* CORE_HRTF_H */ diff --git a/modules/openal-soft/core/logging.cpp b/modules/openal-soft/core/logging.cpp new file mode 100644 index 0000000..7ee7ff2 --- /dev/null +++ b/modules/openal-soft/core/logging.cpp @@ -0,0 +1,101 @@ + +#include "config.h" + +#include "logging.h" + +#include +#include +#include + +#include "strutils.h" +#include "vector.h" + + +#ifdef _WIN32 + +#define WIN32_LEAN_AND_MEAN +#include + +void al_print(LogLevel level, FILE *logfile, const char *fmt, ...) +{ + al::vector dynmsg; + char stcmsg[256]; + char *str{stcmsg}; + + std::va_list args, args2; + va_start(args, fmt); + va_copy(args2, args); + const int msglen{std::vsnprintf(str, sizeof(stcmsg), fmt, args)}; + if(unlikely(msglen >= 0 && static_cast(msglen) >= sizeof(stcmsg))) + { + dynmsg.resize(static_cast(msglen) + 1u); + str = dynmsg.data(); + std::vsnprintf(str, dynmsg.size(), fmt, args2); + } + va_end(args2); + va_end(args); + + if(gLogLevel >= level) + { + fputs(str, logfile); + fflush(logfile); + } + /* OutputDebugStringW has no 'level' property to distinguish between + * informational, warning, or error debug messages. So only print them for + * non-Release builds. + */ +#ifndef NDEBUG + std::wstring wstr{utf8_to_wstr(str)}; + OutputDebugStringW(wstr.c_str()); +#endif +} + +#else + +#ifdef __ANDROID__ +#include +#endif + +void al_print(LogLevel level, FILE *logfile, const char *fmt, ...) +{ + al::vector dynmsg; + char stcmsg[256]; + char *str{stcmsg}; + + std::va_list args, args2; + va_start(args, fmt); + va_copy(args2, args); + const int msglen{std::vsnprintf(str, sizeof(stcmsg), fmt, args)}; + if(unlikely(msglen >= 0 && static_cast(msglen) >= sizeof(stcmsg))) + { + dynmsg.resize(static_cast(msglen) + 1u); + str = dynmsg.data(); + std::vsnprintf(str, dynmsg.size(), fmt, args2); + } + va_end(args2); + va_end(args); + + if(gLogLevel >= level) + { + std::fputs(str, logfile); + std::fflush(logfile); + } +#ifdef __ANDROID__ + auto android_severity = [](LogLevel l) noexcept + { + switch(l) + { + case LogLevel::Trace: return ANDROID_LOG_DEBUG; + case LogLevel::Warning: return ANDROID_LOG_WARN; + case LogLevel::Error: return ANDROID_LOG_ERROR; + /* Should not happen. */ + case LogLevel::Disable: + break; + } + return ANDROID_LOG_ERROR; + }; + __android_log_print(android_severity(level), "openal", "%s", str); +#endif +} + +#endif diff --git a/modules/openal-soft/core/logging.h b/modules/openal-soft/core/logging.h new file mode 100644 index 0000000..8146592 --- /dev/null +++ b/modules/openal-soft/core/logging.h @@ -0,0 +1,52 @@ +#ifndef CORE_LOGGING_H +#define CORE_LOGGING_H + +#include + +#include "opthelpers.h" + + +enum class LogLevel { + Disable, + Error, + Warning, + Trace +}; +extern LogLevel gLogLevel; + +extern FILE *gLogFile; + + +#if !defined(_WIN32) && !defined(__ANDROID__) +#define TRACE(...) do { \ + if UNLIKELY(gLogLevel >= LogLevel::Trace) \ + fprintf(gLogFile, "[ALSOFT] (II) " __VA_ARGS__); \ +} while(0) + +#define WARN(...) do { \ + if UNLIKELY(gLogLevel >= LogLevel::Warning) \ + fprintf(gLogFile, "[ALSOFT] (WW) " __VA_ARGS__); \ +} while(0) + +#define ERR(...) do { \ + if UNLIKELY(gLogLevel >= LogLevel::Error) \ + fprintf(gLogFile, "[ALSOFT] (EE) " __VA_ARGS__); \ +} while(0) + +#else + +#ifdef __USE_MINGW_ANSI_STDIO +[[gnu::format(gnu_printf,3,4)]] +#else +[[gnu::format(printf,3,4)]] +#endif +void al_print(LogLevel level, FILE *logfile, const char *fmt, ...); + +#define TRACE(...) al_print(LogLevel::Trace, gLogFile, "[ALSOFT] (II) " __VA_ARGS__) + +#define WARN(...) al_print(LogLevel::Warning, gLogFile, "[ALSOFT] (WW) " __VA_ARGS__) + +#define ERR(...) al_print(LogLevel::Error, gLogFile, "[ALSOFT] (EE) " __VA_ARGS__) +#endif + +#endif /* CORE_LOGGING_H */ diff --git a/modules/openal-soft/Alc/mastering.cpp b/modules/openal-soft/core/mastering.cpp similarity index 50% rename from modules/openal-soft/Alc/mastering.cpp rename to modules/openal-soft/core/mastering.cpp index c71b3cc..9985047 100644 --- a/modules/openal-soft/Alc/mastering.cpp +++ b/modules/openal-soft/core/mastering.cpp @@ -1,25 +1,31 @@ + #include "config.h" -#include -#include +#include "mastering.h" + #include +#include +#include #include +#include +#include +#include -#include "mastering.h" -#include "alu.h" #include "almalloc.h" -#include "math_defs.h" +#include "alnumeric.h" +#include "alspan.h" +#include "opthelpers.h" -/* These structures assume BUFFERSIZE is a power of 2. */ -static_assert((BUFFERSIZE & (BUFFERSIZE-1)) == 0, "BUFFERSIZE is not a power of 2"); +/* These structures assume BufferLineSize is a power of 2. */ +static_assert((BufferLineSize & (BufferLineSize-1)) == 0, "BufferLineSize is not a power of 2"); struct SlidingHold { - ALfloat mValues[BUFFERSIZE]; - ALsizei mExpiries[BUFFERSIZE]; - ALsizei mLowerIndex; - ALsizei mUpperIndex; - ALsizei mLength; + alignas(16) float mValues[BufferLineSize]; + uint mExpiries[BufferLineSize]; + uint mLowerIndex; + uint mUpperIndex; + uint mLength; }; @@ -34,17 +40,14 @@ using namespace std::placeholders; * * http://www.richardhartersworld.com/cri/2001/slidingmin.html */ -ALfloat UpdateSlidingHold(SlidingHold *Hold, const ALsizei i, const ALfloat in) +float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in) { - static constexpr ALsizei mask{BUFFERSIZE - 1}; - const ALsizei length{Hold->mLength}; - ALfloat (&values)[BUFFERSIZE] = Hold->mValues; - ALsizei (&expiries)[BUFFERSIZE] = Hold->mExpiries; - ALsizei lowerIndex{Hold->mLowerIndex}; - ALsizei upperIndex{Hold->mUpperIndex}; - - ASSUME(upperIndex >= 0); - ASSUME(lowerIndex >= 0); + static constexpr uint mask{BufferLineSize - 1}; + const uint length{Hold->mLength}; + float (&values)[BufferLineSize] = Hold->mValues; + uint (&expiries)[BufferLineSize] = Hold->mExpiries; + uint lowerIndex{Hold->mLowerIndex}; + uint upperIndex{Hold->mUpperIndex}; if(i >= expiries[upperIndex]) upperIndex = (upperIndex + 1) & mask; @@ -77,41 +80,36 @@ ALfloat UpdateSlidingHold(SlidingHold *Hold, const ALsizei i, const ALfloat in) return values[upperIndex]; } -void ShiftSlidingHold(SlidingHold *Hold, const ALsizei n) +void ShiftSlidingHold(SlidingHold *Hold, const uint n) { - ASSUME(Hold->mUpperIndex >= 0); - ASSUME(Hold->mLowerIndex >= 0); - auto exp_begin = std::begin(Hold->mExpiries) + Hold->mUpperIndex; auto exp_last = std::begin(Hold->mExpiries) + Hold->mLowerIndex; - if(exp_last < exp_begin) + if(exp_last-exp_begin < 0) { std::transform(exp_begin, std::end(Hold->mExpiries), exp_begin, - std::bind(std::minus{}, _1, n)); + std::bind(std::minus<>{}, _1, n)); exp_begin = std::begin(Hold->mExpiries); } - std::transform(exp_begin, exp_last+1, exp_begin, std::bind(std::minus{}, _1, n)); + std::transform(exp_begin, exp_last+1, exp_begin, std::bind(std::minus<>{}, _1, n)); } /* Multichannel compression is linked via the absolute maximum of all * channels. */ -void LinkChannels(Compressor *Comp, const ALsizei SamplesToDo, const ALfloat (*RESTRICT OutBuffer)[BUFFERSIZE]) +void LinkChannels(Compressor *Comp, const uint SamplesToDo, const FloatBufferLine *OutBuffer) { - const ALsizei index{Comp->mLookAhead}; - const ALsizei numChans{Comp->mNumChans}; + const size_t numChans{Comp->mNumChans}; ASSUME(SamplesToDo > 0); ASSUME(numChans > 0); - ASSUME(index >= 0); - auto side_begin = std::begin(Comp->mSideChain) + index; + auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; std::fill(side_begin, side_begin+SamplesToDo, 0.0f); - auto fill_max = [SamplesToDo,side_begin](const ALfloat *input) -> void + auto fill_max = [SamplesToDo,side_begin](const FloatBufferLine &input) -> void { - const ALfloat *RESTRICT buffer{al::assume_aligned<16>(input)}; + const float *RESTRICT buffer{al::assume_aligned<16>(input.data())}; auto max_abs = std::bind(maxf, _1, std::bind(static_cast(std::fabs), _2)); std::transform(side_begin, side_begin+SamplesToDo, buffer, side_begin, max_abs); }; @@ -123,25 +121,23 @@ void LinkChannels(Compressor *Comp, const ALsizei SamplesToDo, const ALfloat (*R * it uses an instantaneous squared peak detector and a squared RMS detector * both with 200ms release times. */ -static void CrestDetector(Compressor *Comp, const ALsizei SamplesToDo) +static void CrestDetector(Compressor *Comp, const uint SamplesToDo) { - const ALfloat a_crest{Comp->mCrestCoeff}; - const ALsizei index{Comp->mLookAhead}; - ALfloat y2_peak{Comp->mLastPeakSq}; - ALfloat y2_rms{Comp->mLastRmsSq}; + const float a_crest{Comp->mCrestCoeff}; + float y2_peak{Comp->mLastPeakSq}; + float y2_rms{Comp->mLastRmsSq}; ASSUME(SamplesToDo > 0); - ASSUME(index >= 0); - auto calc_crest = [&y2_rms,&y2_peak,a_crest](const ALfloat x_abs) noexcept -> ALfloat + auto calc_crest = [&y2_rms,&y2_peak,a_crest](const float x_abs) noexcept -> float { - ALfloat x2 = maxf(0.000001f, x_abs * x_abs); + const float x2{clampf(x_abs * x_abs, 0.000001f, 1000000.0f)}; - y2_peak = maxf(x2, lerp(x2, y2_peak, a_crest)); - y2_rms = lerp(x2, y2_rms, a_crest); + y2_peak = maxf(x2, lerpf(x2, y2_peak, a_crest)); + y2_rms = lerpf(x2, y2_rms, a_crest); return y2_peak / y2_rms; }; - auto side_begin = std::begin(Comp->mSideChain) + index; + auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; std::transform(side_begin, side_begin+SamplesToDo, std::begin(Comp->mCrestFactor), calc_crest); Comp->mLastPeakSq = y2_peak; @@ -152,38 +148,32 @@ static void CrestDetector(Compressor *Comp, const ALsizei SamplesToDo) * value of the incoming signal) and performs most of its operations in the * log domain. */ -void PeakDetector(Compressor *Comp, const ALsizei SamplesToDo) +void PeakDetector(Compressor *Comp, const uint SamplesToDo) { - const ALsizei index{Comp->mLookAhead}; - ASSUME(SamplesToDo > 0); - ASSUME(index >= 0); /* Clamp the minimum amplitude to near-zero and convert to logarithm. */ - auto side_begin = std::begin(Comp->mSideChain) + index; + auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; std::transform(side_begin, side_begin+SamplesToDo, side_begin, - std::bind(static_cast(std::log), std::bind(maxf, 0.000001f, _1))); + [](const float s) -> float { return std::log(maxf(0.000001f, s)); }); } /* An optional hold can be used to extend the peak detector so it can more * solidly detect fast transients. This is best used when operating as a * limiter. */ -void PeakHoldDetector(Compressor *Comp, const ALsizei SamplesToDo) +void PeakHoldDetector(Compressor *Comp, const uint SamplesToDo) { - const ALsizei index{Comp->mLookAhead}; - ASSUME(SamplesToDo > 0); - ASSUME(index >= 0); SlidingHold *hold{Comp->mHold}; - ALsizei i{0}; - auto detect_peak = [&i,hold](const ALfloat x_abs) -> ALfloat + uint i{0}; + auto detect_peak = [&i,hold](const float x_abs) -> float { - const ALfloat x_G{std::log(maxf(0.000001f, x_abs))}; + const float x_G{std::log(maxf(0.000001f, x_abs))}; return UpdateSlidingHold(hold, i++, x_G); }; - auto side_begin = std::begin(Comp->mSideChain) + index; + auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; std::transform(side_begin, side_begin+SamplesToDo, side_begin, detect_peak); ShiftSlidingHold(hold, SamplesToDo); @@ -194,52 +184,49 @@ void PeakHoldDetector(Compressor *Comp, const ALsizei SamplesToDo) * to knee width, attack/release times, make-up/post gain, and clipping * reduction. */ -void GainCompressor(Compressor *Comp, const ALsizei SamplesToDo) +void GainCompressor(Compressor *Comp, const uint SamplesToDo) { const bool autoKnee{Comp->mAuto.Knee}; const bool autoAttack{Comp->mAuto.Attack}; const bool autoRelease{Comp->mAuto.Release}; const bool autoPostGain{Comp->mAuto.PostGain}; const bool autoDeclip{Comp->mAuto.Declip}; - const ALsizei lookAhead{Comp->mLookAhead}; - const ALfloat threshold{Comp->mThreshold}; - const ALfloat slope{Comp->mSlope}; - const ALfloat attack{Comp->mAttack}; - const ALfloat release{Comp->mRelease}; - const ALfloat c_est{Comp->mGainEstimate}; - const ALfloat a_adp{Comp->mAdaptCoeff}; - const ALfloat (&crestFactor)[BUFFERSIZE] = Comp->mCrestFactor; - ALfloat (&sideChain)[BUFFERSIZE*2] = Comp->mSideChain; - ALfloat postGain{Comp->mPostGain}; - ALfloat knee{Comp->mKnee}; - ALfloat t_att{attack}; - ALfloat t_rel{release - attack}; - ALfloat a_att{std::exp(-1.0f / t_att)}; - ALfloat a_rel{std::exp(-1.0f / t_rel)}; - ALfloat y_1{Comp->mLastRelease}; - ALfloat y_L{Comp->mLastAttack}; - ALfloat c_dev{Comp->mLastGainDev}; + const uint lookAhead{Comp->mLookAhead}; + const float threshold{Comp->mThreshold}; + const float slope{Comp->mSlope}; + const float attack{Comp->mAttack}; + const float release{Comp->mRelease}; + const float c_est{Comp->mGainEstimate}; + const float a_adp{Comp->mAdaptCoeff}; + const float *crestFactor{Comp->mCrestFactor}; + float postGain{Comp->mPostGain}; + float knee{Comp->mKnee}; + float t_att{attack}; + float t_rel{release - attack}; + float a_att{std::exp(-1.0f / t_att)}; + float a_rel{std::exp(-1.0f / t_rel)}; + float y_1{Comp->mLastRelease}; + float y_L{Comp->mLastAttack}; + float c_dev{Comp->mLastGainDev}; ASSUME(SamplesToDo > 0); - ASSUME(lookAhead >= 0); - for(ALsizei i{0};i < SamplesToDo;i++) + for(float &sideChain : al::span{Comp->mSideChain, SamplesToDo}) { if(autoKnee) knee = maxf(0.0f, 2.5f * (c_dev + c_est)); - const ALfloat knee_h{0.5f * knee}; + const float knee_h{0.5f * knee}; /* This is the gain computer. It applies a static compression curve * to the control signal. */ - const ALfloat x_over{sideChain[lookAhead+i] - threshold}; - const ALfloat y_G{ + const float x_over{std::addressof(sideChain)[lookAhead] - threshold}; + const float y_G{ (x_over <= -knee_h) ? 0.0f : (std::fabs(x_over) < knee_h) ? (x_over + knee_h) * (x_over + knee_h) / (2.0f * knee) : - x_over - }; + x_over}; - const ALfloat y2_crest{crestFactor[i]}; + const float y2_crest{*(crestFactor++)}; if(autoAttack) { t_att = 2.0f*attack/y2_crest; @@ -255,16 +242,16 @@ void GainCompressor(Compressor *Comp, const ALsizei SamplesToDo) * detector. The attack time is subtracted from the release time * above to compensate for the chained operating mode. */ - const ALfloat x_L{-slope * y_G}; - y_1 = maxf(x_L, lerp(x_L, y_1, a_rel)); - y_L = lerp(y_1, y_L, a_att); + const float x_L{-slope * y_G}; + y_1 = maxf(x_L, lerpf(x_L, y_1, a_rel)); + y_L = lerpf(y_1, y_L, a_att); /* Knee width and make-up gain automation make use of a smoothed * measurement of deviation between the control signal and estimate. * The estimate is also used to bias the measurement to hot-start its * average. */ - c_dev = lerp(-(y_L+c_est), c_dev, a_adp); + c_dev = lerpf(-(y_L+c_est), c_dev, a_adp); if(autoPostGain) { @@ -275,12 +262,12 @@ void GainCompressor(Compressor *Comp, const ALsizei SamplesToDo) * same output level. */ if(autoDeclip) - c_dev = maxf(c_dev, sideChain[i] - y_L - threshold - c_est); + c_dev = maxf(c_dev, sideChain - y_L - threshold - c_est); postGain = -(c_dev + c_est); } - sideChain[i] = std::exp(postGain - y_L); + sideChain = std::exp(postGain - y_L); } Comp->mLastRelease = y_1; @@ -293,70 +280,47 @@ void GainCompressor(Compressor *Comp, const ALsizei SamplesToDo) * reaching the offending impulse. This is best used when operating as a * limiter. */ -void SignalDelay(Compressor *Comp, const ALsizei SamplesToDo, ALfloat (*RESTRICT OutBuffer)[BUFFERSIZE]) +void SignalDelay(Compressor *Comp, const uint SamplesToDo, FloatBufferLine *OutBuffer) { - static constexpr ALsizei mask{BUFFERSIZE - 1}; - const ALsizei numChans{Comp->mNumChans}; - const ALsizei indexIn{Comp->mDelayIndex}; - const ALsizei indexOut{Comp->mDelayIndex - Comp->mLookAhead}; + const size_t numChans{Comp->mNumChans}; + const uint lookAhead{Comp->mLookAhead}; ASSUME(SamplesToDo > 0); ASSUME(numChans > 0); + ASSUME(lookAhead > 0); - for(ALsizei c{0};c < numChans;c++) + for(size_t c{0};c < numChans;c++) { - ALfloat *RESTRICT inout{al::assume_aligned<16>(OutBuffer[c])}; - ALfloat *RESTRICT delay{al::assume_aligned<16>(Comp->mDelay[c])}; - for(ALsizei i{0};i < SamplesToDo;i++) - { - const ALfloat sig{inout[i]}; + float *inout{al::assume_aligned<16>(OutBuffer[c].data())}; + float *delaybuf{al::assume_aligned<16>(Comp->mDelay[c].data())}; - inout[i] = delay[(indexOut + i) & mask]; - delay[(indexIn + i) & mask] = sig; + auto inout_end = inout + SamplesToDo; + if LIKELY(SamplesToDo >= lookAhead) + { + auto delay_end = std::rotate(inout, inout_end - lookAhead, inout_end); + std::swap_ranges(inout, delay_end, delaybuf); + } + else + { + auto delay_start = std::swap_ranges(inout, inout_end, delaybuf); + std::rotate(delaybuf, delay_start, delaybuf + lookAhead); } } - - Comp->mDelayIndex = (indexIn + SamplesToDo) & mask; } } // namespace -/* The compressor is initialized with the following settings: - * - * NumChans - Number of channels to process. - * SampleRate - Sample rate to process. - * AutoKnee - Whether to automate the knee width parameter. - * AutoAttack - Whether to automate the attack time parameter. - * AutoRelease - Whether to automate the release time parameter. - * AutoPostGain - Whether to automate the make-up (post) gain parameter. - * AutoDeclip - Whether to automate clipping reduction. Ignored when - * not automating make-up gain. - * LookAheadTime - Look-ahead time (in seconds). - * HoldTime - Peak hold-time (in seconds). - * PreGainDb - Gain applied before detection (in dB). - * PostGainDb - Make-up gain applied after compression (in dB). - * ThresholdDb - Triggering threshold (in dB). - * Ratio - Compression ratio (x:1). Set to INFINITY for true - * limiting. Ignored when automating knee width. - * KneeDb - Knee width (in dB). Ignored when automating knee - * width. - * AttackTimeMin - Attack time (in seconds). Acts as a maximum when - * automating attack time. - * ReleaseTimeMin - Release time (in seconds). Acts as a maximum when - * automating release time. - */ -std::unique_ptr CompressorInit(const ALsizei NumChans, const ALuint SampleRate, - const ALboolean AutoKnee, const ALboolean AutoAttack, - const ALboolean AutoRelease, const ALboolean AutoPostGain, - const ALboolean AutoDeclip, const ALfloat LookAheadTime, - const ALfloat HoldTime, const ALfloat PreGainDb, - const ALfloat PostGainDb, const ALfloat ThresholdDb, - const ALfloat Ratio, const ALfloat KneeDb, - const ALfloat AttackTime, const ALfloat ReleaseTime) + +std::unique_ptr 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) { - auto lookAhead = static_cast( - clampf(std::round(LookAheadTime*SampleRate), 0.0f, BUFFERSIZE-1)); - auto hold = static_cast(clampf(std::round(HoldTime*SampleRate), 0.0f, BUFFERSIZE-1)); + const auto lookAhead = static_cast( + clampf(std::round(LookAheadTime*SampleRate), 0.0f, BufferLineSize-1)); + const auto hold = static_cast( + clampf(std::round(HoldTime*SampleRate), 0.0f, BufferLineSize-1)); size_t size{sizeof(Compressor)}; if(lookAhead > 0) @@ -370,13 +334,12 @@ std::unique_ptr CompressorInit(const ALsizei NumChans, const ALuint size += sizeof(*Compressor::mHold); } - auto Comp = std::unique_ptr{new (al_calloc(16, size)) Compressor{}}; + auto Comp = CompressorPtr{al::construct_at(static_cast(al_calloc(16, size)))}; Comp->mNumChans = NumChans; - Comp->mSampleRate = SampleRate; - Comp->mAuto.Knee = AutoKnee != AL_FALSE; - Comp->mAuto.Attack = AutoAttack != AL_FALSE; - Comp->mAuto.Release = AutoRelease != AL_FALSE; - Comp->mAuto.PostGain = AutoPostGain != AL_FALSE; + Comp->mAuto.Knee = AutoKnee; + Comp->mAuto.Attack = AutoAttack; + Comp->mAuto.Release = AutoRelease; + Comp->mAuto.PostGain = AutoPostGain; Comp->mAuto.Declip = AutoPostGain && AutoDeclip; Comp->mLookAhead = lookAhead; Comp->mPreGain = std::pow(10.0f, PreGainDb / 20.0f); @@ -398,16 +361,15 @@ std::unique_ptr CompressorInit(const ALsizei NumChans, const ALuint { if(hold > 1) { - Comp->mHold = new (reinterpret_cast(Comp.get() + 1)) SlidingHold{}; + Comp->mHold = al::construct_at(reinterpret_cast(Comp.get() + 1)); Comp->mHold->mValues[0] = -std::numeric_limits::infinity(); Comp->mHold->mExpiries[0] = hold; Comp->mHold->mLength = hold; - Comp->mDelay = reinterpret_cast(Comp->mHold + 1); + Comp->mDelay = reinterpret_cast(Comp->mHold + 1); } else - { - Comp->mDelay = reinterpret_cast(Comp.get() + 1); - } + Comp->mDelay = reinterpret_cast(Comp.get() + 1); + std::uninitialized_fill_n(Comp->mDelay, NumChans, FloatBufferLine{}); } Comp->mCrestCoeff = std::exp(-1.0f / (0.200f * SampleRate)); // 200ms @@ -420,24 +382,27 @@ std::unique_ptr CompressorInit(const ALsizei NumChans, const ALuint Compressor::~Compressor() { if(mHold) - mHold->~SlidingHold(); + al::destroy_at(mHold); mHold = nullptr; + if(mDelay) + al::destroy_n(mDelay, mNumChans); + mDelay = nullptr; } -void Compressor::process(const ALsizei SamplesToDo, ALfloat (*OutBuffer)[BUFFERSIZE]) +void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer) { - const ALsizei numChans{mNumChans}; + const size_t numChans{mNumChans}; ASSUME(SamplesToDo > 0); ASSUME(numChans > 0); - const ALfloat preGain{mPreGain}; + const float preGain{mPreGain}; if(preGain != 1.0f) { - auto apply_gain = [SamplesToDo,preGain](ALfloat *input) noexcept -> void + auto apply_gain = [SamplesToDo,preGain](FloatBufferLine &input) noexcept -> void { - ALfloat *buffer{al::assume_aligned<16>(input)}; + float *buffer{al::assume_aligned<16>(input.data())}; std::transform(buffer, buffer+SamplesToDo, buffer, std::bind(std::multiplies{}, _1, preGain)); }; @@ -459,21 +424,16 @@ void Compressor::process(const ALsizei SamplesToDo, ALfloat (*OutBuffer)[BUFFERS if(mDelay) SignalDelay(this, SamplesToDo, OutBuffer); - const ALfloat (&sideChain)[BUFFERSIZE*2] = mSideChain; - auto apply_comp = [SamplesToDo,&sideChain](ALfloat *input) noexcept -> void + const float (&sideChain)[BufferLineSize*2] = mSideChain; + auto apply_comp = [SamplesToDo,&sideChain](FloatBufferLine &input) noexcept -> void { - ALfloat *buffer{al::assume_aligned<16>(input)}; - const ALfloat *gains{al::assume_aligned<16>(&sideChain[0])}; - /* Mark the gains "input-1 type" as restrict, so the compiler can - * vectorize this loop (otherwise it assumes a write to buffer[n] can - * change gains[n+1]). - */ - std::transform(gains, gains+SamplesToDo, buffer, buffer, + float *buffer{al::assume_aligned<16>(input.data())}; + const float *gains{al::assume_aligned<16>(&sideChain[0])}; + std::transform(gains, gains+SamplesToDo, buffer, buffer, std::bind(std::multiplies{}, _1, _2)); }; std::for_each(OutBuffer, OutBuffer+numChans, apply_comp); - ASSUME(mLookAhead >= 0); auto side_begin = std::begin(mSideChain) + SamplesToDo; std::copy(side_begin, side_begin+mLookAhead, std::begin(mSideChain)); } diff --git a/modules/openal-soft/core/mastering.h b/modules/openal-soft/core/mastering.h new file mode 100644 index 0000000..1a36937 --- /dev/null +++ b/modules/openal-soft/core/mastering.h @@ -0,0 +1,105 @@ +#ifndef CORE_MASTERING_H +#define CORE_MASTERING_H + +#include + +#include "almalloc.h" +#include "bufferline.h" + +struct SlidingHold; + +using uint = unsigned int; + + +/* General topology and basic automation was based on the following paper: + * + * D. Giannoulis, M. Massberg and J. D. Reiss, + * "Parameter Automation in a Dynamic Range Compressor," + * Journal of the Audio Engineering Society, v61 (10), Oct. 2013 + * + * Available (along with supplemental reading) at: + * + * http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/ + */ +struct Compressor { + size_t mNumChans{0u}; + + struct { + bool Knee : 1; + bool Attack : 1; + bool Release : 1; + bool PostGain : 1; + bool Declip : 1; + } mAuto{}; + + uint mLookAhead{0}; + + float mPreGain{0.0f}; + float mPostGain{0.0f}; + + float mThreshold{0.0f}; + float mSlope{0.0f}; + float mKnee{0.0f}; + + float mAttack{0.0f}; + float mRelease{0.0f}; + + alignas(16) float mSideChain[2*BufferLineSize]{}; + alignas(16) float mCrestFactor[BufferLineSize]{}; + + SlidingHold *mHold{nullptr}; + FloatBufferLine *mDelay{nullptr}; + + float mCrestCoeff{0.0f}; + float mGainEstimate{0.0f}; + float mAdaptCoeff{0.0f}; + + float mLastPeakSq{0.0f}; + float mLastRmsSq{0.0f}; + float mLastRelease{0.0f}; + float mLastAttack{0.0f}; + float mLastGainDev{0.0f}; + + + ~Compressor(); + void process(const uint SamplesToDo, FloatBufferLine *OutBuffer); + int getLookAhead() const noexcept { return static_cast(mLookAhead); } + + DEF_PLACE_NEWDEL() + + /** + * The compressor is initialized with the following settings: + * + * \param NumChans Number of channels to process. + * \param SampleRate Sample rate to process. + * \param AutoKnee Whether to automate the knee width parameter. + * \param AutoAttack Whether to automate the attack time parameter. + * \param AutoRelease Whether to automate the release time parameter. + * \param AutoPostGain Whether to automate the make-up (post) gain + * parameter. + * \param AutoDeclip Whether to automate clipping reduction. Ignored + * when not automating make-up gain. + * \param LookAheadTime Look-ahead time (in seconds). + * \param HoldTime Peak hold-time (in seconds). + * \param PreGainDb Gain applied before detection (in dB). + * \param PostGainDb Make-up gain applied after compression (in dB). + * \param ThresholdDb Triggering threshold (in dB). + * \param Ratio Compression ratio (x:1). Set to INFINIFTY for true + * limiting. Ignored when automating knee width. + * \param KneeDb Knee width (in dB). Ignored when automating knee + * width. + * \param AttackTime Attack time (in seconds). Acts as a maximum when + * automating attack time. + * \param ReleaseTime Release time (in seconds). Acts as a maximum when + * 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); +}; +using CompressorPtr = std::unique_ptr; + +#endif /* CORE_MASTERING_H */ diff --git a/modules/openal-soft/core/mixer.cpp b/modules/openal-soft/core/mixer.cpp new file mode 100644 index 0000000..4618406 --- /dev/null +++ b/modules/openal-soft/core/mixer.cpp @@ -0,0 +1,126 @@ + +#include "config.h" + +#include "mixer.h" + +#include + +#include "alnumbers.h" +#include "devformat.h" +#include "device.h" +#include "mixer/defs.h" + +struct CTag; + + +MixerFunc MixSamples{Mix_}; + + +std::array CalcAmbiCoeffs(const float y, const float z, const float x, + const float spread) +{ + std::array coeffs; + + /* Zeroth-order */ + coeffs[0] = 1.0f; /* ACN 0 = 1 */ + /* First-order */ + coeffs[1] = al::numbers::sqrt3_v * y; /* ACN 1 = sqrt(3) * Y */ + coeffs[2] = al::numbers::sqrt3_v * z; /* ACN 2 = sqrt(3) * Z */ + coeffs[3] = al::numbers::sqrt3_v * x; /* ACN 3 = sqrt(3) * X */ + /* Second-order */ + const float xx{x*x}, yy{y*y}, zz{z*z}, xy{x*y}, yz{y*z}, xz{x*z}; + coeffs[4] = 3.872983346f * xy; /* ACN 4 = sqrt(15) * X * Y */ + coeffs[5] = 3.872983346f * yz; /* ACN 5 = sqrt(15) * Y * Z */ + coeffs[6] = 1.118033989f * (3.0f*zz - 1.0f); /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */ + coeffs[7] = 3.872983346f * xz; /* ACN 7 = sqrt(15) * X * Z */ + coeffs[8] = 1.936491673f * (xx - yy); /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */ + /* Third-order */ + coeffs[9] = 2.091650066f * (y*(3.0f*xx - yy)); /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */ + coeffs[10] = 10.246950766f * (z*xy); /* ACN 10 = sqrt(105) * Z * X * Y */ + coeffs[11] = 1.620185175f * (y*(5.0f*zz - 1.0f)); /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */ + coeffs[12] = 1.322875656f * (z*(5.0f*zz - 3.0f)); /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */ + coeffs[13] = 1.620185175f * (x*(5.0f*zz - 1.0f)); /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */ + coeffs[14] = 5.123475383f * (z*(xx - yy)); /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */ + coeffs[15] = 2.091650066f * (x*(xx - 3.0f*yy)); /* ACN 15 = sqrt(35/8) * X * (X*X - 3*Y*Y) */ + /* Fourth-order */ + /* ACN 16 = sqrt(35)*3/2 * X * Y * (X*X - Y*Y) */ + /* ACN 17 = sqrt(35/2)*3/2 * (3*X*X - Y*Y) * Y * Z */ + /* ACN 18 = sqrt(5)*3/2 * X * Y * (7*Z*Z - 1) */ + /* ACN 19 = sqrt(5/2)*3/2 * Y * Z * (7*Z*Z - 3) */ + /* ACN 20 = 3/8 * (35*Z*Z*Z*Z - 30*Z*Z + 3) */ + /* ACN 21 = sqrt(5/2)*3/2 * X * Z * (7*Z*Z - 3) */ + /* ACN 22 = sqrt(5)*3/4 * (X*X - Y*Y) * (7*Z*Z - 1) */ + /* ACN 23 = sqrt(35/2)*3/2 * (X*X - 3*Y*Y) * X * Z */ + /* ACN 24 = sqrt(35)*3/8 * (X*X*X*X - 6*X*X*Y*Y + Y*Y*Y*Y) */ + + if(spread > 0.0f) + { + /* Implement the spread by using a spherical source that subtends the + * angle spread. See: + * http://www.ppsloan.org/publications/StupidSH36.pdf - Appendix A3 + * + * When adjusted for N3D normalization instead of SN3D, these + * calculations are: + * + * ZH0 = -sqrt(pi) * (-1+ca); + * ZH1 = 0.5*sqrt(pi) * sa*sa; + * ZH2 = -0.5*sqrt(pi) * ca*(-1+ca)*(ca+1); + * ZH3 = -0.125*sqrt(pi) * (-1+ca)*(ca+1)*(5*ca*ca - 1); + * ZH4 = -0.125*sqrt(pi) * ca*(-1+ca)*(ca+1)*(7*ca*ca - 3); + * ZH5 = -0.0625*sqrt(pi) * (-1+ca)*(ca+1)*(21*ca*ca*ca*ca - 14*ca*ca + 1); + * + * The gain of the source is compensated for size, so that the + * loudness doesn't depend on the spread. Thus: + * + * ZH0 = 1.0f; + * ZH1 = 0.5f * (ca+1.0f); + * ZH2 = 0.5f * (ca+1.0f)*ca; + * ZH3 = 0.125f * (ca+1.0f)*(5.0f*ca*ca - 1.0f); + * ZH4 = 0.125f * (ca+1.0f)*(7.0f*ca*ca - 3.0f)*ca; + * ZH5 = 0.0625f * (ca+1.0f)*(21.0f*ca*ca*ca*ca - 14.0f*ca*ca + 1.0f); + */ + const float ca{std::cos(spread * 0.5f)}; + /* Increase the source volume by up to +3dB for a full spread. */ + const float scale{std::sqrt(1.0f + al::numbers::inv_pi_v/2.0f*spread)}; + + const float ZH0_norm{scale}; + const float ZH1_norm{scale * 0.5f * (ca+1.f)}; + const float ZH2_norm{scale * 0.5f * (ca+1.f)*ca}; + const float ZH3_norm{scale * 0.125f * (ca+1.f)*(5.f*ca*ca-1.f)}; + + /* Zeroth-order */ + coeffs[0] *= ZH0_norm; + /* First-order */ + coeffs[1] *= ZH1_norm; + coeffs[2] *= ZH1_norm; + coeffs[3] *= ZH1_norm; + /* Second-order */ + coeffs[4] *= ZH2_norm; + coeffs[5] *= ZH2_norm; + coeffs[6] *= ZH2_norm; + coeffs[7] *= ZH2_norm; + coeffs[8] *= ZH2_norm; + /* Third-order */ + coeffs[9] *= ZH3_norm; + coeffs[10] *= ZH3_norm; + coeffs[11] *= ZH3_norm; + coeffs[12] *= ZH3_norm; + coeffs[13] *= ZH3_norm; + coeffs[14] *= ZH3_norm; + coeffs[15] *= ZH3_norm; + } + + return coeffs; +} + +void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain, + const al::span gains) +{ + auto ambimap = mix->AmbiMap.cbegin(); + + auto iter = std::transform(ambimap, ambimap+mix->Buffer.size(), gains.begin(), + [coeffs,ingain](const BFChannelConfig &chanmap) noexcept -> float + { return chanmap.Scale * coeffs[chanmap.Index] * ingain; } + ); + std::fill(iter, gains.end(), 0.0f); +} diff --git a/modules/openal-soft/core/mixer.h b/modules/openal-soft/core/mixer.h new file mode 100644 index 0000000..309f422 --- /dev/null +++ b/modules/openal-soft/core/mixer.h @@ -0,0 +1,101 @@ +#ifndef CORE_MIXER_H +#define CORE_MIXER_H + +#include +#include +#include +#include + +#include "alspan.h" +#include "ambidefs.h" +#include "bufferline.h" +#include "devformat.h" + +struct MixParams; + +using MixerFunc = void(*)(const al::span InSamples, + const al::span OutBuffer, float *CurrentGains, const float *TargetGains, + const size_t Counter, const size_t OutPos); + +extern MixerFunc MixSamples; + + +/** + * Calculates ambisonic encoder coefficients using the X, Y, and Z direction + * components, which must represent a normalized (unit length) vector, and the + * spread is the angular width of the sound (0...tau). + * + * NOTE: The components use ambisonic coordinates. As a result: + * + * Ambisonic Y = OpenAL -X + * Ambisonic Z = OpenAL Y + * Ambisonic X = OpenAL -Z + * + * The components are ordered such that OpenAL's X, Y, and Z are the first, + * second, and third parameters respectively -- simply negate X and Z. + */ +std::array CalcAmbiCoeffs(const float y, const float z, const float x, + const float spread); + +/** + * CalcDirectionCoeffs + * + * Calculates ambisonic coefficients based on an OpenAL direction vector. The + * vector must be normalized (unit length), and the spread is the angular width + * of the sound (0...tau). + */ +inline std::array CalcDirectionCoeffs(const float (&dir)[3], + const float spread) +{ + /* Convert from OpenAL coords to Ambisonics. */ + return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2], spread); +} + +/** + * CalcAngleCoeffs + * + * Calculates ambisonic coefficients based on azimuth and elevation. The + * azimuth and elevation parameters are in radians, going right and up + * respectively. + */ +inline std::array CalcAngleCoeffs(const float azimuth, + const float elevation, const float spread) +{ + const float x{-std::sin(azimuth) * std::cos(elevation)}; + const float y{ std::sin(elevation)}; + const float z{ std::cos(azimuth) * std::cos(elevation)}; + + return CalcAmbiCoeffs(x, y, z, spread); +} + + +/** + * ComputePanGains + * + * Computes panning gains using the given channel decoder coefficients and the + * pre-calculated direction or angle coefficients. For B-Format sources, the + * coeffs are a 'slice' of a transform matrix for the input channel, used to + * scale and orient the sound samples. + */ +void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain, + const al::span gains); + + +/** Helper to set an identity/pass-through panning for ambisonic mixing (3D input). */ +template +auto SetAmbiPanIdentity(T iter, I count, F func) -> std::enable_if_t::value> +{ + if(count < 1) return; + + std::array coeffs{{1.0f}}; + func(*iter, coeffs); + ++iter; + for(I i{1};i < count;++i,++iter) + { + coeffs[i-1] = 0.0f; + coeffs[i ] = 1.0f; + func(*iter, coeffs); + } +} + +#endif /* CORE_MIXER_H */ diff --git a/modules/openal-soft/core/mixer/defs.h b/modules/openal-soft/core/mixer/defs.h new file mode 100644 index 0000000..ba304f2 --- /dev/null +++ b/modules/openal-soft/core/mixer/defs.h @@ -0,0 +1,96 @@ +#ifndef CORE_MIXER_DEFS_H +#define CORE_MIXER_DEFS_H + +#include +#include + +#include "alspan.h" +#include "core/bufferline.h" +#include "core/resampler_limits.h" + +struct HrtfChannelState; +struct HrtfFilter; +struct MixHrtfFilter; + +using uint = unsigned int; +using float2 = std::array; + + +constexpr int MixerFracBits{12}; +constexpr int MixerFracOne{1 << MixerFracBits}; +constexpr int MixerFracMask{MixerFracOne - 1}; + +constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */ + + +enum class Resampler { + Point, + Linear, + Cubic, + FastBSinc12, + BSinc12, + FastBSinc24, + BSinc24, + + Max = BSinc24 +}; + +/* Interpolator state. Kind of a misnomer since the interpolator itself is + * stateless. This just keeps it from having to recompute scale-related + * mappings for every sample. + */ +struct BsincState { + float sf; /* Scale interpolation factor. */ + uint m; /* Coefficient count. */ + uint l; /* Left coefficient offset. */ + /* Filter coefficients, followed by the phase, scale, and scale-phase + * delta coefficients. Starting at phase index 0, each subsequent phase + * index follows contiguously. + */ + const float *filter; +}; + +union InterpState { + BsincState bsinc; +}; + +using ResamplerFunc = float*(*)(const InterpState *state, float *RESTRICT src, uint frac, + uint increment, const al::span dst); + +ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState *state); + + +template +float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, uint increment, + const al::span dst); + +template +void Mix_(const al::span InSamples, const al::span OutBuffer, + float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos); + +template +void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, + const MixHrtfFilter *hrtfparams, const size_t BufferSize); +template +void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, + const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize); +template +void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, + const al::span InSamples, float2 *AccumSamples, + float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize); + +/* Vectorized resampler helpers */ +template +inline void InitPosArrays(uint frac, uint increment, uint (&frac_arr)[N], uint (&pos_arr)[N]) +{ + pos_arr[0] = 0; + frac_arr[0] = frac; + for(size_t i{1};i < N;i++) + { + const uint frac_tmp{frac_arr[i-1] + increment}; + pos_arr[i] = pos_arr[i-1] + (frac_tmp>>MixerFracBits); + frac_arr[i] = frac_tmp&MixerFracMask; + } +} + +#endif /* CORE_MIXER_DEFS_H */ diff --git a/modules/openal-soft/core/mixer/hrtfbase.h b/modules/openal-soft/core/mixer/hrtfbase.h new file mode 100644 index 0000000..606f9d4 --- /dev/null +++ b/modules/openal-soft/core/mixer/hrtfbase.h @@ -0,0 +1,129 @@ +#ifndef CORE_MIXER_HRTFBASE_H +#define CORE_MIXER_HRTFBASE_H + +#include +#include + +#include "almalloc.h" +#include "hrtfdefs.h" +#include "opthelpers.h" + + +using uint = unsigned int; + +using ApplyCoeffsT = void(&)(float2 *RESTRICT Values, const size_t irSize, + const ConstHrirSpan Coeffs, const float left, const float right); + +template +inline void MixHrtfBase(const float *InSamples, float2 *RESTRICT AccumSamples, const size_t IrSize, + const MixHrtfFilter *hrtfparams, const size_t BufferSize) +{ + ASSUME(BufferSize > 0); + + const ConstHrirSpan Coeffs{hrtfparams->Coeffs}; + const float gainstep{hrtfparams->GainStep}; + const float gain{hrtfparams->Gain}; + + size_t ldelay{HrtfHistoryLength - hrtfparams->Delay[0]}; + size_t rdelay{HrtfHistoryLength - hrtfparams->Delay[1]}; + float stepcount{0.0f}; + for(size_t i{0u};i < BufferSize;++i) + { + const float g{gain + gainstep*stepcount}; + const float left{InSamples[ldelay++] * g}; + const float right{InSamples[rdelay++] * g}; + ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, left, right); + + stepcount += 1.0f; + } +} + +template +inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSamples, + const size_t IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, + const size_t BufferSize) +{ + ASSUME(BufferSize > 0); + + const ConstHrirSpan OldCoeffs{oldparams->Coeffs}; + const float oldGainStep{oldparams->Gain / static_cast(BufferSize)}; + const ConstHrirSpan NewCoeffs{newparams->Coeffs}; + const float newGainStep{newparams->GainStep}; + + if LIKELY(oldparams->Gain > GainSilenceThreshold) + { + size_t ldelay{HrtfHistoryLength - oldparams->Delay[0]}; + size_t rdelay{HrtfHistoryLength - oldparams->Delay[1]}; + auto stepcount = static_cast(BufferSize); + for(size_t i{0u};i < BufferSize;++i) + { + const float g{oldGainStep*stepcount}; + const float left{InSamples[ldelay++] * g}; + const float right{InSamples[rdelay++] * g}; + ApplyCoeffs(AccumSamples+i, IrSize, OldCoeffs, left, right); + + stepcount -= 1.0f; + } + } + + if LIKELY(newGainStep*static_cast(BufferSize) > GainSilenceThreshold) + { + size_t ldelay{HrtfHistoryLength+1 - newparams->Delay[0]}; + size_t rdelay{HrtfHistoryLength+1 - newparams->Delay[1]}; + float stepcount{1.0f}; + for(size_t i{1u};i < BufferSize;++i) + { + const float g{newGainStep*stepcount}; + const float left{InSamples[ldelay++] * g}; + const float right{InSamples[rdelay++] * g}; + ApplyCoeffs(AccumSamples+i, IrSize, NewCoeffs, left, right); + + stepcount += 1.0f; + } + } +} + +template +inline void MixDirectHrtfBase(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, + const al::span InSamples, float2 *RESTRICT AccumSamples, + float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize) +{ + ASSUME(BufferSize > 0); + + for(const FloatBufferLine &input : InSamples) + { + /* For dual-band processing, the signal needs extra scaling applied to + * the high frequency response. The band-splitter applies this scaling + * with a consistent phase shift regardless of the scale amount. + */ + ChanState->mSplitter.processHfScale({input.data(), BufferSize}, TempBuf, + ChanState->mHfScale); + + /* Now apply the HRIR coefficients to this channel. */ + const float *RESTRICT tempbuf{al::assume_aligned<16>(TempBuf)}; + const ConstHrirSpan Coeffs{ChanState->mCoeffs}; + for(size_t i{0u};i < BufferSize;++i) + { + const float insample{tempbuf[i]}; + ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, insample, insample); + } + + ++ChanState; + } + + /* Add the HRTF signal to the existing "direct" signal. */ + float *RESTRICT left{al::assume_aligned<16>(LeftOut.data())}; + float *RESTRICT right{al::assume_aligned<16>(RightOut.data())}; + for(size_t i{0u};i < BufferSize;++i) + left[i] += AccumSamples[i][0]; + for(size_t i{0u};i < BufferSize;++i) + right[i] += AccumSamples[i][1]; + + /* Copy the new in-progress accumulation values to the front and clear the + * following samples for the next mix. + */ + auto accum_iter = std::copy_n(AccumSamples+BufferSize, HrirLength, AccumSamples); + std::fill_n(accum_iter, BufferSize, float2{}); +} + +#endif /* CORE_MIXER_HRTFBASE_H */ diff --git a/modules/openal-soft/core/mixer/hrtfdefs.h b/modules/openal-soft/core/mixer/hrtfdefs.h new file mode 100644 index 0000000..3c903ed --- /dev/null +++ b/modules/openal-soft/core/mixer/hrtfdefs.h @@ -0,0 +1,53 @@ +#ifndef CORE_MIXER_HRTFDEFS_H +#define CORE_MIXER_HRTFDEFS_H + +#include + +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/filters/splitter.h" + + +using float2 = std::array; +using ubyte = unsigned char; +using ubyte2 = std::array; +using ushort = unsigned short; +using uint = unsigned int; +using uint2 = std::array; + +constexpr uint HrtfHistoryBits{6}; +constexpr uint HrtfHistoryLength{1 << HrtfHistoryBits}; +constexpr uint HrtfHistoryMask{HrtfHistoryLength - 1}; + +constexpr uint HrirBits{7}; +constexpr uint HrirLength{1 << HrirBits}; +constexpr uint HrirMask{HrirLength - 1}; + +constexpr uint MinIrLength{8}; + +using HrirArray = std::array; +using HrirSpan = al::span; +using ConstHrirSpan = al::span; + +struct MixHrtfFilter { + const ConstHrirSpan Coeffs; + uint2 Delay; + float Gain; + float GainStep; +}; + +struct HrtfFilter { + alignas(16) HrirArray Coeffs; + uint2 Delay; + float Gain; +}; + + +struct HrtfChannelState { + BandSplitter mSplitter; + float mHfScale{}; + alignas(16) HrirArray mCoeffs{}; +}; + +#endif /* CORE_MIXER_HRTFDEFS_H */ diff --git a/modules/openal-soft/core/mixer/mixer_c.cpp b/modules/openal-soft/core/mixer/mixer_c.cpp new file mode 100644 index 0000000..f3e6aa6 --- /dev/null +++ b/modules/openal-soft/core/mixer/mixer_c.cpp @@ -0,0 +1,200 @@ +#include "config.h" + +#include +#include +#include + +#include "alnumeric.h" +#include "core/bsinc_tables.h" +#include "defs.h" +#include "hrtfbase.h" + +struct CTag; +struct CopyTag; +struct PointTag; +struct LerpTag; +struct CubicTag; +struct BSincTag; +struct FastBSincTag; + + +namespace { + +constexpr uint FracPhaseBitDiff{MixerFracBits - BSincPhaseBits}; +constexpr uint FracPhaseDiffOne{1 << FracPhaseBitDiff}; + +inline float do_point(const InterpState&, const float *RESTRICT vals, const uint) +{ return vals[0]; } +inline float do_lerp(const InterpState&, const float *RESTRICT vals, const uint frac) +{ return lerpf(vals[0], vals[1], static_cast(frac)*(1.0f/MixerFracOne)); } +inline float do_cubic(const InterpState&, const float *RESTRICT vals, const uint frac) +{ return cubic(vals[0], vals[1], vals[2], vals[3], static_cast(frac)*(1.0f/MixerFracOne)); } +inline float do_bsinc(const InterpState &istate, const float *RESTRICT vals, const uint frac) +{ + const size_t m{istate.bsinc.m}; + ASSUME(m > 0); + + // Calculate the phase index and factor. + const uint pi{frac >> FracPhaseBitDiff}; + const float pf{static_cast(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)}; + + const float *RESTRICT fil{istate.bsinc.filter + m*pi*2}; + const float *RESTRICT phd{fil + m}; + const float *RESTRICT scd{fil + BSincPhaseCount*2*m}; + const float *RESTRICT spd{scd + m}; + + // Apply the scale and phase interpolated filter. + float r{0.0f}; + for(size_t j_f{0};j_f < m;j_f++) + r += (fil[j_f] + istate.bsinc.sf*scd[j_f] + pf*(phd[j_f] + istate.bsinc.sf*spd[j_f])) * vals[j_f]; + return r; +} +inline float do_fastbsinc(const InterpState &istate, const float *RESTRICT vals, const uint frac) +{ + const size_t m{istate.bsinc.m}; + ASSUME(m > 0); + + // Calculate the phase index and factor. + const uint pi{frac >> FracPhaseBitDiff}; + const float pf{static_cast(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)}; + + const float *RESTRICT fil{istate.bsinc.filter + m*pi*2}; + const float *RESTRICT phd{fil + m}; + + // Apply the phase interpolated filter. + float r{0.0f}; + for(size_t j_f{0};j_f < m;j_f++) + r += (fil[j_f] + pf*phd[j_f]) * vals[j_f]; + return r; +} + +using SamplerT = float(&)(const InterpState&, const float*RESTRICT, const uint); +template +float *DoResample(const InterpState *state, float *RESTRICT src, uint frac, uint increment, + const al::span dst) +{ + const InterpState istate{*state}; + for(float &out : dst) + { + out = Sampler(istate, src, frac); + + frac += increment; + src += frac>>MixerFracBits; + frac &= MixerFracMask; + } + return dst.data(); +} + +inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, + const float left, const float right) +{ + ASSUME(IrSize >= MinIrLength); + for(size_t c{0};c < IrSize;++c) + { + Values[c][0] += Coeffs[c][0] * left; + Values[c][1] += Coeffs[c][1] * right; + } +} + +} // namespace + +template<> +float *Resample_(const InterpState*, float *RESTRICT src, uint, uint, + const al::span dst) +{ +#if defined(HAVE_SSE) || defined(HAVE_NEON) + /* Avoid copying the source data if it's aligned like the destination. */ + if((reinterpret_cast(src)&15) == (reinterpret_cast(dst.data())&15)) + return src; +#endif + std::copy_n(src, dst.size(), dst.begin()); + return dst.data(); +} + +template<> +float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, + uint increment, const al::span dst) +{ return DoResample(state, src, frac, increment, dst); } + +template<> +float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, + uint increment, const al::span dst) +{ return DoResample(state, src, frac, increment, dst); } + +template<> +float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, + uint increment, const al::span dst) +{ return DoResample(state, src-1, frac, increment, dst); } + +template<> +float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, + uint increment, const al::span dst) +{ return DoResample(state, src-state->bsinc.l, frac, increment, dst); } + +template<> +float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, + uint increment, const al::span dst) +{ return DoResample(state, src-state->bsinc.l, frac, increment, dst); } + + +template<> +void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, + const MixHrtfFilter *hrtfparams, const size_t BufferSize) +{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } + +template<> +void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, + const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize) +{ + MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, + BufferSize); +} + +template<> +void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, + const al::span InSamples, float2 *AccumSamples, + float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize) +{ + MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, + IrSize, BufferSize); +} + + +template<> +void Mix_(const al::span InSamples, const al::span OutBuffer, + float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) +{ + const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; + const auto min_len = minz(Counter, InSamples.size()); + for(FloatBufferLine &output : OutBuffer) + { + float *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)}; + float gain{*CurrentGains}; + const float step{(*TargetGains-gain) * delta}; + + size_t pos{0}; + if(!(std::abs(step) > std::numeric_limits::epsilon())) + gain = *TargetGains; + else + { + float step_count{0.0f}; + for(;pos != min_len;++pos) + { + dst[pos] += InSamples[pos] * (gain + step*step_count); + step_count += 1.0f; + } + if(pos == Counter) + gain = *TargetGains; + else + gain += step*step_count; + } + *CurrentGains = gain; + ++CurrentGains; + ++TargetGains; + + if(!(std::abs(gain) > GainSilenceThreshold)) + continue; + for(;pos != InSamples.size();++pos) + dst[pos] += InSamples[pos] * gain; + } +} diff --git a/modules/openal-soft/core/mixer/mixer_neon.cpp b/modules/openal-soft/core/mixer/mixer_neon.cpp new file mode 100644 index 0000000..a346892 --- /dev/null +++ b/modules/openal-soft/core/mixer/mixer_neon.cpp @@ -0,0 +1,307 @@ +#include "config.h" + +#include + +#include +#include + +#include "alnumeric.h" +#include "core/bsinc_defs.h" +#include "defs.h" +#include "hrtfbase.h" + +struct NEONTag; +struct LerpTag; +struct BSincTag; +struct FastBSincTag; + + +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ARM_NEON) +#pragma GCC target("fpu=neon") +#endif + +namespace { + +inline float32x4_t set_f4(float l0, float l1, float l2, float l3) +{ + float32x4_t ret{vmovq_n_f32(l0)}; + ret = vsetq_lane_f32(l1, ret, 1); + ret = vsetq_lane_f32(l2, ret, 2); + ret = vsetq_lane_f32(l3, ret, 3); + return ret; +} + +constexpr uint FracPhaseBitDiff{MixerFracBits - BSincPhaseBits}; +constexpr uint FracPhaseDiffOne{1 << FracPhaseBitDiff}; + +inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, + const float left, const float right) +{ + float32x4_t leftright4; + { + float32x2_t leftright2{vmov_n_f32(left)}; + leftright2 = vset_lane_f32(right, leftright2, 1); + leftright4 = vcombine_f32(leftright2, leftright2); + } + + ASSUME(IrSize >= MinIrLength); + for(size_t c{0};c < IrSize;c += 2) + { + float32x4_t vals = vld1q_f32(&Values[c][0]); + float32x4_t coefs = vld1q_f32(&Coeffs[c][0]); + + vals = vmlaq_f32(vals, coefs, leftright4); + + vst1q_f32(&Values[c][0], vals); + } +} + +} // namespace + +template<> +float *Resample_(const InterpState*, float *RESTRICT src, uint frac, + uint increment, const al::span dst) +{ + const int32x4_t increment4 = vdupq_n_s32(static_cast(increment*4)); + const float32x4_t fracOne4 = vdupq_n_f32(1.0f/MixerFracOne); + const int32x4_t fracMask4 = vdupq_n_s32(MixerFracMask); + alignas(16) uint pos_[4], frac_[4]; + int32x4_t pos4, frac4; + + InitPosArrays(frac, increment, frac_, pos_); + frac4 = vld1q_s32(reinterpret_cast(frac_)); + pos4 = vld1q_s32(reinterpret_cast(pos_)); + + auto dst_iter = dst.begin(); + for(size_t todo{dst.size()>>2};todo;--todo) + { + const int pos0{vgetq_lane_s32(pos4, 0)}; + const int pos1{vgetq_lane_s32(pos4, 1)}; + const int pos2{vgetq_lane_s32(pos4, 2)}; + const int pos3{vgetq_lane_s32(pos4, 3)}; + const float32x4_t val1{set_f4(src[pos0], src[pos1], src[pos2], src[pos3])}; + const float32x4_t val2{set_f4(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])}; + + /* val1 + (val2-val1)*mu */ + const float32x4_t r0{vsubq_f32(val2, val1)}; + const float32x4_t mu{vmulq_f32(vcvtq_f32_s32(frac4), fracOne4)}; + const float32x4_t out{vmlaq_f32(val1, mu, r0)}; + + vst1q_f32(dst_iter, out); + dst_iter += 4; + + frac4 = vaddq_s32(frac4, increment4); + pos4 = vaddq_s32(pos4, vshrq_n_s32(frac4, MixerFracBits)); + frac4 = vandq_s32(frac4, fracMask4); + } + + if(size_t todo{dst.size()&3}) + { + src += static_cast(vgetq_lane_s32(pos4, 0)); + frac = static_cast(vgetq_lane_s32(frac4, 0)); + + do { + *(dst_iter++) = lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); + + frac += increment; + src += frac>>MixerFracBits; + frac &= MixerFracMask; + } while(--todo); + } + return dst.data(); +} + +template<> +float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, + uint increment, const al::span dst) +{ + const float *const filter{state->bsinc.filter}; + const float32x4_t sf4{vdupq_n_f32(state->bsinc.sf)}; + const size_t m{state->bsinc.m}; + ASSUME(m > 0); + + src -= state->bsinc.l; + for(float &out_sample : dst) + { + // Calculate the phase index and factor. + const uint pi{frac >> FracPhaseBitDiff}; + const float pf{static_cast(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)}; + + // Apply the scale and phase interpolated filter. + float32x4_t r4{vdupq_n_f32(0.0f)}; + { + const float32x4_t pf4{vdupq_n_f32(pf)}; + const float *RESTRICT fil{filter + m*pi*2}; + const float *RESTRICT phd{fil + m}; + const float *RESTRICT scd{fil + BSincPhaseCount*2*m}; + const float *RESTRICT spd{scd + m}; + size_t td{m >> 2}; + size_t j{0u}; + + do { + /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ + const float32x4_t f4 = vmlaq_f32( + vmlaq_f32(vld1q_f32(&fil[j]), sf4, vld1q_f32(&scd[j])), + pf4, vmlaq_f32(vld1q_f32(&phd[j]), sf4, vld1q_f32(&spd[j]))); + /* r += f*src */ + r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j])); + j += 4; + } while(--td); + } + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + + frac += increment; + src += frac>>MixerFracBits; + frac &= MixerFracMask; + } + return dst.data(); +} + +template<> +float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, + uint increment, const al::span dst) +{ + const float *const filter{state->bsinc.filter}; + const size_t m{state->bsinc.m}; + ASSUME(m > 0); + + src -= state->bsinc.l; + for(float &out_sample : dst) + { + // Calculate the phase index and factor. + const uint pi{frac >> FracPhaseBitDiff}; + const float pf{static_cast(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)}; + + // Apply the phase interpolated filter. + float32x4_t r4{vdupq_n_f32(0.0f)}; + { + const float32x4_t pf4{vdupq_n_f32(pf)}; + const float *RESTRICT fil{filter + m*pi*2}; + const float *RESTRICT phd{fil + m}; + size_t td{m >> 2}; + size_t j{0u}; + + do { + /* f = fil + pf*phd */ + const float32x4_t f4 = vmlaq_f32(vld1q_f32(&fil[j]), pf4, vld1q_f32(&phd[j])); + /* r += f*src */ + r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j])); + j += 4; + } while(--td); + } + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + + frac += increment; + src += frac>>MixerFracBits; + frac &= MixerFracMask; + } + return dst.data(); +} + + +template<> +void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, + const MixHrtfFilter *hrtfparams, const size_t BufferSize) +{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } + +template<> +void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, + const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize) +{ + MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, + BufferSize); +} + +template<> +void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, + const al::span InSamples, float2 *AccumSamples, + float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize) +{ + MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, + IrSize, BufferSize); +} + + +template<> +void Mix_(const al::span InSamples, const al::span OutBuffer, + float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) +{ + const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; + const auto min_len = minz(Counter, InSamples.size()); + const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len; + + for(FloatBufferLine &output : OutBuffer) + { + float *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)}; + float gain{*CurrentGains}; + const float step{(*TargetGains-gain) * delta}; + + size_t pos{0}; + if(!(std::abs(step) > std::numeric_limits::epsilon())) + gain = *TargetGains; + else + { + float step_count{0.0f}; + /* Mix with applying gain steps in aligned multiples of 4. */ + if(size_t todo{min_len >> 2}) + { + const float32x4_t four4{vdupq_n_f32(4.0f)}; + const float32x4_t step4{vdupq_n_f32(step)}; + const float32x4_t gain4{vdupq_n_f32(gain)}; + float32x4_t step_count4{vdupq_n_f32(0.0f)}; + step_count4 = vsetq_lane_f32(1.0f, step_count4, 1); + step_count4 = vsetq_lane_f32(2.0f, step_count4, 2); + step_count4 = vsetq_lane_f32(3.0f, step_count4, 3); + + do { + const float32x4_t val4 = vld1q_f32(&InSamples[pos]); + float32x4_t dry4 = vld1q_f32(&dst[pos]); + dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4)); + step_count4 = vaddq_f32(step_count4, four4); + vst1q_f32(&dst[pos], dry4); + pos += 4; + } while(--todo); + /* NOTE: step_count4 now represents the next four counts after + * the last four mixed samples, so the lowest element + * represents the next step count to apply. + */ + step_count = vgetq_lane_f32(step_count4, 0); + } + /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ + for(size_t leftover{min_len&3};leftover;++pos,--leftover) + { + dst[pos] += InSamples[pos] * (gain + step*step_count); + step_count += 1.0f; + } + if(pos == Counter) + gain = *TargetGains; + else + gain += step*step_count; + + /* Mix until pos is aligned with 4 or the mix is done. */ + for(size_t leftover{aligned_len&3};leftover;++pos,--leftover) + dst[pos] += InSamples[pos] * gain; + } + *CurrentGains = gain; + ++CurrentGains; + ++TargetGains; + + if(!(std::abs(gain) > GainSilenceThreshold)) + continue; + if(size_t todo{(InSamples.size()-pos) >> 2}) + { + const float32x4_t gain4 = vdupq_n_f32(gain); + do { + const float32x4_t val4 = vld1q_f32(&InSamples[pos]); + float32x4_t dry4 = vld1q_f32(&dst[pos]); + dry4 = vmlaq_f32(dry4, val4, gain4); + vst1q_f32(&dst[pos], dry4); + pos += 4; + } while(--todo); + } + for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover) + dst[pos] += InSamples[pos] * gain; + } +} diff --git a/modules/openal-soft/core/mixer/mixer_sse.cpp b/modules/openal-soft/core/mixer/mixer_sse.cpp new file mode 100644 index 0000000..fc4d8cc --- /dev/null +++ b/modules/openal-soft/core/mixer/mixer_sse.cpp @@ -0,0 +1,272 @@ +#include "config.h" + +#include + +#include +#include + +#include "alnumeric.h" +#include "core/bsinc_defs.h" +#include "defs.h" +#include "hrtfbase.h" + +struct SSETag; +struct BSincTag; +struct FastBSincTag; + + +#if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE__) +#pragma GCC target("sse") +#endif + +namespace { + +constexpr uint FracPhaseBitDiff{MixerFracBits - BSincPhaseBits}; +constexpr uint FracPhaseDiffOne{1 << FracPhaseBitDiff}; + +#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z)) + +inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, + const float left, const float right) +{ + const __m128 lrlr{_mm_setr_ps(left, right, left, right)}; + + ASSUME(IrSize >= MinIrLength); + /* This isn't technically correct to test alignment, but it's true for + * systems that support SSE, which is the only one that needs to know the + * alignment of Values (which alternates between 8- and 16-byte aligned). + */ + if(!(reinterpret_cast(Values)&15)) + { + for(size_t i{0};i < IrSize;i += 2) + { + const __m128 coeffs{_mm_load_ps(&Coeffs[i][0])}; + __m128 vals{_mm_load_ps(&Values[i][0])}; + vals = MLA4(vals, lrlr, coeffs); + _mm_store_ps(&Values[i][0], vals); + } + } + else + { + __m128 imp0, imp1; + __m128 coeffs{_mm_load_ps(&Coeffs[0][0])}; + __m128 vals{_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(&Values[0][0]))}; + imp0 = _mm_mul_ps(lrlr, coeffs); + vals = _mm_add_ps(imp0, vals); + _mm_storel_pi(reinterpret_cast<__m64*>(&Values[0][0]), vals); + size_t td{((IrSize+1)>>1) - 1}; + size_t i{1}; + do { + coeffs = _mm_load_ps(&Coeffs[i+1][0]); + vals = _mm_load_ps(&Values[i][0]); + imp1 = _mm_mul_ps(lrlr, coeffs); + imp0 = _mm_shuffle_ps(imp0, imp1, _MM_SHUFFLE(1, 0, 3, 2)); + vals = _mm_add_ps(imp0, vals); + _mm_store_ps(&Values[i][0], vals); + imp0 = imp1; + i += 2; + } while(--td); + vals = _mm_loadl_pi(vals, reinterpret_cast<__m64*>(&Values[i][0])); + imp0 = _mm_movehl_ps(imp0, imp0); + vals = _mm_add_ps(imp0, vals); + _mm_storel_pi(reinterpret_cast<__m64*>(&Values[i][0]), vals); + } +} + +} // namespace + +template<> +float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, + uint increment, const al::span dst) +{ + const float *const filter{state->bsinc.filter}; + const __m128 sf4{_mm_set1_ps(state->bsinc.sf)}; + const size_t m{state->bsinc.m}; + ASSUME(m > 0); + + src -= state->bsinc.l; + for(float &out_sample : dst) + { + // Calculate the phase index and factor. + const uint pi{frac >> FracPhaseBitDiff}; + const float pf{static_cast(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)}; + + // Apply the scale and phase interpolated filter. + __m128 r4{_mm_setzero_ps()}; + { + const __m128 pf4{_mm_set1_ps(pf)}; + const float *RESTRICT fil{filter + m*pi*2}; + const float *RESTRICT phd{fil + m}; + const float *RESTRICT scd{fil + BSincPhaseCount*2*m}; + const float *RESTRICT spd{scd + m}; + size_t td{m >> 2}; + size_t j{0u}; + + do { + /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ + const __m128 f4 = MLA4( + MLA4(_mm_load_ps(&fil[j]), sf4, _mm_load_ps(&scd[j])), + pf4, MLA4(_mm_load_ps(&phd[j]), sf4, _mm_load_ps(&spd[j]))); + /* r += f*src */ + r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j])); + j += 4; + } while(--td); + } + r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + out_sample = _mm_cvtss_f32(r4); + + frac += increment; + src += frac>>MixerFracBits; + frac &= MixerFracMask; + } + return dst.data(); +} + +template<> +float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, + uint increment, const al::span dst) +{ + const float *const filter{state->bsinc.filter}; + const size_t m{state->bsinc.m}; + ASSUME(m > 0); + + src -= state->bsinc.l; + for(float &out_sample : dst) + { + // Calculate the phase index and factor. + const uint pi{frac >> FracPhaseBitDiff}; + const float pf{static_cast(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)}; + + // Apply the phase interpolated filter. + __m128 r4{_mm_setzero_ps()}; + { + const __m128 pf4{_mm_set1_ps(pf)}; + const float *RESTRICT fil{filter + m*pi*2}; + const float *RESTRICT phd{fil + m}; + size_t td{m >> 2}; + size_t j{0u}; + + do { + /* f = fil + pf*phd */ + const __m128 f4 = MLA4(_mm_load_ps(&fil[j]), pf4, _mm_load_ps(&phd[j])); + /* r += f*src */ + r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j])); + j += 4; + } while(--td); + } + r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + out_sample = _mm_cvtss_f32(r4); + + frac += increment; + src += frac>>MixerFracBits; + frac &= MixerFracMask; + } + return dst.data(); +} + + +template<> +void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, + const MixHrtfFilter *hrtfparams, const size_t BufferSize) +{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } + +template<> +void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, + const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize) +{ + MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, + BufferSize); +} + +template<> +void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, + const al::span InSamples, float2 *AccumSamples, + float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize) +{ + MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, + IrSize, BufferSize); +} + + +template<> +void Mix_(const al::span InSamples, const al::span OutBuffer, + float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) +{ + const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; + const auto min_len = minz(Counter, InSamples.size()); + const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len; + + for(FloatBufferLine &output : OutBuffer) + { + float *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)}; + float gain{*CurrentGains}; + const float step{(*TargetGains-gain) * delta}; + + size_t pos{0}; + if(!(std::abs(step) > std::numeric_limits::epsilon())) + gain = *TargetGains; + else + { + float step_count{0.0f}; + /* Mix with applying gain steps in aligned multiples of 4. */ + if(size_t todo{min_len >> 2}) + { + const __m128 four4{_mm_set1_ps(4.0f)}; + const __m128 step4{_mm_set1_ps(step)}; + const __m128 gain4{_mm_set1_ps(gain)}; + __m128 step_count4{_mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f)}; + do { + const __m128 val4{_mm_load_ps(&InSamples[pos])}; + __m128 dry4{_mm_load_ps(&dst[pos])}; + + /* dry += val * (gain + step*step_count) */ + dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4)); + + _mm_store_ps(&dst[pos], dry4); + step_count4 = _mm_add_ps(step_count4, four4); + pos += 4; + } while(--todo); + /* NOTE: step_count4 now represents the next four counts after + * the last four mixed samples, so the lowest element + * represents the next step count to apply. + */ + step_count = _mm_cvtss_f32(step_count4); + } + /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ + for(size_t leftover{min_len&3};leftover;++pos,--leftover) + { + dst[pos] += InSamples[pos] * (gain + step*step_count); + step_count += 1.0f; + } + if(pos == Counter) + gain = *TargetGains; + else + gain += step*step_count; + + /* Mix until pos is aligned with 4 or the mix is done. */ + for(size_t leftover{aligned_len&3};leftover;++pos,--leftover) + dst[pos] += InSamples[pos] * gain; + } + *CurrentGains = gain; + ++CurrentGains; + ++TargetGains; + + if(!(std::abs(gain) > GainSilenceThreshold)) + continue; + if(size_t todo{(InSamples.size()-pos) >> 2}) + { + const __m128 gain4{_mm_set1_ps(gain)}; + do { + const __m128 val4{_mm_load_ps(&InSamples[pos])}; + __m128 dry4{_mm_load_ps(&dst[pos])}; + dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4)); + _mm_store_ps(&dst[pos], dry4); + pos += 4; + } while(--todo); + } + for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover) + dst[pos] += InSamples[pos] * gain; + } +} diff --git a/modules/openal-soft/core/mixer/mixer_sse2.cpp b/modules/openal-soft/core/mixer/mixer_sse2.cpp new file mode 100644 index 0000000..2c0adb8 --- /dev/null +++ b/modules/openal-soft/core/mixer/mixer_sse2.cpp @@ -0,0 +1,89 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2014 by Timothy Arceri . + * 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 +#include + +#include "alnumeric.h" +#include "defs.h" + +struct SSE2Tag; +struct LerpTag; + + +#if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE2__) +#pragma GCC target("sse2") +#endif + +template<> +float *Resample_(const InterpState*, float *RESTRICT src, uint frac, + uint increment, const al::span dst) +{ + const __m128i increment4{_mm_set1_epi32(static_cast(increment*4))}; + const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)}; + const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; + + alignas(16) uint pos_[4], frac_[4]; + InitPosArrays(frac, increment, frac_, pos_); + __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), + static_cast(frac_[2]), static_cast(frac_[3]))}; + __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), + static_cast(pos_[2]), static_cast(pos_[3]))}; + + auto dst_iter = dst.begin(); + for(size_t todo{dst.size()>>2};todo;--todo) + { + const int pos0{_mm_cvtsi128_si32(pos4)}; + const int pos1{_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))}; + const int pos2{_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))}; + const int pos3{_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))}; + const __m128 val1{_mm_setr_ps(src[pos0 ], src[pos1 ], src[pos2 ], src[pos3 ])}; + const __m128 val2{_mm_setr_ps(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])}; + + /* val1 + (val2-val1)*mu */ + const __m128 r0{_mm_sub_ps(val2, val1)}; + const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)}; + const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))}; + + _mm_store_ps(dst_iter, out); + dst_iter += 4; + + frac4 = _mm_add_epi32(frac4, increment4); + pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits)); + frac4 = _mm_and_si128(frac4, fracMask4); + } + + if(size_t todo{dst.size()&3}) + { + src += static_cast(_mm_cvtsi128_si32(pos4)); + frac = static_cast(_mm_cvtsi128_si32(frac4)); + + do { + *(dst_iter++) = lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); + + frac += increment; + src += frac>>MixerFracBits; + frac &= MixerFracMask; + } while(--todo); + } + return dst.data(); +} diff --git a/modules/openal-soft/Alc/mixer/mixer_sse3.cpp b/modules/openal-soft/core/mixer/mixer_sse3.cpp similarity index 100% rename from modules/openal-soft/Alc/mixer/mixer_sse3.cpp rename to modules/openal-soft/core/mixer/mixer_sse3.cpp diff --git a/modules/openal-soft/Alc/mixer/mixer_sse41.cpp b/modules/openal-soft/core/mixer/mixer_sse41.cpp similarity index 51% rename from modules/openal-soft/Alc/mixer/mixer_sse41.cpp rename to modules/openal-soft/core/mixer/mixer_sse41.cpp index 55639f1..cfedfd6 100644 --- a/modules/openal-soft/Alc/mixer/mixer_sse41.cpp +++ b/modules/openal-soft/core/mixer/mixer_sse41.cpp @@ -24,30 +24,34 @@ #include #include -#include "alu.h" +#include "alnumeric.h" #include "defs.h" +struct SSE4Tag; +struct LerpTag; + + +#if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE4_1__) +#pragma GCC target("sse4.1") +#endif template<> -const ALfloat *Resample_(const InterpState* UNUSED(state), - const ALfloat *RESTRICT src, ALsizei frac, ALint increment, - ALfloat *RESTRICT dst, ALsizei dstlen) +float *Resample_(const InterpState*, float *RESTRICT src, uint frac, + uint increment, const al::span dst) { - const __m128i increment4{_mm_set1_epi32(increment*4)}; - const __m128 fracOne4{_mm_set1_ps(1.0f/FRACTIONONE)}; - const __m128i fracMask4{_mm_set1_epi32(FRACTIONMASK)}; - - ASSUME(frac > 0); - ASSUME(increment > 0); - ASSUME(dstlen >= 0); + const __m128i increment4{_mm_set1_epi32(static_cast(increment*4))}; + const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)}; + const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; - alignas(16) ALsizei pos_[4], frac_[4]; - InitiatePositionArrays(frac, increment, frac_, pos_, 4); - __m128i frac4{_mm_setr_epi32(frac_[0], frac_[1], frac_[2], frac_[3])}; - __m128i pos4{_mm_setr_epi32(pos_[0], pos_[1], pos_[2], pos_[3])}; + alignas(16) uint pos_[4], frac_[4]; + InitPosArrays(frac, increment, frac_, pos_); + __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), + static_cast(frac_[2]), static_cast(frac_[3]))}; + __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), + static_cast(pos_[2]), static_cast(pos_[3]))}; - const ALsizei todo{dstlen & ~3}; - for(ALsizei i{0};i < todo;i += 4) + auto dst_iter = dst.begin(); + for(size_t todo{dst.size()>>2};todo;--todo) { const int pos0{_mm_extract_epi32(pos4, 0)}; const int pos1{_mm_extract_epi32(pos4, 1)}; @@ -61,26 +65,30 @@ const ALfloat *Resample_(const InterpState* UNUSED(state), const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)}; const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))}; - _mm_store_ps(&dst[i], out); + _mm_store_ps(dst_iter, out); + dst_iter += 4; frac4 = _mm_add_epi32(frac4, increment4); - pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, FRACTIONBITS)); + pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits)); frac4 = _mm_and_si128(frac4, fracMask4); } - /* NOTE: These four elements represent the position *after* the last four - * samples, so the lowest element is the next position to resample. - */ - ALsizei pos{_mm_cvtsi128_si32(pos4)}; - frac = _mm_cvtsi128_si32(frac4); - - for(ALsizei i{todo};i < dstlen;++i) + if(size_t todo{dst.size()&3}) { - dst[i] = lerp(src[pos], src[pos+1], frac * (1.0f/FRACTIONONE)); + /* NOTE: These four elements represent the position *after* the last + * four samples, so the lowest element is the next position to + * resample. + */ + src += static_cast(_mm_cvtsi128_si32(pos4)); + frac = static_cast(_mm_cvtsi128_si32(frac4)); + + do { + *(dst_iter++) = lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); - frac += increment; - pos += frac>>FRACTIONBITS; - frac &= FRACTIONMASK; + frac += increment; + src += frac>>MixerFracBits; + frac &= MixerFracMask; + } while(--todo); } - return dst; + return dst.data(); } diff --git a/modules/openal-soft/core/resampler_limits.h b/modules/openal-soft/core/resampler_limits.h new file mode 100644 index 0000000..9d4cefd --- /dev/null +++ b/modules/openal-soft/core/resampler_limits.h @@ -0,0 +1,12 @@ +#ifndef CORE_RESAMPLER_LIMITS_H +#define CORE_RESAMPLER_LIMITS_H + +/* Maximum number of samples to pad on the ends of a buffer for resampling. + * Note that the padding is symmetric (half at the beginning and half at the + * end)! + */ +constexpr int MaxResamplerPadding{48}; + +constexpr int MaxResamplerEdge{MaxResamplerPadding >> 1}; + +#endif /* CORE_RESAMPLER_LIMITS_H */ diff --git a/modules/openal-soft/core/rtkit.cpp b/modules/openal-soft/core/rtkit.cpp new file mode 100644 index 0000000..9210220 --- /dev/null +++ b/modules/openal-soft/core/rtkit.cpp @@ -0,0 +1,232 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + Copyright 2009 Lennart Poettering + Copyright 2010 David Henningsson + Copyright 2021 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. +***/ + +#include "config.h" + +#include "rtkit.h" + +#include + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include + + +namespace dbus { + +constexpr int TypeString{'s'}; +constexpr int TypeVariant{'v'}; +constexpr int TypeInt32{'i'}; +constexpr int TypeUInt32{'u'}; +constexpr int TypeInt64{'x'}; +constexpr int TypeUInt64{'t'}; +constexpr int TypeInvalid{'\0'}; + +struct MessageDeleter { + void operator()(DBusMessage *m) { dbus_message_unref(m); } +}; +using MessagePtr = std::unique_ptr; + +} // namespace dbus + +namespace { + +inline pid_t _gettid() +{ +#ifdef __linux__ + return static_cast(syscall(SYS_gettid)); +#elif defined(__FreeBSD__) + long pid{}; + thr_self(&pid); + return static_cast(pid); +#else +#warning gettid not available + return 0; +#endif +} + +int translate_error(const char *name) +{ + if(strcmp(name, DBUS_ERROR_NO_MEMORY) == 0) + return -ENOMEM; + if(strcmp(name, DBUS_ERROR_SERVICE_UNKNOWN) == 0 + || strcmp(name, DBUS_ERROR_NAME_HAS_NO_OWNER) == 0) + return -ENOENT; + if(strcmp(name, DBUS_ERROR_ACCESS_DENIED) == 0 + || strcmp(name, DBUS_ERROR_AUTH_FAILED) == 0) + return -EACCES; + return -EIO; +} + +int rtkit_get_int_property(DBusConnection *connection, const char *propname, long long *propval) +{ + dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, + "org.freedesktop.DBus.Properties", "Get")}; + if(!m) return -ENOMEM; + + const char *interfacestr = RTKIT_SERVICE_NAME; + auto ready = dbus_message_append_args(m.get(), + dbus::TypeString, &interfacestr, + dbus::TypeString, &propname, + dbus::TypeInvalid); + if(!ready) return -ENOMEM; + + dbus::Error error; + dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1, + &error.get())}; + if(!r) return translate_error(error->name); + + if(dbus_set_error_from_message(&error.get(), r.get())) + return translate_error(error->name); + + int ret{-EBADMSG}; + DBusMessageIter iter{}; + dbus_message_iter_init(r.get(), &iter); + while(int curtype{dbus_message_iter_get_arg_type(&iter)}) + { + if(curtype == dbus::TypeVariant) + { + DBusMessageIter subiter{}; + dbus_message_iter_recurse(&iter, &subiter); + + while((curtype=dbus_message_iter_get_arg_type(&subiter)) != dbus::TypeInvalid) + { + if(curtype == dbus::TypeInt32) + { + dbus_int32_t i32{}; + dbus_message_iter_get_basic(&subiter, &i32); + *propval = i32; + ret = 0; + } + + if(curtype == dbus::TypeInt64) + { + dbus_int64_t i64{}; + dbus_message_iter_get_basic(&subiter, &i64); + *propval = i64; + ret = 0; + } + + dbus_message_iter_next(&subiter); + } + } + dbus_message_iter_next(&iter); + } + + return ret; +} + +} // namespace + +int rtkit_get_max_realtime_priority(DBusConnection *connection) +{ + long long retval{}; + int err{rtkit_get_int_property(connection, "MaxRealtimePriority", &retval)}; + return err < 0 ? err : static_cast(retval); +} + +int rtkit_get_min_nice_level(DBusConnection *connection, int *min_nice_level) +{ + long long retval{}; + int err{rtkit_get_int_property(connection, "MinNiceLevel", &retval)}; + if(err >= 0) *min_nice_level = static_cast(retval); + return err; +} + +long long rtkit_get_rttime_usec_max(DBusConnection *connection) +{ + long long retval{}; + int err{rtkit_get_int_property(connection, "RTTimeUSecMax", &retval)}; + return err < 0 ? err : retval; +} + +int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) +{ + if(thread == 0) + thread = _gettid(); + if(thread == 0) + return -ENOTSUP; + + dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, + "org.freedesktop.RealtimeKit1", "MakeThreadRealtime")}; + if(!m) return -ENOMEM; + + auto u64 = static_cast(thread); + auto u32 = static_cast(priority); + auto ready = dbus_message_append_args(m.get(), + dbus::TypeUInt64, &u64, + dbus::TypeUInt32, &u32, + dbus::TypeInvalid); + if(!ready) return -ENOMEM; + + dbus::Error error; + dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1, + &error.get())}; + if(!r) return translate_error(error->name); + + if(dbus_set_error_from_message(&error.get(), r.get())) + return translate_error(error->name); + + return 0; +} + +int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) +{ + if(thread == 0) + thread = _gettid(); + if(thread == 0) + return -ENOTSUP; + + dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, + "org.freedesktop.RealtimeKit1", "MakeThreadHighPriority")}; + if(!m) return -ENOMEM; + + auto u64 = static_cast(thread); + auto s32 = static_cast(nice_level); + auto ready = dbus_message_append_args(m.get(), + dbus::TypeUInt64, &u64, + dbus::TypeInt32, &s32, + dbus::TypeInvalid); + if(!ready) return -ENOMEM; + + dbus::Error error; + dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1, + &error.get())}; + if(!r) return translate_error(error->name); + + if(dbus_set_error_from_message(&error.get(), r.get())) + return translate_error(error->name); + + return 0; +} diff --git a/modules/openal-soft/core/rtkit.h b/modules/openal-soft/core/rtkit.h new file mode 100644 index 0000000..d4994e2 --- /dev/null +++ b/modules/openal-soft/core/rtkit.h @@ -0,0 +1,71 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foortkithfoo +#define foortkithfoo + +/*** + Copyright 2009 Lennart Poettering + Copyright 2010 David Henningsson + + 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. +***/ + +#include + +#include "dbus_wrap.h" + +/* This is the reference implementation for a client for + * RealtimeKit. You don't have to use this, but if do, just copy these + * sources into your repository */ + +#define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1" +#define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1" + +/* This is mostly equivalent to sched_setparam(thread, SCHED_RR, { + * .sched_priority = priority }). 'thread' needs to be a kernel thread + * id as returned by gettid(), not a pthread_t! If 'thread' is 0 the + * current thread is used. The returned value is a negative errno + * style error code, or 0 on success. */ +int rtkit_make_realtime(DBusConnection *system_bus, pid_t thread, int priority); + +/* This is mostly equivalent to setpriority(PRIO_PROCESS, thread, + * nice_level). 'thread' needs to be a kernel thread id as returned by + * gettid(), not a pthread_t! If 'thread' is 0 the current thread is + * used. The returned value is a negative errno style error code, or 0 + * on success.*/ +int rtkit_make_high_priority(DBusConnection *system_bus, pid_t thread, int nice_level); + +/* Return the maximum value of realtime priority available. Realtime requests + * above this value will fail. A negative value is an errno style error code. + */ +int rtkit_get_max_realtime_priority(DBusConnection *system_bus); + +/* Retreive the minimum value of nice level available. High prio requests + * below this value will fail. The returned value is a negative errno + * style error code, or 0 on success.*/ +int rtkit_get_min_nice_level(DBusConnection *system_bus, int *min_nice_level); + +/* Return the maximum value of RLIMIT_RTTIME to set before attempting a + * realtime request. A negative value is an errno style error code. + */ +long long rtkit_get_rttime_usec_max(DBusConnection *system_bus); + +#endif diff --git a/modules/openal-soft/core/uhjfilter.cpp b/modules/openal-soft/core/uhjfilter.cpp new file mode 100644 index 0000000..7a68f1b --- /dev/null +++ b/modules/openal-soft/core/uhjfilter.cpp @@ -0,0 +1,241 @@ + +#include "config.h" + +#include "uhjfilter.h" + +#include +#include + +#include "alcomplex.h" +#include "alnumeric.h" +#include "opthelpers.h" +#include "phase_shifter.h" + + +namespace { + +const PhaseShifterT PShift{}; + +} // namespace + + +/* Encoding UHJ from B-Format is done as: + * + * S = 0.9396926*W + 0.1855740*X + * D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y + * + * Left = (S + D)/2.0 + * Right = (S - D)/2.0 + * T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y + * Q = 0.9772*Z + * + * where j is a wide-band +90 degree phase shift. 3-channel UHJ excludes Q, + * while 2-channel excludes Q and T. + * + * The phase shift is done using a linear FIR filter derived from an FFT'd + * impulse with the desired shift. + */ + +void UhjEncoder::encode(float *LeftOut, float *RightOut, const FloatBufferLine *InSamples, + const size_t SamplesToDo) +{ + ASSUME(SamplesToDo > 0); + + float *RESTRICT left{al::assume_aligned<16>(LeftOut)}; + float *RESTRICT right{al::assume_aligned<16>(RightOut)}; + + const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0].data())}; + const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1].data())}; + const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2].data())}; + + /* Combine the previously delayed S/D signal with the input. Include any + * existing direct signal with it. + */ + + /* S = 0.9396926*W + 0.1855740*X */ + auto miditer = mS.begin() + sFilterDelay; + std::transform(winput, winput+SamplesToDo, xinput, miditer, + [](const float w, const float x) noexcept -> float + { return 0.9396926f*w + 0.1855740f*x; }); + for(size_t i{0};i < SamplesToDo;++i,++miditer) + *miditer += left[i] + right[i]; + + /* D = 0.6554516*Y */ + auto sideiter = mD.begin() + sFilterDelay; + std::transform(yinput, yinput+SamplesToDo, sideiter, + [](const float y) noexcept -> float { return 0.6554516f*y; }); + for(size_t i{0};i < SamplesToDo;++i,++sideiter) + *sideiter += left[i] - right[i]; + + /* D += j(-0.3420201*W + 0.5098604*X) */ + auto tmpiter = std::copy(mWXHistory.cbegin(), mWXHistory.cend(), mTemp.begin()); + std::transform(winput, winput+SamplesToDo, xinput, tmpiter, + [](const float w, const float x) noexcept -> float + { return -0.3420201f*w + 0.5098604f*x; }); + std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory.size(), mWXHistory.begin()); + PShift.processAccum({mD.data(), SamplesToDo}, mTemp.data()); + + /* Left = (S + D)/2.0 */ + for(size_t i{0};i < SamplesToDo;i++) + left[i] = (mS[i] + mD[i]) * 0.5f; + /* Right = (S - D)/2.0 */ + for(size_t i{0};i < SamplesToDo;i++) + right[i] = (mS[i] - mD[i]) * 0.5f; + + /* Copy the future samples to the front for next time. */ + std::copy(mS.cbegin()+SamplesToDo, mS.cbegin()+SamplesToDo+sFilterDelay, mS.begin()); + std::copy(mD.cbegin()+SamplesToDo, mD.cbegin()+SamplesToDo+sFilterDelay, mD.begin()); +} + + +/* Decoding UHJ is done as: + * + * S = Left + Right + * D = Left - Right + * + * W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) + * X = 0.418496*S - j(0.828331*D + 0.767820*T) + * Y = 0.795968*D - 0.676392*T + j(0.186633*S) + * Z = 1.023332*Q + * + * where j is a +90 degree phase shift. 3-channel UHJ excludes Q, while 2- + * channel excludes Q and T. + */ +void UhjDecoder::decode(const al::span samples, const size_t samplesToDo, + const size_t forwardSamples) +{ + ASSUME(samplesToDo > 0); + + { + const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; + const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + const float *RESTRICT t{al::assume_aligned<16>(samples[2])}; + + /* S = Left + Right */ + for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + mS[i] = left[i] + right[i]; + + /* D = Left - Right */ + for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + mD[i] = left[i] - right[i]; + + /* T */ + for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + mT[i] = t[i]; + } + + float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; + float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; + float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + + /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ + auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); + std::transform(mD.cbegin(), mD.cbegin()+samplesToDo+sFilterDelay, mT.cbegin(), tmpiter, + [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); + std::copy_n(mTemp.cbegin()+forwardSamples, mDTHistory.size(), mDTHistory.begin()); + PShift.process({xoutput, samplesToDo}, mTemp.data()); + + /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ + for(size_t i{0};i < samplesToDo;++i) + woutput[i] = 0.981532f*mS[i] + 0.197484f*xoutput[i]; + /* X = 0.418496*S - j(0.828331*D + 0.767820*T) */ + for(size_t i{0};i < samplesToDo;++i) + xoutput[i] = 0.418496f*mS[i] - xoutput[i]; + + /* Precompute j*S and store in youtput. */ + tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); + std::copy_n(mS.cbegin(), samplesToDo+sFilterDelay, tmpiter); + std::copy_n(mTemp.cbegin()+forwardSamples, mSHistory.size(), mSHistory.begin()); + PShift.process({youtput, samplesToDo}, mTemp.data()); + + /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ + for(size_t i{0};i < samplesToDo;++i) + youtput[i] = 0.795968f*mD[i] - 0.676392f*mT[i] + 0.186633f*youtput[i]; + + if(samples.size() > 3) + { + float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])}; + /* Z = 1.023332*Q */ + for(size_t i{0};i < samplesToDo;++i) + zoutput[i] = 1.023332f*zoutput[i]; + } +} + + +/* Super Stereo processing is done as: + * + * 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 + * + * where j is a +90 degree phase shift. w is a variable control for the + * resulting stereo width, with the range 0 <= w <= 0.7. + */ +void UhjDecoder::decodeStereo(const al::span samples, const size_t samplesToDo, + const size_t forwardSamples) +{ + ASSUME(samplesToDo > 0); + + { + const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; + const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + + for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + mS[i] = left[i] + right[i]; + + /* Pre-apply the width factor to the difference signal D. Smoothly + * interpolate when it changes. + */ + const float wtarget{mWidthControl}; + const float wcurrent{unlikely(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth}; + if(likely(wtarget == wcurrent) || unlikely(forwardSamples == 0)) + { + for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + mD[i] = (left[i] - right[i]) * wcurrent; + } + else + { + const float wstep{(wtarget - wcurrent) / static_cast(forwardSamples)}; + float fi{0.0f}; + size_t i{0}; + for(;i < forwardSamples;++i) + { + mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi); + fi += 1.0f; + } + for(;i < samplesToDo+sFilterDelay;++i) + mD[i] = (left[i] - right[i]) * wtarget; + mCurrentWidth = wtarget; + } + } + + float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; + float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; + float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + + /* Precompute j*D and store in xoutput. */ + auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); + std::copy_n(mD.cbegin(), samplesToDo+sFilterDelay, tmpiter); + std::copy_n(mTemp.cbegin()+forwardSamples, mDTHistory.size(), mDTHistory.begin()); + PShift.process({xoutput, samplesToDo}, mTemp.data()); + + /* W = 0.6098637*S - 0.6896511*j*w*D */ + for(size_t i{0};i < samplesToDo;++i) + woutput[i] = 0.6098637f*mS[i] - 0.6896511f*xoutput[i]; + /* X = 0.8624776*S + 0.7626955*j*w*D */ + for(size_t i{0};i < samplesToDo;++i) + xoutput[i] = 0.8624776f*mS[i] + 0.7626955f*xoutput[i]; + + /* Precompute j*S and store in youtput. */ + tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); + std::copy_n(mS.cbegin(), samplesToDo+sFilterDelay, tmpiter); + std::copy_n(mTemp.cbegin()+forwardSamples, mSHistory.size(), mSHistory.begin()); + PShift.process({youtput, samplesToDo}, mTemp.data()); + + /* Y = 1.6822415*w*D - 0.2156194*j*S */ + for(size_t i{0};i < samplesToDo;++i) + youtput[i] = 1.6822415f*mD[i] - 0.2156194f*youtput[i]; +} diff --git a/modules/openal-soft/core/uhjfilter.h b/modules/openal-soft/core/uhjfilter.h new file mode 100644 index 0000000..0453def --- /dev/null +++ b/modules/openal-soft/core/uhjfilter.h @@ -0,0 +1,84 @@ +#ifndef CORE_UHJFILTER_H +#define CORE_UHJFILTER_H + +#include + +#include "almalloc.h" +#include "bufferline.h" +#include "resampler_limits.h" + + +struct UhjFilterBase { + /* The filter delay is half it's effective size, so a delay of 128 has a + * FIR length of 256. + */ + static constexpr size_t sFilterDelay{128}; +}; + +struct UhjEncoder : public UhjFilterBase { + /* Delays and processing storage for the unfiltered signal. */ + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + + /* History for the FIR filter. */ + alignas(16) std::array mWXHistory{}; + + alignas(16) std::array mTemp{}; + + /** + * Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input + * signal. The input must use FuMa channel ordering and UHJ scaling (FuMa + * with an additional +3dB boost). + */ + void encode(float *LeftOut, float *RightOut, const FloatBufferLine *InSamples, + const size_t SamplesToDo); + + DEF_NEWDEL(UhjEncoder) +}; + + +struct UhjDecoder : public UhjFilterBase { + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mT{}; + + alignas(16) std::array mDTHistory{}; + alignas(16) std::array mSHistory{}; + + alignas(16) std::array mTemp{}; + + float mCurrentWidth{-1.0f}; + + /** + * The width factor for Super Stereo processing. Can be changed in between + * calls to decodeStereo, with valid values being between 0...0.7. + */ + float mWidthControl{0.593f}; + + /** + * Decodes a 3- or 4-channel UHJ signal into a B-Format signal with FuMa + * channel ordering and UHJ scaling. For 3-channel, the 3rd channel may be + * attenuated by 'n', where 0 <= n <= 1. So to decode 2-channel UHJ, supply + * 3 channels with the 3rd channel silent (n=0). The B-Format signal + * reconstructed from 2-channel UHJ should not be run through a normal + * B-Format decoder, as it needs different shelf filters. + */ + void decode(const al::span samples, const size_t samplesToDo, + const size_t forwardSamples); + + /** + * Applies Super Stereo processing on a stereo signal to create a B-Format + * signal with FuMa channel ordering and UHJ scaling. The samples span + * should contain 3 channels, the first two being the left and right stereo + * channels, and the third left empty. + */ + void decodeStereo(const al::span samples, const size_t samplesToDo, + const size_t forwardSamples); + + using DecoderFunc = void (UhjDecoder::*)(const al::span samples, + const size_t samplesToDo, const size_t forwardSamples); + + DEF_NEWDEL(UhjDecoder) +}; + +#endif /* CORE_UHJFILTER_H */ diff --git a/modules/openal-soft/core/uiddefs.cpp b/modules/openal-soft/core/uiddefs.cpp new file mode 100644 index 0000000..244c01a --- /dev/null +++ b/modules/openal-soft/core/uiddefs.cpp @@ -0,0 +1,37 @@ + +#include "config.h" + + +#ifndef AL_NO_UID_DEFS + +#if defined(HAVE_GUIDDEF_H) || defined(HAVE_INITGUID_H) +#define INITGUID +#include +#ifdef HAVE_GUIDDEF_H +#include +#else +#include +#endif + +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71); +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71); + +DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf,0x08, 0x00,0xa0,0xc9,0x25,0xcd,0x16); + +DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e,0x3d, 0xc4,0x57,0x92,0x91,0x69,0x2e); +DEFINE_GUID(IID_IMMDeviceEnumerator, 0xa95664d2, 0x9614, 0x4f35, 0xa7,0x46, 0xde,0x8d,0xb6,0x36,0x17,0xe6); +DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1,0x78, 0xc2,0xf5,0x68,0xa7,0x03,0xb2); +DEFINE_GUID(IID_IAudioRenderClient, 0xf294acfc, 0x3146, 0x4483, 0xa7,0xbf, 0xad,0xdc,0xa7,0xc2,0x60,0xe2); +DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4,0xde, 0x18,0x5c,0x39,0x5c,0xd3,0x17); + +#ifdef HAVE_WASAPI +#include +#include +#include +DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14); +DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0); +DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 ); +#endif +#endif + +#endif /* AL_NO_UID_DEFS */ diff --git a/modules/openal-soft/core/voice.cpp b/modules/openal-soft/core/voice.cpp new file mode 100644 index 0000000..e269c4a --- /dev/null +++ b/modules/openal-soft/core/voice.cpp @@ -0,0 +1,945 @@ + +#include "config.h" + +#include "voice.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "albyte.h" +#include "alnumeric.h" +#include "aloptional.h" +#include "alspan.h" +#include "alstring.h" +#include "ambidefs.h" +#include "async_event.h" +#include "buffer_storage.h" +#include "context.h" +#include "cpu_caps.h" +#include "devformat.h" +#include "device.h" +#include "filters/biquad.h" +#include "filters/nfc.h" +#include "filters/splitter.h" +#include "fmt_traits.h" +#include "logging.h" +#include "mixer.h" +#include "mixer/defs.h" +#include "mixer/hrtfdefs.h" +#include "opthelpers.h" +#include "resampler_limits.h" +#include "ringbuffer.h" +#include "vector.h" +#include "voice_change.h" + +struct CTag; +#ifdef HAVE_SSE +struct SSETag; +#endif +#ifdef HAVE_NEON +struct NEONTag; +#endif +struct CopyTag; + + +static_assert(!(sizeof(DeviceBase::MixerBufferLine)&15), + "DeviceBase::MixerBufferLine must be a multiple of 16 bytes"); +static_assert(!(MaxResamplerEdge&3), "MaxResamplerEdge is not a multiple of 4"); + +Resampler ResamplerDefault{Resampler::Linear}; + +namespace { + +using uint = unsigned int; + +using HrtfMixerFunc = void(*)(const float *InSamples, float2 *AccumSamples, const uint IrSize, + const MixHrtfFilter *hrtfparams, const size_t BufferSize); +using HrtfMixerBlendFunc = void(*)(const float *InSamples, float2 *AccumSamples, + const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, + const size_t BufferSize); + +HrtfMixerFunc MixHrtfSamples{MixHrtf_}; +HrtfMixerBlendFunc MixHrtfBlendSamples{MixHrtfBlend_}; + +inline MixerFunc SelectMixer() +{ +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return Mix_; +#endif +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return Mix_; +#endif + return Mix_; +} + +inline HrtfMixerFunc SelectHrtfMixer() +{ +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return MixHrtf_; +#endif +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return MixHrtf_; +#endif + return MixHrtf_; +} + +inline HrtfMixerBlendFunc SelectHrtfBlendMixer() +{ +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return MixHrtfBlend_; +#endif +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return MixHrtfBlend_; +#endif + return MixHrtfBlend_; +} + +} // namespace + +void Voice::InitMixer(al::optional resampler) +{ + if(resampler) + { + struct ResamplerEntry { + const char name[16]; + const Resampler resampler; + }; + constexpr ResamplerEntry ResamplerList[]{ + { "none", Resampler::Point }, + { "point", Resampler::Point }, + { "linear", Resampler::Linear }, + { "cubic", Resampler::Cubic }, + { "bsinc12", Resampler::BSinc12 }, + { "fast_bsinc12", Resampler::FastBSinc12 }, + { "bsinc24", Resampler::BSinc24 }, + { "fast_bsinc24", Resampler::FastBSinc24 }, + }; + + const char *str{resampler->c_str()}; + if(al::strcasecmp(str, "bsinc") == 0) + { + WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", str); + str = "bsinc12"; + } + else if(al::strcasecmp(str, "sinc4") == 0 || al::strcasecmp(str, "sinc8") == 0) + { + WARN("Resampler option \"%s\" is deprecated, using cubic\n", str); + str = "cubic"; + } + + auto iter = std::find_if(std::begin(ResamplerList), std::end(ResamplerList), + [str](const ResamplerEntry &entry) -> bool + { return al::strcasecmp(str, entry.name) == 0; }); + if(iter == std::end(ResamplerList)) + ERR("Invalid resampler: %s\n", str); + else + ResamplerDefault = iter->resampler; + } + + MixSamples = SelectMixer(); + MixHrtfBlendSamples = SelectHrtfBlendMixer(); + MixHrtfSamples = SelectHrtfMixer(); +} + + +namespace { + +void SendSourceStoppedEvent(ContextBase *context, uint id) +{ + RingBuffer *ring{context->mAsyncEvents.get()}; + auto evt_vec = ring->getWriteVector(); + if(evt_vec.first.len < 1) return; + + AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), + AsyncEvent::SourceStateChange)}; + evt->u.srcstate.id = id; + evt->u.srcstate.state = AsyncEvent::SrcState::Stop; + + ring->writeAdvance(1); +} + + +const float *DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, float *dst, + const al::span src, int type) +{ + switch(type) + { + case AF_None: + lpfilter.clear(); + hpfilter.clear(); + break; + + case AF_LowPass: + lpfilter.process(src, dst); + hpfilter.clear(); + return dst; + case AF_HighPass: + lpfilter.clear(); + hpfilter.process(src, dst); + return dst; + + case AF_BandPass: + DualBiquad{lpfilter, hpfilter}.process(src, dst); + return dst; + } + return src.data(); +} + + +template +inline void LoadSamples(const al::span dstSamples, const size_t dstOffset, + const al::byte *src, const size_t srcOffset, const FmtChannels srcChans, const size_t srcStep, + const size_t samples) noexcept +{ + constexpr size_t sampleSize{sizeof(typename al::FmtTypeTraits::Type)}; + auto s = src + srcOffset*srcStep*sampleSize; + if(srcChans == FmtUHJ2 || srcChans == FmtSuperStereo) + { + al::LoadSampleArray(dstSamples[0]+dstOffset, s, srcStep, samples); + al::LoadSampleArray(dstSamples[1]+dstOffset, s+sampleSize, srcStep, samples); + std::fill_n(dstSamples[2]+dstOffset, samples, 0.0f); + } + else + { + for(auto *dst : dstSamples) + { + al::LoadSampleArray(dst+dstOffset, s, srcStep, samples); + s += sampleSize; + } + } +} + +void LoadSamples(const al::span dstSamples, const size_t dstOffset, const al::byte *src, + const size_t srcOffset, const FmtType srcType, const FmtChannels srcChans, + const size_t srcStep, const size_t samples) noexcept +{ +#define HANDLE_FMT(T) case T: \ + LoadSamples(dstSamples, dstOffset, src, srcOffset, srcChans, srcStep, \ + samples); \ + break + + switch(srcType) + { + HANDLE_FMT(FmtUByte); + HANDLE_FMT(FmtShort); + HANDLE_FMT(FmtFloat); + HANDLE_FMT(FmtDouble); + HANDLE_FMT(FmtMulaw); + HANDLE_FMT(FmtAlaw); + } +#undef HANDLE_FMT +} + +void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, + const size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels, + const size_t srcStep, const size_t samplesToLoad, const al::span voiceSamples) +{ + const uint loopStart{buffer->mLoopStart}; + const uint loopEnd{buffer->mLoopEnd}; + ASSUME(loopEnd > loopStart); + + /* If current pos is beyond the loop range, do not loop */ + if(!bufferLoopItem || dataPosInt >= loopEnd) + { + /* Load what's left to play from the buffer */ + const size_t remaining{minz(samplesToLoad, buffer->mSampleLen-dataPosInt)}; + LoadSamples(voiceSamples, 0, buffer->mSamples, dataPosInt, sampleType, sampleChannels, + srcStep, remaining); + + if(const size_t toFill{samplesToLoad - remaining}) + { + for(auto *chanbuffer : voiceSamples) + { + auto srcsamples = chanbuffer + remaining - 1; + std::fill_n(srcsamples + 1, toFill, *srcsamples); + } + } + } + else + { + /* Load what's left of this loop iteration */ + const size_t remaining{minz(samplesToLoad, loopEnd-dataPosInt)}; + LoadSamples(voiceSamples, 0, buffer->mSamples, dataPosInt, sampleType, sampleChannels, + srcStep, remaining); + + /* Load repeats of the loop to fill the buffer. */ + const auto loopSize = static_cast(loopEnd - loopStart); + size_t samplesLoaded{remaining}; + while(const size_t toFill{minz(samplesToLoad - samplesLoaded, loopSize)}) + { + LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, loopStart, sampleType, + sampleChannels, srcStep, toFill); + samplesLoaded += toFill; + } + } +} + +void LoadBufferCallback(VoiceBufferItem *buffer, const size_t numCallbackSamples, + const FmtType sampleType, const FmtChannels sampleChannels, const size_t srcStep, + const size_t samplesToLoad, const al::span voiceSamples) +{ + /* Load what's left to play from the buffer */ + const size_t remaining{minz(samplesToLoad, numCallbackSamples)}; + LoadSamples(voiceSamples, 0, buffer->mSamples, 0, sampleType, sampleChannels, srcStep, + remaining); + + if(const size_t toFill{samplesToLoad - remaining}) + { + for(auto *chanbuffer : voiceSamples) + { + auto srcsamples = chanbuffer + remaining - 1; + std::fill_n(srcsamples + 1, toFill, *srcsamples); + } + } +} + +void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, + size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels, + const size_t srcStep, const size_t samplesToLoad, const al::span voiceSamples) +{ + /* Crawl the buffer queue to fill in the temp buffer */ + size_t samplesLoaded{0}; + while(buffer && samplesLoaded != samplesToLoad) + { + if(dataPosInt >= buffer->mSampleLen) + { + dataPosInt -= buffer->mSampleLen; + buffer = buffer->mNext.load(std::memory_order_acquire); + if(!buffer) buffer = bufferLoopItem; + continue; + } + + const size_t remaining{minz(samplesToLoad-samplesLoaded, buffer->mSampleLen-dataPosInt)}; + LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, dataPosInt, sampleType, + sampleChannels, srcStep, remaining); + + samplesLoaded += remaining; + if(samplesLoaded == samplesToLoad) + break; + + dataPosInt = 0; + buffer = buffer->mNext.load(std::memory_order_acquire); + if(!buffer) buffer = bufferLoopItem; + } + if(const size_t toFill{samplesToLoad - samplesLoaded}) + { + size_t chanidx{0}; + for(auto *chanbuffer : voiceSamples) + { + auto srcsamples = chanbuffer + samplesLoaded - 1; + std::fill_n(srcsamples + 1, toFill, *srcsamples); + ++chanidx; + } + } +} + + +void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &parms, + const float TargetGain, const uint Counter, uint OutPos, const bool IsPlaying, + DeviceBase *Device) +{ + const uint IrSize{Device->mIrSize}; + auto &HrtfSamples = Device->HrtfSourceData; + auto &AccumSamples = Device->HrtfAccumData; + + /* Copy the HRTF history and new input samples into a temp buffer. */ + auto src_iter = std::copy(parms.Hrtf.History.begin(), parms.Hrtf.History.end(), + std::begin(HrtfSamples)); + std::copy_n(samples, DstBufferSize, src_iter); + /* Copy the last used samples back into the history buffer for later. */ + if(likely(IsPlaying)) + std::copy_n(std::begin(HrtfSamples) + DstBufferSize, parms.Hrtf.History.size(), + parms.Hrtf.History.begin()); + + /* If fading and this is the first mixing pass, fade between the IRs. */ + uint fademix{0u}; + if(Counter && OutPos == 0) + { + fademix = minu(DstBufferSize, Counter); + + float gain{TargetGain}; + + /* The new coefficients need to fade in completely since they're + * replacing the old ones. To keep the gain fading consistent, + * interpolate between the old and new target gains given how much of + * the fade time this mix handles. + */ + if(Counter > fademix) + { + const float a{static_cast(fademix) / static_cast(Counter)}; + gain = lerpf(parms.Hrtf.Old.Gain, TargetGain, a); + } + + MixHrtfFilter hrtfparams{ + parms.Hrtf.Target.Coeffs, + parms.Hrtf.Target.Delay, + 0.0f, gain / static_cast(fademix)}; + MixHrtfBlendSamples(HrtfSamples, AccumSamples+OutPos, IrSize, &parms.Hrtf.Old, &hrtfparams, + fademix); + + /* Update the old parameters with the result. */ + parms.Hrtf.Old = parms.Hrtf.Target; + parms.Hrtf.Old.Gain = gain; + OutPos += fademix; + } + + if(fademix < DstBufferSize) + { + const uint todo{DstBufferSize - fademix}; + float gain{TargetGain}; + + /* Interpolate the target gain if the gain fading lasts longer than + * this mix. + */ + if(Counter > DstBufferSize) + { + const float a{static_cast(todo) / static_cast(Counter-fademix)}; + gain = lerpf(parms.Hrtf.Old.Gain, TargetGain, a); + } + + MixHrtfFilter hrtfparams{ + parms.Hrtf.Target.Coeffs, + parms.Hrtf.Target.Delay, + parms.Hrtf.Old.Gain, + (gain - parms.Hrtf.Old.Gain) / static_cast(todo)}; + MixHrtfSamples(HrtfSamples+fademix, AccumSamples+OutPos, IrSize, &hrtfparams, todo); + + /* Store the now-current gain for next time. */ + parms.Hrtf.Old.Gain = gain; + } +} + +void DoNfcMix(const al::span samples, FloatBufferLine *OutBuffer, DirectParams &parms, + const float *TargetGains, const uint Counter, const uint OutPos, DeviceBase *Device) +{ + using FilterProc = void (NfcFilter::*)(const al::span, float*); + static constexpr FilterProc NfcProcess[MaxAmbiOrder+1]{ + nullptr, &NfcFilter::process1, &NfcFilter::process2, &NfcFilter::process3}; + + float *CurrentGains{parms.Gains.Current.data()}; + MixSamples(samples, {OutBuffer, 1u}, CurrentGains, TargetGains, Counter, OutPos); + ++OutBuffer; + ++CurrentGains; + ++TargetGains; + + const al::span nfcsamples{Device->NfcSampleData, samples.size()}; + size_t order{1}; + while(const size_t chancount{Device->NumChannelsPerOrder[order]}) + { + (parms.NFCtrlFilter.*NfcProcess[order])(samples, nfcsamples.data()); + MixSamples(nfcsamples, {OutBuffer, chancount}, CurrentGains, TargetGains, Counter, OutPos); + OutBuffer += chancount; + CurrentGains += chancount; + TargetGains += chancount; + if(++order == MaxAmbiOrder+1) + break; + } +} + +} // namespace + +void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo) +{ + static constexpr std::array SilentTarget{}; + + ASSUME(SamplesToDo > 0); + + /* Get voice info */ + uint DataPosInt{mPosition.load(std::memory_order_relaxed)}; + uint DataPosFrac{mPositionFrac.load(std::memory_order_relaxed)}; + VoiceBufferItem *BufferListItem{mCurrentBuffer.load(std::memory_order_relaxed)}; + VoiceBufferItem *BufferLoopItem{mLoopBuffer.load(std::memory_order_relaxed)}; + const uint increment{mStep}; + if UNLIKELY(increment < 1) + { + /* If the voice is supposed to be stopping but can't be mixed, just + * stop it before bailing. + */ + if(vstate == Stopping) + mPlayState.store(Stopped, std::memory_order_release); + return; + } + + DeviceBase *Device{Context->mDevice}; + const uint NumSends{Device->NumAuxSends}; + + ResamplerFunc Resample{(increment == MixerFracOne && DataPosFrac == 0) ? + Resample_ : mResampler}; + + uint Counter{mFlags.test(VoiceIsFading) ? SamplesToDo : 0}; + if(!Counter) + { + /* No fading, just overwrite the old/current params. */ + for(auto &chandata : mChans) + { + { + DirectParams &parms = chandata.mDryParams; + if(!mFlags.test(VoiceHasHrtf)) + parms.Gains.Current = parms.Gains.Target; + else + parms.Hrtf.Old = parms.Hrtf.Target; + } + for(uint send{0};send < NumSends;++send) + { + if(mSend[send].Buffer.empty()) + continue; + + SendParams &parms = chandata.mWetParams[send]; + parms.Gains.Current = parms.Gains.Target; + } + } + } + else if UNLIKELY(!BufferListItem) + Counter = std::min(Counter, 64u); + + std::array SamplePointers; + const al::span MixingSamples{SamplePointers.data(), mChans.size()}; + auto offset_bufferline = [](DeviceBase::MixerBufferLine &bufline) noexcept -> float* + { return bufline.data() + MaxResamplerEdge; }; + std::transform(Device->mSampleData.end() - mChans.size(), Device->mSampleData.end(), + MixingSamples.begin(), offset_bufferline); + + const uint PostPadding{MaxResamplerEdge + + (mDecoder ? uint{UhjDecoder::sFilterDelay} : 0u)}; + uint buffers_done{0u}; + uint OutPos{0u}; + do { + /* Figure out how many buffer samples will be needed */ + uint DstBufferSize{SamplesToDo - OutPos}; + uint SrcBufferSize; + + if(increment <= MixerFracOne) + { + /* Calculate the last written dst sample pos. */ + uint64_t DataSize64{DstBufferSize - 1}; + /* Calculate the last read src sample pos. */ + DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits; + /* +1 to get the src sample count, include padding. */ + DataSize64 += 1 + PostPadding; + + /* Result is guaranteed to be <= BufferLineSize+PostPadding since + * we won't use more src samples than dst samples+padding. + */ + SrcBufferSize = static_cast(DataSize64); + } + else + { + uint64_t DataSize64{DstBufferSize}; + /* Calculate the end src sample pos, include padding. */ + DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits; + DataSize64 += PostPadding; + + if(DataSize64 <= DeviceBase::MixerLineSize - MaxResamplerEdge) + SrcBufferSize = static_cast(DataSize64); + else + { + /* If the source size got saturated, we can't fill the desired + * dst size. Figure out how many samples we can actually mix. + */ + SrcBufferSize = DeviceBase::MixerLineSize - MaxResamplerEdge; + + DataSize64 = SrcBufferSize - PostPadding; + DataSize64 = ((DataSize64<(DataSize64) & ~3u; + /* If the voice is stopping, only one mixing iteration will + * be done, so ensure it fades out completely this mix. + */ + if(unlikely(vstate == Stopping)) + Counter = std::min(Counter, DstBufferSize); + } + ASSUME(DstBufferSize > 0); + } + } + + if(unlikely(!BufferListItem)) + { + const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits}; + auto prevSamples = mPrevSamples.data(); + SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerEdge; + for(auto *chanbuffer : MixingSamples) + { + auto srcend = std::copy_n(prevSamples->data(), MaxResamplerPadding, + chanbuffer-MaxResamplerEdge); + + /* When loading from a voice that ended prematurely, only take + * the samples that get closest to 0 amplitude. This helps + * certain sounds fade out better. + */ + auto abs_lt = [](const float lhs, const float rhs) noexcept -> bool + { return std::abs(lhs) < std::abs(rhs); }; + auto srciter = std::min_element(chanbuffer, srcend, abs_lt); + + std::fill(srciter+1, chanbuffer + SrcBufferSize, *srciter); + + std::copy_n(chanbuffer-MaxResamplerEdge+srcOffset, prevSamples->size(), + prevSamples->data()); + ++prevSamples; + } + } + else + { + auto prevSamples = mPrevSamples.data(); + for(auto *chanbuffer : MixingSamples) + { + std::copy_n(prevSamples->data(), MaxResamplerEdge, chanbuffer-MaxResamplerEdge); + ++prevSamples; + } + if(mFlags.test(VoiceIsStatic)) + LoadBufferStatic(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, + mFmtChannels, mFrameStep, SrcBufferSize, MixingSamples); + else if(mFlags.test(VoiceIsCallback)) + { + if(!mFlags.test(VoiceCallbackStopped) && SrcBufferSize > mNumCallbackSamples) + { + const size_t byteOffset{mNumCallbackSamples*mFrameSize}; + const size_t needBytes{SrcBufferSize*mFrameSize - byteOffset}; + + const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData, + &BufferListItem->mSamples[byteOffset], static_cast(needBytes))}; + if(gotBytes < 0) + mFlags.set(VoiceCallbackStopped); + else if(static_cast(gotBytes) < needBytes) + { + mFlags.set(VoiceCallbackStopped); + mNumCallbackSamples += static_cast(gotBytes) / mFrameSize; + } + else + mNumCallbackSamples = SrcBufferSize; + } + LoadBufferCallback(BufferListItem, mNumCallbackSamples, mFmtType, mFmtChannels, + mFrameStep, SrcBufferSize, MixingSamples); + } + else + LoadBufferQueue(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, mFmtChannels, + mFrameStep, SrcBufferSize, MixingSamples); + + const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits}; + if(mDecoder) + { + SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerEdge; + ((*mDecoder).*mDecoderFunc)(MixingSamples, SrcBufferSize, + srcOffset * likely(vstate == Playing)); + } + /* Store the last source samples used for next time. */ + if(likely(vstate == Playing)) + { + prevSamples = mPrevSamples.data(); + for(auto *chanbuffer : MixingSamples) + { + /* Store the last source samples used for next time. */ + std::copy_n(chanbuffer-MaxResamplerEdge+srcOffset, prevSamples->size(), + prevSamples->data()); + ++prevSamples; + } + } + } + + auto voiceSamples = MixingSamples.begin(); + for(auto &chandata : mChans) + { + /* Resample, then apply ambisonic upsampling as needed. */ + float *ResampledData{Resample(&mResampleState, *voiceSamples, DataPosFrac, increment, + {Device->ResampledData, DstBufferSize})}; + ++voiceSamples; + + if(mFlags.test(VoiceIsAmbisonic)) + chandata.mAmbiSplitter.processScale({ResampledData, DstBufferSize}, + chandata.mAmbiHFScale, chandata.mAmbiLFScale); + + /* Now filter and mix to the appropriate outputs. */ + const al::span FilterBuf{Device->FilteredData}; + { + DirectParams &parms = chandata.mDryParams; + const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(), + {ResampledData, DstBufferSize}, mDirect.FilterType)}; + + if(mFlags.test(VoiceHasHrtf)) + { + const float TargetGain{parms.Hrtf.Target.Gain * likely(vstate == Playing)}; + DoHrtfMix(samples, DstBufferSize, parms, TargetGain, Counter, OutPos, + (vstate == Playing), Device); + } + else + { + const float *TargetGains{likely(vstate == Playing) ? parms.Gains.Target.data() + : SilentTarget.data()}; + if(mFlags.test(VoiceHasNfc)) + DoNfcMix({samples, DstBufferSize}, mDirect.Buffer.data(), parms, + TargetGains, Counter, OutPos, Device); + else + MixSamples({samples, DstBufferSize}, mDirect.Buffer, + parms.Gains.Current.data(), TargetGains, Counter, OutPos); + } + } + + for(uint send{0};send < NumSends;++send) + { + if(mSend[send].Buffer.empty()) + continue; + + SendParams &parms = chandata.mWetParams[send]; + const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(), + {ResampledData, DstBufferSize}, mSend[send].FilterType)}; + + const float *TargetGains{likely(vstate == Playing) ? parms.Gains.Target.data() + : SilentTarget.data()}; + MixSamples({samples, DstBufferSize}, mSend[send].Buffer, + parms.Gains.Current.data(), TargetGains, Counter, OutPos); + } + } + /* If the voice is stopping, we're now done. */ + if(unlikely(vstate == Stopping)) + break; + + /* Update positions */ + DataPosFrac += increment*DstBufferSize; + const uint SrcSamplesDone{DataPosFrac>>MixerFracBits}; + DataPosInt += SrcSamplesDone; + DataPosFrac &= MixerFracMask; + + OutPos += DstBufferSize; + Counter = maxu(DstBufferSize, Counter) - DstBufferSize; + + if(unlikely(!BufferListItem)) + { + /* Do nothing extra when there's no buffers. */ + } + else if(mFlags.test(VoiceIsStatic)) + { + if(BufferLoopItem) + { + /* Handle looping static source */ + const uint LoopStart{BufferListItem->mLoopStart}; + const uint LoopEnd{BufferListItem->mLoopEnd}; + if(DataPosInt >= LoopEnd) + { + assert(LoopEnd > LoopStart); + DataPosInt = ((DataPosInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart; + } + } + else + { + /* Handle non-looping static source */ + if(DataPosInt >= BufferListItem->mSampleLen) + { + BufferListItem = nullptr; + break; + } + } + } + else if(mFlags.test(VoiceIsCallback)) + { + /* Handle callback buffer source */ + if(SrcSamplesDone < mNumCallbackSamples) + { + const size_t byteOffset{SrcSamplesDone*mFrameSize}; + const size_t byteEnd{mNumCallbackSamples*mFrameSize}; + al::byte *data{BufferListItem->mSamples}; + std::copy(data+byteOffset, data+byteEnd, data); + mNumCallbackSamples -= SrcSamplesDone; + } + else + { + BufferListItem = nullptr; + mNumCallbackSamples = 0; + } + } + else + { + /* Handle streaming source */ + do { + if(BufferListItem->mSampleLen > DataPosInt) + break; + + DataPosInt -= BufferListItem->mSampleLen; + + ++buffers_done; + BufferListItem = BufferListItem->mNext.load(std::memory_order_relaxed); + if(!BufferListItem) BufferListItem = BufferLoopItem; + } while(BufferListItem); + } + } while(OutPos < SamplesToDo); + + mFlags.set(VoiceIsFading); + + /* Don't update positions and buffers if we were stopping. */ + if(unlikely(vstate == Stopping)) + { + mPlayState.store(Stopped, std::memory_order_release); + return; + } + + /* Capture the source ID in case it's reset for stopping. */ + const uint SourceID{mSourceID.load(std::memory_order_relaxed)}; + + /* Update voice info */ + mPosition.store(DataPosInt, std::memory_order_relaxed); + mPositionFrac.store(DataPosFrac, std::memory_order_relaxed); + mCurrentBuffer.store(BufferListItem, std::memory_order_relaxed); + if(!BufferListItem) + { + mLoopBuffer.store(nullptr, std::memory_order_relaxed); + mSourceID.store(0u, std::memory_order_relaxed); + } + std::atomic_thread_fence(std::memory_order_release); + + /* Send any events now, after the position/buffer info was updated. */ + const uint enabledevt{Context->mEnabledEvts.load(std::memory_order_acquire)}; + if(buffers_done > 0 && (enabledevt&AsyncEvent::BufferCompleted)) + { + RingBuffer *ring{Context->mAsyncEvents.get()}; + auto evt_vec = ring->getWriteVector(); + if(evt_vec.first.len > 0) + { + AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), + AsyncEvent::BufferCompleted)}; + evt->u.bufcomp.id = SourceID; + evt->u.bufcomp.count = buffers_done; + ring->writeAdvance(1); + } + } + + if(!BufferListItem) + { + /* If the voice just ended, set it to Stopping so the next render + * ensures any residual noise fades to 0 amplitude. + */ + mPlayState.store(Stopping, std::memory_order_release); + if((enabledevt&AsyncEvent::SourceStateChange)) + SendSourceStoppedEvent(Context, SourceID); + } +} + +void Voice::prepare(DeviceBase *device) +{ + /* Even if storing really high order ambisonics, we only mix channels for + * orders up to the device order. The rest are simply dropped. + */ + uint num_channels{(mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 3 : + ChannelsFromFmt(mFmtChannels, minu(mAmbiOrder, device->mAmbiOrder))}; + if(unlikely(num_channels > device->mSampleData.size())) + { + ERR("Unexpected channel count: %u (limit: %zu, %d:%d)\n", num_channels, + device->mSampleData.size(), mFmtChannels, mAmbiOrder); + num_channels = static_cast(device->mSampleData.size()); + } + if(mChans.capacity() > 2 && num_channels < mChans.capacity()) + { + decltype(mChans){}.swap(mChans); + decltype(mPrevSamples){}.swap(mPrevSamples); + } + mChans.reserve(maxu(2, num_channels)); + mChans.resize(num_channels); + mPrevSamples.reserve(maxu(2, num_channels)); + mPrevSamples.resize(num_channels); + + if(IsUHJ(mFmtChannels)) + { + mDecoder = std::make_unique(); + mDecoderFunc = (mFmtChannels == FmtSuperStereo) ? &UhjDecoder::decodeStereo + : &UhjDecoder::decode; + } + else + { + mDecoder = nullptr; + mDecoderFunc = nullptr; + } + + /* Clear the stepping value explicitly so the mixer knows not to mix this + * until the update gets applied. + */ + mStep = 0; + + /* Make sure the sample history is cleared. */ + std::fill(mPrevSamples.begin(), mPrevSamples.end(), HistoryLine{}); + + /* Don't need to set the VoiceIsAmbisonic flag if the device is not higher + * order than the voice. No HF scaling is necessary to mix it. + */ + if(mAmbiOrder && device->mAmbiOrder > mAmbiOrder) + { + const uint8_t *OrderFromChan{Is2DAmbisonic(mFmtChannels) ? + AmbiIndex::OrderFrom2DChannel().data() : AmbiIndex::OrderFromChannel().data()}; + const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder); + + const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; + for(auto &chandata : mChans) + { + chandata.mAmbiHFScale = scales[*(OrderFromChan++)]; + chandata.mAmbiLFScale = 1.0f; + chandata.mAmbiSplitter = splitter; + chandata.mDryParams = DirectParams{}; + chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; + std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); + } + /* 2-channel UHJ needs different shelf filters. However, we can't just + * use different shelf filters after mixing it and with any old speaker + * setup the user has. To make this work, we apply the expected shelf + * filters for decoding UHJ2 to quad (only needs LF scaling), and act + * as if those 4 quad channels are encoded right back onto first-order + * B-Format, which then upsamples to higher order as normal (only needs + * HF scaling). + * + * This isn't perfect, but without an entirely separate and limited + * UHJ2 path, it's better than nothing. + */ + if(mFmtChannels == FmtUHJ2) + { + mChans[0].mAmbiLFScale = 0.661f; + mChans[1].mAmbiLFScale = 1.293f; + mChans[2].mAmbiLFScale = 1.293f; + } + mFlags.set(VoiceIsAmbisonic); + } + else if(mFmtChannels == FmtUHJ2 && !device->mUhjEncoder) + { + /* 2-channel UHJ with first-order output also needs the shelf filter + * correction applied, except with UHJ output (UHJ2->B-Format->UHJ2 is + * identity, so don't mess with it). + */ + const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; + for(auto &chandata : mChans) + { + chandata.mAmbiHFScale = 1.0f; + chandata.mAmbiLFScale = 1.0f; + chandata.mAmbiSplitter = splitter; + chandata.mDryParams = DirectParams{}; + chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; + std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); + } + mChans[0].mAmbiLFScale = 0.661f; + mChans[1].mAmbiLFScale = 1.293f; + mChans[2].mAmbiLFScale = 1.293f; + mFlags.set(VoiceIsAmbisonic); + } + else + { + for(auto &chandata : mChans) + { + chandata.mDryParams = DirectParams{}; + chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; + std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); + } + mFlags.reset(VoiceIsAmbisonic); + } +} diff --git a/modules/openal-soft/core/voice.h b/modules/openal-soft/core/voice.h new file mode 100644 index 0000000..70b8084 --- /dev/null +++ b/modules/openal-soft/core/voice.h @@ -0,0 +1,276 @@ +#ifndef CORE_VOICE_H +#define CORE_VOICE_H + +#include +#include +#include +#include +#include +#include + +#include "albyte.h" +#include "almalloc.h" +#include "aloptional.h" +#include "alspan.h" +#include "bufferline.h" +#include "buffer_storage.h" +#include "devformat.h" +#include "filters/biquad.h" +#include "filters/nfc.h" +#include "filters/splitter.h" +#include "mixer/defs.h" +#include "mixer/hrtfdefs.h" +#include "resampler_limits.h" +#include "uhjfilter.h" +#include "vector.h" + +struct ContextBase; +struct DeviceBase; +struct EffectSlot; +enum class DistanceModel : unsigned char; + +using uint = unsigned int; + + +#define MAX_SENDS 6 + + +enum class SpatializeMode : unsigned char { + Off, + On, + Auto +}; + +enum class DirectMode : unsigned char { + Off, + DropMismatch, + RemixMismatch +}; + + +/* Maximum number of extra source samples that may need to be loaded, for + * resampling or conversion purposes. + */ +constexpr uint MaxPostVoiceLoad{MaxResamplerEdge + UhjDecoder::sFilterDelay}; + + +enum { + AF_None = 0, + AF_LowPass = 1, + AF_HighPass = 2, + AF_BandPass = AF_LowPass | AF_HighPass +}; + + +struct DirectParams { + BiquadFilter LowPass; + BiquadFilter HighPass; + + NfcFilter NFCtrlFilter; + + struct { + HrtfFilter Old; + HrtfFilter Target; + alignas(16) std::array History; + } Hrtf; + + struct { + std::array Current; + std::array Target; + } Gains; +}; + +struct SendParams { + BiquadFilter LowPass; + BiquadFilter HighPass; + + struct { + std::array Current; + std::array Target; + } Gains; +}; + + +struct VoiceBufferItem { + std::atomic mNext{nullptr}; + + CallbackType mCallback{nullptr}; + void *mUserData{nullptr}; + + uint mSampleLen{0u}; + uint mLoopStart{0u}; + uint mLoopEnd{0u}; + + al::byte *mSamples{nullptr}; +}; + + +struct VoiceProps { + float Pitch; + float Gain; + float OuterGain; + float MinGain; + float MaxGain; + float InnerAngle; + float OuterAngle; + float RefDistance; + float MaxDistance; + float RolloffFactor; + std::array Position; + std::array Velocity; + std::array Direction; + std::array OrientAt; + std::array OrientUp; + bool HeadRelative; + DistanceModel mDistanceModel; + Resampler mResampler; + DirectMode DirectChannels; + SpatializeMode mSpatializeMode; + + bool DryGainHFAuto; + bool WetGainAuto; + bool WetGainHFAuto; + float OuterGainHF; + + float AirAbsorptionFactor; + float RoomRolloffFactor; + float DopplerFactor; + + std::array StereoPan; + + float Radius; + float EnhWidth; + + /** Direct filter and auxiliary send info. */ + struct { + float Gain; + float GainHF; + float HFReference; + float GainLF; + float LFReference; + } Direct; + struct SendData { + EffectSlot *Slot; + float Gain; + float GainHF; + float HFReference; + float GainLF; + float LFReference; + } Send[MAX_SENDS]; +}; + +struct VoicePropsItem : public VoiceProps { + std::atomic next{nullptr}; + + DEF_NEWDEL(VoicePropsItem) +}; + +enum : uint { + VoiceIsStatic, + VoiceIsCallback, + VoiceIsAmbisonic, + VoiceCallbackStopped, + VoiceIsFading, + VoiceHasHrtf, + VoiceHasNfc, + + VoiceFlagCount +}; + +struct Voice { + enum State { + Stopped, + Playing, + Stopping, + Pending + }; + + std::atomic mUpdate{nullptr}; + + VoiceProps mProps; + + std::atomic mSourceID{0u}; + std::atomic mPlayState{Stopped}; + std::atomic mPendingChange{false}; + + /** + * Source offset in samples, relative to the currently playing buffer, NOT + * the whole queue. + */ + std::atomic mPosition; + /** Fractional (fixed-point) offset to the next sample. */ + std::atomic mPositionFrac; + + /* Current buffer queue item being played. */ + std::atomic mCurrentBuffer; + + /* Buffer queue item to loop to at end of queue (will be NULL for non- + * looping voices). + */ + std::atomic mLoopBuffer; + + /* Properties for the attached buffer(s). */ + FmtChannels mFmtChannels; + FmtType mFmtType; + uint mFrequency; + uint mFrameStep; /**< In steps of the sample type size. */ + uint mFrameSize; /**< In bytes. */ + AmbiLayout mAmbiLayout; + AmbiScaling mAmbiScaling; + uint mAmbiOrder; + + std::unique_ptr mDecoder; + UhjDecoder::DecoderFunc mDecoderFunc{}; + + /** Current target parameters used for mixing. */ + uint mStep{0}; + + ResamplerFunc mResampler; + + InterpState mResampleState; + + std::bitset mFlags{}; + uint mNumCallbackSamples{0}; + + struct TargetData { + int FilterType; + al::span Buffer; + }; + TargetData mDirect; + std::array mSend; + + /* The first MaxResamplerPadding/2 elements are the sample history from the + * previous mix, with an additional MaxResamplerPadding/2 elements that are + * now current (which may be overwritten if the buffer data is still + * available). + */ + using HistoryLine = std::array; + al::vector mPrevSamples{2}; + + struct ChannelData { + float mAmbiHFScale, mAmbiLFScale; + BandSplitter mAmbiSplitter; + + DirectParams mDryParams; + std::array mWetParams; + }; + al::vector mChans{2}; + + Voice() = default; + ~Voice() = default; + + Voice(const Voice&) = delete; + Voice& operator=(const Voice&) = delete; + + void mix(const State vstate, ContextBase *Context, const uint SamplesToDo); + + void prepare(DeviceBase *device); + + static void InitMixer(al::optional resampler); + + DEF_NEWDEL(Voice) +}; + +extern Resampler ResamplerDefault; + +#endif /* CORE_VOICE_H */ diff --git a/modules/openal-soft/core/voice_change.h b/modules/openal-soft/core/voice_change.h new file mode 100644 index 0000000..ddc6186 --- /dev/null +++ b/modules/openal-soft/core/voice_change.h @@ -0,0 +1,31 @@ +#ifndef VOICE_CHANGE_H +#define VOICE_CHANGE_H + +#include + +#include "almalloc.h" + +struct Voice; + +using uint = unsigned int; + + +enum class VChangeState { + Reset, + Stop, + Play, + Pause, + Restart +}; +struct VoiceChange { + Voice *mOldVoice{nullptr}; + Voice *mVoice{nullptr}; + uint mSourceID{0}; + VChangeState mState{}; + + std::atomic mNext{nullptr}; + + DEF_NEWDEL(VoiceChange) +}; + +#endif /* VOICE_CHANGE_H */ diff --git a/modules/openal-soft/docs/ambisonics.txt b/modules/openal-soft/docs/ambisonics.txt index 2d94427..b03e3be 100644 --- a/modules/openal-soft/docs/ambisonics.txt +++ b/modules/openal-soft/docs/ambisonics.txt @@ -79,30 +79,23 @@ Soft (or any other OpenAL implementation that wishes to) can render using Ambisonics and decode the ambisonic mix for a high level of accuracy over what simple pan-pot could provide. -This is effectively what the high-quality mode option does, when given an -appropriate decoder configuation for the playback channel layout. 3D rendering -and effect mixing is done to an ambisonic buffer, which is later decoded for -output utilizing the benefits available to ambisonic processing. - -The basic, non-high-quality, renderer uses similar principles, however it skips -the frequency-dependent processing (so low frequency sounds are treated the -same as high frequency sounds) and does some creative manipulation of the -involved math to skip the intermediate ambisonic buffer, rendering more -directly to the output while still taking advantage of all the available -speakers to reconstruct the sound wave. This method trades away some playback -quality for less memory and processor usage. - -In addition to providing good support for surround sound playback, Ambisonics -also has benefits with stereo output. 2-channel UHJ is a stereo-compatible -format that encodes some surround sound information using a wide-band 90-degree -phase shift filter. It works by taking a B-Format signal, and deriving a -frontal stereo mix with the rear sounds attenuated and filtered in with it. -Although the result is not as good as 3-channel (2D) B-Format, it has the -distinct advantage of only using 2 channels and being compatible with stereo -output. This means it will sound just fine when played as-is through a normal -stereo device, or it may optionally be fed to a properly configured surround -sound receiver which can extract the encoded information and restore some of -the original surround sound signal. +When given an appropriate decoder configuration for the channel layout, the +ambisonic mix can be decoded utilizing the benefits available to ambisonic +processing, including frequency-dependent processing and near-field effects. +Without a decoder configuration, the ambisonic mix can still be decoded for +good stereo or surround sound output, although without near-field effects as +there's no speaker distance information. + +In addition to surround sound output, Ambisonics also has benefits with stereo +output. 2-channel UHJ is a stereo-compatible format that encodes some surround +sound information using a wide-band 90-degree phase shift filter. This is +generated by taking the ambisonic mix and deriving a front-stereo mix with +with the rear sounds filtered in with it. Although the result is not as good as +3-channel (2D) B-Format, it has the distinct advantage of only using 2 channels +and being compatible with stereo output. This means it will sound just fine +when played as-is through a normal stereo device, or it may optionally be fed +to a properly configured surround sound receiver which can extract the encoded +information and restore some of the original surround sound signal. What Are Its Limitations? diff --git a/modules/openal-soft/docs/env-vars.txt b/modules/openal-soft/docs/env-vars.txt index a973ee8..77a30c5 100644 --- a/modules/openal-soft/docs/env-vars.txt +++ b/modules/openal-soft/docs/env-vars.txt @@ -11,9 +11,6 @@ Specifies the amount of logging OpenAL Soft will write out: 1 - Prints out errors only 2 - Prints out warnings and errors 3 - Prints out additional information, as well as warnings and errors -4 - Same as 3, but also device and context reference count changes. This will - print out *a lot* of info, and is generally not useful unless you're trying - to track a reference leak within the library. ALSOFT_LOGFILE Specifies a filename that logged output will be written to. Note that the file @@ -67,8 +64,13 @@ to it before passing in 3D coordinates. Depending on how exactly this is done, it can cause correct output for stereo but incorrect Z panning for surround sound (i.e., sounds that are supposed to be behind you sound like they're in front, and vice-versa). Setting this to "true" or "1" will negate the localized -Z coordinate to attempt to fix output for apps that have incorrect front/back -panning. +Z coordinate to flip front/back panning for 3D sources. + +__ALSOFT_REVERSE_Y +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_SUSPEND_CONTEXT Due to the OpenAL spec not being very clear about them, behavior of the @@ -79,13 +81,3 @@ which some applications make use of to protect against partial updates. In an attempt to standardize on that behavior, OpenAL Soft has changed those methods accordingly. Setting this to "ignore" restores the previous no-op behavior for applications that interact poorly with the new behavior. - -__ALSOFT_REVERB_IGNORES_SOUND_SPEED -Older versions of OpenAL Soft ignored the app-specified speed of sound when -calculating distance-related reverb decays and always assumed the default -343.3m/s. Now, both of the AL_SPEED_OF_SOUND and AL_METERS_PER_UNIT properties -are taken into account for speed of sound adjustments to have an appropriate -affect on the reverb decay. Consequently, applications that use reverb but -don't set these properties properly may find the reverb decay too strong. -Setting this to "true" or "1" will revert to the old behavior for those apps -and assume the default speed of sound for reverb. diff --git a/modules/openal-soft/docs/hrtf.txt b/modules/openal-soft/docs/hrtf.txt index ba8cd8f..7a1a500 100644 --- a/modules/openal-soft/docs/hrtf.txt +++ b/modules/openal-soft/docs/hrtf.txt @@ -13,25 +13,22 @@ including above and below the listener, instead of just to the front, back, and sides. The default data set is based on the KEMAR HRTF data provided by MIT, which can -be found at . It's only -available when using 44100hz or 48000hz playback. +be found at . Custom HRTF Data Sets ===================== OpenAL Soft also provides an option to use user-specified data sets, in -addition to or in place of the default set. This allows users to provide their -own data sets, which could be better suited for their heads, or to work with -stereo speakers instead of headphones, or to support more playback sample -rates, for example. +addition to or in place of the default set. This allows users to provide data +sets that could be better suited for their heads, or to work with stereo +speakers instead of headphones, for example. The file format is specified below. It uses little-endian byte order. == -ALchar magic[8] = "MinPHR02"; +ALchar magic[8] = "MinPHR03"; ALuint sampleRate; -ALubyte sampleType; /* Can be 0 (16-bit) or 1 (24-bit). */ ALubyte channelType; /* Can be 0 (mono) or 1 (stereo). */ ALubyte hrirSize; /* Can be 8 to 128 in steps of 8. */ ALubyte fdCount; /* Can be 1 to 16. */ @@ -42,28 +39,30 @@ struct { ALubyte azCount[evCount]; /* Each can be 1 to 128. */ } fields[fdCount]; -/* NOTE: ALtype can be ALshort (16-bit) or ALbyte[3] (24-bit) depending on - * sampleType, +/* NOTE: ALbyte3 is a packed 24-bit sample type, * hrirCount is the sum of all azCounts. * channels can be 1 (mono) or 2 (stereo) depending on channelType. */ -ALtype coefficients[hrirCount][hrirSize][channels]; +ALbyte3 coefficients[hrirCount][hrirSize][channels]; ALubyte delays[hrirCount][channels]; /* Each can be 0 to 63. */ == -The data is described as thus: +The data layout is as follows: -The file first starts with the 8-byte marker, "MinPHR02", to identify it as an +The file first starts with the 8-byte marker, "MinPHR03", to identify it as an HRTF data set. This is followed by an unsigned 32-bit integer, specifying the -sample rate the data set is designed for (OpenAL Soft will not use it if the -output device's playback rate doesn't match). +sample rate the data set is designed for (OpenAL Soft will resample the HRIRs +if the output device's playback rate doesn't match). -Afterward, an unsigned 8-bit integer specifies how many sample points (or -finite impulse response filter coefficients) make up each HRIR. +Afterward, an unsigned 8-bit integer specifies the channel type, which can be 0 +(mono, single-channel) or 1 (stereo, dual-channel). After this is another 8-bit +integer which specifies how many sample points (or finite impulse response +filter coefficients) make up each HRIR. The following unsigned 8-bit integer specifies the number of fields used by the -data set. Then for each field an unsigned 16-bit short specifies the distance -for that field (in millimeters), followed by an 8-bit integer for the number of +data set, which must be in descending order (farthest first, closest last). +Then for each field an unsigned 16-bit short specifies the distance for that +field in millimeters, followed by an 8-bit integer for the number of elevations. These elevations start at the bottom (-90 degrees), and increment upwards. Following this is an array of unsigned 8-bit integers, one for each elevation which specifies the number of azimuths (and thus HRIRs) that make up @@ -71,14 +70,14 @@ each elevation. Azimuths start clockwise from the front, constructing a full circle. Mono HRTFs use the same HRIRs for both ears by reversing the azimuth calculation (ie. left = angle, right = 360-angle). -The actual coefficients follow. Each coefficient is a signed 16-bit or 24-bit -sample. Stereo HRTFs interleave left/right ear coefficients. The HRIRs must -be minimum-phase. This allows the use of a smaller filter length, reducing +The actual coefficients follow. Each coefficient is a signed 24-bit sample. +Stereo HRTFs interleave left/right ear coefficients. The HRIRs must be +minimum-phase. This allows the use of a smaller filter length, reducing computation. For reference, the default data set uses a 32-point filter while even the smallest data set provided by MIT used a 128-sample filter (a 4x reduction by applying minimum-phase reconstruction). -After the coefficients is an array of unsigned 8-bit delay values, one for -each HRIR (with stereo HRTFs interleaving left/right ear delays). This is the -propagation delay (in samples) a signal must wait before being convolved with -the corresponding minimum-phase HRIR filter. +After the coefficients is an array of unsigned 8-bit delay values as 6.2 fixed- +point integers, one for each HRIR (with stereo HRTFs interleaving left/right +ear delays). This is the propagation delay in samples a signal must wait before +being convolved with the corresponding minimum-phase HRIR filter. diff --git a/modules/openal-soft/examples/alconvolve.c b/modules/openal-soft/examples/alconvolve.c new file mode 100644 index 0000000..93fd2eb --- /dev/null +++ b/modules/openal-soft/examples/alconvolve.c @@ -0,0 +1,594 @@ +/* + * OpenAL Convolution Reverb Example + * + * Copyright (c) 2020 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 applying convolution reverb to a source. */ + +#include +#include +#include +#include +#include +#include + +#include "sndfile.h" + +#include "AL/al.h" +#include "AL/alext.h" + +#include "common/alhelpers.h" + + +#ifndef AL_SOFT_convolution_reverb +#define AL_SOFT_convolution_reverb +#define AL_EFFECT_CONVOLUTION_REVERB_SOFT 0xA000 +#endif + + +/* Filter object functions */ +static LPALGENFILTERS alGenFilters; +static LPALDELETEFILTERS alDeleteFilters; +static LPALISFILTER alIsFilter; +static LPALFILTERI alFilteri; +static LPALFILTERIV alFilteriv; +static LPALFILTERF alFilterf; +static LPALFILTERFV alFilterfv; +static LPALGETFILTERI alGetFilteri; +static LPALGETFILTERIV alGetFilteriv; +static LPALGETFILTERF alGetFilterf; +static LPALGETFILTERFV alGetFilterfv; + +/* Effect object functions */ +static LPALGENEFFECTS alGenEffects; +static LPALDELETEEFFECTS alDeleteEffects; +static LPALISEFFECT alIsEffect; +static LPALEFFECTI alEffecti; +static LPALEFFECTIV alEffectiv; +static LPALEFFECTF alEffectf; +static LPALEFFECTFV alEffectfv; +static LPALGETEFFECTI alGetEffecti; +static LPALGETEFFECTIV alGetEffectiv; +static LPALGETEFFECTF alGetEffectf; +static LPALGETEFFECTFV alGetEffectfv; + +/* Auxiliary Effect Slot object functions */ +static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; +static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; +static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; +static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; +static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; +static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; +static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; +static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; +static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; +static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; +static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; + + +/* This stuff defines a simple streaming player object, the same as alstream.c. + * Comments are removed for brevity, see alstream.c for more details. + */ +#define NUM_BUFFERS 4 +#define BUFFER_SAMPLES 8192 + +typedef struct StreamPlayer { + ALuint buffers[NUM_BUFFERS]; + ALuint source; + + SNDFILE *sndfile; + SF_INFO sfinfo; + float *membuf; + + ALenum format; +} StreamPlayer; + +static StreamPlayer *NewPlayer(void) +{ + StreamPlayer *player; + + player = calloc(1, sizeof(*player)); + assert(player != NULL); + + alGenBuffers(NUM_BUFFERS, player->buffers); + assert(alGetError() == AL_NO_ERROR && "Could not create buffers"); + + alGenSources(1, &player->source); + assert(alGetError() == AL_NO_ERROR && "Could not create source"); + + alSource3i(player->source, AL_POSITION, 0, 0, -1); + alSourcei(player->source, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(player->source, AL_ROLLOFF_FACTOR, 0); + assert(alGetError() == AL_NO_ERROR && "Could not set source parameters"); + + return player; +} + +static void ClosePlayerFile(StreamPlayer *player) +{ + if(player->sndfile) + sf_close(player->sndfile); + player->sndfile = NULL; + + free(player->membuf); + player->membuf = NULL; +} + +static void DeletePlayer(StreamPlayer *player) +{ + ClosePlayerFile(player); + + alDeleteSources(1, &player->source); + alDeleteBuffers(NUM_BUFFERS, player->buffers); + if(alGetError() != AL_NO_ERROR) + fprintf(stderr, "Failed to delete object IDs\n"); + + memset(player, 0, sizeof(*player)); + free(player); +} + +static int OpenPlayerFile(StreamPlayer *player, const char *filename) +{ + size_t frame_size; + + ClosePlayerFile(player); + + player->sndfile = sf_open(filename, SFM_READ, &player->sfinfo); + if(!player->sndfile) + { + fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(NULL)); + return 0; + } + + player->format = AL_NONE; + if(player->sfinfo.channels == 1) + player->format = AL_FORMAT_MONO_FLOAT32; + else if(player->sfinfo.channels == 2) + player->format = AL_FORMAT_STEREO_FLOAT32; + else if(player->sfinfo.channels == 6) + player->format = AL_FORMAT_51CHN32; + else if(player->sfinfo.channels == 3) + { + if(sf_command(player->sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + player->format = AL_FORMAT_BFORMAT2D_FLOAT32; + } + else if(player->sfinfo.channels == 4) + { + if(sf_command(player->sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + player->format = AL_FORMAT_BFORMAT3D_FLOAT32; + } + if(!player->format) + { + fprintf(stderr, "Unsupported channel count: %d\n", player->sfinfo.channels); + sf_close(player->sndfile); + player->sndfile = NULL; + return 0; + } + + frame_size = (size_t)(BUFFER_SAMPLES * player->sfinfo.channels) * sizeof(float); + player->membuf = malloc(frame_size); + + return 1; +} + +static int StartPlayer(StreamPlayer *player) +{ + ALsizei i; + + alSourceRewind(player->source); + alSourcei(player->source, AL_BUFFER, 0); + + for(i = 0;i < NUM_BUFFERS;i++) + { + sf_count_t slen = sf_readf_float(player->sndfile, player->membuf, BUFFER_SAMPLES); + if(slen < 1) break; + + slen *= player->sfinfo.channels * (sf_count_t)sizeof(float); + alBufferData(player->buffers[i], player->format, player->membuf, (ALsizei)slen, + player->sfinfo.samplerate); + } + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error buffering for playback\n"); + return 0; + } + + alSourceQueueBuffers(player->source, i, player->buffers); + alSourcePlay(player->source); + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error starting playback\n"); + return 0; + } + + return 1; +} + +static int UpdatePlayer(StreamPlayer *player) +{ + ALint processed, state; + + alGetSourcei(player->source, AL_SOURCE_STATE, &state); + alGetSourcei(player->source, AL_BUFFERS_PROCESSED, &processed); + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error checking source state\n"); + return 0; + } + + while(processed > 0) + { + ALuint bufid; + sf_count_t slen; + + alSourceUnqueueBuffers(player->source, 1, &bufid); + processed--; + + slen = sf_readf_float(player->sndfile, player->membuf, BUFFER_SAMPLES); + if(slen > 0) + { + slen *= player->sfinfo.channels * (sf_count_t)sizeof(float); + alBufferData(bufid, player->format, player->membuf, (ALsizei)slen, + player->sfinfo.samplerate); + alSourceQueueBuffers(player->source, 1, &bufid); + } + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error buffering data\n"); + return 0; + } + } + + if(state != AL_PLAYING && state != AL_PAUSED) + { + ALint queued; + + alGetSourcei(player->source, AL_BUFFERS_QUEUED, &queued); + if(queued == 0) + return 0; + + alSourcePlay(player->source); + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error restarting playback\n"); + return 0; + } + } + + return 1; +} + + +/* CreateEffect creates a new OpenAL effect object with a convolution reverb + * type, and returns the new effect ID. + */ +static ALuint CreateEffect(void) +{ + ALuint effect = 0; + ALenum err; + + printf("Using Convolution Reverb\n"); + + /* Create the effect object and set the convolution reverb effect type. */ + alGenEffects(1, &effect); + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_CONVOLUTION_REVERB_SOFT); + + /* Check if an error occured, and clean up if so. */ + err = alGetError(); + if(err != AL_NO_ERROR) + { + fprintf(stderr, "OpenAL error: %s\n", alGetString(err)); + if(alIsEffect(effect)) + alDeleteEffects(1, &effect); + return 0; + } + + return effect; +} + +/* LoadBuffer loads the named audio file into an OpenAL buffer object, and + * returns the new buffer ID. + */ +static ALuint LoadSound(const char *filename) +{ + const char *namepart; + ALenum err, format; + ALuint buffer; + SNDFILE *sndfile; + SF_INFO sfinfo; + float *membuf; + sf_count_t num_frames; + ALsizei num_bytes; + + /* Open the audio file and check that it's usable. */ + sndfile = sf_open(filename, SFM_READ, &sfinfo); + if(!sndfile) + { + fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile)); + return 0; + } + if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(float))/sfinfo.channels) + { + fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames); + sf_close(sndfile); + return 0; + } + + /* Get the sound format, and figure out the OpenAL format. Use floats since + * impulse responses will usually have more than 16-bit precision. + */ + format = AL_NONE; + if(sfinfo.channels == 1) + format = AL_FORMAT_MONO_FLOAT32; + else if(sfinfo.channels == 2) + format = AL_FORMAT_STEREO_FLOAT32; + else if(sfinfo.channels == 3) + { + if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + format = AL_FORMAT_BFORMAT2D_FLOAT32; + } + else if(sfinfo.channels == 4) + { + if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + format = AL_FORMAT_BFORMAT3D_FLOAT32; + } + if(!format) + { + fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels); + sf_close(sndfile); + return 0; + } + + namepart = strrchr(filename, '/'); + if(namepart || (namepart=strrchr(filename, '\\'))) + namepart++; + else + namepart = filename; + printf("Loading: %s (%s, %dhz, %" PRId64 " samples / %.2f seconds)\n", namepart, + FormatName(format), sfinfo.samplerate, sfinfo.frames, + (double)sfinfo.frames / sfinfo.samplerate); + fflush(stdout); + + /* Decode the whole audio file to a buffer. */ + membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(float)); + + num_frames = sf_readf_float(sndfile, membuf, sfinfo.frames); + if(num_frames < 1) + { + free(membuf); + sf_close(sndfile); + fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames); + return 0; + } + num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(float); + + /* Buffer the audio data into a new buffer object, then free the data and + * close the file. + */ + buffer = 0; + alGenBuffers(1, &buffer); + alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate); + + free(membuf); + sf_close(sndfile); + + /* Check if an error occured, and clean up if so. */ + err = alGetError(); + if(err != AL_NO_ERROR) + { + fprintf(stderr, "OpenAL Error: %s\n", alGetString(err)); + if(buffer && alIsBuffer(buffer)) + alDeleteBuffers(1, &buffer); + return 0; + } + + return buffer; +} + + +int main(int argc, char **argv) +{ + ALuint ir_buffer, filter, effect, slot; + StreamPlayer *player; + int i; + + /* Print out usage if no arguments were specified */ + if(argc < 2) + { + fprintf(stderr, "Usage: %s [-device ] " + "<[-dry | -nodry] filename>...\n", argv[0]); + return 1; + } + + argv++; argc--; + if(InitAL(&argv, &argc) != 0) + return 1; + + if(!alIsExtensionPresent("AL_SOFTX_convolution_reverb")) + { + CloseAL(); + fprintf(stderr, "Error: Convolution revern not supported\n"); + return 1; + } + + if(argc < 2) + { + CloseAL(); + fprintf(stderr, "Error: Missing impulse response or sound files\n"); + return 1; + } + + /* Define a macro to help load the function pointers. */ +#define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) + LOAD_PROC(LPALGENFILTERS, alGenFilters); + LOAD_PROC(LPALDELETEFILTERS, alDeleteFilters); + LOAD_PROC(LPALISFILTER, alIsFilter); + LOAD_PROC(LPALFILTERI, alFilteri); + LOAD_PROC(LPALFILTERIV, alFilteriv); + LOAD_PROC(LPALFILTERF, alFilterf); + LOAD_PROC(LPALFILTERFV, alFilterfv); + LOAD_PROC(LPALGETFILTERI, alGetFilteri); + LOAD_PROC(LPALGETFILTERIV, alGetFilteriv); + LOAD_PROC(LPALGETFILTERF, alGetFilterf); + LOAD_PROC(LPALGETFILTERFV, alGetFilterfv); + + LOAD_PROC(LPALGENEFFECTS, alGenEffects); + LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects); + LOAD_PROC(LPALISEFFECT, alIsEffect); + LOAD_PROC(LPALEFFECTI, alEffecti); + LOAD_PROC(LPALEFFECTIV, alEffectiv); + LOAD_PROC(LPALEFFECTF, alEffectf); + LOAD_PROC(LPALEFFECTFV, alEffectfv); + LOAD_PROC(LPALGETEFFECTI, alGetEffecti); + LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv); + LOAD_PROC(LPALGETEFFECTF, alGetEffectf); + LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv); + + LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots); + LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots); + LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv); +#undef LOAD_PROC + + /* Load the reverb into an effect. */ + effect = CreateEffect(); + if(!effect) + { + CloseAL(); + return 1; + } + + /* Load the impulse response sound into a buffer. */ + ir_buffer = LoadSound(argv[0]); + if(!ir_buffer) + { + alDeleteEffects(1, &effect); + CloseAL(); + return 1; + } + + /* Create the effect slot object. This is what "plays" an effect on sources + * that connect to it. + */ + slot = 0; + alGenAuxiliaryEffectSlots(1, &slot); + + /* Set the impulse response sound buffer on the effect slot. This allows + * effects to access it as needed. In this case, convolution reverb uses it + * as the filter source. NOTE: Unlike the effect object, the buffer *is* + * kept referenced and may not be changed or deleted as long as it's set, + * just like with a source. When another buffer is set, or the effect slot + * is deleted, the buffer reference is released. + * + * The effect slot's gain is reduced because the impulse responses I've + * tested with result in excessively loud reverb. Is that normal? Even with + * this, it seems a bit on the loud side. + * + * Also note: unlike standard or EAX reverb, there is no automatic + * attenuation of a source's reverb response with distance, so the reverb + * will remain full volume regardless of a given sound's distance from the + * listener. You can use a send filter to alter a given source's + * contribution to reverb. + */ + alAuxiliaryEffectSloti(slot, AL_BUFFER, (ALint)ir_buffer); + alAuxiliaryEffectSlotf(slot, AL_EFFECTSLOT_GAIN, 1.0f / 16.0f); + alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, (ALint)effect); + assert(alGetError()==AL_NO_ERROR && "Failed to set effect slot"); + + /* Create a filter that can silence the dry path. */ + filter = 0; + alGenFilters(1, &filter); + alFilteri(filter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); + alFilterf(filter, AL_LOWPASS_GAIN, 0.0f); + + player = NewPlayer(); + /* Connect the player's source to the effect slot. */ + alSource3i(player->source, AL_AUXILIARY_SEND_FILTER, (ALint)slot, 0, AL_FILTER_NULL); + assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); + + /* Play each file listed on the command line */ + for(i = 1;i < argc;i++) + { + const char *namepart; + + if(argc-i > 1) + { + if(strcasecmp(argv[i], "-nodry") == 0) + { + alSourcei(player->source, AL_DIRECT_FILTER, (ALint)filter); + ++i; + } + else if(strcasecmp(argv[i], "-dry") == 0) + { + alSourcei(player->source, AL_DIRECT_FILTER, AL_FILTER_NULL); + ++i; + } + } + + if(!OpenPlayerFile(player, argv[i])) + continue; + + namepart = strrchr(argv[i], '/'); + if(namepart || (namepart=strrchr(argv[i], '\\'))) + namepart++; + else + namepart = argv[i]; + + printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->format), + player->sfinfo.samplerate); + fflush(stdout); + + if(!StartPlayer(player)) + { + ClosePlayerFile(player); + continue; + } + + while(UpdatePlayer(player)) + al_nssleep(10000000); + + ClosePlayerFile(player); + } + printf("Done.\n"); + + /* All files done. Delete the player and effect resources, and close down + * OpenAL. + */ + DeletePlayer(player); + player = NULL; + + alDeleteAuxiliaryEffectSlots(1, &slot); + alDeleteEffects(1, &effect); + alDeleteFilters(1, &filter); + alDeleteBuffers(1, &ir_buffer); + + CloseAL(); + + return 0; +} diff --git a/modules/openal-soft/examples/alffplay.cpp b/modules/openal-soft/examples/alffplay.cpp index 6298cb4..c3f4c50 100644 --- a/modules/openal-soft/examples/alffplay.cpp +++ b/modules/openal-soft/examples/alffplay.cpp @@ -1,36 +1,63 @@ /* * An example showing how to play a stream sync'd to video, using ffmpeg. * - * Requires C++11. + * Requires C++14. */ #include #include #include #include +#include #include +#include #include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include #include #include -#include +#include +#include +#include extern "C" { +#ifdef __GNUC__ +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Wconversion\"") +_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") +#endif #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libavformat/avio.h" -#include "libavutil/time.h" +#include "libavformat/version.h" +#include "libavutil/avutil.h" +#include "libavutil/error.h" +#include "libavutil/frame.h" +#include "libavutil/mem.h" #include "libavutil/pixfmt.h" -#include "libavutil/avstring.h" +#include "libavutil/rational.h" +#include "libavutil/samplefmt.h" +#include "libavutil/time.h" +#include "libavutil/version.h" #include "libavutil/channel_layout.h" #include "libswscale/swscale.h" #include "libswresample/swresample.h" + +constexpr auto AVNoPtsValue = AV_NOPTS_VALUE; +constexpr auto AVErrorEOF = AVERROR_EOF; + +struct SwsContext; +#ifdef __GNUC__ +_Pragma("GCC diagnostic pop") +#endif } #include "SDL.h" @@ -41,47 +68,6 @@ extern "C" { #include "common/alhelpers.h" -extern "C" { -/* Undefine this to disable use of experimental extensions. Don't use for - * production code! Interfaces and behavior may change prior to being - * finalized. - */ -#define ALLOW_EXPERIMENTAL_EXTS - -#ifdef ALLOW_EXPERIMENTAL_EXTS -#ifndef AL_SOFT_map_buffer -#define AL_SOFT_map_buffer 1 -typedef unsigned int ALbitfieldSOFT; -#define AL_MAP_READ_BIT_SOFT 0x00000001 -#define AL_MAP_WRITE_BIT_SOFT 0x00000002 -#define AL_MAP_PERSISTENT_BIT_SOFT 0x00000004 -#define AL_PRESERVE_DATA_BIT_SOFT 0x00000008 -typedef void (AL_APIENTRY*LPALBUFFERSTORAGESOFT)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags); -typedef void* (AL_APIENTRY*LPALMAPBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access); -typedef void (AL_APIENTRY*LPALUNMAPBUFFERSOFT)(ALuint buffer); -typedef void (AL_APIENTRY*LPALFLUSHMAPPEDBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length); -#endif - -#ifndef AL_SOFT_events -#define AL_SOFT_events 1 -#define AL_EVENT_CALLBACK_FUNCTION_SOFT 0x1220 -#define AL_EVENT_CALLBACK_USER_PARAM_SOFT 0x1221 -#define AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT 0x1222 -#define AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT 0x1223 -#define AL_EVENT_TYPE_ERROR_SOFT 0x1224 -#define AL_EVENT_TYPE_PERFORMANCE_SOFT 0x1225 -#define AL_EVENT_TYPE_DEPRECATED_SOFT 0x1226 -#define AL_EVENT_TYPE_DISCONNECTED_SOFT 0x1227 -typedef void (AL_APIENTRY*ALEVENTPROCSOFT)(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message, - void *userParam); -typedef void (AL_APIENTRY*LPALEVENTCONTROLSOFT)(ALsizei count, const ALenum *types, ALboolean enable); -typedef void (AL_APIENTRY*LPALEVENTCALLBACKSOFT)(ALEVENTPROCSOFT callback, void *userParam); -typedef void* (AL_APIENTRY*LPALGETPOINTERSOFT)(ALenum pname); -typedef void (AL_APIENTRY*LPALGETPOINTERVSOFT)(ALenum pname, void **values); -#endif -#endif /* ALLOW_EXPERIMENTAL_EXTS */ -} namespace { @@ -97,29 +83,27 @@ using microseconds = std::chrono::microseconds; using milliseconds = std::chrono::milliseconds; using seconds = std::chrono::seconds; using seconds_d64 = std::chrono::duration; +using std::chrono::duration_cast; const std::string AppName{"alffplay"}; -bool EnableDirectOut{false}; +ALenum DirectOutMode{AL_FALSE}; bool EnableWideStereo{false}; +bool EnableSuperStereo{false}; +bool DisableVideo{false}; LPALGETSOURCEI64VSOFT alGetSourcei64vSOFT; LPALCGETINTEGER64VSOFT alcGetInteger64vSOFT; - -#ifdef AL_SOFT_map_buffer -LPALBUFFERSTORAGESOFT alBufferStorageSOFT; -LPALMAPBUFFERSOFT alMapBufferSOFT; -LPALUNMAPBUFFERSOFT alUnmapBufferSOFT; -#endif - -#ifdef AL_SOFT_events LPALEVENTCONTROLSOFT alEventControlSOFT; LPALEVENTCALLBACKSOFT alEventCallbackSOFT; -#endif + +LPALBUFFERCALLBACKSOFT alBufferCallbackSOFT; +ALenum FormatStereo8{AL_FORMAT_STEREO8}; +ALenum FormatStereo16{AL_FORMAT_STEREO16}; +ALenum FormatStereo32F{AL_FORMAT_STEREO_FLOAT32}; const seconds AVNoSyncThreshold{10}; -const milliseconds VideoSyncThreshold(10); -#define VIDEO_PICTURE_QUEUE_SIZE 16 +#define VIDEO_PICTURE_QUEUE_SIZE 24 const seconds_d64 AudioSyncThreshold{0.03}; const milliseconds AudioSampleCorrectionMax{50}; @@ -127,16 +111,13 @@ const milliseconds AudioSampleCorrectionMax{50}; #define AUDIO_DIFF_AVG_NB 20 const double AudioAvgFilterCoeff{std::pow(0.01, 1.0/AUDIO_DIFF_AVG_NB)}; /* Per-buffer size, in time */ -const milliseconds AudioBufferTime{20}; +constexpr milliseconds AudioBufferTime{20}; /* Buffer total size, in time (should be divisible by the buffer time) */ -const milliseconds AudioBufferTotalTime{800}; - -#define MAX_QUEUE_SIZE (15 * 1024 * 1024) /* Bytes of compressed data to keep queued */ +constexpr milliseconds AudioBufferTotalTime{800}; +constexpr auto AudioBufferCount = AudioBufferTotalTime / AudioBufferTime; enum { - FF_UPDATE_EVENT = SDL_USEREVENT, - FF_REFRESH_EVENT, - FF_MOVIE_DONE_EVENT + FF_MOVIE_DONE_EVENT = SDL_USEREVENT }; enum class SyncMaster { @@ -144,7 +125,7 @@ enum class SyncMaster { Video, External, - Default = External + Default = Audio }; @@ -167,6 +148,11 @@ struct AVCodecCtxDeleter { }; using AVCodecCtxPtr = std::unique_ptr; +struct AVPacketDeleter { + void operator()(AVPacket *pkt) { av_packet_free(&pkt); } +}; +using AVPacketPtr = std::unique_ptr; + struct AVFrameDeleter { void operator()(AVFrame *ptr) { av_frame_free(&ptr); } }; @@ -183,42 +169,105 @@ struct SwsContextDeleter { using SwsContextPtr = std::unique_ptr; -class PacketQueue { - std::deque mPackets; +template +class DataQueue { + std::mutex mPacketMutex, mFrameMutex; + std::condition_variable mPacketCond; + std::condition_variable mInFrameCond, mOutFrameCond; + + std::deque mPackets; size_t mTotalSize{0}; + bool mFinished{false}; + + AVPacketPtr getPacket() + { + std::unique_lock plock{mPacketMutex}; + while(mPackets.empty() && !mFinished) + mPacketCond.wait(plock); + if(mPackets.empty()) + return nullptr; + + auto ret = std::move(mPackets.front()); + mPackets.pop_front(); + mTotalSize -= static_cast(ret->size); + return ret; + } public: - ~PacketQueue() { clear(); } + int sendPacket(AVCodecContext *codecctx) + { + AVPacketPtr packet{getPacket()}; - bool empty() const noexcept { return mPackets.empty(); } - size_t totalSize() const noexcept { return mTotalSize; } + int ret{}; + { + std::unique_lock flock{mFrameMutex}; + while((ret=avcodec_send_packet(codecctx, packet.get())) == AVERROR(EAGAIN)) + mInFrameCond.wait_for(flock, milliseconds{50}); + } + mOutFrameCond.notify_one(); - void put(const AVPacket *pkt) + 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}); + } + mInFrameCond.notify_one(); + return ret; } - AVPacket *front() noexcept - { return &mPackets.front(); } + void setFinished() + { + { + std::lock_guard _{mPacketMutex}; + mFinished = true; + } + mPacketCond.notify_one(); + } - void pop() + void flush() { - AVPacket *pkt = &mPackets.front(); - mTotalSize -= pkt->size; - av_packet_unref(pkt); - mPackets.pop_front(); + { + std::lock_guard _{mPacketMutex}; + mFinished = true; + + mPackets.clear(); + mTotalSize = 0; + } + mPacketCond.notify_one(); } - void clear() + bool put(const AVPacket *pkt) { - for(AVPacket &pkt : mPackets) - av_packet_unref(&pkt); - mPackets.clear(); - mTotalSize = 0; + { + std::unique_lock lock{mPacketMutex}; + if(mTotalSize >= SizeLimit || mFinished) + return false; + + mPackets.push_back(AVPacketPtr{av_packet_alloc()}); + if(av_packet_ref(mPackets.back().get(), pkt) != 0) + { + mPackets.pop_back(); + return true; + } + + mTotalSize += static_cast(mPackets.back()->size); + } + mPacketCond.notify_one(); + return true; } }; @@ -231,8 +280,7 @@ struct AudioState { AVStream *mStream{nullptr}; AVCodecCtxPtr mCodecCtx; - std::mutex mQueueMtx; - std::condition_variable mQueueCond; + DataQueue<2*1024*1024> mQueue; /* Used for clock difference average computation */ seconds_d64 mClockDiffAvg{0}; @@ -248,7 +296,7 @@ struct AudioState { SwrContextPtr mSwresCtx; /* Conversion format, for what gets fed to OpenAL */ - int mDstChanLayout{0}; + uint64_t mDstChanLayout{0}; AVSampleFormat mDstSampleFmt{AV_SAMPLE_FMT_NONE}; /* Storage of converted samples */ @@ -257,17 +305,21 @@ struct AudioState { int mSamplesPos{0}; int mSamplesMax{0}; + std::unique_ptr mBufferData; + size_t mBufferDataSize{0}; + std::atomic mReadPos{0}; + std::atomic mWritePos{0}; + /* OpenAL format */ ALenum mFormat{AL_NONE}; - ALsizei mFrameSize{0}; + ALuint mFrameSize{0}; std::mutex mSrcMutex; std::condition_variable mSrcCond; std::atomic_flag mConnected; - std::atomic mPrepared{false}; ALuint mSource{0}; - std::vector mBuffers; - ALsizei mBufferIdx{0}; + std::array mBuffers{}; + ALuint mBufferIdx{0}; AudioState(MovieState &movie) : mMovie(movie) { mConnected.test_and_set(std::memory_order_relaxed); } @@ -275,17 +327,21 @@ struct AudioState { { if(mSource) alDeleteSources(1, &mSource); - if(!mBuffers.empty()) - alDeleteBuffers(mBuffers.size(), mBuffers.data()); + if(mBuffers[0]) + alDeleteBuffers(static_cast(mBuffers.size()), mBuffers.data()); av_freep(&mSamples); } -#ifdef AL_SOFT_events - static void AL_APIENTRY EventCallback(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message, - void *userParam); -#endif + static void AL_APIENTRY eventCallbackC(ALenum eventType, ALuint object, ALuint param, + ALsizei length, const ALchar *message, void *userParam) + { static_cast(userParam)->eventCallback(eventType, object, param, length, message); } + void eventCallback(ALenum eventType, ALuint object, ALuint param, ALsizei length, + const ALchar *message); + + static ALsizei AL_APIENTRY bufferCallbackC(void *userptr, void *data, ALsizei size) + { return static_cast(userptr)->bufferCallback(data, size); } + ALsizei bufferCallback(void *data, ALsizei size); nanoseconds getClockNoLock(); nanoseconds getClock() @@ -294,12 +350,12 @@ struct AudioState { return getClockNoLock(); } - bool isBufferFilled() const { return mPrepared.load(); } - void startPlayback(); + bool startPlayback(); int getSync(); int decodeFrame(); - bool readAudio(uint8_t *samples, int length); + bool readAudio(uint8_t *samples, unsigned int length, int &sample_skip); + bool readAudio(int sample_skip); int handler(); }; @@ -310,53 +366,46 @@ struct VideoState { AVStream *mStream{nullptr}; AVCodecCtxPtr mCodecCtx; - std::mutex mQueueMtx; - std::condition_variable mQueueCond; + DataQueue<14*1024*1024> mQueue; - nanoseconds mClock{0}; - nanoseconds mFrameTimer{0}; - nanoseconds mFrameLastPts{0}; - nanoseconds mFrameLastDelay{0}; - nanoseconds mCurrentPts{0}; - /* time (av_gettime) at which we updated mCurrentPts - used to have running video pts */ - microseconds mCurrentPtsTime{0}; + /* The pts of the currently displayed frame, and the time (av_gettime) it + * was last updated - used to have running video pts + */ + nanoseconds mDisplayPts{0}; + microseconds mDisplayPtsTime{microseconds::min()}; + std::mutex mDispPtsMutex; - /* Decompressed video frame, and swscale context for conversion */ - AVFramePtr mDecodedFrame; + /* Swscale context for format conversion */ SwsContextPtr mSwscaleCtx; struct Picture { - SDL_Texture *mImage{nullptr}; - int mWidth{0}, mHeight{0}; /* Logical image size (actual size may be larger) */ - std::atomic mUpdated{false}; - nanoseconds mPts{0}; - - ~Picture() - { - if(mImage) - SDL_DestroyTexture(mImage); - mImage = nullptr; - } + AVFramePtr mFrame{}; + nanoseconds mPts{nanoseconds::min()}; }; std::array mPictQ; - size_t mPictQSize{0}, mPictQRead{0}, mPictQWrite{0}; + std::atomic mPictQRead{0u}, mPictQWrite{1u}; std::mutex mPictQMutex; std::condition_variable mPictQCond; + + SDL_Texture *mImage{nullptr}; + int mWidth{0}, mHeight{0}; /* Full texture size */ bool mFirstUpdate{true}; + std::atomic mEOS{false}; std::atomic mFinalUpdate{false}; VideoState(MovieState &movie) : mMovie(movie) { } + ~VideoState() + { + if(mImage) + SDL_DestroyTexture(mImage); + mImage = nullptr; + } nanoseconds getClock(); - bool isBufferFilled(); - - static Uint32 SDLCALL sdl_refresh_timer_cb(Uint32 interval, void *opaque); - void schedRefresh(milliseconds delay); - void display(SDL_Window *screen, SDL_Renderer *renderer); - void refreshTimer(SDL_Window *screen, SDL_Renderer *renderer); - void updatePicture(SDL_Window *screen, SDL_Renderer *renderer); - int queuePicture(nanoseconds pts); + + void display(SDL_Window *screen, SDL_Renderer *renderer, AVFrame *frame); + void updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool redraw); int handler(); }; @@ -366,19 +415,17 @@ struct MovieState { SyncMaster mAVSyncType{SyncMaster::Default}; - microseconds mClockBase{0}; - std::atomic mPlaying{false}; - - std::mutex mSendMtx; - std::condition_variable mSendCond; - /* NOTE: false/clear = need data, true/set = no data needed */ - std::atomic_flag mSendDataGood; + microseconds mClockBase{microseconds::min()}; std::atomic mQuit{false}; AudioState mAudio; VideoState mVideo; + std::mutex mStartupMutex; + std::condition_variable mStartupCond; + bool mStartupDone{false}; + std::thread mParseThread; std::thread mAudioThread; std::thread mVideoThread; @@ -390,7 +437,7 @@ struct MovieState { { } ~MovieState() { - mQuit = true; + stop(); if(mParseThread.joinable()) mParseThread.join(); } @@ -398,6 +445,7 @@ struct MovieState { static int decode_interrupt_cb(void *ctx); bool prepare(); void setTitle(SDL_Window *window); + void stop(); nanoseconds getClock(); @@ -405,7 +453,7 @@ struct MovieState { nanoseconds getDuration(); - int streamComponentOpen(int stream_index); + int streamComponentOpen(unsigned int stream_index); int parse_handler(); }; @@ -429,7 +477,54 @@ nanoseconds AudioState::getClockNoLock() // The clock is simply the current device time relative to the recorded // start time. We can also subtract the latency to get more a accurate // position of where the audio device actually is in the output stream. - std::max(device_time - mDeviceStartTime - latency, nanoseconds::zero()); + return device_time - mDeviceStartTime - latency; + } + + if(mBufferDataSize > 0) + { + if(mDeviceStartTime == nanoseconds::min()) + return nanoseconds::zero(); + + /* With a callback buffer and no device clock, mDeviceStartTime is + * actually the timestamp of the first sample frame played. The audio + * clock, then, is that plus the current source offset. + */ + ALint64SOFT offset[2]; + if(alGetSourcei64vSOFT) + alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset); + else + { + ALint ioffset; + alGetSourcei(mSource, AL_SAMPLE_OFFSET, &ioffset); + offset[0] = ALint64SOFT{ioffset} << 32; + offset[1] = 0; + } + /* NOTE: The source state must be checked last, in case an underrun + * occurs and the source stops between getting the state and retrieving + * the offset+latency. + */ + ALint status; + alGetSourcei(mSource, AL_SOURCE_STATE, &status); + + nanoseconds pts{}; + if(status == AL_PLAYING || status == AL_PAUSED) + pts = mDeviceStartTime - nanoseconds{offset[1]} + + duration_cast(fixed32{offset[0] / mCodecCtx->sample_rate}); + else + { + /* If the source is stopped, the pts of the next sample to be heard + * is the pts of the next sample to be buffered, minus the amount + * already in the buffer ready to play. + */ + const size_t woffset{mWritePos.load(std::memory_order_acquire)}; + const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; + const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) - + roffset}; + + pts = mCurrentPts - nanoseconds{seconds{readable/mFrameSize}}/mCodecCtx->sample_rate; + } + + return pts; } /* The source-based clock is based on 4 components: @@ -451,10 +546,6 @@ nanoseconds AudioState::getClockNoLock() if(mSource) { ALint64SOFT offset[2]; - - /* NOTE: The source state must be checked last, in case an underrun - * occurs and the source stops between retrieving the offset+latency - * and getting the state. */ if(alGetSourcei64vSOFT) alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset); else @@ -471,12 +562,12 @@ nanoseconds AudioState::getClockNoLock() /* If the source is AL_STOPPED, then there was an underrun and all * buffers are processed, so ignore the source queue. The audio thread * will put the source into an AL_INITIAL state and clear the queue - * when it starts recovery. */ + * when it starts recovery. + */ if(status != AL_STOPPED) { pts -= AudioBufferTime*queued; - pts += std::chrono::duration_cast( - fixed32{offset[0] / mCodecCtx->sample_rate}); + pts += duration_cast(fixed32{offset[0] / mCodecCtx->sample_rate}); } /* Don't offset by the latency if the source isn't playing. */ if(status == AL_PLAYING) @@ -486,27 +577,59 @@ nanoseconds AudioState::getClockNoLock() return std::max(pts, nanoseconds::zero()); } -void AudioState::startPlayback() +bool AudioState::startPlayback() { + const size_t woffset{mWritePos.load(std::memory_order_acquire)}; + const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; + const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) - + roffset}; + + if(mBufferDataSize > 0) + { + if(readable == 0) + return false; + if(!alcGetInteger64vSOFT) + mDeviceStartTime = mCurrentPts - + nanoseconds{seconds{readable/mFrameSize}}/mCodecCtx->sample_rate; + } + else + { + ALint queued{}; + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + if(queued == 0) return false; + } + alSourcePlay(mSource); if(alcGetInteger64vSOFT) { - // Subtract the total buffer queue time from the current pts to get the - // pts of the start of the queue. - nanoseconds startpts{mCurrentPts - AudioBufferTotalTime}; + /* Subtract the total buffer queue time from the current pts to get the + * pts of the start of the queue. + */ int64_t srctimes[2]{0,0}; alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_CLOCK_SOFT, srctimes); auto device_time = nanoseconds{srctimes[1]}; - auto src_offset = std::chrono::duration_cast(fixed32{srctimes[0]}) / + auto src_offset = duration_cast(fixed32{srctimes[0]}) / mCodecCtx->sample_rate; - // The mixer may have ticked and incremented the device time and sample - // offset, so subtract the source offset from the device time to get - // the device time the source started at. Also subtract startpts to get - // the device time the stream would have started at to reach where it - // is now. - mDeviceStartTime = device_time - src_offset - startpts; + /* The mixer may have ticked and incremented the device time and sample + * offset, so subtract the source offset from the device time to get + * the device time the source started at. Also subtract startpts to get + * the device time the stream would have started at to reach where it + * is now. + */ + if(mBufferDataSize > 0) + { + nanoseconds startpts{mCurrentPts - + nanoseconds{seconds{readable/mFrameSize}}/mCodecCtx->sample_rate}; + mDeviceStartTime = device_time - src_offset - startpts; + } + else + { + nanoseconds startpts{mCurrentPts - AudioBufferTotalTime}; + mDeviceStartTime = device_time - src_offset - startpts; + } } + return true; } int AudioState::getSync() @@ -531,84 +654,55 @@ int AudioState::getSync() return 0; /* Constrain the per-update difference to avoid exceedingly large skips */ - diff = std::min(std::max(diff, -AudioSampleCorrectionMax), - AudioSampleCorrectionMax); - return static_cast(std::chrono::duration_cast(diff*mCodecCtx->sample_rate).count()); + diff = std::min(diff, AudioSampleCorrectionMax); + return static_cast(duration_cast(diff*mCodecCtx->sample_rate).count()); } int AudioState::decodeFrame() { - while(!mMovie.mQuit.load(std::memory_order_relaxed)) - { - std::unique_lock lock(mQueueMtx); - int ret = avcodec_receive_frame(mCodecCtx.get(), mDecodedFrame.get()); - if(ret == AVERROR(EAGAIN)) - { - mMovie.mSendDataGood.clear(std::memory_order_relaxed); - std::unique_lock(mMovie.mSendMtx).unlock(); - mMovie.mSendCond.notify_one(); - do { - mQueueCond.wait(lock); - ret = avcodec_receive_frame(mCodecCtx.get(), mDecodedFrame.get()); - } while(ret == AVERROR(EAGAIN)); - } - lock.unlock(); - if(ret == AVERROR_EOF) break; - mMovie.mSendDataGood.clear(std::memory_order_relaxed); - mMovie.mSendCond.notify_one(); - if(ret < 0) - { - std::cerr<< "Failed to decode frame: "<nb_samples <= 0) + do { + while(int ret{mQueue.receiveFrame(mCodecCtx.get(), mDecodedFrame.get())}) { - av_frame_unref(mDecodedFrame.get()); - continue; + if(ret == AVErrorEOF) return 0; + std::cerr<< "Failed to receive frame: "<nb_samples <= 0); - /* If provided, update w/ pts */ - if(mDecodedFrame->best_effort_timestamp != AV_NOPTS_VALUE) - mCurrentPts = std::chrono::duration_cast( - seconds_d64(av_q2d(mStream->time_base)*mDecodedFrame->best_effort_timestamp) - ); + /* If provided, update w/ pts */ + if(mDecodedFrame->best_effort_timestamp != AVNoPtsValue) + mCurrentPts = duration_cast(seconds_d64{av_q2d(mStream->time_base) * + static_cast(mDecodedFrame->best_effort_timestamp)}); - if(mDecodedFrame->nb_samples > mSamplesMax) - { - av_freep(&mSamples); - av_samples_alloc( - &mSamples, nullptr, mCodecCtx->channels, - mDecodedFrame->nb_samples, mDstSampleFmt, 0 - ); - mSamplesMax = mDecodedFrame->nb_samples; - } - /* Return the amount of sample frames converted */ - int data_size{swr_convert(mSwresCtx.get(), &mSamples, mDecodedFrame->nb_samples, - const_cast(mDecodedFrame->data), mDecodedFrame->nb_samples)}; - - av_frame_unref(mDecodedFrame.get()); - return data_size; + if(mDecodedFrame->nb_samples > mSamplesMax) + { + av_freep(&mSamples); + av_samples_alloc(&mSamples, nullptr, mCodecCtx->channels, mDecodedFrame->nb_samples, + mDstSampleFmt, 0); + mSamplesMax = mDecodedFrame->nb_samples; } + /* Return the amount of sample frames converted */ + int data_size{swr_convert(mSwresCtx.get(), &mSamples, mDecodedFrame->nb_samples, + const_cast(mDecodedFrame->data), mDecodedFrame->nb_samples)}; - return 0; + av_frame_unref(mDecodedFrame.get()); + return data_size; } /* Duplicates the sample at in to out, count times. The frame size is a * multiple of the template type size. */ template -static void sample_dup(uint8_t *out, const uint8_t *in, int count, int frame_size) +static void sample_dup(uint8_t *out, const uint8_t *in, size_t count, size_t frame_size) { - const T *sample = reinterpret_cast(in); - T *dst = reinterpret_cast(out); + auto *sample = reinterpret_cast(in); + auto *dst = reinterpret_cast(out); if(frame_size == sizeof(T)) std::fill_n(dst, count, *sample); else { /* NOTE: frame_size is a multiple of sizeof(T). */ - int type_mult = frame_size / sizeof(T); - int i = 0; + size_t type_mult{frame_size / sizeof(T)}; + size_t i{0}; std::generate_n(dst, count*type_mult, [sample,type_mult,&i]() -> T { @@ -621,44 +715,26 @@ static void sample_dup(uint8_t *out, const uint8_t *in, int count, int frame_siz } -bool AudioState::readAudio(uint8_t *samples, int length) +bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_skip) { - int sample_skip = getSync(); - int audio_size = 0; + unsigned int audio_size{0}; /* Read the next chunk of data, refill the buffer, and queue it * on the source */ length /= mFrameSize; - while(audio_size < length) + while(mSamplesLen > 0 && audio_size < length) { - if(mSamplesLen <= 0 || mSamplesPos >= mSamplesLen) - { - int frame_len = decodeFrame(); - if(frame_len <= 0) break; - - mSamplesLen = frame_len; - mSamplesPos = std::min(mSamplesLen, sample_skip); - sample_skip -= mSamplesPos; - - // Adjust the device start time and current pts by the amount we're - // skipping/duplicating, so that the clock remains correct for the - // current stream position. - auto skip = nanoseconds(seconds(mSamplesPos)) / mCodecCtx->sample_rate; - mDeviceStartTime -= skip; - mCurrentPts += skip; - continue; - } - - int rem = length - audio_size; + unsigned int rem{length - audio_size}; if(mSamplesPos >= 0) { - int len = mSamplesLen - mSamplesPos; + const auto len = static_cast(mSamplesLen - mSamplesPos); if(rem > len) rem = len; - memcpy(samples, mSamples + mSamplesPos*mFrameSize, rem*mFrameSize); + std::copy_n(mSamples + static_cast(mSamplesPos)*mFrameSize, + rem*mFrameSize, samples); } else { - rem = std::min(rem, -mSamplesPos); + rem = std::min(rem, static_cast(-mSamplesPos)); /* Add samples by copying the first sample */ if((mFrameSize&7) == 0) @@ -672,115 +748,263 @@ bool AudioState::readAudio(uint8_t *samples, int length) } mSamplesPos += rem; - mCurrentPts += nanoseconds(seconds(rem)) / mCodecCtx->sample_rate; + mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; samples += rem*mFrameSize; audio_size += rem; + + while(mSamplesPos >= mSamplesLen) + { + mSamplesLen = decodeFrame(); + mSamplesPos = std::min(mSamplesLen, sample_skip); + if(mSamplesLen <= 0) break; + + sample_skip -= mSamplesPos; + + // Adjust the device start time and current pts by the amount we're + // skipping/duplicating, so that the clock remains correct for the + // current stream position. + auto skip = nanoseconds{seconds{mSamplesPos}} / mCodecCtx->sample_rate; + mDeviceStartTime -= skip; + mCurrentPts += skip; + continue; + } } if(audio_size <= 0) return false; if(audio_size < length) { - int rem = length - audio_size; + const unsigned int rem{length - audio_size}; std::fill_n(samples, rem*mFrameSize, - (mDstSampleFmt == AV_SAMPLE_FMT_U8) ? 0x80 : 0x00); - mCurrentPts += nanoseconds(seconds(rem)) / mCodecCtx->sample_rate; - audio_size += rem; + (mDstSampleFmt == AV_SAMPLE_FMT_U8) ? 0x80 : 0x00); + mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; } return true; } - -#ifdef AL_SOFT_events -void AL_APIENTRY AudioState::EventCallback(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message, - void *userParam) +bool AudioState::readAudio(int sample_skip) { - AudioState *self = reinterpret_cast(userParam); + size_t woffset{mWritePos.load(std::memory_order_acquire)}; + while(mSamplesLen > 0) + { + const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; + + if(mSamplesPos < 0) + { + size_t rem{(((roffset > woffset) ? roffset-1 + : ((roffset == 0) ? mBufferDataSize-1 + : mBufferDataSize)) - woffset) / mFrameSize}; + rem = std::min(rem, static_cast(-mSamplesPos)); + if(rem == 0) break; + auto *splout{&mBufferData[woffset]}; + if((mFrameSize&7) == 0) + sample_dup(splout, mSamples, rem, mFrameSize); + else if((mFrameSize&3) == 0) + sample_dup(splout, mSamples, rem, mFrameSize); + else if((mFrameSize&1) == 0) + sample_dup(splout, mSamples, rem, mFrameSize); + else + sample_dup(splout, mSamples, rem, mFrameSize); + woffset += rem * mFrameSize; + if(woffset == mBufferDataSize) + woffset = 0; + mWritePos.store(woffset, std::memory_order_release); + mSamplesPos += static_cast(rem); + mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; + continue; + } + + const size_t boffset{static_cast(mSamplesPos) * size_t{mFrameSize}}; + const size_t nbytes{static_cast(mSamplesLen)*size_t{mFrameSize} - + boffset}; + if(roffset > woffset) + { + const size_t writable{roffset-woffset-1}; + if(writable < nbytes) break; + + memcpy(&mBufferData[woffset], mSamples+boffset, nbytes); + woffset += nbytes; + } + else + { + const size_t writable{mBufferDataSize+roffset-woffset-1}; + if(writable < nbytes) break; + + const size_t todo1{std::min(nbytes, mBufferDataSize-woffset)}; + const size_t todo2{nbytes - todo1}; + + memcpy(&mBufferData[woffset], mSamples+boffset, todo1); + woffset += todo1; + if(woffset == mBufferDataSize) + { + woffset = 0; + if(todo2 > 0) + { + memcpy(&mBufferData[woffset], mSamples+boffset+todo1, todo2); + woffset += todo2; + } + } + } + mWritePos.store(woffset, std::memory_order_release); + mCurrentPts += nanoseconds{seconds{mSamplesLen-mSamplesPos}} / mCodecCtx->sample_rate; + + do { + mSamplesLen = decodeFrame(); + mSamplesPos = std::min(mSamplesLen, sample_skip); + if(mSamplesLen <= 0) return false; + + sample_skip -= mSamplesPos; + + auto skip = nanoseconds{seconds{mSamplesPos}} / mCodecCtx->sample_rate; + mDeviceStartTime -= skip; + mCurrentPts += skip; + } while(mSamplesPos >= mSamplesLen); + } + + return true; +} + + +void AL_APIENTRY AudioState::eventCallback(ALenum eventType, ALuint object, ALuint param, + ALsizei length, const ALchar *message) +{ if(eventType == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT) { /* Temporarily lock the source mutex to ensure it's not between * checking the processed count and going to sleep. */ - std::unique_lock(self->mSrcMutex).unlock(); - self->mSrcCond.notify_one(); + std::unique_lock{mSrcMutex}.unlock(); + mSrcCond.notify_one(); return; } - std::cout<< "\n---- AL Event on AudioState "<(length)}<<"\n----"<< std::endl; if(eventType == AL_EVENT_TYPE_DISCONNECTED_SOFT) { - { std::lock_guard lock(self->mSrcMutex); - self->mConnected.clear(std::memory_order_release); + { + std::lock_guard lock{mSrcMutex}; + mConnected.clear(std::memory_order_release); } - std::unique_lock(self->mSrcMutex).unlock(); - self->mSrcCond.notify_one(); + mSrcCond.notify_one(); } } -#endif -int AudioState::handler() +ALsizei AudioState::bufferCallback(void *data, ALsizei size) { - std::unique_lock srclock(mSrcMutex); - milliseconds sleep_time = AudioBufferTime / 3; - ALenum fmt; - -#ifdef AL_SOFT_events - const std::array evt_types{{ - AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, - AL_EVENT_TYPE_ERROR_SOFT, AL_EVENT_TYPE_PERFORMANCE_SOFT, AL_EVENT_TYPE_DEPRECATED_SOFT, - AL_EVENT_TYPE_DISCONNECTED_SOFT - }}; - if(alEventControlSOFT) - { - alEventControlSOFT(evt_types.size(), evt_types.data(), AL_TRUE); - alEventCallbackSOFT(EventCallback, this); - sleep_time = AudioBufferTotalTime; + ALsizei got{0}; + + size_t roffset{mReadPos.load(std::memory_order_acquire)}; + while(got < size) + { + const size_t woffset{mWritePos.load(std::memory_order_relaxed)}; + if(woffset == roffset) break; + + size_t todo{((woffset < roffset) ? mBufferDataSize : woffset) - roffset}; + todo = std::min(todo, static_cast(size-got)); + + memcpy(data, &mBufferData[roffset], todo); + data = static_cast(data) + todo; + got += static_cast(todo); + + roffset += todo; + if(roffset == mBufferDataSize) + roffset = 0; } -#endif + mReadPos.store(roffset, std::memory_order_release); + + return got; +} + +int AudioState::handler() +{ + std::unique_lock srclock{mSrcMutex, std::defer_lock}; + milliseconds sleep_time{AudioBufferTime / 3}; + + struct EventControlManager { + const std::array evt_types{{ + AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, + AL_EVENT_TYPE_DISCONNECTED_SOFT}}; + + EventControlManager(milliseconds &sleep_time) + { + if(alEventControlSOFT) + { + alEventControlSOFT(static_cast(evt_types.size()), evt_types.data(), + AL_TRUE); + alEventCallbackSOFT(&AudioState::eventCallbackC, this); + sleep_time = AudioBufferTotalTime; + } + } + ~EventControlManager() + { + if(alEventControlSOFT) + { + alEventControlSOFT(static_cast(evt_types.size()), evt_types.data(), + AL_FALSE); + alEventCallbackSOFT(nullptr, nullptr); + } + } + }; + EventControlManager event_controller{sleep_time}; + + const bool has_bfmt_ex{alIsExtensionPresent("AL_SOFT_bformat_ex") != AL_FALSE}; + ALenum ambi_layout{AL_FUMA_SOFT}; + ALenum ambi_scale{AL_FUMA_SOFT}; + + std::unique_ptr samples; + ALsizei buffer_len{0}; /* Find a suitable format for OpenAL. */ mDstChanLayout = 0; mFormat = AL_NONE; - if((mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP) && - alIsExtensionPresent("AL_EXT_FLOAT32")) + if((mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP + || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_DBL + || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_DBLP + || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S32 + || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S32P + || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S64 + || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S64P) + && alIsExtensionPresent("AL_EXT_FLOAT32")) { mDstSampleFmt = AV_SAMPLE_FMT_FLT; mFrameSize = 4; - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1 && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_71CHN32")) != AL_NONE && fmt != -1) + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 8; - mFormat = fmt; - } - if((mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 || - mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_51CHN32")) != AL_NONE && fmt != -1) - { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 6; - mFormat = fmt; + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 8; + mFormat = alGetEnumValue("AL_FORMAT_71CHN32"); + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 + || mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 6; + mFormat = alGetEnumValue("AL_FORMAT_51CHN32"); + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_QUAD) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 4; + mFormat = alGetEnumValue("AL_FORMAT_QUAD32"); + } } if(mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) { @@ -793,48 +1017,57 @@ int AudioState::handler() * have no way to specify if the source is actually B-Format (let alone * if it's 2D or 3D). */ - if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 && - alIsExtensionPresent("AL_EXT_BFORMAT") && - (fmt=alGetEnumValue("AL_FORMAT_BFORMAT3D_FLOAT32")) != AL_NONE && fmt != -1) + if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 + && alIsExtensionPresent("AL_EXT_BFORMAT")) { - int order{static_cast(std::sqrt(mCodecCtx->channels)) - 1}; - if((order+1)*(order+1) == mCodecCtx->channels || - (order+1)*(order+1) + 2 == mCodecCtx->channels) + /* Calculate what should be the ambisonic order from the number of + * channels, and confirm that's the number of channels. Opus allows + * an optional non-diegetic stereo stream with the B-Format stream, + * which we can ignore, so check for that too. + */ + auto order = static_cast(std::sqrt(mCodecCtx->channels)) - 1; + int channels{(order+1) * (order+1)}; + if(channels == mCodecCtx->channels || channels+2 == mCodecCtx->channels) { /* OpenAL only supports first-order with AL_EXT_BFORMAT, which * is 4 channels for 3D buffers. */ mFrameSize *= 4; - mFormat = fmt; + mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_FLOAT32"); } } - if(!mFormat) + if(!mFormat || mFormat == -1) { mDstChanLayout = AV_CH_LAYOUT_STEREO; mFrameSize *= 2; - mFormat = AL_FORMAT_STEREO_FLOAT32; + mFormat = FormatStereo32F; } } if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8 || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P) { mDstSampleFmt = AV_SAMPLE_FMT_U8; mFrameSize = 1; - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1 && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_71CHN8")) != AL_NONE && fmt != -1) + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 8; - mFormat = fmt; - } - if((mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 || - mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_51CHN8")) != AL_NONE && fmt != -1) - { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 6; - mFormat = fmt; + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 8; + mFormat = alGetEnumValue("AL_FORMAT_71CHN8"); + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 + || mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 6; + mFormat = alGetEnumValue("AL_FORMAT_51CHN8"); + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_QUAD) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 4; + mFormat = alGetEnumValue("AL_FORMAT_QUAD8"); + } } if(mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) { @@ -842,45 +1075,49 @@ int AudioState::handler() mFrameSize *= 1; mFormat = AL_FORMAT_MONO8; } - if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 && - alIsExtensionPresent("AL_EXT_BFORMAT") && - (fmt=alGetEnumValue("AL_FORMAT_BFORMAT3D8")) != AL_NONE && fmt != -1) + if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 + && alIsExtensionPresent("AL_EXT_BFORMAT")) { - int order{static_cast(std::sqrt(mCodecCtx->channels)) - 1}; - if((order+1)*(order+1) == mCodecCtx->channels || - (order+1)*(order+1) + 2 == mCodecCtx->channels) + auto order = static_cast(std::sqrt(mCodecCtx->channels)) - 1; + int channels{(order+1) * (order+1)}; + if(channels == mCodecCtx->channels || channels+2 == mCodecCtx->channels) { mFrameSize *= 4; - mFormat = fmt; + mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_8"); } } - if(!mFormat) + if(!mFormat || mFormat == -1) { mDstChanLayout = AV_CH_LAYOUT_STEREO; mFrameSize *= 2; - mFormat = AL_FORMAT_STEREO8; + mFormat = FormatStereo8; } } - if(!mFormat) + if(!mFormat || mFormat == -1) { mDstSampleFmt = AV_SAMPLE_FMT_S16; mFrameSize = 2; - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1 && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_71CHN16")) != AL_NONE && fmt != -1) - { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 8; - mFormat = fmt; - } - if((mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 || - mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_51CHN16")) != AL_NONE && fmt != -1) + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 6; - mFormat = fmt; + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 8; + mFormat = alGetEnumValue("AL_FORMAT_71CHN16"); + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 + || mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 6; + mFormat = alGetEnumValue("AL_FORMAT_51CHN16"); + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_QUAD) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 4; + mFormat = alGetEnumValue("AL_FORMAT_QUAD16"); + } } if(mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) { @@ -888,28 +1125,24 @@ int AudioState::handler() mFrameSize *= 1; mFormat = AL_FORMAT_MONO16; } - if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 && - alIsExtensionPresent("AL_EXT_BFORMAT") && - (fmt=alGetEnumValue("AL_FORMAT_BFORMAT3D16")) != AL_NONE && fmt != -1) + if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 + && alIsExtensionPresent("AL_EXT_BFORMAT")) { - int order{static_cast(std::sqrt(mCodecCtx->channels)) - 1}; - if((order+1)*(order+1) == mCodecCtx->channels || - (order+1)*(order+1) + 2 == mCodecCtx->channels) + auto order = static_cast(std::sqrt(mCodecCtx->channels)) - 1; + int channels{(order+1) * (order+1)}; + if(channels == mCodecCtx->channels || channels+2 == mCodecCtx->channels) { mFrameSize *= 4; - mFormat = fmt; + mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_16"); } } - if(!mFormat) + if(!mFormat || mFormat == -1) { mDstChanLayout = AV_CH_LAYOUT_STEREO; mFrameSize *= 2; - mFormat = AL_FORMAT_STEREO16; + mFormat = FormatStereo16; } } - void *samples = nullptr; - ALsizei buffer_len = std::chrono::duration_cast>( - mCodecCtx->sample_rate * AudioBufferTime).count() * mFrameSize; mSamples = nullptr; mSamplesMax = 0; @@ -920,15 +1153,13 @@ int AudioState::handler() if(!mDecodedFrame) { std::cerr<< "Failed to allocate audio frame" <sample_rate, @@ -940,153 +1171,228 @@ int AudioState::handler() * defacto-standard. This is not true for .amb files, which use FuMa. */ std::vector mtx(64*64, 0.0); - mtx[0 + 0*64] = std::sqrt(0.5); - mtx[3 + 1*64] = 1.0; - mtx[1 + 2*64] = 1.0; - mtx[2 + 3*64] = 1.0; + ambi_layout = AL_ACN_SOFT; + ambi_scale = AL_SN3D_SOFT; + if(has_bfmt_ex) + { + /* An identity matrix that doesn't remix any channels. */ + std::cout<< "Found AL_SOFT_bformat_ex" <sample_rate, - mCodecCtx->channel_layout ? mCodecCtx->channel_layout : - static_cast(av_get_default_channel_layout(mCodecCtx->channels)), + static_cast(mDstChanLayout), mDstSampleFmt, mCodecCtx->sample_rate, + mCodecCtx->channel_layout ? static_cast(mCodecCtx->channel_layout) + : av_get_default_channel_layout(mCodecCtx->channels), mCodecCtx->sample_fmt, mCodecCtx->sample_rate, 0, nullptr)); if(!mSwresCtx || swr_init(mSwresCtx.get()) != 0) { std::cerr<< "Failed to initialize audio converter" <(mBuffers.size()), mBuffers.data()); alGenSources(1, &mSource); - if(EnableDirectOut) - alSourcei(mSource, AL_DIRECT_CHANNELS_SOFT, AL_TRUE); - if (EnableWideStereo) { - ALfloat angles[2] = {static_cast(M_PI / 3.0), - static_cast(-M_PI / 3.0)}; + if(DirectOutMode) + alSourcei(mSource, AL_DIRECT_CHANNELS_SOFT, DirectOutMode); + if(EnableWideStereo) + { + const float angles[2]{static_cast(M_PI / 3.0), static_cast(-M_PI / 3.0)}; alSourcefv(mSource, AL_STEREO_ANGLES, angles); } + if(has_bfmt_ex) + { + for(ALuint bufid : mBuffers) + { + alBufferi(bufid, AL_AMBISONIC_LAYOUT_SOFT, ambi_layout); + alBufferi(bufid, AL_AMBISONIC_SCALING_SOFT, ambi_scale); + } + } +#ifdef AL_SOFT_UHJ + if(EnableSuperStereo) + alSourcei(mSource, AL_STEREO_MODE_SOFT, AL_SUPER_STEREO_SOFT); +#endif if(alGetError() != AL_NO_ERROR) - goto finish; + return 0; -#ifdef AL_SOFT_map_buffer - if(alBufferStorageSOFT) + bool callback_ok{false}; + if(alBufferCallbackSOFT) { - for(ALuint bufid : mBuffers) - alBufferStorageSOFT(bufid, mFormat, nullptr, buffer_len, mCodecCtx->sample_rate, - AL_MAP_WRITE_BIT_SOFT); + alBufferCallbackSOFT(mBuffers[0], mFormat, mCodecCtx->sample_rate, bufferCallbackC, this); + alSourcei(mSource, AL_BUFFER, static_cast(mBuffers[0])); if(alGetError() != AL_NO_ERROR) { - fprintf(stderr, "Failed to use mapped buffers\n"); - samples = av_malloc(buffer_len); + fprintf(stderr, "Failed to set buffer callback\n"); + alSourcei(mSource, AL_BUFFER, 0); + } + else + { + mBufferDataSize = static_cast(duration_cast(mCodecCtx->sample_rate * + AudioBufferTotalTime).count()) * mFrameSize; + mBufferData = std::make_unique(mBufferDataSize); + std::fill_n(mBufferData.get(), mBufferDataSize, uint8_t{}); + + mReadPos.store(0, std::memory_order_relaxed); + mWritePos.store(mBufferDataSize/mFrameSize/2*mFrameSize, std::memory_order_relaxed); + + ALCint refresh{}; + alcGetIntegerv(alcGetContextsDevice(alcGetCurrentContext()), ALC_REFRESH, 1, &refresh); + sleep_time = milliseconds{seconds{1}} / refresh; + callback_ok = true; } } - else -#endif - samples = av_malloc(buffer_len); + if(!callback_ok) + buffer_len = static_cast(duration_cast(mCodecCtx->sample_rate * + AudioBufferTime).count() * mFrameSize); + if(buffer_len > 0) + samples = std::make_unique(static_cast(buffer_len)); - while(alGetError() == AL_NO_ERROR && !mMovie.mQuit.load(std::memory_order_relaxed) && - mConnected.test_and_set(std::memory_order_relaxed)) + /* Prefill the codec buffer. */ + auto packet_sender = [this]() { - /* First remove any processed buffers. */ - ALint processed; - alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); - while(processed > 0) + while(1) { - std::array bids; - alSourceUnqueueBuffers(mSource, std::min(bids.size(), processed), - bids.data()); - processed -= std::min(bids.size(), processed); + const int ret{mQueue.sendPacket(mCodecCtx.get())}; + if(ret == AVErrorEOF) break; } + }; + auto sender = std::async(std::launch::async, packet_sender); - /* Refill the buffer queue. */ - ALint queued; - alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); - while(static_cast(queued) < mBuffers.size()) + srclock.lock(); + if(alcGetInteger64vSOFT) + { + int64_t devtime{}; + alcGetInteger64vSOFT(alcGetContextsDevice(alcGetCurrentContext()), ALC_DEVICE_CLOCK_SOFT, + 1, &devtime); + mDeviceStartTime = nanoseconds{devtime} - mCurrentPts; + } + + mSamplesLen = decodeFrame(); + if(mSamplesLen > 0) + { + mSamplesPos = std::min(mSamplesLen, getSync()); + + auto skip = nanoseconds{seconds{mSamplesPos}} / mCodecCtx->sample_rate; + mDeviceStartTime -= skip; + mCurrentPts += skip; + } + + while(1) + { + ALenum state; + if(mBufferDataSize > 0) + { + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + /* If mQuit is set, don't actually quit until we can't get more + * audio, indicating we've reached the flush packet and the packet + * sender will also quit. + * + * If mQuit is not set, don't quit even if there's no more audio, + * so what's buffered has a chance to play to the real end. + */ + if(!readAudio(getSync()) && mMovie.mQuit.load(std::memory_order_relaxed)) + goto finish; + } + else { - ALuint bufid = mBuffers[mBufferIdx]; + ALint processed, queued; - uint8_t *ptr = reinterpret_cast(samples -#ifdef AL_SOFT_map_buffer - ? samples : alMapBufferSOFT(bufid, 0, buffer_len, AL_MAP_WRITE_BIT_SOFT) -#endif - ); - if(!ptr) break; + /* First remove any processed buffers. */ + alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); + while(processed > 0) + { + ALuint bid; + alSourceUnqueueBuffers(mSource, 1, &bid); + --processed; + } - /* Read the next chunk of data, filling the buffer, and queue it on - * the source */ - bool got_audio = readAudio(ptr, buffer_len); -#ifdef AL_SOFT_map_buffer - if(!samples) alUnmapBufferSOFT(bufid); -#endif - if(!got_audio) break; + /* Refill the buffer queue. */ + int sync_skip{getSync()}; + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + while(static_cast(queued) < mBuffers.size()) + { + /* Read the next chunk of data, filling the buffer, and queue + * it on the source. + */ + const bool got_audio{readAudio(samples.get(), static_cast(buffer_len), + sync_skip)}; + if(!got_audio) + { + if(mMovie.mQuit.load(std::memory_order_relaxed)) + goto finish; + break; + } - if(samples) - alBufferData(bufid, mFormat, samples, buffer_len, mCodecCtx->sample_rate); + const ALuint bufid{mBuffers[mBufferIdx]}; + mBufferIdx = static_cast((mBufferIdx+1) % mBuffers.size()); - alSourceQueueBuffers(mSource, 1, &bufid); - mBufferIdx = (mBufferIdx+1) % mBuffers.size(); - ++queued; - } - if(queued == 0) - break; + alBufferData(bufid, mFormat, samples.get(), buffer_len, mCodecCtx->sample_rate); + alSourceQueueBuffers(mSource, 1, &bufid); + ++queued; + } - /* Check that the source is playing. */ - ALint state; - alGetSourcei(mSource, AL_SOURCE_STATE, &state); - if(state == AL_STOPPED) - { - /* AL_STOPPED means there was an underrun. Clear the buffer queue - * since this likely means we're late, and rewind the source to get - * it back into an AL_INITIAL state. - */ - alSourceRewind(mSource); - alSourcei(mSource, AL_BUFFER, 0); - if(alcGetInteger64vSOFT) + /* Check that the source is playing. */ + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) { - /* Also update the device start time with the current device - * clock, so the decoder knows we're running behind. + /* AL_STOPPED means there was an underrun. Clear the buffer + * queue since this likely means we're late, and rewind the + * source to get it back into an AL_INITIAL state. */ - int64_t devtime{}; - alcGetInteger64vSOFT(alcGetContextsDevice(alcGetCurrentContext()), - ALC_DEVICE_CLOCK_SOFT, 1, &devtime); - mDeviceStartTime = nanoseconds{devtime} - mCurrentPts; + alSourceRewind(mSource); + alSourcei(mSource, AL_BUFFER, 0); + if(alcGetInteger64vSOFT) + { + /* Also update the device start time with the current + * device clock, so the decoder knows we're running behind. + */ + int64_t devtime{}; + alcGetInteger64vSOFT(alcGetContextsDevice(alcGetCurrentContext()), + ALC_DEVICE_CLOCK_SOFT, 1, &devtime); + mDeviceStartTime = nanoseconds{devtime} - mCurrentPts; + } + continue; } - continue; } /* (re)start the source if needed, and wait for a buffer to finish */ if(state != AL_PLAYING && state != AL_PAUSED) { - if(mMovie.mPlaying.load(std::memory_order_relaxed)) - startPlayback(); - else - mPrepared.store(true); + if(!startPlayback()) + break; } + if(ALenum err{alGetError()}) + std::cerr<< "Got AL error: 0x"< lock(mPictQMutex); - return mPictQSize >= mPictQ.size(); -} - -Uint32 SDLCALL VideoState::sdl_refresh_timer_cb(Uint32 /*interval*/, void *opaque) -{ - SDL_Event evt{}; - evt.user.type = FF_REFRESH_EVENT; - evt.user.data1 = opaque; - SDL_PushEvent(&evt); - return 0; /* 0 means stop timer */ -} - -/* Schedules an FF_REFRESH_EVENT event to occur in 'delay' ms. */ -void VideoState::schedRefresh(milliseconds delay) -{ - SDL_AddTimer(delay.count(), sdl_refresh_timer_cb, this); + std::lock_guard _{mDispPtsMutex}; + if(mDisplayPtsTime == microseconds::min()) + return nanoseconds::zero(); + auto delta = get_avtime() - mDisplayPtsTime; + return mDisplayPts + delta; } -/* Called by VideoState::refreshTimer to display the next video frame. */ -void VideoState::display(SDL_Window *screen, SDL_Renderer *renderer) +/* Called by VideoState::updateVideo to display the next video frame. */ +void VideoState::display(SDL_Window *screen, SDL_Renderer *renderer, AVFrame *frame) { - Picture *vp = &mPictQ[mPictQRead]; - - if(!vp->mImage) + if(!mImage) return; - float aspect_ratio; + double aspect_ratio; int win_w, win_h; int w, h, x, y; - if(mCodecCtx->sample_aspect_ratio.num == 0) - aspect_ratio = 0.0f; + 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(mCodecCtx->sample_aspect_ratio) * mCodecCtx->width / - mCodecCtx->height; + aspect_ratio = av_q2d(frame->sample_aspect_ratio) * frame_width / + frame_height; } - if(aspect_ratio <= 0.0f) - aspect_ratio = static_cast(mCodecCtx->width) / static_cast(mCodecCtx->height); + if(aspect_ratio <= 0.0) + aspect_ratio = static_cast(frame_width) / frame_height; SDL_GetWindowSize(screen, &win_w, &win_h); h = win_h; - w = (static_cast(rint(h * aspect_ratio)) + 3) & ~3; + w = (static_cast(std::rint(h * aspect_ratio)) + 3) & ~3; if(w > win_w) { w = win_w; - h = (static_cast(rint(w / aspect_ratio)) + 3) & ~3; + h = (static_cast(std::rint(w / aspect_ratio)) + 3) & ~3; } x = (win_w - w) / 2; y = (win_h - h) / 2; - SDL_Rect src_rect{ 0, 0, vp->mWidth, vp->mHeight }; + SDL_Rect src_rect{ 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, vp->mImage, &src_rect, &dst_rect); + SDL_RenderCopy(renderer, mImage, &src_rect, &dst_rect); SDL_RenderPresent(renderer); } -/* FF_REFRESH_EVENT handler called on the main thread where the SDL_Renderer - * was created. It handles the display of the next decoded video frame (if not - * falling behind), and sets up the timer for the following video frame. +/* Called regularly on the main thread where the SDL_Renderer was created. It + * handles updating the textures of decoded frames and displaying the latest + * frame. */ -void VideoState::refreshTimer(SDL_Window *screen, SDL_Renderer *renderer) +void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool redraw) { - if(!mStream) + size_t read_idx{mPictQRead.load(std::memory_order_relaxed)}; + Picture *vp{&mPictQ[read_idx]}; + + auto clocktime = mMovie.getMasterClock(); + bool updated{false}; + while(1) { - if(mEOS) + size_t next_idx{(read_idx+1)%mPictQ.size()}; + if(next_idx == mPictQWrite.load(std::memory_order_acquire)) + break; + Picture *nextvp{&mPictQ[next_idx]}; + if(clocktime < nextvp->mPts && !mMovie.mQuit.load(std::memory_order_relaxed)) { - mFinalUpdate = true; - std::unique_lock(mPictQMutex).unlock(); - mPictQCond.notify_all(); - return; + /* For the first update, ensure the first frame gets shown. */ + if(!mFirstUpdate || updated) + break; } - schedRefresh(milliseconds(100)); - return; - } - if(!mMovie.mPlaying.load(std::memory_order_relaxed)) - { - schedRefresh(milliseconds(1)); - return; - } - std::unique_lock lock(mPictQMutex); -retry: - if(mPictQSize == 0) + vp = nextvp; + updated = true; + read_idx = next_idx; + } + if(mMovie.mQuit.load(std::memory_order_relaxed)) { if(mEOS) mFinalUpdate = true; - else - schedRefresh(milliseconds(1)); - lock.unlock(); - mPictQCond.notify_all(); + mPictQRead.store(read_idx, std::memory_order_release); + std::unique_lock{mPictQMutex}.unlock(); + mPictQCond.notify_one(); return; } - Picture *vp = &mPictQ[mPictQRead]; - mCurrentPts = vp->mPts; - mCurrentPtsTime = get_avtime(); - - /* Get delay using the frame pts and the pts from last frame. */ - auto delay = vp->mPts - mFrameLastPts; - if(delay <= seconds::zero() || delay >= seconds(1)) - { - /* If incorrect delay, use previous one. */ - delay = mFrameLastDelay; - } - /* Save for next frame. */ - mFrameLastDelay = delay; - mFrameLastPts = vp->mPts; - - /* Update delay to sync to clock if not master source. */ - if(mMovie.mAVSyncType != SyncMaster::Video) + AVFrame *frame{vp->mFrame.get()}; + if(updated) { - auto ref_clock = mMovie.getMasterClock(); - auto diff = vp->mPts - ref_clock; + mPictQRead.store(read_idx, std::memory_order_release); + std::unique_lock{mPictQMutex}.unlock(); + mPictQCond.notify_one(); - /* Skip or repeat the frame. Take delay into account. */ - auto sync_threshold = std::min(delay, VideoSyncThreshold); - if(!(diff < AVNoSyncThreshold && diff > -AVNoSyncThreshold)) + /* allocate or resize the buffer! */ + bool fmt_updated{false}; + if(!mImage || mWidth != frame->width || mHeight != frame->height) { - if(diff <= -sync_threshold) - delay = nanoseconds::zero(); - else if(diff >= sync_threshold) - delay *= 2; + 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; } - } - - mFrameTimer += delay; - /* Compute the REAL delay. */ - auto actual_delay = mFrameTimer - get_avtime(); - if(!(actual_delay >= VideoSyncThreshold)) - { - /* We don't have time to handle this picture, just skip to the next one. */ - mPictQRead = (mPictQRead+1)%mPictQ.size(); - mPictQSize--; - goto retry; - } - schedRefresh(std::chrono::duration_cast(actual_delay)); - - /* Show the picture! */ - display(screen, renderer); - - /* Update queue for next picture. */ - mPictQRead = (mPictQRead+1)%mPictQ.size(); - mPictQSize--; - lock.unlock(); - mPictQCond.notify_all(); -} - -/* FF_UPDATE_EVENT handler, updates the picture's texture. It's called on the - * main thread where the renderer was created. - */ -void VideoState::updatePicture(SDL_Window *screen, SDL_Renderer *renderer) -{ - Picture *vp = &mPictQ[mPictQWrite]; - bool fmt_updated = false; - - /* allocate or resize the buffer! */ - if(!vp->mImage || vp->mWidth != mCodecCtx->width || vp->mHeight != mCodecCtx->height) - { - fmt_updated = true; - if(vp->mImage) - SDL_DestroyTexture(vp->mImage); - vp->mImage = SDL_CreateTexture( - renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, - mCodecCtx->coded_width, mCodecCtx->coded_height - ); - if(!vp->mImage) - std::cerr<< "Failed to create YV12 texture!" <mWidth = mCodecCtx->width; - vp->mHeight = mCodecCtx->height; - if(mFirstUpdate && vp->mWidth > 0 && vp->mHeight > 0) + 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(mFirstUpdate && frame_width > 0 && frame_height > 0) { /* For the first update, set the window size to the video size. */ mFirstUpdate = false; - int w = vp->mWidth; - int h = vp->mHeight; - if(mCodecCtx->sample_aspect_ratio.den != 0) + if(frame->sample_aspect_ratio.den != 0) { - double aspect_ratio = av_q2d(mCodecCtx->sample_aspect_ratio); + double aspect_ratio = av_q2d(frame->sample_aspect_ratio); if(aspect_ratio >= 1.0) - w = static_cast(w*aspect_ratio + 0.5); + frame_width = static_cast(frame_width*aspect_ratio + 0.5); else if(aspect_ratio > 0.0) - h = static_cast(h/aspect_ratio + 0.5); + frame_height = static_cast(frame_height/aspect_ratio + 0.5); } - SDL_SetWindowSize(screen, w, h); + SDL_SetWindowSize(screen, frame_width, frame_height); } - } - - if(vp->mImage) - { - AVFrame *frame = mDecodedFrame.get(); - void *pixels = nullptr; - int pitch = 0; - if(mCodecCtx->pix_fmt == AV_PIX_FMT_YUV420P) - SDL_UpdateYUVTexture(vp->mImage, nullptr, - frame->data[0], frame->linesize[0], - frame->data[1], frame->linesize[1], - frame->data[2], frame->linesize[2] - ); - else if(SDL_LockTexture(vp->mImage, nullptr, &pixels, &pitch) != 0) - std::cerr<< "Failed to lock texture" <coded_width; - int coded_h = mCodecCtx->coded_height; - int w = mCodecCtx->width; - int h = mCodecCtx->height; - if(!mSwscaleCtx || fmt_updated) + void *pixels{nullptr}; + int pitch{0}; + + if(mCodecCtx->pix_fmt == AV_PIX_FMT_YUV420P) + SDL_UpdateYUVTexture(mImage, nullptr, + frame->data[0], frame->linesize[0], + frame->data[1], frame->linesize[1], + frame->data[2], frame->linesize[2] + ); + else if(SDL_LockTexture(mImage, nullptr, &pixels, &pitch) != 0) + std::cerr<< "Failed to lock texture" <pix_fmt, - w, h, AV_PIX_FMT_YUV420P, 0, - nullptr, nullptr, nullptr - )); - } + // Convert the image into YUV format that SDL uses + int w{frame->width}; + int h{frame->height}; + if(!mSwscaleCtx || fmt_updated) + { + mSwscaleCtx.reset(sws_getContext( + w, h, mCodecCtx->pix_fmt, + w, h, AV_PIX_FMT_YUV420P, 0, + nullptr, nullptr, nullptr + )); + } + + /* point pict at the queue */ + uint8_t *pict_data[3]; + pict_data[0] = static_cast(pixels); + pict_data[1] = pict_data[0] + w*h; + pict_data[2] = pict_data[1] + w*h/4; - /* point pict at the queue */ - uint8_t *pict_data[3]; - pict_data[0] = reinterpret_cast(pixels); - pict_data[1] = pict_data[0] + coded_w*coded_h; - pict_data[2] = pict_data[1] + coded_w*coded_h/4; + int pict_linesize[3]; + pict_linesize[0] = pitch; + pict_linesize[1] = pitch / 2; + pict_linesize[2] = pitch / 2; - int pict_linesize[3]; - pict_linesize[0] = pitch; - pict_linesize[1] = pitch / 2; - pict_linesize[2] = pitch / 2; + sws_scale(mSwscaleCtx.get(), reinterpret_cast(frame->data), frame->linesize, + 0, h, pict_data, pict_linesize); + SDL_UnlockTexture(mImage); + } - sws_scale(mSwscaleCtx.get(), reinterpret_cast(frame->data), - frame->linesize, 0, h, pict_data, pict_linesize); - SDL_UnlockTexture(vp->mImage); + redraw = true; } } - vp->mUpdated.store(true, std::memory_order_release); - std::unique_lock(mPictQMutex).unlock(); - mPictQCond.notify_one(); -} - -int VideoState::queuePicture(nanoseconds pts) -{ - /* Wait until we have space for a new pic */ - std::unique_lock lock(mPictQMutex); - while(mPictQSize >= mPictQ.size() && !mMovie.mQuit.load(std::memory_order_relaxed)) - mPictQCond.wait(lock); - lock.unlock(); - - if(mMovie.mQuit.load(std::memory_order_relaxed)) - return -1; - - Picture *vp = &mPictQ[mPictQWrite]; - - /* We have to create/update the picture in the main thread */ - vp->mUpdated.store(false, std::memory_order_relaxed); - SDL_Event evt{}; - evt.user.type = FF_UPDATE_EVENT; - evt.user.data1 = this; - SDL_PushEvent(&evt); - - /* Wait until the picture is updated. */ - lock.lock(); - while(!vp->mUpdated.load(std::memory_order_relaxed)) + if(redraw) { - if(mMovie.mQuit.load(std::memory_order_relaxed)) - return -1; - mPictQCond.wait(lock); + /* Show the picture! */ + display(screen, renderer, frame); } - if(mMovie.mQuit.load(std::memory_order_relaxed)) - return -1; - vp->mPts = pts; - mPictQWrite = (mPictQWrite+1)%mPictQ.size(); - mPictQSize++; - lock.unlock(); + if(updated) + { + auto disp_time = get_avtime(); - return 0; + std::lock_guard _{mDispPtsMutex}; + mDisplayPts = vp->mPts; + mDisplayPtsTime = disp_time; + } + if(mEOS.load(std::memory_order_acquire)) + { + if((read_idx+1)%mPictQ.size() == mPictQWrite.load(std::memory_order_acquire)) + { + mFinalUpdate = true; + std::unique_lock{mPictQMutex}.unlock(); + mPictQCond.notify_one(); + } + } } int VideoState::handler() { - mDecodedFrame.reset(av_frame_alloc()); - while(!mMovie.mQuit.load(std::memory_order_relaxed)) + std::for_each(mPictQ.begin(), mPictQ.end(), + [](Picture &pict) -> void + { pict.mFrame = AVFramePtr{av_frame_alloc()}; }); + + /* Prefill the codec buffer. */ + auto packet_sender = [this]() { - std::unique_lock lock(mQueueMtx); - /* Decode video frame */ - int ret = avcodec_receive_frame(mCodecCtx.get(), mDecodedFrame.get()); - if(ret == AVERROR(EAGAIN)) + while(1) { - mMovie.mSendDataGood.clear(std::memory_order_relaxed); - std::unique_lock(mMovie.mSendMtx).unlock(); - mMovie.mSendCond.notify_one(); - do { - mQueueCond.wait(lock); - ret = avcodec_receive_frame(mCodecCtx.get(), mDecodedFrame.get()); - } while(ret == AVERROR(EAGAIN)); + const int ret{mQueue.sendPacket(mCodecCtx.get())}; + if(ret == AVErrorEOF) break; } - lock.unlock(); - if(ret == AVERROR_EOF) break; - mMovie.mSendDataGood.clear(std::memory_order_relaxed); - mMovie.mSendCond.notify_one(); - if(ret < 0) + }; + auto sender = std::async(std::launch::async, packet_sender); + + { + std::lock_guard _{mDispPtsMutex}; + mDisplayPtsTime = get_avtime(); + } + + auto current_pts = nanoseconds::zero(); + while(1) + { + size_t write_idx{mPictQWrite.load(std::memory_order_relaxed)}; + Picture *vp{&mPictQ[write_idx]}; + + /* Retrieve video frame. */ + AVFrame *decoded_frame{vp->mFrame.get()}; + while(int ret{mQueue.receiveFrame(mCodecCtx.get(), decoded_frame)}) { - std::cerr<< "Failed to decode frame: "<best_effort_timestamp != AV_NOPTS_VALUE) - mClock = std::chrono::duration_cast( - seconds_d64(av_q2d(mStream->time_base)*mDecodedFrame->best_effort_timestamp) - ); - pts = mClock; + if(decoded_frame->best_effort_timestamp != AVNoPtsValue) + current_pts = duration_cast(seconds_d64{av_q2d(mStream->time_base) * + static_cast(decoded_frame->best_effort_timestamp)}); + vp->mPts = current_pts; /* Update the video clock to the next expected PTS. */ auto frame_delay = av_q2d(mCodecCtx->time_base); - frame_delay += mDecodedFrame->repeat_pict * (frame_delay * 0.5); - mClock += std::chrono::duration_cast(seconds_d64(frame_delay)); + frame_delay += decoded_frame->repeat_pict * (frame_delay * 0.5); + current_pts += duration_cast(seconds_d64{frame_delay}); - if(queuePicture(pts) < 0) - break; - av_frame_unref(mDecodedFrame.get()); + /* Put the frame in the queue to be loaded into a texture and displayed + * by the rendering thread. + */ + write_idx = (write_idx+1)%mPictQ.size(); + mPictQWrite.store(write_idx, std::memory_order_release); + + 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); + } } +finish: mEOS = true; - std::unique_lock lock(mPictQMutex); - if(mMovie.mQuit.load(std::memory_order_relaxed)) - { - mPictQRead = 0; - mPictQWrite = 0; - mPictQSize = 0; - } - while(!mFinalUpdate) - mPictQCond.wait(lock); + std::unique_lock lock{mPictQMutex}; + while(!mFinalUpdate) mPictQCond.wait(lock); return 0; } @@ -1445,13 +1670,13 @@ int VideoState::handler() int MovieState::decode_interrupt_cb(void *ctx) { - return reinterpret_cast(ctx)->mQuit.load(std::memory_order_relaxed); + return static_cast(ctx)->mQuit.load(std::memory_order_relaxed); } bool MovieState::prepare() { - AVIOContext *avioctx = nullptr; - AVIOInterruptCB intcb = { decode_interrupt_cb, this }; + AVIOContext *avioctx{nullptr}; + AVIOInterruptCB intcb{decode_interrupt_cb, this}; if(avio_open2(&avioctx, mFilename.c_str(), AVIO_FLAG_READ, &intcb, nullptr)) { std::cerr<< "Failed to open "<pb = mIOContext.get(); fmtctx->interrupt_callback = intcb; if(avformat_open_input(&fmtctx, mFilename.c_str(), nullptr, nullptr) != 0) @@ -1479,9 +1704,13 @@ bool MovieState::prepare() return false; } - mVideo.schedRefresh(milliseconds(40)); + /* Dump information about file onto standard error */ + av_dump_format(mFormatCtx.get(), 0, mFilename.c_str(), 0); + + mParseThread = std::thread{std::mem_fn(&MovieState::parse_handler), this}; - mParseThread = std::thread(std::mem_fn(&MovieState::parse_handler), this); + std::unique_lock slock{mStartupMutex}; + while(!mStartupDone) mStartupCond.wait(slock); return true; } @@ -1497,16 +1726,16 @@ void MovieState::setTitle(SDL_Window *window) nanoseconds MovieState::getClock() { - if(!mPlaying.load(std::memory_order_relaxed)) + if(mClockBase == microseconds::min()) return nanoseconds::zero(); return get_avtime() - mClockBase; } nanoseconds MovieState::getMasterClock() { - if(mAVSyncType == SyncMaster::Video) + if(mAVSyncType == SyncMaster::Video && mVideo.mStream) return mVideo.getClock(); - if(mAVSyncType == SyncMaster::Audio) + if(mAVSyncType == SyncMaster::Audio && mAudio.mStream) return mAudio.getClock(); return getClock(); } @@ -1514,25 +1743,25 @@ nanoseconds MovieState::getMasterClock() nanoseconds MovieState::getDuration() { return std::chrono::duration>(mFormatCtx->duration); } -int MovieState::streamComponentOpen(int stream_index) +int MovieState::streamComponentOpen(unsigned int stream_index) { - if(stream_index < 0 || static_cast(stream_index) >= mFormatCtx->nb_streams) + if(stream_index >= mFormatCtx->nb_streams) return -1; /* Get a pointer to the codec context for the stream, and open the * associated codec. */ - AVCodecCtxPtr avctx(avcodec_alloc_context3(nullptr)); + AVCodecCtxPtr avctx{avcodec_alloc_context3(nullptr)}; if(!avctx) return -1; if(avcodec_parameters_to_context(avctx.get(), mFormatCtx->streams[stream_index]->codecpar)) return -1; - AVCodec *codec = avcodec_find_decoder(avctx->codec_id); + 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<streams[stream_index]; mAudio.mCodecCtx = std::move(avctx); - - mAudioThread = std::thread(std::mem_fn(&AudioState::handler), &mAudio); break; case AVMEDIA_TYPE_VIDEO: mVideo.mStream = mFormatCtx->streams[stream_index]; mVideo.mCodecCtx = std::move(avctx); - - mVideoThread = std::thread(std::mem_fn(&VideoState::handler), &mVideo); break; default: return -1; } - return stream_index; + return static_cast(stream_index); } int MovieState::parse_handler() { - int video_index = -1; - int audio_index = -1; + auto &audio_queue = mAudio.mQueue; + auto &video_queue = mVideo.mQueue; - /* Dump information about file onto standard error */ - av_dump_format(mFormatCtx.get(), 0, mFilename.c_str(), 0); + int video_index{-1}; + int audio_index{-1}; /* Find the first video and audio streams */ - for(unsigned int i = 0;i < mFormatCtx->nb_streams;i++) + for(unsigned int i{0u};i < mFormatCtx->nb_streams;i++) { auto codecpar = mFormatCtx->streams[i]->codecpar; - if(codecpar->codec_type == AVMEDIA_TYPE_VIDEO && video_index < 0) + if(codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !DisableVideo && video_index < 0) video_index = streamComponentOpen(i); else if(codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0) audio_index = streamComponentOpen(i); } + { + std::unique_lock slock{mStartupMutex}; + mStartupDone = true; + } + mStartupCond.notify_all(); + if(video_index < 0 && audio_index < 0) { std::cerr<< mFilename<<": could not open codecs" <= 0) + mAudioThread = std::thread{std::mem_fn(&AudioState::handler), &mAudio}; + if(video_index >= 0) + mVideoThread = std::thread{std::mem_fn(&VideoState::handler), &mVideo}; /* Main packet reading/dispatching loop */ - while(!mQuit.load(std::memory_order_relaxed) && !input_finished) + AVPacketPtr packet{av_packet_alloc()}; + while(!mQuit.load(std::memory_order_relaxed)) { - AVPacket packet; - if(av_read_frame(mFormatCtx.get(), &packet) < 0) - input_finished = true; - else - { - /* Copy the packet into the queue it's meant for. */ - if(packet.stream_index == video_index) - video_queue.put(&packet); - else if(packet.stream_index == audio_index) - audio_queue.put(&packet); - av_packet_unref(&packet); - } - - do { - /* Send whatever queued packets we have. */ - if(!audio_queue.empty()) - { - std::unique_lock lock(mAudio.mQueueMtx); - int ret; - do { - ret = avcodec_send_packet(mAudio.mCodecCtx.get(), audio_queue.front()); - if(ret != AVERROR(EAGAIN)) audio_queue.pop(); - } while(ret != AVERROR(EAGAIN) && !audio_queue.empty()); - lock.unlock(); - mAudio.mQueueCond.notify_one(); - } - if(!video_queue.empty()) - { - std::unique_lock lock(mVideo.mQueueMtx); - int ret; - do { - ret = avcodec_send_packet(mVideo.mCodecCtx.get(), video_queue.front()); - if(ret != AVERROR(EAGAIN)) video_queue.pop(); - } while(ret != AVERROR(EAGAIN) && !video_queue.empty()); - lock.unlock(); - mVideo.mQueueCond.notify_one(); - } - /* If the queues are completely empty, or it's not full and there's - * more input to read, go get more. - */ - size_t queue_size = audio_queue.totalSize() + video_queue.totalSize(); - if(queue_size == 0 || (queue_size < MAX_QUEUE_SIZE && !input_finished)) - break; + if(av_read_frame(mFormatCtx.get(), packet.get()) < 0) + break; - if(!mPlaying.load(std::memory_order_relaxed)) - { - if((!mAudio.mCodecCtx || mAudio.isBufferFilled()) && - (!mVideo.mCodecCtx || mVideo.isBufferFilled())) - { - /* Set the base time 50ms ahead of the current av time. */ - mClockBase = get_avtime() + milliseconds(50); - mVideo.mCurrentPtsTime = mClockBase; - mVideo.mFrameTimer = mVideo.mCurrentPtsTime; - mAudio.startPlayback(); - mPlaying.store(std::memory_order_release); - } - } - /* Nothing to send or get for now, wait a bit and try again. */ - { std::unique_lock lock(mSendMtx); - if(mSendDataGood.test_and_set(std::memory_order_relaxed)) - mSendCond.wait_for(lock, milliseconds(10)); - } - } while(!mQuit.load(std::memory_order_relaxed)); - } - /* Pass a null packet to finish the send buffers (the receive functions - * will get AVERROR_EOF when emptied). - */ - if(mVideo.mCodecCtx) - { - { std::lock_guard lock(mVideo.mQueueMtx); - avcodec_send_packet(mVideo.mCodecCtx.get(), nullptr); + /* Copy the packet into the queue it's meant for. */ + if(packet->stream_index == video_index) + { + while(!mQuit.load(std::memory_order_acquire) && !video_queue.put(packet.get())) + std::this_thread::sleep_for(milliseconds{100}); } - mVideo.mQueueCond.notify_one(); - } - if(mAudio.mCodecCtx) - { - { std::lock_guard lock(mAudio.mQueueMtx); - avcodec_send_packet(mAudio.mCodecCtx.get(), nullptr); + else if(packet->stream_index == audio_index) + { + while(!mQuit.load(std::memory_order_acquire) && !audio_queue.put(packet.get())) + std::this_thread::sleep_for(milliseconds{100}); } - mAudio.mQueueCond.notify_one(); + + av_packet_unref(packet.get()); } - video_queue.clear(); - audio_queue.clear(); + /* Finish the queues so the receivers know nothing more is coming. */ + video_queue.setFinished(); + audio_queue.setFinished(); /* all done - wait for it */ if(mVideoThread.joinable()) @@ -1681,7 +1855,7 @@ int MovieState::parse_handler() mAudioThread.join(); mVideo.mEOS = true; - std::unique_lock lock(mVideo.mPictQMutex); + std::unique_lock lock{mVideo.mPictQMutex}; while(!mVideo.mFinalUpdate) mVideo.mPictQCond.wait(lock); lock.unlock(); @@ -1693,18 +1867,24 @@ int MovieState::parse_handler() return 0; } +void MovieState::stop() +{ + mQuit = true; + mAudio.mQueue.flush(); + mVideo.mQueue.flush(); +} + // Helper class+method to print the time with human-readable formatting. struct PrettyTime { seconds mTime; }; -inline std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs) +std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs) { using hours = std::chrono::hours; using minutes = std::chrono::minutes; - using std::chrono::duration_cast; - seconds t = rhs.mTime; + seconds t{rhs.mTime}; if(t.count() < 0) { os << '-'; @@ -1712,7 +1892,7 @@ inline std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs) } // Only handle up to hour formatting - if(t >= hours(1)) + if(t >= hours{1}) os << duration_cast(t).count() << 'h' << std::setfill('0') << std::setw(2) << (duration_cast(t).count() % 60) << 'm'; else @@ -1735,36 +1915,38 @@ int main(int argc, char *argv[]) return 1; } /* Register all formats and codecs */ +#if !(LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)) av_register_all(); +#endif /* Initialize networking protocols */ avformat_network_init(); - if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) + if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { std::cerr<< "Could not initialize SDL - <<"<( - alGetProcAddress("alBufferStorageSOFT")); - alMapBufferSOFT = reinterpret_cast( - alGetProcAddress("alMapBufferSOFT")); - alUnmapBufferSOFT = reinterpret_cast( - alGetProcAddress("alUnmapBufferSOFT")); - } -#endif -#ifdef AL_SOFT_events - if(alIsExtensionPresent("AL_SOFTX_events")) + if(alIsExtensionPresent("AL_SOFT_events")) { std::cout<< "Found AL_SOFT_events" <( @@ -1834,20 +2004,30 @@ int main(int argc, char *argv[]) alEventCallbackSOFT = reinterpret_cast( alGetProcAddress("alEventCallbackSOFT")); } -#endif + if(alIsExtensionPresent("AL_SOFT_callback_buffer")) + { + std::cout<< "Found AL_SOFT_callback_buffer" <( + alGetProcAddress("alBufferCallbackSOFT")); + } - int fileidx = 0; + int fileidx{0}; for(;fileidx < argc;++fileidx) { if(strcmp(argv[fileidx], "-direct") == 0) { - if(!alIsExtensionPresent("AL_SOFT_direct_channels")) - std::cerr<< "AL_SOFT_direct_channels not supported for direct output" <(new MovieState(argv[fileidx++])); + movState = std::unique_ptr{new MovieState{argv[fileidx++]}}; if(!movState->prepare()) movState = nullptr; } if(!movState) @@ -1878,81 +2082,77 @@ int main(int argc, char *argv[]) /* Default to going to the next movie at the end of one. */ enum class EomAction { Next, Quit - } eom_action = EomAction::Next; - seconds last_time(-1); - SDL_Event event; + } eom_action{EomAction::Next}; + seconds last_time{seconds::min()}; while(1) { - int have_evt = SDL_WaitEventTimeout(&event, 10); + /* SDL_WaitEventTimeout is broken, just force a 10ms sleep. */ + std::this_thread::sleep_for(milliseconds{10}); auto cur_time = std::chrono::duration_cast(movState->getMasterClock()); if(cur_time != last_time) { auto end_time = std::chrono::duration_cast(movState->getDuration()); - std::cout<< "\r "<mQuit = true; - eom_action = EomAction::Quit; - break; - - case SDLK_n: - movState->mQuit = true; - eom_action = EomAction::Next; - break; - - default: - break; + case SDLK_ESCAPE: + movState->stop(); + eom_action = EomAction::Quit; + break; + + case SDLK_n: + movState->stop(); + eom_action = EomAction::Next; + break; + + default: + break; } break; case SDL_WINDOWEVENT: switch(event.window.event) { - case SDL_WINDOWEVENT_RESIZED: - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); - SDL_RenderFillRect(renderer, nullptr); - break; - - default: - break; + 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; } break; case SDL_QUIT: - movState->mQuit = true; + movState->stop(); eom_action = EomAction::Quit; break; - case FF_UPDATE_EVENT: - reinterpret_cast(event.user.data1)->updatePicture( - screen, renderer - ); - break; - - case FF_REFRESH_EVENT: - reinterpret_cast(event.user.data1)->refreshTimer( - screen, renderer - ); - break; - case FF_MOVIE_DONE_EVENT: std::cout<<'\n'; - last_time = seconds(-1); + last_time = seconds::min(); if(eom_action != EomAction::Quit) { movState = nullptr; while(fileidx < argc && !movState) { - movState = std::unique_ptr(new MovieState(argv[fileidx++])); + movState = std::unique_ptr{new MovieState{argv[fileidx++]}}; if(!movState->prepare()) movState = nullptr; } if(movState) @@ -1977,7 +2177,10 @@ int main(int argc, char *argv[]) default: break; + } } + + movState->mVideo.updateVideo(screen, renderer, force_redraw); } std::cerr<< "SDL_WaitEvent error - "< #include +#include +#include #include +#include +#include +#include -#include +#include "sndfile.h" #include "AL/al.h" #include "AL/alc.h" @@ -49,68 +53,73 @@ static LPALCRESETDEVICESOFT alcResetDeviceSOFT; */ static ALuint LoadSound(const char *filename) { - Sound_Sample *sample; ALenum err, format; ALuint buffer; - Uint32 slen; - - /* Open the audio file */ - sample = Sound_NewSampleFromFile(filename, NULL, 65536); - if(!sample) + SNDFILE *sndfile; + SF_INFO sfinfo; + short *membuf; + sf_count_t num_frames; + ALsizei num_bytes; + + /* Open the audio file and check that it's usable. */ + sndfile = sf_open(filename, SFM_READ, &sfinfo); + if(!sndfile) + { + fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile)); + return 0; + } + if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(short))/sfinfo.channels) { - fprintf(stderr, "Could not open audio in %s\n", filename); + fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames); + sf_close(sndfile); return 0; } /* Get the sound format, and figure out the OpenAL format */ - if(sample->actual.channels == 1) + format = AL_NONE; + if(sfinfo.channels == 1) + format = AL_FORMAT_MONO16; + else if(sfinfo.channels == 2) + format = AL_FORMAT_STEREO16; + else if(sfinfo.channels == 3) { - if(sample->actual.format == AUDIO_U8) - format = AL_FORMAT_MONO8; - else if(sample->actual.format == AUDIO_S16SYS) - format = AL_FORMAT_MONO16; - else - { - fprintf(stderr, "Unsupported sample format: 0x%04x\n", sample->actual.format); - Sound_FreeSample(sample); - return 0; - } + if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + format = AL_FORMAT_BFORMAT2D_16; } - else if(sample->actual.channels == 2) + else if(sfinfo.channels == 4) { - if(sample->actual.format == AUDIO_U8) - format = AL_FORMAT_STEREO8; - else if(sample->actual.format == AUDIO_S16SYS) - format = AL_FORMAT_STEREO16; - else - { - fprintf(stderr, "Unsupported sample format: 0x%04x\n", sample->actual.format); - Sound_FreeSample(sample); - return 0; - } + if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + format = AL_FORMAT_BFORMAT3D_16; } - else + if(!format) { - fprintf(stderr, "Unsupported channel count: %d\n", sample->actual.channels); - Sound_FreeSample(sample); + fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels); + sf_close(sndfile); return 0; } - /* Decode the whole audio stream to a buffer. */ - slen = Sound_DecodeAll(sample); - if(!sample->buffer || slen == 0) + /* Decode the whole audio file to a buffer. */ + membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(short)); + + num_frames = sf_readf_short(sndfile, membuf, sfinfo.frames); + if(num_frames < 1) { - fprintf(stderr, "Failed to read audio from %s\n", filename); - Sound_FreeSample(sample); + free(membuf); + sf_close(sndfile); + fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames); return 0; } + num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(short); /* Buffer the audio data into a new buffer object, then free the data and - * close the file. */ + * close the file. + */ buffer = 0; alGenBuffers(1, &buffer); - alBufferData(buffer, format, sample->buffer, slen, sample->actual.rate); - Sound_FreeSample(sample); + alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate); + + free(membuf); + sf_close(sndfile); /* Check if an error occured, and clean up if so. */ err = alGetError(); @@ -129,6 +138,7 @@ static ALuint LoadSound(const char *filename) int main(int argc, char **argv) { ALCdevice *device; + ALCcontext *context; ALboolean has_angle_ext; ALuint source, buffer; const char *soundname; @@ -150,7 +160,8 @@ int main(int argc, char **argv) if(InitAL(&argv, &argc) != 0) return 1; - device = alcGetContextsDevice(alcGetCurrentContext()); + context = alcGetCurrentContext(); + device = alcGetContextsDevice(context); if(!alcIsExtensionPresent(device, "ALC_SOFT_HRTF")) { fprintf(stderr, "Error: ALC_SOFT_HRTF not supported\n"); @@ -159,16 +170,16 @@ int main(int argc, char **argv) } /* Define a macro to help load the function pointers. */ -#define LOAD_PROC(d, x) ((x) = alcGetProcAddress((d), #x)) - LOAD_PROC(device, alcGetStringiSOFT); - LOAD_PROC(device, alcResetDeviceSOFT); +#define LOAD_PROC(d, T, x) ((x) = FUNCTION_CAST(T, alcGetProcAddress((d), #x))) + LOAD_PROC(device, LPALCGETSTRINGISOFT, alcGetStringiSOFT); + LOAD_PROC(device, LPALCRESETDEVICESOFT, alcResetDeviceSOFT); #undef LOAD_PROC /* Check for the AL_EXT_STEREO_ANGLES extension to be able to also rotate * stereo sources. */ has_angle_ext = alIsExtensionPresent("AL_EXT_STEREO_ANGLES"); - printf("AL_EXT_STEREO_ANGLES%s found\n", has_angle_ext?"":" not"); + printf("AL_EXT_STEREO_ANGLES %sfound\n", has_angle_ext?"":"not "); /* Check for user-preferred HRTF */ if(strcmp(argv[0], "-hrtf") == 0) @@ -235,14 +246,10 @@ int main(int argc, char **argv) } fflush(stdout); - /* Initialize SDL_sound. */ - Sound_Init(); - /* Load the sound into a buffer. */ buffer = LoadSound(soundname); if(!buffer) { - Sound_Quit(); CloseAL(); return 1; } @@ -252,7 +259,7 @@ int main(int argc, char **argv) alGenSources(1, &source); alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); alSource3f(source, AL_POSITION, 0.0f, 0.0f, -1.0f); - alSourcei(source, AL_BUFFER, buffer); + alSourcei(source, AL_BUFFER, (ALint)buffer); assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Play the sound until it finishes. */ @@ -261,6 +268,8 @@ int main(int argc, char **argv) do { al_nssleep(10000000); + alcSuspendContext(context); + /* Rotate the source around the listener by about 1/4 cycle per second, * and keep it within -pi...+pi. */ @@ -279,15 +288,14 @@ int main(int argc, char **argv) ALfloat angles[2] = { (ALfloat)(M_PI/6.0 - angle), (ALfloat)(-M_PI/6.0 - angle) }; alSourcefv(source, AL_STEREO_ANGLES, angles); } + alcProcessContext(context); alGetSourcei(source, AL_SOURCE_STATE, &state); } while(alGetError() == AL_NO_ERROR && state == AL_PLAYING); - /* All done. Delete resources, and close down SDL_sound and OpenAL. */ + /* All done. Delete resources, and close down OpenAL. */ alDeleteSources(1, &source); alDeleteBuffers(1, &buffer); - - Sound_Quit(); CloseAL(); return 0; diff --git a/modules/openal-soft/examples/allatency.c b/modules/openal-soft/examples/allatency.c index d561373..ab4a4eb 100644 --- a/modules/openal-soft/examples/allatency.c +++ b/modules/openal-soft/examples/allatency.c @@ -24,13 +24,15 @@ /* This file contains an example for checking the latency of a sound. */ -#include #include +#include +#include +#include +#include -#include +#include "sndfile.h" #include "AL/al.h" -#include "AL/alc.h" #include "AL/alext.h" #include "common/alhelpers.h" @@ -54,68 +56,73 @@ static LPALGETSOURCEI64VSOFT alGetSourcei64vSOFT; */ static ALuint LoadSound(const char *filename) { - Sound_Sample *sample; ALenum err, format; ALuint buffer; - Uint32 slen; - - /* Open the audio file */ - sample = Sound_NewSampleFromFile(filename, NULL, 65536); - if(!sample) + SNDFILE *sndfile; + SF_INFO sfinfo; + short *membuf; + sf_count_t num_frames; + ALsizei num_bytes; + + /* Open the audio file and check that it's usable. */ + sndfile = sf_open(filename, SFM_READ, &sfinfo); + if(!sndfile) + { + fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile)); + return 0; + } + if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(short))/sfinfo.channels) { - fprintf(stderr, "Could not open audio in %s\n", filename); + fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames); + sf_close(sndfile); return 0; } /* Get the sound format, and figure out the OpenAL format */ - if(sample->actual.channels == 1) + format = AL_NONE; + if(sfinfo.channels == 1) + format = AL_FORMAT_MONO16; + else if(sfinfo.channels == 2) + format = AL_FORMAT_STEREO16; + else if(sfinfo.channels == 3) { - if(sample->actual.format == AUDIO_U8) - format = AL_FORMAT_MONO8; - else if(sample->actual.format == AUDIO_S16SYS) - format = AL_FORMAT_MONO16; - else - { - fprintf(stderr, "Unsupported sample format: 0x%04x\n", sample->actual.format); - Sound_FreeSample(sample); - return 0; - } + if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + format = AL_FORMAT_BFORMAT2D_16; } - else if(sample->actual.channels == 2) + else if(sfinfo.channels == 4) { - if(sample->actual.format == AUDIO_U8) - format = AL_FORMAT_STEREO8; - else if(sample->actual.format == AUDIO_S16SYS) - format = AL_FORMAT_STEREO16; - else - { - fprintf(stderr, "Unsupported sample format: 0x%04x\n", sample->actual.format); - Sound_FreeSample(sample); - return 0; - } + if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + format = AL_FORMAT_BFORMAT3D_16; } - else + if(!format) { - fprintf(stderr, "Unsupported channel count: %d\n", sample->actual.channels); - Sound_FreeSample(sample); + fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels); + sf_close(sndfile); return 0; } - /* Decode the whole audio stream to a buffer. */ - slen = Sound_DecodeAll(sample); - if(!sample->buffer || slen == 0) + /* Decode the whole audio file to a buffer. */ + membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(short)); + + num_frames = sf_readf_short(sndfile, membuf, sfinfo.frames); + if(num_frames < 1) { - fprintf(stderr, "Failed to read audio from %s\n", filename); - Sound_FreeSample(sample); + free(membuf); + sf_close(sndfile); + fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames); return 0; } + num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(short); /* Buffer the audio data into a new buffer object, then free the data and - * close the file. */ + * close the file. + */ buffer = 0; alGenBuffers(1, &buffer); - alBufferData(buffer, format, sample->buffer, slen, sample->actual.rate); - Sound_FreeSample(sample); + alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate); + + free(membuf); + sf_close(sndfile); /* Check if an error occured, and clean up if so. */ err = alGetError(); @@ -157,29 +164,25 @@ int main(int argc, char **argv) } /* Define a macro to help load the function pointers. */ -#define LOAD_PROC(x) ((x) = alGetProcAddress(#x)) - LOAD_PROC(alSourcedSOFT); - LOAD_PROC(alSource3dSOFT); - LOAD_PROC(alSourcedvSOFT); - LOAD_PROC(alGetSourcedSOFT); - LOAD_PROC(alGetSource3dSOFT); - LOAD_PROC(alGetSourcedvSOFT); - LOAD_PROC(alSourcei64SOFT); - LOAD_PROC(alSource3i64SOFT); - LOAD_PROC(alSourcei64vSOFT); - LOAD_PROC(alGetSourcei64SOFT); - LOAD_PROC(alGetSource3i64SOFT); - LOAD_PROC(alGetSourcei64vSOFT); +#define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) + LOAD_PROC(LPALSOURCEDSOFT, alSourcedSOFT); + LOAD_PROC(LPALSOURCE3DSOFT, alSource3dSOFT); + LOAD_PROC(LPALSOURCEDVSOFT, alSourcedvSOFT); + LOAD_PROC(LPALGETSOURCEDSOFT, alGetSourcedSOFT); + LOAD_PROC(LPALGETSOURCE3DSOFT, alGetSource3dSOFT); + LOAD_PROC(LPALGETSOURCEDVSOFT, alGetSourcedvSOFT); + LOAD_PROC(LPALSOURCEI64SOFT, alSourcei64SOFT); + LOAD_PROC(LPALSOURCE3I64SOFT, alSource3i64SOFT); + LOAD_PROC(LPALSOURCEI64VSOFT, alSourcei64vSOFT); + LOAD_PROC(LPALGETSOURCEI64SOFT, alGetSourcei64SOFT); + LOAD_PROC(LPALGETSOURCE3I64SOFT, alGetSource3i64SOFT); + LOAD_PROC(LPALGETSOURCEI64VSOFT, alGetSourcei64vSOFT); #undef LOAD_PROC - /* Initialize SDL_sound. */ - Sound_Init(); - /* Load the sound into a buffer. */ buffer = LoadSound(argv[0]); if(!buffer) { - Sound_Quit(); CloseAL(); return 1; } @@ -187,7 +190,7 @@ int main(int argc, char **argv) /* Create the source to play the sound with. */ source = 0; alGenSources(1, &source); - alSourcei(source, AL_BUFFER, buffer); + alSourcei(source, AL_BUFFER, (ALint)buffer); assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Play the sound until it finishes. */ @@ -205,11 +208,9 @@ int main(int argc, char **argv) } while(alGetError() == AL_NO_ERROR && state == AL_PLAYING); printf("\n"); - /* All done. Delete resources, and close down SDL_sound and OpenAL. */ + /* All done. Delete resources, and close down OpenAL. */ alDeleteSources(1, &source); alDeleteBuffers(1, &buffer); - - Sound_Quit(); CloseAL(); return 0; diff --git a/modules/openal-soft/examples/alloopback.c b/modules/openal-soft/examples/alloopback.c index 16553f9..7513458 100644 --- a/modules/openal-soft/examples/alloopback.c +++ b/modules/openal-soft/examples/alloopback.c @@ -26,11 +26,14 @@ * output handling. */ -#include #include #include +#include -#include +#include "SDL.h" +#include "SDL_audio.h" +#include "SDL_error.h" +#include "SDL_stdinc.h" #include "AL/al.h" #include "AL/alc.h" @@ -146,10 +149,10 @@ int main(int argc, char *argv[]) } /* Define a macro to help load the function pointers. */ -#define LOAD_PROC(x) ((x) = alcGetProcAddress(NULL, #x)) - LOAD_PROC(alcLoopbackOpenDeviceSOFT); - LOAD_PROC(alcIsRenderFormatSupportedSOFT); - LOAD_PROC(alcRenderSamplesSOFT); +#define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alcGetProcAddress(NULL, #x))) + LOAD_PROC(LPALCLOOPBACKOPENDEVICESOFT, alcLoopbackOpenDeviceSOFT); + LOAD_PROC(LPALCISRENDERFORMATSUPPORTEDSOFT, alcIsRenderFormatSupportedSOFT); + LOAD_PROC(LPALCRENDERSAMPLESSOFT, alcRenderSamplesSOFT); #undef LOAD_PROC if(SDL_Init(SDL_INIT_AUDIO) == -1) @@ -246,7 +249,7 @@ int main(int argc, char *argv[]) /* Create the source to play the sound with. */ source = 0; alGenSources(1, &source); - alSourcei(source, AL_BUFFER, buffer); + alSourcei(source, AL_BUFFER, (ALint)buffer); assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Play the sound until it finishes. */ diff --git a/modules/openal-soft/examples/almultireverb.c b/modules/openal-soft/examples/almultireverb.c index f1b1872..a77cc59 100644 --- a/modules/openal-soft/examples/almultireverb.c +++ b/modules/openal-soft/examples/almultireverb.c @@ -29,15 +29,20 @@ * listener. */ -#include + #include +#include +#include #include +#include +#include +#include -#include +#include "sndfile.h" #include "AL/al.h" #include "AL/alc.h" -#include "AL/alext.h" +#include "AL/efx.h" #include "AL/efx-presets.h" #include "common/alhelpers.h" @@ -148,68 +153,62 @@ static int LoadEffect(ALuint effect, const EFXEAXREVERBPROPERTIES *reverb) */ static ALuint LoadSound(const char *filename) { - Sound_Sample *sample; ALenum err, format; ALuint buffer; - Uint32 slen; - - /* Open the audio file */ - sample = Sound_NewSampleFromFile(filename, NULL, 65536); - if(!sample) + SNDFILE *sndfile; + SF_INFO sfinfo; + short *membuf; + sf_count_t num_frames; + ALsizei num_bytes; + + /* Open the audio file and check that it's usable. */ + sndfile = sf_open(filename, SFM_READ, &sfinfo); + if(!sndfile) { - fprintf(stderr, "Could not open audio in %s\n", filename); + fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile)); return 0; } - - /* Get the sound format, and figure out the OpenAL format */ - if(sample->actual.channels == 1) - { - if(sample->actual.format == AUDIO_U8) - format = AL_FORMAT_MONO8; - else if(sample->actual.format == AUDIO_S16SYS) - format = AL_FORMAT_MONO16; - else - { - fprintf(stderr, "Unsupported sample format: 0x%04x\n", sample->actual.format); - Sound_FreeSample(sample); - return 0; - } - } - else if(sample->actual.channels == 2) + if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(short))/sfinfo.channels) { - if(sample->actual.format == AUDIO_U8) - format = AL_FORMAT_STEREO8; - else if(sample->actual.format == AUDIO_S16SYS) - format = AL_FORMAT_STEREO16; - else - { - fprintf(stderr, "Unsupported sample format: 0x%04x\n", sample->actual.format); - Sound_FreeSample(sample); - return 0; - } + fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames); + sf_close(sndfile); + return 0; } + + /* Get the sound format, and figure out the OpenAL format */ + if(sfinfo.channels == 1) + format = AL_FORMAT_MONO16; + else if(sfinfo.channels == 2) + format = AL_FORMAT_STEREO16; else { - fprintf(stderr, "Unsupported channel count: %d\n", sample->actual.channels); - Sound_FreeSample(sample); + fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels); + sf_close(sndfile); return 0; } - /* Decode the whole audio stream to a buffer. */ - slen = Sound_DecodeAll(sample); - if(!sample->buffer || slen == 0) + /* Decode the whole audio file to a buffer. */ + membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(short)); + + num_frames = sf_readf_short(sndfile, membuf, sfinfo.frames); + if(num_frames < 1) { - fprintf(stderr, "Failed to read audio from %s\n", filename); - Sound_FreeSample(sample); + free(membuf); + sf_close(sndfile); + fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames); return 0; } + num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(short); /* Buffer the audio data into a new buffer object, then free the data and - * close the file. */ + * close the file. + */ buffer = 0; alGenBuffers(1, &buffer); - alBufferData(buffer, format, sample->buffer, slen, sample->actual.rate); - Sound_FreeSample(sample); + alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate); + + free(membuf); + sf_close(sndfile); /* Check if an error occured, and clean up if so. */ err = alGetError(); @@ -440,8 +439,8 @@ static void UpdateListenerAndEffects(float timediff, const ALuint slots[2], cons } /* Finally, update the effect slots with the updated effect parameters. */ - alAuxiliaryEffectSloti(slots[0], AL_EFFECTSLOT_EFFECT, effects[0]); - alAuxiliaryEffectSloti(slots[1], AL_EFFECTSLOT_EFFECT, effects[1]); + alAuxiliaryEffectSloti(slots[0], AL_EFFECTSLOT_EFFECT, (ALint)effects[0]); + alAuxiliaryEffectSloti(slots[1], AL_EFFECTSLOT_EFFECT, (ALint)effects[1]); } @@ -520,53 +519,49 @@ int main(int argc, char **argv) } /* Define a macro to help load the function pointers. */ -#define LOAD_PROC(x) ((x) = alGetProcAddress(#x)) - LOAD_PROC(alGenFilters); - LOAD_PROC(alDeleteFilters); - LOAD_PROC(alIsFilter); - LOAD_PROC(alFilteri); - LOAD_PROC(alFilteriv); - LOAD_PROC(alFilterf); - LOAD_PROC(alFilterfv); - LOAD_PROC(alGetFilteri); - LOAD_PROC(alGetFilteriv); - LOAD_PROC(alGetFilterf); - LOAD_PROC(alGetFilterfv); - - LOAD_PROC(alGenEffects); - LOAD_PROC(alDeleteEffects); - LOAD_PROC(alIsEffect); - LOAD_PROC(alEffecti); - LOAD_PROC(alEffectiv); - LOAD_PROC(alEffectf); - LOAD_PROC(alEffectfv); - LOAD_PROC(alGetEffecti); - LOAD_PROC(alGetEffectiv); - LOAD_PROC(alGetEffectf); - LOAD_PROC(alGetEffectfv); - - LOAD_PROC(alGenAuxiliaryEffectSlots); - LOAD_PROC(alDeleteAuxiliaryEffectSlots); - LOAD_PROC(alIsAuxiliaryEffectSlot); - LOAD_PROC(alAuxiliaryEffectSloti); - LOAD_PROC(alAuxiliaryEffectSlotiv); - LOAD_PROC(alAuxiliaryEffectSlotf); - LOAD_PROC(alAuxiliaryEffectSlotfv); - LOAD_PROC(alGetAuxiliaryEffectSloti); - LOAD_PROC(alGetAuxiliaryEffectSlotiv); - LOAD_PROC(alGetAuxiliaryEffectSlotf); - LOAD_PROC(alGetAuxiliaryEffectSlotfv); +#define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) + LOAD_PROC(LPALGENFILTERS, alGenFilters); + LOAD_PROC(LPALDELETEFILTERS, alDeleteFilters); + LOAD_PROC(LPALISFILTER, alIsFilter); + LOAD_PROC(LPALFILTERI, alFilteri); + LOAD_PROC(LPALFILTERIV, alFilteriv); + LOAD_PROC(LPALFILTERF, alFilterf); + LOAD_PROC(LPALFILTERFV, alFilterfv); + LOAD_PROC(LPALGETFILTERI, alGetFilteri); + LOAD_PROC(LPALGETFILTERIV, alGetFilteriv); + LOAD_PROC(LPALGETFILTERF, alGetFilterf); + LOAD_PROC(LPALGETFILTERFV, alGetFilterfv); + + LOAD_PROC(LPALGENEFFECTS, alGenEffects); + LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects); + LOAD_PROC(LPALISEFFECT, alIsEffect); + LOAD_PROC(LPALEFFECTI, alEffecti); + LOAD_PROC(LPALEFFECTIV, alEffectiv); + LOAD_PROC(LPALEFFECTF, alEffectf); + LOAD_PROC(LPALEFFECTFV, alEffectfv); + LOAD_PROC(LPALGETEFFECTI, alGetEffecti); + LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv); + LOAD_PROC(LPALGETEFFECTF, alGetEffectf); + LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv); + + LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots); + LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots); + LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv); #undef LOAD_PROC - /* Initialize SDL_sound. */ - Sound_Init(); - /* Load the sound into a buffer. */ buffer = LoadSound(argv[0]); if(!buffer) { CloseAL(); - Sound_Quit(); return 1; } @@ -582,7 +577,6 @@ int main(int argc, char **argv) { alDeleteEffects(2, effects); alDeleteBuffers(1, &buffer); - Sound_Quit(); CloseAL(); return 1; } @@ -595,8 +589,8 @@ int main(int argc, char **argv) * effect properties. Modifying or deleting the effect object afterward * won't directly affect the effect slot until they're reapplied like this. */ - alAuxiliaryEffectSloti(slots[0], AL_EFFECTSLOT_EFFECT, effects[0]); - alAuxiliaryEffectSloti(slots[1], AL_EFFECTSLOT_EFFECT, effects[1]); + alAuxiliaryEffectSloti(slots[0], AL_EFFECTSLOT_EFFECT, (ALint)effects[0]); + alAuxiliaryEffectSloti(slots[1], AL_EFFECTSLOT_EFFECT, (ALint)effects[1]); assert(alGetError()==AL_NO_ERROR && "Failed to set effect slot"); /* For the purposes of this example, prepare a filter that optionally @@ -618,8 +612,8 @@ int main(int argc, char **argv) alGenSources(1, &source); alSourcei(source, AL_LOOPING, AL_TRUE); alSource3f(source, AL_POSITION, -5.0f, 0.0f, -2.0f); - alSourcei(source, AL_DIRECT_FILTER, direct_filter); - alSourcei(source, AL_BUFFER, buffer); + alSourcei(source, AL_DIRECT_FILTER, (ALint)direct_filter); + alSourcei(source, AL_BUFFER, (ALint)buffer); /* Connect the source to the effect slots. Here, we connect source send 0 * to Zone 0's slot, and send 1 to Zone 1's slot. Filters can be specified @@ -628,8 +622,8 @@ int main(int argc, char **argv) * can only see a zone through a window or thin wall may be attenuated for * that zone. */ - alSource3i(source, AL_AUXILIARY_SEND_FILTER, slots[0], 0, AL_FILTER_NULL); - alSource3i(source, AL_AUXILIARY_SEND_FILTER, slots[1], 1, AL_FILTER_NULL); + alSource3i(source, AL_AUXILIARY_SEND_FILTER, (ALint)slots[0], 0, AL_FILTER_NULL); + alSource3i(source, AL_AUXILIARY_SEND_FILTER, (ALint)slots[1], 1, AL_FILTER_NULL); assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Get the current time as the base for timing in the main loop. */ @@ -650,12 +644,12 @@ int main(int argc, char **argv) * Convert the difference to seconds. */ curtime = altime_get(); - timediff = (ALfloat)(curtime - basetime) / 1000.0f; + timediff = (float)(curtime - basetime) / 1000.0f; /* Avoid negative time deltas, in case of non-monotonic clocks. */ if(timediff < 0.0f) timediff = 0.0f; - else while(timediff >= 4.0f*((loops&1)+1)) + else while(timediff >= 4.0f*(float)((loops&1)+1)) { /* For this example, each transition occurs over 4 seconds, and * there's 2 transitions per cycle. @@ -681,14 +675,13 @@ int main(int argc, char **argv) alGetSourcei(source, AL_SOURCE_STATE, &state); } while(alGetError() == AL_NO_ERROR && state == AL_PLAYING && loops < MaxTransitions); - /* All done. Delete resources, and close down SDL_sound and OpenAL. */ + /* All done. Delete resources, and close down OpenAL. */ alDeleteSources(1, &source); alDeleteAuxiliaryEffectSlots(2, slots); alDeleteEffects(2, effects); alDeleteFilters(1, &direct_filter); alDeleteBuffers(1, &buffer); - Sound_Quit(); CloseAL(); return 0; diff --git a/modules/openal-soft/examples/alplay.c b/modules/openal-soft/examples/alplay.c index 81cb56d..56d5434 100644 --- a/modules/openal-soft/examples/alplay.c +++ b/modules/openal-soft/examples/alplay.c @@ -24,13 +24,16 @@ /* This file contains an example for playing a sound buffer. */ -#include #include +#include +#include +#include +#include -#include +#include "sndfile.h" #include "AL/al.h" -#include "AL/alc.h" +#include "AL/alext.h" #include "common/alhelpers.h" @@ -40,68 +43,73 @@ */ static ALuint LoadSound(const char *filename) { - Sound_Sample *sample; ALenum err, format; ALuint buffer; - Uint32 slen; - - /* Open the audio file */ - sample = Sound_NewSampleFromFile(filename, NULL, 65536); - if(!sample) + SNDFILE *sndfile; + SF_INFO sfinfo; + short *membuf; + sf_count_t num_frames; + ALsizei num_bytes; + + /* Open the audio file and check that it's usable. */ + sndfile = sf_open(filename, SFM_READ, &sfinfo); + if(!sndfile) { - fprintf(stderr, "Could not open audio in %s\n", filename); + fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile)); + return 0; + } + if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(short))/sfinfo.channels) + { + fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames); + sf_close(sndfile); return 0; } /* Get the sound format, and figure out the OpenAL format */ - if(sample->actual.channels == 1) + format = AL_NONE; + if(sfinfo.channels == 1) + format = AL_FORMAT_MONO16; + else if(sfinfo.channels == 2) + format = AL_FORMAT_STEREO16; + else if(sfinfo.channels == 3) { - if(sample->actual.format == AUDIO_U8) - format = AL_FORMAT_MONO8; - else if(sample->actual.format == AUDIO_S16SYS) - format = AL_FORMAT_MONO16; - else - { - fprintf(stderr, "Unsupported sample format: 0x%04x\n", sample->actual.format); - Sound_FreeSample(sample); - return 0; - } + if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + format = AL_FORMAT_BFORMAT2D_16; } - else if(sample->actual.channels == 2) + else if(sfinfo.channels == 4) { - if(sample->actual.format == AUDIO_U8) - format = AL_FORMAT_STEREO8; - else if(sample->actual.format == AUDIO_S16SYS) - format = AL_FORMAT_STEREO16; - else - { - fprintf(stderr, "Unsupported sample format: 0x%04x\n", sample->actual.format); - Sound_FreeSample(sample); - return 0; - } + if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + format = AL_FORMAT_BFORMAT3D_16; } - else + if(!format) { - fprintf(stderr, "Unsupported channel count: %d\n", sample->actual.channels); - Sound_FreeSample(sample); + fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels); + sf_close(sndfile); return 0; } - /* Decode the whole audio stream to a buffer. */ - slen = Sound_DecodeAll(sample); - if(!sample->buffer || slen == 0) + /* Decode the whole audio file to a buffer. */ + membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(short)); + + num_frames = sf_readf_short(sndfile, membuf, sfinfo.frames); + if(num_frames < 1) { - fprintf(stderr, "Failed to read audio from %s\n", filename); - Sound_FreeSample(sample); + free(membuf); + sf_close(sndfile); + fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames); return 0; } + num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(short); /* Buffer the audio data into a new buffer object, then free the data and - * close the file. */ + * close the file. + */ buffer = 0; alGenBuffers(1, &buffer); - alBufferData(buffer, format, sample->buffer, slen, sample->actual.rate); - Sound_FreeSample(sample); + alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate); + + free(membuf); + sf_close(sndfile); /* Check if an error occured, and clean up if so. */ err = alGetError(); @@ -135,14 +143,10 @@ int main(int argc, char **argv) if(InitAL(&argv, &argc) != 0) return 1; - /* Initialize SDL_sound. */ - Sound_Init(); - /* Load the sound into a buffer. */ buffer = LoadSound(argv[0]); if(!buffer) { - Sound_Quit(); CloseAL(); return 1; } @@ -150,7 +154,7 @@ int main(int argc, char **argv) /* Create the source to play the sound with. */ source = 0; alGenSources(1, &source); - alSourcei(source, AL_BUFFER, buffer); + alSourcei(source, AL_BUFFER, (ALint)buffer); assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Play the sound until it finishes. */ @@ -166,11 +170,10 @@ int main(int argc, char **argv) } while(alGetError() == AL_NO_ERROR && state == AL_PLAYING); printf("\n"); - /* All done. Delete resources, and close down SDL_sound and OpenAL. */ + /* All done. Delete resources, and close down OpenAL. */ alDeleteSources(1, &source); alDeleteBuffers(1, &buffer); - Sound_Quit(); CloseAL(); return 0; diff --git a/modules/openal-soft/examples/alrecord.c b/modules/openal-soft/examples/alrecord.c index 30f5f79..0389449 100644 --- a/modules/openal-soft/examples/alrecord.c +++ b/modules/openal-soft/examples/alrecord.c @@ -28,7 +28,6 @@ #include #include #include -#include #include "AL/al.h" #include "AL/alc.h" @@ -36,6 +35,8 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + #if defined(_WIN64) #define SZFMT "%I64u" @@ -55,13 +56,19 @@ static float msvc_strtof(const char *str, char **end) static void fwrite16le(ALushort val, FILE *f) { - ALubyte data[2] = { val&0xff, (val>>8)&0xff }; + ALubyte data[2]; + data[0] = (ALubyte)(val&0xff); + data[1] = (ALubyte)(val>>8); fwrite(data, 1, 2, f); } static void fwrite32le(ALuint val, FILE *f) { - ALubyte data[4] = { val&0xff, (val>>8)&0xff, (val>>16)&0xff, (val>>24)&0xff }; + ALubyte data[4]; + data[0] = (ALubyte)(val&0xff); + data[1] = (ALubyte)((val>>8)&0xff); + data[2] = (ALubyte)((val>>16)&0xff); + data[3] = (ALubyte)(val>>24); fwrite(data, 1, 4, f); } @@ -74,9 +81,9 @@ typedef struct Recorder { ALuint mDataSize; float mRecTime; - int mChannels; - int mBits; - int mSampleRate; + ALuint mChannels; + ALuint mBits; + ALuint mSampleRate; ALuint mFrameSize; ALbyte *mBuffer; ALsizei mBufferSize; @@ -140,7 +147,7 @@ int main(int argc, char **argv) return 1; } - recorder.mChannels = strtol(argv[1], &end, 0); + recorder.mChannels = (ALuint)strtoul(argv[1], &end, 0); if((recorder.mChannels != 1 && recorder.mChannels != 2) || (end && *end != '\0')) { fprintf(stderr, "Invalid channels: %s\n", argv[1]); @@ -157,7 +164,7 @@ int main(int argc, char **argv) return 1; } - recorder.mBits = strtol(argv[1], &end, 0); + recorder.mBits = (ALuint)strtoul(argv[1], &end, 0); if((recorder.mBits != 8 && recorder.mBits != 16 && recorder.mBits != 32) || (end && *end != '\0')) { @@ -175,7 +182,7 @@ int main(int argc, char **argv) return 1; } - recorder.mSampleRate = strtol(argv[1], &end, 0); + recorder.mSampleRate = (ALuint)strtoul(argv[1], &end, 0); if(!(recorder.mSampleRate >= 8000 && recorder.mSampleRate <= 96000) || (end && *end != '\0')) { fprintf(stderr, "Invalid sample rate: %s\n", argv[1]); @@ -286,15 +293,15 @@ int main(int argc, char **argv) // 16-bit val, format type id (1 = integer PCM, 3 = float PCM) fwrite16le((recorder.mBits == 32) ? 0x0003 : 0x0001, recorder.mFile); // 16-bit val, channel count - fwrite16le(recorder.mChannels, recorder.mFile); + fwrite16le((ALushort)recorder.mChannels, recorder.mFile); // 32-bit val, frequency fwrite32le(recorder.mSampleRate, recorder.mFile); // 32-bit val, bytes per second fwrite32le(recorder.mSampleRate * recorder.mFrameSize, recorder.mFile); // 16-bit val, frame size - fwrite16le(recorder.mFrameSize, recorder.mFile); + fwrite16le((ALushort)recorder.mFrameSize, recorder.mFile); // 16-bit val, bits per sample - fwrite16le(recorder.mBits, recorder.mFile); + fwrite16le((ALushort)recorder.mBits, recorder.mFile); // 16-bit val, extra byte count fwrite16le(0, recorder.mFile); @@ -317,6 +324,7 @@ int main(int argc, char **argv) recorder.mRecTime, (recorder.mRecTime != 1.0f) ? "s" : "" ); + err = ALC_NO_ERROR; alcCaptureStart(recorder.mDevice); while((double)recorder.mDataSize/(double)recorder.mSampleRate < recorder.mRecTime && (err=alcGetError(recorder.mDevice)) == ALC_NO_ERROR && !ferror(recorder.mFile)) @@ -331,7 +339,7 @@ int main(int argc, char **argv) } if(count > recorder.mBufferSize) { - ALbyte *data = calloc(recorder.mFrameSize, count); + ALbyte *data = calloc(recorder.mFrameSize, (ALuint)count); free(recorder.mBuffer); recorder.mBuffer = data; recorder.mBufferSize = count; @@ -365,7 +373,7 @@ int main(int argc, char **argv) } } #endif - recorder.mDataSize += (ALuint)fwrite(recorder.mBuffer, recorder.mFrameSize, count, + recorder.mDataSize += (ALuint)fwrite(recorder.mBuffer, recorder.mFrameSize, (ALuint)count, recorder.mFile); } alcCaptureStop(recorder.mDevice); @@ -385,7 +393,7 @@ int main(int argc, char **argv) { fwrite32le(recorder.mDataSize*recorder.mFrameSize, recorder.mFile); if(fseek(recorder.mFile, 4, SEEK_SET) == 0) - fwrite32le(total_size - 8, recorder.mFile); + fwrite32le((ALuint)total_size - 8, recorder.mFile); } fclose(recorder.mFile); diff --git a/modules/openal-soft/examples/alreverb.c b/modules/openal-soft/examples/alreverb.c index e6c9e60..11a3ac6 100644 --- a/modules/openal-soft/examples/alreverb.c +++ b/modules/openal-soft/examples/alreverb.c @@ -24,14 +24,18 @@ /* This file contains an example for applying reverb to a sound. */ -#include #include +#include +#include +#include +#include -#include +#include "sndfile.h" #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" +#include "AL/efx.h" #include "AL/efx-presets.h" #include "common/alhelpers.h" @@ -147,68 +151,73 @@ static ALuint LoadEffect(const EFXEAXREVERBPROPERTIES *reverb) */ static ALuint LoadSound(const char *filename) { - Sound_Sample *sample; ALenum err, format; ALuint buffer; - Uint32 slen; - - /* Open the audio file */ - sample = Sound_NewSampleFromFile(filename, NULL, 65536); - if(!sample) + SNDFILE *sndfile; + SF_INFO sfinfo; + short *membuf; + sf_count_t num_frames; + ALsizei num_bytes; + + /* Open the audio file and check that it's usable. */ + sndfile = sf_open(filename, SFM_READ, &sfinfo); + if(!sndfile) + { + fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile)); + return 0; + } + if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(short))/sfinfo.channels) { - fprintf(stderr, "Could not open audio in %s\n", filename); + fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames); + sf_close(sndfile); return 0; } /* Get the sound format, and figure out the OpenAL format */ - if(sample->actual.channels == 1) + format = AL_NONE; + if(sfinfo.channels == 1) + format = AL_FORMAT_MONO16; + else if(sfinfo.channels == 2) + format = AL_FORMAT_STEREO16; + else if(sfinfo.channels == 3) { - if(sample->actual.format == AUDIO_U8) - format = AL_FORMAT_MONO8; - else if(sample->actual.format == AUDIO_S16SYS) - format = AL_FORMAT_MONO16; - else - { - fprintf(stderr, "Unsupported sample format: 0x%04x\n", sample->actual.format); - Sound_FreeSample(sample); - return 0; - } + if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + format = AL_FORMAT_BFORMAT2D_16; } - else if(sample->actual.channels == 2) + else if(sfinfo.channels == 4) { - if(sample->actual.format == AUDIO_U8) - format = AL_FORMAT_STEREO8; - else if(sample->actual.format == AUDIO_S16SYS) - format = AL_FORMAT_STEREO16; - else - { - fprintf(stderr, "Unsupported sample format: 0x%04x\n", sample->actual.format); - Sound_FreeSample(sample); - return 0; - } + if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + format = AL_FORMAT_BFORMAT3D_16; } - else + if(!format) { - fprintf(stderr, "Unsupported channel count: %d\n", sample->actual.channels); - Sound_FreeSample(sample); + fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels); + sf_close(sndfile); return 0; } - /* Decode the whole audio stream to a buffer. */ - slen = Sound_DecodeAll(sample); - if(!sample->buffer || slen == 0) + /* Decode the whole audio file to a buffer. */ + membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(short)); + + num_frames = sf_readf_short(sndfile, membuf, sfinfo.frames); + if(num_frames < 1) { - fprintf(stderr, "Failed to read audio from %s\n", filename); - Sound_FreeSample(sample); + free(membuf); + sf_close(sndfile); + fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames); return 0; } + num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(short); /* Buffer the audio data into a new buffer object, then free the data and - * close the file. */ + * close the file. + */ buffer = 0; alGenBuffers(1, &buffer); - alBufferData(buffer, format, sample->buffer, slen, sample->actual.rate); - Sound_FreeSample(sample); + alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate); + + free(membuf); + sf_close(sndfile); /* Check if an error occured, and clean up if so. */ err = alGetError(); @@ -250,41 +259,37 @@ int main(int argc, char **argv) } /* Define a macro to help load the function pointers. */ -#define LOAD_PROC(x) ((x) = alGetProcAddress(#x)) - LOAD_PROC(alGenEffects); - LOAD_PROC(alDeleteEffects); - LOAD_PROC(alIsEffect); - LOAD_PROC(alEffecti); - LOAD_PROC(alEffectiv); - LOAD_PROC(alEffectf); - LOAD_PROC(alEffectfv); - LOAD_PROC(alGetEffecti); - LOAD_PROC(alGetEffectiv); - LOAD_PROC(alGetEffectf); - LOAD_PROC(alGetEffectfv); - - LOAD_PROC(alGenAuxiliaryEffectSlots); - LOAD_PROC(alDeleteAuxiliaryEffectSlots); - LOAD_PROC(alIsAuxiliaryEffectSlot); - LOAD_PROC(alAuxiliaryEffectSloti); - LOAD_PROC(alAuxiliaryEffectSlotiv); - LOAD_PROC(alAuxiliaryEffectSlotf); - LOAD_PROC(alAuxiliaryEffectSlotfv); - LOAD_PROC(alGetAuxiliaryEffectSloti); - LOAD_PROC(alGetAuxiliaryEffectSlotiv); - LOAD_PROC(alGetAuxiliaryEffectSlotf); - LOAD_PROC(alGetAuxiliaryEffectSlotfv); +#define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) + LOAD_PROC(LPALGENEFFECTS, alGenEffects); + LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects); + LOAD_PROC(LPALISEFFECT, alIsEffect); + LOAD_PROC(LPALEFFECTI, alEffecti); + LOAD_PROC(LPALEFFECTIV, alEffectiv); + LOAD_PROC(LPALEFFECTF, alEffectf); + LOAD_PROC(LPALEFFECTFV, alEffectfv); + LOAD_PROC(LPALGETEFFECTI, alGetEffecti); + LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv); + LOAD_PROC(LPALGETEFFECTF, alGetEffectf); + LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv); + + LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots); + LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots); + LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv); #undef LOAD_PROC - /* Initialize SDL_sound. */ - Sound_Init(); - /* Load the sound into a buffer. */ buffer = LoadSound(argv[0]); if(!buffer) { CloseAL(); - Sound_Quit(); return 1; } @@ -293,7 +298,6 @@ int main(int argc, char **argv) if(!effect) { alDeleteBuffers(1, &buffer); - Sound_Quit(); CloseAL(); return 1; } @@ -307,18 +311,18 @@ int main(int argc, char **argv) * effectively copies the effect properties. You can modify or delete the * effect object afterward without affecting the effect slot. */ - alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, effect); + alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, (ALint)effect); assert(alGetError()==AL_NO_ERROR && "Failed to set effect slot"); /* Create the source to play the sound with. */ source = 0; alGenSources(1, &source); - alSourcei(source, AL_BUFFER, buffer); + alSourcei(source, AL_BUFFER, (ALint)buffer); /* Connect the source to the effect slot. This tells the source to use the * effect slot 'slot', on send #0 with the AL_FILTER_NULL filter object. */ - alSource3i(source, AL_AUXILIARY_SEND_FILTER, slot, 0, AL_FILTER_NULL); + alSource3i(source, AL_AUXILIARY_SEND_FILTER, (ALint)slot, 0, AL_FILTER_NULL); assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Play the sound until it finishes. */ @@ -328,13 +332,12 @@ int main(int argc, char **argv) alGetSourcei(source, AL_SOURCE_STATE, &state); } while(alGetError() == AL_NO_ERROR && state == AL_PLAYING); - /* All done. Delete resources, and close down SDL_sound and OpenAL. */ + /* All done. Delete resources, and close down OpenAL. */ alDeleteSources(1, &source); alDeleteAuxiliaryEffectSlots(1, &slot); alDeleteEffects(1, &effect); alDeleteBuffers(1, &buffer); - Sound_Quit(); CloseAL(); return 0; diff --git a/modules/openal-soft/examples/alstream.c b/modules/openal-soft/examples/alstream.c index 68115e8..2e427a3 100644 --- a/modules/openal-soft/examples/alstream.c +++ b/modules/openal-soft/examples/alstream.c @@ -24,33 +24,25 @@ /* This file contains a relatively simple streaming audio player. */ -#include -#include -#include -#include #include +#include +#include +#include +#include -#include +#include "sndfile.h" #include "AL/al.h" -#include "AL/alc.h" #include "AL/alext.h" #include "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 - /* Define the number of buffers and buffer size (in milliseconds) to use. 4 - * buffers with 200ms each gives a nice per-chunk size, and lets the queue last - * for almost one second. */ + * buffers with 8192 samples each gives a nice per-chunk size, and lets the + * queue last for almost one second at 44.1khz. */ #define NUM_BUFFERS 4 -#define BUFFER_TIME_MS 200 +#define BUFFER_SAMPLES 8192 typedef struct StreamPlayer { /* These are the buffers and source to play out through OpenAL with */ @@ -58,11 +50,12 @@ typedef struct StreamPlayer { ALuint source; /* Handle for the audio file */ - Sound_Sample *sample; + SNDFILE *sndfile; + SF_INFO sfinfo; + short *membuf; - /* The format of the output stream */ + /* The format of the output stream (sample rate is in sfinfo) */ ALenum format; - ALsizei srate; } StreamPlayer; static StreamPlayer *NewPlayer(void); @@ -119,80 +112,63 @@ static void DeletePlayer(StreamPlayer *player) * it will be closed first. */ static int OpenPlayerFile(StreamPlayer *player, const char *filename) { - Uint32 frame_size; + size_t frame_size; ClosePlayerFile(player); - /* Open the file and get the first stream from it */ - player->sample = Sound_NewSampleFromFile(filename, NULL, 0); - if(!player->sample) + /* Open the audio file and check that it's usable. */ + player->sndfile = sf_open(filename, SFM_READ, &player->sfinfo); + if(!player->sndfile) { - fprintf(stderr, "Could not open audio in %s\n", filename); - goto error; + fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(NULL)); + return 0; } - /* Get the stream format, and figure out the OpenAL format */ - if(player->sample->actual.channels == 1) + /* Get the sound format, and figure out the OpenAL format */ + if(player->sfinfo.channels == 1) + player->format = AL_FORMAT_MONO16; + else if(player->sfinfo.channels == 2) + player->format = AL_FORMAT_STEREO16; + else if(player->sfinfo.channels == 3) { - if(player->sample->actual.format == AUDIO_U8) - player->format = AL_FORMAT_MONO8; - else if(player->sample->actual.format == AUDIO_S16SYS) - player->format = AL_FORMAT_MONO16; - else - { - fprintf(stderr, "Unsupported sample format: 0x%04x\n", player->sample->actual.format); - goto error; - } + if(sf_command(player->sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + player->format = AL_FORMAT_BFORMAT2D_16; } - else if(player->sample->actual.channels == 2) + else if(player->sfinfo.channels == 4) { - if(player->sample->actual.format == AUDIO_U8) - player->format = AL_FORMAT_STEREO8; - else if(player->sample->actual.format == AUDIO_S16SYS) - player->format = AL_FORMAT_STEREO16; - else - { - fprintf(stderr, "Unsupported sample format: 0x%04x\n", player->sample->actual.format); - goto error; - } + if(sf_command(player->sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + player->format = AL_FORMAT_BFORMAT3D_16; } - else + if(!player->format) { - fprintf(stderr, "Unsupported channel count: %d\n", player->sample->actual.channels); - goto error; + fprintf(stderr, "Unsupported channel count: %d\n", player->sfinfo.channels); + sf_close(player->sndfile); + player->sndfile = NULL; + return 0; } - player->srate = player->sample->actual.rate; - frame_size = player->sample->actual.channels * - SDL_AUDIO_BITSIZE(player->sample->actual.format) / 8; - - /* Set the buffer size, given the desired millisecond length. */ - Sound_SetBufferSize(player->sample, (Uint32)((Uint64)player->srate*BUFFER_TIME_MS/1000) * - frame_size); + frame_size = (size_t)(BUFFER_SAMPLES * player->sfinfo.channels) * sizeof(short); + player->membuf = malloc(frame_size); return 1; - -error: - if(player->sample) - Sound_FreeSample(player->sample); - player->sample = NULL; - - return 0; } /* Closes the audio file stream */ static void ClosePlayerFile(StreamPlayer *player) { - if(player->sample) - Sound_FreeSample(player->sample); - player->sample = NULL; + if(player->sndfile) + sf_close(player->sndfile); + player->sndfile = NULL; + + free(player->membuf); + player->membuf = NULL; } /* Prebuffers some audio from the file, and starts playing the source */ static int StartPlayer(StreamPlayer *player) { - size_t i; + ALsizei i; /* Rewind the source position and clear the buffer queue */ alSourceRewind(player->source); @@ -202,11 +178,12 @@ static int StartPlayer(StreamPlayer *player) for(i = 0;i < NUM_BUFFERS;i++) { /* Get some data to give it to the buffer */ - Uint32 slen = Sound_Decode(player->sample); - if(slen == 0) break; + sf_count_t slen = sf_readf_short(player->sndfile, player->membuf, BUFFER_SAMPLES); + if(slen < 1) break; - alBufferData(player->buffers[i], player->format, - player->sample->buffer, slen, player->srate); + slen *= player->sfinfo.channels * (sf_count_t)sizeof(short); + alBufferData(player->buffers[i], player->format, player->membuf, (ALsizei)slen, + player->sfinfo.samplerate); } if(alGetError() != AL_NO_ERROR) { @@ -243,21 +220,19 @@ static int UpdatePlayer(StreamPlayer *player) while(processed > 0) { ALuint bufid; - Uint32 slen; + sf_count_t slen; alSourceUnqueueBuffers(player->source, 1, &bufid); processed--; - if((player->sample->flags&(SOUND_SAMPLEFLAG_EOF|SOUND_SAMPLEFLAG_ERROR))) - continue; - /* Read the next chunk of data, refill the buffer, and queue it * back on the source */ - slen = Sound_Decode(player->sample); + slen = sf_readf_short(player->sndfile, player->membuf, BUFFER_SAMPLES); if(slen > 0) { - alBufferData(bufid, player->format, player->sample->buffer, slen, - player->srate); + slen *= player->sfinfo.channels * (sf_count_t)sizeof(short); + alBufferData(bufid, player->format, player->membuf, (ALsizei)slen, + player->sfinfo.samplerate); alSourceQueueBuffers(player->source, 1, &bufid); } if(alGetError() != AL_NO_ERROR) @@ -305,8 +280,6 @@ int main(int argc, char **argv) if(InitAL(&argv, &argc) != 0) return 1; - Sound_Init(); - player = NewPlayer(); /* Play each file listed on the command line */ @@ -325,7 +298,7 @@ int main(int argc, char **argv) namepart = argv[i]; printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->format), - player->srate); + player->sfinfo.samplerate); fflush(stdout); if(!StartPlayer(player)) @@ -342,11 +315,10 @@ int main(int argc, char **argv) } printf("Done.\n"); - /* All files done. Delete the player, and close down SDL_sound and OpenAL */ + /* All files done. Delete the player, and close down OpenAL */ DeletePlayer(player); player = NULL; - Sound_Quit(); CloseAL(); return 0; diff --git a/modules/openal-soft/examples/alstreamcb.cpp b/modules/openal-soft/examples/alstreamcb.cpp new file mode 100644 index 0000000..e0dff4a --- /dev/null +++ b/modules/openal-soft/examples/alstreamcb.cpp @@ -0,0 +1,392 @@ +/* + * OpenAL Callback-based Stream Example + * + * Copyright (c) 2020 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 a streaming audio player using a callback buffer. */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "sndfile.h" + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "common/alhelpers.h" + + +namespace { + +using std::chrono::seconds; +using std::chrono::nanoseconds; + +LPALBUFFERCALLBACKSOFT alBufferCallbackSOFT; + +struct StreamPlayer { + /* A lockless ring-buffer (supports single-provider, single-consumer + * operation). + */ + std::unique_ptr mBufferData; + size_t mBufferDataSize{0}; + std::atomic mReadPos{0}; + std::atomic mWritePos{0}; + + /* The buffer to get the callback, and source to play with. */ + ALuint mBuffer{0}, mSource{0}; + size_t mStartOffset{0}; + + /* Handle for the audio file to decode. */ + SNDFILE *mSndfile{nullptr}; + SF_INFO mSfInfo{}; + size_t mDecoderOffset{0}; + + /* The format of the callback samples. */ + ALenum mFormat; + + StreamPlayer() + { + alGenBuffers(1, &mBuffer); + if(ALenum err{alGetError()}) + throw std::runtime_error{"alGenBuffers failed"}; + alGenSources(1, &mSource); + if(ALenum err{alGetError()}) + { + alDeleteBuffers(1, &mBuffer); + throw std::runtime_error{"alGenSources failed"}; + } + } + ~StreamPlayer() + { + alDeleteSources(1, &mSource); + alDeleteBuffers(1, &mBuffer); + if(mSndfile) + sf_close(mSndfile); + } + + void close() + { + if(mSndfile) + { + alSourceRewind(mSource); + alSourcei(mSource, AL_BUFFER, 0); + sf_close(mSndfile); + mSndfile = nullptr; + } + } + + bool open(const char *filename) + { + close(); + + /* Open the file and figure out the OpenAL format. */ + mSndfile = sf_open(filename, SFM_READ, &mSfInfo); + if(!mSndfile) + { + fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(mSndfile)); + return false; + } + + mFormat = AL_NONE; + if(mSfInfo.channels == 1) + mFormat = AL_FORMAT_MONO_FLOAT32; + else if(mSfInfo.channels == 2) + mFormat = AL_FORMAT_STEREO_FLOAT32; + else if(mSfInfo.channels == 3) + { + if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + mFormat = AL_FORMAT_BFORMAT2D_FLOAT32; + } + else if(mSfInfo.channels == 4) + { + if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + mFormat = AL_FORMAT_BFORMAT3D_FLOAT32; + } + if(!mFormat) + { + fprintf(stderr, "Unsupported channel count: %d\n", mSfInfo.channels); + sf_close(mSndfile); + mSndfile = nullptr; + + return false; + } + + /* Set a 1s ring buffer size. */ + mBufferDataSize = static_cast(mSfInfo.samplerate*mSfInfo.channels) * sizeof(float); + mBufferData.reset(new ALbyte[mBufferDataSize]); + mReadPos.store(0, std::memory_order_relaxed); + mWritePos.store(0, std::memory_order_relaxed); + mDecoderOffset = 0; + + return true; + } + + /* The actual C-style callback just forwards to the non-static method. Not + * strictly needed and the compiler will optimize it to a normal function, + * but it allows the callback implementation to have a nice 'this' pointer + * with normal member access. + */ + static ALsizei AL_APIENTRY bufferCallbackC(void *userptr, void *data, ALsizei size) + { return static_cast(userptr)->bufferCallback(data, size); } + ALsizei bufferCallback(void *data, ALsizei size) + { + /* NOTE: The callback *MUST* be real-time safe! That means no blocking, + * no allocations or deallocations, no I/O, no page faults, or calls to + * functions that could do these things (this includes calling to + * libraries like SDL_sound, libsndfile, ffmpeg, etc). Nothing should + * 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(got < size) + { + /* If the write offset == read offset, there's nothing left in the + * ring-buffer. Break from the loop and give what has been written. + */ + const size_t woffset{mWritePos.load(std::memory_order_relaxed)}; + if(woffset == roffset) break; + + /* If the write offset is behind the read offset, the readable + * portion wrapped around. Just read up to the end of the buffer in + * that case, otherwise read up to the write offset. Also limit the + * amount to copy given how much is remaining to write. + */ + size_t todo{((woffset < roffset) ? mBufferDataSize : woffset) - roffset}; + todo = std::min(todo, static_cast(size-got)); + + /* Copy from the ring buffer to the provided output buffer. Wrap + * the resulting read offset if it reached the end of the ring- + * buffer. + */ + memcpy(data, &mBufferData[roffset], todo); + data = static_cast(data) + todo; + got += static_cast(todo); + + roffset += todo; + if(roffset == mBufferDataSize) + roffset = 0; + } + /* Finally, store the updated read offset, and return how many bytes + * have been written. + */ + mReadPos.store(roffset, std::memory_order_release); + + return got; + } + + bool prepare() + { + alBufferCallbackSOFT(mBuffer, mFormat, mSfInfo.samplerate, bufferCallbackC, this); + alSourcei(mSource, AL_BUFFER, static_cast(mBuffer)); + if(ALenum err{alGetError()}) + { + fprintf(stderr, "Failed to set callback: %s (0x%04x)\n", alGetString(err), err); + return false; + } + return true; + } + + bool update() + { + ALenum state; + ALint pos; + alGetSourcei(mSource, AL_SAMPLE_OFFSET, &pos); + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + + const size_t frame_size{static_cast(mSfInfo.channels) * sizeof(float)}; + size_t woffset{mWritePos.load(std::memory_order_acquire)}; + if(state != AL_INITIAL) + { + const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; + const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) - + roffset}; + /* For a stopped (underrun) source, the current playback offset is + * the current decoder offset excluding the readable buffered data. + * For a playing/paused source, it's the source's offset including + * the playback offset the source was started with. + */ + const size_t curtime{((state==AL_STOPPED) ? (mDecoderOffset-readable) / frame_size + : (static_cast(pos) + mStartOffset/frame_size)) + / static_cast(mSfInfo.samplerate)}; + printf("\r%3zus (%3zu%% full)", curtime, readable * 100 / mBufferDataSize); + } + else + fputs("Starting...", stdout); + fflush(stdout); + + while(!sf_error(mSndfile)) + { + size_t read_bytes; + const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; + if(roffset > woffset) + { + /* 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}; + if(writable < frame_size) break; + + sf_count_t num_frames{sf_readf_float(mSndfile, + reinterpret_cast(&mBufferData[woffset]), + static_cast(writable/frame_size))}; + if(num_frames < 1) break; + + read_bytes = static_cast(num_frames) * frame_size; + 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 ? mBufferDataSize-woffset-1 : + (mBufferDataSize-woffset)}; + if(writable < frame_size) break; + + sf_count_t num_frames{sf_readf_float(mSndfile, + reinterpret_cast(&mBufferData[woffset]), + static_cast(writable/frame_size))}; + if(num_frames < 1) break; + + read_bytes = static_cast(num_frames) * frame_size; + woffset += read_bytes; + if(woffset == mBufferDataSize) + woffset = 0; + } + mWritePos.store(woffset, std::memory_order_release); + mDecoderOffset += read_bytes; + } + + if(state != AL_PLAYING && state != AL_PAUSED) + { + /* If the source is not playing or paused, it either underrun + * (AL_STOPPED) or is just getting started (AL_INITIAL). If the + * 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 : (mBufferDataSize+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; + alSourcePlay(mSource); + if(alGetError() != AL_NO_ERROR) + return false; + } + return true; + } +}; + +} // namespace + +int main(int argc, char **argv) +{ + /* A simple RAII container for OpenAL startup and shutdown. */ + struct AudioManager { + AudioManager(char ***argv_, int *argc_) + { + if(InitAL(argv_, argc_) != 0) + throw std::runtime_error{"Failed to initialize OpenAL"}; + } + ~AudioManager() { CloseAL(); } + }; + + /* Print out usage if no arguments were specified */ + if(argc < 2) + { + fprintf(stderr, "Usage: %s [-device ] \n", argv[0]); + return 1; + } + + argv++; argc--; + AudioManager almgr{&argv, &argc}; + + if(!alIsExtensionPresent("AL_SOFT_callback_buffer")) + { + fprintf(stderr, "AL_SOFT_callback_buffer extension not available\n"); + return 1; + } + + alBufferCallbackSOFT = reinterpret_cast( + alGetProcAddress("alBufferCallbackSOFT")); + + ALCint refresh{25}; + alcGetIntegerv(alcGetContextsDevice(alcGetCurrentContext()), ALC_REFRESH, 1, &refresh); + + std::unique_ptr player{new StreamPlayer{}}; + + /* Play each file listed on the command line */ + for(int i{0};i < argc;++i) + { + if(!player->open(argv[i])) + continue; + + /* Get the name portion, without the path, for display. */ + const char *namepart{strrchr(argv[i], '/')}; + if(namepart || (namepart=strrchr(argv[i], '\\'))) + ++namepart; + else + namepart = argv[i]; + + printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->mFormat), + player->mSfInfo.samplerate); + fflush(stdout); + + if(!player->prepare()) + { + player->close(); + continue; + } + + while(player->update()) + std::this_thread::sleep_for(nanoseconds{seconds{1}} / refresh); + putc('\n', stdout); + + /* All done with this file. Close it and go to the next */ + player->close(); + } + /* All done. */ + printf("Done.\n"); + + return 0; +} diff --git a/modules/openal-soft/examples/altonegen.c b/modules/openal-soft/examples/altonegen.c index 628e695..75db2d6 100644 --- a/modules/openal-soft/examples/altonegen.c +++ b/modules/openal-soft/examples/altonegen.c @@ -44,6 +44,8 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + #ifndef M_PI #define M_PI (3.14159265358979323846) #endif @@ -82,22 +84,25 @@ static void ApplySin(ALfloat *data, ALdouble g, ALuint srate, ALuint freq) ALdouble smps_per_cycle = (ALdouble)srate / freq; ALuint i; for(i = 0;i < srate;i++) - data[i] += (ALfloat)(sin(i/smps_per_cycle * 2.0*M_PI) * g); + { + ALdouble ival; + data[i] += (ALfloat)(sin(modf(i/smps_per_cycle, &ival) * 2.0*M_PI) * g); + } } /* Generates waveforms using additive synthesis. Each waveform is constructed * by summing one or more sine waves, up to (and excluding) nyquist. */ -static ALuint CreateWave(enum WaveType type, ALuint freq, ALuint srate) +static ALuint CreateWave(enum WaveType type, ALuint freq, ALuint srate, ALfloat gain) { ALuint seed = 22222; - ALint data_size; + ALuint data_size; ALfloat *data; ALuint buffer; ALenum err; ALuint i; - data_size = srate * sizeof(ALfloat); + data_size = (ALuint)(srate * sizeof(ALfloat)); data = calloc(1, data_size); switch(type) { @@ -139,10 +144,16 @@ static ALuint CreateWave(enum WaveType type, ALuint freq, ALuint srate) break; } + if(gain != 1.0f) + { + for(i = 0;i < srate;i++) + data[i] *= gain; + } + /* Buffer the audio data into a new buffer object. */ buffer = 0; alGenBuffers(1, &buffer); - alBufferData(buffer, AL_FORMAT_MONO_FLOAT32, data, data_size, srate); + alBufferData(buffer, AL_FORMAT_MONO_FLOAT32, data, (ALsizei)data_size, (ALsizei)srate); free(data); /* Check if an error occured, and clean up if so. */ @@ -170,6 +181,7 @@ int main(int argc, char *argv[]) ALint tone_freq = 1000; ALCint dev_rate; ALenum state; + ALfloat gain = 1.0f; int i; argv++; argc--; @@ -185,7 +197,8 @@ int main(int argc, char *argv[]) for(i = 0;i < argc;i++) { - if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) + if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-?") == 0 + || strcmp(argv[i], "--help") == 0) { fprintf(stderr, "OpenAL Tone Generator\n" "\n" @@ -197,6 +210,7 @@ int main(int argc, char *argv[]) " --waveform/-w Waveform type: sine (default), square, sawtooth,\n" " triangle, impulse, noise\n" " --freq/-f Tone frequency (default 1000 hz)\n" +" --gain/-g gain 0.0 to 1 (default 1)\n" " --srate/-s Sampling rate (default output rate)\n", appname ); @@ -236,6 +250,16 @@ int main(int argc, char *argv[]) tone_freq = 1; } } + else if(i+1 < argc && (strcmp(argv[i], "--gain") == 0 || strcmp(argv[i], "-g") == 0)) + { + i++; + gain = (ALfloat)atof(argv[i]); + if(gain < 0.0f || gain > 1.0f) + { + fprintf(stderr, "Invalid gain: %s (min: 0.0, max 1.0)\n", argv[i]); + gain = 1.0f; + } + } else if(i+1 < argc && (strcmp(argv[i], "--srate") == 0 || strcmp(argv[i], "-s") == 0)) { i++; @@ -257,7 +281,7 @@ int main(int argc, char *argv[]) srate = dev_rate; /* Load the sound into a buffer. */ - buffer = CreateWave(wavetype, tone_freq, srate); + buffer = CreateWave(wavetype, (ALuint)tone_freq, (ALuint)srate, gain); if(!buffer) { CloseAL(); @@ -271,7 +295,7 @@ int main(int argc, char *argv[]) /* Create the source to play the sound with. */ source = 0; alGenSources(1, &source); - alSourcei(source, AL_BUFFER, buffer); + alSourcei(source, AL_BUFFER, (ALint)buffer); assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Play the sound for a while. */ diff --git a/modules/openal-soft/examples/common/alhelpers.c b/modules/openal-soft/examples/common/alhelpers.c index 3077d0b..1d498ff 100644 --- a/modules/openal-soft/examples/common/alhelpers.c +++ b/modules/openal-soft/examples/common/alhelpers.c @@ -28,7 +28,8 @@ * finding an appropriate buffer format, and getting readable strings for * channel configs and sample types. */ -#include +#include "alhelpers.h" + #include #include #include @@ -37,8 +38,6 @@ #include "AL/alc.h" #include "AL/alext.h" -#include "alhelpers.h" - /* InitAL opens a device and sets up a context using default attributes, making * the program ready to call OpenAL functions. */ @@ -109,10 +108,18 @@ const char *FormatName(ALenum format) { switch(format) { - case AL_FORMAT_MONO8: return "Mono, U8"; - case AL_FORMAT_MONO16: return "Mono, S16"; - case AL_FORMAT_STEREO8: return "Stereo, U8"; - case AL_FORMAT_STEREO16: return "Stereo, S16"; + case AL_FORMAT_MONO8: return "Mono, U8"; + case AL_FORMAT_MONO16: return "Mono, S16"; + case AL_FORMAT_MONO_FLOAT32: return "Mono, Float32"; + case AL_FORMAT_STEREO8: return "Stereo, U8"; + case AL_FORMAT_STEREO16: return "Stereo, S16"; + case AL_FORMAT_STEREO_FLOAT32: return "Stereo, Float32"; + case AL_FORMAT_BFORMAT2D_8: return "B-Format 2D, U8"; + case AL_FORMAT_BFORMAT2D_16: return "B-Format 2D, S16"; + case AL_FORMAT_BFORMAT2D_FLOAT32: return "B-Format 2D, Float32"; + case AL_FORMAT_BFORMAT3D_8: return "B-Format 3D, U8"; + case AL_FORMAT_BFORMAT3D_16: return "B-Format 3D, S16"; + case AL_FORMAT_BFORMAT3D_FLOAT32: return "B-Format 3D, Float32"; } return "Unknown Format"; } @@ -161,12 +168,12 @@ int altime_get(void) struct timespec ts; int ret = clock_gettime(CLOCK_REALTIME, &ts); if(ret != 0) return 0; - cur_time = ts.tv_sec*1000 + ts.tv_nsec/1000000; + cur_time = (int)(ts.tv_sec*1000 + ts.tv_nsec/1000000); #else /* _POSIX_TIMERS > 0 */ struct timeval tv; int ret = gettimeofday(&tv, NULL); if(ret != 0) return 0; - cur_time = tv.tv_sec*1000 + tv.tv_usec/1000; + cur_time = (int)(tv.tv_sec*1000 + tv.tv_usec/1000); #endif if(!start_time) @@ -177,8 +184,8 @@ int altime_get(void) void al_nssleep(unsigned long nsec) { struct timespec ts, rem; - ts.tv_sec = nsec / 1000000000ul; - ts.tv_nsec = nsec % 1000000000ul; + ts.tv_sec = (time_t)(nsec / 1000000000ul); + ts.tv_nsec = (long)(nsec % 1000000000ul); while(nanosleep(&ts, &rem) == -1 && errno == EINTR) ts = rem; } diff --git a/modules/openal-soft/examples/common/alhelpers.h b/modules/openal-soft/examples/common/alhelpers.h index 5caeda3..34f7386 100644 --- a/modules/openal-soft/examples/common/alhelpers.h +++ b/modules/openal-soft/examples/common/alhelpers.h @@ -1,9 +1,7 @@ #ifndef ALHELPERS_H #define ALHELPERS_H -#include "AL/alc.h" #include "AL/al.h" -#include "AL/alext.h" #ifdef __cplusplus extern "C" { @@ -20,6 +18,19 @@ void CloseAL(void); int altime_get(void); void al_nssleep(unsigned long nsec); +/* C doesn't allow casting between function and non-function pointer types, so + * with C99 we need to use a union to reinterpret the pointer type. Pre-C99 + * still needs to use a normal cast and live with the warning (C++ is fine with + * a regular reinterpret_cast). + */ +#if __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) +#else +#define FUNCTION_CAST(T, ptr) (T)(ptr) +#endif + #ifdef __cplusplus } // extern "C" #endif diff --git a/modules/openal-soft/hrtf/Default HRTF.mhr b/modules/openal-soft/hrtf/Default HRTF.mhr new file mode 100644 index 0000000..516a678 Binary files /dev/null and b/modules/openal-soft/hrtf/Default HRTF.mhr differ diff --git a/modules/openal-soft/hrtf/default-44100.mhr b/modules/openal-soft/hrtf/default-44100.mhr deleted file mode 100644 index f7992e1..0000000 Binary files a/modules/openal-soft/hrtf/default-44100.mhr and /dev/null differ diff --git a/modules/openal-soft/hrtf/default-48000.mhr b/modules/openal-soft/hrtf/default-48000.mhr deleted file mode 100644 index 66fd4f5..0000000 Binary files a/modules/openal-soft/hrtf/default-48000.mhr and /dev/null differ diff --git a/modules/openal-soft/include/AL/al.h b/modules/openal-soft/include/AL/al.h index 413b383..8749e1b 100644 --- a/modules/openal-soft/include/AL/al.h +++ b/modules/openal-soft/include/AL/al.h @@ -22,7 +22,7 @@ extern "C" { #endif -/** Deprecated macro. */ +/* Deprecated macros. */ #define OPENAL #define ALAPI AL_API #define ALAPIENTRY AL_APIENTRY @@ -30,7 +30,7 @@ extern "C" { #define AL_ILLEGAL_ENUM AL_INVALID_ENUM #define AL_ILLEGAL_COMMAND AL_INVALID_OPERATION -/** Supported AL version. */ +/* Supported AL versions. */ #define AL_VERSION_1_0 #define AL_VERSION_1_1 @@ -234,7 +234,7 @@ typedef void ALvoid; */ #define AL_SOURCE_STATE 0x1010 -/** Source state value. */ +/* Source state values. */ #define AL_INITIAL 0x1011 #define AL_PLAYING 0x1012 #define AL_PAUSED 0x1013 @@ -300,7 +300,7 @@ typedef void ALvoid; * Source maximum distance. * Type: ALfloat * Range: [0.0 - ] - * Default: +inf + * Default: FLT_MAX * * The distance above which the source is not attenuated any further with a * clamped distance model, or where attenuation reaches 0.0 gain for linear @@ -330,15 +330,18 @@ typedef void ALvoid; */ #define AL_SOURCE_TYPE 0x1027 -/** Source type value. */ +/* Source type values. */ #define AL_STATIC 0x1028 #define AL_STREAMING 0x1029 #define AL_UNDETERMINED 0x1030 -/** Buffer format specifier. */ +/** Unsigned 8-bit mono buffer format. */ #define AL_FORMAT_MONO8 0x1100 +/** Signed 16-bit mono buffer format. */ #define AL_FORMAT_MONO16 0x1101 +/** Unsigned 8-bit stereo buffer format. */ #define AL_FORMAT_STEREO8 0x1102 +/** Signed 16-bit stereo buffer format. */ #define AL_FORMAT_STEREO16 0x1103 /** Buffer frequency (query only). */ @@ -350,11 +353,7 @@ typedef void ALvoid; /** Buffer data size (query only). */ #define AL_SIZE 0x2004 -/** - * Buffer state. - * - * Not for public use. - */ +/* Buffer state. Not for public use. */ #define AL_UNUSED 0x2010 #define AL_PENDING 0x2011 #define AL_PROCESSED 0x2012 @@ -441,7 +440,7 @@ AL_API void AL_APIENTRY alSpeedOfSound(ALfloat value); #define AL_DISTANCE_MODEL 0xD000 AL_API void AL_APIENTRY alDistanceModel(ALenum distanceModel); -/** Distance model value. */ +/* Distance model values. */ #define AL_INVERSE_DISTANCE 0xD001 #define AL_INVERSE_DISTANCE_CLAMPED 0xD002 #define AL_LINEAR_DISTANCE 0xD003 @@ -449,12 +448,12 @@ AL_API void AL_APIENTRY alDistanceModel(ALenum distanceModel); #define AL_EXPONENT_DISTANCE 0xD005 #define AL_EXPONENT_DISTANCE_CLAMPED 0xD006 -/** Renderer State management. */ +/* Renderer State management. */ AL_API void AL_APIENTRY alEnable(ALenum capability); AL_API void AL_APIENTRY alDisable(ALenum capability); AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability); -/** State retrieval. */ +/* State retrieval. */ AL_API const ALchar* AL_APIENTRY alGetString(ALenum param); AL_API void AL_APIENTRY alGetBooleanv(ALenum param, ALboolean *values); AL_API void AL_APIENTRY alGetIntegerv(ALenum param, ALint *values); @@ -465,25 +464,25 @@ AL_API ALint AL_APIENTRY alGetInteger(ALenum param); AL_API ALfloat AL_APIENTRY alGetFloat(ALenum param); AL_API ALdouble AL_APIENTRY alGetDouble(ALenum param); -/** - * Error retrieval. - * - * Obtain the first error generated in the AL context since the last check. - */ +/* Error retrieval. */ + +/** Obtain the first error generated in the AL context since the last check. */ AL_API ALenum AL_APIENTRY alGetError(void); +/** Query for the presence of an extension on the AL context. */ +AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extname); /** - * Extension support. - * - * Query for the presence of an extension, and obtain any appropriate function - * pointers and enum values. + * Retrieve the address of a function. The returned function may be context- + * specific. */ -AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extname); AL_API void* AL_APIENTRY alGetProcAddress(const ALchar *fname); +/** + * Retrieve the value of an enum. The returned value may be context-specific. + */ AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *ename); -/** Set Listener parameters */ +/* Set Listener parameters */ AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value); AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); AL_API void AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values); @@ -491,7 +490,7 @@ AL_API void AL_APIENTRY alListeneri(ALenum param, ALint value); AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3); AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values); -/** Get Listener parameters */ +/* Get Listener parameters */ AL_API void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value); AL_API void AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); AL_API void AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values); @@ -507,7 +506,7 @@ AL_API void AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources); /** Verify a handle is a valid Source. */ AL_API ALboolean AL_APIENTRY alIsSource(ALuint source); -/** Set Source parameters. */ +/* Set Source parameters. */ AL_API void AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value); AL_API void AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); AL_API void AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values); @@ -515,7 +514,7 @@ AL_API void AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value); AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3); AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values); -/** Get Source parameters. */ +/* Get Source parameters. */ AL_API void AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value); AL_API void AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); AL_API void AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values); @@ -558,7 +557,7 @@ AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer); /** Specifies the data to be copied into a buffer */ AL_API void AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq); -/** Set Buffer parameters, */ +/* Set Buffer parameters, */ AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat value); AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *values); @@ -566,7 +565,7 @@ AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value); AL_API void AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3); AL_API void AL_APIENTRY alBufferiv(ALuint buffer, ALenum param, const ALint *values); -/** Get Buffer parameters. */ +/* Get Buffer parameters. */ AL_API void AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value); AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); AL_API void AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values); @@ -574,7 +573,7 @@ AL_API void AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value); AL_API void AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3); AL_API void AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum param, ALint *values); -/** Pointer-to-function type, useful for dynamically getting AL entry points. */ +/* Pointer-to-function type, useful for dynamically getting AL entry points. */ typedef void (AL_APIENTRY *LPALENABLE)(ALenum capability); typedef void (AL_APIENTRY *LPALDISABLE)(ALenum capability); typedef ALboolean (AL_APIENTRY *LPALISENABLED)(ALenum capability); diff --git a/modules/openal-soft/include/AL/alc.h b/modules/openal-soft/include/AL/alc.h index 5786bad..c73b6e9 100644 --- a/modules/openal-soft/include/AL/alc.h +++ b/modules/openal-soft/include/AL/alc.h @@ -22,7 +22,7 @@ extern "C" { #endif -/** Deprecated macro. */ +/* Deprecated macros. */ #define ALCAPI ALC_API #define ALCAPIENTRY ALC_APIENTRY #define ALC_INVALID 0 @@ -89,7 +89,7 @@ typedef void ALCvoid; /** Context attribute: Hz. */ #define ALC_REFRESH 0x1008 -/** Context attribute: AL_TRUE or AL_FALSE. */ +/** Context attribute: AL_TRUE or AL_FALSE synchronous context? */ #define ALC_SYNC 0x1009 /** Context attribute: requested Mono (3D) Sources. */ @@ -117,12 +117,14 @@ typedef void ALCvoid; #define ALC_OUT_OF_MEMORY 0xA005 -/** Runtime ALC version. */ +/** Runtime ALC major version. */ #define ALC_MAJOR_VERSION 0x1000 +/** Runtime ALC minor version. */ #define ALC_MINOR_VERSION 0x1001 -/** Context attribute list properties. */ +/** Context attribute list size. */ #define ALC_ATTRIBUTES_SIZE 0x1002 +/** Context attribute list properties. */ #define ALC_ALL_ATTRIBUTES 0x1003 /** String for the default device specifier. */ @@ -166,49 +168,80 @@ typedef void ALCvoid; #define ALC_ALL_DEVICES_SPECIFIER 0x1013 -/** Context management. */ -ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint* attrlist); +/* Context management. */ + +/** Create and attach a context to the given device. */ +ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrlist); +/** + * Makes the given context the active process-wide context. Passing NULL clears + * the active context. + */ ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context); +/** Resumes processing updates for the given context. */ ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context); +/** Suspends updates for the given context. */ ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context); +/** Remove a context from its device and destroys it. */ ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context); +/** Returns the currently active context. */ ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void); +/** Returns the device that a particular context is attached to. */ ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *context); -/** Device management. */ +/* Device management. */ + +/** Opens the named playback device. */ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename); +/** Closes the given playback device. */ ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device); +/* Error support. */ -/** - * Error support. - * - * Obtain the most recent Device error. - */ +/** Obtain the most recent Device error. */ ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device); +/* Extension support. */ + /** - * Extension support. - * - * Query for the presence of an extension, and obtain any appropriate - * function pointers and enum values. + * Query for the presence of an extension on the device. Pass a NULL device to + * query a device-inspecific extension. */ ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname); -ALC_API void* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname); +/** + * Retrieve the address of a function. Given a non-NULL device, the returned + * function may be device-specific. + */ +ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname); +/** + * Retrieve the value of an enum. Given a non-NULL device, the returned value + * may be device-specific. + */ ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname); -/** Query function. */ +/* Query functions. */ + +/** Returns information about the device, and error strings. */ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param); +/** Returns information about the device and the version of OpenAL. */ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values); -/** Capture function. */ +/* Capture functions. */ + +/** + * Opens the named capture device with the given frequency, format, and buffer + * size. + */ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize); +/** Closes the given capture device. */ ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device); +/** Starts capturing samples into the device buffer. */ ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device); +/** Stops capturing samples. Samples in the device buffer remain available. */ ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device); +/** Reads samples from the device buffer. */ ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); -/** Pointer-to-function type, useful for dynamically getting ALC entry points. */ +/* Pointer-to-function type, useful for dynamically getting ALC entry points. */ typedef ALCcontext* (ALC_APIENTRY *LPALCCREATECONTEXT)(ALCdevice *device, const ALCint *attrlist); typedef ALCboolean (ALC_APIENTRY *LPALCMAKECONTEXTCURRENT)(ALCcontext *context); typedef void (ALC_APIENTRY *LPALCPROCESSCONTEXT)(ALCcontext *context); @@ -220,7 +253,7 @@ typedef ALCdevice* (ALC_APIENTRY *LPALCOPENDEVICE)(const ALCchar *devicename typedef ALCboolean (ALC_APIENTRY *LPALCCLOSEDEVICE)(ALCdevice *device); typedef ALCenum (ALC_APIENTRY *LPALCGETERROR)(ALCdevice *device); typedef ALCboolean (ALC_APIENTRY *LPALCISEXTENSIONPRESENT)(ALCdevice *device, const ALCchar *extname); -typedef void* (ALC_APIENTRY *LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname); +typedef ALCvoid* (ALC_APIENTRY *LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname); typedef ALCenum (ALC_APIENTRY *LPALCGETENUMVALUE)(ALCdevice *device, const ALCchar *enumname); typedef const ALCchar* (ALC_APIENTRY *LPALCGETSTRING)(ALCdevice *device, ALCenum param); typedef void (ALC_APIENTRY *LPALCGETINTEGERV)(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values); diff --git a/modules/openal-soft/include/AL/alext.h b/modules/openal-soft/include/AL/alext.h index cd7f275..1757f34 100644 --- a/modules/openal-soft/include/AL/alext.h +++ b/modules/openal-soft/include/AL/alext.h @@ -22,17 +22,20 @@ #define AL_ALEXT_H #include -/* Define int64_t and uint64_t types */ -#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L -#include -#elif defined(_WIN32) && defined(__GNUC__) +/* Define int64 and uint64 types */ +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ + (defined(__cplusplus) && __cplusplus >= 201103L) #include +typedef int64_t _alsoft_int64_t; +typedef uint64_t _alsoft_uint64_t; #elif defined(_WIN32) -typedef __int64 int64_t; -typedef unsigned __int64 uint64_t; +typedef __int64 _alsoft_int64_t; +typedef unsigned __int64 _alsoft_uint64_t; #else /* Fallback if nothing above works */ -#include +#include +typedef int64_t _alsoft_int64_t; +typedef uint64_t _alsoft_uint64_t; #endif #include "alc.h" @@ -158,9 +161,9 @@ extern "C" { #ifndef AL_EXT_STATIC_BUFFER #define AL_EXT_STATIC_BUFFER 1 -typedef ALvoid (AL_APIENTRY*PFNALBUFFERDATASTATICPROC)(const ALint,ALenum,ALvoid*,ALsizei,ALsizei); +typedef void (AL_APIENTRY*PFNALBUFFERDATASTATICPROC)(const ALint,ALenum,ALvoid*,ALsizei,ALsizei); #ifdef AL_ALEXT_PROTOTYPES -AL_API ALvoid AL_APIENTRY alBufferDataStatic(const ALint buffer, ALenum format, ALvoid *data, ALsizei len, ALsizei freq); +AL_API void AL_APIENTRY alBufferDataStatic(const ALint buffer, ALenum format, ALvoid *data, ALsizei len, ALsizei freq); #endif #endif @@ -193,9 +196,9 @@ ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void); #define AL_SOFT_buffer_sub_data 1 #define AL_BYTE_RW_OFFSETS_SOFT 0x1031 #define AL_SAMPLE_RW_OFFSETS_SOFT 0x1032 -typedef ALvoid (AL_APIENTRY*PFNALBUFFERSUBDATASOFTPROC)(ALuint,ALenum,const ALvoid*,ALsizei,ALsizei); +typedef void (AL_APIENTRY*PFNALBUFFERSUBDATASOFTPROC)(ALuint,ALenum,const ALvoid*,ALsizei,ALsizei); #ifdef AL_ALEXT_PROTOTYPES -AL_API ALvoid AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const ALvoid *data,ALsizei offset,ALsizei length); +AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const ALvoid *data,ALsizei offset,ALsizei length); #endif #endif @@ -343,8 +346,8 @@ ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffe #define AL_SOFT_source_latency 1 #define AL_SAMPLE_OFFSET_LATENCY_SOFT 0x1200 #define AL_SEC_OFFSET_LATENCY_SOFT 0x1201 -typedef int64_t ALint64SOFT; -typedef uint64_t ALuint64SOFT; +typedef _alsoft_int64_t ALint64SOFT; +typedef _alsoft_uint64_t ALuint64SOFT; typedef void (AL_APIENTRY*LPALSOURCEDSOFT)(ALuint,ALenum,ALdouble); typedef void (AL_APIENTRY*LPALSOURCE3DSOFT)(ALuint,ALenum,ALdouble,ALdouble,ALdouble); typedef void (AL_APIENTRY*LPALSOURCEDVSOFT)(ALuint,ALenum,const ALdouble*); @@ -381,11 +384,11 @@ AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64 #ifndef AL_SOFT_deferred_updates #define AL_SOFT_deferred_updates 1 #define AL_DEFERRED_UPDATES_SOFT 0xC002 -typedef ALvoid (AL_APIENTRY*LPALDEFERUPDATESSOFT)(void); -typedef ALvoid (AL_APIENTRY*LPALPROCESSUPDATESSOFT)(void); +typedef void (AL_APIENTRY*LPALDEFERUPDATESSOFT)(void); +typedef void (AL_APIENTRY*LPALPROCESSUPDATESSOFT)(void); #ifdef AL_ALEXT_PROTOTYPES -AL_API ALvoid AL_APIENTRY alDeferUpdatesSOFT(void); -AL_API ALvoid AL_APIENTRY alProcessUpdatesSOFT(void); +AL_API void AL_APIENTRY alDeferUpdatesSOFT(void); +AL_API void AL_APIENTRY alProcessUpdatesSOFT(void); #endif #endif @@ -496,8 +499,8 @@ AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index); #ifndef ALC_SOFT_device_clock #define ALC_SOFT_device_clock 1 -typedef int64_t ALCint64SOFT; -typedef uint64_t ALCuint64SOFT; +typedef _alsoft_int64_t ALCint64SOFT; +typedef _alsoft_uint64_t ALCuint64SOFT; #define ALC_DEVICE_CLOCK_SOFT 0x1600 #define ALC_DEVICE_LATENCY_SOFT 0x1601 #define ALC_DEVICE_CLOCK_LATENCY_SOFT 0x1602 @@ -509,6 +512,133 @@ ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, #endif #endif +#ifndef AL_SOFT_direct_channels_remix +#define AL_SOFT_direct_channels_remix 1 +#define AL_DROP_UNMATCHED_SOFT 0x0001 +#define AL_REMIX_UNMATCHED_SOFT 0x0002 +#endif + +#ifndef AL_SOFT_bformat_ex +#define AL_SOFT_bformat_ex 1 +#define AL_AMBISONIC_LAYOUT_SOFT 0x1997 +#define AL_AMBISONIC_SCALING_SOFT 0x1998 + +/* Ambisonic layouts */ +#define AL_FUMA_SOFT 0x0000 +#define AL_ACN_SOFT 0x0001 + +/* Ambisonic scalings (normalization) */ +/*#define AL_FUMA_SOFT*/ +#define AL_SN3D_SOFT 0x0001 +#define AL_N3D_SOFT 0x0002 +#endif + +#ifndef ALC_SOFT_loopback_bformat +#define ALC_SOFT_loopback_bformat 1 +#define ALC_AMBISONIC_LAYOUT_SOFT 0x1997 +#define ALC_AMBISONIC_SCALING_SOFT 0x1998 +#define ALC_AMBISONIC_ORDER_SOFT 0x1999 +#define ALC_MAX_AMBISONIC_ORDER_SOFT 0x199B + +#define ALC_BFORMAT3D_SOFT 0x1507 + +/* Ambisonic layouts */ +#define ALC_FUMA_SOFT 0x0000 +#define ALC_ACN_SOFT 0x0001 + +/* Ambisonic scalings (normalization) */ +/*#define ALC_FUMA_SOFT*/ +#define ALC_SN3D_SOFT 0x0001 +#define ALC_N3D_SOFT 0x0002 +#endif + +#ifndef AL_SOFT_effect_target +#define AL_SOFT_effect_target +#define AL_EFFECTSLOT_TARGET_SOFT 0x199C +#endif + +#ifndef AL_SOFT_events +#define AL_SOFT_events 1 +#define AL_EVENT_CALLBACK_FUNCTION_SOFT 0x19A2 +#define AL_EVENT_CALLBACK_USER_PARAM_SOFT 0x19A3 +#define AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT 0x19A4 +#define AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT 0x19A5 +#define AL_EVENT_TYPE_DISCONNECTED_SOFT 0x19A6 +typedef void (AL_APIENTRY*ALEVENTPROCSOFT)(ALenum eventType, ALuint object, ALuint param, + ALsizei length, const ALchar *message, + void *userParam); +typedef void (AL_APIENTRY*LPALEVENTCONTROLSOFT)(ALsizei count, const ALenum *types, ALboolean enable); +typedef void (AL_APIENTRY*LPALEVENTCALLBACKSOFT)(ALEVENTPROCSOFT callback, void *userParam); +typedef void* (AL_APIENTRY*LPALGETPOINTERSOFT)(ALenum pname); +typedef void (AL_APIENTRY*LPALGETPOINTERVSOFT)(ALenum pname, void **values); +#ifdef AL_ALEXT_PROTOTYPES +AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable); +AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam); +AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname); +AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values); +#endif +#endif + +#ifndef ALC_SOFT_reopen_device +#define ALC_SOFT_reopen_device +typedef ALCboolean (ALC_APIENTRY*LPALCREOPENDEVICESOFT)(ALCdevice *device, + const ALCchar *deviceName, const ALCint *attribs); +#ifdef AL_ALEXT_PROTOTYPES +ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, const ALCchar *deviceName, + const ALCint *attribs); +#endif +#endif + +#ifndef AL_SOFT_callback_buffer +#define AL_SOFT_callback_buffer +#define AL_BUFFER_CALLBACK_FUNCTION_SOFT 0x19A0 +#define AL_BUFFER_CALLBACK_USER_PARAM_SOFT 0x19A1 +typedef ALsizei (AL_APIENTRY*ALBUFFERCALLBACKTYPESOFT)(ALvoid *userptr, ALvoid *sampledata, ALsizei numbytes); +typedef void (AL_APIENTRY*LPALBUFFERCALLBACKSOFT)(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr); +typedef void (AL_APIENTRY*LPALGETBUFFERPTRSOFT)(ALuint buffer, ALenum param, ALvoid **value); +typedef void (AL_APIENTRY*LPALGETBUFFER3PTRSOFT)(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3); +typedef void (AL_APIENTRY*LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid **values); +#ifdef AL_ALEXT_PROTOTYPES +AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr); +AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr); +AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2); +AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **ptr); +#endif +#endif + +#ifndef AL_SOFT_UHJ +#define AL_SOFT_UHJ +#define AL_FORMAT_UHJ2CHN8_SOFT 0x19A2 +#define AL_FORMAT_UHJ2CHN16_SOFT 0x19A3 +#define AL_FORMAT_UHJ2CHN_FLOAT32_SOFT 0x19A4 +#define AL_FORMAT_UHJ3CHN8_SOFT 0x19A5 +#define AL_FORMAT_UHJ3CHN16_SOFT 0x19A6 +#define AL_FORMAT_UHJ3CHN_FLOAT32_SOFT 0x19A7 +#define AL_FORMAT_UHJ4CHN8_SOFT 0x19A8 +#define AL_FORMAT_UHJ4CHN16_SOFT 0x19A9 +#define AL_FORMAT_UHJ4CHN_FLOAT32_SOFT 0x19AA + +#define AL_STEREO_MODE_SOFT 0x19B0 +#define AL_NORMAL_SOFT 0x0000 +#define AL_SUPER_STEREO_SOFT 0x0001 +#define AL_SUPER_STEREO_WIDTH_SOFT 0x19B1 +#endif + +#ifndef ALC_SOFT_output_mode +#define ALC_SOFT_output_mode +#define ALC_OUTPUT_MODE_SOFT 0x19AC +#define ALC_ANY_SOFT 0x19AD +/*#define ALC_MONO_SOFT 0x1500*/ +/*#define ALC_STEREO_SOFT 0x1501*/ +#define ALC_STEREO_BASIC_SOFT 0x19AE +#define ALC_STEREO_UHJ_SOFT 0x19AF +#define ALC_STEREO_HRTF_SOFT 0x19B2 +/*#define ALC_QUAD_SOFT 0x1503*/ +#define ALC_SURROUND_5_1_SOFT 0x1504 +#define ALC_SURROUND_6_1_SOFT 0x1505 +#define ALC_SURROUND_7_1_SOFT 0x1506 +#endif + #ifdef __cplusplus } #endif diff --git a/modules/openal-soft/include/AL/efx.h b/modules/openal-soft/include/AL/efx.h index 5776698..5ab64a6 100644 --- a/modules/openal-soft/include/AL/efx.h +++ b/modules/openal-soft/include/AL/efx.h @@ -1,6 +1,7 @@ #ifndef AL_EFX_H #define AL_EFX_H +#include #include "alc.h" #include "al.h" @@ -242,41 +243,41 @@ typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat* typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, ALfloat*); #ifdef AL_ALEXT_PROTOTYPES -AL_API ALvoid AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects); -AL_API ALvoid AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects); +AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects); +AL_API void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects); AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect); -AL_API ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue); -AL_API ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *piValues); -AL_API ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue); -AL_API ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *pflValues); -AL_API ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue); -AL_API ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues); -AL_API ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue); -AL_API ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues); - -AL_API ALvoid AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters); -AL_API ALvoid AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters); +AL_API void AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue); +AL_API void AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *piValues); +AL_API void AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue); +AL_API void AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *pflValues); +AL_API void AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue); +AL_API void AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues); +AL_API void AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue); +AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues); + +AL_API void AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters); +AL_API void AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters); AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter); -AL_API ALvoid AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue); -AL_API ALvoid AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *piValues); -AL_API ALvoid AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flValue); -AL_API ALvoid AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *pflValues); -AL_API ALvoid AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *piValue); -AL_API ALvoid AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *piValues); -AL_API ALvoid AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *pflValue); -AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues); - -AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots); -AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots); +AL_API void AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue); +AL_API void AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *piValues); +AL_API void AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flValue); +AL_API void AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *pflValues); +AL_API void AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *piValue); +AL_API void AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *piValues); +AL_API void AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *pflValue); +AL_API void AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues); + +AL_API void AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots); +AL_API void AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots); AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot); -AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue); -AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *piValues); -AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flValue); -AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *pflValues); -AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *piValue); -AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues); -AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *pflValue); -AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues); +AL_API void AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue); +AL_API void AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *piValues); +AL_API void AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flValue); +AL_API void AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *pflValues); +AL_API void AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *piValue); +AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues); +AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *pflValue); +AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues); #endif /* Filter ranges and defaults. */ diff --git a/modules/openal-soft/native-tools/CMakeLists.txt b/modules/openal-soft/native-tools/CMakeLists.txt deleted file mode 100644 index 5e816bb..0000000 --- a/modules/openal-soft/native-tools/CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ -cmake_minimum_required(VERSION 3.0.2) - -project(native-tools) - -include(CheckLibraryExists) - -set(CPP_DEFS ) -if(WIN32) - set(CPP_DEFS ${CPP_DEFS} _WIN32) -endif(WIN32) - -check_library_exists(m pow "" HAVE_LIBM) - -add_executable(bin2h bin2h.c) -# Enforce no dressing for executable names, so the main script can find it -set_target_properties(bin2h PROPERTIES OUTPUT_NAME bin2h) -# Avoid configuration-dependent subdirectories while building with Visual Studio -set_target_properties(bin2h PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}") -set_target_properties(bin2h PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}") -target_compile_definitions(bin2h PRIVATE ${CPP_DEFS}) - -add_executable(bsincgen bsincgen.c) -set_target_properties(bsincgen PROPERTIES OUTPUT_NAME bsincgen) -set_target_properties(bsincgen PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}") -set_target_properties(bsincgen PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}") -target_compile_definitions(bsincgen PRIVATE ${CPP_DEFS}) -if(HAVE_LIBM) - target_link_libraries(bsincgen m) -endif(HAVE_LIBM) diff --git a/modules/openal-soft/native-tools/bin2h.c b/modules/openal-soft/native-tools/bin2h.c deleted file mode 100644 index 92f2b8a..0000000 --- a/modules/openal-soft/native-tools/bin2h.c +++ /dev/null @@ -1,100 +0,0 @@ - -#ifdef _MSC_VER -#define _CRT_SECURE_NO_WARNINGS -#endif - -#include -#include -#include -#include - -int main (int argc, char *argv[]) -{ - char* input_name; - FILE* input_file; - - char* output_name; - FILE* output_file; - - char* variable_name; - - if (4 != argc) - { - puts("Usage: bin2h [input] [output] [variable]"); - return EXIT_FAILURE; - } - - input_name = argv[1]; - output_name = argv[2]; - variable_name = argv[3]; - - input_file = fopen(input_name, "rb"); - - if (NULL == input_file) - { - printf("Could not open input file '%s': %s\n", input_name, strerror(errno)); - return EXIT_FAILURE; - } - - output_file = fopen(output_name, "w"); - - if (NULL == output_file) - { - printf("Could not open output file '%s': %s\n", output_name, strerror(errno)); - return EXIT_FAILURE; - } - - if (fprintf(output_file, "static const unsigned char %s[] = {", variable_name) < 0) - { - printf("Could not write to output file '%s': %s\n", output_name, strerror(ferror(output_file))); - return EXIT_FAILURE; - } - - while (0 == feof(input_file)) - { - unsigned char buffer[4096]; - size_t i, count = fread(buffer, 1, sizeof(buffer), input_file); - - if (sizeof(buffer) != count) - { - if (0 == feof(input_file) || 0 != ferror(input_file)) - { - printf("Could not read from input file '%s': %s\n", input_name, strerror(ferror(input_file))); - return EXIT_FAILURE; - } - } - - for (i = 0; i < count; ++i) - { - if ((i & 15) == 0) - { - if (fprintf(output_file, "\n ") < 0) - { - printf("Could not write to output file '%s': %s\n", output_name, strerror(ferror(output_file))); - return EXIT_FAILURE; - } - } - - if (fprintf(output_file, "0x%2.2x, ", buffer[i]) < 0) - { - printf("Could not write to output file '%s': %s\n", output_name, strerror(ferror(output_file))); - return EXIT_FAILURE; - } - - } - } - - if (fprintf(output_file, "\n};\n") < 0) - { - printf("Could not write to output file '%s': %s\n", output_name, strerror(ferror(output_file))); - return EXIT_FAILURE; - } - - if (fclose(output_file) < 0) - { - printf("Could not close output file '%s': %s\n", output_name, strerror(ferror(output_file))); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/modules/openal-soft/native-tools/bsincgen.c b/modules/openal-soft/native-tools/bsincgen.c deleted file mode 100644 index cb23652..0000000 --- a/modules/openal-soft/native-tools/bsincgen.c +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Sinc interpolator coefficient and delta generator for the OpenAL Soft - * cross platform audio library. - * - * Copyright (C) 2015 by Christopher Fitzgerald. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - * - * Or visit: http://www.gnu.org/licenses/old-licenses/lgpl-2.0.html - * - * -------------------------------------------------------------------------- - * - * This is a modified version of the bandlimited windowed sinc interpolator - * algorithm presented here: - * - * Smith, J.O. "Windowed Sinc Interpolation", in - * Physical Audio Signal Processing, - * https://ccrma.stanford.edu/~jos/pasp/Windowed_Sinc_Interpolation.html, - * online book, - * accessed October 2012. - */ - -#define _UNICODE -#include -#include -#include -#include - -#include "../common/win_main_utf8.h" - - -#ifndef M_PI -#define M_PI (3.14159265358979323846) -#endif - -#if !(defined(_ISOC99_SOURCE) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L)) -#define log2(x) (log(x) / log(2.0)) -#endif - -/* Same as in alu.h! */ -#define FRACTIONBITS (12) -#define FRACTIONONE (1<= b) ? a : b; } - -/* NOTE: This is the normalized (instead of just sin(x)/x) cardinal sine - * function. - * 2 f_t sinc(2 f_t x) - * f_t -- normalized transition frequency (0.5 is nyquist) - * x -- sample index (-N to N) - */ -static double Sinc(const double x) -{ - if(fabs(x) < 1e-15) - return 1.0; - return sin(M_PI * x) / (M_PI * x); -} - -static double BesselI_0(const double x) -{ - double term, sum, last_sum, x2, y; - int i; - - term = 1.0; - sum = 1.0; - x2 = x / 2.0; - i = 1; - - do { - y = x2 / i; - i++; - last_sum = sum; - term *= y * y; - sum += term; - } while(sum != last_sum); - - return sum; -} - -/* NOTE: k is assumed normalized (-1 to 1) - * beta is equivalent to 2 alpha - */ -static double Kaiser(const double b, const double k) -{ - if(!(k >= -1.0 && k <= 1.0)) - return 0.0; - return BesselI_0(b * sqrt(1.0 - k*k)) / BesselI_0(b); -} - -/* Calculates the (normalized frequency) transition width of the Kaiser window. - * Rejection is in dB. - */ -static double CalcKaiserWidth(const double rejection, const int order) -{ - double w_t = 2.0 * M_PI; - - if(rejection > 21.0) - return (rejection - 7.95) / (order * 2.285 * w_t); - /* This enforces a minimum rejection of just above 21.18dB */ - return 5.79 / (order * w_t); -} - -static double CalcKaiserBeta(const double rejection) -{ - if(rejection > 50.0) - return 0.1102 * (rejection - 8.7); - else if(rejection >= 21.0) - return (0.5842 * pow(rejection - 21.0, 0.4)) + - (0.07886 * (rejection - 21.0)); - return 0.0; -} - -/* Generates the coefficient, delta, and index tables required by the bsinc resampler */ -static void BsiGenerateTables(FILE *output, const char *tabname, const double rejection, const int order) -{ - static double filter[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT + 1][BSINC_POINTS_MAX]; - static double scDeltas[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT ][BSINC_POINTS_MAX]; - static double phDeltas[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT + 1][BSINC_POINTS_MAX]; - static double spDeltas[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT ][BSINC_POINTS_MAX]; - static int mt[BSINC_SCALE_COUNT]; - static double at[BSINC_SCALE_COUNT]; - const int num_points_min = order + 1; - double width, beta, scaleBase, scaleRange; - int si, pi, i; - - memset(filter, 0, sizeof(filter)); - memset(scDeltas, 0, sizeof(scDeltas)); - memset(phDeltas, 0, sizeof(phDeltas)); - memset(spDeltas, 0, sizeof(spDeltas)); - - /* Calculate windowing parameters. The width describes the transition - band, but it may vary due to the linear interpolation between scales - of the filter. - */ - width = CalcKaiserWidth(rejection, order); - beta = CalcKaiserBeta(rejection); - scaleBase = width / 2.0; - scaleRange = 1.0 - scaleBase; - - // Determine filter scaling. - for(si = 0; si < BSINC_SCALE_COUNT; si++) - { - const double scale = scaleBase + (scaleRange * si / (BSINC_SCALE_COUNT - 1)); - const double a = MinDouble(floor(num_points_min / (2.0 * scale)), num_points_min); - const int m = 2 * (int)a; - - mt[si] = m; - at[si] = a; - } - - /* Calculate the Kaiser-windowed Sinc filter coefficients for each scale - and phase. - */ - for(si = 0; si < BSINC_SCALE_COUNT; si++) - { - const int m = mt[si]; - const int o = num_points_min - (m / 2); - const int l = (m / 2) - 1; - const double a = at[si]; - const double scale = scaleBase + (scaleRange * si / (BSINC_SCALE_COUNT - 1)); - const double cutoff = (0.5 * scale) - (scaleBase * MaxDouble(0.5, scale)); - - for(pi = 0; pi <= BSINC_PHASE_COUNT; pi++) - { - const double phase = l + ((double)pi / BSINC_PHASE_COUNT); - - for(i = 0; i < m; i++) - { - const double x = i - phase; - filter[si][pi][o + i] = Kaiser(beta, x / a) * 2.0 * cutoff * Sinc(2.0 * cutoff * x); - } - } - } - - /* Linear interpolation between scales is simplified by pre-calculating - the delta (b - a) in: x = a + f (b - a) - - Given a difference in points between scales, the destination points - will be 0, thus: x = a + f (-a) - */ - for(si = 0; si < (BSINC_SCALE_COUNT - 1); si++) - { - const int m = mt[si]; - const int o = num_points_min - (m / 2); - - for(pi = 0; pi < BSINC_PHASE_COUNT; pi++) - { - for(i = 0; i < m; i++) - scDeltas[si][pi][o + i] = filter[si + 1][pi][o + i] - filter[si][pi][o + i]; - } - } - - // Linear interpolation between phases is also simplified. - for(si = 0; si < BSINC_SCALE_COUNT; si++) - { - const int m = mt[si]; - const int o = num_points_min - (m / 2); - - for(pi = 0; pi < BSINC_PHASE_COUNT; pi++) - { - for(i = 0; i < m; i++) - phDeltas[si][pi][o + i] = filter[si][pi + 1][o + i] - filter[si][pi][o + i]; - } - } - - /* This last simplification is done to complete the bilinear equation for - the combination of scale and phase. - */ - for(si = 0; si < (BSINC_SCALE_COUNT - 1); si++) - { - const int m = mt[si]; - const int o = num_points_min - (m / 2); - - for(pi = 0; pi < BSINC_PHASE_COUNT; pi++) - { - for(i = 0; i < m; i++) - spDeltas[si][pi][o + i] = phDeltas[si + 1][pi][o + i] - phDeltas[si][pi][o + i]; - } - } - - // Make sure the number of points is a multiple of 4 (for SIMD). - for(si = 0; si < BSINC_SCALE_COUNT; si++) - mt[si] = (mt[si]+3) & ~3; - - // Calculate the table size. - i = 0; - for(si = 0; si < BSINC_SCALE_COUNT; si++) - i += 4 * BSINC_PHASE_COUNT * mt[si]; - - fprintf(output, -"/* This %d%s order filter has a rejection of -%.0fdB, yielding a transition width\n" -" * of ~%.3f (normalized frequency). Order increases when downsampling to a\n" -" * limit of one octave, after which the quality of the filter (transition\n" -" * width) suffers to reduce the CPU cost. The bandlimiting will cut all sound\n" -" * after downsampling by ~%.2f octaves.\n" -" */\n" -"alignas(16) static constexpr float %s_tab[%d] = {\n", - order, (((order%100)/10) == 1) ? "th" : - ((order%10) == 1) ? "st" : - ((order%10) == 2) ? "nd" : - ((order%10) == 3) ? "rd" : "th", - rejection, width, log2(1.0/scaleBase), tabname, i); - for(si = 0; si < BSINC_SCALE_COUNT; si++) - { - const int m = mt[si]; - const int o = num_points_min - (m / 2); - - for(pi = 0; pi < BSINC_PHASE_COUNT; pi++) - { - fprintf(output, " /* %2d,%2d (%d) */", si, pi, m); - fprintf(output, "\n "); - for(i = 0; i < m; i++) - fprintf(output, " %+14.9ef,", filter[si][pi][o + i]); - fprintf(output, "\n "); - for(i = 0; i < m; i++) - fprintf(output, " %+14.9ef,", scDeltas[si][pi][o + i]); - fprintf(output, "\n "); - for(i = 0; i < m; i++) - fprintf(output, " %+14.9ef,", phDeltas[si][pi][o + i]); - fprintf(output, "\n "); - for(i = 0; i < m; i++) - fprintf(output, " %+14.9ef,", spDeltas[si][pi][o + i]); - fprintf(output, "\n"); - } - } - fprintf(output, "};\nconst BSincTable %s = {\n", tabname); - - /* The scaleBase is calculated from the Kaiser window transition width. - It represents the absolute limit to the filter before it fully cuts - the signal. The limit in octaves can be calculated by taking the - base-2 logarithm of its inverse: log_2(1 / scaleBase) - */ - fprintf(output, " /* scaleBase */ %.9ef, /* scaleRange */ %.9ef,\n", scaleBase, 1.0 / scaleRange); - - fprintf(output, " /* m */ {"); - fprintf(output, " %d", mt[0]); - for(si = 1; si < BSINC_SCALE_COUNT; si++) - fprintf(output, ", %d", mt[si]); - fprintf(output, " },\n"); - - fprintf(output, " /* filterOffset */ {"); - fprintf(output, " %d", 0); - i = mt[0]*4*BSINC_PHASE_COUNT; - for(si = 1; si < BSINC_SCALE_COUNT; si++) - { - fprintf(output, ", %d", i); - i += mt[si]*4*BSINC_PHASE_COUNT; - } - - fprintf(output, " },\n"); - fprintf(output, " %s_tab\n", tabname); - fprintf(output, "};\n\n"); -} - - -int main(int argc, char *argv[]) -{ - FILE *output; - - GET_UNICODE_ARGS(&argc, &argv); - - if(argc > 2) - { - fprintf(stderr, "Usage: %s [output file]\n", argv[0]); - return 1; - } - - if(argc == 2) - { - output = fopen(argv[1], "wb"); - if(!output) - { - fprintf(stderr, "Failed to open %s for writing\n", argv[1]); - return 1; - } - } - else - output = stdout; - - fprintf(output, "/* Generated by bsincgen, do not edit! */\n\n" -"static_assert(BSINC_SCALE_COUNT == %d, \"Unexpected BSINC_SCALE_COUNT value!\");\n" -"static_assert(BSINC_PHASE_COUNT == %d, \"Unexpected BSINC_PHASE_COUNT value!\");\n" -"static_assert(FRACTIONONE == %d, \"Unexpected FRACTIONONE value!\");\n\n" -"struct BSincTable {\n" -" const float scaleBase, scaleRange;\n" -" const int m[BSINC_SCALE_COUNT];\n" -" const int filterOffset[BSINC_SCALE_COUNT];\n" -" const float *Tab;\n" -"};\n\n", BSINC_SCALE_COUNT, BSINC_PHASE_COUNT, FRACTIONONE); - /* A 23rd order filter with a -60dB drop at nyquist. */ - BsiGenerateTables(output, "bsinc24", 60.0, 23); - /* An 11th order filter with a -40dB drop at nyquist. */ - BsiGenerateTables(output, "bsinc12", 40.0, 11); - - if(output != stdout) - fclose(output); - output = NULL; - - return 0; -} diff --git a/modules/openal-soft/presets/3D7.1.ambdec b/modules/openal-soft/presets/3D7.1.ambdec index 42b6a0b..66e5650 100644 --- a/modules/openal-soft/presets/3D7.1.ambdec +++ b/modules/openal-soft/presets/3D7.1.ambdec @@ -1,43 +1,61 @@ # AmbDec configuration # Written by Ambisonic Decoder Toolbox, version 8.0 -/description 3D7_2h1v_allrad_5200_rE_max_1_band +# input channel order: W Y Z X + +/description 3D7-noCenter_1h1v_pinv_even_energy_rV_max_rE_2_band + +# Similar to the the ITU-5.1-nocenter configuration, the front-center is +# declared here so that an appropriate distance may be set (for proper delaying +# or attenuating of dialog and such which feed it directly). It otherwise does +# not contribute to positional sound output due to its irregular position. /version 3 -/dec/chan_mask 1bf -/dec/freq_bands 1 -/dec/speakers 7 -/dec/coeff_scale fuma +/dec/chan_mask f +/dec/freq_bands 2 +/dec/speakers 6 +/dec/coeff_scale n3d -/opt/input_scale fuma -/opt/nfeff_comp output +/opt/input_scale n3d +/opt/nfeff_comp input /opt/delay_comp on /opt/level_comp on /opt/xover_freq 400.000000 /opt/xover_ratio 0.000000 /speakers/{ -# id dist azim elev conn +# id dist azim elev conn #----------------------------------------------------------------------- -add_spkr LF 1.500000 51.000000 24.000000 -add_spkr RF 1.500000 -51.000000 24.000000 -add_spkr CE 1.500000 0.000000 0.000000 -add_spkr LB 1.500000 180.000000 55.000000 -add_spkr RB 1.500000 0.000000 -55.000000 -add_spkr LS 1.500000 129.000000 -24.000000 -add_spkr RS 1.500000 -129.000000 -24.000000 +add_spkr FL 1.828800 51.000000 24.000000 +add_spkr FR 1.828800 -51.000000 24.000000 +add_spkr FC 1.828800 0.000000 0.000000 +add_spkr BL 1.828800 180.000000 55.000000 +add_spkr BR 1.828800 0.000000 -55.000000 +add_spkr SL 1.828800 129.000000 -24.000000 +add_spkr SR 1.828800 -129.000000 -24.000000 +/} + +/lfmatrix/{ +order_gain 1.00000000e+00 1.00000000e+00 0.000000 0.000000 +add_row 1.66669447e-01 2.04127551e-01 1.17487922e-01 1.66927066e-01 +add_row 1.66669447e-01 -2.04127551e-01 1.17487922e-01 1.66927066e-01 +add_row 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 +add_row 1.66669447e-01 0.00000000e+00 2.36070520e-01 -1.66153012e-01 +add_row 1.66669447e-01 0.00000000e+00 -2.36070520e-01 1.66153012e-01 +add_row 1.66669447e-01 2.04127551e-01 -1.17487922e-01 -1.66927066e-01 +add_row 1.66669447e-01 -2.04127551e-01 -1.17487922e-01 -1.66927066e-01 /} -/matrix/{ -order_gain 1.000000 0.774597 0.400000 0.000000 -add_row 0.325031 0.357638 0.206500 0.234037 0.202440 0.135692 0.116927 -0.098768 -add_row 0.325036 -0.357619 0.206537 0.234033 -0.202427 -0.135680 0.116934 -0.098768 -add_row 0.080073 -0.000010 -0.000296 0.155843 -0.000016 -0.000011 -0.000623 0.163306 -add_row 0.353556 0.000002 0.408453 -0.288377 -0.000004 -0.000003 -0.221039 0.077297 -add_row 0.325297 0.000008 -0.414018 0.232789 0.000004 0.000003 -0.232940 0.018311 -add_row 0.353558 0.352704 -0.203542 -0.290124 -0.191868 -0.134582 0.110616 -0.038294 -add_row 0.353556 -0.352691 -0.203576 -0.290115 0.191871 0.134585 0.110612 -0.038293 +/hfmatrix/{ +order_gain 1.73205081e+00 1.00000000e+00 0.000000 0.000000 +add_row 1.66669447e-01 2.04127551e-01 1.17487922e-01 1.66927066e-01 +add_row 1.66669447e-01 -2.04127551e-01 1.17487922e-01 1.66927066e-01 +add_row 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 +add_row 1.66669447e-01 0.00000000e+00 2.36070520e-01 -1.66153012e-01 +add_row 1.66669447e-01 0.00000000e+00 -2.36070520e-01 1.66153012e-01 +add_row 1.66669447e-01 2.04127551e-01 -1.17487922e-01 -1.66927066e-01 +add_row 1.66669447e-01 -2.04127551e-01 -1.17487922e-01 -1.66927066e-01 /} /end diff --git a/modules/openal-soft/presets/itu5.1.ambdec b/modules/openal-soft/presets/itu5.1.ambdec index 7438603..8f4b14e 100644 --- a/modules/openal-soft/presets/itu5.1.ambdec +++ b/modules/openal-soft/presets/itu5.1.ambdec @@ -1,7 +1,6 @@ # AmbDec configuration -# Written by Ambisonic Decoder Toolbox, version 8.0 -/description itu50_2h0p_allrad_5200_rE_max_1_band +/description itu50_2h0p_idhoa /version 3 @@ -15,7 +14,7 @@ /opt/delay_comp on /opt/level_comp on /opt/xover_freq 400.000000 -/opt/xover_ratio 3.000000 +/opt/xover_ratio 0.000000 /speakers/{ # id dist azim elev conn @@ -29,20 +28,20 @@ add_spkr RS 1.000000 -110.000000 0.000000 /lfmatrix/{ order_gain 1.000000 1.000000 1.000000 0.000000 -add_row 0.420330 0.330200 -0.312250 0.019350 -0.027010 -add_row 0.197700 0.288820 0.287820 0.049110 0.007420 -add_row 0.058030 0.000000 0.205970 0.000000 0.050790 -add_row 0.197700 -0.288820 0.287820 -0.049110 0.007420 -add_row 0.420330 -0.330200 -0.312250 -0.019350 -0.027010 +add_row 4.9010985e-1 3.7730501e-1 -3.7310699e-1 -1.2591453e-1 1.4513300e-2 +add_row 1.4908573e-1 3.0356168e-1 1.5329006e-1 2.4511248e-1 -1.5075313e-1 +add_row 1.3765492e-1 0.0000000e+0 4.4941794e-1 0.0000000e+0 2.5784407e-1 +add_row 1.4908573e-1 -3.0356168e-1 1.5329006e-1 -2.4511248e-1 -1.5075313e-1 +add_row 4.9010985e-1 -3.7730501e-1 -3.7310699e-1 1.2591453e-1 1.4513300e-2 /} /hfmatrix/{ -order_gain 1.000000 0.866025 0.500000 0.000000 -add_row 0.470934 0.378170 -0.400085 -0.082226 -0.044377 -add_row 0.208954 0.257988 0.230383 0.288520 -0.025085 -add_row 0.109403 -0.000002 0.194278 -0.000003 0.200863 -add_row 0.208950 -0.257989 0.230379 -0.288516 -0.025088 -add_row 0.470936 -0.378173 -0.400081 0.082228 -0.044372 +order_gain 1.000000 1.000000 1.000000 0.000000 +add_row 5.6731600e-1 4.2292000e-1 -3.1549500e-1 -6.3449000e-2 -2.9238000e-2 +add_row 3.6858400e-1 2.7234900e-1 3.2161600e-1 1.9264500e-1 4.8260000e-2 +add_row 1.8357900e-1 0.0000000e+0 1.9958800e-1 0.0000000e+0 9.6282000e-2 +add_row 3.6858400e-1 -2.7234900e-1 3.2161600e-1 -1.9264500e-1 4.8260000e-2 +add_row 5.6731600e-1 -4.2292000e-1 -3.1549500e-1 6.3449000e-2 -2.9238000e-2 /} /end diff --git a/modules/openal-soft/resources/openal32.rc b/modules/openal-soft/resources/openal32.rc new file mode 100644 index 0000000..1d7b9aa --- /dev/null +++ b/modules/openal-soft/resources/openal32.rc @@ -0,0 +1,90 @@ +#pragma code_page(65001) +// Microsoft Visual C++ generated resource script. +// +#include +#include "resource.h" +#include "version.h" +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION ALSOFT_VERSION_NUM + PRODUCTVERSION ALSOFT_VERSION_NUM + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "" + VALUE "FileDescription", "Main implementation library" + VALUE "FileVersion", ALSOFT_VERSION + VALUE "InternalName", "OpenAL32.dll" + VALUE "LegalCopyright", "GNU LGPL - Version 2, June 1991" + VALUE "OriginalFilename", "OpenAL32.dll" + VALUE "ProductName", "OpenAL Soft" + VALUE "ProductVersion", ALSOFT_VERSION + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/modules/openal-soft/resources/resource.h b/modules/openal-soft/resources/resource.h new file mode 100644 index 0000000..287c911 --- /dev/null +++ b/modules/openal-soft/resources/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by openal32.rc, router.rc, soft_oal.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/modules/openal-soft/resources/router.rc b/modules/openal-soft/resources/router.rc new file mode 100644 index 0000000..5292039 --- /dev/null +++ b/modules/openal-soft/resources/router.rc @@ -0,0 +1,90 @@ +#pragma code_page(65001) +// Microsoft Visual C++ generated resource script. +// +#include +#include "resource.h" +#include "version.h" +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION ALSOFT_VERSION_NUM + PRODUCTVERSION ALSOFT_VERSION_NUM + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "" + VALUE "FileDescription", "Router library" + VALUE "FileVersion", ALSOFT_VERSION + VALUE "InternalName", "OpenAL32.dll" + VALUE "LegalCopyright", "GNU LGPL - Version 2, June 1991" + VALUE "OriginalFilename", "OpenAL32.dll" + VALUE "ProductName", "OpenAL Soft" + VALUE "ProductVersion", ALSOFT_VERSION + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/modules/openal-soft/resources/soft_oal.rc b/modules/openal-soft/resources/soft_oal.rc new file mode 100644 index 0000000..91e1750 --- /dev/null +++ b/modules/openal-soft/resources/soft_oal.rc @@ -0,0 +1,90 @@ +#pragma code_page(65001) +// Microsoft Visual C++ generated resource script. +// +#include +#include "resource.h" +#include "version.h" +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION ALSOFT_VERSION_NUM + PRODUCTVERSION ALSOFT_VERSION_NUM + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "" + VALUE "FileDescription", "Main implementation library" + VALUE "FileVersion", ALSOFT_VERSION + VALUE "InternalName", "soft_oal.dll" + VALUE "LegalCopyright", "GNU LGPL - Version 2, June 1991" + VALUE "OriginalFilename", "soft_oal.dll" + VALUE "ProductName", "OpenAL Soft" + VALUE "ProductVersion", ALSOFT_VERSION + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/modules/openal-soft/router/al.cpp b/modules/openal-soft/router/al.cpp index 4c8b000..aabb5f3 100644 --- a/modules/openal-soft/router/al.cpp +++ b/modules/openal-soft/router/al.cpp @@ -11,31 +11,31 @@ std::atomic CurrentCtxDriver{nullptr}; #define DECL_THUNK1(R,n,T1) AL_API R AL_APIENTRY n(T1 a) \ { \ - DriverIface *iface = ThreadCtxDriver; \ + DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a); \ } #define DECL_THUNK2(R,n,T1,T2) AL_API R AL_APIENTRY n(T1 a, T2 b) \ { \ - DriverIface *iface = ThreadCtxDriver; \ + DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b); \ } #define DECL_THUNK3(R,n,T1,T2,T3) AL_API R AL_APIENTRY n(T1 a, T2 b, T3 c) \ { \ - DriverIface *iface = ThreadCtxDriver; \ + DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b, c); \ } #define DECL_THUNK4(R,n,T1,T2,T3,T4) AL_API R AL_APIENTRY n(T1 a, T2 b, T3 c, T4 d) \ { \ - DriverIface *iface = ThreadCtxDriver; \ + DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b, c, d); \ } #define DECL_THUNK5(R,n,T1,T2,T3,T4,T5) AL_API R AL_APIENTRY n(T1 a, T2 b, T3 c, T4 d, T5 e) \ { \ - DriverIface *iface = ThreadCtxDriver; \ + DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b, c, d, e); \ } @@ -46,7 +46,7 @@ std::atomic CurrentCtxDriver{nullptr}; */ AL_API ALenum AL_APIENTRY alGetError(void) { - DriverIface *iface = ThreadCtxDriver; + DriverIface *iface = GetThreadDriver(); if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); return iface ? iface->alGetError() : AL_NO_ERROR; } diff --git a/modules/openal-soft/router/alc.cpp b/modules/openal-soft/router/alc.cpp index 0f55252..210f683 100644 --- a/modules/openal-soft/router/alc.cpp +++ b/modules/openal-soft/router/alc.cpp @@ -11,15 +11,16 @@ #include #include "AL/alc.h" +#include "alstring.h" #include "router.h" #define DECL(x) { #x, reinterpret_cast(x) } struct FuncExportEntry { - const ALCchar *funcName; - ALCvoid *address; + const char *funcName; + void *address; }; -static const std::array alcFunctions{{ +static const std::array alcFunctions{{ DECL(alcCreateContext), DECL(alcMakeContextCurrent), DECL(alcProcessContext), @@ -248,7 +249,7 @@ static const ALCint alcMajorVersion = 1; static const ALCint alcMinorVersion = 1; -static std::mutex EnumerationLock; +static std::recursive_mutex EnumerationLock; static std::mutex ContextSwitchLock; static std::atomic LastError{ALC_NO_ERROR}; @@ -324,7 +325,8 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) devicename = nullptr; if(devicename) { - { std::lock_guard _{EnumerationLock}; + { + std::lock_guard _{EnumerationLock}; if(DevicesList.Names.empty()) (void)alcGetString(nullptr, ALC_DEVICE_SPECIFIER); idx = GetDriverIndexForName(&DevicesList, devicename); @@ -433,21 +435,21 @@ ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) */ if(idx < 0) { - DriverIface *oldiface = ThreadCtxDriver; + DriverIface *oldiface = GetThreadDriver(); if(oldiface) oldiface->alcSetThreadContext(nullptr); oldiface = CurrentCtxDriver.exchange(nullptr); if(oldiface) oldiface->alcMakeContextCurrent(nullptr); } else { - DriverIface *oldiface = ThreadCtxDriver; + DriverIface *oldiface = GetThreadDriver(); if(oldiface && oldiface != &DriverList[idx]) oldiface->alcSetThreadContext(nullptr); oldiface = CurrentCtxDriver.exchange(&DriverList[idx]); if(oldiface && oldiface != &DriverList[idx]) oldiface->alcMakeContextCurrent(nullptr); } - ThreadCtxDriver = nullptr; + SetThreadDriver(nullptr); return ALC_TRUE; } @@ -490,7 +492,7 @@ ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void) { - DriverIface *iface = ThreadCtxDriver; + DriverIface *iface = GetThreadDriver(); if(!iface) iface = CurrentCtxDriver.load(); return iface ? iface->alcGetCurrentContext() : nullptr; } @@ -539,7 +541,7 @@ ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const A ptr = alcExtensionList; while(ptr && *ptr) { - if(strncasecmp(ptr, extname, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len]))) + if(al::strncasecmp(ptr, extname, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len]))) return ALC_TRUE; if((ptr=strchr(ptr, ' ')) != nullptr) { @@ -564,11 +566,11 @@ ALC_API void* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *f return DriverList[idx].alcGetProcAddress(device, funcname); } - auto entry = std::find_if(alcFunctions.cbegin(), alcFunctions.cend(), + auto iter = std::find_if(alcFunctions.cbegin(), alcFunctions.cend(), [funcname](const FuncExportEntry &entry) -> bool { return strcmp(funcname, entry.funcName) == 0; } ); - return (entry != alcFunctions.cend()) ? entry->address : nullptr; + return (iter != alcFunctions.cend()) ? iter->address : nullptr; } ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname) @@ -584,11 +586,11 @@ ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *e return DriverList[idx].alcGetEnumValue(device, enumname); } - auto entry = std::find_if(alcEnumerations.cbegin(), alcEnumerations.cend(), + auto iter = std::find_if(alcEnumerations.cbegin(), alcEnumerations.cend(), [enumname](const EnumExportEntry &entry) -> bool { return strcmp(enumname, entry.enumName) == 0; } ); - return (entry != alcEnumerations.cend()) ? entry->value : 0; + return (iter != alcEnumerations.cend()) ? iter->value : 0; } ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param) @@ -622,30 +624,31 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para return alcExtensionList; case ALC_DEVICE_SPECIFIER: - { std::lock_guard _{EnumerationLock}; + { + std::lock_guard _{EnumerationLock}; DevicesList.clear(); - ALint idx = 0; + ALint idx{0}; for(const auto &drv : DriverList) { /* Only enumerate names from drivers that support it. */ - if(drv.ALCVer >= MAKE_ALC_VER(1, 1) || - drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) + if(drv.ALCVer >= MAKE_ALC_VER(1, 1) + || drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) AppendDeviceList(&DevicesList, - drv.alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx - ); - idx++; + drv.alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx); + ++idx; } /* Ensure the list is double-null termianted. */ if(DevicesList.Names.empty()) - DevicesList.Names.emplace_back(0); - DevicesList.Names.emplace_back(0); + DevicesList.Names.emplace_back('\0'); + DevicesList.Names.emplace_back('\0'); return DevicesList.Names.data(); } case ALC_ALL_DEVICES_SPECIFIER: - { std::lock_guard _{EnumerationLock}; + { + std::lock_guard _{EnumerationLock}; AllDevicesList.clear(); - ALint idx = 0; + ALint idx{0}; for(const auto &drv : DriverList) { /* If the driver doesn't support ALC_ENUMERATE_ALL_EXT, substitute @@ -653,78 +656,75 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para */ if(drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) AppendDeviceList(&AllDevicesList, - drv.alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER), idx - ); - else if(drv.ALCVer >= MAKE_ALC_VER(1, 1) || - drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) + drv.alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER), idx); + else if(drv.ALCVer >= MAKE_ALC_VER(1, 1) + || drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) AppendDeviceList(&AllDevicesList, - drv.alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx - ); + drv.alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx); ++idx; } /* Ensure the list is double-null termianted. */ if(AllDevicesList.Names.empty()) - AllDevicesList.Names.emplace_back(0); - AllDevicesList.Names.emplace_back(0); + AllDevicesList.Names.emplace_back('\0'); + AllDevicesList.Names.emplace_back('\0'); return AllDevicesList.Names.data(); } case ALC_CAPTURE_DEVICE_SPECIFIER: - { std::lock_guard _{EnumerationLock}; + { + std::lock_guard _{EnumerationLock}; CaptureDevicesList.clear(); - ALint idx = 0; + ALint idx{0}; for(const auto &drv : DriverList) { - if(drv.ALCVer >= MAKE_ALC_VER(1, 1) || - drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) + if(drv.ALCVer >= MAKE_ALC_VER(1, 1) + || drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) AppendDeviceList(&CaptureDevicesList, - drv.alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER), idx - ); + drv.alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER), idx); ++idx; } /* Ensure the list is double-null termianted. */ if(CaptureDevicesList.Names.empty()) - CaptureDevicesList.Names.emplace_back(0); - CaptureDevicesList.Names.emplace_back(0); + CaptureDevicesList.Names.emplace_back('\0'); + CaptureDevicesList.Names.emplace_back('\0'); return CaptureDevicesList.Names.data(); } case ALC_DEFAULT_DEVICE_SPECIFIER: { - auto drv = std::find_if(DriverList.cbegin(), DriverList.cend(), + auto iter = std::find_if(DriverList.cbegin(), DriverList.cend(), [](const DriverIface &drv) -> bool { - return drv.ALCVer >= MAKE_ALC_VER(1, 1) || - drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"); + return drv.ALCVer >= MAKE_ALC_VER(1, 1) + || drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"); } ); - if(drv != DriverList.cend()) - return drv->alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); + if(iter != DriverList.cend()) + return iter->alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); return ""; } case ALC_DEFAULT_ALL_DEVICES_SPECIFIER: { - auto drv = std::find_if(DriverList.cbegin(), DriverList.cend(), + auto iter = std::find_if(DriverList.cbegin(), DriverList.cend(), [](const DriverIface &drv) -> bool - { return drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") != ALC_FALSE; } - ); - if(drv != DriverList.cend()) - return drv->alcGetString(nullptr, ALC_DEFAULT_ALL_DEVICES_SPECIFIER); + { return drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") != ALC_FALSE; }); + if(iter != DriverList.cend()) + return iter->alcGetString(nullptr, ALC_DEFAULT_ALL_DEVICES_SPECIFIER); return ""; } case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER: { - auto drv = std::find_if(DriverList.cbegin(), DriverList.cend(), + auto iter = std::find_if(DriverList.cbegin(), DriverList.cend(), [](const DriverIface &drv) -> bool { - return drv.ALCVer >= MAKE_ALC_VER(1, 1) || - drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE"); + return drv.ALCVer >= MAKE_ALC_VER(1, 1) + || drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE"); } ); - if(drv != DriverList.cend()) - return drv->alcGetString(nullptr, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); + if(iter != DriverList.cend()) + return iter->alcGetString(nullptr, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); return ""; } @@ -799,7 +799,8 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, devicename = nullptr; if(devicename) { - { std::lock_guard _{EnumerationLock}; + { + std::lock_guard _{EnumerationLock}; if(CaptureDevicesList.Names.empty()) (void)alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); idx = GetDriverIndexForName(&CaptureDevicesList, devicename); @@ -812,21 +813,17 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, return nullptr; } TRACE("Found driver %d for name \"%s\"\n", idx, devicename); - device = DriverList[idx].alcCaptureOpenDevice( - devicename, frequency, format, buffersize - ); + device = DriverList[idx].alcCaptureOpenDevice(devicename, frequency, format, buffersize); } else { for(const auto &drv : DriverList) { - if(drv.ALCVer >= MAKE_ALC_VER(1, 1) || - drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) + if(drv.ALCVer >= MAKE_ALC_VER(1, 1) + || drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) { TRACE("Using default capture device from driver %d\n", idx); - device = drv.alcCaptureOpenDevice( - nullptr, frequency, format, buffersize - ); + device = drv.alcCaptureOpenDevice(nullptr, frequency, format, buffersize); break; } ++idx; @@ -901,10 +898,10 @@ ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) if(!context) { - DriverIface *oldiface = ThreadCtxDriver; + DriverIface *oldiface = GetThreadDriver(); if(oldiface && !oldiface->alcSetThreadContext(nullptr)) return ALC_FALSE; - ThreadCtxDriver = nullptr; + SetThreadDriver(nullptr); return ALC_TRUE; } @@ -913,10 +910,10 @@ ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) { if(DriverList[idx].alcSetThreadContext(context)) { - DriverIface *oldiface = ThreadCtxDriver; + DriverIface *oldiface = GetThreadDriver(); if(oldiface != &DriverList[idx]) { - ThreadCtxDriver = &DriverList[idx]; + SetThreadDriver(&DriverList[idx]); if(oldiface) oldiface->alcSetThreadContext(nullptr); } return ALC_TRUE; @@ -929,7 +926,7 @@ ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) { - DriverIface *iface = ThreadCtxDriver; + DriverIface *iface = GetThreadDriver(); if(iface) return iface->alcGetThreadContext(); return nullptr; } diff --git a/modules/openal-soft/router/router.cpp b/modules/openal-soft/router/router.cpp index 5b976e9..67c3481 100644 --- a/modules/openal-soft/router/router.cpp +++ b/modules/openal-soft/router/router.cpp @@ -3,18 +3,20 @@ #include "router.h" -#include -#include -#include - #include +#include +#include +#include #include "AL/alc.h" #include "AL/al.h" + #include "almalloc.h" +#include "strutils.h" #include "version.h" + std::vector DriverList; thread_local DriverIface *ThreadCtxDriver; @@ -22,56 +24,57 @@ thread_local DriverIface *ThreadCtxDriver; enum LogLevel LogLevel = LogLevel_Error; FILE *LogFile; +#ifdef __MINGW32__ +DriverIface *GetThreadDriver() noexcept { return ThreadCtxDriver; } +void SetThreadDriver(DriverIface *driver) noexcept { ThreadCtxDriver = driver; } +#endif + static void LoadDriverList(void); -BOOL APIENTRY DllMain(HINSTANCE UNUSED(module), DWORD reason, void* UNUSED(reserved)) +BOOL APIENTRY DllMain(HINSTANCE, DWORD reason, void*) { - const char *str; - switch(reason) { - case DLL_PROCESS_ATTACH: - LogFile = stderr; - str = getenv("ALROUTER_LOGFILE"); - if(str && *str != '\0') - { - FILE *f = fopen(str, "w"); - if(f == nullptr) - ERR("Could not open log file: %s\n", str); - else - LogFile = f; - } - str = getenv("ALROUTER_LOGLEVEL"); - if(str && *str != '\0') - { - char *end = nullptr; - long l = strtol(str, &end, 0); - if(!end || *end != '\0') - ERR("Invalid log level value: %s\n", str); - else if(l < LogLevel_None || l > LogLevel_Trace) - ERR("Log level out of range: %s\n", str); - else - LogLevel = static_cast(l); - } - TRACE("Initializing router v0.1-%s %s\n", ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); - LoadDriverList(); + case DLL_PROCESS_ATTACH: + LogFile = stderr; + if(auto logfname = al::getenv("ALROUTER_LOGFILE")) + { + FILE *f = fopen(logfname->c_str(), "w"); + if(f == nullptr) + ERR("Could not open log file: %s\n", logfname->c_str()); + else + LogFile = f; + } + if(auto loglev = al::getenv("ALROUTER_LOGLEVEL")) + { + char *end = nullptr; + long l = strtol(loglev->c_str(), &end, 0); + if(!end || *end != '\0') + ERR("Invalid log level value: %s\n", loglev->c_str()); + else if(l < LogLevel_None || l > LogLevel_Trace) + ERR("Log level out of range: %s\n", loglev->c_str()); + else + LogLevel = static_cast(l); + } + TRACE("Initializing router v0.1-%s %s\n", ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); + LoadDriverList(); - break; + break; - case DLL_THREAD_ATTACH: - break; - case DLL_THREAD_DETACH: - break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + break; - case DLL_PROCESS_DETACH: - DriverList.clear(); + case DLL_PROCESS_DETACH: + DriverList.clear(); - if(LogFile && LogFile != stderr) - fclose(LogFile); - LogFile = nullptr; + if(LogFile && LogFile != stderr) + fclose(LogFile); + LogFile = nullptr; - break; + break; } return TRUE; } @@ -83,7 +86,7 @@ static void AddModule(HMODULE module, const WCHAR *name) { if(drv.Module == module) { - TRACE("Skipping already-loaded module %p\n", module); + TRACE("Skipping already-loaded module %p\n", decltype(std::declval()){module}); FreeLibrary(module); return; } @@ -101,8 +104,8 @@ static void AddModule(HMODULE module, const WCHAR *name) /* Load required functions. */ int err = 0; #define LOAD_PROC(x) do { \ - newdrv.x = reinterpret_cast( \ - GetProcAddress(module, #x)); \ + newdrv.x = reinterpret_cast(reinterpret_cast( \ + GetProcAddress(module, #x))); \ if(!newdrv.x) \ { \ ERR("Failed to find entry point for %s in %ls\n", #x, name); \ @@ -211,7 +214,10 @@ static void AddModule(HMODULE module, const WCHAR *name) if(newdrv.alcGetError(nullptr) == ALC_NO_ERROR) newdrv.ALCVer = MAKE_ALC_VER(alc_ver[0], alc_ver[1]); else + { + WARN("Failed to query ALC version for %ls, assuming 1.0\n", name); newdrv.ALCVer = MAKE_ALC_VER(1, 0); + } #undef LOAD_PROC #define LOAD_PROC(x) do { \ @@ -235,7 +241,7 @@ static void AddModule(HMODULE module, const WCHAR *name) DriverList.pop_back(); return; } - TRACE("Loaded module %p, %ls, ALC %d.%d\n", module, name, + TRACE("Loaded module %p, %ls, ALC %d.%d\n", decltype(std::declval()){module}, name, newdrv.ALCVer>>8, newdrv.ALCVer&255); #undef LOAD_PROC } @@ -362,7 +368,7 @@ PtrIntMap::~PtrIntMap() mCapacity = 0; } -ALenum PtrIntMap::insert(ALvoid *key, ALint value) +ALenum PtrIntMap::insert(void *key, int value) { std::lock_guard maplock{mLock}; auto iter = std::lower_bound(mKeys, mKeys+mSize, key); @@ -372,15 +378,15 @@ ALenum PtrIntMap::insert(ALvoid *key, ALint value) { if(mSize == mCapacity) { - ALvoid **newkeys{nullptr}; + void **newkeys{nullptr}; ALsizei newcap{mCapacity ? (mCapacity<<1) : 4}; if(newcap > mCapacity) - newkeys = static_cast( + newkeys = static_cast( al_calloc(16, (sizeof(mKeys[0])+sizeof(mValues[0]))*newcap) ); if(!newkeys) return AL_OUT_OF_MEMORY; - auto newvalues = reinterpret_cast(&newkeys[newcap]); + auto newvalues = reinterpret_cast(&newkeys[newcap]); if(mKeys) { @@ -406,9 +412,9 @@ ALenum PtrIntMap::insert(ALvoid *key, ALint value) return AL_NO_ERROR; } -ALint PtrIntMap::removeByKey(ALvoid *key) +int PtrIntMap::removeByKey(void *key) { - ALint ret = -1; + int ret = -1; std::lock_guard maplock{mLock}; auto iter = std::lower_bound(mKeys, mKeys+mSize, key); @@ -427,9 +433,9 @@ ALint PtrIntMap::removeByKey(ALvoid *key) return ret; } -ALint PtrIntMap::lookupByKey(ALvoid *key) +int PtrIntMap::lookupByKey(void *key) { - ALint ret = -1; + int ret = -1; std::lock_guard maplock{mLock}; auto iter = std::lower_bound(mKeys, mKeys+mSize, key); diff --git a/modules/openal-soft/router/router.h b/modules/openal-soft/router/router.h index d2574e7..f49a96e 100644 --- a/modules/openal-soft/router/router.h +++ b/modules/openal-soft/router/router.h @@ -7,28 +7,17 @@ #include -#include -#include #include #include +#include +#include +#include #include "AL/alc.h" #include "AL/al.h" #include "AL/alext.h" -#ifndef UNUSED -#if defined(__cplusplus) -#define UNUSED(x) -#elif defined(__GNUC__) -#define UNUSED(x) UNUSED_##x __attribute__((unused)) -#elif defined(__LCLINT__) -#define UNUSED(x) /*@unused@*/ x -#else -#define UNUSED(x) x -#endif -#endif - #define MAKE_ALC_VER(major, minor) (((major)<<8) | (minor)) struct DriverIface { @@ -134,8 +123,9 @@ struct DriverIface { LPALSPEEDOFSOUND alSpeedOfSound{nullptr}; LPALDISTANCEMODEL alDistanceModel{nullptr}; - DriverIface(std::wstring name, HMODULE mod) - : Name(std::move(name)), Module(mod) + template + DriverIface(T&& name, HMODULE mod) + : Name(std::forward(name)), Module(mod) { } ~DriverIface() { @@ -150,11 +140,22 @@ extern std::vector DriverList; extern thread_local DriverIface *ThreadCtxDriver; extern std::atomic CurrentCtxDriver; +/* HACK: MinGW generates bad code when accessing an extern thread_local object. + * Add a wrapper function for it that only accesses it where it's defined. + */ +#ifdef __MINGW32__ +DriverIface *GetThreadDriver() noexcept; +void SetThreadDriver(DriverIface *driver) noexcept; +#else +inline DriverIface *GetThreadDriver() noexcept { return ThreadCtxDriver; } +inline void SetThreadDriver(DriverIface *driver) noexcept { ThreadCtxDriver = driver; } +#endif + class PtrIntMap { - ALvoid **mKeys{nullptr}; + void **mKeys{nullptr}; /* Shares memory with keys. */ - ALint *mValues{nullptr}; + int *mValues{nullptr}; ALsizei mSize{0}; ALsizei mCapacity{0}; @@ -164,9 +165,9 @@ public: PtrIntMap() = default; ~PtrIntMap(); - ALenum insert(ALvoid *key, ALint value); - ALint removeByKey(ALvoid *key); - ALint lookupByKey(ALvoid *key); + ALenum insert(void *key, int value); + int removeByKey(void *key); + int lookupByKey(void *key); }; diff --git a/modules/openal-soft/utils/alsoft-config/CMakeLists.txt b/modules/openal-soft/utils/alsoft-config/CMakeLists.txt index 7996ee9..65a7063 100644 --- a/modules/openal-soft/utils/alsoft-config/CMakeLists.txt +++ b/modules/openal-soft/utils/alsoft-config/CMakeLists.txt @@ -1,60 +1,31 @@ project(alsoft-config) -option(ALSOFT_NO_QT5 "Use Qt4 instead of Qt5 for alsoft-config" FALSE) - -include_directories("${alsoft-config_BINARY_DIR}") - -set(alsoft-config_SRCS - main.cpp - mainwindow.cpp - mainwindow.h -) -set(alsoft-config_UIS mainwindow.ui) -set(alsoft-config_MOCS mainwindow.h) - -find_package(Qt5Widgets) -if(Qt5Widgets_FOUND AND NOT ALSOFT_NO_QT5) - qt5_wrap_ui(UIS ${alsoft-config_UIS}) - - qt5_wrap_cpp(MOCS ${alsoft-config_MOCS}) - - add_executable(alsoft-config ${alsoft-config_SRCS} ${UIS} ${RSCS} ${TRS} ${MOCS}) +if(Qt5Widgets_FOUND) + qt5_wrap_ui(UIS mainwindow.ui) + + qt5_wrap_cpp(MOCS mainwindow.h) + + add_executable(alsoft-config + main.cpp + mainwindow.cpp + mainwindow.h + verstr.cpp + verstr.h + ${UIS} ${RSCS} ${TRS} ${MOCS}) target_link_libraries(alsoft-config Qt5::Widgets) - target_include_directories(alsoft-config PRIVATE "${OpenAL_BINARY_DIR}") - set_property(TARGET alsoft-config APPEND PROPERTY COMPILE_FLAGS ${EXTRA_CFLAGS}) + target_include_directories(alsoft-config PRIVATE "${alsoft-config_BINARY_DIR}" + "${OpenAL_BINARY_DIR}") set_target_properties(alsoft-config PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR}) if(TARGET build_version) add_dependencies(alsoft-config build_version) endif() - install(TARGETS alsoft-config - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - ) -else() - # Need Qt 4.8.0 or newer for the iconset theme attribute to work - find_package(Qt4 4.8.0 COMPONENTS QtCore QtGui) - if(QT4_FOUND) - include(${QT_USE_FILE}) - - qt4_wrap_ui(UIS ${alsoft-config_UIS}) - - qt4_wrap_cpp(MOCS ${alsoft-config_MOCS}) - - add_executable(alsoft-config ${alsoft-config_SRCS} ${UIS} ${RSCS} ${TRS} ${MOCS}) - target_link_libraries(alsoft-config ${QT_LIBRARIES}) - target_include_directories(alsoft-config PRIVATE "${OpenAL_BINARY_DIR}") - set_property(TARGET alsoft-config APPEND PROPERTY COMPILE_FLAGS ${EXTRA_CFLAGS}) - set_target_properties(alsoft-config PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR}) - if(TARGET build_version) - add_dependencies(alsoft-config build_version) - endif() + message(STATUS "Building configuration program") + if(ALSOFT_INSTALL_UTILS) install(TARGETS alsoft-config - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - ) + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() endif() diff --git a/modules/openal-soft/utils/alsoft-config/mainwindow.cpp b/modules/openal-soft/utils/alsoft-config/mainwindow.cpp index 4e2b246..baecf52 100644 --- a/modules/openal-soft/utils/alsoft-config/mainwindow.cpp +++ b/modules/openal-soft/utils/alsoft-config/mainwindow.cpp @@ -1,7 +1,7 @@ #include "config.h" -#include "version.h" +#include "mainwindow.h" #include #include @@ -11,8 +11,13 @@ #include #include #include -#include "mainwindow.h" #include "ui_mainwindow.h" +#include "verstr.h" + +#ifdef _WIN32 +#include +#include +#endif namespace { @@ -23,6 +28,9 @@ static const struct { #ifdef HAVE_JACK { "jack", "JACK" }, #endif +#ifdef HAVE_PIPEWIRE + { "pipewire", "PipeWire" }, +#endif #ifdef HAVE_PULSEAUDIO { "pulse", "PulseAudio" }, #endif @@ -75,8 +83,7 @@ static const struct NameValuePair { { "Mono", "mono" }, { "Stereo", "stereo" }, { "Quadraphonic", "quad" }, - { "5.1 Surround (Side)", "surround51" }, - { "5.1 Surround (Rear)", "surround51rear" }, + { "5.1 Surround", "surround51" }, { "6.1 Surround", "surround61" }, { "7.1 Surround", "surround71" }, @@ -101,7 +108,9 @@ static const struct NameValuePair { { "Linear", "linear" }, { "Default (Linear)", "" }, { "Cubic Spline", "cubic" }, + { "11th order Sinc (fast)", "fast_bsinc12" }, { "11th order Sinc", "bsinc12" }, + { "23rd order Sinc (fast)", "fast_bsinc24" }, { "23rd order Sinc", "bsinc24" }, { "", "" } @@ -115,13 +124,23 @@ static const struct NameValuePair { { "Default", "" }, { "Pan Pot", "panpot" }, { "UHJ", "uhj" }, + { "Binaural", "hrtf" }, { "", "" } }, ambiFormatList[] = { { "Default", "" }, { "AmbiX (ACN, SN3D)", "ambix" }, - { "ACN, N3D", "acn+n3d" }, { "Furse-Malham", "fuma" }, + { "ACN, N3D", "acn+n3d" }, + { "ACN, FuMa", "acn+fuma" }, + + { "", "" } +}, hrtfModeList[] = { + { "1st Order Ambisonic", "ambi1" }, + { "2nd Order Ambisonic", "ambi2" }, + { "3rd Order Ambisonic", "ambi3" }, + { "Default (Full)", "" }, + { "Full", "full" }, { "", "" } }; @@ -130,7 +149,14 @@ static QString getDefaultConfigName() { #ifdef Q_OS_WIN32 static const char fname[] = "alsoft.ini"; - QByteArray base = qgetenv("AppData"); + auto get_appdata_path = []() noexcept -> QString + { + WCHAR buffer[MAX_PATH]; + if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE) + return QString::fromWCharArray(buffer); + return QString(); + }; + QString base = get_appdata_path(); #else static const char fname[] = "alsoft.conf"; QByteArray base = qgetenv("XDG_CONFIG_HOME"); @@ -149,7 +175,14 @@ static QString getDefaultConfigName() static QString getBaseDataPath() { #ifdef Q_OS_WIN32 - QByteArray base = qgetenv("AppData"); + auto get_appdata_path = []() noexcept -> QString + { + WCHAR buffer[MAX_PATH]; + if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE) + return QString::fromWCharArray(buffer); + return QString(); + }; + QString base = get_appdata_path(); #else QByteArray base = qgetenv("XDG_DATA_HOME"); if(base.isEmpty()) @@ -162,7 +195,7 @@ static QString getBaseDataPath() return base; } -static QStringList getAllDataPaths(QString append=QString()) +static QStringList getAllDataPaths(const QString &append) { QStringList list; list.append(getBaseDataPath()); @@ -172,7 +205,11 @@ static QStringList getAllDataPaths(QString append=QString()) QString paths = qgetenv("XDG_DATA_DIRS"); if(paths.isEmpty()) paths = "/usr/local/share/:/usr/share/"; +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + list += paths.split(QChar(':'), Qt::SkipEmptyParts); +#else list += paths.split(QChar(':'), QString::SkipEmptyParts); +#endif #endif QStringList::iterator iter = list.begin(); while(iter != list.end()) @@ -196,7 +233,7 @@ static QString getValueFromName(const NameValuePair (&list)[N], const QString &s if(str == list[i].name) return list[i].value; } - return QString(); + return QString{}; } template @@ -207,7 +244,7 @@ static QString getNameFromValue(const NameValuePair (&list)[N], const QString &s if(str == list[i].value) return list[i].name; } - return QString(); + return QString{}; } @@ -224,10 +261,10 @@ QString getCheckValue(const QCheckBox *checkbox) { const Qt::CheckState state{checkbox->checkState()}; if(state == Qt::Checked) - return QString("true"); + return QString{"true"}; if(state == Qt::Unchecked) - return QString("false"); - return QString(); + return QString{"false"}; + return QString{}; } } @@ -267,6 +304,9 @@ MainWindow::MainWindow(QWidget *parent) : } ui->resamplerSlider->setRange(0, count-1); + for(count = 0;hrtfModeList[count].name[0];count++) { + } + ui->hrtfmodeSlider->setRange(0, count-1); ui->hrtfStateComboBox->adjustSize(); #if !defined(HAVE_NEON) && !defined(HAVE_SSE) @@ -309,126 +349,135 @@ MainWindow::MainWindow(QWidget *parent) : #endif - mPeriodSizeValidator = new QIntValidator(64, 8192, this); + mPeriodSizeValidator = new QIntValidator{64, 8192, this}; ui->periodSizeEdit->setValidator(mPeriodSizeValidator); - mPeriodCountValidator = new QIntValidator(2, 16, this); + mPeriodCountValidator = new QIntValidator{2, 16, this}; ui->periodCountEdit->setValidator(mPeriodCountValidator); - mSourceCountValidator = new QIntValidator(0, 4096, this); + mSourceCountValidator = new QIntValidator{0, 4096, this}; ui->srcCountLineEdit->setValidator(mSourceCountValidator); - mEffectSlotValidator = new QIntValidator(0, 64, this); + mEffectSlotValidator = new QIntValidator{0, 64, this}; ui->effectSlotLineEdit->setValidator(mEffectSlotValidator); - mSourceSendValidator = new QIntValidator(0, 16, this); + mSourceSendValidator = new QIntValidator{0, 16, this}; ui->srcSendLineEdit->setValidator(mSourceSendValidator); - mSampleRateValidator = new QIntValidator(8000, 192000, this); + mSampleRateValidator = new QIntValidator{8000, 192000, this}; ui->sampleRateCombo->lineEdit()->setValidator(mSampleRateValidator); - mJackBufferValidator = new QIntValidator(0, 8192, this); + mJackBufferValidator = new QIntValidator{0, 8192, this}; ui->jackBufferSizeLine->setValidator(mJackBufferValidator); - connect(ui->actionLoad, SIGNAL(triggered()), this, SLOT(loadConfigFromFile())); - connect(ui->actionSave_As, SIGNAL(triggered()), this, SLOT(saveConfigAsFile())); - - connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(showAboutPage())); - - connect(ui->closeCancelButton, SIGNAL(clicked()), this, SLOT(cancelCloseAction())); - connect(ui->applyButton, SIGNAL(clicked()), this, SLOT(saveCurrentConfig())); - - connect(ui->channelConfigCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(enableApplyButton())); - connect(ui->sampleFormatCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(enableApplyButton())); - connect(ui->stereoModeCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(enableApplyButton())); - connect(ui->sampleRateCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(enableApplyButton())); - connect(ui->sampleRateCombo, SIGNAL(editTextChanged(const QString&)), this, SLOT(enableApplyButton())); - - connect(ui->resamplerSlider, SIGNAL(valueChanged(int)), this, SLOT(updateResamplerLabel(int))); - - connect(ui->periodSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(updatePeriodSizeEdit(int))); - connect(ui->periodSizeEdit, SIGNAL(editingFinished()), this, SLOT(updatePeriodSizeSlider())); - connect(ui->periodCountSlider, SIGNAL(valueChanged(int)), this, SLOT(updatePeriodCountEdit(int))); - connect(ui->periodCountEdit, SIGNAL(editingFinished()), this, SLOT(updatePeriodCountSlider())); - - connect(ui->stereoEncodingComboBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(enableApplyButton())); - connect(ui->ambiFormatComboBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(enableApplyButton())); - connect(ui->outputLimiterCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->outputDitherCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - - connect(ui->decoderHQModeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->decoderDistCompCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->decoderNFEffectsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->decoderNFRefDelaySpinBox, SIGNAL(valueChanged(double)), this, SLOT(enableApplyButton())); - connect(ui->decoderQuadLineEdit, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); - connect(ui->decoderQuadButton, SIGNAL(clicked()), this, SLOT(selectQuadDecoderFile())); - connect(ui->decoder51LineEdit, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); - connect(ui->decoder51Button, SIGNAL(clicked()), this, SLOT(select51DecoderFile())); - connect(ui->decoder61LineEdit, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); - connect(ui->decoder61Button, SIGNAL(clicked()), this, SLOT(select61DecoderFile())); - connect(ui->decoder71LineEdit, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); - connect(ui->decoder71Button, SIGNAL(clicked()), this, SLOT(select71DecoderFile())); - - connect(ui->preferredHrtfComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(enableApplyButton())); - connect(ui->hrtfStateComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(enableApplyButton())); - connect(ui->hrtfAddButton, SIGNAL(clicked()), this, SLOT(addHrtfFile())); - connect(ui->hrtfRemoveButton, SIGNAL(clicked()), this, SLOT(removeHrtfFile())); - connect(ui->hrtfFileList, SIGNAL(itemSelectionChanged()), this, SLOT(updateHrtfRemoveButton())); - connect(ui->defaultHrtfPathsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - - connect(ui->srcCountLineEdit, SIGNAL(editingFinished()), this, SLOT(enableApplyButton())); - connect(ui->srcSendLineEdit, SIGNAL(editingFinished()), this, SLOT(enableApplyButton())); - connect(ui->effectSlotLineEdit, SIGNAL(editingFinished()), this, SLOT(enableApplyButton())); - - connect(ui->enableSSECheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableSSE2CheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableSSE3CheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableSSE41CheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableNeonCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); + connect(ui->actionLoad, &QAction::triggered, this, &MainWindow::loadConfigFromFile); + connect(ui->actionSave_As, &QAction::triggered, this, &MainWindow::saveConfigAsFile); + + connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::showAboutPage); + + connect(ui->closeCancelButton, &QPushButton::clicked, this, &MainWindow::cancelCloseAction); + connect(ui->applyButton, &QPushButton::clicked, this, &MainWindow::saveCurrentConfig); + + auto qcb_cicint = static_cast(&QComboBox::currentIndexChanged); + connect(ui->channelConfigCombo, qcb_cicint, this, &MainWindow::enableApplyButton); + connect(ui->sampleFormatCombo, qcb_cicint, this, &MainWindow::enableApplyButton); + connect(ui->stereoModeCombo, qcb_cicint, this, &MainWindow::enableApplyButton); + connect(ui->sampleRateCombo, qcb_cicint, this, &MainWindow::enableApplyButton); + connect(ui->sampleRateCombo, &QComboBox::editTextChanged, this, &MainWindow::enableApplyButton); + + connect(ui->resamplerSlider, &QSlider::valueChanged, this, &MainWindow::updateResamplerLabel); + + connect(ui->periodSizeSlider, &QSlider::valueChanged, this, &MainWindow::updatePeriodSizeEdit); + connect(ui->periodSizeEdit, &QLineEdit::editingFinished, this, &MainWindow::updatePeriodSizeSlider); + connect(ui->periodCountSlider, &QSlider::valueChanged, this, &MainWindow::updatePeriodCountEdit); + connect(ui->periodCountEdit, &QLineEdit::editingFinished, this, &MainWindow::updatePeriodCountSlider); + + connect(ui->stereoEncodingComboBox, qcb_cicint, this, &MainWindow::enableApplyButton); + connect(ui->ambiFormatComboBox, qcb_cicint, this, &MainWindow::enableApplyButton); + connect(ui->outputLimiterCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->outputDitherCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + + connect(ui->decoderHQModeCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->decoderDistCompCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->decoderNFEffectsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + auto qdsb_vcd = static_cast(&QDoubleSpinBox::valueChanged); + connect(ui->decoderNFRefDelaySpinBox, qdsb_vcd, this, &MainWindow::enableApplyButton); + connect(ui->decoderQuadLineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); + connect(ui->decoderQuadButton, &QPushButton::clicked, this, &MainWindow::selectQuadDecoderFile); + connect(ui->decoder51LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); + connect(ui->decoder51Button, &QPushButton::clicked, this, &MainWindow::select51DecoderFile); + connect(ui->decoder61LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); + connect(ui->decoder61Button, &QPushButton::clicked, this, &MainWindow::select61DecoderFile); + connect(ui->decoder71LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); + connect(ui->decoder71Button, &QPushButton::clicked, this, &MainWindow::select71DecoderFile); + + connect(ui->preferredHrtfComboBox, qcb_cicint, this, &MainWindow::enableApplyButton); + connect(ui->hrtfStateComboBox, qcb_cicint, this, &MainWindow::enableApplyButton); + connect(ui->hrtfmodeSlider, &QSlider::valueChanged, this, &MainWindow::updateHrtfModeLabel); + + connect(ui->hrtfAddButton, &QPushButton::clicked, this, &MainWindow::addHrtfFile); + connect(ui->hrtfRemoveButton, &QPushButton::clicked, this, &MainWindow::removeHrtfFile); + connect(ui->hrtfFileList, &QListWidget::itemSelectionChanged, this, &MainWindow::updateHrtfRemoveButton); + connect(ui->defaultHrtfPathsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + + connect(ui->srcCountLineEdit, &QLineEdit::editingFinished, this, &MainWindow::enableApplyButton); + connect(ui->srcSendLineEdit, &QLineEdit::editingFinished, this, &MainWindow::enableApplyButton); + connect(ui->effectSlotLineEdit, &QLineEdit::editingFinished, this, &MainWindow::enableApplyButton); + + connect(ui->enableSSECheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableSSE2CheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableSSE3CheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableSSE41CheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableNeonCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); ui->enabledBackendList->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enabledBackendList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showEnabledBackendMenu(QPoint))); + connect(ui->enabledBackendList, &QListWidget::customContextMenuRequested, this, &MainWindow::showEnabledBackendMenu); ui->disabledBackendList->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->disabledBackendList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showDisabledBackendMenu(QPoint))); - connect(ui->backendCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - - connect(ui->defaultReverbComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(enableApplyButton())); - connect(ui->enableEaxReverbCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableStdReverbCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableAutowahCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableChorusCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableCompressorCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableDistortionCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableEchoCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableEqualizerCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableFlangerCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableFrequencyShifterCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableModulatorCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enableDedicatedCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->enablePitchShifterCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - - connect(ui->pulseAutospawnCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->pulseAllowMovesCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->pulseFixRateCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->pulseAdjLatencyCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - - connect(ui->jackAutospawnCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->jackBufferSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(updateJackBufferSizeEdit(int))); - connect(ui->jackBufferSizeLine, SIGNAL(editingFinished()), this, SLOT(updateJackBufferSizeSlider())); - - connect(ui->alsaDefaultDeviceLine, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); - connect(ui->alsaDefaultCaptureLine, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); - connect(ui->alsaResamplerCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - connect(ui->alsaMmapCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); - - connect(ui->ossDefaultDeviceLine, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); - connect(ui->ossPlaybackPushButton, SIGNAL(clicked(bool)), this, SLOT(selectOSSPlayback())); - connect(ui->ossDefaultCaptureLine, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); - connect(ui->ossCapturePushButton, SIGNAL(clicked(bool)), this, SLOT(selectOSSCapture())); - - connect(ui->solarisDefaultDeviceLine, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); - connect(ui->solarisPlaybackPushButton, SIGNAL(clicked(bool)), this, SLOT(selectSolarisPlayback())); - - connect(ui->waveOutputLine, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); - connect(ui->waveOutputButton, SIGNAL(clicked(bool)), this, SLOT(selectWaveOutput())); - connect(ui->waveBFormatCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton())); + connect(ui->disabledBackendList, &QListWidget::customContextMenuRequested, this, &MainWindow::showDisabledBackendMenu); + connect(ui->backendCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + + connect(ui->defaultReverbComboBox, qcb_cicint, this, &MainWindow::enableApplyButton); + connect(ui->enableEaxReverbCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableStdReverbCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableAutowahCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableChorusCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableCompressorCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableDistortionCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableEchoCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableEqualizerCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableFlangerCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableFrequencyShifterCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableModulatorCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableDedicatedCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enablePitchShifterCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableVocalMorpherCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + + connect(ui->pulseAutospawnCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->pulseAllowMovesCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->pulseFixRateCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->pulseAdjLatencyCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + + connect(ui->pwireAssumeAudioCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + + connect(ui->jackAutospawnCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->jackConnectPortsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->jackRtMixCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->jackBufferSizeSlider, &QSlider::valueChanged, this, &MainWindow::updateJackBufferSizeEdit); + connect(ui->jackBufferSizeLine, &QLineEdit::editingFinished, this, &MainWindow::updateJackBufferSizeSlider); + + connect(ui->alsaDefaultDeviceLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); + connect(ui->alsaDefaultCaptureLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); + connect(ui->alsaResamplerCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->alsaMmapCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + + connect(ui->ossDefaultDeviceLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); + connect(ui->ossPlaybackPushButton, &QPushButton::clicked, this, &MainWindow::selectOSSPlayback); + connect(ui->ossDefaultCaptureLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); + connect(ui->ossCapturePushButton, &QPushButton::clicked, this, &MainWindow::selectOSSCapture); + + connect(ui->solarisDefaultDeviceLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); + connect(ui->solarisPlaybackPushButton, &QPushButton::clicked, this, &MainWindow::selectSolarisPlayback); + + connect(ui->waveOutputLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); + connect(ui->waveOutputButton, &QPushButton::clicked, this, &MainWindow::selectWaveOutput); + connect(ui->waveBFormatCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); ui->backendListWidget->setCurrentRow(0); ui->tabWidget->setCurrentIndex(0); @@ -438,10 +487,9 @@ MainWindow::MainWindow(QWidget *parent) : for(int i = 0;backendList[i].backend_name[0];i++) { QList items = ui->backendListWidget->findItems( - backendList[i].full_string, Qt::MatchFixedString - ); - foreach(const QListWidgetItem *item, items) - ui->backendListWidget->setItemHidden(item, false); + backendList[i].full_string, Qt::MatchFixedString); + foreach(QListWidgetItem *item, items) + item->setHidden(false); } loadConfig(getDefaultConfigName()); @@ -467,8 +515,7 @@ void MainWindow::closeEvent(QCloseEvent *event) { QMessageBox::StandardButton btn = QMessageBox::warning(this, tr("Apply changes?"), tr("Save changes before quitting?"), - QMessageBox::Save | QMessageBox::No | QMessageBox::Cancel - ); + QMessageBox::Save | QMessageBox::No | QMessageBox::Cancel); if(btn == QMessageBox::Save) saveCurrentConfig(); if(btn == QMessageBox::Cancel) @@ -488,9 +535,8 @@ void MainWindow::cancelCloseAction() void MainWindow::showAboutPage() { QMessageBox::information(this, tr("About"), - tr("OpenAL Soft Configuration Utility.\nBuilt for OpenAL Soft library version ")+ - (ALSOFT_VERSION "-" ALSOFT_GIT_COMMIT_HASH " (" ALSOFT_GIT_BRANCH " branch).") - ); + tr("OpenAL Soft Configuration Utility.\nBuilt for OpenAL Soft library version ") + + GetVersionString()); } @@ -507,17 +553,17 @@ QStringList MainWindow::collectHrtfs() { if(!fname.endsWith(".mhr", Qt::CaseInsensitive)) continue; - QString fullname = dir.absoluteFilePath(fname); + QString fullname{dir.absoluteFilePath(fname)}; if(processed.contains(fullname)) continue; processed.push_back(fullname); - QString name = fname.left(fname.length()-4); + QString name{fname.left(fname.length()-4)}; if(!ret.contains(name)) ret.push_back(name); else { - size_t i = 2; + size_t i{2}; do { QString s = name+" #"+QString::number(i); if(!ret.contains(s)) @@ -536,25 +582,25 @@ QStringList MainWindow::collectHrtfs() QStringList paths = getAllDataPaths("/openal/hrtf"); foreach(const QString &name, paths) { - QDir dir(name); - QStringList fnames = dir.entryList(QDir::Files | QDir::Readable, QDir::Name); + QDir dir{name}; + QStringList fnames{dir.entryList(QDir::Files | QDir::Readable, QDir::Name)}; foreach(const QString &fname, fnames) { if(!fname.endsWith(".mhr", Qt::CaseInsensitive)) continue; - QString fullname = dir.absoluteFilePath(fname); + QString fullname{dir.absoluteFilePath(fname)}; if(processed.contains(fullname)) continue; processed.push_back(fullname); - QString name = fname.left(fname.length()-4); + QString name{fname.left(fname.length()-4)}; if(!ret.contains(name)) ret.push_back(name); else { - size_t i = 2; + size_t i{2}; do { - QString s = name+" #"+QString::number(i); + QString s{name+" #"+QString::number(i)}; if(!ret.contains(s)) { ret.push_back(s); @@ -567,8 +613,7 @@ QStringList MainWindow::collectHrtfs() } #ifdef ALSOFT_EMBED_HRTF_DATA - ret.push_back("Built-In 44100hz"); - ret.push_back("Built-In 48000hz"); + ret.push_back("Built-In HRTF"); #endif } return ret; @@ -584,33 +629,35 @@ void MainWindow::loadConfigFromFile() void MainWindow::loadConfig(const QString &fname) { - QSettings settings(fname, QSettings::IniFormat); + QSettings settings{fname, QSettings::IniFormat}; QString sampletype = settings.value("sample-type").toString(); ui->sampleFormatCombo->setCurrentIndex(0); if(sampletype.isEmpty() == false) { - QString str = getNameFromValue(sampleTypeList, sampletype); + QString str{getNameFromValue(sampleTypeList, sampletype)}; if(!str.isEmpty()) { - int j = ui->sampleFormatCombo->findText(str); + const int j{ui->sampleFormatCombo->findText(str)}; if(j > 0) ui->sampleFormatCombo->setCurrentIndex(j); } } - QString channelconfig = settings.value("channels").toString(); + QString channelconfig{settings.value("channels").toString()}; ui->channelConfigCombo->setCurrentIndex(0); if(channelconfig.isEmpty() == false) { - QString str = getNameFromValue(speakerModeList, channelconfig); + if(channelconfig == "surround51rear") + channelconfig = "surround51"; + QString str{getNameFromValue(speakerModeList, channelconfig)}; if(!str.isEmpty()) { - int j = ui->channelConfigCombo->findText(str); + const int j{ui->channelConfigCombo->findText(str)}; if(j > 0) ui->channelConfigCombo->setCurrentIndex(j); } } - QString srate = settings.value("frequency").toString(); + QString srate{settings.value("frequency").toString()}; if(srate.isEmpty()) ui->sampleRateCombo->setCurrentIndex(0); else @@ -651,15 +698,15 @@ void MainWindow::loadConfig(const QString &fname) ui->stereoModeCombo->setCurrentIndex(0); if(stereomode.isEmpty() == false) { - QString str = getNameFromValue(stereoModeList, stereomode); + QString str{getNameFromValue(stereoModeList, stereomode)}; if(!str.isEmpty()) { - int j = ui->stereoModeCombo->findText(str); + const int j{ui->stereoModeCombo->findText(str)}; if(j > 0) ui->stereoModeCombo->setCurrentIndex(j); } } - int periodsize = settings.value("period_size").toInt(); + int periodsize{settings.value("period_size").toInt()}; ui->periodSizeEdit->clear(); if(periodsize >= 64) { @@ -667,7 +714,7 @@ void MainWindow::loadConfig(const QString &fname) updatePeriodSizeSlider(); } - int periodcount = settings.value("periods").toInt(); + int periodcount{settings.value("periods").toInt()}; ui->periodCountEdit->clear(); if(periodcount >= 2) { @@ -678,35 +725,34 @@ void MainWindow::loadConfig(const QString &fname) ui->outputLimiterCheckBox->setCheckState(getCheckState(settings.value("output-limiter"))); ui->outputDitherCheckBox->setCheckState(getCheckState(settings.value("dither"))); - QString stereopan = settings.value("stereo-encoding").toString(); + QString stereopan{settings.value("stereo-encoding").toString()}; ui->stereoEncodingComboBox->setCurrentIndex(0); if(stereopan.isEmpty() == false) { - QString str = getNameFromValue(stereoEncList, stereopan); + QString str{getNameFromValue(stereoEncList, stereopan)}; if(!str.isEmpty()) { - int j = ui->stereoEncodingComboBox->findText(str); + const int j{ui->stereoEncodingComboBox->findText(str)}; if(j > 0) ui->stereoEncodingComboBox->setCurrentIndex(j); } } - QString ambiformat = settings.value("ambi-format").toString(); + QString ambiformat{settings.value("ambi-format").toString()}; ui->ambiFormatComboBox->setCurrentIndex(0); if(ambiformat.isEmpty() == false) { - QString str = getNameFromValue(ambiFormatList, ambiformat); + QString str{getNameFromValue(ambiFormatList, ambiformat)}; if(!str.isEmpty()) { - int j = ui->ambiFormatComboBox->findText(str); + const int j{ui->ambiFormatComboBox->findText(str)}; if(j > 0) ui->ambiFormatComboBox->setCurrentIndex(j); } } - bool hqmode = settings.value("decoder/hq-mode", false).toBool(); - ui->decoderHQModeCheckBox->setChecked(hqmode); + ui->decoderHQModeCheckBox->setChecked(getCheckState(settings.value("decoder/hq-mode"))); ui->decoderDistCompCheckBox->setCheckState(getCheckState(settings.value("decoder/distance-comp"))); ui->decoderNFEffectsCheckBox->setCheckState(getCheckState(settings.value("decoder/nfc"))); - double refdelay = settings.value("decoder/nfc-ref-delay", 0.0).toDouble(); + double refdelay{settings.value("decoder/nfc-ref-delay", 0.0).toDouble()}; ui->decoderNFRefDelaySpinBox->setValue(refdelay); ui->decoderQuadLineEdit->setText(settings.value("decoder/quad").toString()); @@ -714,22 +760,38 @@ void MainWindow::loadConfig(const QString &fname) ui->decoder61LineEdit->setText(settings.value("decoder/surround61").toString()); ui->decoder71LineEdit->setText(settings.value("decoder/surround71").toString()); - QStringList disabledCpuExts = settings.value("disable-cpu-exts").toStringList(); + QStringList disabledCpuExts{settings.value("disable-cpu-exts").toStringList()}; if(disabledCpuExts.size() == 1) disabledCpuExts = disabledCpuExts[0].split(QChar(',')); - for(QStringList::iterator iter = disabledCpuExts.begin();iter != disabledCpuExts.end();iter++) - *iter = iter->trimmed(); + for(QString &name : disabledCpuExts) + name = name.trimmed(); ui->enableSSECheckBox->setChecked(!disabledCpuExts.contains("sse", Qt::CaseInsensitive)); ui->enableSSE2CheckBox->setChecked(!disabledCpuExts.contains("sse2", Qt::CaseInsensitive)); ui->enableSSE3CheckBox->setChecked(!disabledCpuExts.contains("sse3", Qt::CaseInsensitive)); ui->enableSSE41CheckBox->setChecked(!disabledCpuExts.contains("sse4.1", Qt::CaseInsensitive)); ui->enableNeonCheckBox->setChecked(!disabledCpuExts.contains("neon", Qt::CaseInsensitive)); - QStringList hrtf_paths = settings.value("hrtf-paths").toStringList(); + QString hrtfmode{settings.value("hrtf-mode").toString().trimmed()}; + ui->hrtfmodeSlider->setValue(2); + ui->hrtfmodeLabel->setText(hrtfModeList[3].name); + /* The "basic" mode name is no longer supported. Use "ambi2" instead. */ + if(hrtfmode == "basic") + hrtfmode = "ambi2"; + for(int i = 0;hrtfModeList[i].name[0];i++) + { + if(hrtfmode == hrtfModeList[i].value) + { + ui->hrtfmodeSlider->setValue(i); + ui->hrtfmodeLabel->setText(hrtfModeList[i].name); + break; + } + } + + QStringList hrtf_paths{settings.value("hrtf-paths").toStringList()}; if(hrtf_paths.size() == 1) hrtf_paths = hrtf_paths[0].split(QChar(',')); - for(QStringList::iterator iter = hrtf_paths.begin();iter != hrtf_paths.end();iter++) - *iter = iter->trimmed(); + for(QString &name : hrtf_paths) + name = name.trimmed(); if(!hrtf_paths.empty() && !hrtf_paths.back().isEmpty()) ui->defaultHrtfPathsCheckBox->setCheckState(Qt::Unchecked); else @@ -742,7 +804,7 @@ void MainWindow::loadConfig(const QString &fname) ui->hrtfFileList->addItems(hrtf_paths); updateHrtfRemoveButton(); - QString hrtfstate = settings.value("hrtf").toString().toLower(); + QString hrtfstate{settings.value("hrtf").toString().toLower()}; if(hrtfstate == "true") ui->hrtfStateComboBox->setCurrentIndex(1); else if(hrtfstate == "false") @@ -754,16 +816,16 @@ void MainWindow::loadConfig(const QString &fname) ui->preferredHrtfComboBox->addItem("- Any -"); if(ui->defaultHrtfPathsCheckBox->isChecked()) { - QStringList hrtfs = collectHrtfs(); + QStringList hrtfs{collectHrtfs()}; foreach(const QString &name, hrtfs) ui->preferredHrtfComboBox->addItem(name); } - QString defaulthrtf = settings.value("default-hrtf").toString(); + QString defaulthrtf{settings.value("default-hrtf").toString()}; ui->preferredHrtfComboBox->setCurrentIndex(0); if(defaulthrtf.isEmpty() == false) { - int i = ui->preferredHrtfComboBox->findText(defaulthrtf); + int i{ui->preferredHrtfComboBox->findText(defaulthrtf)}; if(i > 0) ui->preferredHrtfComboBox->setCurrentIndex(i); else @@ -777,23 +839,23 @@ void MainWindow::loadConfig(const QString &fname) ui->enabledBackendList->clear(); ui->disabledBackendList->clear(); - QStringList drivers = settings.value("drivers").toStringList(); + QStringList drivers{settings.value("drivers").toStringList()}; if(drivers.size() == 0) ui->backendCheckBox->setChecked(true); else { if(drivers.size() == 1) drivers = drivers[0].split(QChar(',')); - for(QStringList::iterator iter = drivers.begin();iter != drivers.end();iter++) + for(QString &name : drivers) { - *iter = iter->trimmed(); + name = name.trimmed(); /* Convert "mmdevapi" references to "wasapi" for backwards * compatibility. */ - if(*iter == "-mmdevapi") - *iter = "-wasapi"; - else if(*iter == "mmdevapi") - *iter = "wasapi"; + if(name == "-mmdevapi") + name = "-wasapi"; + else if(name == "mmdevapi") + name = "wasapi"; } bool lastWasEmpty = false; @@ -813,7 +875,7 @@ void MainWindow::loadConfig(const QString &fname) } else if(backend.size() > 1) { - QStringRef backendref = backend.rightRef(backend.size()-1); + QStringRef backendref{backend.rightRef(backend.size()-1)}; for(int j = 0;backendList[j].backend_name[0];j++) { if(backendref == backendList[j].backend_name) @@ -827,7 +889,7 @@ void MainWindow::loadConfig(const QString &fname) ui->backendCheckBox->setChecked(lastWasEmpty); } - QString defaultreverb = settings.value("default-reverb").toString().toLower(); + QString defaultreverb{settings.value("default-reverb").toString().toLower()}; ui->defaultReverbComboBox->setCurrentIndex(0); if(defaultreverb.isEmpty() == false) { @@ -841,11 +903,11 @@ void MainWindow::loadConfig(const QString &fname) } } - QStringList excludefx = settings.value("excludefx").toStringList(); + QStringList excludefx{settings.value("excludefx").toStringList()}; if(excludefx.size() == 1) excludefx = excludefx[0].split(QChar(',')); - for(QStringList::iterator iter = excludefx.begin();iter != excludefx.end();iter++) - *iter = iter->trimmed(); + for(QString &name : excludefx) + name = name.trimmed(); ui->enableEaxReverbCheck->setChecked(!excludefx.contains("eaxreverb", Qt::CaseInsensitive)); ui->enableStdReverbCheck->setChecked(!excludefx.contains("reverb", Qt::CaseInsensitive)); ui->enableAutowahCheck->setChecked(!excludefx.contains("autowah", Qt::CaseInsensitive)); @@ -859,13 +921,19 @@ void MainWindow::loadConfig(const QString &fname) ui->enableModulatorCheck->setChecked(!excludefx.contains("modulator", Qt::CaseInsensitive)); ui->enableDedicatedCheck->setChecked(!excludefx.contains("dedicated", Qt::CaseInsensitive)); ui->enablePitchShifterCheck->setChecked(!excludefx.contains("pshifter", Qt::CaseInsensitive)); + ui->enableVocalMorpherCheck->setChecked(!excludefx.contains("vmorpher", Qt::CaseInsensitive)); ui->pulseAutospawnCheckBox->setCheckState(getCheckState(settings.value("pulse/spawn-server"))); ui->pulseAllowMovesCheckBox->setCheckState(getCheckState(settings.value("pulse/allow-moves"))); ui->pulseFixRateCheckBox->setCheckState(getCheckState(settings.value("pulse/fix-rate"))); ui->pulseAdjLatencyCheckBox->setCheckState(getCheckState(settings.value("pulse/adjust-latency"))); + ui->pwireAssumeAudioCheckBox->setCheckState(settings.value("pipewire/assume-audio").toBool() + ? Qt::Checked : Qt::Unchecked); + ui->jackAutospawnCheckBox->setCheckState(getCheckState(settings.value("jack/spawn-server"))); + ui->jackConnectPortsCheckBox->setCheckState(getCheckState(settings.value("jack/connect-ports"))); + ui->jackRtMixCheckBox->setCheckState(getCheckState(settings.value("jack/rt-mix"))); ui->jackBufferSizeLine->setText(settings.value("jack/buffer-size", QString()).toString()); updateJackBufferSizeSlider(); @@ -899,7 +967,7 @@ void MainWindow::saveCurrentConfig() void MainWindow::saveConfigAsFile() { - QString fname = QFileDialog::getOpenFileName(this, tr("Select Files")); + QString fname{QFileDialog::getOpenFileName(this, tr("Select Files"))}; if(fname.isEmpty() == false) { saveConfig(fname); @@ -910,13 +978,13 @@ void MainWindow::saveConfigAsFile() void MainWindow::saveConfig(const QString &fname) const { - QSettings settings(fname, QSettings::IniFormat); + QSettings settings{fname, QSettings::IniFormat}; /* HACK: Compound any stringlist values into a comma-separated string. */ - QStringList allkeys = settings.allKeys(); + QStringList allkeys{settings.allKeys()}; foreach(const QString &key, allkeys) { - QStringList vals = settings.value(key).toStringList(); + QStringList vals{settings.value(key).toStringList()}; if(vals.size() > 1) settings.setValue(key, vals.join(QChar(','))); } @@ -924,9 +992,9 @@ void MainWindow::saveConfig(const QString &fname) const settings.setValue("sample-type", getValueFromName(sampleTypeList, ui->sampleFormatCombo->currentText())); settings.setValue("channels", getValueFromName(speakerModeList, ui->channelConfigCombo->currentText())); - uint rate = ui->sampleRateCombo->currentText().toUInt(); + uint rate{ui->sampleRateCombo->currentText().toUInt()}; if(rate <= 0) - settings.setValue("frequency", QString()); + settings.setValue("frequency", QString{}); else settings.setValue("frequency", rate); @@ -945,14 +1013,12 @@ void MainWindow::saveConfig(const QString &fname) const settings.setValue("output-limiter", getCheckValue(ui->outputLimiterCheckBox)); settings.setValue("dither", getCheckValue(ui->outputDitherCheckBox)); - settings.setValue("decoder/hq-mode", - ui->decoderHQModeCheckBox->isChecked() ? QString("true") : QString(/*"false"*/) - ); + settings.setValue("decoder/hq-mode", getCheckValue(ui->decoderHQModeCheckBox)); settings.setValue("decoder/distance-comp", getCheckValue(ui->decoderDistCompCheckBox)); settings.setValue("decoder/nfc", getCheckValue(ui->decoderNFEffectsCheckBox)); double refdelay = ui->decoderNFRefDelaySpinBox->value(); settings.setValue("decoder/nfc-ref-delay", - (refdelay > 0.0) ? QString::number(refdelay) : QString() + (refdelay > 0.0) ? QString::number(refdelay) : QString{} ); settings.setValue("decoder/quad", ui->decoderQuadLineEdit->text()); @@ -973,32 +1039,35 @@ void MainWindow::saveConfig(const QString &fname) const strlist.append("neon"); settings.setValue("disable-cpu-exts", strlist.join(QChar(','))); + settings.setValue("hrtf-mode", hrtfModeList[ui->hrtfmodeSlider->value()].value); + if(ui->hrtfStateComboBox->currentIndex() == 1) settings.setValue("hrtf", "true"); else if(ui->hrtfStateComboBox->currentIndex() == 2) settings.setValue("hrtf", "false"); else - settings.setValue("hrtf", QString()); + settings.setValue("hrtf", QString{}); if(ui->preferredHrtfComboBox->currentIndex() == 0) - settings.setValue("default-hrtf", QString()); + settings.setValue("default-hrtf", QString{}); else { - QString str = ui->preferredHrtfComboBox->currentText(); + QString str{ui->preferredHrtfComboBox->currentText()}; settings.setValue("default-hrtf", str); } strlist.clear(); + strlist.reserve(ui->hrtfFileList->count()); for(int i = 0;i < ui->hrtfFileList->count();i++) strlist.append(ui->hrtfFileList->item(i)->text()); if(!strlist.empty() && ui->defaultHrtfPathsCheckBox->isChecked()) - strlist.append(QString()); - settings.setValue("hrtf-paths", strlist.join(QChar(','))); + strlist.append(QString{}); + settings.setValue("hrtf-paths", strlist.join(QChar{','})); strlist.clear(); for(int i = 0;i < ui->enabledBackendList->count();i++) { - QString label = ui->enabledBackendList->item(i)->text(); + QString label{ui->enabledBackendList->item(i)->text()}; for(int j = 0;backendList[j].backend_name[0];j++) { if(label == backendList[j].full_string) @@ -1010,12 +1079,12 @@ void MainWindow::saveConfig(const QString &fname) const } for(int i = 0;i < ui->disabledBackendList->count();i++) { - QString label = ui->disabledBackendList->item(i)->text(); + QString label{ui->disabledBackendList->item(i)->text()}; for(int j = 0;backendList[j].backend_name[0];j++) { if(label == backendList[j].full_string) { - strlist.append(QChar('-')+QString(backendList[j].backend_name)); + strlist.append(QChar{'-'}+QString{backendList[j].backend_name}); break; } } @@ -1023,15 +1092,15 @@ void MainWindow::saveConfig(const QString &fname) const if(strlist.size() == 0 && !ui->backendCheckBox->isChecked()) strlist.append("-all"); else if(ui->backendCheckBox->isChecked()) - strlist.append(QString()); + strlist.append(QString{}); settings.setValue("drivers", strlist.join(QChar(','))); // TODO: Remove check when we can properly match global values. if(ui->defaultReverbComboBox->currentIndex() == 0) - settings.setValue("default-reverb", QString()); + settings.setValue("default-reverb", QString{}); else { - QString str = ui->defaultReverbComboBox->currentText().toLower(); + QString str{ui->defaultReverbComboBox->currentText().toLower()}; settings.setValue("default-reverb", str); } @@ -1062,14 +1131,21 @@ void MainWindow::saveConfig(const QString &fname) const strlist.append("dedicated"); if(!ui->enablePitchShifterCheck->isChecked()) strlist.append("pshifter"); - settings.setValue("excludefx", strlist.join(QChar(','))); + if(!ui->enableVocalMorpherCheck->isChecked()) + strlist.append("vmorpher"); + settings.setValue("excludefx", strlist.join(QChar{','})); settings.setValue("pulse/spawn-server", getCheckValue(ui->pulseAutospawnCheckBox)); settings.setValue("pulse/allow-moves", getCheckValue(ui->pulseAllowMovesCheckBox)); settings.setValue("pulse/fix-rate", getCheckValue(ui->pulseFixRateCheckBox)); settings.setValue("pulse/adjust-latency", getCheckValue(ui->pulseAdjLatencyCheckBox)); + settings.setValue("pipewire/assume-audio", ui->pwireAssumeAudioCheckBox->isChecked() + ? QString{"true"} : QString{/*"false"*/}); + settings.setValue("jack/spawn-server", getCheckValue(ui->jackAutospawnCheckBox)); + settings.setValue("jack/connect-ports", getCheckValue(ui->jackConnectPortsCheckBox)); + settings.setValue("jack/rt-mix", getCheckValue(ui->jackRtMixCheckBox)); settings.setValue("jack/buffer-size", ui->jackBufferSizeLine->text()); settings.setValue("alsa/device", ui->alsaDefaultDeviceLine->text()); @@ -1084,7 +1160,7 @@ void MainWindow::saveConfig(const QString &fname) const settings.setValue("wave/file", ui->waveOutputLine->text()); settings.setValue("wave/bformat", - ui->waveBFormatCheckBox->isChecked() ? QString("true") : QString(/*"false"*/) + ui->waveBFormatCheckBox->isChecked() ? QString{"true"} : QString{/*"false"*/} ); /* Remove empty keys @@ -1093,8 +1169,8 @@ void MainWindow::saveConfig(const QString &fname) const allkeys = settings.allKeys(); foreach(const QString &key, allkeys) { - QString str = settings.value(key).toString(); - if(str == QString()) + QString str{settings.value(key).toString()}; + if(str == QString{}) settings.remove(key); } } @@ -1166,13 +1242,13 @@ void MainWindow::select71DecoderFile() { selectDecoderFile(ui->decoder71LineEdit, "Select 7.1 Surround Decoder");} void MainWindow::selectDecoderFile(QLineEdit *line, const char *caption) { - QString dir = line->text(); + QString dir{line->text()}; if(dir.isEmpty() || QDir::isRelativePath(dir)) { - QStringList paths = getAllDataPaths("/openal/presets"); + QStringList paths{getAllDataPaths("/openal/presets")}; while(!paths.isEmpty()) { - if(QDir(paths.last()).exists()) + if(QDir{paths.last()}.exists()) { dir = paths.last(); break; @@ -1180,9 +1256,8 @@ void MainWindow::selectDecoderFile(QLineEdit *line, const char *caption) paths.removeLast(); } } - QString fname = QFileDialog::getOpenFileName(this, tr(caption), - dir, tr("AmbDec Files (*.ambdec);;All Files (*.*)") - ); + QString fname{QFileDialog::getOpenFileName(this, tr(caption), + dir, tr("AmbDec Files (*.ambdec);;All Files (*.*)"))}; if(!fname.isEmpty()) { line->setText(fname); @@ -1201,16 +1276,23 @@ void MainWindow::updateJackBufferSizeEdit(int size) void MainWindow::updateJackBufferSizeSlider() { - int value = ui->jackBufferSizeLine->text().toInt(); - int pos = static_cast(floor(log2(value) + 0.5)); + int value{ui->jackBufferSizeLine->text().toInt()}; + auto pos = static_cast(floor(log2(value) + 0.5)); ui->jackBufferSizeSlider->setSliderPosition(pos); enableApplyButton(); } +void MainWindow::updateHrtfModeLabel(int num) +{ + ui->hrtfmodeLabel->setText(hrtfModeList[num].name); + enableApplyButton(); +} + + void MainWindow::addHrtfFile() { - QString path = QFileDialog::getExistingDirectory(this, tr("Select HRTF Path")); + QString path{QFileDialog::getExistingDirectory(this, tr("Select HRTF Path"))}; if(path.isEmpty() == false && !getAllDataPaths("/openal/hrtf").contains(path)) { ui->hrtfFileList->addItem(path); @@ -1220,7 +1302,7 @@ void MainWindow::addHrtfFile() void MainWindow::removeHrtfFile() { - QList selected = ui->hrtfFileList->selectedItems(); + QList selected{ui->hrtfFileList->selectedItems()}; if(!selected.isEmpty()) { foreach(QListWidgetItem *item, selected) @@ -1236,37 +1318,37 @@ void MainWindow::updateHrtfRemoveButton() void MainWindow::showEnabledBackendMenu(QPoint pt) { - QMap actionMap; + QHash actionMap; pt = ui->enabledBackendList->mapToGlobal(pt); QMenu ctxmenu; - QAction *removeAction = ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove"); + QAction *removeAction{ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove")}; if(ui->enabledBackendList->selectedItems().size() == 0) removeAction->setEnabled(false); ctxmenu.addSeparator(); for(size_t i = 0;backendList[i].backend_name[0];i++) { - QString backend = backendList[i].full_string; - QAction *action = ctxmenu.addAction(QString("Add ")+backend); + QString backend{backendList[i].full_string}; + QAction *action{ctxmenu.addAction(QString("Add ")+backend)}; actionMap[action] = backend; if(ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0 || ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0) action->setEnabled(false); } - QAction *gotAction = ctxmenu.exec(pt); + QAction *gotAction{ctxmenu.exec(pt)}; if(gotAction == removeAction) { - QList selected = ui->enabledBackendList->selectedItems(); + QList selected{ui->enabledBackendList->selectedItems()}; foreach(QListWidgetItem *item, selected) delete item; enableApplyButton(); } else if(gotAction != nullptr) { - QMap::const_iterator iter = actionMap.find(gotAction); - if(iter != actionMap.end()) + auto iter = actionMap.constFind(gotAction); + if(iter != actionMap.cend()) ui->enabledBackendList->addItem(iter.value()); enableApplyButton(); } @@ -1274,37 +1356,37 @@ void MainWindow::showEnabledBackendMenu(QPoint pt) void MainWindow::showDisabledBackendMenu(QPoint pt) { - QMap actionMap; + QHash actionMap; pt = ui->disabledBackendList->mapToGlobal(pt); QMenu ctxmenu; - QAction *removeAction = ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove"); + QAction *removeAction{ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove")}; if(ui->disabledBackendList->selectedItems().size() == 0) removeAction->setEnabled(false); ctxmenu.addSeparator(); for(size_t i = 0;backendList[i].backend_name[0];i++) { - QString backend = backendList[i].full_string; - QAction *action = ctxmenu.addAction(QString("Add ")+backend); + QString backend{backendList[i].full_string}; + QAction *action{ctxmenu.addAction(QString("Add ")+backend)}; actionMap[action] = backend; if(ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0 || ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0) action->setEnabled(false); } - QAction *gotAction = ctxmenu.exec(pt); + QAction *gotAction{ctxmenu.exec(pt)}; if(gotAction == removeAction) { - QList selected = ui->disabledBackendList->selectedItems(); + QList selected{ui->disabledBackendList->selectedItems()}; foreach(QListWidgetItem *item, selected) delete item; enableApplyButton(); } else if(gotAction != nullptr) { - QMap::const_iterator iter = actionMap.find(gotAction); - if(iter != actionMap.end()) + auto iter = actionMap.constFind(gotAction); + if(iter != actionMap.cend()) ui->disabledBackendList->addItem(iter.value()); enableApplyButton(); } @@ -1312,9 +1394,9 @@ void MainWindow::showDisabledBackendMenu(QPoint pt) void MainWindow::selectOSSPlayback() { - QString current = ui->ossDefaultDeviceLine->text(); + QString current{ui->ossDefaultDeviceLine->text()}; if(current.isEmpty()) current = ui->ossDefaultDeviceLine->placeholderText(); - QString fname = QFileDialog::getOpenFileName(this, tr("Select Playback Device"), current); + QString fname{QFileDialog::getOpenFileName(this, tr("Select Playback Device"), current)}; if(!fname.isEmpty()) { ui->ossDefaultDeviceLine->setText(fname); @@ -1324,9 +1406,9 @@ void MainWindow::selectOSSPlayback() void MainWindow::selectOSSCapture() { - QString current = ui->ossDefaultCaptureLine->text(); + QString current{ui->ossDefaultCaptureLine->text()}; if(current.isEmpty()) current = ui->ossDefaultCaptureLine->placeholderText(); - QString fname = QFileDialog::getOpenFileName(this, tr("Select Capture Device"), current); + QString fname{QFileDialog::getOpenFileName(this, tr("Select Capture Device"), current)}; if(!fname.isEmpty()) { ui->ossDefaultCaptureLine->setText(fname); @@ -1336,9 +1418,9 @@ void MainWindow::selectOSSCapture() void MainWindow::selectSolarisPlayback() { - QString current = ui->solarisDefaultDeviceLine->text(); + QString current{ui->solarisDefaultDeviceLine->text()}; if(current.isEmpty()) current = ui->solarisDefaultDeviceLine->placeholderText(); - QString fname = QFileDialog::getOpenFileName(this, tr("Select Playback Device"), current); + QString fname{QFileDialog::getOpenFileName(this, tr("Select Playback Device"), current)}; if(!fname.isEmpty()) { ui->solarisDefaultDeviceLine->setText(fname); @@ -1348,9 +1430,8 @@ void MainWindow::selectSolarisPlayback() void MainWindow::selectWaveOutput() { - QString fname = QFileDialog::getSaveFileName(this, tr("Select Wave File Output"), - ui->waveOutputLine->text(), tr("Wave Files (*.wav *.amb);;All Files (*.*)") - ); + QString fname{QFileDialog::getSaveFileName(this, tr("Select Wave File Output"), + ui->waveOutputLine->text(), tr("Wave Files (*.wav *.amb);;All Files (*.*)"))}; if(!fname.isEmpty()) { ui->waveOutputLine->setText(fname); diff --git a/modules/openal-soft/utils/alsoft-config/mainwindow.h b/modules/openal-soft/utils/alsoft-config/mainwindow.h index 8b76384..ca53582 100644 --- a/modules/openal-soft/utils/alsoft-config/mainwindow.h +++ b/modules/openal-soft/utils/alsoft-config/mainwindow.h @@ -43,6 +43,7 @@ private slots: void updateJackBufferSizeEdit(int size); void updateJackBufferSizeSlider(); + void updateHrtfModeLabel(int num); void addHrtfFile(); void removeHrtfFile(); diff --git a/modules/openal-soft/utils/alsoft-config/mainwindow.ui b/modules/openal-soft/utils/alsoft-config/mainwindow.ui index 46d1b7a..ba8f83f 100644 --- a/modules/openal-soft/utils/alsoft-config/mainwindow.ui +++ b/modules/openal-soft/utils/alsoft-config/mainwindow.ui @@ -7,7 +7,7 @@ 0 0 564 - 460 + 469 @@ -21,8 +21,7 @@ - - + .. @@ -31,7 +30,7 @@ 470 405 81 - 21 + 31 @@ -39,8 +38,7 @@ - - + .. @@ -64,8 +62,8 @@ 110 50 - 78 - 21 + 80 + 31 @@ -82,7 +80,7 @@ float and converted to the output sample type as needed. 0 50 101 - 21 + 31 @@ -98,7 +96,7 @@ float and converted to the output sample type as needed. 0 20 101 - 21 + 31 @@ -113,8 +111,8 @@ float and converted to the output sample type as needed. 110 20 - 78 - 21 + 80 + 31 @@ -131,7 +129,7 @@ to stereo output. 380 20 - 95 + 100 31 @@ -194,7 +192,7 @@ to stereo output. 290 20 81 - 21 + 31 @@ -210,7 +208,7 @@ to stereo output. 290 50 81 - 21 + 31 @@ -225,8 +223,8 @@ to stereo output. 380 50 - 78 - 21 + 101 + 31 @@ -256,7 +254,7 @@ otherwise be suitable for speakers. 20 30 511 - 91 + 81 @@ -432,28 +430,31 @@ frames needed for each mixing update. 130 - 130 - 131 - 21 + 120 + 111 + 31 Pan Pot uses standard amplitude panning (aka -pair-wise, stereo pair, etc) between -30 and +30 -degrees, while UHJ creates a stereo-compatible -two-channel UHJ mix, which encodes some -surround sound information into stereo output -that can be decoded with a surround sound -receiver. +pair-wise, stereo pair, etc). + +UHJ creates a stereo-compatible two-channel +UHJ mix, which encodes some surround sound +information into stereo output that can be +decoded with a surround sound receiver. + +Binaural applies HRTF filters to create a sense +of 3D space with headphones. 20 - 130 + 120 101 - 21 + 31 @@ -466,23 +467,26 @@ receiver. - 270 - 130 - 111 - 21 + 260 + 120 + 121 + 31 Ambisonic Format: + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + 390 - 130 + 120 131 - 21 + 31 @@ -533,7 +537,7 @@ quantization with low-level whitenoise. 60 - 80 + 90 421 81 @@ -611,7 +615,7 @@ quantization with low-level whitenoise. - 10 + 30 20 181 21 @@ -631,11 +635,14 @@ appropriate speaker configuration you intend to use. High Quality Mode: + + true + - 10 + 30 50 181 21 @@ -834,7 +841,7 @@ configuration file. - 10 + 30 80 181 21 @@ -884,7 +891,7 @@ normal output is created with no near-field simulation. 20 0 - 151 + 171 21 @@ -898,9 +905,9 @@ normal output is created with no near-field simulation. - 180 + 200 0 - 91 + 81 21 @@ -998,8 +1005,7 @@ normal output is created with no near-field simulation. - - + .. false @@ -1039,8 +1045,7 @@ listed above. - - + .. @@ -1048,10 +1053,10 @@ listed above. - 40 - 50 + 50 + 60 71 - 21 + 31 @@ -1065,9 +1070,9 @@ listed above. 130 - 50 + 60 161 - 21 + 31 @@ -1096,10 +1101,10 @@ application or system to determine if it should be used. - 20 + 30 20 91 - 21 + 31 @@ -1115,13 +1120,84 @@ application or system to determine if it should be used. 130 20 161 - 21 + 31 The default HRTF to use if the application doesn't request one. + + + + 50 + 100 + 441 + 81 + + + + HRTF Render Method + + + + + 20 + 30 + 51 + 21 + + + + Speed + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 340 + 30 + 51 + 21 + + + + Quality + + + + + + 80 + 30 + 251 + 21 + + + + Qt::Horizontal + + + + + + 50 + 50 + 321 + 21 + + + + Default + + + Qt::AlignCenter + + + @@ -1149,6 +1225,11 @@ application or system to determine if it should be used. PulseAudio + + + PipeWire + + JACK @@ -1351,6 +1432,26 @@ drop-outs. + + + + + 20 + 10 + 161 + 21 + + + + Assumes PipeWire has support for audio, allowing +the backend to initialize even when no audio devices +are reported. + + + Assume audio support + + + @@ -1372,7 +1473,7 @@ drop-outs. 10 - 40 + 110 401 80 @@ -1380,7 +1481,8 @@ drop-outs. The update buffer size, in samples, that the backend will keep buffered to handle the server's real-time -processing requests. Must be a power of 2. +processing requests. Must be a power of 2. Ignored +when Real-time Mixing is used. Buffer Size @@ -1430,6 +1532,46 @@ processing requests. Must be a power of 2. + + + + 20 + 40 + 141 + 21 + + + + AutoConnect Ports + + + true + + + + + + 20 + 70 + 141 + 21 + + + + Renders samples directly in the real-time +processing callback. This allows for lower +latency and less overall CPU utilization, but +can increase the risk of underruns when +increasing the amount of processing the +mixer needs to do. + + + Real-time Mixing + + + true + + @@ -2193,6 +2335,22 @@ added by the ALC_EXT_DEDICATED extension. true + + + + 320 + 210 + 131 + 21 + + + + Vocal morpher + + + true + + @@ -2200,7 +2358,7 @@ added by the ALC_EXT_DEDICATED extension. 10 20 141 - 21 + 31 @@ -2215,7 +2373,7 @@ added by the ALC_EXT_DEDICATED extension. 160 20 - 129 + 135 31 @@ -2366,7 +2524,7 @@ added by the ALC_EXT_DEDICATED extension. 370 405 91 - 21 + 31 @@ -2374,8 +2532,7 @@ added by the ALC_EXT_DEDICATED extension. - - + .. @@ -2409,8 +2566,7 @@ added by the ALC_EXT_DEDICATED extension. - - + .. &Quit @@ -2419,8 +2575,7 @@ added by the ALC_EXT_DEDICATED extension. - - + .. Save &As... @@ -2432,8 +2587,7 @@ added by the ALC_EXT_DEDICATED extension. - - + .. &Load... @@ -2451,22 +2605,6 @@ added by the ALC_EXT_DEDICATED extension. - - actionQuit - activated() - MainWindow - close() - - - -1 - -1 - - - 267 - 181 - - - backendListWidget currentRowChanged(int) diff --git a/modules/openal-soft/utils/alsoft-config/verstr.cpp b/modules/openal-soft/utils/alsoft-config/verstr.cpp new file mode 100644 index 0000000..42b1aea --- /dev/null +++ b/modules/openal-soft/utils/alsoft-config/verstr.cpp @@ -0,0 +1,10 @@ + +#include "verstr.h" + +#include "version.h" + + +QString GetVersionString() +{ + return QStringLiteral(ALSOFT_VERSION "-" ALSOFT_GIT_COMMIT_HASH " (" ALSOFT_GIT_BRANCH " branch)."); +} diff --git a/modules/openal-soft/utils/alsoft-config/verstr.h b/modules/openal-soft/utils/alsoft-config/verstr.h new file mode 100644 index 0000000..73e3ecb --- /dev/null +++ b/modules/openal-soft/utils/alsoft-config/verstr.h @@ -0,0 +1,8 @@ +#ifndef VERSTR_H +#define VERSTR_H + +#include + +QString GetVersionString(); + +#endif /* VERSTR_H */ diff --git a/modules/openal-soft/utils/makemhr/loaddef.cpp b/modules/openal-soft/utils/makemhr/loaddef.cpp index d3962c1..d325eda 100644 --- a/modules/openal-soft/utils/makemhr/loaddef.cpp +++ b/modules/openal-soft/utils/makemhr/loaddef.cpp @@ -21,14 +21,26 @@ * Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html */ -#include +#include "loaddef.h" + +#include +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include -#include "mysofa.h" - -#include "loaddef.h" +#include "alfstream.h" +#include "alstring.h" +#include "makemhr.h" +#include "mysofa.h" // Constants for accessing the token reader's ring buffer. #define TR_RING_BITS (16) @@ -40,13 +52,16 @@ // Token reader state for parsing the data set definition. struct TokenReaderT { - FILE *mFile; - const char *mName; - uint mLine; - uint mColumn; - char mRing[TR_RING_SIZE]; - size_t mIn; - size_t mOut; + std::istream &mIStream; + const char *mName{}; + uint mLine{}; + uint mColumn{}; + char mRing[TR_RING_SIZE]{}; + std::streamsize mIn{}; + std::streamsize mOut{}; + + TokenReaderT(std::istream &istream) noexcept : mIStream{istream} { } + TokenReaderT(const TokenReaderT&) = default; }; @@ -138,7 +153,8 @@ struct SourceRefT { // Setup the reader on the given file. The filename can be NULL if no error // output is desired. -static void TrSetup(FILE *fp, const char *startbytes, size_t startbytecount, const char *filename, TokenReaderT *tr) +static void TrSetup(const char *startbytes, std::streamsize startbytecount, const char *filename, + TokenReaderT *tr) { const char *name = nullptr; @@ -159,7 +175,6 @@ static void TrSetup(FILE *fp, const char *startbytes, size_t startbytecount, con } } - tr->mFile = fp; tr->mName = name; tr->mLine = 1; tr->mColumn = 1; @@ -168,7 +183,7 @@ static void TrSetup(FILE *fp, const char *startbytes, size_t startbytecount, con if(startbytecount > 0) { - memcpy(tr->mRing, startbytes, startbytecount); + std::copy_n(startbytes, startbytecount, std::begin(tr->mRing)); tr->mIn += startbytecount; } } @@ -177,22 +192,27 @@ static void TrSetup(FILE *fp, const char *startbytes, size_t startbytecount, con // is text to process. static int TrLoad(TokenReaderT *tr) { - size_t toLoad, in, count; + std::istream &istream = tr->mIStream; - toLoad = TR_RING_SIZE - (tr->mIn - tr->mOut); - if(toLoad >= TR_LOAD_SIZE && !feof(tr->mFile)) + std::streamsize toLoad{TR_RING_SIZE - static_cast(tr->mIn - tr->mOut)}; + if(toLoad >= TR_LOAD_SIZE && istream.good()) { // Load TR_LOAD_SIZE (or less if at the end of the file) per read. toLoad = TR_LOAD_SIZE; - in = tr->mIn&TR_RING_MASK; - count = TR_RING_SIZE - in; + std::streamsize in{tr->mIn&TR_RING_MASK}; + std::streamsize count{TR_RING_SIZE - in}; if(count < toLoad) { - tr->mIn += fread(&tr->mRing[in], 1, count, tr->mFile); - tr->mIn += fread(&tr->mRing[0], 1, toLoad-count, tr->mFile); + istream.read(&tr->mRing[in], count); + tr->mIn += istream.gcount(); + istream.read(&tr->mRing[0], toLoad-count); + tr->mIn += istream.gcount(); } else - tr->mIn += fread(&tr->mRing[in], 1, toLoad, tr->mFile); + { + istream.read(&tr->mRing[in], toLoad); + tr->mIn += istream.gcount(); + } if(tr->mOut >= TR_RING_SIZE) { @@ -301,7 +321,8 @@ static int TrIsIdent(TokenReaderT *tr) // errors and will not proceed to the next token. static int TrIsOperator(TokenReaderT *tr, const char *op) { - size_t out, len; + std::streamsize out; + size_t len; char ch; if(!TrSkipWhitespace(tr)) @@ -401,7 +422,7 @@ static int TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int return 0; } temp[len] = '\0'; - *value = strtol(temp, nullptr, 10); + *value = static_cast(strtol(temp, nullptr, 10)); if(*value < loBound || *value > hiBound) { TrErrorAt(tr, tr->mLine, col, "Expected a value from %d to %d.\n", loBound, hiBound); @@ -596,26 +617,24 @@ static int TrReadOperator(TokenReaderT *tr, const char *op) // Read a binary value of the specified byte order and byte size from a file, // storing it as a 32-bit unsigned integer. -static int ReadBin4(FILE *fp, const char *filename, const ByteOrderT order, const uint bytes, uint32_t *out) +static int ReadBin4(std::istream &istream, const char *filename, const ByteOrderT order, const uint bytes, uint32_t *out) { uint8_t in[4]; - uint32_t accum; - uint i; - - if(fread(in, 1, bytes, fp) != bytes) + istream.read(reinterpret_cast(in), static_cast(bytes)); + if(istream.gcount() != bytes) { fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); return 0; } - accum = 0; + uint32_t accum{0}; switch(order) { case BO_LITTLE: - for(i = 0;i < bytes;i++) + for(uint i = 0;i < bytes;i++) accum = (accum<<8) | in[bytes - i - 1]; break; case BO_BIG: - for(i = 0;i < bytes;i++) + for(uint i = 0;i < bytes;i++) accum = (accum<<8) | in[i]; break; default: @@ -627,18 +646,19 @@ static int ReadBin4(FILE *fp, const char *filename, const ByteOrderT order, cons // Read a binary value of the specified byte order from a file, storing it as // a 64-bit unsigned integer. -static int ReadBin8(FILE *fp, const char *filename, const ByteOrderT order, uint64_t *out) +static int ReadBin8(std::istream &istream, const char *filename, const ByteOrderT order, uint64_t *out) { uint8_t in[8]; uint64_t accum; uint i; - if(fread(in, 1, 8, fp) != 8) + istream.read(reinterpret_cast(in), 8); + if(istream.gcount() != 8) { fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); return 0; } - accum = 0ULL; + accum = 0; switch(order) { case BO_LITTLE: @@ -662,7 +682,8 @@ static int ReadBin8(FILE *fp, const char *filename, const ByteOrderT order, uint * whether they are padded toward the MSB (negative) or LSB (positive). * Floating-point types are not normalized. */ -static int ReadBinAsDouble(FILE *fp, const char *filename, const ByteOrderT order, const ElementTypeT type, const uint bytes, const int bits, double *out) +static int ReadBinAsDouble(std::istream &istream, const char *filename, const ByteOrderT order, + const ElementTypeT type, const uint bytes, const int bits, double *out) { union { uint32_t ui; @@ -677,14 +698,14 @@ static int ReadBinAsDouble(FILE *fp, const char *filename, const ByteOrderT orde *out = 0.0; if(bytes > 4) { - if(!ReadBin8(fp, filename, order, &v8.ui)) + if(!ReadBin8(istream, filename, order, &v8.ui)) return 0; if(type == ET_FP) *out = v8.f; } else { - if(!ReadBin4(fp, filename, order, bytes, &v4.ui)) + if(!ReadBin4(istream, filename, order, bytes, &v4.ui)) return 0; if(type == ET_FP) *out = v4.f; @@ -743,7 +764,8 @@ static int ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const Eleme // Read the RIFF/RIFX WAVE format chunk from a file, validating it against // the source parameters and data set metrics. -static int ReadWaveFormat(FILE *fp, const ByteOrderT order, const uint hrirRate, SourceRefT *src) +static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const uint hrirRate, + SourceRefT *src) { uint32_t fourCC, chunkSize; uint32_t format, channels, rate, dummy, block, size, bits; @@ -751,21 +773,21 @@ static int ReadWaveFormat(FILE *fp, const ByteOrderT order, const uint hrirRate, chunkSize = 0; do { if(chunkSize > 0) - fseek(fp, static_cast(chunkSize), SEEK_CUR); - if(!ReadBin4(fp, src->mPath, BO_LITTLE, 4, &fourCC) || - !ReadBin4(fp, src->mPath, order, 4, &chunkSize)) + istream.seekg(static_cast(chunkSize), std::ios::cur); + if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) return 0; } while(fourCC != FOURCC_FMT); - if(!ReadBin4(fp, src->mPath, order, 2, &format) || - !ReadBin4(fp, src->mPath, order, 2, &channels) || - !ReadBin4(fp, src->mPath, order, 4, &rate) || - !ReadBin4(fp, src->mPath, order, 4, &dummy) || - !ReadBin4(fp, src->mPath, order, 2, &block)) + 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(fp, src->mPath, order, 2, &size)) + if(!ReadBin4(istream, src->mPath, order, 2, &size)) return 0; size /= 8; if(block > size) @@ -775,23 +797,23 @@ static int ReadWaveFormat(FILE *fp, const ByteOrderT order, const uint hrirRate, size = block; if(format == WAVE_FORMAT_EXTENSIBLE) { - fseek(fp, 2, SEEK_CUR); - if(!ReadBin4(fp, src->mPath, order, 2, &bits)) + istream.seekg(2, std::ios::cur); + if(!ReadBin4(istream, src->mPath, order, 2, &bits)) return 0; if(bits == 0) bits = 8 * size; - fseek(fp, 4, SEEK_CUR); - if(!ReadBin4(fp, src->mPath, order, 2, &format)) + istream.seekg(4, std::ios::cur); + if(!ReadBin4(istream, src->mPath, order, 2, &format)) return 0; - fseek(fp, static_cast(chunkSize - 26), SEEK_CUR); + istream.seekg(static_cast(chunkSize - 26), std::ios::cur); } else { bits = 8 * size; if(chunkSize > 14) - fseek(fp, static_cast(chunkSize - 16), SEEK_CUR); + istream.seekg(static_cast(chunkSize - 16), std::ios::cur); else - fseek(fp, static_cast(chunkSize - 14), SEEK_CUR); + istream.seekg(static_cast(chunkSize - 14), std::ios::cur); } if(format != WAVE_FORMAT_PCM && format != WAVE_FORMAT_IEEE_FLOAT) { @@ -838,7 +860,8 @@ static int ReadWaveFormat(FILE *fp, const ByteOrderT order, const uint hrirRate, } // Read a RIFF/RIFX WAVE data chunk, converting all elements to doubles. -static int ReadWaveData(FILE *fp, const SourceRefT *src, const ByteOrderT order, const uint n, double *hrir) +static int ReadWaveData(std::istream &istream, const SourceRefT *src, const ByteOrderT order, + const uint n, double *hrir) { int pre, post, skip; uint i; @@ -850,19 +873,20 @@ static int ReadWaveData(FILE *fp, const SourceRefT *src, const ByteOrderT order, { skip += pre; if(skip > 0) - fseek(fp, skip, SEEK_CUR); - if(!ReadBinAsDouble(fp, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) + istream.seekg(skip, std::ios::cur); + if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) return 0; skip = post; } if(skip > 0) - fseek(fp, skip, SEEK_CUR); + istream.seekg(skip, std::ios::cur); return 1; } // Read the RIFF/RIFX WAVE list or data chunk, converting all elements to // doubles. -static int ReadWaveList(FILE *fp, const SourceRefT *src, const ByteOrderT order, const uint n, double *hrir) +static int ReadWaveList(std::istream &istream, const SourceRefT *src, const ByteOrderT order, + const uint n, double *hrir) { uint32_t fourCC, chunkSize, listSize, count; uint block, skip, offset, i; @@ -870,8 +894,8 @@ static int ReadWaveList(FILE *fp, const SourceRefT *src, const ByteOrderT order, for(;;) { - if(!ReadBin4(fp, src->mPath, BO_LITTLE, 4, &fourCC) || - !ReadBin4(fp, src->mPath, 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) @@ -883,21 +907,21 @@ static int ReadWaveList(FILE *fp, const SourceRefT *src, const ByteOrderT order, fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath); return 0; } - fseek(fp, static_cast(src->mOffset * block), SEEK_CUR); - if(!ReadWaveData(fp, src, order, n, &hrir[0])) + istream.seekg(static_cast(src->mOffset * block), std::ios::cur); + if(!ReadWaveData(istream, src, order, n, &hrir[0])) return 0; return 1; } else if(fourCC == FOURCC_LIST) { - if(!ReadBin4(fp, src->mPath, BO_LITTLE, 4, &fourCC)) + if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)) return 0; chunkSize -= 4; if(fourCC == FOURCC_WAVL) break; } if(chunkSize > 0) - fseek(fp, static_cast(chunkSize), SEEK_CUR); + istream.seekg(static_cast(chunkSize), std::ios::cur); } listSize = chunkSize; block = src->mSize * src->mSkip; @@ -906,8 +930,8 @@ static int ReadWaveList(FILE *fp, const SourceRefT *src, const ByteOrderT order, lastSample = 0.0; while(offset < n && listSize > 8) { - if(!ReadBin4(fp, src->mPath, BO_LITTLE, 4, &fourCC) || - !ReadBin4(fp, src->mPath, 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) @@ -915,13 +939,13 @@ static int ReadWaveList(FILE *fp, const SourceRefT *src, const ByteOrderT order, count = chunkSize / block; if(count > skip) { - fseek(fp, static_cast(skip * block), SEEK_CUR); + istream.seekg(static_cast(skip * block), std::ios::cur); chunkSize -= skip * block; count -= skip; skip = 0; if(count > (n - offset)) count = n - offset; - if(!ReadWaveData(fp, src, order, count, &hrir[offset])) + if(!ReadWaveData(istream, src, order, count, &hrir[offset])) return 0; chunkSize -= count * block; offset += count; @@ -935,7 +959,7 @@ static int ReadWaveList(FILE *fp, const SourceRefT *src, const ByteOrderT order, } else if(fourCC == FOURCC_SLNT) { - if(!ReadBin4(fp, src->mPath, order, 4, &count)) + if(!ReadBin4(istream, src->mPath, order, 4, &count)) return 0; chunkSize -= 4; if(count > skip) @@ -955,7 +979,7 @@ static int ReadWaveList(FILE *fp, const SourceRefT *src, const ByteOrderT order, } } if(chunkSize > 0) - fseek(fp, static_cast(chunkSize), SEEK_CUR); + istream.seekg(static_cast(chunkSize), std::ios::cur); } if(offset < n) { @@ -967,13 +991,14 @@ static int ReadWaveList(FILE *fp, const SourceRefT *src, const ByteOrderT order, // Load a source HRIR from an ASCII text file containing a list of elements // separated by whitespace or common list operators (',', ';', ':', '|'). -static int LoadAsciiSource(FILE *fp, const SourceRefT *src, const uint n, double *hrir) +static int LoadAsciiSource(std::istream &istream, const SourceRefT *src, + const uint n, double *hrir) { - TokenReaderT tr; + TokenReaderT tr{istream}; uint i, j; double dummy; - TrSetup(fp, nullptr, 0, nullptr, &tr); + TrSetup(nullptr, 0, nullptr, &tr); for(i = 0;i < src->mOffset;i++) { if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &dummy)) @@ -993,29 +1018,29 @@ static int LoadAsciiSource(FILE *fp, const SourceRefT *src, const uint n, double } // Load a source HRIR from a binary file. -static int LoadBinarySource(FILE *fp, const SourceRefT *src, const ByteOrderT order, const uint n, double *hrir) +static int LoadBinarySource(std::istream &istream, const SourceRefT *src, const ByteOrderT order, + const uint n, double *hrir) { - uint i; - - fseek(fp, static_cast(src->mOffset), SEEK_SET); - for(i = 0;i < n;i++) + istream.seekg(static_cast(src->mOffset), std::ios::beg); + for(uint i{0};i < n;i++) { - if(!ReadBinAsDouble(fp, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) + if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) return 0; if(src->mSkip > 0) - fseek(fp, static_cast(src->mSkip), SEEK_CUR); + istream.seekg(static_cast(src->mSkip), std::ios::cur); } return 1; } // Load a source HRIR from a RIFF/RIFX WAVE file. -static int LoadWaveSource(FILE *fp, SourceRefT *src, const uint hrirRate, const uint n, double *hrir) +static int LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hrirRate, + const uint n, double *hrir) { uint32_t fourCC, dummy; ByteOrderT order; - if(!ReadBin4(fp, src->mPath, BO_LITTLE, 4, &fourCC) || - !ReadBin4(fp, src->mPath, 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; @@ -1027,16 +1052,16 @@ static int LoadWaveSource(FILE *fp, SourceRefT *src, const uint hrirRate, const return 0; } - if(!ReadBin4(fp, src->mPath, 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); return 0; } - if(!ReadWaveFormat(fp, order, hrirRate, src)) + if(!ReadWaveFormat(istream, order, hrirRate, src)) return 0; - if(!ReadWaveList(fp, src, order, n, hrir)) + if(!ReadWaveList(istream, src, order, n, hrir)) return 0; return 1; } @@ -1046,7 +1071,7 @@ static int LoadWaveSource(FILE *fp, SourceRefT *src, const uint hrirRate, const // Load a Spatially Oriented Format for Accoustics (SOFA) file. static MYSOFA_EASY* LoadSofaFile(SourceRefT *src, const uint hrirRate, const uint n) { - struct MYSOFA_EASY *sofa{mysofa_cache_lookup(src->mPath, (float)hrirRate)}; + struct MYSOFA_EASY *sofa{mysofa_cache_lookup(src->mPath, static_cast(hrirRate))}; if(sofa) return sofa; sofa = static_cast(calloc(1, sizeof(*sofa))); @@ -1066,14 +1091,9 @@ static MYSOFA_EASY* LoadSofaFile(SourceRefT *src, const uint hrirRate, const uin fprintf(stderr, "\nError: Could not load source file '%s'.\n", src->mPath); return nullptr; } + /* NOTE: Some valid SOFA files are failing this check. */ err = mysofa_check(sofa->hrtf); if(err != MYSOFA_OK) -/* NOTE: Some valid SOFA files are failing this check. - { - mysofa_close(sofa); - fprintf(stderr, "\nError: Malformed source file '%s'.\n", src->mPath); - return nullptr; - }*/ fprintf(stderr, "\nWarning: Supposedly malformed source file '%s'.\n", src->mPath); if((src->mOffset + n) > sofa->hrtf->N) { @@ -1095,7 +1115,7 @@ static MYSOFA_EASY* LoadSofaFile(SourceRefT *src, const uint hrirRate, const uin fprintf(stderr, "\nError: Out of memory.\n"); return nullptr; } - return mysofa_cache_store(sofa, src->mPath, (float)hrirRate); + return mysofa_cache_store(sofa, src->mPath, static_cast(hrirRate)); } // Copies the HRIR data from a particular SOFA measurement. @@ -1122,9 +1142,9 @@ static int LoadSofaSource(SourceRefT *src, const uint hrirRate, const uint n, do various coordinate systems, listener/source orientations, and direciontal vectors defined in the SOFA file. */ - target[0] = src->mAzimuth; - target[1] = src->mElevation; - target[2] = src->mRadius; + target[0] = static_cast(src->mAzimuth); + target[1] = static_cast(src->mElevation); + target[2] = static_cast(src->mRadius); mysofa_s2c(target); nearest = mysofa_lookup(sofa->lookup, target); @@ -1146,7 +1166,7 @@ static int LoadSofaSource(SourceRefT *src, const uint hrirRate, const uint n, do return 0; } - ExtractSofaHrir(sofa, nearest, src->mChannel, src->mOffset, n, hrir); + ExtractSofaHrir(sofa, static_cast(nearest), src->mChannel, src->mOffset, n, hrir); return 1; } @@ -1154,41 +1174,40 @@ static int LoadSofaSource(SourceRefT *src, const uint hrirRate, const uint n, do // Load a source HRIR from a supported file type. static int LoadSource(SourceRefT *src, const uint hrirRate, const uint n, double *hrir) { - FILE *fp{nullptr}; + std::unique_ptr istream; if(src->mFormat != SF_SOFA) { if(src->mFormat == SF_ASCII) - fp = fopen(src->mPath, "r"); + istream.reset(new al::ifstream{src->mPath}); else - fp = fopen(src->mPath, "rb"); - if(fp == nullptr) + istream.reset(new al::ifstream{src->mPath, std::ios::binary}); + if(!istream->good()) { fprintf(stderr, "\nError: Could not open source file '%s'.\n", src->mPath); return 0; } } - int result; + int result{0}; switch(src->mFormat) { case SF_ASCII: - result = LoadAsciiSource(fp, src, n, hrir); + result = LoadAsciiSource(*istream, src, n, hrir); break; case SF_BIN_LE: - result = LoadBinarySource(fp, src, BO_LITTLE, n, hrir); + result = LoadBinarySource(*istream, src, BO_LITTLE, n, hrir); break; case SF_BIN_BE: - result = LoadBinarySource(fp, src, BO_BIG, n, hrir); + result = LoadBinarySource(*istream, src, BO_BIG, n, hrir); break; case SF_WAVE: - result = LoadWaveSource(fp, src, hrirRate, n, hrir); + result = LoadWaveSource(*istream, src, hrirRate, n, hrir); break; case SF_SOFA: result = LoadSofaSource(src, hrirRate, n, hrir); break; - default: - result = 0; + case SF_NONE: + break; } - if(fp) fclose(fp); return result; } @@ -1196,9 +1215,9 @@ static int LoadSource(SourceRefT *src, const uint hrirRate, const uint n, double // Match the channel type from a given identifier. static ChannelTypeT MatchChannelType(const char *ident) { - if(strcasecmp(ident, "mono") == 0) + if(al::strcasecmp(ident, "mono") == 0) return CT_MONO; - if(strcasecmp(ident, "stereo") == 0) + if(al::strcasecmp(ident, "stereo") == 0) return CT_STEREO; return CT_NONE; } @@ -1225,7 +1244,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc TrIndication(tr, &line, &col); if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) return 0; - if(strcasecmp(ident, "rate") == 0) + if(al::strcasecmp(ident, "rate") == 0) { if(hasRate) { @@ -1239,7 +1258,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc hData->mIrRate = static_cast(intVal); hasRate = 1; } - else if(strcasecmp(ident, "type") == 0) + else if(al::strcasecmp(ident, "type") == 0) { char type[MAX_IDENT_LEN+1]; @@ -1266,7 +1285,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc } hasType = 1; } - else if(strcasecmp(ident, "points") == 0) + else if(al::strcasecmp(ident, "points") == 0) { if(hasPoints) { @@ -1296,7 +1315,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc hData->mIrSize = points; hasPoints = 1; } - else if(strcasecmp(ident, "radius") == 0) + else if(al::strcasecmp(ident, "radius") == 0) { if(hasRadius) { @@ -1310,7 +1329,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc hData->mRadius = fpVal; hasRadius = 1; } - else if(strcasecmp(ident, "distance") == 0) + else if(al::strcasecmp(ident, "distance") == 0) { uint count = 0; @@ -1349,7 +1368,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc fdCount = count; hasDistance = 1; } - else if(strcasecmp(ident, "azimuths") == 0) + else if(al::strcasecmp(ident, "azimuths") == 0) { uint count = 0; @@ -1467,15 +1486,15 @@ static int ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, // Match the source format from a given identifier. static SourceFormatT MatchSourceFormat(const char *ident) { - if(strcasecmp(ident, "ascii") == 0) + if(al::strcasecmp(ident, "ascii") == 0) return SF_ASCII; - if(strcasecmp(ident, "bin_le") == 0) + if(al::strcasecmp(ident, "bin_le") == 0) return SF_BIN_LE; - if(strcasecmp(ident, "bin_be") == 0) + if(al::strcasecmp(ident, "bin_be") == 0) return SF_BIN_BE; - if(strcasecmp(ident, "wave") == 0) + if(al::strcasecmp(ident, "wave") == 0) return SF_WAVE; - if(strcasecmp(ident, "sofa") == 0) + if(al::strcasecmp(ident, "sofa") == 0) return SF_SOFA; return SF_NONE; } @@ -1483,9 +1502,9 @@ static SourceFormatT MatchSourceFormat(const char *ident) // Match the source element type from a given identifier. static ElementTypeT MatchElementType(const char *ident) { - if(strcasecmp(ident, "int") == 0) + if(al::strcasecmp(ident, "int") == 0) return ET_INT; - if(strcasecmp(ident, "fp") == 0) + if(al::strcasecmp(ident, "fp") == 0) return ET_FP; return ET_NONE; } @@ -1531,7 +1550,7 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) src->mType = ET_NONE; src->mSize = 0; src->mBits = 0; - src->mChannel = (uint)intVal; + src->mChannel = static_cast(intVal); src->mSkip = 0; } else if(src->mFormat == SF_WAVE) @@ -1665,7 +1684,7 @@ static int ReadSofaRef(TokenReaderT *tr, SourceRefT *src) TrReadOperator(tr, "@"); if(!TrReadInt(tr, 0, 0x7FFFFFFF, &intVal)) return 0; - src->mOffset = (uint)intVal; + src->mOffset = static_cast(intVal); } else src->mOffset = 0; @@ -1679,9 +1698,9 @@ static int ReadSofaRef(TokenReaderT *tr, SourceRefT *src) // Match the target ear (index) from a given identifier. static int MatchTargetEar(const char *ident) { - if(strcasecmp(ident, "left") == 0) + if(al::strcasecmp(ident, "left") == 0) return 0; - if(strcasecmp(ident, "right") == 0) + if(al::strcasecmp(ident, "right") == 0) return 1; return -1; } @@ -1692,23 +1711,15 @@ static double AverageHrirOnset(const uint rate, const uint n, const double *hrir { std::vector upsampled(10 * n); { - ResamplerT rs; - ResamplerSetup(&rs, rate, 10 * rate); - ResamplerRun(&rs, n, hrir, 10 * n, upsampled.data()); + PPhaseResampler rs; + rs.init(rate, 10 * rate); + rs.process(n, hrir, 10 * n, upsampled.data()); } - double mag{0.0}; - for(uint i{0u};i < 10*n;i++) - mag = std::max(std::abs(upsampled[i]), mag); - - mag *= 0.15; - uint i{0u}; - for(;i < 10*n;i++) - { - if(std::abs(upsampled[i]) >= mag) - break; - } - return Lerp(onset, static_cast(i) / (10*rate), f); + auto abs_lt = [](const double &lhs, const double &rhs) -> bool + { return std::abs(lhs) < std::abs(rhs); }; + auto iter = std::max_element(upsampled.cbegin(), upsampled.cend(), abs_lt); + return Lerp(onset, static_cast(std::distance(upsampled.cbegin(), iter))/(10*rate), f); } // Calculate the magnitude response of an HRIR and average it with any @@ -1736,7 +1747,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData) hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize); double *hrirs = hData->mHrirsBase.data(); std::vector hrir(hData->mIrPoints); - uint line, col, fi, ei, ai, ti; + uint line, col, fi, ei, ai; int count; printf("Loading sources..."); @@ -1826,27 +1837,25 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData) for(fi = 0;fi < hData->mFdCount;fi++) { double delta = aer[2] - hData->mFds[fi].mDistance; - if(std::abs(delta) < 0.001) - break; + if(std::abs(delta) < 0.001) break; } if(fi >= hData->mFdCount) continue; - double ef{(90.0 + aer[1]) * (hData->mFds[fi].mEvCount - 1) / 180.0}; - ei = (int)std::round(ef); - ef = (ef - ei) * 180.0f / (hData->mFds[fi].mEvCount - 1); + double ef{(90.0 + aer[1]) / 180.0 * (hData->mFds[fi].mEvCount - 1)}; + ei = static_cast(std::round(ef)); + ef = (ef - ei) * 180.0 / (hData->mFds[fi].mEvCount - 1); if(std::abs(ef) >= 0.1) continue; - double af{aer[0] * hData->mFds[fi].mEvs[ei].mAzCount / 360.0f}; - ai = (int)std::round(af); - af = (af - ai) * 360.0f / hData->mFds[fi].mEvs[ei].mAzCount; + double af{aer[0] / 360.0 * hData->mFds[fi].mEvs[ei].mAzCount}; + ai = static_cast(std::round(af)); + af = (af - ai) * 360.0 / hData->mFds[fi].mEvs[ei].mAzCount; ai = ai % hData->mFds[fi].mEvs[ei].mAzCount; if(std::abs(af) >= 0.1) continue; HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] != nullptr) { TrErrorAt(tr, line, col, "Redefinition of source [ %d, %d, %d ].\n", fi, ei, ai); @@ -1891,7 +1900,6 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData) for(;;) { SourceRefT src; - uint ti = 0; if(!ReadSourceRef(tr, &src)) return 0; @@ -1906,13 +1914,14 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData) if(!LoadSource(&src, hData->mIrRate, hData->mIrPoints, hrir.data())) return 0; + uint ti{0}; if(hData->mChannelType == CT_STEREO) { char ident[MAX_IDENT_LEN+1]; if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) return 0; - ti = MatchTargetEar(ident); + ti = static_cast(MatchTargetEar(ident)); if(static_cast(ti) < 0) { TrErrorAt(tr, line, col, "Expected a target ear.\n"); @@ -1975,7 +1984,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData) } } } - for(ti = 0;ti < channels;ti++) + for(uint ti{0};ti < channels;ti++) { for(fi = 0;fi < hData->mFdCount;fi++) { @@ -2002,24 +2011,16 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData) } -bool LoadDefInput(FILE *fp, const char *startbytes, size_t startbytecount, const char *filename, - const uint fftSize, const uint truncSize, const ChannelModeT chanMode, HrirDataT *hData) +bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount, + const char *filename, const uint fftSize, const uint truncSize, const ChannelModeT chanMode, + HrirDataT *hData) { - TokenReaderT tr; + TokenReaderT tr{istream}; - TrSetup(fp, startbytes, startbytecount, filename, &tr); - if(!ProcessMetrics(&tr, fftSize, truncSize, chanMode, hData)) - { - if(fp != stdin) - fclose(fp); + TrSetup(startbytes, startbytecount, filename, &tr); + if(!ProcessMetrics(&tr, fftSize, truncSize, chanMode, hData) + || !ProcessSources(&tr, hData)) return false; - } - if(!ProcessSources(&tr, hData)) - { - if(fp != stdin) - fclose(fp); - return false; - } return true; } diff --git a/modules/openal-soft/utils/makemhr/loaddef.h b/modules/openal-soft/utils/makemhr/loaddef.h index 0586222..34fbb83 100644 --- a/modules/openal-soft/utils/makemhr/loaddef.h +++ b/modules/openal-soft/utils/makemhr/loaddef.h @@ -1,12 +1,13 @@ #ifndef LOADDEF_H #define LOADDEF_H -#include +#include #include "makemhr.h" -bool LoadDefInput(FILE *fp, const char *startbytes, size_t startbytecount, const char *filename, - const uint fftSize, const uint truncSize, const ChannelModeT chanMode, HrirDataT *hData); +bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount, + const char *filename, const uint fftSize, const uint truncSize, const ChannelModeT chanMode, + HrirDataT *hData); #endif /* LOADDEF_H */ diff --git a/modules/openal-soft/utils/makemhr/loadsofa.cpp b/modules/openal-soft/utils/makemhr/loadsofa.cpp index b6dd66d..7d091be 100644 --- a/modules/openal-soft/utils/makemhr/loadsofa.cpp +++ b/modules/openal-soft/utils/makemhr/loadsofa.cpp @@ -21,135 +21,30 @@ * Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html */ -#include -#include -#include - -#include "mysofa.h" - #include "loadsofa.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -static const char *SofaErrorStr(int err) -{ - switch(err) - { - case MYSOFA_OK: return "OK"; - case MYSOFA_INVALID_FORMAT: return "Invalid format"; - case MYSOFA_UNSUPPORTED_FORMAT: return "Unsupported format"; - case MYSOFA_INTERNAL_ERROR: return "Internal error"; - case MYSOFA_NO_MEMORY: return "Out of memory"; - case MYSOFA_READ_ERROR: return "Read error"; - } - return "Unknown"; -} - - -/* Produces a sorted array of unique elements from a particular axis of the - * triplets array. The filters are used to focus on particular coordinates - * of other axes as necessary. The epsilons are used to constrain the - * equality of unique elements. - */ -static uint GetUniquelySortedElems(const uint m, const float *triplets, const int axis, - const double *const (&filters)[3], const double (&epsilons)[3], float *elems) -{ - uint count{0u}; - for(uint i{0u};i < 3*m;i += 3) - { - const float elem{triplets[i + axis]}; - - uint j; - for(j = 0;j < 3;j++) - { - if(filters[j] && std::fabs(triplets[i + j] - *filters[j]) > epsilons[j]) - break; - } - if(j < 3) - continue; - - for(j = 0;j < count;j++) - { - const float delta{elem - elems[j]}; - - if(delta > epsilons[axis]) - continue; - - if(delta >= -epsilons[axis]) - break; - - for(uint k{count};k > j;k--) - elems[k] = elems[k - 1]; - - elems[j] = elem; - count++; - break; - } - - if(j >= count) - elems[count++] = elem; - } - - return count; -} - -/* Given a list of elements, this will produce the smallest step size that - * can uniformly cover a fair portion of the list. Ideally this will be over - * half, but in degenerate cases this can fall to a minimum of 5 (the lower - * limit on elevations necessary to build a layout). - */ -static float GetUniformStepSize(const double epsilon, const uint m, const float *elems) -{ - auto steps = std::vector(m, 0.0f); - auto counts = std::vector(m, 0u); - float step{0.0f}; - uint count{0u}; - - for(uint stride{1u};stride < m/2;stride++) - { - for(uint i{0u};i < m-stride;i++) - { - const float step{elems[i + stride] - elems[i]}; - - uint j; - for(j = 0;j < count;j++) - { - if(std::fabs(step - steps[j]) < epsilon) - { - counts[j]++; - break; - } - } - - if(j >= count) - { - steps[j] = step; - counts[j] = 1; - count++; - } - } +#include "makemhr.h" +#include "polyphase_resampler.h" +#include "sofa-support.h" - for(uint i{1u};i < count;i++) - { - if(counts[i] > counts[0]) - { - steps[0] = steps[i]; - counts[0] = counts[i]; - } - } - - count = 1; +#include "mysofa.h" - if(counts[0] > m/2) - { - step = steps[0]; - return step; - } - } - if(counts[0] > 5) - step = steps[0]; - return step; -} +using uint = unsigned int; /* Attempts to produce a compatible layout. Most data sets tend to be * uniform and have the same major axis as used by OpenAL Soft's HRTF model. @@ -159,21 +54,10 @@ static float GetUniformStepSize(const double epsilon, const uint m, const float */ static bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData) { - std::vector aers(3*m, 0.0f); - std::vector elems(m, 0.0f); + fprintf(stdout, "Detecting compatible layout...\n"); - for(uint i{0u};i < 3*m;i += 3) - { - aers[i] = xyzs[i]; - aers[i + 1] = xyzs[i + 1]; - aers[i + 2] = xyzs[i + 2]; - mysofa_c2s(&aers[i]); - } - - const uint fdCount{GetUniquelySortedElems(m, aers.data(), 2, - (const double*[3]){ nullptr, nullptr, nullptr }, (const double[3]){ 0.1, 0.1, 0.001 }, - elems.data())}; - if(fdCount > MAX_FD_COUNT) + auto fds = GetCompatibleLayout(m, xyzs); + if(fds.size() > MAX_FD_COUNT) { fprintf(stdout, "Incompatible layout (inumerable radii).\n"); return false; @@ -181,105 +65,26 @@ static bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData) double distances[MAX_FD_COUNT]{}; uint evCounts[MAX_FD_COUNT]{}; - uint evStarts[MAX_FD_COUNT]{}; - auto azCounts = std::vector(MAX_FD_COUNT * MAX_EV_COUNT); - for(uint fi{0u};fi < fdCount;fi++) - { - distances[fi] = elems[fi]; - if(fi > 0 && distances[fi] <= distances[fi-1]) - { - fprintf(stderr, "Distances must increase.\n"); - return 0; - } - } - if(distances[0] < hData->mRadius) - { - fprintf(stderr, "Distance cannot start below head radius.\n"); - return 0; - } + auto azCounts = std::vector(MAX_FD_COUNT*MAX_EV_COUNT, 0u); - for(uint fi{0u};fi < fdCount;fi++) + uint fi{0u}, ir_total{0u}; + for(const auto &field : fds) { - const double dist{distances[fi]}; - uint evCount{GetUniquelySortedElems(m, aers.data(), 1, - (const double*[3]){ nullptr, nullptr, &dist }, (const double[3]){ 0.1, 0.1, 0.001 }, - elems.data())}; - - if(evCount > MAX_EV_COUNT) - { - fprintf(stderr, "Incompatible layout (innumerable elevations).\n"); - return false; - } - - float step{GetUniformStepSize(0.1, evCount, elems.data())}; - if(step <= 0.0f) - { - fprintf(stderr, "Incompatible layout (non-uniform elevations).\n"); - return false; - } - - uint evStart{0u}; - for(uint ei{0u};ei < evCount;ei++) - { - float ev{90.0f + elems[ei]}; - float eif{std::round(ev / step)}; - - if(std::fabs(eif - (uint)eif) < (0.1f / step)) - { - evStart = static_cast(eif); - break; - } - } - - evCount = static_cast(std::round(180.0f / step)) + 1; - if(evCount < 5) - { - fprintf(stderr, "Incompatible layout (too few uniform elevations).\n"); - return false; - } + distances[fi] = field.mDistance; + evCounts[fi] = field.mEvCount; - evCounts[fi] = evCount; - evStarts[fi] = evStart; - - for(uint ei{evStart};ei < evCount;ei++) + for(uint ei{0u};ei < field.mEvStart;ei++) + azCounts[fi*MAX_EV_COUNT + ei] = field.mAzCounts[field.mEvCount-ei-1]; + for(uint ei{field.mEvStart};ei < field.mEvCount;ei++) { - const double ev{-90.0 + ei*180.0/(evCount - 1)}; - const uint azCount{GetUniquelySortedElems(m, aers.data(), 0, - (const double*[3]){ nullptr, &ev, &dist }, (const double[3]){ 0.1, 0.1, 0.001 }, - elems.data())}; - - if(azCount > MAX_AZ_COUNT) - { - fprintf(stderr, "Incompatible layout (innumerable azimuths).\n"); - return false; - } - - if(ei > 0 && ei < (evCount - 1)) - { - step = GetUniformStepSize(0.1, azCount, elems.data()); - if(step <= 0.0f) - { - fprintf(stderr, "Incompatible layout (non-uniform azimuths).\n"); - return false; - } - - azCounts[fi*MAX_EV_COUNT + ei] = static_cast(std::round(360.0f / step)); - } - else if(azCount != 1) - { - fprintf(stderr, "Incompatible layout (non-singular poles).\n"); - return false; - } - else - { - azCounts[fi*MAX_EV_COUNT + ei] = 1; - } + azCounts[fi*MAX_EV_COUNT + ei] = field.mAzCounts[ei]; + ir_total += field.mAzCounts[ei]; } - for(uint ei{0u};ei < evStart;ei++) - azCounts[fi*MAX_EV_COUNT + ei] = azCounts[fi*MAX_EV_COUNT + evCount - ei - 1]; + ++fi; } - return PrepareHrirData(fdCount, distances, evCounts, azCounts.data(), hData) != 0; + fprintf(stdout, "Using %u of %u IRs.\n", ir_total, m); + return PrepareHrirData(fi, distances, evCounts, azCounts.data(), hData) != 0; } @@ -427,120 +232,151 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf) /* Calculate the onset time of a HRIR. */ -static double CalcHrirOnset(const uint rate, const uint n, std::vector &upsampled, - const double *hrir) +static constexpr int OnsetRateMultiple{10}; +static double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n, + std::vector &upsampled, const double *hrir) { - { - ResamplerT rs; - ResamplerSetup(&rs, rate, 10 * rate); - ResamplerRun(&rs, n, hrir, 10 * n, upsampled.data()); - } - - double mag{std::accumulate(upsampled.cbegin(), upsampled.cend(), double{0.0}, - [](const double mag, const double sample) -> double - { return std::max(mag, std::abs(sample)); })}; + rs.process(n, hrir, static_cast(upsampled.size()), upsampled.data()); - mag *= 0.15; - auto iter = std::find_if(upsampled.cbegin(), upsampled.cend(), - [mag](const double sample) -> bool { return (std::abs(sample) >= mag); }); - return static_cast(std::distance(upsampled.cbegin(), iter)) / (10.0*rate); + auto abs_lt = [](const double &lhs, const double &rhs) -> bool + { return std::abs(lhs) < std::abs(rhs); }; + auto iter = std::max_element(upsampled.cbegin(), upsampled.cend(), abs_lt); + return static_cast(std::distance(upsampled.cbegin(), iter)) / + (double{OnsetRateMultiple}*rate); } /* Calculate the magnitude response of a HRIR. */ static void CalcHrirMagnitude(const uint points, const uint n, std::vector &h, - const double *hrir, double *mag) + double *hrir) { auto iter = std::copy_n(hrir, points, h.begin()); std::fill(iter, h.end(), complex_d{0.0, 0.0}); FftForward(n, h.data()); - MagnitudeResponse(n, h.data(), mag); + MagnitudeResponse(n, h.data(), hrir); } static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData) { - const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; - hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize); - double *hrirs = hData->mHrirsBase.data(); - - /* Temporary buffers used to calculate the IR's onset and frequency - * magnitudes. - */ - auto upsampled = std::vector(10 * hData->mIrPoints); - auto htemp = std::vector(hData->mFftSize); - auto hrir = std::vector(hData->mFftSize); + std::atomic loaded_count{0u}; - for(uint si{0u};si < sofaHrtf->M;si++) + auto load_proc = [sofaHrtf,hData,&loaded_count]() -> bool { - printf("\rLoading HRIRs... %d of %d", si+1, sofaHrtf->M); - fflush(stdout); + const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; + hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize, 0.0); + double *hrirs = hData->mHrirsBase.data(); - float aer[3]{ - sofaHrtf->SourcePosition.values[3*si], - sofaHrtf->SourcePosition.values[3*si + 1], - sofaHrtf->SourcePosition.values[3*si + 2] - }; - mysofa_c2s(aer); + for(uint si{0u};si < sofaHrtf->M;++si) + { + loaded_count.fetch_add(1u); - if(std::abs(aer[1]) >= 89.999f) - aer[0] = 0.0f; - else - aer[0] = std::fmod(360.0f - aer[0], 360.0f); + float aer[3]{ + sofaHrtf->SourcePosition.values[3*si], + sofaHrtf->SourcePosition.values[3*si + 1], + sofaHrtf->SourcePosition.values[3*si + 2] + }; + mysofa_c2s(aer); - auto field = std::find_if(hData->mFds.cbegin(), hData->mFds.cend(), - [&aer](const HrirFdT &fld) -> bool + if(std::abs(aer[1]) >= 89.999f) + aer[0] = 0.0f; + else + aer[0] = std::fmod(360.0f - aer[0], 360.0f); + + auto field = std::find_if(hData->mFds.cbegin(), hData->mFds.cend(), + [&aer](const HrirFdT &fld) -> bool + { + double delta = aer[2] - fld.mDistance; + return (std::abs(delta) < 0.001); + }); + if(field == hData->mFds.cend()) + continue; + + double ef{(90.0+aer[1]) / 180.0 * (field->mEvCount-1)}; + auto ei = static_cast(std::round(ef)); + ef = (ef-ei) * 180.0 / (field->mEvCount-1); + if(std::abs(ef) >= 0.1) continue; + + double af{aer[0] / 360.0 * field->mEvs[ei].mAzCount}; + auto ai = static_cast(std::round(af)); + af = (af-ai) * 360.0 / field->mEvs[ei].mAzCount; + ai %= field->mEvs[ei].mAzCount; + if(std::abs(af) >= 0.1) continue; + + HrirAzT *azd = &field->mEvs[ei].mAzs[ai]; + if(azd->mIrs[0] != nullptr) { - double delta = aer[2] - fld.mDistance; - return (std::abs(delta) < 0.001); - }); - if(field == hData->mFds.cend()) - continue; - - double ef{(90.0+aer[1]) * (field->mEvCount-1) / 180.0}; - auto ei = static_cast(std::round(ef)); - ef = (ef-ei) * 180.0f / (field->mEvCount-1); - if(std::abs(ef) >= 0.1) continue; - - double af{aer[0] * field->mEvs[ei].mAzCount / 360.0f}; - auto ai = static_cast(std::round(af)); - af = (af-ai) * 360.0f / field->mEvs[ei].mAzCount; - ai %= field->mEvs[ei].mAzCount; - if(std::abs(af) >= 0.1) continue; - - HrirAzT *azd = &field->mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] != nullptr) - { - fprintf(stderr, "Multiple measurements near [ a=%f, e=%f, r=%f ].\n", - aer[0], aer[1], aer[2]); - return false; + fprintf(stderr, "\nMultiple measurements near [ a=%f, e=%f, r=%f ].\n", + aer[0], aer[1], aer[2]); + return false; + } + + for(uint ti{0u};ti < channels;++ti) + { + azd->mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd->mIndex)]; + std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N], + hData->mIrPoints, azd->mIrs[ti]); + } + + /* TODO: Since some SOFA files contain minimum phase HRIRs, + * it would be beneficial to check for per-measurement delays + * (when available) to reconstruct the HRTDs. + */ } + return true; + }; + + std::future_status load_status{}; + 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); + fflush(stdout); + } while(load_status != std::future_status::ready); + fputc('\n', stdout); + return load_future.get(); +} + - for(uint ti{0u};ti < channels;++ti) +/* Calculates the frequency magnitudes of the HRIR set. Work is delegated to + * this struct, which runs asynchronously on one or more threads (sharing the + * same calculator object). + */ +struct MagCalculator { + const uint mFftSize{}; + const uint mIrPoints{}; + std::vector mIrs{}; + std::atomic mCurrent{}; + std::atomic mDone{}; + + void Worker() + { + auto htemp = std::vector(mFftSize); + + while(1) { - std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N], - hData->mIrPoints, hrir.begin()); - azd->mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd->mIndex)]; - azd->mDelays[ti] = CalcHrirOnset(hData->mIrRate, hData->mIrPoints, upsampled, - hrir.data()); - CalcHrirMagnitude(hData->mIrPoints, hData->mFftSize, htemp, hrir.data(), - azd->mIrs[ti]); + /* Load the current index to process. */ + size_t idx{mCurrent.load()}; + do { + /* If the index is at the end, we're done. */ + if(idx >= mIrs.size()) + return; + /* Otherwise, increment the current index atomically so other + * threads know to go to the next one. If this call fails, the + * current index was just changed by another thread and the new + * value is loaded into idx, which we'll recheck. + */ + } while(!mCurrent.compare_exchange_weak(idx, idx+1, std::memory_order_relaxed)); + + CalcHrirMagnitude(mIrPoints, mFftSize, htemp, mIrs[idx]); + + /* Increment the number of IRs done. */ + mDone.fetch_add(1); } - - // TODO: Since some SOFA files contain minimum phase HRIRs, - // it would be beneficial to check for per-measurement delays - // (when available) to reconstruct the HRTDs. } - printf("\n"); - return true; -} - -struct MySofaHrtfDeleter { - void operator()(MYSOFA_HRTF *ptr) { mysofa_free(ptr); } }; -using MySofaHrtfPtr = std::unique_ptr; -bool LoadSofaFile(const char *filename, const uint fftSize, const uint truncSize, - const ChannelModeT chanMode, HrirDataT *hData) +bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize, + const uint truncSize, const ChannelModeT chanMode, HrirDataT *hData) { int err; MySofaHrtfPtr sofaHrtf{mysofa_load(filename, &err)}; @@ -550,14 +386,9 @@ bool LoadSofaFile(const char *filename, const uint fftSize, const uint truncSize return false; } + /* NOTE: Some valid SOFA files are failing this check. */ err = mysofa_check(sofaHrtf.get()); if(err != MYSOFA_OK) -/* NOTE: Some valid SOFA files are failing this check. - { - fprintf(stdout, "Error: Malformed source file '%s' (%s).\n", filename, SofaErrorStr(err)); - return false; - } -*/ fprintf(stderr, "Warning: Supposedly malformed source file '%s' (%s).\n", filename, SofaErrorStr(err)); @@ -598,8 +429,8 @@ bool LoadSofaFile(const char *filename, const uint fftSize, const uint truncSize /* Assume a default head radius of 9cm. */ hData->mRadius = 0.09; - if(!PrepareSampleRate(sofaHrtf.get(), hData) || !PrepareDelay(sofaHrtf.get(), hData) || - !CheckIrData(sofaHrtf.get())) + if(!PrepareSampleRate(sofaHrtf.get(), hData) || !PrepareDelay(sofaHrtf.get(), hData) + || !CheckIrData(sofaHrtf.get())) return false; if(!PrepareLayout(sofaHrtf->M, sofaHrtf->SourcePosition.values, hData)) return false; @@ -642,11 +473,13 @@ bool LoadSofaFile(const char *filename, const uint fftSize, const uint truncSize } } + + size_t hrir_total{0}; const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; double *hrirs = hData->mHrirsBase.data(); for(uint fi{0u};fi < hData->mFdCount;fi++) { - for(uint ei{0u};ei < hData->mFds[fi].mEvCount;ei++) + for(uint ei{0u};ei < hData->mFds[fi].mEvStart;ei++) { for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) { @@ -655,7 +488,82 @@ bool LoadSofaFile(const char *filename, const uint fftSize, const uint truncSize azd.mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd.mIndex)]; } } + + for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;ei++) + hrir_total += hData->mFds[fi].mEvs[ei].mAzCount * channels; + } + + std::atomic hrir_done{0}; + auto onset_proc = [hData,channels,&hrir_done]() -> bool + { + /* Temporary buffer used to calculate the IR's onset. */ + auto upsampled = std::vector(OnsetRateMultiple * hData->mIrPoints); + /* This resampler is used to help detect the response onset. */ + PPhaseResampler rs; + rs.init(hData->mIrRate, OnsetRateMultiple*hData->mIrRate); + + for(uint fi{0u};fi < hData->mFdCount;fi++) + { + for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;ei++) + { + for(uint ai{0};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + { + HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; + for(uint ti{0};ti < channels;ti++) + { + hrir_done.fetch_add(1u, std::memory_order_acq_rel); + azd.mDelays[ti] = CalcHrirOnset(rs, hData->mIrRate, hData->mIrPoints, + upsampled, azd.mIrs[ti]); + } + } + } + } + return true; + }; + + std::future_status load_status{}; + 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); + fflush(stdout); + } while(load_status != std::future_status::ready); + fputc('\n', stdout); + if(!load_future.get()) + return false; + + MagCalculator calculator{hData->mFftSize, hData->mIrPoints}; + for(uint fi{0u};fi < hData->mFdCount;fi++) + { + for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;ei++) + { + for(uint ai{0};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + { + HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; + for(uint ti{0};ti < channels;ti++) + calculator.mIrs.push_back(azd.mIrs[ti]); + } + } } + std::vector thrds; + thrds.reserve(numThreads); + for(size_t i{0};i < numThreads;++i) + thrds.emplace_back(std::mem_fn(&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()); + fflush(stdout); + } while(count != calculator.mIrs.size()); + fputc('\n', stdout); + + for(auto &thrd : thrds) + { + if(thrd.joinable()) + thrd.join(); + } return true; } diff --git a/modules/openal-soft/utils/makemhr/loadsofa.h b/modules/openal-soft/utils/makemhr/loadsofa.h index 93bf170..803bcf8 100644 --- a/modules/openal-soft/utils/makemhr/loadsofa.h +++ b/modules/openal-soft/utils/makemhr/loadsofa.h @@ -4,7 +4,7 @@ #include "makemhr.h" -bool LoadSofaFile(const char *filename, const uint fftSize, const uint truncSize, - const ChannelModeT chanMode, HrirDataT *hData); +bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize, + const uint truncSize, const ChannelModeT chanMode, HrirDataT *hData); #endif /* LOADSOFA_H */ diff --git a/modules/openal-soft/utils/makemhr/makemhr.cpp b/modules/openal-soft/utils/makemhr/makemhr.cpp index f6e2893..554bdfe 100644 --- a/modules/openal-soft/utils/makemhr/makemhr.cpp +++ b/modules/openal-soft/utils/makemhr/makemhr.cpp @@ -59,45 +59,44 @@ * 1999 */ +#define _UNICODE #include "config.h" -#define _UNICODE +#include "makemhr.h" + +#include +#include +#include +#include +#include +#include #include #include -#include -#include #include -#include -#include -#include -#include -#ifdef HAVE_STRINGS_H -#include -#endif +#include +#include +#include +#include +#include +#include +#include +#include + #ifdef HAVE_GETOPT #include #else -#include "getopt.h" +#include "../getopt.h" #endif -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mysofa.h" - -#include "makemhr.h" +#include "alfstream.h" +#include "alspan.h" +#include "alstring.h" #include "loaddef.h" #include "loadsofa.h" #include "win_main_utf8.h" + namespace { using namespace std::placeholders; @@ -130,16 +129,12 @@ enum HeadModelT { // The limits to the truncation window size on the command line. #define MIN_TRUNCSIZE (16) -#define MAX_TRUNCSIZE (512) +#define MAX_TRUNCSIZE (128) // The limits to the custom head radius on the command line. #define MIN_CUSTOM_RADIUS (0.05) #define MAX_CUSTOM_RADIUS (0.15) -// The truncation window size must be a multiple of the below value to allow -// for vectorized convolution. -#define MOD_TRUNCSIZE (8) - // The defaults for the command line options. #define DEFAULT_FFTSIZE (65536) #define DEFAULT_EQUALIZE (1) @@ -153,8 +148,8 @@ enum HeadModelT { #define MAX_HRTD (63.0) // The OpenAL Soft HRTF format marker. It stands for minimum-phase head -// response protocol 02. -#define MHR_FORMAT ("MinPHR02") +// response protocol 03. +#define MHR_FORMAT ("MinPHR03") /* Channel index enums. Mono uses LeftChannel only. */ enum ChannelIndex : uint { @@ -167,42 +162,31 @@ enum ChannelIndex : uint { * pattern string are replaced with the replacement string. The result is * truncated if necessary. */ -static int StrSubst(const char *in, const char *pat, const char *rep, const size_t maxLen, char *out) +static std::string StrSubst(al::span in, const al::span pat, + const al::span rep) { - size_t inLen, patLen, repLen; - size_t si, di; - int truncated; - - inLen = strlen(in); - patLen = strlen(pat); - repLen = strlen(rep); - si = 0; - di = 0; - truncated = 0; - while(si < inLen && di < maxLen) + std::string ret; + ret.reserve(in.size() + pat.size()); + + while(in.size() >= pat.size()) { - if(patLen <= inLen-si) + if(al::strncasecmp(in.data(), pat.data(), pat.size()) == 0) { - if(strncasecmp(&in[si], pat, patLen) == 0) - { - if(repLen > maxLen-di) - { - repLen = maxLen - di; - truncated = 1; - } - strncpy(&out[di], rep, repLen); - si += patLen; - di += repLen; - } + in = in.subspan(pat.size()); + ret.append(rep.data(), rep.size()); + } + else + { + size_t endpos{1}; + while(endpos < in.size() && in[endpos] != pat.front()) + ++endpos; + ret.append(in.data(), endpos); + in = in.subspan(endpos); } - out[di] = in[si]; - si++; - di++; } - if(si < inLen) - truncated = 1; - out[di] = '\0'; - return !truncated; + ret.append(in.data(), in.size()); + + return ret; } @@ -225,15 +209,16 @@ static inline uint dither_rng(uint *seed) // Performs a triangular probability density function dither. The input samples // should be normalized (-1 to +1). static void TpdfDither(double *RESTRICT out, const double *RESTRICT in, const double scale, - const int count, const int step, uint *seed) + const uint count, const uint step, uint *seed) { static constexpr double PRNG_SCALE = 1.0 / std::numeric_limits::max(); - for(int i{0};i < count;i++) + for(uint i{0};i < count;i++) { uint prn0{dither_rng(seed)}; uint prn1{dither_rng(seed)}; - out[i*step] = std::round(in[i]*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE)); + *out = std::round(*(in++)*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE)); + out += step; } } @@ -259,18 +244,18 @@ static void FftArrange(const uint n, complex_d *inout) } // Performs the summation. -static void FftSummation(const int n, const double s, complex_d *cplx) +static void FftSummation(const uint n, const double s, complex_d *cplx) { double pi; - int m, m2; - int i, k, mk; + uint m, m2; + uint i, k, mk; pi = s * M_PI; for(m = 1, m2 = 2;m < n; m <<= 1, m2 <<= 1) { // v = Complex (-2.0 * sin (0.5 * pi / m) * sin (0.5 * pi / m), -sin (pi / m)) - double sm = sin(0.5 * pi / m); - auto v = complex_d{-2.0*sm*sm, -sin(pi / m)}; + double sm = std::sin(0.5 * pi / m); + auto v = complex_d{-2.0*sm*sm, -std::sin(pi / m)}; auto w = complex_d{1.0, 0.0}; for(i = 0;i < m;i++) { @@ -373,17 +358,13 @@ static void LimitMagnitudeResponse(const uint n, const uint m, const double limi * residuals (which were discarded). The mirrored half of the response is * reconstructed. */ -static void MinimumPhase(const uint n, const double *in, complex_d *out) +static void MinimumPhase(const uint n, double *mags, complex_d *out) { - const uint m = 1 + (n / 2); - std::vector mags(n); + const uint m{(n/2) + 1}; uint i; for(i = 0;i < m;i++) - { - mags[i] = std::max(EPSILON, in[i]); - out[i] = complex_d{std::log(mags[i]), 0.0}; - } + out[i] = std::log(mags[i]); for(;i < n;i++) { mags[i] = mags[n - i]; @@ -395,240 +376,7 @@ static void MinimumPhase(const uint n, const double *in, complex_d *out) for(i = 0;i < n;i++) { auto a = std::exp(complex_d{0.0, out[i].imag()}); - out[i] = complex_d{mags[i], 0.0} * a; - } -} - - -/*************************** - *** Resampler functions *** - ***************************/ - -/* This is the normalized cardinal sine (sinc) function. - * - * sinc(x) = { 1, x = 0 - * { sin(pi x) / (pi x), otherwise. - */ -static double Sinc(const double x) -{ - if(std::abs(x) < EPSILON) - return 1.0; - return std::sin(M_PI * x) / (M_PI * x); -} - -/* The zero-order modified Bessel function of the first kind, used for the - * Kaiser window. - * - * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k) - * = sum_{k=0}^inf ((x / 2)^k / k!)^2 - */ -static double BesselI_0(const double x) -{ - double term, sum, x2, y, last_sum; - int k; - - // Start at k=1 since k=0 is trivial. - term = 1.0; - sum = 1.0; - x2 = x/2.0; - k = 1; - - // Let the integration converge until the term of the sum is no longer - // significant. - do { - y = x2 / k; - k++; - last_sum = sum; - term *= y * y; - sum += term; - } while(sum != last_sum); - return sum; -} - -/* Calculate a Kaiser window from the given beta value and a normalized k - * [-1, 1]. - * - * w(k) = { I_0(B sqrt(1 - k^2)) / I_0(B), -1 <= k <= 1 - * { 0, elsewhere. - * - * Where k can be calculated as: - * - * k = i / l, where -l <= i <= l. - * - * or: - * - * k = 2 i / M - 1, where 0 <= i <= M. - */ -static double Kaiser(const double b, const double k) -{ - if(!(k >= -1.0 && k <= 1.0)) - return 0.0; - return BesselI_0(b * std::sqrt(1.0 - k*k)) / BesselI_0(b); -} - -// Calculates the greatest common divisor of a and b. -static uint Gcd(uint x, uint y) -{ - while(y > 0) - { - uint z{y}; - y = x % y; - x = z; - } - return x; -} - -/* Calculates the size (order) of the Kaiser window. Rejection is in dB and - * the transition width is normalized frequency (0.5 is nyquist). - * - * M = { ceil((r - 7.95) / (2.285 2 pi f_t)), r > 21 - * { ceil(5.79 / 2 pi f_t), r <= 21. - * - */ -static uint CalcKaiserOrder(const double rejection, const double transition) -{ - double w_t = 2.0 * M_PI * transition; - if(rejection > 21.0) - return static_cast(std::ceil((rejection - 7.95) / (2.285 * w_t))); - return static_cast(std::ceil(5.79 / w_t)); -} - -// Calculates the beta value of the Kaiser window. Rejection is in dB. -static double CalcKaiserBeta(const double rejection) -{ - if(rejection > 50.0) - return 0.1102 * (rejection - 8.7); - if(rejection >= 21.0) - return (0.5842 * std::pow(rejection - 21.0, 0.4)) + - (0.07886 * (rejection - 21.0)); - return 0.0; -} - -/* Calculates a point on the Kaiser-windowed sinc filter for the given half- - * width, beta, gain, and cutoff. The point is specified in non-normalized - * samples, from 0 to M, where M = (2 l + 1). - * - * w(k) 2 p f_t sinc(2 f_t x) - * - * x -- centered sample index (i - l) - * k -- normalized and centered window index (x / l) - * w(k) -- window function (Kaiser) - * p -- gain compensation factor when sampling - * f_t -- normalized center frequency (or cutoff; 0.5 is nyquist) - */ -static double SincFilter(const int l, const double b, const double gain, const double cutoff, const int i) -{ - return Kaiser(b, static_cast(i - l) / l) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * (i - l)); -} - -/* This is a polyphase sinc-filtered resampler. - * - * Upsample Downsample - * - * p/q = 3/2 p/q = 3/5 - * - * M-+-+-+-> M-+-+-+-> - * -------------------+ ---------------------+ - * p s * f f f f|f| | p s * f f f f f | - * | 0 * 0 0 0|0|0 | | 0 * 0 0 0 0|0| | - * v 0 * 0 0|0|0 0 | v 0 * 0 0 0|0|0 | - * s * f|f|f f f | s * f f|f|f f | - * 0 * |0|0 0 0 0 | 0 * 0|0|0 0 0 | - * --------+=+--------+ 0 * |0|0 0 0 0 | - * d . d .|d|. d . d ----------+=+--------+ - * d . . . .|d|. . . . - * q-> - * q-+-+-+-> - * - * P_f(i,j) = q i mod p + pj - * P_s(i,j) = floor(q i / p) - j - * d[i=0..N-1] = sum_{j=0}^{floor((M - 1) / p)} { - * { f[P_f(i,j)] s[P_s(i,j)], P_f(i,j) < M - * { 0, P_f(i,j) >= M. } - */ - -// Calculate the resampling metrics and build the Kaiser-windowed sinc filter -// that's used to cut frequencies above the destination nyquist. -void ResamplerSetup(ResamplerT *rs, const uint srcRate, const uint dstRate) -{ - double cutoff, width, beta; - uint gcd, l; - int i; - - gcd = Gcd(srcRate, dstRate); - rs->mP = dstRate / gcd; - rs->mQ = srcRate / gcd; - /* The cutoff is adjusted by half the transition width, so the transition - * ends before the nyquist (0.5). Both are scaled by the downsampling - * factor. - */ - if(rs->mP > rs->mQ) - { - cutoff = 0.475 / rs->mP; - width = 0.05 / rs->mP; - } - else - { - cutoff = 0.475 / rs->mQ; - width = 0.05 / rs->mQ; - } - // A rejection of -180 dB is used for the stop band. Round up when - // calculating the left offset to avoid increasing the transition width. - l = (CalcKaiserOrder(180.0, width)+1) / 2; - beta = CalcKaiserBeta(180.0); - rs->mM = l*2 + 1; - rs->mL = l; - rs->mF.resize(rs->mM); - for(i = 0;i < (static_cast(rs->mM));i++) - rs->mF[i] = SincFilter(static_cast(l), beta, rs->mP, cutoff, i); -} - -// Perform the upsample-filter-downsample resampling operation using a -// polyphase filter implementation. -void ResamplerRun(ResamplerT *rs, const uint inN, const double *in, const uint outN, double *out) -{ - const uint p = rs->mP, q = rs->mQ, m = rs->mM, l = rs->mL; - std::vector workspace; - const double *f = rs->mF.data(); - uint j_f, j_s; - double *work; - uint i; - - if(outN == 0) - return; - - // Handle in-place operation. - if(in == out) - { - workspace.resize(outN); - work = workspace.data(); - } - else - work = out; - // Resample the input. - for(i = 0;i < outN;i++) - { - double r = 0.0; - // Input starts at l to compensate for the filter delay. This will - // drop any build-up from the first half of the filter. - j_f = (l + (q * i)) % p; - j_s = (l + (q * i)) / p; - while(j_f < m) - { - // Only take input when 0 <= j_s < inN. This single unsigned - // comparison catches both cases. - if(j_s < inN) - r += f[j_f] * in[j_s]; - j_f += p; - j_s--; - } - work[i] = r; - } - // Clean up after in-place operation. - if(work != out) - { - for(i = 0;i < outN;i++) - out[i] = work[i]; + out[i] = a * mags[i]; } } @@ -673,11 +421,11 @@ static int WriteBin4(const uint bytes, const uint32_t in, FILE *fp, const char * // Store the OpenAL Soft HRTF data set. static int StoreMhr(const HrirDataT *hData, const char *filename) { - uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1; - uint n = hData->mIrPoints; - FILE *fp; + const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; + const uint n{hData->mIrPoints}; + uint dither_seed{22222}; uint fi, ei, ai, i; - uint dither_seed = 22222; + FILE *fp; if((fp=fopen(filename, "wb")) == nullptr) { @@ -688,15 +436,13 @@ static int StoreMhr(const HrirDataT *hData, const char *filename) return 0; if(!WriteBin4(4, hData->mIrRate, fp, filename)) return 0; - if(!WriteBin4(1, static_cast(hData->mSampleType), fp, filename)) - return 0; if(!WriteBin4(1, static_cast(hData->mChannelType), fp, filename)) return 0; if(!WriteBin4(1, hData->mIrPoints, fp, filename)) return 0; if(!WriteBin4(1, hData->mFdCount, fp, filename)) return 0; - for(fi = 0;fi < hData->mFdCount;fi++) + for(fi = hData->mFdCount-1;fi < hData->mFdCount;fi--) { auto fdist = static_cast(std::round(1000.0 * hData->mFds[fi].mDistance)); if(!WriteBin4(2, fdist, fp, filename)) @@ -710,12 +456,10 @@ static int StoreMhr(const HrirDataT *hData, const char *filename) } } - for(fi = 0;fi < hData->mFdCount;fi++) + for(fi = hData->mFdCount-1;fi < hData->mFdCount;fi--) { - const double scale = (hData->mSampleType == ST_S16) ? 32767.0 : - ((hData->mSampleType == ST_S24) ? 8388607.0 : 0.0); - const int bps = (hData->mSampleType == ST_S16) ? 2 : - ((hData->mSampleType == ST_S24) ? 3 : 0); + constexpr double scale{8388607.0}; + constexpr uint bps{3u}; for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) { @@ -729,30 +473,29 @@ static int StoreMhr(const HrirDataT *hData, const char *filename) TpdfDither(out+1, azd->mIrs[1], scale, n, channels, &dither_seed); for(i = 0;i < (channels * n);i++) { - int v = static_cast(Clamp(out[i], -scale-1.0, scale)); + const auto v = static_cast(Clamp(out[i], -scale-1.0, scale)); if(!WriteBin4(bps, static_cast(v), fp, filename)) return 0; } } } } - for(fi = 0;fi < hData->mFdCount;fi++) + for(fi = hData->mFdCount-1;fi < hData->mFdCount;fi--) { + /* Delay storage has 2 bits of extra precision. */ + constexpr double DelayPrecScale{4.0}; for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) { for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) { const HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; - int v = static_cast(std::min(std::round(hData->mIrRate * azd.mDelays[0]), MAX_HRTD)); - if(!WriteBin4(1, static_cast(v), fp, filename)) - return 0; + auto v = static_cast(std::round(azd.mDelays[0]*DelayPrecScale)); + if(!WriteBin4(1, v, fp, filename)) return 0; if(hData->mChannelType == CT_STEREO) { - v = static_cast(std::min(std::round(hData->mIrRate * azd.mDelays[1]), MAX_HRTD)); - - if(!WriteBin4(1, static_cast(v), fp, filename)) - return 0; + v = static_cast(std::round(azd.mDelays[1]*DelayPrecScale)); + if(!WriteBin4(1, v, fp, filename)) return 0; } } } @@ -957,129 +700,88 @@ static void DiffuseFieldEqualize(const uint channels, const uint m, const double } } -/* Perform minimum-phase reconstruction using the magnitude responses of the - * HRIR set. Work is delegated to this struct, which runs asynchronously on one - * or more threads (sharing the same reconstructor object). - */ -struct HrirReconstructor { - std::vector mIrs; - std::atomic mCurrent; - std::atomic mDone; - size_t mFftSize; - size_t mIrPoints; - - void Worker() - { - auto h = std::vector(mFftSize); +// Resamples the HRIRs for use at the given sampling rate. +static void ResampleHrirs(const uint rate, HrirDataT *hData) +{ + struct Resampler { + const double scale; + const size_t m; - while(1) + /* Resampling from a lower rate to a higher rate. This likely only + * works properly when 1 <= scale <= 2. + */ + void upsample(double *resampled, const double *ir) const { - /* Load the current index to process. */ - size_t idx{mCurrent.load()}; - do { - /* If the index is at the end, we're done. */ - if(idx >= mIrs.size()) - return; - /* Otherwise, increment the current index atomically so other - * threads know to go to the next one. If this call fails, the - * current index was just changed by another thread and the new - * value is loaded into idx, which we'll recheck. - */ - } while(!mCurrent.compare_exchange_weak(idx, idx+1, std::memory_order_relaxed)); - - /* Now do the reconstruction, and apply the inverse FFT to get the - * time-domain response. - */ - MinimumPhase(mFftSize, mIrs[idx], h.data()); - FftInverse(mFftSize, h.data()); - for(size_t i{0u};i < mIrPoints;++i) - mIrs[idx][i] = h[i].real(); + std::fill_n(resampled, m, 0.0); + resampled[0] = ir[0]; + for(size_t in{1};in < m;++in) + { + const auto offset = static_cast(in) / scale; + const auto out = static_cast(offset); - /* Increment the number of IRs done. */ - mDone.fetch_add(1); + const double a{offset - static_cast(out)}; + if(out == m-1) + resampled[out] += ir[in]*(1.0-a); + else + { + resampled[out ] += ir[in]*(1.0-a); + resampled[out+1] += ir[in]*a; + } + } } - } -}; - -static void ReconstructHrirs(const HrirDataT *hData) -{ - const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; - - /* Count the number of IRs to process (excluding elevations that will be - * synthesized later). - */ - size_t total{hData->mIrCount}; - for(uint fi{0u};fi < hData->mFdCount;fi++) - { - for(uint ei{0u};ei < hData->mFds[fi].mEvStart;ei++) - total -= hData->mFds[fi].mEvs[ei].mAzCount; - } - total *= channels; - /* Set up the reconstructor with the needed size info and pointers to the - * IRs to process. - */ - HrirReconstructor reconstructor; - reconstructor.mIrs.reserve(total); - reconstructor.mCurrent.store(0, std::memory_order_relaxed); - reconstructor.mDone.store(0, std::memory_order_relaxed); - reconstructor.mFftSize = hData->mFftSize; - reconstructor.mIrPoints = hData->mIrPoints; - for(uint fi{0u};fi < hData->mFdCount;fi++) - { - const HrirFdT &field = hData->mFds[fi]; - for(uint ei{field.mEvStart};ei < field.mEvCount;ei++) + /* Resampling from a higher rate to a lower rate. This likely only + * works properly when 0.5 <= scale <= 1.0. + */ + void downsample(double *resampled, const double *ir) const { - const HrirEvT &elev = field.mEvs[ei]; - for(uint ai{0u};ai < elev.mAzCount;ai++) + resampled[0] = ir[0]; + for(size_t out{1};out < m;++out) { - const HrirAzT &azd = elev.mAzs[ai]; - for(uint ti{0u};ti < channels;ti++) - reconstructor.mIrs.push_back(azd.mIrs[ti]); + const auto offset = static_cast(out) * scale; + const auto in = static_cast(offset); + + const double a{offset - static_cast(in)}; + if(in == m-1) + resampled[out] = ir[in]*(1.0-a); + else + resampled[out] = ir[in]*(1.0-a) + ir[in+1]*a; } } - } - - /* Launch two threads to work on reconstruction. */ - std::thread thrd1{std::mem_fn(&HrirReconstructor::Worker), &reconstructor}; - std::thread thrd2{std::mem_fn(&HrirReconstructor::Worker), &reconstructor}; - - /* Keep track of the number of IRs done, periodically reporting it. */ - size_t count; - while((count=reconstructor.mDone.load()) != total) - { - size_t pcdone{count * 100 / total}; - - printf("\r%3zu%% done (%zu of %zu)", pcdone, count, total); - fflush(stdout); + }; - std::this_thread::sleep_for(std::chrono::milliseconds{50}); - } - size_t pcdone{count * 100 / total}; - printf("\r%3zu%% done (%zu of %zu)\n", pcdone, count, total); + while(rate > hData->mIrRate*2) + ResampleHrirs(hData->mIrRate*2, hData); + while(rate < (hData->mIrRate+1)/2) + ResampleHrirs((hData->mIrRate+1)/2, hData); - if(thrd2.joinable()) thrd2.join(); - if(thrd1.joinable()) thrd1.join(); -} + const auto scale = static_cast(rate) / hData->mIrRate; + const size_t m{hData->mFftSize/2u + 1u}; + auto resampled = std::vector(m); -// Resamples the HRIRs for use at the given sampling rate. -static void ResampleHrirs(const uint rate, HrirDataT *hData) -{ - uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1; - uint n = hData->mIrPoints; - uint ti, fi, ei, ai; - ResamplerT rs; + const Resampler resampler{scale, m}; + auto do_resample = std::bind( + std::mem_fn((scale > 1.0) ? &Resampler::upsample : &Resampler::downsample), &resampler, + _1, _2); - ResamplerSetup(&rs, hData->mIrRate, rate); - for(fi = 0;fi < hData->mFdCount;fi++) + const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; + for(uint fi{0};fi < hData->mFdCount;++fi) { - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++) + for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;++ei) { - for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + for(uint ai{0};ai < hData->mFds[fi].mEvs[ei].mAzCount;++ai) { HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - for(ti = 0;ti < channels;ti++) - ResamplerRun(&rs, n, azd->mIrs[ti], n, azd->mIrs[ti]); + for(uint ti{0};ti < channels;++ti) + { + do_resample(resampled.data(), azd->mIrs[ti]); + /* This should probably be rescaled according to the scale, + * however it'll all be normalized in the end so a constant + * scalar is fine to leave. + */ + std::transform(resampled.cbegin(), resampled.cend(), azd->mIrs[ti], + [](const double d) { return std::max(d, EPSILON); }); + } } } } @@ -1226,98 +928,220 @@ static void SynthesizeOnsets(HrirDataT *hData) /* Attempt to synthesize any missing HRIRs at the bottom elevations of each * field. Right now this just blends the lowest elevation HRIRs together and - * applies some attenuation and high frequency damping. It is a simple, if + * applies a low-pass filter to simulate body occlusion. It is a simple, if * inaccurate model. */ static void SynthesizeHrirs(HrirDataT *hData) { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; - const uint irSize{hData->mIrPoints}; + auto htemp = std::vector(hData->mFftSize); + const uint m{hData->mFftSize/2u + 1u}; + auto filter = std::vector(m); const double beta{3.5e-6 * hData->mIrRate}; - auto proc_field = [channels,irSize,beta](HrirFdT &field) -> void + auto proc_field = [channels,m,beta,&htemp,&filter](HrirFdT &field) -> void { const uint oi{field.mEvStart}; if(oi <= 0) return; for(uint ti{0u};ti < channels;ti++) { - for(uint i{0u};i < irSize;i++) - field.mEvs[0].mAzs[0].mIrs[ti][i] = 0.0; - /* Blend the lowest defined elevation's responses for an average - * -90 degree elevation response. + uint a0, a1; + double af; + + /* Use the lowest immediate-left response for the left ear and + * lowest immediate-right response for the right ear. Given no comb + * effects as a result of the left response reaching the right ear + * and vice-versa, this produces a decent phantom-center response + * underneath the head. */ - double blend_count{0.0}; - for(uint ai{0u};ai < field.mEvs[oi].mAzCount;ai++) + CalcAzIndices(field, oi, ((ti==0) ? -M_PI : M_PI) / 2.0, &a0, &a1, &af); + for(uint i{0u};i < m;i++) { - /* Only include the left responses for the left ear, and the - * right responses for the right ear. This removes the cross- - * talk that shouldn't exist for the -90 degree elevation - * response (and would be mistimed anyway). NOTE: Azimuth goes - * from 0...2pi rather than -pi...+pi (0 in front, clockwise). - */ - if(std::abs(field.mEvs[oi].mAzs[ai].mAzimuth) < EPSILON || - (ti == LeftChannel && field.mEvs[oi].mAzs[ai].mAzimuth > M_PI-EPSILON) || - (ti == RightChannel && field.mEvs[oi].mAzs[ai].mAzimuth < M_PI+EPSILON)) - { - for(uint i{0u};i < irSize;i++) - field.mEvs[0].mAzs[0].mIrs[ti][i] += field.mEvs[oi].mAzs[ai].mIrs[ti][i]; - blend_count += 1.0; - } + field.mEvs[0].mAzs[0].mIrs[ti][i] = Lerp(field.mEvs[oi].mAzs[a0].mIrs[ti][i], + field.mEvs[oi].mAzs[a1].mIrs[ti][i], af); } - if(blend_count > 0.0) + } + + for(uint ei{1u};ei < field.mEvStart;ei++) + { + const double of{static_cast(ei) / field.mEvStart}; + const double b{(1.0 - of) * beta}; + double lp[4]{}; + + /* Calculate a low-pass filter to simulate body occlusion. */ + lp[0] = Lerp(1.0, lp[0], b); + lp[1] = Lerp(lp[0], lp[1], b); + lp[2] = Lerp(lp[1], lp[2], b); + lp[3] = Lerp(lp[2], lp[3], b); + htemp[0] = lp[3]; + for(size_t i{1u};i < htemp.size();i++) { - for(uint i{0u};i < irSize;i++) - field.mEvs[0].mAzs[0].mIrs[ti][i] /= blend_count; + lp[0] = Lerp(0.0, lp[0], b); + lp[1] = Lerp(lp[0], lp[1], b); + lp[2] = Lerp(lp[1], lp[2], b); + lp[3] = Lerp(lp[2], lp[3], b); + htemp[i] = lp[3]; } + /* Get the filter's frequency-domain response and extract the + * frequency magnitudes (phase will be reconstructed later)). + */ + FftForward(static_cast(htemp.size()), htemp.data()); + std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(), + [](const complex_d &c) -> double { return std::abs(c); }); - for(uint ei{1u};ei < field.mEvStart;ei++) + for(uint ai{0u};ai < field.mEvs[ei].mAzCount;ai++) { - const double of{static_cast(ei) / field.mEvStart}; - const double b{(1.0 - of) * beta}; - for(uint ai{0u};ai < field.mEvs[ei].mAzCount;ai++) - { - uint a0, a1; - double af; + uint a0, a1; + double af; - CalcAzIndices(field, oi, field.mEvs[ei].mAzs[ai].mAzimuth, &a0, &a1, &af); - double lp[4]{}; - for(uint i{0u};i < irSize;i++) + CalcAzIndices(field, oi, field.mEvs[ei].mAzs[ai].mAzimuth, &a0, &a1, &af); + for(uint ti{0u};ti < channels;ti++) + { + for(uint i{0u};i < m;i++) { /* Blend the two defined HRIRs closest to this azimuth, * then blend that with the synthesized -90 elevation. */ const double s1{Lerp(field.mEvs[oi].mAzs[a0].mIrs[ti][i], field.mEvs[oi].mAzs[a1].mIrs[ti][i], af)}; - const double s0{Lerp(field.mEvs[0].mAzs[0].mIrs[ti][i], s1, of)}; - /* Apply a low-pass to simulate body occlusion. */ - lp[0] = Lerp(s0, lp[0], b); - lp[1] = Lerp(lp[0], lp[1], b); - lp[2] = Lerp(lp[1], lp[2], b); - lp[3] = Lerp(lp[2], lp[3], b); - field.mEvs[ei].mAzs[ai].mIrs[ti][i] = lp[3]; + const double s{Lerp(field.mEvs[0].mAzs[0].mIrs[ti][i], s1, of)}; + field.mEvs[ei].mAzs[ai].mIrs[ti][i] = s * filter[i]; } } } - const double b{beta}; - double lp[4]{}; - for(uint i{0u};i < irSize;i++) - { - const double s0{field.mEvs[0].mAzs[0].mIrs[ti][i]}; - lp[0] = Lerp(s0, lp[0], b); - lp[1] = Lerp(lp[0], lp[1], b); - lp[2] = Lerp(lp[1], lp[2], b); - lp[3] = Lerp(lp[2], lp[3], b); - field.mEvs[0].mAzs[0].mIrs[ti][i] = lp[3]; - } } - field.mEvStart = 0; + const double b{beta}; + double lp[4]{}; + lp[0] = Lerp(1.0, lp[0], b); + lp[1] = Lerp(lp[0], lp[1], b); + lp[2] = Lerp(lp[1], lp[2], b); + lp[3] = Lerp(lp[2], lp[3], b); + htemp[0] = lp[3]; + for(size_t i{1u};i < htemp.size();i++) + { + lp[0] = Lerp(0.0, lp[0], b); + lp[1] = Lerp(lp[0], lp[1], b); + lp[2] = Lerp(lp[1], lp[2], b); + lp[3] = Lerp(lp[2], lp[3], b); + htemp[i] = lp[3]; + } + FftForward(static_cast(htemp.size()), htemp.data()); + std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(), + [](const complex_d &c) -> double { return std::abs(c); }); + + for(uint ti{0u};ti < channels;ti++) + { + for(uint i{0u};i < m;i++) + field.mEvs[0].mAzs[0].mIrs[ti][i] *= filter[i]; + } }; std::for_each(hData->mFds.begin(), hData->mFds.begin()+hData->mFdCount, proc_field); } // The following routines assume a full set of HRIRs for all elevations. +/* Perform minimum-phase reconstruction using the magnitude responses of the + * HRIR set. Work is delegated to this struct, which runs asynchronously on one + * or more threads (sharing the same reconstructor object). + */ +struct HrirReconstructor { + std::vector mIrs; + std::atomic mCurrent; + std::atomic mDone; + uint mFftSize; + uint mIrPoints; + + void Worker() + { + auto h = std::vector(mFftSize); + auto mags = std::vector(mFftSize); + size_t m{(mFftSize/2) + 1}; + + while(1) + { + /* Load the current index to process. */ + size_t idx{mCurrent.load()}; + do { + /* If the index is at the end, we're done. */ + if(idx >= mIrs.size()) + return; + /* Otherwise, increment the current index atomically so other + * threads know to go to the next one. If this call fails, the + * current index was just changed by another thread and the new + * value is loaded into idx, which we'll recheck. + */ + } while(!mCurrent.compare_exchange_weak(idx, idx+1, std::memory_order_relaxed)); + + /* Now do the reconstruction, and apply the inverse FFT to get the + * time-domain response. + */ + for(size_t i{0};i < m;++i) + mags[i] = std::max(mIrs[idx][i], EPSILON); + MinimumPhase(mFftSize, mags.data(), h.data()); + FftInverse(mFftSize, h.data()); + for(uint i{0u};i < mIrPoints;++i) + mIrs[idx][i] = h[i].real(); + + /* Increment the number of IRs done. */ + mDone.fetch_add(1); + } + } +}; + +static void ReconstructHrirs(const HrirDataT *hData, const uint numThreads) +{ + const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; + + /* Set up the reconstructor with the needed size info and pointers to the + * IRs to process. + */ + HrirReconstructor reconstructor; + reconstructor.mCurrent.store(0, std::memory_order_relaxed); + reconstructor.mDone.store(0, std::memory_order_relaxed); + reconstructor.mFftSize = hData->mFftSize; + reconstructor.mIrPoints = hData->mIrPoints; + for(uint fi{0u};fi < hData->mFdCount;fi++) + { + const HrirFdT &field = hData->mFds[fi]; + for(uint ei{0};ei < field.mEvCount;ei++) + { + const HrirEvT &elev = field.mEvs[ei]; + for(uint ai{0u};ai < elev.mAzCount;ai++) + { + const HrirAzT &azd = elev.mAzs[ai]; + for(uint ti{0u};ti < channels;ti++) + reconstructor.mIrs.push_back(azd.mIrs[ti]); + } + } + } + + /* Launch threads to work on reconstruction. */ + std::vector thrds; + thrds.reserve(numThreads); + for(size_t i{0};i < numThreads;++i) + thrds.emplace_back(std::mem_fn(&HrirReconstructor::Worker), &reconstructor); + + /* Keep track of the number of IRs done, periodically reporting it. */ + size_t count; + do { + std::this_thread::sleep_for(std::chrono::milliseconds{50}); + + count = reconstructor.mDone.load(); + size_t pcdone{count * 100 / reconstructor.mIrs.size()}; + + printf("\r%3zu%% done (%zu of %zu)", pcdone, count, reconstructor.mIrs.size()); + fflush(stdout); + } while(count < reconstructor.mIrs.size()); + fputc('\n', stdout); + + for(auto &thrd : thrds) + { + if(thrd.joinable()) + thrd.join(); + } +} + // Normalize the HRIR set and slightly attenuate the result. static void NormalizeHrirs(HrirDataT *hData) { @@ -1326,32 +1150,32 @@ static void NormalizeHrirs(HrirDataT *hData) /* Find the maximum amplitude and RMS out of all the IRs. */ struct LevelPair { double amp, rms; }; - auto proc0_field = [channels,irSize](const LevelPair levels, const HrirFdT &field) -> LevelPair + auto proc0_field = [channels,irSize](const LevelPair levels0, const HrirFdT &field) -> LevelPair { - auto proc_elev = [channels,irSize](const LevelPair levels, const HrirEvT &elev) -> LevelPair + auto proc_elev = [channels,irSize](const LevelPair levels1, const HrirEvT &elev) -> LevelPair { - auto proc_azi = [channels,irSize](const LevelPair levels, const HrirAzT &azi) -> LevelPair + auto proc_azi = [channels,irSize](const LevelPair levels2, const HrirAzT &azi) -> LevelPair { - auto proc_channel = [irSize](const LevelPair levels, const double *ir) -> LevelPair + auto proc_channel = [irSize](const LevelPair levels3, const double *ir) -> LevelPair { /* Calculate the peak amplitude and RMS of this IR. */ auto current = std::accumulate(ir, ir+irSize, LevelPair{0.0, 0.0}, - [](const LevelPair current, const double impulse) -> LevelPair + [](const LevelPair cur, const double impulse) -> LevelPair { - return LevelPair{std::max(std::abs(impulse), current.amp), - current.rms + impulse*impulse}; + return {std::max(std::abs(impulse), cur.amp), + cur.rms + impulse*impulse}; }); current.rms = std::sqrt(current.rms / irSize); /* Accumulate levels by taking the maximum amplitude and RMS. */ - return LevelPair{std::max(current.amp, levels.amp), - std::max(current.rms, levels.rms)}; + return LevelPair{std::max(current.amp, levels3.amp), + std::max(current.rms, levels3.rms)}; }; - return std::accumulate(azi.mIrs, azi.mIrs+channels, levels, proc_channel); + return std::accumulate(azi.mIrs, azi.mIrs+channels, levels2, proc_channel); }; - return std::accumulate(elev.mAzs, elev.mAzs+elev.mAzCount, levels, proc_azi); + return std::accumulate(elev.mAzs, elev.mAzs+elev.mAzCount, levels1, proc_azi); }; - return std::accumulate(field.mEvs, field.mEvs+field.mEvCount, levels, proc_elev); + return std::accumulate(field.mEvs, field.mEvs+field.mEvCount, levels0, proc_elev); }; const auto maxlev = std::accumulate(hData->mFds.begin(), hData->mFds.begin()+hData->mFdCount, LevelPair{0.0, 0.0}, proc0_field); @@ -1449,6 +1273,7 @@ static void CalculateHrtds(const HeadModelT model, const double radius, HrirData } } + double maxHrtd{0.0}; for(fi = 0;fi < hData->mFdCount;fi++) { double minHrtd{std::numeric_limits::infinity()}; @@ -1465,10 +1290,32 @@ static void CalculateHrtds(const HeadModelT model, const double radius, HrirData for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) { - for(ti = 0;ti < channels;ti++) + for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + { + HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; + + for(ti = 0;ti < channels;ti++) + { + azd->mDelays[ti] = (azd->mDelays[ti]-minHrtd) * hData->mIrRate; + maxHrtd = std::max(maxHrtd, azd->mDelays[ti]); + } + } + } + } + if(maxHrtd > MAX_HRTD) + { + fprintf(stdout, " Scaling for max delay of %f samples to %f\n...\n", maxHrtd, MAX_HRTD); + const double scale{MAX_HRTD / maxHrtd}; + for(fi = 0;fi < hData->mFdCount;fi++) + { + for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) { for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) - hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[ti] -= minHrtd; + { + HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; + for(ti = 0;ti < channels;ti++) + azd->mDelays[ti] *= scale; + } } } } @@ -1533,64 +1380,60 @@ int PrepareHrirData(const uint fdCount, const double (&distances)[MAX_FD_COUNT], * resulting data set as desired. If the input name is NULL it will read * from standard input. */ -static int ProcessDefinition(const char *inName, const uint outRate, const ChannelModeT chanMode, const uint fftSize, const int equalize, const int surface, const double limit, const uint truncSize, const HeadModelT model, const double radius, const char *outName) +static int ProcessDefinition(const char *inName, const uint outRate, const ChannelModeT chanMode, + const bool farfield, const uint numThreads, const uint fftSize, const int equalize, + const int surface, const double limit, const uint truncSize, const HeadModelT model, + const double radius, const char *outName) { - char rateStr[8+1], expName[MAX_PATH_LEN]; - char startbytes[4]{}; - size_t startbytecount{0u}; HrirDataT hData; - FILE *fp; - int ret; + fprintf(stdout, "Using %u thread%s.\n", numThreads, (numThreads==1)?"":"s"); if(!inName) { inName = "stdin"; - fp = stdin; + fprintf(stdout, "Reading HRIR definition from %s...\n", inName); + if(!LoadDefInput(std::cin, nullptr, 0, inName, fftSize, truncSize, chanMode, &hData)) + return 0; } else { - fp = fopen(inName, "r"); - if(fp == nullptr) + std::unique_ptr input{new al::ifstream{inName}}; + if(!input->is_open()) { fprintf(stderr, "Error: Could not open input file '%s'\n", inName); return 0; } - startbytecount = fread(startbytes, 1, sizeof(startbytes), fp); - if(startbytecount != sizeof(startbytes)) + char startbytes[4]{}; + input->read(startbytes, sizeof(startbytes)); + std::streamsize startbytecount{input->gcount()}; + if(startbytecount != sizeof(startbytes) || !input->good()) { - fclose(fp); fprintf(stderr, "Error: Could not read input file '%s'\n", inName); return 0; } - if(startbytes[0] == '\x89' && startbytes[1] == 'H' && startbytes[2] == 'D' && - startbytes[3] == 'F') + if(startbytes[0] == '\x89' && startbytes[1] == 'H' && startbytes[2] == 'D' + && startbytes[3] == 'F') { - fclose(fp); - fp = nullptr; - + input = nullptr; fprintf(stdout, "Reading HRTF data from %s...\n", inName); - if(!LoadSofaFile(inName, fftSize, truncSize, chanMode, &hData)) + if(!LoadSofaFile(inName, numThreads, fftSize, truncSize, chanMode, &hData)) + return 0; + } + else + { + fprintf(stdout, "Reading HRIR definition from %s...\n", inName); + if(!LoadDefInput(*input, startbytes, startbytecount, inName, fftSize, truncSize, chanMode, &hData)) return 0; } - } - if(fp != nullptr) - { - fprintf(stdout, "Reading HRIR definition from %s...\n", inName); - const bool success{LoadDefInput(fp, startbytes, startbytecount, inName, fftSize, truncSize, - chanMode, &hData)}; - if(fp != stdin) - fclose(fp); - if(!success) - return 0; } if(equalize) { - uint c = (hData.mChannelType == CT_STEREO) ? 2 : 1; - uint m = 1 + hData.mFftSize / 2; - std::vector dfa(c * m); + uint c{(hData.mChannelType == CT_STEREO) ? 2u : 1u}; + uint m{hData.mFftSize/2u + 1u}; + auto dfa = std::vector(c * m); if(hData.mFdCount > 1) { @@ -1602,29 +1445,43 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann fprintf(stdout, "Performing diffuse-field equalization...\n"); DiffuseFieldEqualize(c, m, dfa.data(), &hData); } - fprintf(stdout, "Performing minimum phase reconstruction...\n"); - ReconstructHrirs(&hData); + if(hData.mFds.size() > 1) + { + fprintf(stdout, "Sorting %zu fields...\n", 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, + (hData.mFds.size()-1 != 1) ? "s" : ""); + hData.mFds.erase(hData.mFds.cbegin(), hData.mFds.cend()-1); + hData.mFdCount = 1; + } + } if(outRate != 0 && outRate != hData.mIrRate) { fprintf(stdout, "Resampling HRIRs...\n"); ResampleHrirs(outRate, &hData); } - fprintf(stdout, "Truncating minimum-phase HRIRs...\n"); - hData.mIrPoints = truncSize; fprintf(stdout, "Synthesizing missing elevations...\n"); if(model == HM_DATASET) SynthesizeOnsets(&hData); SynthesizeHrirs(&hData); + fprintf(stdout, "Performing minimum phase reconstruction...\n"); + ReconstructHrirs(&hData, numThreads); + fprintf(stdout, "Truncating minimum-phase HRIRs...\n"); + hData.mIrPoints = truncSize; fprintf(stdout, "Normalizing final HRIRs...\n"); NormalizeHrirs(&hData); fprintf(stdout, "Calculating impulse delays...\n"); CalculateHrtds(model, (radius > DEFAULT_CUSTOM_RADIUS) ? radius : hData.mRadius, &hData); - snprintf(rateStr, 8, "%u", hData.mIrRate); - StrSubst(outName, "%r", rateStr, MAX_PATH_LEN, expName); - fprintf(stdout, "Creating MHR data set %s...\n", expName); - ret = StoreMhr(&hData, expName); - return ret; + const auto rateStr = std::to_string(hData.mIrRate); + const auto expName = StrSubst({outName, strlen(outName)}, {"%r", 2}, + {rateStr.data(), rateStr.size()}); + fprintf(stdout, "Creating MHR data set %s...\n", expName.c_str()); + return StoreMhr(&hData, expName.c_str()); } static void PrintHelp(const char *argv0, FILE *ofile) @@ -1635,6 +1492,8 @@ static void PrintHelp(const char *argv0, FILE *ofile) fprintf(ofile, " resample the HRIRs accordingly.\n"); fprintf(ofile, " -m Change the data set to mono, mirroring the left ear for the\n"); fprintf(ofile, " right ear.\n"); + fprintf(ofile, " -a Change the data set to single field, using the farthest field.\n"); + fprintf(ofile, " -j Number of threads used to process HRIRs (default: 2).\n"); fprintf(ofile, " -f Override the FFT window size (default: %u).\n", DEFAULT_FFTSIZE); fprintf(ofile, " -e {on|off} Toggle diffuse-field equalization (default: %s).\n", (DEFAULT_EQUALIZE ? "on" : "off")); fprintf(ofile, " -s {on|off} Toggle surface-weighted diffuse-field average (default: %s).\n", (DEFAULT_SURFACE ? "on" : "off")); @@ -1659,13 +1518,13 @@ int main(int argc, char *argv[]) char *end = nullptr; ChannelModeT chanMode; HeadModelT model; + uint numThreads; uint truncSize; double radius; + bool farfield; double limit; int opt; - GET_UNICODE_ARGS(&argc, &argv); - if(argc < 2) { fprintf(stdout, "HRTF Processing and Composition Utility\n\n"); @@ -1680,16 +1539,18 @@ int main(int argc, char *argv[]) equalize = DEFAULT_EQUALIZE; surface = DEFAULT_SURFACE; limit = DEFAULT_LIMIT; + numThreads = 2; truncSize = DEFAULT_TRUNCSIZE; model = DEFAULT_HEAD_MODEL; radius = DEFAULT_CUSTOM_RADIUS; + farfield = false; - while((opt=getopt(argc, argv, "r:mf:e:s:l:w:d:c:e:i:o:h")) != -1) + while((opt=getopt(argc, argv, "r:maj:f:e:s:l:w:d:c:e:i:o:h")) != -1) { switch(opt) { case 'r': - outRate = strtoul(optarg, &end, 10); + outRate = static_cast(strtoul(optarg, &end, 10)); if(end[0] != '\0' || outRate < MIN_RATE || outRate > MAX_RATE) { fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg, opt, MIN_RATE, MAX_RATE); @@ -1701,8 +1562,23 @@ int main(int argc, char *argv[]) chanMode = CM_ForceMono; break; + case 'a': + farfield = true; + break; + + case 'j': + numThreads = static_cast(strtoul(optarg, &end, 10)); + if(end[0] != '\0' || numThreads > 64) + { + fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg, opt, 0, 64); + exit(EXIT_FAILURE); + } + if(numThreads == 0) + numThreads = std::thread::hardware_concurrency(); + break; + case 'f': - fftSize = strtoul(optarg, &end, 10); + fftSize = static_cast(strtoul(optarg, &end, 10)); if(end[0] != '\0' || (fftSize&(fftSize-1)) || fftSize < MIN_FFTSIZE || fftSize > MAX_FFTSIZE) { fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected a power-of-two between %u to %u.\n", optarg, opt, MIN_FFTSIZE, MAX_FFTSIZE); @@ -1749,10 +1625,10 @@ int main(int argc, char *argv[]) break; case 'w': - truncSize = strtoul(optarg, &end, 10); - if(end[0] != '\0' || truncSize < MIN_TRUNCSIZE || truncSize > MAX_TRUNCSIZE || (truncSize%MOD_TRUNCSIZE)) + truncSize = static_cast(strtoul(optarg, &end, 10)); + if(end[0] != '\0' || truncSize < MIN_TRUNCSIZE || truncSize > MAX_TRUNCSIZE) { - fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected multiple of %u between %u to %u.\n", optarg, opt, MOD_TRUNCSIZE, MIN_TRUNCSIZE, MAX_TRUNCSIZE); + fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg, opt, MIN_TRUNCSIZE, MAX_TRUNCSIZE); exit(EXIT_FAILURE); } break; @@ -1796,8 +1672,8 @@ int main(int argc, char *argv[]) } } - int ret = ProcessDefinition(inName, outRate, chanMode, fftSize, equalize, surface, limit, - truncSize, model, radius, outName); + int ret = ProcessDefinition(inName, outRate, chanMode, farfield, numThreads, fftSize, equalize, + surface, limit, truncSize, model, radius, outName); if(!ret) return -1; fprintf(stdout, "Operation completed.\n"); diff --git a/modules/openal-soft/utils/makemhr/makemhr.h b/modules/openal-soft/utils/makemhr/makemhr.h index 24520bd..89607cc 100644 --- a/modules/openal-soft/utils/makemhr/makemhr.h +++ b/modules/openal-soft/utils/makemhr/makemhr.h @@ -4,20 +4,25 @@ #include #include +#include "polyphase_resampler.h" + // The maximum path length used when processing filenames. #define MAX_PATH_LEN (256) // The limit to the number of 'distances' listed in the data set definition. +// Must be less than 256 #define MAX_FD_COUNT (16) -// The limits to the number of 'azimuths' listed in the data set definition. +// The limits to the number of 'elevations' listed in the data set definition. +// Must be less than 256. #define MIN_EV_COUNT (5) -#define MAX_EV_COUNT (128) +#define MAX_EV_COUNT (181) // The limits for each of the 'azimuths' listed in the data set definition. +// Must be less than 256. #define MIN_AZ_COUNT (1) -#define MAX_AZ_COUNT (128) +#define MAX_AZ_COUNT (255) // The limits for the 'distance' from source to listener for each field in // the definition file. @@ -108,16 +113,6 @@ void FftForward(const uint n, complex_d *inout); void FftInverse(const uint n, complex_d *inout); -// The resampler metrics and FIR filter. -struct ResamplerT { - uint mP, mQ, mM, mL; - std::vector mF; -}; - -void ResamplerSetup(ResamplerT *rs, const uint srcRate, const uint dstRate); -void ResamplerRun(ResamplerT *rs, const uint inN, const double *in, const uint outN, double *out); - - // Performs linear interpolation. inline double Lerp(const double a, const double b, const double f) { return a + f * (b - a); } diff --git a/modules/openal-soft/utils/openal-info.c b/modules/openal-soft/utils/openal-info.c index 12dc631..959324c 100644 --- a/modules/openal-soft/utils/openal-info.c +++ b/modules/openal-soft/utils/openal-info.c @@ -22,90 +22,29 @@ * THE SOFTWARE. */ +#include +#include #include -#include #include +#include #include "AL/alc.h" #include "AL/al.h" #include "AL/alext.h" -#ifndef ALC_ENUMERATE_ALL_EXT -#define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012 -#define ALC_ALL_DEVICES_SPECIFIER 0x1013 -#endif - -#ifndef ALC_EXT_EFX -#define ALC_EFX_MAJOR_VERSION 0x20001 -#define ALC_EFX_MINOR_VERSION 0x20002 -#define ALC_MAX_AUXILIARY_SENDS 0x20003 -#endif - +#include "win_main_utf8.h" -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include - -static WCHAR *FromUTF8(const char *str) -{ - WCHAR *out = NULL; - int len; - - if((len=MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0)) > 0) - { - out = calloc(sizeof(WCHAR), len); - MultiByteToWideChar(CP_UTF8, 0, str, -1, out, len); - } - return out; -} - -/* Override printf, fprintf, and fwrite so we can print UTF-8 strings. */ -static void al_fprintf(FILE *file, const char *fmt, ...) -{ - char str[1024]; - WCHAR *wstr; - va_list ap; - - va_start(ap, fmt); - vsnprintf(str, sizeof(str), fmt, ap); - va_end(ap); - - str[sizeof(str)-1] = 0; - wstr = FromUTF8(str); - if(!wstr) - fprintf(file, " %s", str); - else - fprintf(file, "%ls", wstr); - free(wstr); -} -#define fprintf al_fprintf -#define printf(...) al_fprintf(stdout, __VA_ARGS__) - -static size_t al_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *file) -{ - char str[1024]; - WCHAR *wstr; - size_t len; - - len = size * nmemb; - if(len > sizeof(str)-1) - len = sizeof(str)-1; - memcpy(str, ptr, len); - str[len] = 0; - - wstr = FromUTF8(str); - if(!wstr) - fprintf(file, " %s", str); - else - fprintf(file, "%ls", wstr); - free(wstr); - - return len / size; -} -#define fwrite al_fwrite +/* C doesn't allow casting between function and non-function pointer types, so + * with C99 we need to use a union to reinterpret the pointer type. Pre-C99 + * still needs to use a normal cast and live with the warning (C++ is fine with + * a regular reinterpret_cast). + */ +#if __STDC_VERSION__ >= 199901L +#define FUNCTION_CAST(T, ptr) (union{void *p; T f;}){ptr}.f +#else +#define FUNCTION_CAST(T, ptr) (T)(ptr) #endif - #define MAX_WIDTH 80 static void printList(const char *list, char separator) @@ -124,7 +63,7 @@ static void printList(const char *list, char separator) next = strchr(list, separator); if(next) { - len = next-list; + len = (size_t)(next-list); do { next++; } while(*next == separator); @@ -223,7 +162,8 @@ static void printHRTFInfo(ALCdevice *device) return; } - alcGetStringiSOFT = alcGetProcAddress(device, "alcGetStringiSOFT"); + alcGetStringiSOFT = FUNCTION_CAST(LPALCGETSTRINGISOFT, + alcGetProcAddress(device, "alcGetStringiSOFT")); alcGetIntegerv(device, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtfs); if(!num_hrtfs) @@ -241,6 +181,34 @@ static void printHRTFInfo(ALCdevice *device) checkALCErrors(device); } +static void printModeInfo(ALCdevice *device) +{ + if(alcIsExtensionPresent(device, "ALC_SOFT_output_mode")) + { + const char *modename = "(error)"; + ALCenum mode = 0; + + alcGetIntegerv(device, ALC_OUTPUT_MODE_SOFT, 1, &mode); + checkALCErrors(device); + switch(mode) + { + case ALC_ANY_SOFT: modename = "Unknown / unspecified"; break; + case ALC_MONO_SOFT: modename = "Mono"; break; + case ALC_STEREO_SOFT: modename = "Stereo (unspecified encoding)"; break; + case ALC_STEREO_BASIC_SOFT: modename = "Stereo (basic)"; break; + case ALC_STEREO_UHJ_SOFT: modename = "Stereo (UHJ)"; break; + case ALC_STEREO_HRTF_SOFT: modename = "Stereo (HRTF)"; break; + case ALC_QUAD_SOFT: modename = "Quadraphonic"; break; + case ALC_SURROUND_5_1_SOFT: modename = "5.1 Surround"; break; + case ALC_SURROUND_6_1_SOFT: modename = "6.1 Surround"; break; + case ALC_SURROUND_7_1_SOFT: modename = "7.1 Surround"; break; + } + printf("Output channel mode: %s\n", modename); + } + else + printf("Output mode extension not available\n"); +} + static void printALInfo(void) { printf("OpenAL vendor string: %s\n", alGetString(AL_VENDOR)); @@ -263,7 +231,7 @@ static void printResamplerInfo(void) return; } - alGetStringiSOFT = alGetProcAddress("alGetStringiSOFT"); + alGetStringiSOFT = FUNCTION_CAST(LPALGETSTRINGISOFT, alGetProcAddress("alGetStringiSOFT")); num_resamplers = alGetInteger(AL_NUM_RESAMPLERS_SOFT); def_resampler = alGetInteger(AL_DEFAULT_RESAMPLER_SOFT); @@ -285,26 +253,35 @@ static void printResamplerInfo(void) static void printEFXInfo(ALCdevice *device) { - ALCint major, minor, sends; - static const ALchar filters[][32] = { - "AL_FILTER_LOWPASS", "AL_FILTER_HIGHPASS", "AL_FILTER_BANDPASS", "" + static LPALGENFILTERS palGenFilters; + static LPALDELETEFILTERS palDeleteFilters; + static LPALFILTERI palFilteri; + static LPALGENEFFECTS palGenEffects; + static LPALDELETEEFFECTS palDeleteEffects; + static LPALEFFECTI palEffecti; + + static const ALint filters[] = { + AL_FILTER_LOWPASS, AL_FILTER_HIGHPASS, AL_FILTER_BANDPASS, + AL_FILTER_NULL }; char filterNames[] = "Low-pass,High-pass,Band-pass,"; - static const ALchar effects[][32] = { - "AL_EFFECT_EAXREVERB", "AL_EFFECT_REVERB", "AL_EFFECT_CHORUS", - "AL_EFFECT_DISTORTION", "AL_EFFECT_ECHO", "AL_EFFECT_FLANGER", - "AL_EFFECT_FREQUENCY_SHIFTER", "AL_EFFECT_VOCAL_MORPHER", - "AL_EFFECT_PITCH_SHIFTER", "AL_EFFECT_RING_MODULATOR", - "AL_EFFECT_AUTOWAH", "AL_EFFECT_COMPRESSOR", "AL_EFFECT_EQUALIZER", "" + static const ALint effects[] = { + AL_EFFECT_EAXREVERB, AL_EFFECT_REVERB, AL_EFFECT_CHORUS, + AL_EFFECT_DISTORTION, AL_EFFECT_ECHO, AL_EFFECT_FLANGER, + AL_EFFECT_FREQUENCY_SHIFTER, AL_EFFECT_VOCAL_MORPHER, + AL_EFFECT_PITCH_SHIFTER, AL_EFFECT_RING_MODULATOR, + AL_EFFECT_AUTOWAH, AL_EFFECT_COMPRESSOR, AL_EFFECT_EQUALIZER, + AL_EFFECT_NULL }; - static const ALchar dedeffects[][64] = { - "AL_EFFECT_DEDICATED_DIALOGUE", - "AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT", "" + static const ALint dedeffects[] = { + AL_EFFECT_DEDICATED_DIALOGUE, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, + AL_EFFECT_NULL }; char effectNames[] = "EAX Reverb,Reverb,Chorus,Distortion,Echo,Flanger," - "Frequency Shifter,Vocal Morpher,Pitch Shifter," - "Ring Modulator,Autowah,Compressor,Equalizer," - "Dedicated Dialog,Dedicated LFE,"; + "Frequency Shifter,Vocal Morpher,Pitch Shifter,Ring Modulator,Autowah," + "Compressor,Equalizer,Dedicated Dialog,Dedicated LFE,"; + ALCint major, minor, sends; + ALuint object; char *current; int i; @@ -314,6 +291,13 @@ static void printEFXInfo(ALCdevice *device) return; } + palGenFilters = FUNCTION_CAST(LPALGENFILTERS, alGetProcAddress("alGenFilters")); + palDeleteFilters = FUNCTION_CAST(LPALDELETEFILTERS, alGetProcAddress("alDeleteFilters")); + palFilteri = FUNCTION_CAST(LPALFILTERI, alGetProcAddress("alFilteri")); + palGenEffects = FUNCTION_CAST(LPALGENEFFECTS, alGetProcAddress("alGenEffects")); + palDeleteEffects = FUNCTION_CAST(LPALDELETEEFFECTS, alGetProcAddress("alDeleteEffects")); + palEffecti = FUNCTION_CAST(LPALEFFECTI, alGetProcAddress("alEffecti")); + alcGetIntegerv(device, ALC_EFX_MAJOR_VERSION, 1, &major); alcGetIntegerv(device, ALC_EFX_MINOR_VERSION, 1, &minor); if(checkALCErrors(device) == ALC_NO_ERROR) @@ -322,14 +306,17 @@ static void printEFXInfo(ALCdevice *device) if(checkALCErrors(device) == ALC_NO_ERROR) printf("Max auxiliary sends: %d\n", sends); + palGenFilters(1, &object); + checkALErrors(); + current = filterNames; - for(i = 0;filters[i][0];i++) + for(i = 0;filters[i] != AL_FILTER_NULL;i++) { char *next = strchr(current, ','); - ALenum val; + assert(next != NULL); - val = alGetEnumValue(filters[i]); - if(alGetError() != AL_NO_ERROR || val == 0 || val == -1) + palFilteri(object, AL_FILTER_TYPE, filters[i]); + if(alGetError() != AL_NO_ERROR) memmove(current, next+1, strlen(next)); else current = next+1; @@ -337,27 +324,31 @@ static void printEFXInfo(ALCdevice *device) printf("Supported filters:"); printList(filterNames, ','); + palDeleteFilters(1, &object); + palGenEffects(1, &object); + checkALErrors(); + current = effectNames; - for(i = 0;effects[i][0];i++) + for(i = 0;effects[i] != AL_EFFECT_NULL;i++) { char *next = strchr(current, ','); - ALenum val; + assert(next != NULL); - val = alGetEnumValue(effects[i]); - if(alGetError() != AL_NO_ERROR || val == 0 || val == -1) + palEffecti(object, AL_EFFECT_TYPE, effects[i]); + if(alGetError() != AL_NO_ERROR) memmove(current, next+1, strlen(next)); else current = next+1; } if(alcIsExtensionPresent(device, "ALC_EXT_DEDICATED")) { - for(i = 0;dedeffects[i][0];i++) + for(i = 0;dedeffects[i] != AL_EFFECT_NULL;i++) { char *next = strchr(current, ','); - ALenum val; + assert(next != NULL); - val = alGetEnumValue(dedeffects[i]); - if(alGetError() != AL_NO_ERROR || val == 0 || val == -1) + palEffecti(object, AL_EFFECT_TYPE, dedeffects[i]); + if(alGetError() != AL_NO_ERROR) memmove(current, next+1, strlen(next)); else current = next+1; @@ -365,14 +356,18 @@ static void printEFXInfo(ALCdevice *device) } else { - for(i = 0;dedeffects[i][0];i++) + for(i = 0;dedeffects[i] != AL_EFFECT_NULL;i++) { char *next = strchr(current, ','); + assert(next != NULL); memmove(current, next+1, strlen(next)); } } printf("Supported effects:"); printList(effectNames, ','); + + palDeleteEffects(1, &object); + checkALErrors(); } int main(int argc, char *argv[]) @@ -380,6 +375,11 @@ int main(int argc, char *argv[]) ALCdevice *device; ALCcontext *context; +#ifdef _WIN32 + /* OpenAL Soft gives UTF-8 strings, so set the console to expect that. */ + SetConsoleOutputCP(CP_UTF8); +#endif + if(argc > 1 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0)) { @@ -425,6 +425,7 @@ int main(int argc, char *argv[]) return 1; } + printModeInfo(device); printALInfo(); printResamplerInfo(); printEFXInfo(device); diff --git a/modules/openal-soft/utils/sofa-info.cpp b/modules/openal-soft/utils/sofa-info.cpp index e31ca65..6dffef4 100644 --- a/modules/openal-soft/utils/sofa-info.cpp +++ b/modules/openal-soft/utils/sofa-info.cpp @@ -22,46 +22,18 @@ */ #include -#include -#include +#include #include -#include +#include "sofa-support.h" + +#include "mysofa.h" #include "win_main_utf8.h" using uint = unsigned int; -// Per-field measurement info. -struct HrirFdT { - float mDistance{0.0f}; - uint mEvCount{0u}; - uint mEvStart{0u}; - std::vector mAzCounts; -}; - -static const char *SofaErrorStr(int err) -{ - switch(err) - { - case MYSOFA_OK: - return "OK"; - case MYSOFA_INVALID_FORMAT: - return "Invalid format"; - case MYSOFA_UNSUPPORTED_FORMAT: - return "Unsupported format"; - case MYSOFA_INTERNAL_ERROR: - return "Internal error"; - case MYSOFA_NO_MEMORY: - return "Out of memory"; - case MYSOFA_READ_ERROR: - return "Read error"; - } - - return "Unknown"; -} - static void PrintSofaAttributes(const char *prefix, struct MYSOFA_ATTRIBUTE *attribute) { while(attribute) @@ -74,118 +46,10 @@ static void PrintSofaAttributes(const char *prefix, struct MYSOFA_ATTRIBUTE *att static void PrintSofaArray(const char *prefix, struct MYSOFA_ARRAY *array) { PrintSofaAttributes(prefix, array->attributes); - for(uint i{0u};i < array->elements;i++) fprintf(stdout, "%s[%u]: %.6f\n", prefix, i, array->values[i]); } -/* Produces a sorted array of unique elements from a particular axis of the - * triplets array. The filters are used to focus on particular coordinates - * of other axes as necessary. The epsilons are used to constrain the - * equality of unique elements. - */ -static uint GetUniquelySortedElems(const uint m, const float *triplets, const int axis, - const float *const (&filters)[3], const float (&epsilons)[3], - float *elems) -{ - uint count{0u}; - for(uint i{0u};i < 3*m;i += 3) - { - float elem = triplets[i + axis]; - - uint j; - for(j = 0;j < 3;j++) - { - if(filters[j] && std::fabs(triplets[i + j] - *filters[j]) > epsilons[j]) - break; - } - if(j < 3) - continue; - - for(j = 0;j < count;j++) - { - const float delta{elem - elems[j]}; - - if(delta > epsilons[axis]) - continue; - - if(delta >= -epsilons[axis]) - break; - - for(uint k{count};k > j;k--) - elems[k] = elems[k - 1]; - - elems[j] = elem; - count++; - break; - } - - if(j >= count) - elems[count++] = elem; - } - - return count; -} - -/* Given a list of elements, this will produce the smallest step size that - * can uniformly cover a fair portion of the list. Ideally this will be over - * half, but in degenerate cases this can fall to a minimum of 5 (the lower - * limit on elevations necessary to build a layout). - */ -static float GetUniformStepSize(const float epsilon, const uint m, const float *elems) -{ - std::vector steps(m, 0.0f); - std::vector counts(m, 0u); - float step{0.0f}; - uint count{0u}; - - for(uint stride{1u};stride < m/2;stride++) - { - for(uint i{0u};i < m-stride;i++) - { - const float step{elems[i + stride] - elems[i]}; - - uint j; - for(j = 0;j < count;j++) - { - if(std::fabs(step - steps[j]) < epsilon) - { - counts[j]++; - break; - } - } - - if(j >= count) - { - steps[j] = step; - counts[j] = 1; - count++; - } - } - - for(uint i{1u};i < count;i++) - { - if(counts[i] > counts[0]) - { - steps[0] = steps[i]; - counts[0] = counts[i]; - } - } - - count = 1; - - if(counts[0] > m/2) - { - step = steps[0]; - return step; - } - } - - if(counts[0] > 5) - step = steps[0]; - return step; -} - /* Attempts to produce a compatible layout. Most data sets tend to be * uniform and have the same major axis as used by OpenAL Soft's HRTF model. * This will remove outliers and produce a maximally dense layout when @@ -194,182 +58,74 @@ static float GetUniformStepSize(const float epsilon, const uint m, const float * */ static void PrintCompatibleLayout(const uint m, const float *xyzs) { - std::vector aers(3*m, 0.0f); - std::vector elems(m, 0.0f); - - fprintf(stdout, "\n"); - - for(uint i{0u};i < 3*m;i += 3) - { - aers[i] = xyzs[i]; - aers[i + 1] = xyzs[i + 1]; - aers[i + 2] = xyzs[i + 2]; - mysofa_c2s(&aers[i]); - } + fputc('\n', stdout); - uint fdCount{GetUniquelySortedElems(m, aers.data(), 2, - (const float*[3]){ nullptr, nullptr, nullptr }, (const float[3]){ 0.1f, 0.1f, 0.001f }, - elems.data())}; - if(fdCount > (m / 3)) + auto fds = GetCompatibleLayout(m, xyzs); + if(fds.empty()) { - fprintf(stdout, "Incompatible layout (inumerable radii).\n"); + fprintf(stdout, "No compatible field layouts in SOFA file.\n"); return; } - std::vector fds(fdCount); - for(uint fi{0u};fi < fdCount;fi++) - fds[fi].mDistance = elems[fi]; - - for(uint fi{0u};fi < fdCount;fi++) + uint used_elems{0}; + for(size_t fi{0u};fi < fds.size();++fi) { - float dist{fds[fi].mDistance}; - uint evCount{GetUniquelySortedElems(m, aers.data(), 1, - (const float*[3]){ nullptr, nullptr, &dist }, (const float[3]){ 0.1f, 0.1f, 0.001f }, - elems.data())}; - - if(evCount > (m / 3)) - { - fprintf(stdout, "Incompatible layout (innumerable elevations).\n"); - return; - } - - float step{GetUniformStepSize(0.1f, evCount, elems.data())}; - if(step <= 0.0f) - { - fprintf(stdout, "Incompatible layout (non-uniform elevations).\n"); - return; - } - - uint evStart{0u}; - for(uint ei{0u};ei < evCount;ei++) - { - float ev{90.0f + elems[ei]}; - float eif{std::round(ev / step)}; - - if(std::fabs(eif - (uint)eif) < (0.1f / step)) - { - evStart = static_cast(eif); - break; - } - } - - evCount = static_cast(std::round(180.0f / step)) + 1; - if(evCount < 5) - { - fprintf(stdout, "Incompatible layout (too few uniform elevations).\n"); - return; - } - - fds[fi].mEvCount = evCount; - fds[fi].mEvStart = evStart; - fds[fi].mAzCounts.resize(evCount); - auto &azCounts = fds[fi].mAzCounts; - - for(uint ei{evStart};ei < evCount;ei++) - { - float ev{-90.0f + ei * 180.0f / (evCount - 1)}; - uint azCount{GetUniquelySortedElems(m, aers.data(), 0, - (const float*[3]){ nullptr, &ev, &dist }, (const float[3]){ 0.1f, 0.1f, 0.001f }, - elems.data())}; - - if(azCount > (m / 3)) - { - fprintf(stdout, "Incompatible layout (innumerable azimuths).\n"); - return; - } - - if(ei > 0 && ei < (evCount - 1)) - { - step = GetUniformStepSize(0.1f, azCount, elems.data()); - if(step <= 0.0f) - { - fprintf(stdout, "Incompatible layout (non-uniform azimuths).\n"); - return; - } - - azCounts[ei] = static_cast(std::round(360.0f / step)); - } - else if(azCount != 1) - { - fprintf(stdout, "Incompatible layout (non-singular poles).\n"); - return; - } - else - { - azCounts[ei] = 1; - } - } - - for(uint ei{0u};ei < evStart;ei++) - azCounts[ei] = azCounts[evCount - ei - 1]; + for(uint ei{fds[fi].mEvStart};ei < fds[fi].mEvCount;++ei) + used_elems += fds[fi].mAzCounts[ei]; } - fprintf(stdout, "Compatible Layout:\n\ndistance = %.3f", fds[0].mDistance); - - for(uint fi{1u};fi < fdCount;fi++) + fprintf(stdout, "Compatible Layout (%u of %u measurements):\n\ndistance = %.3f", used_elems, m, + fds[0].mDistance); + for(size_t fi{1u};fi < fds.size();fi++) fprintf(stdout, ", %.3f", fds[fi].mDistance); fprintf(stdout, "\nazimuths = "); - for(uint fi{0u};fi < fdCount;fi++) + for(size_t fi{0u};fi < fds.size();++fi) { - for(uint ei{0u};ei < fds[fi].mEvCount;ei++) + for(uint ei{0u};ei < fds[fi].mEvStart;++ei) + fprintf(stdout, "%d%s", fds[fi].mAzCounts[fds[fi].mEvCount - 1 - ei], ", "); + for(uint ei{fds[fi].mEvStart};ei < fds[fi].mEvCount;++ei) fprintf(stdout, "%d%s", fds[fi].mAzCounts[ei], (ei < (fds[fi].mEvCount - 1)) ? ", " : - (fi < (fdCount - 1)) ? ";\n " : "\n"); + (fi < (fds.size() - 1)) ? ";\n " : "\n"); } } // Load and inspect the given SOFA file. static void SofaInfo(const char *filename) { - struct MYSOFA_EASY sofa; - - sofa.lookup = nullptr; - sofa.neighborhood = nullptr; - int err; - sofa.hrtf = mysofa_load(filename, &err); - - if(!sofa.hrtf) + MySofaHrtfPtr sofa{mysofa_load(filename, &err)}; + if(!sofa) { - mysofa_close(&sofa); - fprintf(stdout, "Error: Could not load source file '%s'.\n", filename); + fprintf(stdout, "Error: Could not load source file '%s' (%s).\n", filename, + SofaErrorStr(err)); return; } - err = mysofa_check(sofa.hrtf); + /* NOTE: Some valid SOFA files are failing this check. */ + err = mysofa_check(sofa.get()); if(err != MYSOFA_OK) -/* NOTE: Some valid SOFA files are failing this check. - { - mysofa_close(&sofa); - fprintf(stdout, "Error: Malformed source file '%s' (%s).\n", filename, SofaErrorStr(err)); + fprintf(stdout, "Warning: Supposedly malformed source file '%s' (%s).\n", filename, + SofaErrorStr(err)); - return; - } -*/ - fprintf(stdout, "Warning: Supposedly malformed source file '%s' (%s).\n", filename, SofaErrorStr(err)); + mysofa_tocartesian(sofa.get()); - mysofa_tocartesian(sofa.hrtf); + PrintSofaAttributes("Info", sofa->attributes); - PrintSofaAttributes("Info", sofa.hrtf->attributes); + fprintf(stdout, "Measurements: %u\n", sofa->M); + fprintf(stdout, "Receivers: %u\n", sofa->R); + fprintf(stdout, "Emitters: %u\n", sofa->E); + fprintf(stdout, "Samples: %u\n", sofa->N); - fprintf(stdout, "Measurements: %u\n", sofa.hrtf->M); - fprintf(stdout, "Receivers: %u\n", sofa.hrtf->R); - fprintf(stdout, "Emitters: %u\n", sofa.hrtf->E); - fprintf(stdout, "Samples: %u\n", sofa.hrtf->N); + PrintSofaArray("SampleRate", &sofa->DataSamplingRate); + PrintSofaArray("DataDelay", &sofa->DataDelay); - PrintSofaArray("SampleRate", &sofa.hrtf->DataSamplingRate); - PrintSofaArray("DataDelay", &sofa.hrtf->DataDelay); - - PrintCompatibleLayout(sofa.hrtf->M, sofa.hrtf->SourcePosition.values); - - mysofa_free(sofa.hrtf); + PrintCompatibleLayout(sofa->M, sofa->SourcePosition.values); } int main(int argc, char *argv[]) { - GET_UNICODE_ARGS(&argc, &argv); - if(argc != 2) { fprintf(stdout, "Usage: %s \n", argv[0]); diff --git a/modules/openal-soft/utils/sofa-support.cpp b/modules/openal-soft/utils/sofa-support.cpp new file mode 100644 index 0000000..e37789d --- /dev/null +++ b/modules/openal-soft/utils/sofa-support.cpp @@ -0,0 +1,292 @@ +/* + * SOFA utility methods for inspecting SOFA file metrics and determining HRTF + * utility compatible layouts. + * + * Copyright (C) 2018-2019 Christopher Fitzgerald + * Copyright (C) 2019 Christopher Robinson + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + */ + +#include "sofa-support.h" + +#include + +#include +#include +#include +#include +#include + +#include "mysofa.h" + + +namespace { + +using uint = unsigned int; +using double3 = std::array; + + +/* Produces a sorted array of unique elements from a particular axis of the + * triplets array. The filters are used to focus on particular coordinates + * of other axes as necessary. The epsilons are used to constrain the + * equality of unique elements. + */ +std::vector GetUniquelySortedElems(const std::vector &aers, const uint axis, + const double *const (&filters)[3], const double (&epsilons)[3]) +{ + std::vector elems; + for(const double3 &aer : aers) + { + const double elem{aer[axis]}; + + uint j; + for(j = 0;j < 3;j++) + { + if(filters[j] && std::abs(aer[j] - *filters[j]) > epsilons[j]) + break; + } + if(j < 3) + continue; + + auto iter = elems.begin(); + for(;iter != elems.end();++iter) + { + const double delta{elem - *iter}; + if(delta > epsilons[axis]) continue; + if(delta >= -epsilons[axis]) break; + + iter = elems.emplace(iter, elem); + break; + } + if(iter == elems.end()) + elems.emplace_back(elem); + } + return elems; +} + +/* Given a list of azimuths, this will produce the smallest step size that can + * uniformly cover the list. Ideally this will be over half, but in degenerate + * cases this can fall to a minimum of 5 (the lower limit). + */ +double GetUniformAzimStep(const double epsilon, const std::vector &elems) +{ + if(elems.size() < 5) return 0.0; + + /* Get the maximum count possible, given the first two elements. It would + * be impossible to have more than this since the first element must be + * included. + */ + uint count{static_cast(std::ceil(360.0 / (elems[1]-elems[0])))}; + count = std::min(count, 255u); + + for(;count >= 5;--count) + { + /* Given the stepping value for this number of elements, check each + * multiple to ensure there's a matching element. + */ + const double step{360.0 / count}; + bool good{true}; + size_t idx{1u}; + for(uint mult{1u};mult < count && good;++mult) + { + const double target{step*mult + elems[0]}; + while(idx < elems.size() && target-elems[idx] > epsilon) + ++idx; + good &= (idx < elems.size()) && !(std::abs(target-elems[idx++]) > epsilon); + } + if(good) + return step; + } + return 0.0; +} + +/* Given a list of elevations, this will produce the smallest step size that + * can uniformly cover the list. Ideally this will be over half, but in + * degenerate cases this can fall to a minimum of 5 (the lower limit). + */ +double GetUniformElevStep(const double epsilon, std::vector &elems) +{ + if(elems.size() < 5) return 0.0; + + /* Reverse the elevations so it increments starting with -90 (flipped from + * +90). This makes it easier to work out a proper stepping value. + */ + std::reverse(elems.begin(), elems.end()); + for(auto &v : elems) v *= -1.0; + + uint count{static_cast(std::ceil(180.0 / (elems[1]-elems[0])))}; + count = std::min(count, 255u); + + double ret{0.0}; + for(;count >= 5;--count) + { + const double step{180.0 / count}; + bool good{true}; + size_t idx{1u}; + /* Elevations don't need to match all multiples if there's not enough + * elements to check. Missing elevations can be synthesized. + */ + for(uint mult{1u};mult <= count && idx < elems.size() && good;++mult) + { + const double target{step*mult + elems[0]}; + while(idx < elems.size() && target-elems[idx] > epsilon) + ++idx; + good &= !(idx < elems.size()) || !(std::abs(target-elems[idx++]) > epsilon); + } + if(good) + { + ret = step; + break; + } + } + /* Re-reverse the elevations to restore the correct order. */ + for(auto &v : elems) v *= -1.0; + std::reverse(elems.begin(), elems.end()); + + return ret; +} + +} // namespace + + +const char *SofaErrorStr(int err) +{ + switch(err) + { + case MYSOFA_OK: return "OK"; + case MYSOFA_INVALID_FORMAT: return "Invalid format"; + case MYSOFA_UNSUPPORTED_FORMAT: return "Unsupported format"; + case MYSOFA_INTERNAL_ERROR: return "Internal error"; + case MYSOFA_NO_MEMORY: return "Out of memory"; + case MYSOFA_READ_ERROR: return "Read error"; + } + return "Unknown"; +} + +std::vector GetCompatibleLayout(const size_t m, const float *xyzs) +{ + auto aers = std::vector(m, double3{}); + for(size_t i{0u};i < m;++i) + { + float vals[3]{xyzs[i*3], xyzs[i*3 + 1], xyzs[i*3 + 2]}; + mysofa_c2s(&vals[0]); + aers[i] = {vals[0], vals[1], vals[2]}; + } + + auto radii = GetUniquelySortedElems(aers, 2, {}, {0.1, 0.1, 0.001}); + std::vector fds; + fds.reserve(radii.size()); + + for(const double dist : radii) + { + auto elevs = GetUniquelySortedElems(aers, 1, {nullptr, nullptr, &dist}, {0.1, 0.1, 0.001}); + + /* Remove elevations that don't have a valid set of azimuths. */ + auto invalid_elev = [&dist,&aers](const double ev) -> bool + { + auto azims = GetUniquelySortedElems(aers, 0, {nullptr, &ev, &dist}, {0.1, 0.1, 0.001}); + + if(std::abs(ev) > 89.999) + return azims.size() != 1; + if(azims.empty() || !(std::abs(azims[0]) < 0.1)) + return true; + return GetUniformAzimStep(0.1, azims) <= 0.0; + }; + elevs.erase(std::remove_if(elevs.begin(), elevs.end(), invalid_elev), elevs.end()); + + double step{GetUniformElevStep(0.1, elevs)}; + if(step <= 0.0) + { + if(elevs.empty()) + fprintf(stdout, "No usable elevations on field distance %f.\n", dist); + else + { + fprintf(stdout, "Non-uniform elevations on field distance %.3f.\nGot: %+.2f", dist, + elevs[0]); + for(size_t ei{1u};ei < elevs.size();++ei) + fprintf(stdout, ", %+.2f", elevs[ei]); + fputc('\n', stdout); + } + continue; + } + + uint evStart{0u}; + for(uint ei{0u};ei < elevs.size();ei++) + { + if(!(elevs[ei] < 0.0)) + { + fprintf(stdout, "Too many missing elevations on field distance %f.\n", dist); + return fds; + } + + double eif{(90.0+elevs[ei]) / step}; + const double ev_start{std::round(eif)}; + + if(std::abs(eif - ev_start) < (0.1/step)) + { + evStart = static_cast(ev_start); + break; + } + } + + const auto evCount = static_cast(std::round(180.0 / step)) + 1; + if(evCount < 5) + { + fprintf(stdout, "Too few uniform elevations on field distance %f.\n", dist); + continue; + } + + SofaField field{}; + field.mDistance = dist; + field.mEvCount = evCount; + field.mEvStart = evStart; + field.mAzCounts.resize(evCount, 0u); + auto &azCounts = field.mAzCounts; + + for(uint ei{evStart};ei < evCount;ei++) + { + double ev{-90.0 + ei*180.0/(evCount - 1)}; + auto azims = GetUniquelySortedElems(aers, 0, {nullptr, &ev, &dist}, {0.1, 0.1, 0.001}); + + if(ei == 0 || ei == (evCount-1)) + { + if(azims.size() != 1) + { + fprintf(stdout, "Non-singular poles on field distance %f.\n", dist); + return fds; + } + azCounts[ei] = 1; + } + else + { + step = GetUniformAzimStep(0.1, azims); + if(step <= 0.0) + { + fprintf(stdout, "Non-uniform azimuths on elevation %f, field distance %f.\n", + ev, dist); + return fds; + } + azCounts[ei] = static_cast(std::round(360.0f / step)); + } + } + + fds.emplace_back(std::move(field)); + } + + return fds; +} diff --git a/modules/openal-soft/utils/sofa-support.h b/modules/openal-soft/utils/sofa-support.h new file mode 100644 index 0000000..1229f49 --- /dev/null +++ b/modules/openal-soft/utils/sofa-support.h @@ -0,0 +1,30 @@ +#ifndef UTILS_SOFA_SUPPORT_H +#define UTILS_SOFA_SUPPORT_H + +#include +#include +#include + +#include "mysofa.h" + + +struct MySofaDeleter { + void operator()(MYSOFA_HRTF *sofa) { mysofa_free(sofa); } +}; +using MySofaHrtfPtr = std::unique_ptr; + +// Per-field measurement info. +struct SofaField { + using uint = unsigned int; + + double mDistance{0.0}; + uint mEvCount{0u}; + uint mEvStart{0u}; + std::vector mAzCounts; +}; + +const char *SofaErrorStr(int err); + +std::vector GetCompatibleLayout(const size_t m, const float *xyzs); + +#endif /* UTILS_SOFA_SUPPORT_H */ diff --git a/modules/openal-soft/utils/uhjdecoder.cpp b/modules/openal-soft/utils/uhjdecoder.cpp new file mode 100644 index 0000000..dc85ba5 --- /dev/null +++ b/modules/openal-soft/utils/uhjdecoder.cpp @@ -0,0 +1,538 @@ +/* + * 2-channel UHJ Decoder + * + * Copyright (c) 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. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "albit.h" +#include "albyte.h" +#include "alcomplex.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alspan.h" +#include "vector.h" +#include "opthelpers.h" +#include "phase_shifter.h" + +#include "sndfile.h" + +#include "win_main_utf8.h" + + +struct FileDeleter { + void operator()(FILE *file) { fclose(file); } +}; +using FilePtr = std::unique_ptr; + +struct SndFileDeleter { + void operator()(SNDFILE *sndfile) { sf_close(sndfile); } +}; +using SndFilePtr = std::unique_ptr; + + +using ubyte = unsigned char; +using ushort = unsigned short; +using uint = unsigned int; +using complex_d = std::complex; + +using byte4 = std::array; + + +constexpr ubyte SUBTYPE_BFORMAT_FLOAT[]{ + 0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, + 0xca, 0x00, 0x00, 0x00 +}; + +void fwrite16le(ushort val, FILE *f) +{ + ubyte data[2]{ static_cast(val&0xff), static_cast((val>>8)&0xff) }; + fwrite(data, 1, 2, f); +} + +void fwrite32le(uint val, FILE *f) +{ + ubyte data[4]{ static_cast(val&0xff), static_cast((val>>8)&0xff), + static_cast((val>>16)&0xff), static_cast((val>>24)&0xff) }; + fwrite(data, 1, 4, f); +} + +template +byte4 f32AsLEBytes(const float &value) = delete; + +template<> +byte4 f32AsLEBytes(const float &value) +{ + byte4 ret{}; + std::memcpy(ret.data(), &value, 4); + return ret; +} +template<> +byte4 f32AsLEBytes(const float &value) +{ + byte4 ret{}; + std::memcpy(ret.data(), &value, 4); + std::swap(ret[0], ret[3]); + std::swap(ret[1], ret[2]); + return ret; +} + + +constexpr uint BufferLineSize{1024}; + +using FloatBufferLine = std::array; +using FloatBufferSpan = al::span; + + +struct UhjDecoder { + constexpr static size_t sFilterDelay{1024}; + + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mT{}; + alignas(16) std::array mQ{}; + + /* History for the FIR filter. */ + alignas(16) std::array mDTHistory{}; + alignas(16) std::array mSHistory{}; + + alignas(16) std::array mTemp{}; + + void decode(const float *RESTRICT InSamples, const size_t InChannels, + const al::span OutSamples, const size_t SamplesToDo); + void decode2(const float *RESTRICT InSamples, const al::span OutSamples, + const size_t SamplesToDo); + + DEF_NEWDEL(UhjDecoder) +}; + +const PhaseShifterT PShift{}; + + +/* Decoding UHJ is done as: + * + * S = Left + Right + * D = Left - Right + * + * W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) + * X = 0.418496*S - j(0.828331*D + 0.767820*T) + * Y = 0.795968*D - 0.676392*T + j(0.186633*S) + * Z = 1.023332*Q + * + * where j is a +90 degree phase shift. 3-channel UHJ excludes Q, while 2- + * channel excludes Q and T. The B-Format signal reconstructed from 2-channel + * UHJ should not be run through a normal B-Format decoder, as it needs + * different shelf filters. + * + * NOTE: Some sources specify + * + * S = (Left + Right)/2 + * D = (Left - Right)/2 + * + * However, this is incorrect. It's halving Left and Right even though they + * were already halved during encoding, causing S and D to be half what they + * initially were at the encoding stage. This division is not present in + * Gerzon's original paper for deriving Sigma (S) or Delta (D) from the L and R + * signals. As proof, taking Y for example: + * + * Y = 0.795968*D - 0.676392*T + j(0.186633*S) + * + * * Plug in the encoding parameters, using ? as a placeholder for whether S + * and D should receive an extra 0.5 factor + * Y = 0.795968*(j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y)*? - + * 0.676392*(j(-0.1432*W + 0.6512*X) - 0.7071068*Y) + + * 0.186633*j(0.9396926*W + 0.1855740*X)*? + * + * * Move common factors in + * Y = (j(-0.3420201*0.795968*?*W + 0.5098604*0.795968*?*X) + 0.6554516*0.795968*?*Y) - + * (j(-0.1432*0.676392*W + 0.6512*0.676392*X) - 0.7071068*0.676392*Y) + + * j(0.9396926*0.186633*?*W + 0.1855740*0.186633*?*X) + * + * * Clean up extraneous groupings + * Y = j(-0.3420201*0.795968*?*W + 0.5098604*0.795968*?*X) + 0.6554516*0.795968*?*Y - + * j(-0.1432*0.676392*W + 0.6512*0.676392*X) + 0.7071068*0.676392*Y + + * j*(0.9396926*0.186633*?*W + 0.1855740*0.186633*?*X) + * + * * Move phase shifts together and combine them + * Y = j(-0.3420201*0.795968*?*W + 0.5098604*0.795968*?*X - -0.1432*0.676392*W - + * 0.6512*0.676392*X + 0.9396926*0.186633*?*W + 0.1855740*0.186633*?*X) + + * 0.6554516*0.795968*?*Y + 0.7071068*0.676392*Y + * + * * Reorder terms + * Y = j(-0.3420201*0.795968*?*W + 0.1432*0.676392*W + 0.9396926*0.186633*?*W + + * 0.5098604*0.795968*?*X + -0.6512*0.676392*X + 0.1855740*0.186633*?*X) + + * 0.7071068*0.676392*Y + 0.6554516*0.795968*?*Y + * + * * Move common factors out + * Y = j((-0.3420201*0.795968*? + 0.1432*0.676392 + 0.9396926*0.186633*?)*W + + * ( 0.5098604*0.795968*? + -0.6512*0.676392 + 0.1855740*0.186633*?)*X) + + * (0.7071068*0.676392 + 0.6554516*0.795968*?)*Y + * + * * Result w/ 0.5 factor: + * -0.3420201*0.795968*0.5 + 0.1432*0.676392 + 0.9396926*0.186633*0.5 = 0.04843*W + * 0.5098604*0.795968*0.5 + -0.6512*0.676392 + 0.1855740*0.186633*0.5 = -0.22023*X + * 0.7071068*0.676392 + 0.6554516*0.795968*0.5 = 0.73914*Y + * -> Y = j(0.04843*W + -0.22023*X) + 0.73914*Y + * + * * Result w/o 0.5 factor: + * -0.3420201*0.795968 + 0.1432*0.676392 + 0.9396926*0.186633 = 0.00000*W + * 0.5098604*0.795968 + -0.6512*0.676392 + 0.1855740*0.186633 = 0.00000*X + * 0.7071068*0.676392 + 0.6554516*0.795968 = 1.00000*Y + * -> Y = j(0.00000*W + 0.00000*X) + 1.00000*Y + * + * Not halving produces a result matching the original input. + */ +void UhjDecoder::decode(const float *RESTRICT InSamples, const size_t InChannels, + const al::span OutSamples, const size_t SamplesToDo) +{ + ASSUME(SamplesToDo > 0); + + float *woutput{OutSamples[0].data()}; + float *xoutput{OutSamples[1].data()}; + float *youtput{OutSamples[2].data()}; + + /* Add a delay to the input channels, to align it with the all-passed + * signal. + */ + + /* S = Left + Right */ + for(size_t i{0};i < SamplesToDo;++i) + mS[sFilterDelay+i] = InSamples[i*InChannels + 0] + InSamples[i*InChannels + 1]; + + /* D = Left - Right */ + for(size_t i{0};i < SamplesToDo;++i) + mD[sFilterDelay+i] = InSamples[i*InChannels + 0] - InSamples[i*InChannels + 1]; + + if(InChannels > 2) + { + /* T */ + for(size_t i{0};i < SamplesToDo;++i) + mT[sFilterDelay+i] = InSamples[i*InChannels + 2]; + } + if(InChannels > 3) + { + /* Q */ + for(size_t i{0};i < SamplesToDo;++i) + mQ[sFilterDelay+i] = InSamples[i*InChannels + 3]; + } + + /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ + auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); + std::transform(mD.cbegin(), mD.cbegin()+SamplesToDo+sFilterDelay, mT.cbegin(), tmpiter, + [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); + std::copy_n(mTemp.cbegin()+SamplesToDo, mDTHistory.size(), mDTHistory.begin()); + PShift.process({xoutput, SamplesToDo}, mTemp.data()); + + for(size_t i{0};i < SamplesToDo;++i) + { + /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ + woutput[i] = 0.981532f*mS[i] + 0.197484f*xoutput[i]; + /* X = 0.418496*S - j(0.828331*D + 0.767820*T) */ + xoutput[i] = 0.418496f*mS[i] - xoutput[i]; + } + + /* Precompute j*S and store in youtput. */ + tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); + std::copy_n(mS.cbegin(), SamplesToDo+sFilterDelay, tmpiter); + std::copy_n(mTemp.cbegin()+SamplesToDo, mSHistory.size(), mSHistory.begin()); + PShift.process({youtput, SamplesToDo}, mTemp.data()); + + for(size_t i{0};i < SamplesToDo;++i) + { + /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ + youtput[i] = 0.795968f*mD[i] - 0.676392f*mT[i] + 0.186633f*youtput[i]; + } + + if(OutSamples.size() > 3) + { + float *zoutput{OutSamples[3].data()}; + /* Z = 1.023332*Q */ + for(size_t i{0};i < SamplesToDo;++i) + zoutput[i] = 1.023332f*mQ[i]; + } + + std::copy(mS.begin()+SamplesToDo, mS.begin()+SamplesToDo+sFilterDelay, mS.begin()); + std::copy(mD.begin()+SamplesToDo, mD.begin()+SamplesToDo+sFilterDelay, mD.begin()); + std::copy(mT.begin()+SamplesToDo, mT.begin()+SamplesToDo+sFilterDelay, mT.begin()); + std::copy(mQ.begin()+SamplesToDo, mQ.begin()+SamplesToDo+sFilterDelay, mQ.begin()); +} + +/* This is an alternative equation for decoding 2-channel UHJ. Not sure what + * the intended benefit is over the above equation as this slightly reduces the + * amount of the original left response and has more of the phase-shifted + * forward response on the left response. + * + * This decoding is done as: + * + * S = Left + Right + * D = Left - Right + * + * W = 0.981530*S + j*0.163585*D + * X = 0.418504*S - j*0.828347*D + * Y = 0.762956*D + j*0.384230*S + * + * where j is a +90 degree phase shift. + * + * NOTE: As above, S and D should not be halved. The only consequence of + * halving here is merely a -6dB reduction in output, but it's still incorrect. + */ +void UhjDecoder::decode2(const float *RESTRICT InSamples, + const al::span OutSamples, const size_t SamplesToDo) +{ + ASSUME(SamplesToDo > 0); + + float *woutput{OutSamples[0].data()}; + float *xoutput{OutSamples[1].data()}; + float *youtput{OutSamples[2].data()}; + + /* S = Left + Right */ + for(size_t i{0};i < SamplesToDo;++i) + mS[sFilterDelay+i] = InSamples[i*2 + 0] + InSamples[i*2 + 1]; + + /* D = Left - Right */ + for(size_t i{0};i < SamplesToDo;++i) + mD[sFilterDelay+i] = InSamples[i*2 + 0] - InSamples[i*2 + 1]; + + /* Precompute j*D and store in xoutput. */ + auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); + std::copy_n(mD.cbegin(), SamplesToDo+sFilterDelay, tmpiter); + std::copy_n(mTemp.cbegin()+SamplesToDo, mDTHistory.size(), mDTHistory.begin()); + PShift.process({xoutput, SamplesToDo}, mTemp.data()); + + for(size_t i{0};i < SamplesToDo;++i) + { + /* W = 0.981530*S + j*0.163585*D */ + woutput[i] = 0.981530f*mS[i] + 0.163585f*xoutput[i]; + /* X = 0.418504*S - j*0.828347*D */ + xoutput[i] = 0.418504f*mS[i] - 0.828347f*xoutput[i]; + } + + /* Precompute j*S and store in youtput. */ + tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); + std::copy_n(mS.cbegin(), SamplesToDo+sFilterDelay, tmpiter); + std::copy_n(mTemp.cbegin()+SamplesToDo, mSHistory.size(), mSHistory.begin()); + PShift.process({youtput, SamplesToDo}, mTemp.data()); + + for(size_t i{0};i < SamplesToDo;++i) + { + /* Y = 0.762956*D + j*0.384230*S */ + youtput[i] = 0.762956f*mD[i] + 0.384230f*youtput[i]; + } + + std::copy(mS.begin()+SamplesToDo, mS.begin()+SamplesToDo+sFilterDelay, mS.begin()); + std::copy(mD.begin()+SamplesToDo, mD.begin()+SamplesToDo+sFilterDelay, mD.begin()); +} + + +int main(int argc, char **argv) +{ + if(argc < 2 || std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0) + { + printf("Usage: %s <[options] filename.wav...>\n\n" + " Options:\n" + " --general Use the general equations for 2-channel UHJ (default).\n" + " --alternative Use the alternative equations for 2-channel UHJ.\n" + "\n" + "Note: When decoding 2-channel UHJ to an .amb file, the result should not use\n" + "the normal B-Format shelf filters! Only 3- and 4-channel UHJ can accurately\n" + "reconstruct the original B-Format signal.", + argv[0]); + return 1; + } + + size_t num_files{0}, num_decoded{0}; + bool use_general{true}; + for(int fidx{1};fidx < argc;++fidx) + { + if(std::strcmp(argv[fidx], "--general") == 0) + { + use_general = true; + continue; + } + if(std::strcmp(argv[fidx], "--alternative") == 0) + { + use_general = false; + continue; + } + ++num_files; + SF_INFO ininfo{}; + SndFilePtr infile{sf_open(argv[fidx], SFM_READ, &ininfo)}; + if(!infile) + { + fprintf(stderr, "Failed to open %s\n", argv[fidx]); + continue; + } + if(sf_command(infile.get(), SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + { + fprintf(stderr, "%s is already B-Format\n", argv[fidx]); + continue; + } + uint outchans{}; + if(ininfo.channels == 2) + outchans = 3; + else if(ininfo.channels == 3 || ininfo.channels == 4) + outchans = static_cast(ininfo.channels); + else + { + fprintf(stderr, "%s is not a 2-, 3-, or 4-channel file\n", argv[fidx]); + continue; + } + printf("Converting %s from %d-channel UHJ%s...\n", argv[fidx], ininfo.channels, + (ininfo.channels == 2) ? use_general ? " (general)" : " (alternative)" : ""); + + std::string outname{argv[fidx]}; + auto lastslash = outname.find_last_of('/'); + if(lastslash != std::string::npos) + outname.erase(0, lastslash+1); + auto lastdot = outname.find_last_of('.'); + if(lastdot != std::string::npos) + outname.resize(lastdot+1); + outname += "amb"; + + FilePtr outfile{fopen(outname.c_str(), "wb")}; + if(!outfile) + { + fprintf(stderr, "Failed to create %s\n", outname.c_str()); + continue; + } + + fputs("RIFF", outfile.get()); + fwrite32le(0xFFFFFFFF, outfile.get()); // 'RIFF' header len; filled in at close + + fputs("WAVE", outfile.get()); + + fputs("fmt ", outfile.get()); + fwrite32le(40, outfile.get()); // 'fmt ' header len; 40 bytes for EXTENSIBLE + + // 16-bit val, format type id (extensible: 0xFFFE) + fwrite16le(0xFFFE, outfile.get()); + // 16-bit val, channel count + fwrite16le(static_cast(outchans), outfile.get()); + // 32-bit val, frequency + fwrite32le(static_cast(ininfo.samplerate), outfile.get()); + // 32-bit val, bytes per second + fwrite32le(static_cast(ininfo.samplerate)*sizeof(float)*outchans, outfile.get()); + // 16-bit val, frame size + fwrite16le(static_cast(sizeof(float)*outchans), outfile.get()); + // 16-bit val, bits per sample + fwrite16le(static_cast(sizeof(float)*8), outfile.get()); + // 16-bit val, extra byte count + fwrite16le(22, outfile.get()); + // 16-bit val, valid bits per sample + fwrite16le(static_cast(sizeof(float)*8), outfile.get()); + // 32-bit val, channel mask + fwrite32le(0, outfile.get()); + // 16 byte GUID, sub-type format + fwrite(SUBTYPE_BFORMAT_FLOAT, 1, 16, outfile.get()); + + fputs("data", outfile.get()); + fwrite32le(0xFFFFFFFF, outfile.get()); // 'data' header len; filled in at close + if(ferror(outfile.get())) + { + fprintf(stderr, "Error writing wave file header: %s (%d)\n", strerror(errno), errno); + continue; + } + + auto DataStart = ftell(outfile.get()); + + auto decoder = std::make_unique(); + auto inmem = std::make_unique(BufferLineSize*static_cast(ininfo.channels)); + auto decmem = al::vector, 16>(outchans); + auto outmem = std::make_unique(BufferLineSize*outchans); + + /* A number of initial samples need to be skipped to cut the lead-in + * from the all-pass filter delay. The same number of samples need to + * be fed through the decoder after reaching the end of the input file + * to ensure none of the original input is lost. + */ + size_t LeadIn{UhjDecoder::sFilterDelay}; + sf_count_t LeadOut{UhjDecoder::sFilterDelay}; + while(LeadOut > 0) + { + sf_count_t sgot{sf_readf_float(infile.get(), inmem.get(), BufferLineSize)}; + sgot = std::max(sgot, 0); + if(sgot < BufferLineSize) + { + const sf_count_t remaining{std::min(BufferLineSize - sgot, LeadOut)}; + std::fill_n(inmem.get() + sgot*ininfo.channels, remaining*ininfo.channels, 0.0f); + sgot += remaining; + LeadOut -= remaining; + } + + auto got = static_cast(sgot); + if(ininfo.channels > 2 || use_general) + decoder->decode(inmem.get(), static_cast(ininfo.channels), decmem, got); + else + decoder->decode2(inmem.get(), decmem, got); + if(LeadIn >= got) + { + LeadIn -= got; + continue; + } + + got -= LeadIn; + for(size_t i{0};i < got;++i) + { + /* Attenuate by -3dB for FuMa output levels. */ + constexpr auto inv_sqrt2 = static_cast(1.0/al::numbers::sqrt2); + for(size_t j{0};j < outchans;++j) + outmem[i*outchans + j] = f32AsLEBytes(decmem[j][LeadIn+i] * inv_sqrt2); + } + LeadIn = 0; + + size_t wrote{fwrite(outmem.get(), sizeof(byte4)*outchans, got, outfile.get())}; + if(wrote < got) + { + fprintf(stderr, "Error writing wave data: %s (%d)\n", strerror(errno), errno); + break; + } + } + + auto DataEnd = ftell(outfile.get()); + if(DataEnd > DataStart) + { + long dataLen{DataEnd - DataStart}; + if(fseek(outfile.get(), 4, SEEK_SET) == 0) + fwrite32le(static_cast(DataEnd-8), outfile.get()); // 'WAVE' header len + if(fseek(outfile.get(), DataStart-4, SEEK_SET) == 0) + fwrite32le(static_cast(dataLen), outfile.get()); // 'data' header len + } + fflush(outfile.get()); + ++num_decoded; + } + if(num_decoded == 0) + fprintf(stderr, "Failed to decode any input files\n"); + else if(num_decoded < num_files) + fprintf(stderr, "Decoded %zu of %zu files\n", num_decoded, num_files); + else + printf("Decoded %zu file%s\n", num_decoded, (num_decoded==1)?"":"s"); + return 0; +} diff --git a/modules/openal-soft/utils/uhjencoder.cpp b/modules/openal-soft/utils/uhjencoder.cpp new file mode 100644 index 0000000..b380ed8 --- /dev/null +++ b/modules/openal-soft/utils/uhjencoder.cpp @@ -0,0 +1,507 @@ +/* + * 2-channel UHJ Encoder + * + * Copyright (c) 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. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "almalloc.h" +#include "alnumbers.h" +#include "alspan.h" +#include "opthelpers.h" +#include "phase_shifter.h" +#include "vector.h" + +#include "sndfile.h" + +#include "win_main_utf8.h" + + +namespace { + +struct SndFileDeleter { + void operator()(SNDFILE *sndfile) { sf_close(sndfile); } +}; +using SndFilePtr = std::unique_ptr; + + +using uint = unsigned int; + +constexpr uint BufferLineSize{1024}; + +using FloatBufferLine = std::array; +using FloatBufferSpan = al::span; + + +struct UhjEncoder { + constexpr static size_t sFilterDelay{1024}; + + /* Delays and processing storage for the unfiltered signal. */ + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mT{}; + alignas(16) std::array mQ{}; + + /* History for the FIR filter. */ + alignas(16) std::array mWXHistory1{}; + alignas(16) std::array mWXHistory2{}; + + alignas(16) std::array mTemp{}; + + void encode(const al::span OutSamples, + const al::span InSamples, const size_t SamplesToDo); + + DEF_NEWDEL(UhjEncoder) +}; + +const PhaseShifterT PShift{}; + + +/* Encoding UHJ from B-Format is done as: + * + * S = 0.9396926*W + 0.1855740*X + * D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y + * + * Left = (S + D)/2.0 + * Right = (S - D)/2.0 + * T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y + * Q = 0.9772*Z + * + * where j is a wide-band +90 degree phase shift. T is excluded from 2-channel + * output, and Q is excluded from 2- and 3-channel output. + */ +void UhjEncoder::encode(const al::span OutSamples, + const al::span InSamples, const size_t SamplesToDo) +{ + const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0].data())}; + const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1].data())}; + const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2].data())}; + const float *RESTRICT zinput{al::assume_aligned<16>(InSamples[3].data())}; + + /* Combine the previously delayed S/D signal with the input. */ + + /* S = 0.9396926*W + 0.1855740*X */ + auto miditer = mS.begin() + sFilterDelay; + std::transform(winput, winput+SamplesToDo, xinput, miditer, + [](const float w, const float x) noexcept -> float + { return 0.9396926f*w + 0.1855740f*x; }); + + /* D = 0.6554516*Y */ + auto sideiter = mD.begin() + sFilterDelay; + std::transform(yinput, yinput+SamplesToDo, sideiter, + [](const float y) noexcept -> float { return 0.6554516f*y; }); + + /* D += j(-0.3420201*W + 0.5098604*X) */ + auto tmpiter = std::copy(mWXHistory1.cbegin(), mWXHistory1.cend(), mTemp.begin()); + std::transform(winput, winput+SamplesToDo, xinput, tmpiter, + [](const float w, const float x) noexcept -> float + { return -0.3420201f*w + 0.5098604f*x; }); + std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory1.size(), mWXHistory1.begin()); + PShift.processAccum({mD.data(), SamplesToDo}, mTemp.data()); + + /* Left = (S + D)/2.0 */ + float *RESTRICT left{al::assume_aligned<16>(OutSamples[0].data())}; + for(size_t i{0};i < SamplesToDo;i++) + left[i] = (mS[i] + mD[i]) * 0.5f; + /* Right = (S - D)/2.0 */ + float *RESTRICT right{al::assume_aligned<16>(OutSamples[1].data())}; + for(size_t i{0};i < SamplesToDo;i++) + right[i] = (mS[i] - mD[i]) * 0.5f; + + if(OutSamples.size() > 2) + { + /* T = -0.7071068*Y */ + sideiter = mT.begin() + sFilterDelay; + std::transform(yinput, yinput+SamplesToDo, sideiter, + [](const float y) noexcept -> float { return -0.7071068f*y; }); + + /* T += j(-0.1432*W + 0.6512*X) */ + tmpiter = std::copy(mWXHistory2.cbegin(), mWXHistory2.cend(), mTemp.begin()); + std::transform(winput, winput+SamplesToDo, xinput, tmpiter, + [](const float w, const float x) noexcept -> float + { return -0.1432f*w + 0.6512f*x; }); + std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory2.size(), mWXHistory2.begin()); + PShift.processAccum({mT.data(), SamplesToDo}, mTemp.data()); + + float *RESTRICT t{al::assume_aligned<16>(OutSamples[2].data())}; + for(size_t i{0};i < SamplesToDo;i++) + t[i] = mT[i]; + } + if(OutSamples.size() > 3) + { + /* Q = 0.9772*Z */ + sideiter = mQ.begin() + sFilterDelay; + std::transform(zinput, zinput+SamplesToDo, sideiter, + [](const float z) noexcept -> float { return 0.9772f*z; }); + + float *RESTRICT q{al::assume_aligned<16>(OutSamples[3].data())}; + for(size_t i{0};i < SamplesToDo;i++) + q[i] = mQ[i]; + } + + /* Copy the future samples to the front for next time. */ + std::copy(mS.cbegin()+SamplesToDo, mS.cbegin()+SamplesToDo+sFilterDelay, mS.begin()); + std::copy(mD.cbegin()+SamplesToDo, mD.cbegin()+SamplesToDo+sFilterDelay, mD.begin()); + std::copy(mT.cbegin()+SamplesToDo, mT.cbegin()+SamplesToDo+sFilterDelay, mT.begin()); + std::copy(mQ.cbegin()+SamplesToDo, mQ.cbegin()+SamplesToDo+sFilterDelay, mQ.begin()); +} + + +struct SpeakerPos { + int mChannelID; + float mAzimuth; + float mElevation; +}; + +/* Azimuth is counter-clockwise. */ +constexpr SpeakerPos StereoMap[2]{ + { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f }, + { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f }, +}, QuadMap[4]{ + { SF_CHANNEL_MAP_LEFT, 45.0f, 0.0f }, + { SF_CHANNEL_MAP_RIGHT, -45.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_LEFT, 135.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_RIGHT, -135.0f, 0.0f }, +}, X51Map[6]{ + { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f }, + { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f }, + { SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_SIDE_LEFT, 110.0f, 0.0f }, + { SF_CHANNEL_MAP_SIDE_RIGHT, -110.0f, 0.0f }, +}, X51RearMap[6]{ + { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f }, + { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f }, + { SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_LEFT, 110.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_RIGHT, -110.0f, 0.0f }, +}, X71Map[8]{ + { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f }, + { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f }, + { SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_LEFT, 150.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_RIGHT, -150.0f, 0.0f }, + { SF_CHANNEL_MAP_SIDE_LEFT, 90.0f, 0.0f }, + { SF_CHANNEL_MAP_SIDE_RIGHT, -90.0f, 0.0f }, +}, X714Map[12]{ + { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f }, + { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f }, + { SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_LEFT, 150.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_RIGHT, -150.0f, 0.0f }, + { SF_CHANNEL_MAP_SIDE_LEFT, 90.0f, 0.0f }, + { SF_CHANNEL_MAP_SIDE_RIGHT, -90.0f, 0.0f }, + { SF_CHANNEL_MAP_TOP_FRONT_LEFT, 45.0f, 35.0f }, + { SF_CHANNEL_MAP_TOP_FRONT_RIGHT, -45.0f, 35.0f }, + { SF_CHANNEL_MAP_TOP_REAR_LEFT, 135.0f, 35.0f }, + { SF_CHANNEL_MAP_TOP_REAR_RIGHT, -135.0f, 35.0f }, +}; + +constexpr auto GenCoeffs(double x /*+front*/, double y /*+left*/, double z /*+up*/) noexcept +{ + /* Coefficients are +3dB of FuMa. */ + return std::array{{ + 1.0f, + static_cast(al::numbers::sqrt2 * x), + static_cast(al::numbers::sqrt2 * y), + static_cast(al::numbers::sqrt2 * z) + }}; +} + +} // namespace + + +int main(int argc, char **argv) +{ + if(argc < 2 || std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0) + { + printf("Usage: %s \n\n", argv[0]); + return 1; + } + + uint uhjchans{2}; + size_t num_files{0}, num_encoded{0}; + for(int fidx{1};fidx < argc;++fidx) + { + if(strcmp(argv[fidx], "-bhj") == 0) + { + uhjchans = 2; + continue; + } + if(strcmp(argv[fidx], "-thj") == 0) + { + uhjchans = 3; + continue; + } + if(strcmp(argv[fidx], "-phj") == 0) + { + uhjchans = 4; + continue; + } + ++num_files; + + std::string outname{argv[fidx]}; + size_t lastslash{outname.find_last_of('/')}; + if(lastslash != std::string::npos) + outname.erase(0, lastslash+1); + size_t extpos{outname.find_last_of('.')}; + if(extpos != std::string::npos) + outname.resize(extpos); + outname += ".uhj.flac"; + + SF_INFO ininfo{}; + SndFilePtr infile{sf_open(argv[fidx], SFM_READ, &ininfo)}; + if(!infile) + { + fprintf(stderr, "Failed to open %s\n", argv[fidx]); + continue; + } + printf("Converting %s to %s...\n", argv[fidx], outname.c_str()); + + /* Work out the channel map, preferably using the actual channel map + * from the file/format, but falling back to assuming WFX order. + * + * TODO: Map indices when the channel order differs from the virtual + * speaker position maps. + */ + al::span spkrs; + auto chanmap = std::vector(static_cast(ininfo.channels), SF_CHANNEL_MAP_INVALID); + if(sf_command(infile.get(), SFC_GET_CHANNEL_MAP_INFO, chanmap.data(), + ininfo.channels*int{sizeof(int)}) == SF_TRUE) + { + static const std::array stereomap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT}}; + static const std::array quadmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, + SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}}; + static const std::array x51map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, + SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE, + SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}}; + static const std::array x51rearmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, + SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE, + SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}}; + static const std::array x71map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, + SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE, + SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT, + SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}}; + static const std::array x714map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, + SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE, + SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT, + SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT, + SF_CHANNEL_MAP_TOP_FRONT_LEFT, SF_CHANNEL_MAP_TOP_FRONT_RIGHT, + SF_CHANNEL_MAP_TOP_REAR_LEFT, SF_CHANNEL_MAP_TOP_REAR_RIGHT}}; + static const std::array ambi2dmap{{SF_CHANNEL_MAP_AMBISONIC_B_W, + SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y}}; + static const std::array ambi3dmap{{SF_CHANNEL_MAP_AMBISONIC_B_W, + SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y, + SF_CHANNEL_MAP_AMBISONIC_B_Z}}; + + auto match_chanmap = [](const al::span a, const al::span b) -> bool + { + return a.size() == b.size() + && std::mismatch(a.begin(), a.end(), b.begin(), b.end()).first == a.end(); + }; + if(match_chanmap(chanmap, stereomap)) + spkrs = StereoMap; + else if(match_chanmap(chanmap, quadmap)) + spkrs = QuadMap; + else if(match_chanmap(chanmap, x51map)) + spkrs = X51Map; + else if(match_chanmap(chanmap, x51rearmap)) + spkrs = X51RearMap; + else if(match_chanmap(chanmap, x71map)) + spkrs = X71Map; + else if(match_chanmap(chanmap, x714map)) + spkrs = X714Map; + else if(match_chanmap(chanmap, ambi2dmap) || match_chanmap(chanmap, ambi3dmap)) + { + /* Do nothing. */ + } + else + { + std::string mapstr; + if(chanmap.size() > 0) + { + mapstr = std::to_string(chanmap[0]); + for(int idx : al::span{chanmap}.subspan<1>()) + { + mapstr += ','; + mapstr += std::to_string(idx); + } + } + fprintf(stderr, " ... %zu channels not supported (map: %s)\n", chanmap.size(), + mapstr.c_str()); + continue; + } + } + else if(ininfo.channels == 2) + { + fprintf(stderr, " ... assuming WFX order stereo\n"); + spkrs = StereoMap; + } + else if(ininfo.channels == 6) + { + fprintf(stderr, " ... assuming WFX order 5.1\n"); + spkrs = X51Map; + } + else if(ininfo.channels == 8) + { + fprintf(stderr, " ... assuming WFX order 7.1\n"); + spkrs = X71Map; + } + else + { + fprintf(stderr, " ... unmapped %d-channel audio not supported\n", ininfo.channels); + continue; + } + + SF_INFO outinfo{}; + outinfo.frames = ininfo.frames; + outinfo.samplerate = ininfo.samplerate; + outinfo.channels = static_cast(uhjchans); + outinfo.format = SF_FORMAT_PCM_24 | SF_FORMAT_FLAC; + SndFilePtr outfile{sf_open(outname.c_str(), SFM_WRITE, &outinfo)}; + if(!outfile) + { + fprintf(stderr, " ... failed to create %s\n", outname.c_str()); + continue; + } + + auto encoder = std::make_unique(); + auto splbuf = al::vector(static_cast(9+ininfo.channels)+uhjchans); + auto ambmem = al::span{&splbuf[0], 4}; + auto encmem = al::span{&splbuf[4], 4}; + auto srcmem = al::span{splbuf[8].data(), BufferLineSize}; + auto outmem = al::span{splbuf[9].data(), BufferLineSize*uhjchans}; + + /* A number of initial samples need to be skipped to cut the lead-in + * from the all-pass filter delay. The same number of samples need to + * be fed through the encoder after reaching the end of the input file + * to ensure none of the original input is lost. + */ + size_t total_wrote{0}; + size_t LeadIn{UhjEncoder::sFilterDelay}; + sf_count_t LeadOut{UhjEncoder::sFilterDelay}; + while(LeadIn > 0 || LeadOut > 0) + { + auto inmem = outmem.data() + outmem.size(); + auto sgot = sf_readf_float(infile.get(), inmem, BufferLineSize); + + sgot = std::max(sgot, 0); + if(sgot < BufferLineSize) + { + const sf_count_t remaining{std::min(BufferLineSize - sgot, LeadOut)}; + std::fill_n(inmem + sgot*ininfo.channels, remaining*ininfo.channels, 0.0f); + sgot += remaining; + LeadOut -= remaining; + } + + for(auto&& buf : ambmem) + buf.fill(0.0f); + + auto got = static_cast(sgot); + if(spkrs.empty()) + { + /* B-Format is already in the correct order. It just needs a + * +3dB boost. + */ + constexpr float scale{al::numbers::sqrt2_v}; + const size_t chans{std::min(static_cast(ininfo.channels), 4u)}; + for(size_t c{0};c < chans;++c) + { + for(size_t i{0};i < got;++i) + ambmem[c][i] = inmem[i*static_cast(ininfo.channels)] * scale; + ++inmem; + } + } + else for(auto&& spkr : spkrs) + { + /* Skip LFE. Or mix directly into W? Or W+X? */ + if(spkr.mChannelID == SF_CHANNEL_MAP_LFE) + { + ++inmem; + continue; + } + + for(size_t i{0};i < got;++i) + srcmem[i] = inmem[i * static_cast(ininfo.channels)]; + ++inmem; + + constexpr auto Deg2Rad = al::numbers::pi / 180.0; + const auto coeffs = GenCoeffs( + std::cos(spkr.mAzimuth*Deg2Rad) * std::cos(spkr.mElevation*Deg2Rad), + std::sin(spkr.mAzimuth*Deg2Rad) * std::cos(spkr.mElevation*Deg2Rad), + std::sin(spkr.mElevation*Deg2Rad)); + for(size_t c{0};c < 4;++c) + { + for(size_t i{0};i < got;++i) + ambmem[c][i] += srcmem[i] * coeffs[c]; + } + } + + encoder->encode(encmem.subspan(0, uhjchans), ambmem, got); + if(LeadIn >= got) + { + LeadIn -= got; + continue; + } + + got -= LeadIn; + for(size_t c{0};c < uhjchans;++c) + { + constexpr float max_val{8388607.0f / 8388608.0f}; + auto clamp = [](float v, float mn, float mx) noexcept + { return std::min(std::max(v, mn), mx); }; + for(size_t i{0};i < got;++i) + outmem[i*uhjchans + c] = clamp(encmem[c][LeadIn+i], -1.0f, max_val); + } + LeadIn = 0; + + sf_count_t wrote{sf_writef_float(outfile.get(), outmem.data(), + static_cast(got))}; + if(wrote < 0) + fprintf(stderr, " ... failed to write samples: %d\n", sf_error(outfile.get())); + else + total_wrote += static_cast(wrote); + } + printf(" ... wrote %zu samples (%" PRId64 ").\n", total_wrote, int64_t{ininfo.frames}); + ++num_encoded; + } + if(num_encoded == 0) + fprintf(stderr, "Failed to encode any input files\n"); + else if(num_encoded < num_files) + fprintf(stderr, "Encoded %zu of %zu files\n", num_encoded, num_files); + else + printf("Encoded %s%zu file%s\n", (num_encoded > 1) ? "all " : "", num_encoded, + (num_encoded == 1) ? "" : "s"); + return 0; +} diff --git a/modules/openal-soft/version.h.in b/modules/openal-soft/version.h.in index 56f738a..9bb439d 100644 --- a/modules/openal-soft/version.h.in +++ b/modules/openal-soft/version.h.in @@ -1,5 +1,6 @@ /* Define to the library version */ #define ALSOFT_VERSION "${LIB_VERSION}" +#define ALSOFT_VERSION_NUM ${LIB_VERSION_NUM} /* Define the branch being built */ #define ALSOFT_GIT_BRANCH "${GIT_BRANCH}"