diff --git a/external/README.md b/external/README.md index f0255f2df1..b20d7b91f9 100644 --- a/external/README.md +++ b/external/README.md @@ -138,7 +138,7 @@ ## OpenAL Soft - Upstream: https://github.com/kcat/openal-soft -- Version: 1.21.1 with cmake modification: exclude target 'ex-common' +- Version: git 1.21.1-4733c9f (7628) - License: LGPL-2.1 ## OpenSSL diff --git a/external/openal/BSD-3Clause b/external/openal/BSD-3Clause index 45cc8e6d36..b1c2dbd768 100644 --- a/external/openal/BSD-3Clause +++ b/external/openal/BSD-3Clause @@ -1,6 +1,7 @@ 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. diff --git a/external/openal/CMakeLists.txt b/external/openal/CMakeLists.txt index b81934cf55..f8a81422f9 100644 --- a/external/openal/CMakeLists.txt +++ b/external/openal/CMakeLists.txt @@ -2,22 +2,24 @@ cmake_minimum_required(VERSION 3.0.2) -# 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) +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() -# 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_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") @@ -370,20 +372,25 @@ set(HAVE_SSE3 0) set(HAVE_SSE4_1 0) set(HAVE_NEON 0) -# Check for SSE+SSE2 support +# Check for SSE 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) +if(HAVE_XMMINTRIN_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) + if(ALSOFT_CPUEXT_SSE) set(HAVE_SSE 1) - set(HAVE_SSE2 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() @@ -622,11 +629,13 @@ set(COMMON_OBJS common/alstring.cpp common/alstring.h common/atomic.h + common/comptr.h common/dynload.cpp common/dynload.h common/intrusive_ptr.h common/math_defs.h common/opthelpers.h + common/phase_shifter.h common/polyphase_resampler.cpp common/polyphase_resampler.h common/pragmadefs.h @@ -643,17 +652,29 @@ set(COMMON_OBJS 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/except.cpp core/except.h core/filters/biquad.h @@ -666,12 +687,47 @@ set(CORE_OBJS 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) +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() +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 @@ -715,20 +771,14 @@ set(OPENAL_OBJS # ALC and related routines set(ALC_OBJS alc/alc.cpp - alc/alcmain.h alc/alu.cpp alc/alu.h alc/alconfig.cpp alc/alconfig.h - alc/alcontext.h - alc/async_event.h - alc/bformatdec.cpp - alc/bformatdec.h - alc/buffer_storage.cpp - alc/buffer_storage.h - alc/compat.h - alc/converter.cpp - alc/converter.h + alc/context.cpp + alc/context.h + alc/device.cpp + alc/device.h alc/effectslot.cpp alc/effectslot.h alc/effects/base.h @@ -746,22 +796,18 @@ set(ALC_OBJS alc/effects/pshifter.cpp alc/effects/reverb.cpp alc/effects/vmorpher.cpp - alc/front_stablizer.h - alc/helpers.cpp - alc/hrtf.cpp - alc/hrtf.h alc/inprogext.h - alc/panning.cpp - alc/uiddefs.cpp - alc/voice.cpp - alc/voice.h - alc/voice_change.h) + alc/panning.cpp) # 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_sse.cpp core/mixer/mixer_sse2.cpp) - set(CPU_EXTS "${CPU_EXTS}, SSE, 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) @@ -1149,10 +1195,13 @@ if(ALSOFT_EMBED_HRTF_DATA) endif() -if(ALSOFT_UTILS AND NOT ALSOFT_NO_CONFIG_UTIL) - find_package(Qt5Widgets) +if(ALSOFT_UTILS) + find_package(MySOFA) + if(NOT ALSOFT_NO_CONFIG_UTIL) + find_package(Qt5Widgets) + endif() endif() -if(ALSOFT_EXAMPLES) +if(ALSOFT_UTILS OR ALSOFT_EXAMPLES) find_package(SndFile) find_package(SDL2) if(SDL2_FOUND) @@ -1233,7 +1282,8 @@ else() router/al.cpp ) target_compile_definitions(OpenAL - PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES ${CPP_DEFS}) + 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 @@ -1309,7 +1359,6 @@ target_include_directories(${IMPL_TARGET} ${INC_PATHS} ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR} - ${OpenAL_SOURCE_DIR}/alc ${OpenAL_SOURCE_DIR}/common ) @@ -1318,7 +1367,8 @@ set_target_properties(${IMPL_TARGET} PROPERTIES OUTPUT_NAME ${LIBNAME} SOVERSION ${LIB_MAJOR_VERSION} ) target_compile_definitions(${IMPL_TARGET} - PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES ${CPP_DEFS}) + PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" + ${CPP_DEFS}) target_compile_options(${IMPL_TARGET} PRIVATE ${C_FLAGS}) if(TARGET build_version) @@ -1430,7 +1480,16 @@ if(ALSOFT_UTILS) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} openal-info) endif() - find_package(MySOFA) + 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}) + endif() + if(MYSOFA_FOUND) set(SOFA_SUPPORT_SRCS utils/sofa-support.cpp @@ -1476,7 +1535,7 @@ if(ALSOFT_UTILS) message(STATUS "") endif() -if(ALSOFT_EXAMPLES) + # Add a static library with common functions used by multiple example targets add_library(ex-common STATIC EXCLUDE_FROM_ALL examples/common/alhelpers.c @@ -1485,7 +1544,6 @@ 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}) -endif() if(ALSOFT_EXAMPLES) add_executable(altonegen examples/altonegen.c) diff --git a/external/openal/al/auxeffectslot.cpp b/external/openal/al/auxeffectslot.cpp index 10f13be719..d1ab29ee72 100644 --- a/external/openal/al/auxeffectslot.cpp +++ b/external/openal/al/auxeffectslot.cpp @@ -36,18 +36,18 @@ #include "AL/efx.h" #include "albit.h" -#include "alcmain.h" -#include "alcontext.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 "alu.h" #include "buffer.h" #include "core/except.h" #include "core/fpu_ctrl.h" #include "core/logging.h" #include "effect.h" -#include "inprogext.h" #include "opthelpers.h" @@ -308,7 +308,7 @@ void FreeEffectSlot(ALCcontext *context, ALeffectslot *slot) && slot->mState == SlotState::Playing) \ slot->updateProps(context.get()); \ else \ - slot->PropsClean.clear(std::memory_order_release); \ + slot->mPropsDirty.set(std::memory_order_release); \ } while(0) } // namespace @@ -325,7 +325,7 @@ START_API_FUNC if UNLIKELY(n <= 0) return; std::unique_lock slotlock{context->mEffectSlotLock}; - ALCdevice *device{context->mDevice.get()}; + 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)", @@ -460,7 +460,7 @@ START_API_FUNC if(slot->mState == SlotState::Playing) return; - slot->PropsClean.test_and_set(std::memory_order_acq_rel); + slot->mPropsDirty.test_and_clear(std::memory_order_acq_rel); slot->updateProps(context.get()); AddActiveEffectSlots({&slot, 1}, context.get()); @@ -491,7 +491,7 @@ START_API_FUNC if(slot->mState != SlotState::Playing) { - slot->PropsClean.test_and_set(std::memory_order_acq_rel); + slot->mPropsDirty.test_and_clear(std::memory_order_acq_rel); slot->updateProps(context.get()); } slots[i] = slot; @@ -571,7 +571,7 @@ START_API_FUNC switch(param) { case AL_EFFECTSLOT_EFFECT: - device = context->mDevice.get(); + device = context->mALDevice.get(); { std::lock_guard ___{device->EffectLock}; @@ -587,8 +587,12 @@ START_API_FUNC } if UNLIKELY(slot->mState == SlotState::Initial) { + slot->mPropsDirty.test_and_clear(std::memory_order_acq_rel); + slot->updateProps(context.get()); + AddActiveEffectSlots({&slot, 1}, context.get()); slot->mState = SlotState::Playing; + return; } break; @@ -596,6 +600,8 @@ START_API_FUNC 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; @@ -603,6 +609,8 @@ START_API_FUNC 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}; @@ -631,12 +639,20 @@ START_API_FUNC break; case AL_BUFFER: - device = context->mDevice.get(); + 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{}; @@ -720,6 +736,8 @@ START_API_FUNC 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; @@ -884,7 +902,7 @@ END_API_FUNC ALeffectslot::ALeffectslot() { - PropsClean.test_and_set(std::memory_order_relaxed); + mPropsDirty.test_and_clear(std::memory_order_relaxed); EffectStateFactory *factory{getFactoryByType(EffectSlotType::None)}; assert(factory != nullptr); @@ -928,7 +946,7 @@ ALenum ALeffectslot::initEffect(ALeffect *effect, ALCcontext *context) } al::intrusive_ptr state{factory->create()}; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::unique_lock statelock{device->StateLock}; state->mOutTarget = device->Dry.Buffer; { @@ -1004,7 +1022,7 @@ void UpdateAllEffectSlotProps(ALCcontext *context) usemask &= ~(1_u64 << idx); if(slot->mState != SlotState::Stopped - && slot->PropsClean.test_and_set(std::memory_order_acq_rel)) + && slot->mPropsDirty.test_and_clear(std::memory_order_acq_rel)) slot->updateProps(context); } } diff --git a/external/openal/al/auxeffectslot.h b/external/openal/al/auxeffectslot.h index c1c9703f7d..274238305b 100644 --- a/external/openal/al/auxeffectslot.h +++ b/external/openal/al/auxeffectslot.h @@ -8,11 +8,11 @@ #include "AL/alc.h" #include "AL/efx.h" -#include "alcmain.h" +#include "alc/device.h" +#include "alc/effectslot.h" +#include "alc/effects/base.h" #include "almalloc.h" #include "atomic.h" -#include "effectslot.h" -#include "effects/base.h" #include "intrusive_ptr.h" #include "vector.h" @@ -40,7 +40,7 @@ struct ALeffectslot { al::intrusive_ptr State; } Effect; - std::atomic_flag PropsClean; + al::atomic_invflag mPropsDirty; SlotState mState{SlotState::Initial}; diff --git a/external/openal/al/buffer.cpp b/external/openal/al/buffer.cpp index 14fae1ee86..26fe863ebc 100644 --- a/external/openal/al/buffer.cpp +++ b/external/openal/al/buffer.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include "AL/al.h" @@ -43,14 +44,16 @@ #include "albit.h" #include "albyte.h" -#include "alcmain.h" -#include "alcontext.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 "inprogext.h" +#include "core/logging.h" +#include "core/voice.h" #include "opthelpers.h" @@ -271,6 +274,9 @@ ALuint ChannelsFromUserFmt(UserFmtChannels chans, ALuint ambiorder) noexcept 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; } @@ -465,6 +471,9 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, case UserFmtX71: DstChannels = FmtX71; break; case UserFmtBFormat2D: DstChannels = FmtBFormat2D; break; case UserFmtBFormat3D: DstChannels = FmtBFormat3D; break; + case UserFmtUHJ2: DstChannels = FmtUHJ2; break; + case UserFmtUHJ3: DstChannels = FmtUHJ3; break; + case UserFmtUHJ4: DstChannels = FmtUHJ4; break; } if UNLIKELY(static_cast(SrcChannels) != static_cast(DstChannels)) SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format"); @@ -501,7 +510,9 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, unpackalign, NameFromUserFmtType(SrcType)); const ALuint ambiorder{(DstChannels == FmtBFormat2D || DstChannels == FmtBFormat3D) ? - ALBuf->UnpackAmbiOrder : 0}; + ALBuf->UnpackAmbiOrder : + ((DstChannels == FmtUHJ2 || DstChannels == FmtUHJ3 || DstChannels == FmtUHJ4) ? 1 : + 0)}; if((access&AL_PRESERVE_DATA_BIT_SOFT)) { @@ -622,6 +633,9 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, case UserFmtX71: DstChannels = FmtX71; break; case UserFmtBFormat2D: DstChannels = FmtBFormat2D; break; case UserFmtBFormat3D: DstChannels = FmtBFormat3D; break; + case UserFmtUHJ2: DstChannels = FmtUHJ2; break; + case UserFmtUHJ3: DstChannels = FmtUHJ3; break; + case UserFmtUHJ4: DstChannels = FmtUHJ4; break; } if UNLIKELY(static_cast(SrcChannels) != static_cast(DstChannels)) SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid format"); @@ -643,10 +657,13 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, SETERR_RETURN(context, AL_INVALID_ENUM,, "Unsupported callback format"); const ALuint ambiorder{(DstChannels == FmtBFormat2D || DstChannels == FmtBFormat3D) ? - ALBuf->UnpackAmbiOrder : 0}; + ALBuf->UnpackAmbiOrder : + ((DstChannels == FmtUHJ2 || DstChannels == FmtUHJ3 || DstChannels == FmtUHJ4) ? 1 : + 0)}; + constexpr uint line_size{BufferLineSize + MaxPostVoiceLoad}; al::vector(FrameSizeFromFmt(DstChannels, DstType, ambiorder) * - size_t{BufferLineSize + (MaxResamplerPadding>>1)}).swap(ALBuf->mData); + size_t{line_size}).swap(ALBuf->mData); ALBuf->mCallback = callback; ALBuf->mUserData = userptr; @@ -675,7 +692,7 @@ al::optional DecomposeUserFormat(ALenum format) UserFmtChannels channels; UserFmtType type; }; - static const std::array UserFmtList{{ + static const std::array UserFmtList{{ { AL_FORMAT_MONO8, UserFmtMono, UserFmtUByte }, { AL_FORMAT_MONO16, UserFmtMono, UserFmtShort }, { AL_FORMAT_MONO_FLOAT32, UserFmtMono, UserFmtFloat }, @@ -731,6 +748,18 @@ al::optional DecomposeUserFormat(ALenum format) { 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) @@ -754,7 +783,7 @@ START_API_FUNC context->setError(AL_INVALID_VALUE, "Generating %d buffers", n); if UNLIKELY(n <= 0) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; if(!EnsureBuffers(device, static_cast(n))) { @@ -794,7 +823,7 @@ START_API_FUNC context->setError(AL_INVALID_VALUE, "Deleting %d buffers", n); if UNLIKELY(n <= 0) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; /* First try to find any buffers that are invalid or in-use. */ @@ -834,7 +863,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if LIKELY(context) { - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; if(!buffer || LookupBuffer(device, buffer)) return AL_TRUE; @@ -855,7 +884,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); @@ -889,7 +918,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return nullptr; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); @@ -942,7 +971,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); @@ -965,7 +994,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); @@ -997,7 +1026,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); @@ -1127,7 +1156,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; if UNLIKELY(LookupBuffer(device, buffer) == nullptr) @@ -1147,7 +1176,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; if UNLIKELY(LookupBuffer(device, buffer) == nullptr) @@ -1166,7 +1195,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; if UNLIKELY(LookupBuffer(device, buffer) == nullptr) @@ -1188,7 +1217,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); @@ -1250,7 +1279,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; if UNLIKELY(LookupBuffer(device, buffer) == nullptr) @@ -1283,7 +1312,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); @@ -1321,7 +1350,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); @@ -1343,7 +1372,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; if UNLIKELY(LookupBuffer(device, buffer) == nullptr) @@ -1371,7 +1400,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; if UNLIKELY(LookupBuffer(device, buffer) == nullptr) @@ -1393,7 +1422,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); if UNLIKELY(!albuf) @@ -1450,7 +1479,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + 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); @@ -1488,7 +1517,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); if UNLIKELY(!albuf) @@ -1516,7 +1545,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); @@ -1546,7 +1575,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->BufferLock}; ALbuffer *albuf = LookupBuffer(device, buffer); if UNLIKELY(!albuf) @@ -1574,7 +1603,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + 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); @@ -1602,7 +1631,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + 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); diff --git a/external/openal/al/buffer.h b/external/openal/al/buffer.h index 9f72fb1ba7..a78c65c636 100644 --- a/external/openal/al/buffer.h +++ b/external/openal/al/buffer.h @@ -6,10 +6,10 @@ #include "AL/al.h" #include "albyte.h" +#include "alc/inprogext.h" #include "almalloc.h" #include "atomic.h" -#include "buffer_storage.h" -#include "inprogext.h" +#include "core/buffer_storage.h" #include "vector.h" @@ -35,6 +35,9 @@ enum UserFmtChannels : unsigned char { UserFmtX71 = FmtX71, UserFmtBFormat2D = FmtBFormat2D, UserFmtBFormat3D = FmtBFormat3D, + UserFmtUHJ2 = FmtUHJ2, + UserFmtUHJ3 = FmtUHJ3, + UserFmtUHJ4 = FmtUHJ4, }; diff --git a/external/openal/al/effect.cpp b/external/openal/al/effect.cpp index 93aa55472f..a15194fdbc 100644 --- a/external/openal/al/effect.cpp +++ b/external/openal/al/effect.cpp @@ -39,14 +39,15 @@ #include "AL/efx.h" #include "albit.h" -#include "alcmain.h" -#include "alcontext.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 "effects/base.h" #include "opthelpers.h" #include "vector.h" @@ -233,7 +234,7 @@ START_API_FUNC context->setError(AL_INVALID_VALUE, "Generating %d effects", n); if UNLIKELY(n <= 0) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; if(!EnsureEffects(device, static_cast(n))) { @@ -273,7 +274,7 @@ START_API_FUNC context->setError(AL_INVALID_VALUE, "Deleting %d effects", n); if UNLIKELY(n <= 0) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; /* First try to find any effects that are invalid. */ @@ -304,7 +305,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if LIKELY(context) { - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; if(!effect || LookupEffect(device, effect)) return AL_TRUE; @@ -319,7 +320,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; @@ -369,7 +370,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; @@ -392,7 +393,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; @@ -415,7 +416,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; @@ -438,7 +439,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; @@ -470,7 +471,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; @@ -493,7 +494,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; @@ -516,7 +517,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; diff --git a/external/openal/al/effects/autowah.cpp b/external/openal/al/effects/autowah.cpp index 65d4b702c3..9abef1f708 100644 --- a/external/openal/al/effects/autowah.cpp +++ b/external/openal/al/effects/autowah.cpp @@ -8,7 +8,7 @@ #include "AL/efx.h" -#include "effects/base.h" +#include "alc/effects/base.h" #include "effects.h" namespace { diff --git a/external/openal/al/effects/chorus.cpp b/external/openal/al/effects/chorus.cpp index b239528317..2466d97e1f 100644 --- a/external/openal/al/effects/chorus.cpp +++ b/external/openal/al/effects/chorus.cpp @@ -1,17 +1,22 @@ #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" -#include "effects/base.h" 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"); diff --git a/external/openal/al/effects/compressor.cpp b/external/openal/al/effects/compressor.cpp index 94e074314c..f5db2a6fd9 100644 --- a/external/openal/al/effects/compressor.cpp +++ b/external/openal/al/effects/compressor.cpp @@ -4,8 +4,8 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" namespace { diff --git a/external/openal/al/effects/convolution.cpp b/external/openal/al/effects/convolution.cpp index 4d87b1c496..8e850fd3e2 100644 --- a/external/openal/al/effects/convolution.cpp +++ b/external/openal/al/effects/convolution.cpp @@ -2,10 +2,10 @@ #include "config.h" #include "AL/al.h" -#include "inprogext.h" +#include "alc/inprogext.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" namespace { diff --git a/external/openal/al/effects/dedicated.cpp b/external/openal/al/effects/dedicated.cpp index 334d9e56f4..db57003c2a 100644 --- a/external/openal/al/effects/dedicated.cpp +++ b/external/openal/al/effects/dedicated.cpp @@ -4,10 +4,10 @@ #include #include "AL/al.h" -#include "AL/efx.h" +#include "AL/alext.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" namespace { diff --git a/external/openal/al/effects/distortion.cpp b/external/openal/al/effects/distortion.cpp index 8961a4d99d..f5d64a9af5 100644 --- a/external/openal/al/effects/distortion.cpp +++ b/external/openal/al/effects/distortion.cpp @@ -4,8 +4,8 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" namespace { diff --git a/external/openal/al/effects/echo.cpp b/external/openal/al/effects/echo.cpp index 79a60521db..65f691c66c 100644 --- a/external/openal/al/effects/echo.cpp +++ b/external/openal/al/effects/echo.cpp @@ -4,8 +4,8 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" namespace { diff --git a/external/openal/al/effects/effects.h b/external/openal/al/effects/effects.h index d6c88c4f1e..30b4bd7508 100644 --- a/external/openal/al/effects/effects.h +++ b/external/openal/al/effects/effects.h @@ -12,7 +12,11 @@ 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; } diff --git a/external/openal/al/effects/equalizer.cpp b/external/openal/al/effects/equalizer.cpp index 3a7c0a8f00..3c03968865 100644 --- a/external/openal/al/effects/equalizer.cpp +++ b/external/openal/al/effects/equalizer.cpp @@ -4,8 +4,8 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" namespace { diff --git a/external/openal/al/effects/fshifter.cpp b/external/openal/al/effects/fshifter.cpp index 444b02607f..4aa860a249 100644 --- a/external/openal/al/effects/fshifter.cpp +++ b/external/openal/al/effects/fshifter.cpp @@ -1,12 +1,14 @@ #include "config.h" +#include + #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "aloptional.h" #include "effects.h" -#include "effects/base.h" namespace { diff --git a/external/openal/al/effects/modulator.cpp b/external/openal/al/effects/modulator.cpp index 89dcc209f1..d8a1bed6be 100644 --- a/external/openal/al/effects/modulator.cpp +++ b/external/openal/al/effects/modulator.cpp @@ -1,12 +1,14 @@ #include "config.h" +#include + #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "aloptional.h" #include "effects.h" -#include "effects/base.h" namespace { diff --git a/external/openal/al/effects/null.cpp b/external/openal/al/effects/null.cpp index 0ac5278fa8..516446db05 100644 --- a/external/openal/al/effects/null.cpp +++ b/external/openal/al/effects/null.cpp @@ -4,8 +4,8 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" namespace { diff --git a/external/openal/al/effects/pshifter.cpp b/external/openal/al/effects/pshifter.cpp index e6b0b3b059..56059a3c8b 100644 --- a/external/openal/al/effects/pshifter.cpp +++ b/external/openal/al/effects/pshifter.cpp @@ -4,8 +4,8 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" namespace { diff --git a/external/openal/al/effects/reverb.cpp b/external/openal/al/effects/reverb.cpp index caa0c81eb6..3f234b9399 100644 --- a/external/openal/al/effects/reverb.cpp +++ b/external/openal/al/effects/reverb.cpp @@ -6,8 +6,8 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" namespace { diff --git a/external/openal/al/effects/vmorpher.cpp b/external/openal/al/effects/vmorpher.cpp index 03eb2c6242..1b4710ffc1 100644 --- a/external/openal/al/effects/vmorpher.cpp +++ b/external/openal/al/effects/vmorpher.cpp @@ -1,12 +1,14 @@ #include "config.h" +#include + #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "aloptional.h" #include "effects.h" -#include "effects/base.h" namespace { diff --git a/external/openal/al/error.cpp b/external/openal/al/error.cpp index 444b55aae0..b5b6f4302a 100644 --- a/external/openal/al/error.cpp +++ b/external/openal/al/error.cpp @@ -35,7 +35,7 @@ #include "AL/al.h" #include "AL/alc.h" -#include "alcontext.h" +#include "alc/context.h" #include "almalloc.h" #include "core/except.h" #include "core/logging.h" diff --git a/external/openal/al/event.cpp b/external/openal/al/event.cpp index a5d7be38c2..fd4667f767 100644 --- a/external/openal/al/event.cpp +++ b/external/openal/al/event.cpp @@ -18,17 +18,17 @@ #include "AL/alc.h" #include "albyte.h" -#include "alcontext.h" +#include "alc/context.h" +#include "alc/effects/base.h" +#include "alc/inprogext.h" #include "almalloc.h" -#include "async_event.h" +#include "core/async_event.h" #include "core/except.h" #include "core/logging.h" -#include "effects/base.h" -#include "inprogext.h" +#include "core/voice_change.h" #include "opthelpers.h" #include "ringbuffer.h" #include "threads.h" -#include "voice_change.h" static int EventThread(ALCcontext *context) @@ -75,25 +75,22 @@ static int EventThread(ALCcontext *context) msg += " state has changed to "; switch(evt.u.srcstate.state) { - case VChangeState::Reset: + case AsyncEvent::SrcState::Reset: msg += "AL_INITIAL"; state = AL_INITIAL; break; - case VChangeState::Stop: + case AsyncEvent::SrcState::Stop: msg += "AL_STOPPED"; state = AL_STOPPED; break; - case VChangeState::Play: + case AsyncEvent::SrcState::Play: msg += "AL_PLAYING"; state = AL_PLAYING; break; - case VChangeState::Pause: + case AsyncEvent::SrcState::Pause: msg += "AL_PAUSED"; state = AL_PAUSED; break; - /* Shouldn't happen */ - case VChangeState::Restart: - break; } context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id, state, static_cast(msg.length()), msg.c_str(), context->mEventParam); diff --git a/external/openal/al/extension.cpp b/external/openal/al/extension.cpp index 7346a5c6a0..d47bb576ea 100644 --- a/external/openal/al/extension.cpp +++ b/external/openal/al/extension.cpp @@ -27,7 +27,7 @@ #include "AL/al.h" #include "AL/alc.h" -#include "alcontext.h" +#include "alc/context.h" #include "alstring.h" #include "core/except.h" #include "opthelpers.h" diff --git a/external/openal/al/filter.cpp b/external/openal/al/filter.cpp index 0bcfe40863..21a8fee7af 100644 --- a/external/openal/al/filter.cpp +++ b/external/openal/al/filter.cpp @@ -37,8 +37,8 @@ #include "AL/efx.h" #include "albit.h" -#include "alcmain.h" -#include "alcontext.h" +#include "alc/context.h" +#include "alc/device.h" #include "almalloc.h" #include "alnumeric.h" #include "core/except.h" @@ -52,7 +52,11 @@ 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; @@ -404,8 +408,8 @@ START_API_FUNC context->setError(AL_INVALID_VALUE, "Generating %d filters", n); if UNLIKELY(n <= 0) return; - ALCdevice *device{context->mDevice.get()}; - std::lock_guard _{device->EffectLock}; + 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"); @@ -444,7 +448,7 @@ START_API_FUNC context->setError(AL_INVALID_VALUE, "Deleting %d filters", n); if UNLIKELY(n <= 0) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; /* First try to find any filters that are invalid. */ @@ -475,7 +479,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if LIKELY(context) { - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; if(!filter || LookupFilter(device, filter)) return AL_TRUE; @@ -491,7 +495,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; @@ -532,7 +536,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; @@ -555,7 +559,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; @@ -578,7 +582,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; @@ -601,7 +605,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; @@ -636,7 +640,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; @@ -659,7 +663,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; @@ -682,7 +686,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; std::lock_guard _{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; diff --git a/external/openal/al/listener.cpp b/external/openal/al/listener.cpp index 4bbc145c33..0997ff9508 100644 --- a/external/openal/al/listener.cpp +++ b/external/openal/al/listener.cpp @@ -29,7 +29,7 @@ #include "AL/alc.h" #include "AL/efx.h" -#include "alcontext.h" +#include "alc/context.h" #include "almalloc.h" #include "atomic.h" #include "core/except.h" @@ -40,7 +40,7 @@ if(!context->mDeferUpdates.load(std::memory_order_acquire)) \ UpdateListenerProps(context.get()); \ else \ - listener.PropsClean.clear(std::memory_order_release); \ + listener.mPropsDirty.set(std::memory_order_release); \ } while(0) diff --git a/external/openal/al/listener.h b/external/openal/al/listener.h index 5f3ce3ec87..f333276352 100644 --- a/external/openal/al/listener.h +++ b/external/openal/al/listener.h @@ -9,6 +9,7 @@ #include "AL/efx.h" #include "almalloc.h" +#include "atomic.h" struct ALlistener { @@ -19,9 +20,9 @@ struct ALlistener { float Gain{1.0f}; float mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT}; - std::atomic_flag PropsClean; + al::atomic_invflag mPropsDirty; - ALlistener() { PropsClean.test_and_set(std::memory_order_relaxed); } + ALlistener() { mPropsDirty.test_and_clear(std::memory_order_relaxed); } DISABLE_ALLOC() }; diff --git a/external/openal/al/source.cpp b/external/openal/al/source.cpp index 7cddb53a6f..05c42f057d 100644 --- a/external/openal/al/source.cpp +++ b/external/openal/al/source.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -46,31 +47,31 @@ #include "AL/efx.h" #include "albit.h" -#include "alcmain.h" -#include "alcontext.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 "alu.h" #include "atomic.h" #include "auxeffectslot.h" -#include "backends/base.h" -#include "bformatdec.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 "inprogext.h" #include "math_defs.h" #include "opthelpers.h" #include "ringbuffer.h" #include "threads.h" -#include "voice_change.h" namespace { @@ -183,7 +184,7 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context */ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime) { - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; const VoiceBufferItem *Current{}; uint64_t readPos{}; ALuint refcount; @@ -222,7 +223,7 @@ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds */ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime) { - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; const VoiceBufferItem *Current{}; uint64_t readPos{}; ALuint refcount; @@ -271,7 +272,7 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl */ double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) { - ALCdevice *device{context->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; const VoiceBufferItem *Current{}; ALuint readPos{}; ALuint readPosFrac{}; @@ -349,6 +350,57 @@ double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) 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; + + assert(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; @@ -439,72 +491,34 @@ void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, AL std::memory_order_relaxed); ALbuffer *buffer{BufferList->mBuffer}; - ALuint num_channels{buffer->channelsFromFmt()}; + ALuint num_channels{(buffer->mChannels==FmtUHJ2) ? 3 : buffer->channelsFromFmt()}; voice->mFrequency = buffer->mSampleRate; voice->mFmtChannels = buffer->mChannels; voice->mFmtType = buffer->mType; - voice->mSampleSize = buffer->bytesFromFmt(); - voice->mAmbiLayout = buffer->mAmbiLayout; - voice->mAmbiScaling = buffer->mAmbiScaling; + voice->mFrameSize = buffer->frameSizeFromFmt(); + voice->mAmbiLayout = (buffer->mChannels == FmtUHJ2 || buffer->mChannels == FmtUHJ3 + || voice->mFmtChannels == FmtUHJ4) ? AmbiLayout::FuMa : buffer->mAmbiLayout; + voice->mAmbiScaling = (buffer->mChannels == FmtUHJ2 || buffer->mChannels == FmtUHJ3 + || voice->mFmtChannels == FmtUHJ4) ? AmbiScaling::FuMa : buffer->mAmbiScaling; voice->mAmbiOrder = buffer->mAmbiOrder; if(buffer->mCallback) voice->mFlags |= VoiceIsCallback; else if(source->SourceType == AL_STATIC) voice->mFlags |= VoiceIsStatic; voice->mNumCallbackSamples = 0; - /* Clear the stepping value explicitly so the mixer knows not to mix this - * until the update gets applied. - */ - voice->mStep = 0; - if(voice->mChans.capacity() > 2 && num_channels < voice->mChans.capacity()) - al::vector{}.swap(voice->mChans); + { + decltype(voice->mChans){}.swap(voice->mChans); + decltype(voice->mVoiceSamples){}.swap(voice->mVoiceSamples); + } voice->mChans.reserve(maxu(2, num_channels)); voice->mChans.resize(num_channels); + voice->mVoiceSamples.reserve(maxu(2, num_channels)); + voice->mVoiceSamples.resize(num_channels); - /* Don't need to set the VOICE_IS_AMBISONIC flag if the device is not - * higher order than the voice. No HF scaling is necessary to mix it. - */ - if(voice->mAmbiOrder && device->mAmbiOrder > voice->mAmbiOrder) - { - const uint8_t *OrderFromChan{(voice->mFmtChannels == FmtBFormat2D) ? - AmbiIndex::OrderFrom2DChannel().data() : - AmbiIndex::OrderFromChannel().data()}; - const auto scales = BFormatDec::GetHFOrderScales(voice->mAmbiOrder, device->mAmbiOrder); + voice->prepare(device); - const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; - - for(auto &chandata : voice->mChans) - { - chandata.mPrevSamples.fill(0.0f); - chandata.mAmbiScale = scales[*(OrderFromChan++)]; - chandata.mAmbiSplitter = splitter; - chandata.mDryParams = DirectParams{}; - std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); - } - - voice->mFlags |= VoiceIsAmbisonic; - } - else - { - /* Clear previous samples. */ - for(auto &chandata : voice->mChans) - { - chandata.mPrevSamples.fill(0.0f); - chandata.mDryParams = DirectParams{}; - std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); - } - } - - if(device->AvgSpeakerDist > 0.0f) - { - const float w1{SpeedOfSoundMetersPerSec / - (device->AvgSpeakerDist * static_cast(device->Frequency))}; - for(auto &chandata : voice->mChans) - chandata.mDryParams.NFCtrlFilter.init(w1); - } - - source->PropsClean.test_and_set(std::memory_order_acq_rel); + source->mPropsDirty.test_and_clear(std::memory_order_acq_rel); UpdateSourceProps(source, voice, context); voice->mSourceID.store(source->id, std::memory_order_release); @@ -527,7 +541,7 @@ VoiceChange *GetVoiceChanger(ALCcontext *ctx) void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail) { - ALCdevice *device{ctx->mDevice.get()}; + ALCdevice *device{ctx->mALDevice.get()}; VoiceChange *oldhead{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)}; while(VoiceChange *next{oldhead->mNext.load(std::memory_order_relaxed)}) @@ -538,15 +552,20 @@ void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail) device->waitForMix(); if UNLIKELY(!connected) { - /* If the device is disconnected, just ignore all pending changes. */ - VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)}; - while(VoiceChange *next{cur->mNext.load(std::memory_order_acquire)}) + if(ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) { - cur = next; - if(Voice *voice{cur->mVoice}) - voice->mSourceID.store(0, std::memory_order_relaxed); + /* 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); } - ctx->mCurrentVoiceChange.store(cur, std::memory_order_release); } } @@ -934,6 +953,11 @@ enum SourceProp : ALenum { /* 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, @@ -983,6 +1007,9 @@ ALuint FloatValsByProp(ALenum prop) 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: return 1; case AL_STEREO_ANGLES: @@ -1045,6 +1072,9 @@ ALuint DoubleValsByProp(ALenum prop) 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: return 1; case AL_SEC_OFFSET_LATENCY_SOFT: @@ -1095,7 +1125,7 @@ bool UpdateSourceProps(ALsource *source, ALCcontext *context) if(SourceShouldUpdate(source, context) && (voice=GetSourceVoice(source, context)) != nullptr) UpdateSourceProps(source, voice, context); else - source->PropsClean.clear(std::memory_order_release); + source->mPropsDirty.set(std::memory_order_release); return true; } @@ -1105,6 +1135,7 @@ bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a switch(prop) { + case AL_SEC_LENGTH_SOFT: case AL_SEC_OFFSET_LATENCY_SOFT: case AL_SEC_OFFSET_CLOCK_SOFT: /* Query only */ @@ -1223,7 +1254,7 @@ bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a auto vpos = GetSampleOffset(Source->mQueue, prop, values[0]); if(!vpos) SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid offset"); - if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mDevice.get())) + if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mALDevice.get())) return true; } Source->OffsetType = prop; @@ -1298,6 +1329,8 @@ bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a 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: CHECKSIZE(values, 1); ival = static_cast(values[0]); return SetSourceiv(Source, Context, prop, {&ival, 1u}); @@ -1323,7 +1356,7 @@ bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values) { - ALCdevice *device{Context->mDevice.get()}; + ALCdevice *device{Context->mALDevice.get()}; ALeffectslot *slot{nullptr}; al::deque oldlist; std::unique_lock slotlock; @@ -1335,6 +1368,8 @@ bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a 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, false, "Setting read-only source property 0x%04x", prop); @@ -1577,7 +1612,7 @@ bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a */ Voice *voice{GetSourceVoice(Source, Context)}; if(voice) UpdateSourceProps(Source, voice, Context); - else Source->PropsClean.clear(std::memory_order_release); + else Source->mPropsDirty.set(std::memory_order_release); } else { @@ -1606,6 +1641,7 @@ bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a case AL_AIR_ABSORPTION_FACTOR: case AL_ROOM_ROLLOFF_FACTOR: case AL_SOURCE_RADIUS: + case AL_SEC_LENGTH_SOFT: CHECKSIZE(values, 1); fvals[0] = static_cast(values[0]); return SetSourcefv(Source, Context, prop, {fvals, 1u}); @@ -1655,6 +1691,8 @@ bool SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const 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 */ @@ -1716,6 +1754,7 @@ bool SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const case AL_AIR_ABSORPTION_FACTOR: case AL_ROOM_ROLLOFF_FACTOR: case AL_SOURCE_RADIUS: + case AL_SEC_LENGTH_SOFT: CHECKSIZE(values, 1); fvals[0] = static_cast(values[0]); return SetSourcefv(Source, Context, prop, {fvals, 1u}); @@ -1761,7 +1800,7 @@ bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values) { - ALCdevice *device{Context->mDevice.get()}; + ALCdevice *device{Context->mALDevice.get()}; ClockLatency clocktime; nanoseconds srcclock; int ivals[MaxValues]; @@ -1851,6 +1890,13 @@ bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a values[0] = Source->Radius; 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]; @@ -2044,6 +2090,14 @@ bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a 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); @@ -2126,7 +2180,7 @@ bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values) { - ALCdevice *device = Context->mDevice.get(); + ALCdevice *device{Context->mALDevice.get()}; ClockLatency clocktime; nanoseconds srcclock; double dvals[MaxValues]; @@ -2135,6 +2189,13 @@ bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const 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 @@ -2277,7 +2338,7 @@ START_API_FUNC if UNLIKELY(n <= 0) return; std::unique_lock srclock{context->mSourceLock}; - ALCdevice *device{context->mDevice.get()}; + 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)", @@ -2869,18 +2930,23 @@ START_API_FUNC ++sources; } - ALCdevice *device{context->mDevice.get()}; - /* If the device is disconnected, go right to stopped. */ + 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)) { - /* TODO: Send state change event? */ - for(ALsource *source : srchandles) + if(context->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) { - source->Offset = 0.0; - source->OffsetType = AL_NONE; - source->state = AL_STOPPED; + for(ALsource *source : srchandles) + { + /* TODO: Send state change event? */ + source->Offset = 0.0; + source->OffsetType = AL_NONE; + source->state = AL_STOPPED; + } + return; } - return; } /* Count the number of reusable voices. */ @@ -3247,7 +3313,7 @@ START_API_FUNC 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->mDevice.get()}; + ALCdevice *device{context->mALDevice.get()}; ALbuffer *BufferFmt{nullptr}; for(auto &item : source->mQueue) { @@ -3396,6 +3462,17 @@ START_API_FUNC 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; @@ -3413,7 +3490,7 @@ ALsource::ALsource() send.LFReference = HIGHPASSFREQREF; } - PropsClean.test_and_set(std::memory_order_relaxed); + mPropsDirty.test_and_clear(std::memory_order_relaxed); } ALsource::~ALsource() @@ -3440,7 +3517,7 @@ void UpdateAllSourceProps(ALCcontext *context) ALsource *source = sid ? LookupSource(context, sid) : nullptr; if(source && source->VoiceIdx == vidx) { - if(!source->PropsClean.test_and_set(std::memory_order_acq_rel)) + if(source->mPropsDirty.test_and_clear(std::memory_order_acq_rel)) UpdateSourceProps(source, voice, context); } ++vidx; diff --git a/external/openal/al/source.h b/external/openal/al/source.h index 6572864fab..4d1f66dc1a 100644 --- a/external/openal/al/source.h +++ b/external/openal/al/source.h @@ -11,14 +11,15 @@ #include "AL/al.h" #include "AL/alc.h" -#include "alcontext.h" +#include "alc/alu.h" +#include "alc/context.h" #include "aldeque.h" #include "almalloc.h" #include "alnumeric.h" -#include "alu.h" +#include "atomic.h" +#include "core/voice.h" #include "math_defs.h" #include "vector.h" -#include "voice.h" struct ALbuffer; struct ALeffectslot; @@ -109,7 +110,7 @@ struct ALsource { /** Source Buffer Queue head. */ al::deque mQueue; - std::atomic_flag PropsClean; + al::atomic_invflag mPropsDirty; /* Index into the context's Voices array. Lazily updated, only checked and * reset when looking up the voice. diff --git a/external/openal/al/state.cpp b/external/openal/al/state.cpp index ae761fb870..10c7bc741e 100644 --- a/external/openal/al/state.cpp +++ b/external/openal/al/state.cpp @@ -24,26 +24,27 @@ #include #include -#include -#include #include +#include +#include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" -#include "alcontext.h" -#include "almalloc.h" +#include "alc/alu.h" +#include "alc/context.h" +#include "alc/inprogext.h" #include "alnumeric.h" -#include "alspan.h" -#include "alu.h" +#include "aloptional.h" #include "atomic.h" +#include "core/context.h" #include "core/except.h" -#include "event.h" -#include "inprogext.h" +#include "core/mixer/defs.h" +#include "core/voice.h" +#include "intrusive_ptr.h" #include "opthelpers.h" #include "strutils.h" -#include "voice.h" namespace { @@ -142,7 +143,7 @@ END_API_FUNC if(!context->mDeferUpdates.load(std::memory_order_acquire)) \ UpdateContextProps(context.get()); \ else \ - context->mPropsClean.clear(std::memory_order_release); \ + context->mPropsDirty.set(std::memory_order_release); \ } while(0) @@ -152,12 +153,18 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - std::lock_guard _{context->mPropLock}; switch(capability) { case AL_SOURCE_DISTANCE_MODEL: - context->mSourceDistanceModel = true; - DO_UPDATEPROPS(); + { + 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: @@ -172,12 +179,18 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; - std::lock_guard _{context->mPropLock}; switch(capability) { case AL_SOURCE_DISTANCE_MODEL: - context->mSourceDistanceModel = false; - DO_UPDATEPROPS(); + { + std::lock_guard _{context->mPropLock}; + context->mSourceDistanceModel = false; + DO_UPDATEPROPS(); + } + break; + + case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: + context->mStopVoicesOnDisconnect = false; break; default: @@ -200,6 +213,10 @@ START_API_FUNC 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); } diff --git a/external/openal/alc/alc.cpp b/external/openal/alc/alc.cpp index a755e67556..e564fcab63 100644 --- a/external/openal/alc/alc.cpp +++ b/external/openal/alc/alc.cpp @@ -27,10 +27,11 @@ #include #endif -#include #include #include #include +#include +#include #include #include #include @@ -47,9 +48,10 @@ #include #include #include -#include +#include +#include #include -#include +#include #include #include "AL/al.h" @@ -60,49 +62,46 @@ #include "al/auxeffectslot.h" #include "al/buffer.h" #include "al/effect.h" -#include "al/event.h" #include "al/filter.h" #include "al/listener.h" #include "al/source.h" #include "albit.h" -#include "alcmain.h" #include "albyte.h" #include "alconfig.h" -#include "alcontext.h" +#include "alc/context.h" +#include "alc/effectslot.h" #include "almalloc.h" #include "alnumeric.h" #include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "alu.h" -#include "async_event.h" #include "atomic.h" -#include "bformatdec.h" -#include "compat.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/except.h" +#include "core/helpers.h" #include "core/mastering.h" -#include "core/filters/nfc.h" -#include "core/filters/splitter.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 "front_stablizer.h" -#include "hrtf.h" #include "inprogext.h" #include "intrusive_ptr.h" #include "opthelpers.h" -#include "pragmadefs.h" -#include "ringbuffer.h" #include "strutils.h" #include "threads.h" -#include "vecmat.h" #include "vector.h" -#include "voice_change.h" #include "backends/base.h" #include "backends/null.h" @@ -154,6 +153,31 @@ #endif +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 { using namespace std::placeholders; @@ -161,6 +185,7 @@ using std::chrono::seconds; using std::chrono::nanoseconds; using voidp = void*; +using float2 = std::array; /************************************************ @@ -269,6 +294,8 @@ const struct { DECL(alcGetInteger64vSOFT), + DECL(alcReopenDeviceSOFT), + DECL(alEnable), DECL(alDisable), DECL(alIsEnabled), @@ -821,6 +848,18 @@ constexpr struct { 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_STOP_SOURCES_ON_DISCONNECT_SOFT), }; #undef DECL @@ -843,86 +882,17 @@ std::string alcAllDevicesList; std::string alcCaptureDeviceList; /* Default is always the first in the list */ -al::string alcDefaultAllDevicesSpecifier; -al::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_bformat_ex " - "AL_SOFTX_bformat_hoa " - "AL_SOFT_block_alignment " - "AL_SOFTX_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_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::string alcDefaultAllDevicesSpecifier; +std::string alcCaptureDefaultDeviceSpecifier; std::atomic LastNullDeviceError{ALC_NO_ERROR}; -/* Thread-local current context. The handling may look a little obtuse, but - * it's designed this way to avoid a bug with 32-bit GCC/MinGW, which causes - * thread-local object destructors to get a junk 'this' pointer. This method - * has the benefit of making LocalContext access more efficient since it's a - * a plain pointer, with the ThreadContext object used to check it at thread - * exit (and given no data fields, 'this' being junk is inconsequential since - * it's never accessed). - */ -thread_local ALCcontext *LocalContext{nullptr}; -class ThreadCtx { -public: - ~ThreadCtx() - { - if(ALCcontext *ctx{LocalContext}) - { - const bool result{ctx->releaseIfNoDelete()}; - ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx}, - result ? "" : ", leak detected"); - } - } - - void set(ALCcontext *ctx) const noexcept { LocalContext = ctx; } -}; -thread_local ThreadCtx ThreadContext; - -/* 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. */ @@ -942,7 +912,8 @@ constexpr ALCchar alcNoDeviceExtList[] = "ALC_EXT_EFX " "ALC_EXT_thread_local_context " "ALC_SOFT_loopback " - "ALC_SOFT_loopback_bformat"; + "ALC_SOFT_loopback_bformat " + "ALC_SOFTX_reopen_device"; constexpr ALCchar alcExtensionList[] = "ALC_ENUMERATE_ALL_EXT " "ALC_ENUMERATION_EXT " @@ -956,7 +927,8 @@ constexpr ALCchar alcExtensionList[] = "ALC_SOFT_loopback " "ALC_SOFT_loopback_bformat " "ALC_SOFT_output_limiter " - "ALC_SOFT_pause_device"; + "ALC_SOFT_pause_device " + "ALC_SOFTX_reopen_device"; constexpr int alcMajorVersion{1}; constexpr int alcMinorVersion{1}; @@ -964,12 +936,6 @@ constexpr int alcEFXMajorVersion{1}; constexpr int alcEFXMinorVersion{0}; -/* To avoid extraneous allocations, a 0-sized FlexArray is defined - * globally as a sharable object. - */ -al::FlexArray EmptyContextArray{0u}; - - using DeviceRef = al::intrusive_ptr; @@ -1018,7 +984,7 @@ void alc_initconfig(void) TRACE("Initializing library v%s-%s %s\n", ALSOFT_VERSION, ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); { - al::string names; + std::string names; if(al::size(BackendList) < 1) names = "(none)"; else @@ -1114,9 +1080,11 @@ void alc_initconfig(void) if(auto priopt = ConfigValueInt(nullptr, nullptr, "rt-prio")) RTPrioLevel = *priopt; + if(auto limopt = ConfigValueBool(nullptr, nullptr, "rt-time-limit")) + AllowRTTimeLimit = *limopt; aluInit(); - aluInitMixer(); + Voice::InitMixer(ConfigValueStr(nullptr, nullptr, "resampler")); auto traperr = al::getenv("ALSOFT_TRAP_ERROR"); if(traperr && (al::strcasecmp(traperr->c_str(), "true") == 0 @@ -1256,10 +1224,10 @@ void alc_initconfig(void) } while(next++); } - InitEffect(&DefaultEffect); + InitEffect(&ALCcontext::sDefaultEffect); auto defrevopt = al::getenv("ALSOFT_DEFAULT_REVERB"); if(defrevopt || (defrevopt=ConfigValueStr(nullptr, nullptr, "default-reverb"))) - LoadReverbPreset(defrevopt->c_str(), &DefaultEffect); + LoadReverbPreset(defrevopt->c_str(), &ALCcontext::sDefaultEffect); } #define DO_INITCONFIG() std::call_once(alc_config_once, [](){alc_initconfig();}) @@ -1296,40 +1264,6 @@ void ProbeCaptureDeviceList() } } -} // namespace - -/* Mixing thread piority level */ -int RTPrioLevel{1}; - -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 - -/************************************************ - * Device format information - ************************************************/ -namespace { struct DevFmtPair { DevFmtChannels chans; DevFmtType type; }; al::optional DecomposeDevFormat(ALenum format) @@ -1522,92 +1456,9 @@ const std::array X71Downmix{{ { BackCenter, {{{BackLeft, 0.5f}, {BackRight, 0.5f}}} }, }}; -} // namespace - -/************************************************ - * Miscellaneous ALC helpers - ************************************************/ - -void ALCcontext::processUpdates() -{ - std::lock_guard _{mPropLock}; - if(mDeferUpdates.exchange(false, std::memory_order_acq_rel)) - { - /* 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 */ - } - - if(!mPropsClean.test_and_set(std::memory_order_acq_rel)) - UpdateContextProps(this); - if(!mListener.PropsClean.test_and_set(std::memory_order_acq_rel)) - UpdateListenerProps(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); - } -} - - -void ALCcontext::allocVoiceChanges(size_t addcount) -{ - constexpr size_t clustersize{128}; - /* Convert element count to cluster count. */ - addcount = (addcount+(clustersize-1)) / clustersize; - while(addcount) - { - 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(); - --addcount; - } -} - -void ALCcontext::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; - } -} - /** Stores the latest ALC device error. */ -static void alcSetError(ALCdevice *device, ALCenum errorCode) +void alcSetError(ALCdevice *device, ALCenum errorCode) { WARN("Error generated on device %p, code 0x%04x\n", voidp{device}, errorCode); if(TrapALCError) @@ -1628,7 +1479,7 @@ static void alcSetError(ALCdevice *device, ALCenum errorCode) } -static std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const float threshold) +std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const float threshold) { constexpr bool AutoKnee{true}; constexpr bool AutoAttack{true}; @@ -1667,7 +1518,7 @@ static inline void UpdateClockBase(ALCdevice *device) * Updates device parameters according to the attribute list (caller is * responsible for holding the list lock). */ -static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) +ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) { HrtfRequestMode hrtf_userreq{Hrtf_Default}; HrtfRequestMode hrtf_appreq{Hrtf_Default}; @@ -1884,7 +1735,7 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) return ALC_NO_ERROR; device->AvgSpeakerDist = 0.0f; - device->Uhj_Encoder = nullptr; + device->mUhjEncoder = nullptr; device->AmbiDecoder = nullptr; device->Bs2b = nullptr; device->PostProcess = nullptr; @@ -1912,7 +1763,7 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) /************************************************************************* * Update device format request if HRTF is requested */ - device->HrtfStatus = ALC_HRTF_DISABLED_SOFT; + device->mHrtfStatus = ALC_HRTF_DISABLED_SOFT; if(device->Type != DeviceType::Loopback) { if(auto hrtfopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "hrtf")) @@ -1949,6 +1800,7 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) throw al::backend_exception{al::backend_error::DeviceError, "Device reset failure"}; } catch(std::exception &e) { + ERR("Device error: %s\n", e.what()); device->handleDisconnect("%s", e.what()); return ALC_INVALID_DEVICE; } @@ -1995,8 +1847,8 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) device->AuxiliaryEffectSlotMax, device->NumAuxSends); nanoseconds::rep sample_delay{0}; - if(device->Uhj_Encoder) - sample_delay += Uhj2Encoder::sFilterSize; + if(device->mUhjEncoder) + sample_delay += UhjEncoder::sFilterDelay; if(device->mHrtfState) sample_delay += HrtfDirectDelay; if(auto *ambidec = device->AmbiDecoder.get()) @@ -2101,8 +1953,10 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) TRACE("Fixed device latency: %" PRId64 "ns\n", int64_t{device->FixedLatency.count()}); FPUCtl mixer_mode{}; - for(ALCcontext *context : *device->mContexts.load()) + for(ContextBase *ctxbase : *device->mContexts.load()) { + auto *context = static_cast(ctxbase); + auto GetEffectBuffer = [](ALbuffer *buffer) noexcept -> EffectState::Buffer { if(!buffer) return EffectState::Buffer{}; @@ -2174,7 +2028,7 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) auto send_begin = source->Send.begin() + static_cast(num_sends); std::for_each(send_begin, source->Send.end(), clear_send); - source->PropsClean.clear(std::memory_order_release); + source->mPropsDirty.set(std::memory_order_release); } } @@ -2213,58 +2067,13 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) if(voice->mSourceID.load(std::memory_order_relaxed) == 0u) continue; - voice->mStep = 0; - voice->mFlags |= VoiceIsFading; - - if(voice->mAmbiOrder && device->mAmbiOrder > voice->mAmbiOrder) - { - const uint8_t *OrderFromChan{(voice->mFmtChannels == FmtBFormat2D) ? - AmbiIndex::OrderFrom2DChannel().data() : - AmbiIndex::OrderFromChannel().data()}; - - const BandSplitter splitter{device->mXOverFreq / - static_cast(device->Frequency)}; - - const auto scales = BFormatDec::GetHFOrderScales(voice->mAmbiOrder, - device->mAmbiOrder); - for(auto &chandata : voice->mChans) - { - chandata.mPrevSamples.fill(0.0f); - chandata.mAmbiScale = scales[*(OrderFromChan++)]; - chandata.mAmbiSplitter = splitter; - chandata.mDryParams = DirectParams{}; - std::fill_n(chandata.mWetParams.begin(), num_sends, SendParams{}); - } - - voice->mFlags |= VoiceIsAmbisonic; - } - else - { - /* Clear previous samples. */ - for(auto &chandata : voice->mChans) - { - chandata.mPrevSamples.fill(0.0f); - chandata.mDryParams = DirectParams{}; - std::fill_n(chandata.mWetParams.begin(), num_sends, SendParams{}); - } - - voice->mFlags &= ~VoiceIsAmbisonic; - } - - if(device->AvgSpeakerDist > 0.0f) - { - /* Reinitialize the NFC filters for new parameters. */ - const float w1{SpeedOfSoundMetersPerSec / - (device->AvgSpeakerDist * static_cast(device->Frequency))}; - for(auto &chandata : voice->mChans) - chandata.mDryParams.NFCtrlFilter.init(w1); - } + voice->prepare(device); } srclock.unlock(); - context->mPropsClean.test_and_set(std::memory_order_release); + context->mPropsDirty.test_and_clear(std::memory_order_release); UpdateContextProps(context); - context->mListener.PropsClean.test_and_set(std::memory_order_release); + context->mListener.mPropsDirty.test_and_clear(std::memory_order_release); UpdateListenerProps(context); UpdateAllSourceProps(context); } @@ -2278,6 +2087,7 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) device->Flags.set(DeviceRunning); } catch(al::backend_exception& e) { + ERR("%s\n", e.what()); device->handleDisconnect("%s", e.what()); return ALC_INVALID_DEVICE; } @@ -2286,44 +2096,51 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) return ALC_NO_ERROR; } - -ALCdevice::ALCdevice(DeviceType type) : Type{type}, mContexts{&EmptyContextArray} +/** + * Updates device parameters as above, and also first clears the disconnected + * status, if set. + */ +bool ResetDeviceParams(ALCdevice *device, const int *attrList) { -} + /* If the device was disconnected, reset it since we're opened anew. */ + if UNLIKELY(!device->Connected.load(std::memory_order_relaxed)) + { + /* Make sure disconnection is finished before continuing on. */ + device->waitForMix(); -ALCdevice::~ALCdevice() -{ - TRACE("Freeing device %p\n", voidp{this}); + for(ContextBase *ctxbase : *device->mContexts.load(std::memory_order_acquire)) + { + auto *ctx = static_cast(ctxbase); + if(!ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) + continue; - Backend = nullptr; + /* 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); - 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"); + ctx->mVoiceClusters.clear(); + ctx->allocVoices(std::max(256, + ctx->mActiveVoiceCount.load(std::memory_order_relaxed))); + } - 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"); + device->Connected.store(true); + } - 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"); + ALCenum err{UpdateDeviceParams(device, attrList)}; + if LIKELY(err == ALC_NO_ERROR) return ALC_TRUE; - mHrtf = nullptr; - - auto *oldarray = mContexts.exchange(nullptr, std::memory_order_relaxed); - if(oldarray != &EmptyContextArray) delete oldarray; + alcSetError(device, err); + return ALC_FALSE; } /** Checks if the device handle is valid, and returns a new reference if so. */ -static DeviceRef VerifyDevice(ALCdevice *device) +DeviceRef VerifyDevice(ALCdevice *device) { std::lock_guard _{ListLock}; auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); @@ -2336,217 +2153,10 @@ static DeviceRef VerifyDevice(ALCdevice *device) } -ALCcontext::ALCcontext(al::intrusive_ptr device) : mDevice{std::move(device)} -{ - mPropsClean.test_and_set(std::memory_order_relaxed); -} - -ALCcontext::~ALCcontext() -{ - TRACE("Freeing context %p\n", voidp{this}); - - 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 = 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; - - 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; - } - 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; - - count = 0; - VoicePropsItem *vprops{mFreeVoiceProps.exchange(nullptr, std::memory_order_acquire)}; - while(vprops) - { - std::unique_ptr old{vprops}; - vprops = old->next.load(std::memory_order_relaxed); - ++count; - } - TRACE("Freed %zu voice property object%s\n", count, (count==1)?"":"s"); - - delete mVoices.exchange(nullptr, std::memory_order_relaxed); - - count = 0; - ListenerProps *lprops{mParams.ListenerUpdate.exchange(nullptr, std::memory_order_relaxed)}; - if(lprops) - { - ++count; - delete lprops; - } - lprops = mFreeListenerProps.exchange(nullptr, std::memory_order_acquire); - while(lprops) - { - std::unique_ptr old{lprops}; - lprops = old->next.load(std::memory_order_relaxed); - ++count; - } - TRACE("Freed %zu listener property object%s\n", count, (count==1)?"":"s"); - - 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 ALCcontext::init() -{ - if(DefaultEffect.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(1); - { - VoiceChange *cur{mVoiceChangeTail}; - while(VoiceChange *next{cur->mNext.load(std::memory_order_relaxed)}) - cur = next; - mCurrentVoiceChange.store(cur, std::memory_order_relaxed); - } - - mExtensionList = alExtList; - - - mParams.Matrix = alu::Matrix::Identity(); - mParams.Velocity = alu::Vector{}; - mParams.Gain = mListener.Gain; - mParams.MetersPerUnit = mListener.mMetersPerUnit; - 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(LocalContext == this) - { - WARN("%p released while current on thread\n", voidp{this}); - ThreadContext.set(nullptr); - release(); - } - - ALCcontext *origctx{this}; - if(GlobalContext.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 &EmptyContextArray; - 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 != &EmptyContextArray) - { - mDevice->waitForMix(); - delete oldarray; - } - - ret = !newarray->empty(); - } - else - ret = !oldarray->empty(); - - StopEventThrd(this); - - return ret; -} - - /** * 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.begin(), ContextList.end(), context); @@ -2558,16 +2168,18 @@ static ContextRef VerifyContext(ALCcontext *context) return nullptr; } +} // namespace + /** Returns a new reference to the currently active context for this thread. */ ContextRef GetContextRef(void) { - ALCcontext *context{LocalContext}; + ALCcontext *context{ALCcontext::sLocalContext}; if(context) context->add_ref(); else { std::lock_guard _{ListLock}; - context = GlobalContext.load(std::memory_order_acquire); + context = ALCcontext::sGlobalContext.load(std::memory_order_acquire); if(context) context->add_ref(); } return ContextRef{context}; @@ -2654,7 +2266,15 @@ START_API_FUNC case ALC_ALL_DEVICES_SPECIFIER: if(DeviceRef dev{VerifyDevice(Device)}) - value = dev->DeviceName.c_str(); + { + if(dev->Type != DeviceType::Playback) + alcSetError(dev.get(), ALC_INVALID_ENUM); + else + { + std::lock_guard _{dev->StateLock}; + value = dev->DeviceName.c_str(); + } + } else { ProbeAllDevicesList(); @@ -2664,7 +2284,15 @@ START_API_FUNC case ALC_CAPTURE_DEVICE_SPECIFIER: if(DeviceRef dev{VerifyDevice(Device)}) - value = dev->DeviceName.c_str(); + { + if(dev->Type != DeviceType::Capture) + alcSetError(dev.get(), ALC_INVALID_ENUM); + else + { + std::lock_guard _{dev->StateLock}; + value = dev->DeviceName.c_str(); + } + } else { ProbeCaptureDeviceList(); @@ -2706,7 +2334,7 @@ START_API_FUNC 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); @@ -2722,15 +2350,6 @@ START_API_FUNC END_API_FUNC -static inline int NumAttrsForDevice(ALCdevice *device) -{ - if(device->Type == DeviceType::Capture) return 9; - if(device->Type != DeviceType::Loopback) return 29; - if(device->FmtChans == DevFmtAmbi3D) - return 35; - return 29; -} - static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values) { size_t i; @@ -2787,15 +2406,15 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span if(device->Type == DeviceType::Capture) { + constexpr int MaxCaptureAttributes{9}; switch(param) { case ALC_ATTRIBUTES_SIZE: - values[0] = NumAttrsForDevice(device); + values[0] = MaxCaptureAttributes; return 1; - case ALC_ALL_ATTRIBUTES: i = 0; - if(values.size() < static_cast(NumAttrsForDevice(device))) + if(values.size() < MaxCaptureAttributes) alcSetError(device, ALC_INVALID_VALUE); else { @@ -2809,6 +2428,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[i++] = ALC_CONNECTED; values[i++] = device->Connected.load(std::memory_order_relaxed); values[i++] = 0; + assert(i == MaxCaptureAttributes); } return i; @@ -2840,6 +2460,12 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span } /* render device */ + auto NumAttrsForDevice = [](ALCdevice *aldev) noexcept + { + if(aldev->Type == DeviceType::Loopback && aldev->FmtChans == DevFmtAmbi3D) + return 35; + return 29; + }; switch(param) { case ALC_ATTRIBUTES_SIZE: @@ -2906,7 +2532,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[i++] = (device->mHrtf ? ALC_TRUE : ALC_FALSE); values[i++] = ALC_HRTF_STATUS_SOFT; - values[i++] = device->HrtfStatus; + values[i++] = device->mHrtfStatus; values[i++] = ALC_OUTPUT_LIMITER_SOFT; values[i++] = device->Limiter ? ALC_TRUE : ALC_FALSE; @@ -3028,14 +2654,14 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span return 1; case ALC_HRTF_STATUS_SOFT: - values[0] = device->HrtfStatus; + values[0] = device->mHrtfStatus; return 1; case ALC_NUM_HRTF_SPECIFIERS_SOFT: { std::lock_guard _{device->StateLock}; - device->HrtfList = EnumerateHrtf(device->DeviceName.c_str()); - values[0] = static_cast(minz(device->HrtfList.size(), + device->enumerateHrtfs(); + values[0] = static_cast(minz(device->mHrtfList.size(), std::numeric_limits::max())); } return 1; @@ -3077,24 +2703,30 @@ START_API_FUNC if(!dev || dev->Type == DeviceType::Capture) { auto ivals = al::vector(static_cast(size)); - size_t got{GetIntegerv(dev.get(), pname, ivals)}; - std::copy_n(ivals.begin(), got, values); + if(size_t got{GetIntegerv(dev.get(), pname, ivals)}) + std::copy_n(ivals.begin(), got, values); return; } /* render device */ + auto NumAttrsForDevice = [](ALCdevice *aldev) noexcept + { + if(aldev->Type == DeviceType::Loopback && aldev->FmtChans == DevFmtAmbi3D) + return 39; + return 33; + }; switch(pname) { case ALC_ATTRIBUTES_SIZE: - *values = NumAttrsForDevice(dev.get())+4; + *values = NumAttrsForDevice(dev.get()); break; case ALC_ALL_ATTRIBUTES: - if(size < NumAttrsForDevice(dev.get())+4) + if(size < NumAttrsForDevice(dev.get())) alcSetError(dev.get(), ALC_INVALID_VALUE); else { - size_t i{0}; std::lock_guard _{dev->StateLock}; + size_t i{0}; values[i++] = ALC_FREQUENCY; values[i++] = dev->Frequency; @@ -3108,6 +2740,12 @@ START_API_FUNC } else { + values[i++] = ALC_FORMAT_CHANNELS_SOFT; + values[i++] = EnumFromDevFmt(dev->FmtChans); + + values[i++] = ALC_FORMAT_TYPE_SOFT; + values[i++] = EnumFromDevFmt(dev->FmtType); + if(dev->FmtChans == DevFmtAmbi3D) { values[i++] = ALC_AMBISONIC_LAYOUT_SOFT; @@ -3119,12 +2757,6 @@ START_API_FUNC values[i++] = ALC_AMBISONIC_ORDER_SOFT; values[i++] = dev->mAmbiOrder; } - - values[i++] = ALC_FORMAT_CHANNELS_SOFT; - values[i++] = EnumFromDevFmt(dev->FmtChans); - - values[i++] = ALC_FORMAT_TYPE_SOFT; - values[i++] = EnumFromDevFmt(dev->FmtType); } values[i++] = ALC_MONO_SOURCES; @@ -3140,7 +2772,7 @@ START_API_FUNC values[i++] = (dev->mHrtf ? ALC_TRUE : ALC_FALSE); values[i++] = ALC_HRTF_STATUS_SOFT; - values[i++] = dev->HrtfStatus; + values[i++] = dev->mHrtfStatus; values[i++] = ALC_OUTPUT_LIMITER_SOFT; values[i++] = dev->Limiter ? ALC_TRUE : ALC_FALSE; @@ -3193,8 +2825,8 @@ START_API_FUNC default: auto ivals = al::vector(static_cast(size)); - size_t got{GetIntegerv(dev.get(), pname, ivals)}; - std::copy_n(ivals.begin(), got, values); + if(size_t got{GetIntegerv(dev.get(), pname, ivals)}) + std::copy_n(ivals.begin(), got, values); break; } } @@ -3318,7 +2950,7 @@ START_API_FUNC UpdateListenerProps(context.get()); { - using ContextArray = al::FlexArray; + using ContextArray = al::FlexArray; /* Allocate a new context array, which holds 1 more than the current/ * old array. @@ -3337,7 +2969,7 @@ START_API_FUNC * to finish before deleting the old array. */ dev->mContexts.store(newarray.release()); - if(oldarray != &EmptyContextArray) + if(oldarray != &DeviceBase::sEmptyContextArray) { dev->waitForMix(); delete oldarray; @@ -3353,7 +2985,7 @@ START_API_FUNC if(ALeffectslot *slot{context->mDefaultSlot.get()}) { - if(slot->initEffect(&DefaultEffect, context.get()) == AL_NO_ERROR) + if(slot->initEffect(&ALCcontext::sDefaultEffect, context.get()) == AL_NO_ERROR) slot->updateProps(context.get()); else ERR("Failed to initialize the default effect\n"); @@ -3381,7 +3013,7 @@ START_API_FUNC ContextRef ctx{*iter}; ContextList.erase(iter); - ALCdevice *Device{ctx->mDevice.get()}; + ALCdevice *Device{ctx->mALDevice.get()}; std::lock_guard _{Device->StateLock}; if(!ctx->deinit() && Device->Flags.test(DeviceRunning)) @@ -3396,8 +3028,8 @@ END_API_FUNC ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void) START_API_FUNC { - ALCcontext *Context{LocalContext}; - if(!Context) Context = GlobalContext.load(); + ALCcontext *Context{ALCcontext::sLocalContext}; + if(!Context) Context = ALCcontext::sGlobalContext.load(); return Context; } END_API_FUNC @@ -3405,7 +3037,7 @@ END_API_FUNC /** Returns the currently active thread-local context. */ ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) START_API_FUNC -{ return LocalContext; } +{ return ALCcontext::sLocalContext; } END_API_FUNC ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) @@ -3426,14 +3058,14 @@ 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}; - if(ctx) ThreadContext.set(nullptr); + ctx = ContextRef{ALCcontext::sLocalContext}; + if(ctx) ALCcontext::sThreadContext.set(nullptr); /* Reset (decrement) the previous thread-local reference. */ return ALC_TRUE; @@ -3456,8 +3088,8 @@ START_API_FUNC } } /* context's reference count is already incremented */ - ContextRef old{LocalContext}; - ThreadContext.set(ctx.release()); + ContextRef old{ALCcontext::sLocalContext}; + ALCcontext::sThreadContext.set(ctx.release()); return ALC_TRUE; } @@ -3473,7 +3105,7 @@ START_API_FUNC alcSetError(nullptr, ALC_INVALID_CONTEXT); return nullptr; } - return ctx->mDevice.get(); + return ctx->mALDevice.get(); } END_API_FUNC @@ -3702,7 +3334,7 @@ START_API_FUNC std::unique_lock statelock{dev->StateLock}; al::vector orphanctxs; - for(ALCcontext *ctx : *dev->mContexts.load()) + for(ContextBase *ctx : *dev->mContexts.load()) { auto ctxiter = std::lower_bound(ContextList.begin(), ContextList.end(), ctx); if(ctxiter != ContextList.end() && *ctxiter == ctx) @@ -3853,6 +3485,7 @@ START_API_FUNC 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); } @@ -4006,13 +3639,12 @@ END_API_FUNC 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 != DeviceType::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 - dev->renderSamples(buffer, static_cast(samples), dev->channelsFromFmt()); + device->renderSamples(buffer, static_cast(samples), device->channelsFromFmt()); } END_API_FUNC @@ -4063,6 +3695,7 @@ START_API_FUNC 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); } @@ -4084,8 +3717,8 @@ START_API_FUNC else switch(paramName) { case ALC_HRTF_SPECIFIER_SOFT: - if(index >= 0 && static_cast(index) < dev->HrtfList.size()) - return dev->HrtfList[static_cast(index)].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; @@ -4119,34 +3752,67 @@ START_API_FUNC if(dev->Flags.test(DeviceRunning)) dev->Backend->stop(); dev->Flags.reset(DeviceRunning); - if(!dev->Connected.load(std::memory_order_relaxed)) - { - /* Make sure disconnection is finished before continuing on. */ - dev->waitForMix(); - for(ALCcontext *ctx : *dev->mContexts.load(std::memory_order_acquire)) - { - /* 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->mVoiceClusters.clear(); - ctx->allocVoices(std::max(256, - ctx->mActiveVoiceCount.load(std::memory_order_relaxed))); - } - - dev->Connected.store(true); - } - - ALCenum err{UpdateDeviceParams(dev.get(), attribs)}; - if LIKELY(err == ALC_NO_ERROR) return ALC_TRUE; - - alcSetError(dev.get(), err); - return ALC_FALSE; + return ResetDeviceParams(dev.get(), attribs) ? ALC_TRUE : ALC_FALSE; +} +END_API_FUNC + + +/************************************************ + * 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}; + auto backend = dev->Backend.get(); + + /* Force the backend to stop mixing first since we're reopening. */ + if(dev->Flags.test(DeviceRunning)) + backend->stop(); + dev->Flags.reset(DeviceRunning); + + try { + backend->open(deviceName); + } + catch(al::backend_exception &e) { + 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(dev->Connected.load(std::memory_order_relaxed) && !dev->Flags.test(DevicePaused) + && !dev->mContexts.load(std::memory_order_relaxed)->empty()) + { + try { + 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(); + TRACE("Reopened device %p, \"%s\"\n", voidp{dev.get()}, dev->DeviceName.c_str()); + + return ResetDeviceParams(dev.get(), attribs) ? ALC_TRUE : ALC_FALSE; } END_API_FUNC diff --git a/external/openal/alc/alconfig.cpp b/external/openal/alc/alconfig.cpp index 634679aabd..5e00c054b5 100644 --- a/external/openal/alc/alconfig.cpp +++ b/external/openal/alc/alconfig.cpp @@ -40,7 +40,7 @@ #include "alfstream.h" #include "alstring.h" -#include "compat.h" +#include "core/helpers.h" #include "core/logging.h" #include "strutils.h" #include "vector.h" diff --git a/external/openal/alc/alu.cpp b/external/openal/alc/alu.cpp index a29c283385..d370aba7cd 100644 --- a/external/openal/alc/alu.cpp +++ b/external/openal/alc/alu.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -37,47 +36,48 @@ #include #include #include -#include +#include #include -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/efx.h" - -#include "alcmain.h" -#include "alcontext.h" #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "alstring.h" -#include "async_event.h" #include "atomic.h" -#include "bformatdec.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/filters/biquad.h" #include "core/filters/nfc.h" -#include "core/filters/splitter.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 "effects/base.h" #include "effectslot.h" -#include "front_stablizer.h" -#include "hrtf.h" -#include "inprogext.h" +#include "intrusive_ptr.h" #include "math_defs.h" #include "opthelpers.h" #include "ringbuffer.h" #include "strutils.h" #include "threads.h" #include "vecmat.h" -#include "voice.h" -#include "voice_change.h" +#include "vector.h" struct CTag; #ifdef HAVE_SSE @@ -92,7 +92,6 @@ struct SSE4Tag; #ifdef HAVE_NEON struct NEONTag; #endif -struct CopyTag; struct PointTag; struct LerpTag; struct CubicTag; @@ -100,12 +99,13 @@ struct BSincTag; struct FastBSincTag; -static_assert(MaxResamplerPadding >= BSincPointsMax, "MaxResamplerPadding is too small"); static_assert(!(MaxResamplerPadding&1), "MaxResamplerPadding is not a multiple of two"); namespace { +using uint = unsigned int; + constexpr uint MaxPitch{10}; static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!"); @@ -154,9 +154,9 @@ struct ChanMap { float elevation; }; -using HrtfDirectMixerFunc = void(*)(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const al::span InSamples, float2 *AccumSamples, - float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize); +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_}; @@ -182,8 +182,8 @@ inline void BsincPrepare(const uint increment, BsincState *state, const BSincTab if(increment > MixerFracOne) { - sf = MixerFracOne / static_cast(increment); - sf = maxf(0.0f, (BSincScaleCount-1) * (sf-table->scaleBase) * table->scaleRange); + 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 @@ -280,7 +280,7 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState } -void ALCdevice::ProcessHrtf(const size_t SamplesToDo) +void DeviceBase::ProcessHrtf(const size_t SamplesToDo) { /* HRTF is stereo output only. */ const uint lidx{RealOut.ChannelIndex[FrontLeft]}; @@ -290,12 +290,12 @@ void ALCdevice::ProcessHrtf(const size_t SamplesToDo) mHrtfState->mTemp.data(), mHrtfState->mChannels.data(), mHrtfState->mIrSize, SamplesToDo); } -void ALCdevice::ProcessAmbiDec(const size_t SamplesToDo) +void DeviceBase::ProcessAmbiDec(const size_t SamplesToDo) { AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo); } -void ALCdevice::ProcessAmbiDecStablized(const size_t SamplesToDo) +void DeviceBase::ProcessAmbiDecStablized(const size_t SamplesToDo) { /* Decode with front image stablization. */ const uint lidx{RealOut.ChannelIndex[FrontLeft]}; @@ -306,18 +306,18 @@ void ALCdevice::ProcessAmbiDecStablized(const size_t SamplesToDo) SamplesToDo); } -void ALCdevice::ProcessUhj(const size_t 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]}; /* Encode to stereo-compatible 2-channel UHJ output. */ - Uhj_Encoder->encode(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer.data(), + mUhjEncoder->encode(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer.data(), SamplesToDo); } -void ALCdevice::ProcessBs2b(const size_t SamplesToDo) +void DeviceBase::ProcessBs2b(const size_t SamplesToDo) { /* First, decode the ambisonic mix to the "real" output. */ AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo); @@ -365,7 +365,7 @@ inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept } -bool CalcContextParams(ALCcontext *ctx) +bool CalcContextParams(ContextBase *ctx) { ContextProps *props{ctx->mParams.ContextUpdate.exchange(nullptr, std::memory_order_acq_rel)}; if(!props) return false; @@ -380,7 +380,7 @@ bool CalcContextParams(ALCcontext *ctx) return true; } -bool CalcListenerParams(ALCcontext *ctx) +bool CalcListenerParams(ContextBase *ctx) { ListenerProps *props{ctx->mParams.ListenerUpdate.exchange(nullptr, std::memory_order_acq_rel)}; @@ -418,7 +418,7 @@ bool CalcListenerParams(ALCcontext *ctx) return true; } -bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ALCcontext *context) +bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBase *context) { EffectSlotProps *props{slot->Update.exchange(nullptr, std::memory_order_acq_rel)}; if(!props) return false; @@ -488,7 +488,7 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ALCcontex output = EffectTarget{&target->Wet, nullptr}; else { - ALCdevice *device{context->mDevice.get()}; + DeviceBase *device{context->mDevice}; output = EffectTarget{&device->Dry, &device->RealOut}; } state->update(context, slot, &slot->mEffectProps, output); @@ -678,7 +678,7 @@ struct GainTriplet { float Base, HF, LF; }; void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, const float zpos, const float Distance, const float Spread, const GainTriplet &DryGain, const al::span WetGain, EffectSlot *(&SendSlots)[MAX_SENDS], - const VoiceProps *props, const ContextParams &Context, const ALCdevice *Device) + const VoiceProps *props, const ContextParams &Context, const DeviceBase *Device) { static const ChanMap MonoMap[1]{ { FrontCenter, 0.0f, 0.0f } @@ -790,16 +790,21 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con case FmtBFormat2D: case FmtBFormat3D: + case FmtUHJ2: + case FmtUHJ3: + case FmtUHJ4: DirectChannels = DirectMode::Off; break; } voice->mFlags &= ~(VoiceHasHrtf | VoiceHasNfc); - if(voice->mFmtChannels == FmtBFormat2D || voice->mFmtChannels == FmtBFormat3D) + if(voice->mFmtChannels == FmtBFormat2D || voice->mFmtChannels == FmtBFormat3D + || voice->mFmtChannels == FmtUHJ2 || voice->mFmtChannels == FmtUHJ3 + || voice->mFmtChannels == FmtUHJ4) { /* Special handling for B-Format sources. */ - if(Device->AvgSpeakerDist > 0.0f) + if(Device->AvgSpeakerDist > 0.0f && voice->mFmtChannels != FmtUHJ2) { if(!(Distance > std::numeric_limits::epsilon())) { @@ -898,7 +903,9 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con /* Convert the rotation matrix for input ordering and scaling, and * whether input is 2D or 3D. */ - const uint8_t *index_map{(voice->mFmtChannels == FmtBFormat2D) ? + const uint8_t *index_map{ + (voice->mFmtChannels == FmtBFormat2D || voice->mFmtChannels == FmtUHJ2 + || voice->mFmtChannels == FmtUHJ3) ? GetAmbi2DLayout(voice->mAmbiLayout).data() : GetAmbiLayout(voice->mAmbiLayout).data()}; @@ -1196,9 +1203,9 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con } } -void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ALCcontext *context) +void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context) { - const ALCdevice *Device{context->mDevice.get()}; + const DeviceBase *Device{context->mDevice}; EffectSlot *SendSlots[MAX_SENDS]; voice->mDirect.Buffer = Device->Dry.Buffer; @@ -1242,9 +1249,9 @@ void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ALCcon context->mParams, Device); } -void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ALCcontext *context) +void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context) { - const ALCdevice *Device{context->mDevice.get()}; + const DeviceBase *Device{context->mDevice}; const uint NumSends{Device->NumAuxSends}; /* Set mixing buffers and get send parameters. */ @@ -1542,7 +1549,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ALCcontex context->mParams, Device); } -void CalcSourceParams(Voice *voice, ALCcontext *context, bool force) +void CalcSourceParams(Voice *voice, ContextBase *context, bool force) { VoicePropsItem *props{voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel)}; if(!props && !force) return; @@ -1555,7 +1562,9 @@ void CalcSourceParams(Voice *voice, ALCcontext *context, bool force) } if((voice->mProps.DirectChannels != DirectMode::Off && voice->mFmtChannels != FmtMono - && voice->mFmtChannels != FmtBFormat2D && voice->mFmtChannels != FmtBFormat3D) + && voice->mFmtChannels != FmtBFormat2D && voice->mFmtChannels != FmtBFormat3D + && voice->mFmtChannels != FmtUHJ2 && voice->mFmtChannels != FmtUHJ3 + && voice->mFmtChannels != FmtUHJ3) || voice->mProps.mSpatializeMode==SpatializeMode::Off || (voice->mProps.mSpatializeMode==SpatializeMode::Auto && voice->mFmtChannels != FmtMono)) CalcNonAttnSourceParams(voice, &voice->mProps, context); @@ -1564,7 +1573,7 @@ void CalcSourceParams(Voice *voice, ALCcontext *context, bool force) } -void SendSourceStateEvent(ALCcontext *context, uint id, VChangeState state) +void SendSourceStateEvent(ContextBase *context, uint id, VChangeState state) { RingBuffer *ring{context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); @@ -1572,12 +1581,29 @@ void SendSourceStateEvent(ALCcontext *context, uint id, VChangeState state) AsyncEvent *evt{::new(evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}}; evt->u.srcstate.id = id; - evt->u.srcstate.state = state; + switch(state) + { + 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); + } ring->writeAdvance(1); } -void ProcessVoiceChanges(ALCcontext *ctx) +void ProcessVoiceChanges(ContextBase *ctx) { VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)}; VoiceChange *next{cur->mNext.load(std::memory_order_acquire)}; @@ -1672,7 +1698,7 @@ void ProcessVoiceChanges(ALCcontext *ctx) ctx->mCurrentVoiceChange.store(cur, std::memory_order_release); } -void ProcessParamUpdates(ALCcontext *ctx, const EffectSlotArray &slots, +void ProcessParamUpdates(ContextBase *ctx, const EffectSlotArray &slots, const al::span voices) { ProcessVoiceChanges(ctx); @@ -1696,11 +1722,11 @@ void ProcessParamUpdates(ALCcontext *ctx, const EffectSlotArray &slots, IncrementRef(ctx->mUpdateCount); } -void ProcessContexts(ALCdevice *device, const uint SamplesToDo) +void ProcessContexts(DeviceBase *device, const uint SamplesToDo) { ASSUME(SamplesToDo > 0); - for(ALCcontext *ctx : *device->mContexts.load(std::memory_order_acquire)) + for(ContextBase *ctx : *device->mContexts.load(std::memory_order_acquire)) { const EffectSlotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire); const al::span voices{ctx->getVoicesSpanAcquired()}; @@ -1902,7 +1928,7 @@ void Write(const al::span InBuffer, void *OutBuffer, cons } // namespace -void ALCdevice::renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep) +void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep) { FPUCtl mixer_mode{}; for(uint written{0u};written < numSamples;) @@ -1973,7 +1999,7 @@ void ALCdevice::renderSamples(void *outBuffer, const uint numSamples, const size } } -void ALCdevice::handleDisconnect(const char *msg, ...) +void DeviceBase::handleDisconnect(const char *msg, ...) { if(!Connected.exchange(false, std::memory_order_acq_rel)) return; @@ -1989,7 +2015,7 @@ void ALCdevice::handleDisconnect(const char *msg, ...) evt.u.disconnect.msg[sizeof(evt.u.disconnect.msg)-1] = 0; IncrementRef(MixCount); - for(ALCcontext *ctx : *mContexts.load()) + for(ContextBase *ctx : *mContexts.load()) { const uint enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)}; if((enabledevt&EventType_Disconnected)) @@ -2004,6 +2030,12 @@ void ALCdevice::handleDisconnect(const char *msg, ...) } } + if(!ctx->mStopVoicesOnDisconnect) + { + ProcessVoiceChanges(ctx); + continue; + } + auto voicelist = ctx->getVoicesSpanAcquired(); auto stop_voice = [](Voice *voice) -> void { diff --git a/external/openal/alc/alu.h b/external/openal/alc/alu.h index 2aa1a65243..67c7c410fd 100644 --- a/external/openal/alc/alu.h +++ b/external/openal/alc/alu.h @@ -1,40 +1,15 @@ #ifndef ALU_H #define ALU_H -#include -#include -#include -#include - -#include "alspan.h" -#include "core/ambidefs.h" -#include "core/bufferline.h" -#include "core/devformat.h" - struct ALCcontext; struct ALCdevice; struct EffectSlot; -struct MixParams; - - -#define MAX_SENDS 6 - - -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; constexpr float GainMixMax{1000.0f}; /* +60dB */ -constexpr float SpeedOfSoundMetersPerSec{343.3f}; constexpr float AirAbsorbGainHF{0.99426f}; /* -0.05dB */ -/** Target gain for the reverb decay feedback reaching the decay time. */ -constexpr float ReverbDecayGain{0.001f}; /* -60 dB */ - enum HrtfRequestMode { Hrtf_Default = 0, @@ -44,8 +19,6 @@ enum HrtfRequestMode { void aluInit(void); -void aluInitMixer(void); - /* aluInitRenderer * * Set up the appropriate panning method and mixing method given the device @@ -56,84 +29,6 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, HrtfRequestMode hrtf_appreq void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context); -/** - * 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); - } -} - extern const float ConeScale; extern const float ZScale; diff --git a/external/openal/alc/backends/alsa.cpp b/external/openal/alc/backends/alsa.cpp index bdfdd040e7..f6f318eb7e 100644 --- a/external/openal/alc/backends/alsa.cpp +++ b/external/openal/alc/backends/alsa.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/alsa.h" +#include "alsa.h" #include #include @@ -36,12 +36,12 @@ #include #include "albyte.h" -#include "alcmain.h" -#include "alconfig.h" +#include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" #include "aloptional.h" -#include "alu.h" +#include "core/device.h" +#include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "ringbuffer.h" @@ -407,7 +407,7 @@ 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(); @@ -635,12 +635,16 @@ void AlsaPlayback::open(const char *name) name = alsaDevice; driver = GetConfigValue(nullptr, "alsa", "device", "default"); } - 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) 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(); @@ -863,7 +867,7 @@ ClockLatency AlsaPlayback::getClockLatency() struct AlsaCapture final : public BackendBase { - AlsaCapture(ALCdevice *device) noexcept : BackendBase{device} { } + AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~AlsaCapture() override; void open(const char *name) override; @@ -1252,7 +1256,7 @@ std::string AlsaBackendFactory::probe(BackendType type) 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/external/openal/alc/backends/alsa.h b/external/openal/alc/backends/alsa.h index 12023cb6c9..b256dcf5a9 100644 --- a/external/openal/alc/backends/alsa.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/base.cpp b/external/openal/alc/backends/base.cpp index c4a4abebda..0c28d238d2 100644 --- a/external/openal/alc/backends/base.cpp +++ b/external/openal/alc/backends/base.cpp @@ -3,21 +3,22 @@ #include "base.h" +#include +#include #include -#include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include -#endif #include "albit.h" -#include "alcmain.h" -#include "alnumeric.h" -#include "aloptional.h" -#include "atomic.h" #include "core/logging.h" +#include "aloptional.h" +#endif + +#include "atomic.h" +#include "core/devformat.h" bool BackendBase::reset() diff --git a/external/openal/alc/backends/base.h b/external/openal/alc/backends/base.h index df076276b4..d661bc4603 100644 --- a/external/openal/alc/backends/base.h +++ b/external/openal/alc/backends/base.h @@ -2,12 +2,13 @@ #define ALC_BACKENDS_BASE_H #include +#include #include -#include +#include #include #include "albyte.h" -#include "alcmain.h" +#include "core/device.h" #include "core/except.h" @@ -30,9 +31,9 @@ struct BackendBase { virtual ClockLatency getClockLatency(); - ALCdevice *const mDevice; + DeviceBase *const mDevice; - BackendBase(ALCdevice *device) noexcept : mDevice{device} { } + BackendBase(DeviceBase *device) noexcept : mDevice{device} { } virtual ~BackendBase() = default; protected: @@ -57,7 +58,7 @@ enum class BackendType { /* Helper to get the current clock time from the device's ClockBase, and * SamplesDone converted from the sample rate. */ -inline std::chrono::nanoseconds GetDeviceClockTime(ALCdevice *device) +inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device) { using std::chrono::seconds; using std::chrono::nanoseconds; @@ -69,7 +70,7 @@ inline std::chrono::nanoseconds GetDeviceClockTime(ALCdevice *device) /* Helper to get the device latency from the backend, including any fixed * latency from post-processing. */ -inline ClockLatency GetClockLatency(ALCdevice *device) +inline ClockLatency GetClockLatency(DeviceBase *device) { BackendBase *backend{device->Backend.get()}; ClockLatency ret{backend->getClockLatency()}; @@ -85,7 +86,7 @@ struct BackendFactory { virtual std::string probe(BackendType type) = 0; - virtual BackendPtr createBackend(ALCdevice *device, BackendType type) = 0; + virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0; protected: virtual ~BackendFactory() = default; @@ -103,7 +104,11 @@ 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; diff --git a/external/openal/alc/backends/coreaudio.cpp b/external/openal/alc/backends/coreaudio.cpp index 8e38a777df..006c3a76b8 100644 --- a/external/openal/alc/backends/coreaudio.cpp +++ b/external/openal/alc/backends/coreaudio.cpp @@ -20,23 +20,23 @@ #include "config.h" -#include "backends/coreaudio.h" +#include "coreaudio.h" #include +#include #include #include #include +#include #include -#include "alcmain.h" -#include "alu.h" -#include "ringbuffer.h" -#include "converter.h" +#include "alnumeric.h" +#include "core/converter.h" +#include "core/device.h" #include "core/logging.h" -#include "backends/base.h" +#include "ringbuffer.h" -#include #include #include @@ -47,7 +47,7 @@ static const char ca_device[] = "CoreAudio Default"; struct CoreAudioPlayback final : public BackendBase { - CoreAudioPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~CoreAudioPlayback() override; OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags, @@ -105,7 +105,7 @@ void CoreAudioPlayback::open(const char *name) /* open the default output unit */ AudioComponentDescription desc{}; desc.componentType = kAudioUnitType_Output; -#if TARGET_OS_IOS +#if TARGET_OS_IOS || TARGET_OS_TV desc.componentSubType = kAudioUnitSubType_RemoteIO; #else desc.componentSubType = kAudioUnitSubType_DefaultOutput; @@ -118,17 +118,27 @@ void CoreAudioPlayback::open(const char *name) if(comp == nullptr) 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) 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); + 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) + { + AudioUnitUninitialize(mAudioUnit); + AudioComponentInstanceDispose(mAudioUnit); + } + mAudioUnit = audioUnit; + mDevice->DeviceName = name; } @@ -294,7 +304,7 @@ void CoreAudioPlayback::stop() struct CoreAudioCapture final : public BackendBase { - CoreAudioCapture(ALCdevice *device) noexcept : BackendBase{device} { } + CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~CoreAudioCapture() override; OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags, @@ -400,7 +410,7 @@ void CoreAudioCapture::open(const char *name) name}; desc.componentType = kAudioUnitType_Output; -#if TARGET_OS_IOS +#if TARGET_OS_IOS || TARGET_OS_TV desc.componentSubType = kAudioUnitSubType_RemoteIO; #else desc.componentSubType = kAudioUnitSubType_HALOutput; @@ -436,7 +446,7 @@ void CoreAudioCapture::open(const char *name) throw al::backend_exception{al::backend_error::DeviceError, "Could not enable audio unit input property: %u", err}; -#if !TARGET_OS_IOS +#if !TARGET_OS_IOS && !TARGET_OS_TV { // Get the default input device AudioDeviceID inputDevice = kAudioDeviceUnknown; @@ -676,7 +686,7 @@ std::string CoreAudioBackendFactory::probe(BackendType type) 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/external/openal/alc/backends/coreaudio.h b/external/openal/alc/backends/coreaudio.h index 1695780689..1252edde38 100644 --- a/external/openal/alc/backends/coreaudio.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/dsound.cpp b/external/openal/alc/backends/dsound.cpp index aa3a92a015..401f81657d 100644 --- a/external/openal/alc/backends/dsound.cpp +++ b/external/openal/alc/backends/dsound.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/dsound.h" +#include "dsound.h" #define WIN32_LEAN_AND_MEAN #include @@ -44,9 +44,10 @@ #include #include -#include "alcmain.h" -#include "alu.h" -#include "compat.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" @@ -169,21 +170,21 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi struct DSoundPlayback final : public BackendBase { - DSoundPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~DSoundPlayback() override; int mixerProc(); - void open(const ALCchar *name) 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; @@ -193,19 +194,11 @@ struct DSoundPlayback final : public BackendBase { 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; @@ -234,8 +227,8 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() 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; @@ -344,31 +337,34 @@ void DSoundPlayback::open(const char *name) } 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)) 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; } bool DSoundPlayback::reset() { - if(mNotifies) - mNotifies->Release(); mNotifies = nullptr; - if(mBuffer) - mBuffer->Release(); mBuffer = nullptr; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); mPrimaryBuffer = nullptr; switch(mDevice->FmtType) @@ -465,7 +461,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); @@ -485,7 +481,7 @@ retry_open: 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; @@ -499,7 +495,7 @@ retry_open: hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr); if(SUCCEEDED(hr)) { - mNotifies = static_cast(ptr); + mNotifies = ComPtr{static_cast(ptr)}; uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; assert(num_updates <= MAX_UPDATES); @@ -517,14 +513,8 @@ retry_open: if(FAILED(hr)) { - if(mNotifies) - mNotifies->Release(); mNotifies = nullptr; - if(mBuffer) - mBuffer->Release(); mBuffer = nullptr; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); mPrimaryBuffer = nullptr; return false; } @@ -558,7 +548,7 @@ void DSoundPlayback::stop() struct DSoundCapture final : public BackendBase { - DSoundCapture(ALCdevice *device) noexcept : BackendBase{device} { } + DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~DSoundCapture() override; void open(const char *name) override; @@ -567,8 +557,8 @@ struct DSoundCapture final : public BackendBase { 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}; @@ -582,12 +572,8 @@ DSoundCapture::~DSoundCapture() if(mDSCbuffer) { mDSCbuffer->Stop(); - mDSCbuffer->Release(); mDSCbuffer = nullptr; } - - if(mDSC) - mDSC->Release(); mDSC = nullptr; } @@ -693,20 +679,16 @@ void DSoundCapture::open(const char *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 = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false); if(FAILED(hr)) { mRing = nullptr; - if(mDSCbuffer) - mDSCbuffer->Release(); mDSCbuffer = nullptr; - if(mDSC) - mDSC->Release(); mDSC = nullptr; throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", @@ -858,7 +840,7 @@ std::string DSoundBackendFactory::probe(BackendType type) 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/external/openal/alc/backends/dsound.h b/external/openal/alc/backends/dsound.h index 83f7d5c776..787f227a0a 100644 --- a/external/openal/alc/backends/dsound.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/jack.cpp b/external/openal/alc/backends/jack.cpp index 17b1f13955..54bd19e646 100644 --- a/external/openal/alc/backends/jack.cpp +++ b/external/openal/alc/backends/jack.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/jack.h" +#include "jack.h" #include #include @@ -31,9 +31,10 @@ #include #include -#include "alcmain.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" @@ -45,9 +46,6 @@ namespace { -constexpr char jackDevice[] = "JACK Default"; - - #ifdef HAVE_DYNLOAD #define JACK_FUNCS(MAGIC) \ MAGIC(jack_client_open); \ @@ -160,21 +158,55 @@ struct DeviceEntry { al::vector PlaybackList; -void EnumerateDevices(al::vector &list) +void EnumerateDevices(jack_client_t *client, al::vector &list) { - al::vector{}.swap(list); + std::remove_reference_t{}.swap(list); - list.emplace_back(DeviceEntry{jackDevice, ""}); - - std::string customList{ConfigValueStr(nullptr, "jack", "custom-devices").value_or("")}; - size_t strpos{0}; - while(strpos < customList.size()) + const char **ports{jack_get_ports(client, nullptr, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)}; + if(ports) { - size_t nextpos{customList.find(';', strpos)}; - size_t seppos{customList.find('=', strpos)}; + 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", ""}); + } + jack_free(ports); + } + + auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices"); + if(!listopt) return; + + size_t strpos{0}; + while(strpos < listopt->size()) + { + size_t nextpos{listopt->find(';', strpos)}; + size_t seppos{listopt->find('=', strpos)}; if(seppos >= nextpos || seppos == strpos) { - const std::string entry{customList.substr(strpos, nextpos-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; @@ -182,18 +214,18 @@ void EnumerateDevices(al::vector &list) } size_t count{1}; - std::string name{customList.substr(strpos, seppos-strpos)}; + std::string name{listopt->substr(strpos, seppos-strpos)}; auto check_name = [&name](const DeviceEntry &entry) -> bool { return entry.mName == name; }; while(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend()) { - name = customList.substr(strpos, seppos-strpos); + name = listopt->substr(strpos, seppos-strpos); name += " #"; name += std::to_string(++count); } ++seppos; - list.emplace_back(DeviceEntry{std::move(name), customList.substr(seppos, nextpos-seppos)}); + list.emplace_back(DeviceEntry{std::move(name), listopt->substr(seppos, nextpos-seppos)}); const auto &entry = list.back(); TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); @@ -204,7 +236,7 @@ void EnumerateDevices(al::vector &list) struct JackPlayback final : public BackendBase { - JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~JackPlayback() override; int process(jack_nframes_t numframes) noexcept; @@ -352,39 +384,44 @@ int JackPlayback::mixerProc() void JackPlayback::open(const char *name) { - mPortPattern.clear(); - - if(!name) - name = jackDevice; - else if(strcmp(name, jackDevice) != 0) + if(!mClient) { - if(PlaybackList.empty()) - EnumerateDevices(PlaybackList); + 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); + } + } + + if(PlaybackList.empty()) + EnumerateDevices(mClient, PlaybackList); + + if(!name && !PlaybackList.empty()) + { + name = PlaybackList[0].mName.c_str(); + mPortPattern = PlaybackList[0].mPattern; + } + else + { 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}; + "Device name \"%s\" not found", name?name:""}; mPortPattern = iter->mPattern; } - const char *client_name{"alsoft"}; - 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); - } - jack_set_process_callback(mClient, &JackPlayback::processC, this); mDevice->DeviceName = name; @@ -453,8 +490,8 @@ void JackPlayback::start() const char *devname{mDevice->DeviceName.c_str()}; if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true)) { - const char **ports{jack_get_ports(mClient, mPortPattern.c_str(), nullptr, - JackPortIsPhysical|JackPortIsInput)}; + const char **ports{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput)}; if(ports == nullptr) { jack_deactivate(mClient); @@ -547,10 +584,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) { @@ -575,10 +615,20 @@ std::string JackBackendFactory::probe(BackendType type) /* Includes null char. */ outnames.append(entry.mName.c_str(), entry.mName.length()+1); }; + + 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: - EnumerateDevices(PlaybackList); + 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: @@ -587,7 +637,7 @@ std::string JackBackendFactory::probe(BackendType type) 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/external/openal/alc/backends/jack.h b/external/openal/alc/backends/jack.h index f966c8f0b2..b83f24dda1 100644 --- a/external/openal/alc/backends/jack.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/loopback.cpp b/external/openal/alc/backends/loopback.cpp index 7f421ea776..bf4ab2467e 100644 --- a/external/openal/alc/backends/loopback.cpp +++ b/external/openal/alc/backends/loopback.cpp @@ -20,18 +20,17 @@ #include "config.h" -#include "backends/loopback.h" +#include "loopback.h" -#include "alcmain.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} { } - void open(const ALCchar *name) override; + void open(const char *name) override; bool reset() override; void start() override; void stop() override; @@ -40,7 +39,7 @@ struct LoopbackBackend final : public BackendBase { }; -void LoopbackBackend::open(const ALCchar *name) +void LoopbackBackend::open(const char *name) { mDevice->DeviceName = name; } @@ -69,7 +68,7 @@ bool LoopbackBackendFactory::querySupport(BackendType) std::string LoopbackBackendFactory::probe(BackendType) { return std::string{}; } -BackendPtr LoopbackBackendFactory::createBackend(ALCdevice *device, BackendType) +BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType) { return BackendPtr{new LoopbackBackend{device}}; } BackendFactory &LoopbackBackendFactory::getFactory() diff --git a/external/openal/alc/backends/loopback.h b/external/openal/alc/backends/loopback.h index d000e03360..cb42b3c8e8 100644 --- a/external/openal/alc/backends/loopback.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/null.cpp b/external/openal/alc/backends/null.cpp index 15a36ddd4a..5a8fc255c1 100644 --- a/external/openal/alc/backends/null.cpp +++ b/external/openal/alc/backends/null.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/null.h" +#include "null.h" #include #include @@ -30,9 +30,9 @@ #include #include -#include "alcmain.h" +#include "core/device.h" #include "almalloc.h" -#include "alu.h" +#include "core/helpers.h" #include "threads.h" @@ -46,7 +46,7 @@ constexpr char nullDevice[] = "No Output"; struct NullBackend final : public BackendBase { - NullBackend(ALCdevice *device) noexcept : BackendBase{device} { } + NullBackend(DeviceBase *device) noexcept : BackendBase{device} { } int mixerProc(); @@ -165,7 +165,7 @@ std::string NullBackendFactory::probe(BackendType type) 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/external/openal/alc/backends/null.h b/external/openal/alc/backends/null.h index 8e9c59e508..7048cad6f0 100644 --- a/external/openal/alc/backends/null.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/oboe.cpp b/external/openal/alc/backends/oboe.cpp index d84cea8e7d..28ade849b1 100644 --- a/external/openal/alc/backends/oboe.cpp +++ b/external/openal/alc/backends/oboe.cpp @@ -5,8 +5,10 @@ #include #include +#include -#include "alu.h" +#include "alnumeric.h" +#include "core/device.h" #include "core/logging.h" #include "oboe/Oboe.h" @@ -18,7 +20,7 @@ constexpr char device_name[] = "Oboe Default"; struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback { - OboePlayback(ALCdevice *device) : BackendBase{device} { } + OboePlayback(DeviceBase *device) : BackendBase{device} { } oboe::ManagedStream mStream; @@ -64,9 +66,10 @@ void OboePlayback::open(const char *name) 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(mStream)}; + ->openManagedStream(stream)}; if(result != oboe::Result::OK) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", oboe::convertToText(result)}; @@ -220,7 +223,7 @@ void OboePlayback::stop() struct OboeCapture final : public BackendBase { - OboeCapture(ALCdevice *device) : BackendBase{device} { } + OboeCapture(DeviceBase *device) : BackendBase{device} { } oboe::ManagedStream mStream; @@ -367,7 +370,7 @@ std::string OboeBackendFactory::probe(BackendType type) return std::string{}; } -BackendPtr OboeBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new OboePlayback{device}}; diff --git a/external/openal/alc/backends/oboe.h b/external/openal/alc/backends/oboe.h index 93b98e37c6..a39c24454f 100644 --- a/external/openal/alc/backends/oboe.h +++ b/external/openal/alc/backends/oboe.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_OBOE_H #define BACKENDS_OBOE_H -#include "backends/base.h" +#include "base.h" struct OboeBackendFactory final : public BackendFactory { public: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/opensl.cpp b/external/openal/alc/backends/opensl.cpp index 917e097f68..7be7f031c1 100644 --- a/external/openal/alc/backends/opensl.cpp +++ b/external/openal/alc/backends/opensl.cpp @@ -21,7 +21,7 @@ #include "config.h" -#include "backends/opensl.h" +#include "opensl.h" #include #include @@ -33,9 +33,9 @@ #include #include "albit.h" -#include "alcmain.h" -#include "alu.h" -#include "compat.h" +#include "alnumeric.h" +#include "core/device.h" +#include "core/helpers.h" #include "core/logging.h" #include "opthelpers.h" #include "ringbuffer.h" @@ -151,7 +151,7 @@ const char *res_str(SLresult result) noexcept struct OpenSLPlayback final : public BackendBase { - OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~OpenSLPlayback() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; @@ -316,6 +316,9 @@ void OpenSLPlayback::open(const char *name) 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)}; PRINTERR(result, "slCreateEngine"); @@ -629,7 +632,7 @@ ClockLatency OpenSLPlayback::getClockLatency() struct OpenSLCapture final : public BackendBase { - OpenSLCapture(ALCdevice *device) noexcept : BackendBase{device} { } + OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~OpenSLCapture() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; @@ -959,7 +962,7 @@ std::string OSLBackendFactory::probe(BackendType type) 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/external/openal/alc/backends/opensl.h b/external/openal/alc/backends/opensl.h index b1c5cf5a99..b81624476c 100644 --- a/external/openal/alc/backends/opensl.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/oss.cpp b/external/openal/alc/backends/oss.cpp index 4cff595990..6d4fa26151 100644 --- a/external/openal/alc/backends/oss.cpp +++ b/external/openal/alc/backends/oss.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/oss.h" +#include "oss.h" #include #include @@ -41,13 +41,13 @@ #include #include -#include "alcmain.h" -#include "alconfig.h" #include "albyte.h" +#include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" #include "aloptional.h" -#include "alu.h" +#include "core/device.h" +#include "core/helpers.h" #include "core/logging.h" #include "ringbuffer.h" #include "threads.h" @@ -226,7 +226,7 @@ uint log2i(uint x) struct OSSPlayback final : public BackendBase { - OSSPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~OSSPlayback() override; int mixerProc(); @@ -249,7 +249,7 @@ struct OSSPlayback final : public BackendBase { OSSPlayback::~OSSPlayback() { if(mFd != -1) - close(mFd); + ::close(mFd); mFd = -1; } @@ -328,11 +328,15 @@ void OSSPlayback::open(const char *name) devname = iter->device_name.c_str(); } - mFd = ::open(devname, O_WRONLY); - if(mFd == -1) + 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; } @@ -438,7 +442,7 @@ void OSSPlayback::stop() struct OSScapture final : public BackendBase { - OSScapture(ALCdevice *device) noexcept : BackendBase{device} { } + OSScapture(DeviceBase *device) noexcept : BackendBase{device} { } ~OSScapture() override; int recordProc(); @@ -676,7 +680,7 @@ std::string OSSBackendFactory::probe(BackendType type) 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/external/openal/alc/backends/oss.h b/external/openal/alc/backends/oss.h index dd92efc3ed..4f2c00b969 100644 --- a/external/openal/alc/backends/oss.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/portaudio.cpp b/external/openal/alc/backends/portaudio.cpp index d2e6a5a448..2d5cd36d34 100644 --- a/external/openal/alc/backends/portaudio.cpp +++ b/external/openal/alc/backends/portaudio.cpp @@ -20,15 +20,15 @@ #include "config.h" -#include "backends/portaudio.h" +#include "portaudio.h" #include #include #include -#include "alcmain.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" @@ -72,7 +72,7 @@ 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, @@ -123,53 +123,58 @@ void PortPlayback::open(const char *name) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; - mUpdateSize = mDevice->UpdateSize; - + PaStreamParameters params{}; auto devidopt = ConfigValueInt(nullptr, "port", "device"); - if(devidopt && *devidopt >= 0) mParams.device = *devidopt; - else mParams.device = Pa_GetDefaultOutputDevice(); - mParams.suggestedLatency = mDevice->BufferSize / static_cast(mDevice->Frequency); - mParams.hostApiSpecificStreamInfo = nullptr; + 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; + params.sampleFormat = paInt8; break; case DevFmtUByte: - mParams.sampleFormat = paUInt8; + params.sampleFormat = paUInt8; break; case DevFmtUShort: /* fall-through */ case DevFmtShort: - mParams.sampleFormat = paInt16; + params.sampleFormat = paInt16; break; case DevFmtUInt: /* fall-through */ case DevFmtInt: - mParams.sampleFormat = paInt32; + params.sampleFormat = paInt32; break; case DevFmtFloat: - mParams.sampleFormat = paFloat32; + 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; } throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", Pa_GetErrorText(err)}; } + Pa_CloseStream(mStream); + mStream = stream; + mParams = params; + mUpdateSize = mDevice->UpdateSize; + mDevice->DeviceName = name; } @@ -226,7 +231,7 @@ 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, @@ -426,7 +431,7 @@ std::string PortBackendFactory::probe(BackendType type) 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/external/openal/alc/backends/portaudio.h b/external/openal/alc/backends/portaudio.h index 9dbd6b9461..c35ccff2d6 100644 --- a/external/openal/alc/backends/portaudio.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/pulseaudio.cpp b/external/openal/alc/backends/pulseaudio.cpp index a6aa93a5e3..23bcecad4d 100644 --- a/external/openal/alc/backends/pulseaudio.cpp +++ b/external/openal/alc/backends/pulseaudio.cpp @@ -21,33 +21,49 @@ #include "config.h" -#include "backends/pulseaudio.h" +#include "pulseaudio.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 "alcmain.h" -#include "alu.h" -#include "alconfig.h" -#include "compat.h" +#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); \ @@ -282,7 +298,7 @@ al::optional ChannelFromPulse(pa_channel_position_t chan) return al::nullopt; } -void SetChannelOrderFromMap(ALCdevice *device, const pa_channel_map &chanmap) +void SetChannelOrderFromMap(DeviceBase *device, const pa_channel_map &chanmap) { device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); for(uint i{0};i < chanmap.channels;++i) @@ -497,19 +513,13 @@ public: pa_context *PulseMainloop::connectContext(std::unique_lock &plock) { - const char *name{"OpenAL Soft"}; - - const PathNamePair &binname = GetProcBinary(); - if(!binname.fname.empty()) - name = binname.fname.c_str(); - 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), name)}; + 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"}; @@ -661,7 +671,7 @@ PulseMainloop gGlobalMainloop; struct PulsePlayback final : public BackendBase { - PulsePlayback(ALCdevice *device) noexcept : BackendBase{device} { } + PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~PulsePlayback() override; void bufferAttrCallback(pa_stream *stream) noexcept; @@ -846,7 +856,8 @@ void PulsePlayback::open(const char *name) } auto plock = mMainloop.getUniqueLock(); - mContext = mMainloop.connectContext(plock); + if(!mContext) + mContext = mMainloop.connectContext(plock); pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | PA_STREAM_FIX_CHANNELS}; @@ -864,8 +875,18 @@ void PulsePlayback::open(const char *name) if(defname) pulse_name = defname->c_str(); } TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = mMainloop.connectStream(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 = static_cast(pa_frame_size(pa_stream_get_sample_spec(mStream))); @@ -1029,34 +1050,34 @@ void PulsePlayback::start() { auto plock = mMainloop.getUniqueLock(); - pa_stream_set_write_callback(mStream, &PulsePlayback::streamWriteCallbackC, this); - pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, - &mMainloop)}; - - /* Write some (silent) samples to fill the prebuf amount if needed. */ - if(size_t prebuf{mAttr.prebuf}) + /* 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)}) { - prebuf = minz(prebuf, pa_stream_writable_size(mStream)); - - void *buf{pa_xmalloc(prebuf)}; + void *buf{pa_xmalloc(todo)}; switch(mSpec.format) { case PA_SAMPLE_U8: - std::fill_n(static_cast(buf), prebuf, 0x80); + std::fill_n(static_cast(buf), todo, 0x80); break; case PA_SAMPLE_ALAW: - std::fill_n(static_cast(buf), prebuf, 0xD5); + std::fill_n(static_cast(buf), todo, 0xD5); break; case PA_SAMPLE_ULAW: - std::fill_n(static_cast(buf), prebuf, 0x7f); + std::fill_n(static_cast(buf), todo, 0x7f); break; default: - std::fill_n(static_cast(buf), prebuf, 0x00); + std::fill_n(static_cast(buf), todo, 0x00); break; } - pa_stream_write(mStream, buf, prebuf, pa_xfree, 0, PA_SEEK_RELATIVE); + 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, &PulseMainloop::streamSuccessCallbackC, + &mMainloop)}; + mMainloop.waitForOperation(op, plock); } @@ -1103,7 +1124,7 @@ ClockLatency PulsePlayback::getClockLatency() struct PulseCapture final : public BackendBase { - PulseCapture(ALCdevice *device) noexcept : BackendBase{device} { } + PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~PulseCapture() override; void streamStateCallback(pa_stream *stream) noexcept; @@ -1505,7 +1526,7 @@ std::string PulseBackendFactory::probe(BackendType type) 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/external/openal/alc/backends/pulseaudio.h b/external/openal/alc/backends/pulseaudio.h index 86b8150217..6690fe8a9f 100644 --- a/external/openal/alc/backends/pulseaudio.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/sdl2.cpp b/external/openal/alc/backends/sdl2.cpp index 084b51c059..fe8ece3180 100644 --- a/external/openal/alc/backends/sdl2.cpp +++ b/external/openal/alc/backends/sdl2.cpp @@ -20,16 +20,15 @@ #include "config.h" -#include "backends/sdl2.h" +#include "sdl2.h" #include #include #include #include -#include "alcmain.h" #include "almalloc.h" -#include "alu.h" +#include "core/device.h" #include "core/logging.h" #include @@ -46,7 +45,7 @@ namespace { 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; void audioCallback(Uint8 *stream, int len) noexcept; @@ -106,32 +105,34 @@ void Sdl2Backend::open(const char *name) /* 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, + 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) + if(!devid) throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()}; - mDevice->Frequency = static_cast(have.freq); - + DevFmtChannels devchans{}; if(have.channels == 1) - mDevice->FmtChans = DevFmtMono; + devchans = DevFmtMono; else if(have.channels == 2) - mDevice->FmtChans = DevFmtStereo; + devchans = DevFmtStereo; else + { + 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; @@ -141,17 +142,20 @@ void Sdl2Backend::open(const char *name) case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break; case AUDIO_F32SYS: mDevice->FmtType = 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 = FrameSizeFromDevFmt(devchans, devtype, 0); + mFrequency = static_cast(have.freq); + mFmtChans = devchans; + mFmtType = devtype; + mUpdateSize = have.samples; mDevice->DeviceName = name ? name : defaultDeviceName; } @@ -162,7 +166,7 @@ bool Sdl2Backend::reset() mDevice->FmtChans = mFmtChans; mDevice->FmtType = mFmtType; mDevice->UpdateSize = mUpdateSize; - mDevice->BufferSize = mUpdateSize * 2; + mDevice->BufferSize = mUpdateSize * 2; /* SDL always (tries to) use two periods. */ setDefaultWFXChannelOrder(); return true; } @@ -208,7 +212,7 @@ std::string SDL2BackendFactory::probe(BackendType type) 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/external/openal/alc/backends/sdl2.h b/external/openal/alc/backends/sdl2.h index ce79d52edd..3bd8df8633 100644 --- a/external/openal/alc/backends/sdl2.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/sndio.cpp b/external/openal/alc/backends/sndio.cpp index 41bdb73b1f..2d8b424c98 100644 --- a/external/openal/alc/backends/sndio.cpp +++ b/external/openal/alc/backends/sndio.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/sndio.h" +#include "sndio.h" #include #include @@ -29,8 +29,9 @@ #include #include -#include "alcmain.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" @@ -45,7 +46,7 @@ static const char sndio_device[] = "SndIO Default"; struct SndioPlayback final : public BackendBase { - SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~SndioPlayback() override; int mixerProc(); @@ -122,10 +123,14 @@ void SndioPlayback::open(const char *name) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; - mSndHandle = sio_open(nullptr, SIO_PLAY, 0); - if(mSndHandle == nullptr) + 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; } @@ -288,7 +293,7 @@ void SndioPlayback::stop() struct SndioCapture final : public BackendBase { - SndioCapture(ALCdevice *device) noexcept : BackendBase{device} { } + SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~SndioCapture() override; int recordProc(); @@ -507,7 +512,7 @@ std::string SndIOBackendFactory::probe(BackendType type) 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/external/openal/alc/backends/sndio.h b/external/openal/alc/backends/sndio.h index 83d02906cf..d943319117 100644 --- a/external/openal/alc/backends/sndio.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/solaris.cpp b/external/openal/alc/backends/solaris.cpp index a5bb45b05d..26f5a5c5cc 100644 --- a/external/openal/alc/backends/solaris.cpp +++ b/external/openal/alc/backends/solaris.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/solaris.h" +#include "solaris.h" #include #include @@ -39,11 +39,10 @@ #include #include -#include "alcmain.h" #include "albyte.h" -#include "alu.h" -#include "alconfig.h" -#include "compat.h" +#include "alc/alconfig.h" +#include "core/device.h" +#include "core/helpers.h" #include "core/logging.h" #include "threads.h" #include "vector.h" @@ -59,7 +58,7 @@ 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(); @@ -148,11 +147,15 @@ void SolarisBackend::open(const char *name) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; - mFd = ::open(solaris_driver.c_str(), O_WRONLY); - if(mFd == -1) + 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; } @@ -291,7 +294,7 @@ std::string SolarisBackendFactory::probe(BackendType type) 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/external/openal/alc/backends/solaris.h b/external/openal/alc/backends/solaris.h index 4a9e505b0d..5da6ad3a95 100644 --- a/external/openal/alc/backends/solaris.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/wasapi.cpp b/external/openal/alc/backends/wasapi.cpp index 0786a7d7c6..d38fc25dec 100644 --- a/external/openal/alc/backends/wasapi.cpp +++ b/external/openal/alc/backends/wasapi.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/wasapi.h" +#include "wasapi.h" #define WIN32_LEAN_AND_MEAN #include @@ -56,10 +56,11 @@ #include #include "albit.h" -#include "alcmain.h" -#include "alu.h" -#include "compat.h" -#include "converter.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 "strutils.h" @@ -129,69 +130,6 @@ inline uint RefTime2Samples(const ReferenceTime &val, uint srate) } -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(mPtr) - mPtr->Release(); - mPtr = rhs.mPtr; - rhs.mPtr = nullptr; - return *this; - } - - 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 - { - T *ret{mPtr}; - mPtr = nullptr; - return ret; - } - - void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); } - void swap(ComPtr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); } -}; - - class GuidPrinter { char mMsg[64]; @@ -238,10 +176,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; @@ -297,26 +234,26 @@ NameGUIDPair get_device_name_and_guid(IMMDevice *device) return std::make_pair(std::move(name), std::move(guid)); } -void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor) +EndpointFormFactor get_device_formfactor(IMMDevice *device) { ComPtr ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr()); + 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); + return formfactor; } @@ -484,26 +421,27 @@ void TraceFormat(const char *msg, const WAVEFORMATEX *format) enum class MsgType { OpenDevice, + ReopenDevice, ResetDevice, StartDevice, StopDevice, CloseDevice, EnumeratePlayback, EnumerateCapture, - QuitThread, - Count + Count, + QuitThread = Count }; 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" }; @@ -511,7 +449,7 @@ constexpr char MessageStr[static_cast(MsgType::Count)][20]{ struct WasapiProxy { virtual ~WasapiProxy() = default; - virtual HRESULT openProxy() = 0; + virtual HRESULT openProxy(const char *name) = 0; virtual void closeProxy() = 0; virtual HRESULT resetProxy() = 0; @@ -521,19 +459,22 @@ struct WasapiProxy { struct Msg { MsgType mType; WasapiProxy *mProxy; + const char *mParam; std::promise mPromise; + + 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)}); + mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)}); } mMsgQueueCond.notify_one(); return future; @@ -545,19 +486,19 @@ struct WasapiProxy { std::future future{promise.get_future()}; { std::lock_guard _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, nullptr, std::move(promise)}); + 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}; mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();}); - msg = std::move(mMsgQueue.front()); + Msg msg{std::move(mMsgQueue.front())}; mMsgQueue.pop_front(); - return msg.mType != MsgType::QuitThread; + return msg; } static int messageHandler(std::promise *promise); @@ -597,12 +538,11 @@ int WasapiProxy::messageHandler(std::promise *promise) TRACE("Starting message loop\n"); uint deviceCount{0}; - Msg msg; - while(popMessage(msg)) + while(Msg msg{popMessage()}) { - TRACE("Got message \"%s\" (0x%04x, this=%p)\n", + TRACE("Got message \"%s\" (0x%04x, this=%p, param=%p)\n", MessageStr[static_cast(msg.mType)], static_cast(msg.mType), - decltype(std::declval()){msg.mProxy}); + static_cast(msg.mProxy), static_cast(msg.mParam)); switch(msg.mType) { @@ -611,7 +551,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)) @@ -621,6 +561,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); @@ -669,11 +614,11 @@ int WasapiProxy::messageHandler(std::promise *promise) 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"); @@ -682,13 +627,13 @@ 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(); void open(const char *name) override; - HRESULT openProxy() override; + HRESULT openProxy(const char *name) override; void closeProxy() override; bool reset() override; @@ -700,8 +645,6 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { ClockLatency getClockLatency() override; - std::wstring mDevId; - HRESULT mOpenStatus{E_FAIL}; ComPtr mMMDev{nullptr}; ComPtr mClient{nullptr}; @@ -796,83 +739,81 @@ 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; - } - - if(SUCCEEDED(hr)) - { - if(name) + mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if(mNotifyEvent == nullptr) { - if(PlaybackDevices.empty()) - pushMessage(MsgType::EnumeratePlayback).wait(); - + ERR("Failed to create notify events: %lu\n", GetLastError()); 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()) - { - 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); - else - { - mDevId = iter->devid; - mDevice->DeviceName = iter->name; - hr = S_OK; - } } } if(SUCCEEDED(hr)) - hr = pushMessage(MsgType::OpenDevice).get(); - mOpenStatus = hr; + { + if(name && PlaybackDevices.empty()) + pushMessage(MsgType::EnumeratePlayback).wait(); + + if(SUCCEEDED(mOpenStatus)) + hr = pushMessage(MsgType::ReopenDevice, name).get(); + else + { + hr = pushMessage(MsgType::OpenDevice, name).get(); + mOpenStatus = hr; + } + } if(FAILED(hr)) - { - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; - - mDevId.clear(); - throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", hr}; - } } -HRESULT WasapiPlayback::openProxy() +HRESULT WasapiPlayback::openProxy(const char *name) { + const wchar_t *devid{nullptr}; + if(name) + { + 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(); + } + void *ptr; + ComPtr mmdev; HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr)}; if(SUCCEEDED(hr)) { ComPtr enumerator{static_cast(ptr)}; - if(mDevId.empty()) - hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mMMDev.getPtr()); + if(!devid) + hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mmdev.getPtr()); else - hr = enumerator->GetDevice(mDevId.c_str(), mMMDev.getPtr()); + hr = enumerator->GetDevice(devid, mmdev.getPtr()); } - if(SUCCEEDED(hr)) - hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); - if(SUCCEEDED(hr)) + if(FAILED(hr)) { - mClient = ComPtr{static_cast(ptr)}; - if(mDevice->DeviceName.empty()) - mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first; + WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + return hr; } - if(FAILED(hr)) - mMMDev = nullptr; + mClient = nullptr; + mMMDev = std::move(mmdev); + if(name) mDevice->DeviceName = name; + else mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first; return hr; } @@ -1105,8 +1046,7 @@ HRESULT WasapiPlayback::resetProxy() } mFrameStep = OutputType.Format.nChannels; - EndpointFormFactor formfactor{UnknownFormFactor}; - get_device_formfactor(mMMDev.get(), &formfactor); + const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())}; mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo && (formfactor == Headphones || formfactor == Headset)); @@ -1220,13 +1160,13 @@ ClockLatency WasapiPlayback::getClockLatency() struct WasapiCapture final : public BackendBase, WasapiProxy { - WasapiCapture(ALCdevice *device) noexcept : BackendBase{device} { } + WasapiCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~WasapiCapture() override; int recordProc(); void open(const char *name) override; - HRESULT openProxy() override; + HRESULT openProxy(const char *name) override; void closeProxy() override; HRESULT resetProxy() override; @@ -1238,8 +1178,6 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { void captureSamples(al::byte *buffer, uint samples) override; uint availableSamples() override; - std::wstring mDevId; - HRESULT mOpenStatus{E_FAIL}; ComPtr mMMDev{nullptr}; ComPtr mClient{nullptr}; @@ -1373,48 +1311,15 @@ void WasapiCapture::open(const char *name) if(SUCCEEDED(hr)) { - 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()) - { - 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); - else - { - mDevId = iter->devid; - mDevice->DeviceName = iter->name; - hr = S_OK; - } - } + if(name && CaptureDevices.empty()) + pushMessage(MsgType::EnumerateCapture).wait(); + 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(); - throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", hr}; - } hr = pushMessage(MsgType::ResetDevice).get(); if(FAILED(hr)) @@ -1425,30 +1330,50 @@ void WasapiCapture::open(const char *name) } } -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)) { ComPtr enumerator{static_cast(ptr)}; - if(mDevId.empty()) + if(!devid) hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr()); else - hr = enumerator->GetDevice(mDevId.c_str(), mMMDev.getPtr()); + hr = enumerator->GetDevice(devid, mMMDev.getPtr()); } - if(SUCCEEDED(hr)) - hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); - if(SUCCEEDED(hr)) + if(FAILED(hr)) { - mClient = ComPtr{static_cast(ptr)}; - if(mDevice->DeviceName.empty()) - mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first; + WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + return hr; } - if(FAILED(hr)) - mMMDev = nullptr; + mClient = nullptr; + if(name) mDevice->DeviceName = name; + else mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first; return hr; } @@ -1567,7 +1492,7 @@ HRESULT WasapiCapture::resetProxy() CoTaskMemFree(wfx); wfx = nullptr; - auto validate_fmt = [](ALCdevice *device, uint32_t chancount, DWORD chanmask) noexcept + auto validate_fmt = [](DeviceBase *device, uint32_t chancount, DWORD chanmask) noexcept -> bool { switch(device->FmtChans) @@ -1832,7 +1757,7 @@ std::string WasapiBackendFactory::probe(BackendType type) 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/external/openal/alc/backends/wasapi.h b/external/openal/alc/backends/wasapi.h index 97143c1ace..bb2671ee84 100644 --- a/external/openal/alc/backends/wasapi.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/wave.cpp b/external/openal/alc/backends/wave.cpp index 4f73823063..259cbc62bf 100644 --- a/external/openal/alc/backends/wave.cpp +++ b/external/openal/alc/backends/wave.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/wave.h" +#include "wave.h" #include #include @@ -35,12 +35,11 @@ #include "albit.h" #include "albyte.h" -#include "alcmain.h" -#include "alconfig.h" +#include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" -#include "alu.h" -#include "compat.h" +#include "core/device.h" +#include "core/helpers.h" #include "core/logging.h" #include "opthelpers.h" #include "strutils.h" @@ -93,7 +92,7 @@ void fwrite32le(uint val, FILE *f) struct WaveBackend final : public BackendBase { - WaveBackend(ALCdevice *device) noexcept : BackendBase{device} { } + WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { } ~WaveBackend() override; int mixerProc(); @@ -132,8 +131,8 @@ int WaveBackend::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(); @@ -214,9 +213,12 @@ void WaveBackend::open(const char *name) 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)}; mFile = _wfopen(wname.c_str(), L"wb"); } #else @@ -392,7 +394,7 @@ std::string WaveBackendFactory::probe(BackendType type) 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/external/openal/alc/backends/wave.h b/external/openal/alc/backends/wave.h index 5719961fe9..e768d33617 100644 --- a/external/openal/alc/backends/wave.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/backends/winmm.cpp b/external/openal/alc/backends/winmm.cpp index 9b88f12ebb..42aee31389 100644 --- a/external/openal/alc/backends/winmm.cpp +++ b/external/openal/alc/backends/winmm.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/winmm.h" +#include "winmm.h" #include #include @@ -38,9 +38,9 @@ #include #include -#include "alcmain.h" -#include "alu.h" -#include "compat.h" +#include "alnumeric.h" +#include "core/device.h" +#include "core/helpers.h" #include "core/logging.h" #include "ringbuffer.h" #include "strutils.h" @@ -125,7 +125,7 @@ void ProbeCaptureDevices(void) struct WinMMPlayback final : public BackendBase { - WinMMPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~WinMMPlayback() override; void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept; @@ -224,27 +224,28 @@ void WinMMPlayback::open(const char *name) auto DeviceID = static_cast(std::distance(PlaybackDevices.cbegin(), iter)); retry_open: - mFormat = WAVEFORMATEX{}; + WAVEFORMATEX format{}; if(mDevice->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; + format.wFormatTag = WAVE_FORMAT_PCM; if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte) - mFormat.wBitsPerSample = 8; + format.wBitsPerSample = 8; else - mFormat.wBitsPerSample = 16; + format.wBitsPerSample = 16; } - mFormat.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); - mFormat.nBlockAlign = static_cast(mFormat.wBitsPerSample * mFormat.nChannels / 8); - mFormat.nSamplesPerSec = mDevice->Frequency; - mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign; - mFormat.cbSize = 0; + format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); + format.nBlockAlign = static_cast(format.wBitsPerSample * format.nChannels / 8); + format.nSamplesPerSec = mDevice->Frequency; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.cbSize = 0; - MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &mFormat, + HWAVEOUT outHandle{}; + MMRESULT res{waveOutOpen(&outHandle, DeviceID, &format, reinterpret_cast(&WinMMPlayback::waveOutProcC), reinterpret_cast(this), CALLBACK_FUNCTION)}; if(res != MMSYSERR_NOERROR) @@ -257,6 +258,11 @@ retry_open: throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res}; } + if(mOutHdl) + waveOutClose(mOutHdl); + mOutHdl = outHandle; + mFormat = format; + mDevice->DeviceName = PlaybackDevices[DeviceID]; } @@ -364,7 +370,7 @@ void WinMMPlayback::stop() struct WinMMCapture final : public BackendBase { - WinMMCapture(ALCdevice *device) noexcept : BackendBase{device} { } + WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~WinMMCapture() override; void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept; @@ -615,7 +621,7 @@ std::string WinMMBackendFactory::probe(BackendType type) 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/external/openal/alc/backends/winmm.h b/external/openal/alc/backends/winmm.h index bf09ddd9b5..45a706aa34 100644 --- a/external/openal/alc/backends/winmm.h +++ b/external/openal/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: @@ -11,7 +11,7 @@ public: 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/external/openal/alc/context.cpp b/external/openal/alc/context.cpp new file mode 100644 index 0000000000..9f0b627295 --- /dev/null +++ b/external/openal/alc/context.cpp @@ -0,0 +1,387 @@ + +#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/logging.h" +#include "core/voice.h" +#include "core/voice_change.h" +#include "device.h" +#include "effectslot.h" +#include "ringbuffer.h" +#include "vecmat.h" + + +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_SOFTX_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_SOFTX_filter_gain_ex " + "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_SOFTX_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; + + +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; + } + + count = 0; + VoicePropsItem *vprops{mFreeVoiceProps.exchange(nullptr, std::memory_order_acquire)}; + while(vprops) + { + std::unique_ptr old{vprops}; + vprops = old->next.load(std::memory_order_relaxed); + ++count; + } + TRACE("Freed %zu voice property object%s\n", count, (count==1)?"":"s"); + + delete mVoices.exchange(nullptr, std::memory_order_relaxed); + + count = 0; + ListenerProps *lprops{mParams.ListenerUpdate.exchange(nullptr, std::memory_order_relaxed)}; + if(lprops) + { + ++count; + delete lprops; + } + lprops = mFreeListenerProps.exchange(nullptr, std::memory_order_acquire); + while(lprops) + { + std::unique_ptr old{lprops}; + lprops = old->next.load(std::memory_order_relaxed); + ++count; + } + TRACE("Freed %zu listener property object%s\n", count, (count==1)?"":"s"); + + 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(size_t addcount) +{ + constexpr size_t clustersize{128}; + /* Convert element count to cluster count. */ + addcount = (addcount+(clustersize-1)) / clustersize; + while(addcount) + { + 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(); + --addcount; + } +} + +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; + } +} + + +ALCcontext::ALCcontext(al::intrusive_ptr device) + : ContextBase{device.get()}, mALDevice{std::move(device)} +{ + mPropsDirty.test_and_clear(std::memory_order_relaxed); +} + +ALCcontext::~ALCcontext() +{ + TRACE("Freeing context %p\n", voidp{this}); + + size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), size_t{0u}, + [](size_t 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; + + 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(1); + { + VoiceChange *cur{mVoiceChangeTail}; + while(VoiceChange *next{cur->mNext.load(std::memory_order_relaxed)}) + cur = next; + mCurrentVoiceChange.store(cur, std::memory_order_relaxed); + } + + mExtensionList = alExtList; + + + mParams.Matrix = alu::Matrix::Identity(); + mParams.Velocity = alu::Vector{}; + mParams.Gain = mListener.Gain; + mParams.MetersPerUnit = mListener.mMetersPerUnit; + 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::processUpdates() +{ + std::lock_guard _{mPropLock}; + if(mDeferUpdates.exchange(false, std::memory_order_acq_rel)) + { + /* 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 */ + } + + if(mPropsDirty.test_and_clear(std::memory_order_acq_rel)) + UpdateContextProps(this); + if(mListener.mPropsDirty.test_and_clear(std::memory_order_acq_rel)) + UpdateListenerProps(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); + } +} diff --git a/external/openal/alc/context.h b/external/openal/alc/context.h new file mode 100644 index 0000000000..d0afbdd94a --- /dev/null +++ b/external/openal/alc/context.h @@ -0,0 +1,167 @@ +#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" + +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; + + + al::atomic_invflag mPropsDirty; + std::atomic 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}; + + 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. + */ + void deferUpdates() noexcept { mDeferUpdates.exchange(true, std::memory_order_acq_rel); } + + /** Resumes update processing after being deferred. */ + void processUpdates(); + +#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; + + /* 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; + + /* Default effect that applies to sources that don't have an effect on send 0. */ + static ALeffect sDefaultEffect; + + DEF_NEWDEL(ALCcontext) +}; + +#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; + +#endif /* ALC_CONTEXT_H */ diff --git a/external/openal/alc/device.cpp b/external/openal/alc/device.cpp new file mode 100644 index 0000000000..0f7d2b5750 --- /dev/null +++ b/external/openal/alc/device.cpp @@ -0,0 +1,76 @@ + +#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 + + +/* This should be in core/device.cpp. */ +DeviceBase::DeviceBase(DeviceType type) : Type{type}, mContexts{&sEmptyContextArray} +{ +} + +DeviceBase::~DeviceBase() +{ + auto *oldarray = mContexts.exchange(nullptr, std::memory_order_relaxed); + if(oldarray != &sEmptyContextArray) delete oldarray; +} + + +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(ConfigValueStr(DeviceName.c_str(), nullptr, "hrtf-paths")); + if(auto defhrtfopt = ConfigValueStr(DeviceName.c_str(), 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); + } +} diff --git a/external/openal/alc/device.h b/external/openal/alc/device.h new file mode 100644 index 0000000000..2f465b16a5 --- /dev/null +++ b/external/openal/alc/device.h @@ -0,0 +1,110 @@ +#ifndef ALC_DEVICE_H +#define ALC_DEVICE_H + +#include +#include +#include +#include +#include + +#include "AL/alc.h" +#include "AL/alext.h" + +#include "almalloc.h" +#include "alnumeric.h" +#include "core/device.h" +#include "intrusive_ptr.h" +#include "vector.h" + +struct ALbuffer; +struct ALeffect; +struct ALfilter; + +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 { + 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}; + + ALCenum LimiterState{ALC_DONT_CARE_SOFT}; + + 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; + + + ALCdevice(DeviceType type) : DeviceBase{type} { } + ~ALCdevice(); + + void enumerateHrtfs(); + + DEF_NEWDEL(ALCdevice) +}; + +#endif diff --git a/external/openal/alc/effects/autowah.cpp b/external/openal/alc/effects/autowah.cpp index 2577eb30cb..9c2ec3353e 100644 --- a/external/openal/alc/effects/autowah.cpp +++ b/external/openal/alc/effects/autowah.cpp @@ -20,16 +20,26 @@ #include "config.h" -#include -#include - #include +#include +#include +#include +#include + +#include "alc/effects/base.h" +#include "alc/effectslot.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" +#include "math_defs.h" -#include "alcmain.h" -#include "alcontext.h" -#include "core/filters/biquad.h" -#include "effectslot.h" -#include "vecmat.h" namespace { @@ -69,8 +79,8 @@ struct AutowahState final : public EffectState { alignas(16) float mBufferOut[BufferLineSize]; - void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override; - void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props, + 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; @@ -78,7 +88,7 @@ struct AutowahState final : public EffectState { DEF_NEWDEL(AutowahState) }; -void AutowahState::deviceUpdate(const ALCdevice*, const Buffer&) +void AutowahState::deviceUpdate(const DeviceBase*, const Buffer&) { /* (Re-)initializing parameters and clear the buffers. */ @@ -104,10 +114,10 @@ void AutowahState::deviceUpdate(const ALCdevice*, const Buffer&) } } -void AutowahState::update(const ALCcontext *context, const EffectSlot *slot, +void AutowahState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->mDevice.get()}; + const DeviceBase *device{context->mDevice}; const auto frequency = static_cast(device->Frequency); const float ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)}; diff --git a/external/openal/alc/effects/base.h b/external/openal/alc/effects/base.h index b482bae227..1fb339aa05 100644 --- a/external/openal/alc/effects/base.h +++ b/external/openal/alc/effects/base.h @@ -1,24 +1,37 @@ #ifndef EFFECTS_BASE_H #define EFFECTS_BASE_H -#include +#include #include "albyte.h" -#include "alcmain.h" #include "almalloc.h" #include "alspan.h" #include "atomic.h" +#include "core/bufferline.h" #include "intrusive_ptr.h" -struct EffectSlot; 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}; @@ -175,8 +188,8 @@ struct EffectState : public al::intrusive_ref { virtual ~EffectState() = default; - virtual void deviceUpdate(const ALCdevice *device, const Buffer &buffer) = 0; - virtual void update(const ALCcontext *context, const EffectSlot *slot, + 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; diff --git a/external/openal/alc/effects/chorus.cpp b/external/openal/alc/effects/chorus.cpp index 365eaf332e..3a1b9ae4d6 100644 --- a/external/openal/alc/effects/chorus.cpp +++ b/external/openal/alc/effects/chorus.cpp @@ -21,20 +21,24 @@ #include "config.h" #include +#include #include -#include #include #include -#include "alcmain.h" -#include "alcontext.h" +#include "alc/effects/base.h" +#include "alc/effectslot.h" #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" -#include "alu.h" -#include "core/ambidefs.h" -#include "effects/base.h" -#include "effectslot.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "core/resampler_limits.h" +#include "intrusive_ptr.h" #include "math_defs.h" #include "opthelpers.h" #include "vector.h" @@ -42,6 +46,8 @@ namespace { +using uint = unsigned int; + #define MAX_UPDATE_SAMPLES 256 struct ChorusState final : public EffectState { @@ -68,8 +74,8 @@ struct ChorusState final : public EffectState { void getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo); void getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo); - void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override; - void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props, + 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; @@ -77,9 +83,9 @@ struct ChorusState final : public EffectState { DEF_NEWDEL(ChorusState) }; -void ChorusState::deviceUpdate(const ALCdevice *Device, const Buffer&) +void ChorusState::deviceUpdate(const DeviceBase *Device, const Buffer&) { - constexpr float max_delay{maxf(AL_CHORUS_MAX_DELAY, AL_FLANGER_MAX_DELAY)}; + 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)}; @@ -94,7 +100,7 @@ void ChorusState::deviceUpdate(const ALCdevice *Device, const Buffer&) } } -void ChorusState::update(const ALCcontext *Context, const EffectSlot *Slot, +void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot, const EffectProps *props, const EffectTarget target) { constexpr int mindelay{(MaxResamplerPadding>>1) << MixerFracBits}; @@ -102,7 +108,7 @@ void ChorusState::update(const ALCcontext *Context, const EffectSlot *Slot, /* 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->mDevice.get()}; + const DeviceBase *device{Context->mDevice}; const auto frequency = static_cast(device->Frequency); mWaveform = props->Chorus.Waveform; @@ -247,7 +253,7 @@ void ChorusState::process(const size_t samplesToDo, const al::span #include +#include +#include -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" -#include "effectslot.h" -#include "vecmat.h" +#include "alc/effects/base.h" +#include "alc/effectslot.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/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" + +struct ContextBase; namespace { @@ -49,8 +73,8 @@ struct CompressorState final : public EffectState { float mEnvFollower{1.0f}; - void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override; - void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props, + 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; @@ -58,7 +82,7 @@ struct CompressorState final : public EffectState { DEF_NEWDEL(CompressorState) }; -void CompressorState::deviceUpdate(const ALCdevice *device, const Buffer&) +void CompressorState::deviceUpdate(const DeviceBase *device, const Buffer&) { /* Number of samples to do a full attack and release (non-integer sample * counts are okay). @@ -73,7 +97,7 @@ void CompressorState::deviceUpdate(const ALCdevice *device, const Buffer&) mReleaseMult = std::pow(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount); } -void CompressorState::update(const ALCcontext*, const EffectSlot *slot, +void CompressorState::update(const ContextBase*, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) { mEnabled = props->Compressor.OnOff; diff --git a/external/openal/alc/effects/convolution.cpp b/external/openal/alc/effects/convolution.cpp index 22311bbb74..ca5a732142 100644 --- a/external/openal/alc/effects/convolution.cpp +++ b/external/openal/alc/effects/convolution.cpp @@ -1,7 +1,15 @@ #include "config.h" +#include +#include +#include +#include +#include +#include +#include #include +#include #ifdef HAVE_SSE_INTRINSICS #include @@ -9,21 +17,26 @@ #include #endif -#include "alcmain.h" +#include "albyte.h" #include "alcomplex.h" -#include "alcontext.h" +#include "alc/effectslot.h" #include "almalloc.h" +#include "alnumeric.h" #include "alspan.h" -#include "bformatdec.h" -#include "buffer_storage.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/filters/splitter.h" #include "core/fmt_traits.h" -#include "core/logging.h" -#include "effects/base.h" -#include "effectslot.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" #include "math_defs.h" #include "polyphase_resampler.h" +#include "vector.h" namespace { @@ -190,8 +203,8 @@ struct ConvolutionState final : public EffectState { void (ConvolutionState::*mMix)(const al::span,const size_t) {&ConvolutionState::NormalMix}; - void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override; - void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props, + 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; @@ -219,7 +232,7 @@ void ConvolutionState::UpsampleMix(const al::span samplesOut, } -void ConvolutionState::deviceUpdate(const ALCdevice *device, const Buffer &buffer) +void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buffer) { constexpr uint MaxConvolveAmbiOrder{1u}; @@ -316,7 +329,7 @@ void ConvolutionState::deviceUpdate(const ALCdevice *device, const Buffer &buffe } -void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot, +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 @@ -374,13 +387,31 @@ void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot, for(auto &chan : *mChans) std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f); const float gain{slot->Gain}; - if(mChannels == FmtBFormat3D || mChannels == FmtBFormat2D) + /* 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) { - ALCdevice *device{context->mDevice.get()}; + 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(mChannels == FmtBFormat3D || mChannels == FmtBFormat2D) + { + DeviceBase *device{context->mDevice}; if(device->mAmbiOrder > mAmbiOrder) { mMix = &ConvolutionState::UpsampleMix; - const auto scales = BFormatDec::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder); + 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]; @@ -403,7 +434,7 @@ void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot, } else { - ALCdevice *device{context->mDevice.get()}; + DeviceBase *device{context->mDevice}; al::span chanmap{}; switch(mChannels) { @@ -416,6 +447,9 @@ void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot, case FmtX71: chanmap = X71Map; break; case FmtBFormat2D: case FmtBFormat3D: + case FmtUHJ2: + case FmtUHJ3: + case FmtUHJ4: break; } diff --git a/external/openal/alc/effects/dedicated.cpp b/external/openal/alc/effects/dedicated.cpp index 9fee7fd7bb..e7ea89e04a 100644 --- a/external/openal/alc/effects/dedicated.cpp +++ b/external/openal/alc/effects/dedicated.cpp @@ -20,25 +20,35 @@ #include "config.h" -#include -#include #include +#include +#include +#include -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" -#include "effectslot.h" +#include "alc/effects/base.h" +#include "alc/effectslot.h" +#include "almalloc.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" + +struct ContextBase; namespace { +using uint = unsigned int; + struct DedicatedState final : public EffectState { float mCurrentGains[MAX_OUTPUT_CHANNELS]; float mTargetGains[MAX_OUTPUT_CHANNELS]; - void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override; - void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props, + 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; @@ -46,12 +56,12 @@ struct DedicatedState final : public EffectState { DEF_NEWDEL(DedicatedState) }; -void DedicatedState::deviceUpdate(const ALCdevice*, const Buffer&) +void DedicatedState::deviceUpdate(const DeviceBase*, const Buffer&) { std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f); } -void DedicatedState::update(const ALCcontext*, const EffectSlot *slot, +void DedicatedState::update(const ContextBase*, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) { std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f); diff --git a/external/openal/alc/effects/distortion.cpp b/external/openal/alc/effects/distortion.cpp index 09dae4c597..26b4df8ea9 100644 --- a/external/openal/alc/effects/distortion.cpp +++ b/external/openal/alc/effects/distortion.cpp @@ -21,13 +21,24 @@ #include "config.h" #include -#include +#include #include +#include -#include "alcmain.h" -#include "alcontext.h" +#include "alc/effects/base.h" +#include "alc/effectslot.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/filters/biquad.h" -#include "effectslot.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" +#include "math_defs.h" namespace { @@ -45,8 +56,8 @@ struct DistortionState final : public EffectState { float mBuffer[2][BufferLineSize]{}; - void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override; - void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props, + 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; @@ -54,16 +65,16 @@ struct DistortionState final : public EffectState { DEF_NEWDEL(DistortionState) }; -void DistortionState::deviceUpdate(const ALCdevice*, const Buffer&) +void DistortionState::deviceUpdate(const DeviceBase*, const Buffer&) { mLowpass.clear(); mBandpass.clear(); } -void DistortionState::update(const ALCcontext *context, const EffectSlot *slot, +void DistortionState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->mDevice.get()}; + const DeviceBase *device{context->mDevice}; /* Store waveshaper edge settings. */ const float edge{minf(std::sin(al::MathDefs::Pi()*0.5f * props->Distortion.Edge), diff --git a/external/openal/alc/effects/echo.cpp b/external/openal/alc/effects/echo.cpp index f782055f2e..4cdef37cff 100644 --- a/external/openal/alc/effects/echo.cpp +++ b/external/openal/alc/effects/echo.cpp @@ -20,20 +20,32 @@ #include "config.h" -#include -#include - #include +#include +#include +#include +#include -#include "alcmain.h" -#include "alcontext.h" +#include "alc/effects/base.h" +#include "alc/effectslot.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/filters/biquad.h" -#include "effectslot.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 { @@ -57,8 +69,8 @@ struct EchoState final : public EffectState { alignas(16) float mTempBuffer[2][BufferLineSize]; - void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override; - void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props, + 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; @@ -66,7 +78,7 @@ struct EchoState final : public EffectState { DEF_NEWDEL(EchoState) }; -void EchoState::deviceUpdate(const ALCdevice *Device, const Buffer&) +void EchoState::deviceUpdate(const DeviceBase *Device, const Buffer&) { const auto frequency = static_cast(Device->Frequency); @@ -85,10 +97,10 @@ void EchoState::deviceUpdate(const ALCdevice *Device, const Buffer&) } } -void EchoState::update(const ALCcontext *context, const EffectSlot *slot, +void EchoState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->mDevice.get()}; + const DeviceBase *device{context->mDevice}; const auto frequency = static_cast(device->Frequency); mTap[0].delay = maxu(float2uint(props->Echo.Delay*frequency + 0.5f), 1); @@ -148,7 +160,7 @@ void EchoState::process(const size_t samplesToDo, const al::span -#include - #include +#include +#include #include +#include +#include -#include "alcmain.h" -#include "alcontext.h" +#include "alc/effects/base.h" +#include "alc/effectslot.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/filters/biquad.h" -#include "effectslot.h" -#include "vecmat.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" namespace { @@ -90,8 +98,8 @@ struct EqualizerState final : public EffectState { FloatBufferLine mSampleBuffer{}; - void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override; - void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props, + 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; @@ -99,7 +107,7 @@ struct EqualizerState final : public EffectState { DEF_NEWDEL(EqualizerState) }; -void EqualizerState::deviceUpdate(const ALCdevice*, const Buffer&) +void EqualizerState::deviceUpdate(const DeviceBase*, const Buffer&) { for(auto &e : mChans) { @@ -108,10 +116,10 @@ void EqualizerState::deviceUpdate(const ALCdevice*, const Buffer&) } } -void EqualizerState::update(const ALCcontext *context, const EffectSlot *slot, +void EqualizerState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->mDevice.get()}; + const DeviceBase *device{context->mDevice}; auto frequency = static_cast(device->Frequency); float gain, f0norm; diff --git a/external/openal/alc/effects/fshifter.cpp b/external/openal/alc/effects/fshifter.cpp index 4037f46b8b..c25aab825e 100644 --- a/external/openal/alc/effects/fshifter.cpp +++ b/external/openal/alc/effects/fshifter.cpp @@ -20,22 +20,32 @@ #include "config.h" -#include -#include -#include -#include #include +#include +#include +#include +#include +#include -#include "alcmain.h" +#include "alc/effects/base.h" +#include "alc/effectslot.h" #include "alcomplex.h" -#include "alcontext.h" -#include "alu.h" -#include "effectslot.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/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" #include "math_defs.h" namespace { +using uint = unsigned int; using complex_d = std::complex; #define HIL_SIZE 1024 @@ -84,8 +94,8 @@ struct FshifterState final : public EffectState { } mGains[2]; - void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override; - void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props, + 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; @@ -93,7 +103,7 @@ struct FshifterState final : public EffectState { DEF_NEWDEL(FshifterState) }; -void FshifterState::deviceUpdate(const ALCdevice*, const Buffer&) +void FshifterState::deviceUpdate(const DeviceBase*, const Buffer&) { /* (Re-)initializing parameters and clear the buffers. */ mCount = 0; @@ -114,10 +124,10 @@ void FshifterState::deviceUpdate(const ALCdevice*, const Buffer&) } } -void FshifterState::update(const ALCcontext *context, const EffectSlot *slot, +void FshifterState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->mDevice.get()}; + const DeviceBase *device{context->mDevice}; const float step{props->Fshifter.Frequency / static_cast(device->Frequency)}; mPhaseStep[0] = mPhaseStep[1] = fastf2u(minf(step, 1.0f) * MixerFracOne); diff --git a/external/openal/alc/effects/modulator.cpp b/external/openal/alc/effects/modulator.cpp index 267c73e7e3..a518ff6374 100644 --- a/external/openal/alc/effects/modulator.cpp +++ b/external/openal/alc/effects/modulator.cpp @@ -20,21 +20,31 @@ #include "config.h" -#include -#include - -#include #include +#include +#include +#include -#include "alcmain.h" -#include "alcontext.h" +#include "alc/effects/base.h" +#include "alc/effectslot.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" #include "core/filters/biquad.h" -#include "effectslot.h" -#include "vecmat.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" +#include "math_defs.h" namespace { +using uint = unsigned int; + #define MAX_UPDATE_SAMPLES 128 #define WAVEFORM_FRACBITS 24 @@ -81,8 +91,8 @@ struct ModulatorState final : public EffectState { } mChans[MaxAmbiChannels]; - void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override; - void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props, + 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; @@ -90,7 +100,7 @@ struct ModulatorState final : public EffectState { DEF_NEWDEL(ModulatorState) }; -void ModulatorState::deviceUpdate(const ALCdevice*, const Buffer&) +void ModulatorState::deviceUpdate(const DeviceBase*, const Buffer&) { for(auto &e : mChans) { @@ -99,10 +109,10 @@ void ModulatorState::deviceUpdate(const ALCdevice*, const Buffer&) } } -void ModulatorState::update(const ALCcontext *context, const EffectSlot *slot, +void ModulatorState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->mDevice.get()}; + const DeviceBase *device{context->mDevice}; const float step{props->Modulator.Frequency / static_cast(device->Frequency)}; mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1})); diff --git a/external/openal/alc/effects/null.cpp b/external/openal/alc/effects/null.cpp index 9d5892857e..cda1420ee6 100644 --- a/external/openal/alc/effects/null.cpp +++ b/external/openal/alc/effects/null.cpp @@ -1,12 +1,17 @@ #include "config.h" -#include "alcmain.h" -#include "alcontext.h" +#include + #include "almalloc.h" #include "alspan.h" -#include "effects/base.h" -#include "effectslot.h" +#include "base.h" +#include "core/bufferline.h" +#include "intrusive_ptr.h" + +struct ContextBase; +struct DeviceBase; +struct EffectSlot; namespace { @@ -15,8 +20,8 @@ struct NullState final : public EffectState { NullState(); ~NullState() override; - void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override; - void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props, + 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; @@ -39,14 +44,14 @@ NullState::~NullState() = default; * format) have been changed. Will always be followed by a call to the update * method, if successful. */ -void NullState::deviceUpdate(const ALCdevice* /*device*/, const Buffer& /*buffer*/) +void NullState::deviceUpdate(const DeviceBase* /*device*/, const Buffer& /*buffer*/) { } /* 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* /*context*/, const EffectSlot* /*slot*/, +void NullState::update(const ContextBase* /*context*/, const EffectSlot* /*slot*/, const EffectProps* /*props*/, const EffectTarget /*target*/) { } diff --git a/external/openal/alc/effects/pshifter.cpp b/external/openal/alc/effects/pshifter.cpp index 5087860fea..261156052e 100644 --- a/external/openal/alc/effects/pshifter.cpp +++ b/external/openal/alc/effects/pshifter.cpp @@ -20,23 +20,33 @@ #include "config.h" -#include -#include -#include -#include #include +#include +#include +#include +#include +#include -#include "alcmain.h" +#include "alc/effects/base.h" +#include "alc/effectslot.h" #include "alcomplex.h" -#include "alcontext.h" +#include "almalloc.h" #include "alnumeric.h" -#include "alu.h" -#include "effectslot.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" #include "math_defs.h" +struct ContextBase; + namespace { +using uint = unsigned int; using complex_d = std::complex; #define STFT_SIZE 1024 @@ -93,8 +103,8 @@ struct PshifterState final : public EffectState { float mTargetGains[MAX_OUTPUT_CHANNELS]; - void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override; - void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props, + 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; @@ -102,7 +112,7 @@ struct PshifterState final : public EffectState { DEF_NEWDEL(PshifterState) }; -void PshifterState::deviceUpdate(const ALCdevice*, const Buffer&) +void PshifterState::deviceUpdate(const DeviceBase*, const Buffer&) { /* (Re-)initializing parameters and clear the buffers. */ mCount = 0; @@ -122,7 +132,7 @@ void PshifterState::deviceUpdate(const ALCdevice*, const Buffer&) std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f); } -void PshifterState::update(const ALCcontext*, const EffectSlot *slot, +void PshifterState::update(const ContextBase*, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) { const int tune{props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune}; diff --git a/external/openal/alc/effects/reverb.cpp b/external/openal/alc/effects/reverb.cpp index a519fc345e..d6f1dbbf04 100644 --- a/external/openal/alc/effects/reverb.cpp +++ b/external/openal/alc/effects/reverb.cpp @@ -20,23 +20,33 @@ #include "config.h" -#include -#include -#include - -#include -#include #include +#include +#include #include +#include +#include +#include -#include "alcmain.h" -#include "alcontext.h" +#include "alc/effects/base.h" +#include "alc/effectslot.h" +#include "almalloc.h" #include "alnumeric.h" -#include "bformatdec.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/filters/biquad.h" -#include "effectslot.h" -#include "vector.h" +#include "core/filters/splitter.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" +#include "math_defs.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. @@ -45,6 +55,11 @@ 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< samplesIn, const al::span samplesOut) override; @@ -556,16 +571,17 @@ void ReverbState::allocLines(const float frequency) /* Multiplier for the maximum density value, i.e. density=1, which is * actually the least density... */ - const float 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 (BufferLineSize) for block processing. */ - float 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())/float{NUM_LINES}*multiplier}; + 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. */ @@ -584,7 +600,7 @@ void ReverbState::allocLines(const float frequency) * time and depth coefficient, and halfed for the low-to-high frequency * swing. */ - constexpr float max_mod_delay{AL_EAXREVERB_MAX_MODULATION_TIME*MODULATION_DEPTH_COEFF / 2.0f}; + constexpr float max_mod_delay{MaxModulationTime*MODULATION_DEPTH_COEFF / 2.0f}; /* The late delay lines are calculated from the largest maximum density * line length, and the maximum modulation delay. An additional sample is @@ -607,18 +623,18 @@ void ReverbState::allocLines(const float frequency) mLate.Delay.realizeLineOffset(mSampleBuffer.data()); } -void ReverbState::deviceUpdate(const ALCdevice *device, const Buffer&) +void ReverbState::deviceUpdate(const DeviceBase *device, const Buffer&) { const auto frequency = static_cast(device->Frequency); /* Allocate the delay lines. */ allocLines(frequency); - const float 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 = float2uint( - (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). @@ -665,7 +681,7 @@ void ReverbState::deviceUpdate(const ALCdevice *device, const Buffer&) if(device->mAmbiOrder > 1) { mMixOut = &ReverbState::MixOutAmbiUp; - mOrderScales = BFormatDec::GetHFOrderScales(1, device->mAmbiOrder); + mOrderScales = AmbiScale::GetHFOrderScales(1, device->mAmbiOrder); } else { @@ -813,15 +829,14 @@ void Modulation::updateModulator(float modTime, float modDepth, float frequency) * (half of it is spent decreasing the frequency, half is spent increasing * it). */ - if(modTime >= AL_EAXREVERB_DEFAULT_MODULATION_TIME) + 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 * AL_EAXREVERB_DEFAULT_MODULATION_TIME * - modDepth * frequency; + Depth[1] = MODULATION_DEPTH_COEFF / 4.0f * DefaultModulationTime * modDepth * frequency; } else Depth[1] = MODULATION_DEPTH_COEFF / 4.0f * modTime * modDepth * frequency; @@ -835,7 +850,8 @@ void LateReverb::updateLines(const float density_mult, const float diffusion, /* Scaling factor to convert the normalized reference frequencies from * representing 0...freq to 0...max_reference. */ - const float norm_weight_factor{frequency / AL_EAXREVERB_MAX_HFREFERENCE}; + constexpr float MaxHFReference{20000.0f}; + const float norm_weight_factor{frequency / MaxHFReference}; const float late_allpass_avg{ std::accumulate(LATE_ALLPASS_LENGTHS.begin(), LATE_ALLPASS_LENGTHS.end(), 0.0f) / @@ -985,10 +1001,10 @@ void ReverbState::update3DPanning(const float *ReflectionsPan, const float *Late } } -void ReverbState::update(const ALCcontext *Context, const EffectSlot *Slot, +void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, const EffectProps *props, const EffectTarget target) { - const ALCdevice *Device{Context->mDevice.get()}; + const DeviceBase *Device{Context->mDevice}; const auto frequency = static_cast(Device->Frequency); /* Calculate the master filters */ @@ -1024,10 +1040,10 @@ void ReverbState::update(const ALCcontext *Context, const EffectSlot *Slot, props->Reverb.DecayTime); /* Calculate the LF/HF decay times. */ - const float lfDecayTime{clampf(props->Reverb.DecayTime * props->Reverb.DecayLFRatio, - AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME)}; - const float 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, diff --git a/external/openal/alc/effects/vmorpher.cpp b/external/openal/alc/effects/vmorpher.cpp index ab21439c7f..6c419ba28b 100644 --- a/external/openal/alc/effects/vmorpher.cpp +++ b/external/openal/alc/effects/vmorpher.cpp @@ -1,39 +1,62 @@ /** - * OpenAL cross platform audio library + * This file is part of the OpenAL Soft cross platform audio library + * * Copyright (C) 2019 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: * - * 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 + * * 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 +#include -#include "alcmain.h" -#include "alcontext.h" -#include "alu.h" -#include "effectslot.h" +#include "alc/effects/base.h" +#include "alc/effectslot.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" #include "math_defs.h" namespace { +using uint = unsigned int; + #define MAX_UPDATE_SAMPLES 256 #define NUM_FORMANTS 4 #define NUM_FILTERS 2 @@ -138,8 +161,8 @@ struct VmorpherState final : public EffectState { alignas(16) float mSampleBufferB[MAX_UPDATE_SAMPLES]{}; alignas(16) float mLfo[MAX_UPDATE_SAMPLES]{}; - void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override; - void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props, + 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; @@ -202,7 +225,7 @@ std::array VmorpherState::getFiltersByPhoneme(VMorpherPhenome p } -void VmorpherState::deviceUpdate(const ALCdevice*, const Buffer&) +void VmorpherState::deviceUpdate(const DeviceBase*, const Buffer&) { for(auto &e : mChans) { @@ -214,10 +237,10 @@ void VmorpherState::deviceUpdate(const ALCdevice*, const Buffer&) } } -void VmorpherState::update(const ALCcontext *context, const EffectSlot *slot, +void VmorpherState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) { - const ALCdevice *device{context->mDevice.get()}; + 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})); diff --git a/external/openal/alc/effectslot.cpp b/external/openal/alc/effectslot.cpp index 8abac2480f..21084f395c 100644 --- a/external/openal/alc/effectslot.cpp +++ b/external/openal/alc/effectslot.cpp @@ -5,8 +5,8 @@ #include -#include "alcontext.h" #include "almalloc.h" +#include "context.h" EffectSlotArray *EffectSlot::CreatePtrArray(size_t count) noexcept diff --git a/external/openal/alc/effectslot.h b/external/openal/alc/effectslot.h index c1eb1cc3db..cbb1a2f552 100644 --- a/external/openal/alc/effectslot.h +++ b/external/openal/alc/effectslot.h @@ -1,14 +1,13 @@ #ifndef EFFECTSLOT_H #define EFFECTSLOT_H -#include +#include #include "almalloc.h" -#include "alcmain.h" +#include "core/device.h" #include "effects/base.h" #include "intrusive_ptr.h" - struct EffectSlot; struct WetBuffer; diff --git a/external/openal/alc/inprogext.h b/external/openal/alc/inprogext.h index ea27a531c6..781ccfd3a7 100644 --- a/external/openal/alc/inprogext.h +++ b/external/openal/alc/inprogext.h @@ -66,6 +66,34 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint * #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_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 +#endif + +#ifndef AL_SOFT_hold_on_disconnect +#define AL_SOFT_hold_on_disconnect +#define AL_STOP_SOURCES_ON_DISCONNECT_SOFT 0x19AB +#endif + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/external/openal/alc/panning.cpp b/external/openal/alc/panning.cpp index 1ac3bb044f..a057224fb0 100644 --- a/external/openal/alc/panning.cpp +++ b/external/openal/alc/panning.cpp @@ -38,24 +38,24 @@ #include "AL/alext.h" #include "al/auxeffectslot.h" -#include "alcmain.h" #include "alconfig.h" -#include "alcontext.h" +#include "alc/context.h" #include "almalloc.h" #include "alnumeric.h" #include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "alu.h" -#include "bformatdec.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 "front_stablizer.h" -#include "hrtf.h" +#include "device.h" #include "math_defs.h" #include "opthelpers.h" @@ -486,11 +486,6 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= decoder = X71Config; break; case DevFmtAmbi3D: - break; - } - - if(device->FmtChans == DevFmtAmbi3D) - { const char *devname{device->DeviceName.c_str()}; auto&& acnmap = GetAmbiLayout(device->mAmbiLayout); auto&& n3dscale = GetAmbiScales(device->mAmbiScale); @@ -499,100 +494,95 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= 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}; } - ); + { return BFChannelConfig{1.0f/n3dscale[acn], acn}; }); AllocChannels(device, count, 0); float nfc_delay{ConfigValueFloat(devname, "decoder", "nfc-ref-delay").value_or(0.0f)}; if(nfc_delay > 0.0f) InitNearFieldCtrl(device, nfc_delay * SpeedOfSoundMetersPerSec, device->mAmbiOrder, true); + return; } - else + + const bool dual_band{hqdec && !decoder.mCoeffsLF.empty()}; + al::vector chancoeffs, chancoeffslf; + for(size_t i{0u};i < decoder.mChannels.size();++i) { - const bool dual_band{hqdec && !decoder.mCoeffsLF.empty()}; - al::vector chancoeffs, chancoeffslf; - for(size_t i{0u};i < decoder.mChannels.size();++i) + const uint idx{GetChannelIdxByName(device->RealOut, decoder.mChannels[i])}; + if(idx == INVALID_CHANNEL_INDEX) { - const uint idx{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; - } - - chancoeffs.resize(maxz(chancoeffs.size(), idx+1u), ChannelDec{}); - al::span coeffs{chancoeffs[idx]}; - size_t start{0}; - for(uint o{0};o <= decoder.mOrder;++o) - { - size_t count{o ? 2u : 1u}; - do { - coeffs[start] = decoder.mCoeffs[i][start] * decoder.mOrderGain[o]; - ++start; - } while(--count); - } - if(!dual_band) - continue; - - chancoeffslf.resize(maxz(chancoeffslf.size(), idx+1u), ChannelDec{}); - coeffs = chancoeffslf[idx]; - start = 0; - for(uint o{0};o <= decoder.mOrder;++o) - { - size_t count{o ? 2u : 1u}; - do { - coeffs[start] = decoder.mCoeffsLF[i][start] * decoder.mOrderGainLF[o]; - ++start; - } while(--count); - } + ERR("Failed to find %s channel in device\n", + GetLabelFromChannel(decoder.mChannels[i])); + continue; } - /* For non-DevFmtAmbi3D, set the ambisonic order. */ - device->mAmbiOrder = decoder.mOrder; - - /* Built-in speaker decoders are always 2D. */ - const size_t ambicount{Ambi2DChannelsFromOrder(decoder.mOrder)}; - std::transform(AmbiIndex::FromACN2D().begin(), AmbiIndex::FromACN2D().begin()+ambicount, - std::begin(device->Dry.AmbiMap), - [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); - AllocChannels(device, ambicount, device->channelsFromFmt()); - - std::unique_ptr stablizer; - if(stablize) + chancoeffs.resize(maxz(chancoeffs.size(), idx+1u), ChannelDec{}); + al::span coeffs{chancoeffs[idx]}; + size_t ambichan{0}; + for(uint o{0};o < decoder.mOrder+1;++o) { - /* 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"); - } + const float order_gain{decoder.mOrderGain[o]}; + const size_t order_max{Ambi2DChannelsFromOrder(o)}; + for(;ambichan < order_max;++ambichan) + coeffs[ambichan] = decoder.mCoeffs[i][ambichan] * order_gain; } + if(!dual_band) + continue; - TRACE("Enabling %s-band %s-order%s ambisonic decoder\n", - !dual_band ? "single" : "dual", - (decoder.mOrder > 2) ? "third" : - (decoder.mOrder > 1) ? "second" : "first", - ""); - device->AmbiDecoder = BFormatDec::Create(ambicount, chancoeffs, chancoeffslf, - std::move(stablizer)); + chancoeffslf.resize(maxz(chancoeffslf.size(), idx+1u), ChannelDec{}); + coeffs = chancoeffslf[idx]; + ambichan = 0; + for(uint o{0};o < decoder.mOrder+1;++o) + { + const float order_gain{decoder.mOrderGainLF[o]}; + const size_t order_max{Ambi2DChannelsFromOrder(o)}; + for(;ambichan < order_max;++ambichan) + coeffs[ambichan] = decoder.mCoeffsLF[i][ambichan] * order_gain; + } } + + /* For non-DevFmtAmbi3D, set the ambisonic order. */ + device->mAmbiOrder = decoder.mOrder; + + /* Built-in speaker decoders are always 2D. */ + const size_t ambicount{Ambi2DChannelsFromOrder(decoder.mOrder)}; + std::transform(AmbiIndex::FromACN2D().begin(), AmbiIndex::FromACN2D().begin()+ambicount, + std::begin(device->Dry.AmbiMap), + [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; }); + AllocChannels(device, ambicount, device->channelsFromFmt()); + + std::unique_ptr stablizer; + if(stablize) + { + /* 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"); + } + } + + TRACE("Enabling %s-band %s-order%s ambisonic decoder\n", + !dual_band ? "single" : "dual", + (decoder.mOrder > 2) ? "third" : + (decoder.mOrder > 1) ? "second" : "first", + ""); + device->AmbiDecoder = BFormatDec::Create(ambicount, chancoeffs, chancoeffslf, + std::move(stablizer)); } void InitCustomPanning(ALCdevice *device, const bool hqdec, const bool stablize, @@ -750,7 +740,9 @@ void InitHrtfPanning(ALCdevice *device) static const float AmbiOrderHFGain1O[MaxAmbiOrder+1]{ 2.000000000e+00f, 1.154700538e+00f }, AmbiOrderHFGain2O[MaxAmbiOrder+1]{ - 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 + /*ENRGY 2.357022604e+00f, 1.825741858e+00f, 9.428090416e-01f*/ }; static_assert(al::size(AmbiPoints1O) == al::size(AmbiMatrix1O), "First-Order Ambisonic HRTF mismatch"); @@ -798,7 +790,7 @@ void InitHrtfPanning(ALCdevice *device) ((ambi_order%10) == 2) ? "nd" : ((ambi_order%10) == 3) ? "rd" : "th", (device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "", - device->HrtfName.c_str()); + device->mHrtfName.c_str()); al::span AmbiPoints{AmbiPoints1O}; const float (*AmbiMatrix)[MaxAmbiChannels]{AmbiMatrix1O}; @@ -854,7 +846,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, HrtfRequestMode hrtf_appreq device->mHrtfState = nullptr; device->mHrtf = nullptr; device->mIrSize = 0; - device->HrtfName.clear(); + device->mHrtfName.clear(); device->mXOverFreq = 400.0f; device->mRenderMode = RenderMode::Normal; @@ -862,7 +854,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, HrtfRequestMode hrtf_appreq { old_hrtf = nullptr; if(hrtf_appreq == Hrtf_Enable) - device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; + device->mHrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; const char *layout{nullptr}; switch(device->FmtChans) @@ -943,42 +935,42 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, HrtfRequestMode hrtf_appreq (hrtf_appreq == Hrtf_Enable); if(!usehrtf) goto no_hrtf; - device->HrtfStatus = ALC_HRTF_ENABLED_SOFT; + device->mHrtfStatus = ALC_HRTF_ENABLED_SOFT; if(headphones && hrtf_appreq != Hrtf_Disable) - device->HrtfStatus = ALC_HRTF_HEADPHONES_DETECTED_SOFT; + device->mHrtfStatus = ALC_HRTF_HEADPHONES_DETECTED_SOFT; } else { if(hrtf_userreq != Hrtf_Enable) { if(hrtf_appreq == Hrtf_Enable) - device->HrtfStatus = ALC_HRTF_DENIED_SOFT; + device->mHrtfStatus = ALC_HRTF_DENIED_SOFT; goto no_hrtf; } - device->HrtfStatus = ALC_HRTF_REQUIRED_SOFT; + device->mHrtfStatus = ALC_HRTF_REQUIRED_SOFT; } - if(device->HrtfList.empty()) - device->HrtfList = EnumerateHrtf(device->DeviceName.c_str()); + if(device->mHrtfList.empty()) + device->enumerateHrtfs(); - if(hrtf_id >= 0 && static_cast(hrtf_id) < device->HrtfList.size()) + if(hrtf_id >= 0 && static_cast(hrtf_id) < device->mHrtfList.size()) { - const std::string &hrtfname = device->HrtfList[static_cast(hrtf_id)]; + const std::string &hrtfname = device->mHrtfList[static_cast(hrtf_id)]; if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) { device->mHrtf = std::move(hrtf); - device->HrtfName = hrtfname; + device->mHrtfName = hrtfname; } } if(!device->mHrtf) { - for(const auto &hrtfname : device->HrtfList) + for(const auto &hrtfname : device->mHrtfList) { if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) { device->mHrtf = std::move(hrtf); - device->HrtfName = hrtfname; + device->mHrtfName = hrtfname; break; } } @@ -1000,7 +992,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, HrtfRequestMode hrtf_appreq device->PostProcess = &ALCdevice::ProcessHrtf; return; } - device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; + device->mHrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; no_hrtf: old_hrtf = nullptr; @@ -1034,7 +1026,7 @@ no_hrtf: } if(device->mRenderMode == RenderMode::Normal) { - device->Uhj_Encoder = std::make_unique(); + device->mUhjEncoder = std::make_unique(); TRACE("UHJ enabled\n"); InitUhjPanning(device); device->PostProcess = &ALCdevice::ProcessUhj; @@ -1049,7 +1041,7 @@ no_hrtf: void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context) { - ALCdevice *device{context->mDevice.get()}; + DeviceBase *device{context->mDevice}; const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)}; auto wetbuffer_iter = context->mWetBuffers.end(); @@ -1098,113 +1090,3 @@ void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context) std::fill(iter, slot->Wet.AmbiMap.end(), BFChannelConfig{}); slot->Wet.Buffer = wetbuffer->mBuffer; } - - -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] = 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 */ - 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 + spread/al::MathDefs::Tau())}; - - 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/external/openal/common/albit.h b/external/openal/common/albit.h index 2c83ca08f5..f60e7b1bd9 100644 --- a/external/openal/common/albit.h +++ b/external/openal/common/albit.h @@ -1,6 +1,7 @@ #ifndef AL_BIT_H #define AL_BIT_H +#include #include #include #if !defined(__GNUC__) && (defined(_WIN32) || defined(_WIN64)) @@ -73,6 +74,17 @@ int> countr_zero(T val) noexcept * 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 { @@ -85,17 +97,18 @@ namespace detail_ { template constexpr std::enable_if_t::value && std::is_unsigned::value, -int> popcount(T v) noexcept +int> popcount(T val) noexcept { - constexpr T m55{detail_::repbits(0x55)}; - constexpr T m33{detail_::repbits(0x33)}; - constexpr T m0f{detail_::repbits(0x0f)}; - constexpr T m01{detail_::repbits(0x01)}; + using fast_type = typename detail_::fast_utype::type; + constexpr fast_type m55{detail_::repbits(0x55)}; + constexpr fast_type m33{detail_::repbits(0x33)}; + constexpr fast_type m0f{detail_::repbits(0x0f)}; + constexpr fast_type m01{detail_::repbits(0x01)}; - v = v - ((v >> 1) & m55); + auto v = val - ((fast_type{val} >> 1) & m55); v = (v & m33) + ((v >> 2) & m33); v = (v + (v >> 4)) & m0f; - return static_cast((v * m01) >> ((sizeof(T)-1)*8)); + return static_cast(((v * m01) >> ((sizeof(T)-1)*8)) & 0xff); } #if defined(_WIN64) diff --git a/external/openal/common/alstring.h b/external/openal/common/alstring.h index 6c49719030..f5127aede2 100644 --- a/external/openal/common/alstring.h +++ b/external/openal/common/alstring.h @@ -2,23 +2,10 @@ #define AL_STRING_H #include -#include -#include - -#include "almalloc.h" namespace al { -template> -using basic_string = std::basic_string>; - -using string = basic_string; -using wstring = basic_string; -using u16string = basic_string; -using u32string = basic_string; - - /* These would be better served by using a string_view-like span/view with * case-insensitive char traits. */ diff --git a/external/openal/common/atomic.h b/external/openal/common/atomic.h index 5e9b04c697..d70382ceae 100644 --- a/external/openal/common/atomic.h +++ b/external/openal/common/atomic.h @@ -2,8 +2,29 @@ #define AL_ATOMIC_H #include +#include +namespace al { + +struct atomic_invflag : protected std::atomic_flag { + atomic_invflag() noexcept = default; + template + atomic_invflag(T&& arg) noexcept : std::atomic_flag{std::forward(arg)} { } + + inline bool test_and_clear(std::memory_order m=std::memory_order_seq_cst) noexcept + { return !test_and_set(m); } + inline bool test_and_clear(std::memory_order m=std::memory_order_seq_cst) volatile noexcept + { return !test_and_set(m); } + + inline void set(std::memory_order m=std::memory_order_seq_cst) noexcept + { clear(m); } + inline void set(std::memory_order m=std::memory_order_seq_cst) volatile noexcept + { clear(m); } +}; + +} // namespace al + using RefCount = std::atomic; inline void InitRef(RefCount &ref, unsigned int value) diff --git a/external/openal/common/comptr.h b/external/openal/common/comptr.h new file mode 100644 index 0000000000..c238991a2c --- /dev/null +++ b/external/openal/common/comptr.h @@ -0,0 +1,70 @@ +#ifndef COMMON_COMPTR_H +#define COMMON_COMPTR_H + +#include +#include + + +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(mPtr) + mPtr->Release(); + mPtr = rhs.mPtr; + rhs.mPtr = nullptr; + return *this; + } + + 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 + { + T *ret{mPtr}; + mPtr = nullptr; + return ret; + } + + void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); } + void swap(ComPtr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); } +}; + +#endif diff --git a/external/openal/common/phase_shifter.h b/external/openal/common/phase_shifter.h new file mode 100644 index 0000000000..18ab34c707 --- /dev/null +++ b/external/openal/common/phase_shifter.h @@ -0,0 +1,347 @@ +#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; +}; + +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}) + { + /* There doesn't seem to be NEON intrinsics to do this kind of stipple + * shuffling, so there's two custom methods for it. + */ + 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; + }; + 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; + }; + 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]); + }; + 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]); + }; + 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)) + { + 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; + }; + 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])}; + /* NOTE: This could alternatively be done with two unaligned loads + * and a shuffle. Which would be better? + */ + 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}) + { + 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; + }; + 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; + }; + 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]); + }; + 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]); + }; + 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)) + { + 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; + }; + 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/external/openal/config.h.in b/external/openal/config.h.in index a28204eff3..5cc78ae555 100644 --- a/external/openal/config.h.in +++ b/external/openal/config.h.in @@ -1,7 +1,3 @@ -/* API declaration export attribute */ -#define AL_API ${EXPORT_DECL} -#define ALC_API ${EXPORT_DECL} - /* Define if HRTF data is embedded in the library */ #cmakedefine ALSOFT_EMBED_HRTF_DATA @@ -17,6 +13,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 diff --git a/external/openal/core/ambidefs.cpp b/external/openal/core/ambidefs.cpp new file mode 100644 index 0000000000..2725748e60 --- /dev/null +++ b/external/openal/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/external/openal/core/ambidefs.h b/external/openal/core/ambidefs.h index a72f7b780c..2273935916 100644 --- a/external/openal/core/ambidefs.h +++ b/external/openal/core/ambidefs.h @@ -97,6 +97,10 @@ struct AmbiScale { }}; 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 { diff --git a/external/openal/core/async_event.h b/external/openal/core/async_event.h new file mode 100644 index 0000000000..054f056399 --- /dev/null +++ b/external/openal/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; + + +enum { + /* End event thread processing. */ + EventType_KillThread = 0, + + /* User event types. */ + EventType_SourceStateChange = 1<<0, + EventType_BufferCompleted = 1<<1, + EventType_Disconnected = 1<<2, + + /* Internal events. */ + EventType_ReleaseEffectState = 65536, +}; + +struct AsyncEvent { + 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/external/openal/core/bformatdec.cpp b/external/openal/core/bformatdec.cpp new file mode 100644 index 0000000000..6bf85ec969 --- /dev/null +++ b/external/openal/core/bformatdec.cpp @@ -0,0 +1,263 @@ + +#include "config.h" + +#include "bformatdec.h" + +#include +#include +#include +#include + +#include "almalloc.h" +#include "ambdec.h" +#include "filters/splitter.h" +#include "front_stablizer.h" +#include "math_defs.h" +#include "mixer.h" +#include "opthelpers.h" + + +namespace { + +inline auto& GetAmbiScales(AmbDecScale scaletype) noexcept +{ + 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 size_t inchans, + const uint srate, const uint (&chanmap)[MAX_OUTPUT_CHANNELS], + std::unique_ptr stablizer) + : mStablizer{std::move(stablizer)}, mDualBand{allow_2band && (conf->FreqBands == 2)} + , mChannelDec{inchans} +{ + const bool periphonic{(conf->ChanMask&AmbiPeriphonicMask) != 0}; + auto&& coeff_scale = GetAmbiScales(conf->CoeffScale); + + if(!mDualBand) + { + for(size_t j{0},k{0};j < mChannelDec.size();++j) + { + const size_t acn{periphonic ? j : AmbiIndex::FromACN2D()[j]}; + if(!(conf->ChanMask&(1u<HFOrderGain[order] / coeff_scale[acn]}; + for(size_t i{0u};i < conf->NumSpeakers;++i) + { + const size_t chanidx{chanmap[i]}; + mChannelDec[j].mGains.Single[chanidx] = conf->Matrix[i][k] * gain; + } + ++k; + } + } + else + { + mChannelDec[0].mXOver.init(conf->XOverFreq / static_cast(srate)); + for(size_t j{1};j < mChannelDec.size();++j) + mChannelDec[j].mXOver = mChannelDec[0].mXOver; + + const float ratio{std::pow(10.0f, conf->XOverRatio / 40.0f)}; + for(size_t j{0},k{0};j < mChannelDec.size();++j) + { + const size_t acn{periphonic ? j : AmbiIndex::FromACN2D()[j]}; + if(!(conf->ChanMask&(1u<HFOrderGain[order] * ratio / coeff_scale[acn]}; + const float lfGain{conf->LFOrderGain[order] / ratio / coeff_scale[acn]}; + for(size_t i{0u};i < conf->NumSpeakers;++i) + { + const size_t chanidx{chanmap[i]}; + mChannelDec[j].mGains.Dual[sHFBand][chanidx] = conf->HFMatrix[i][k] * hfGain; + mChannelDec[j].mGains.Dual[sLFBand][chanidx] = conf->LFMatrix[i][k] * lfGain; + } + ++k; + } + } +} + +BFormatDec::BFormatDec(const size_t inchans, const al::span coeffs, + const al::span coeffslf, 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 + { + 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. Note that + * the samples are stored and combined in reverse, so the newest samples + * are at the front and the oldest at the back. + */ + al::span tmpbuf{mStablizer->TempBuf.data(), SamplesToDo+FrontStablizer::DelayLength}; + auto tmpiter = tmpbuf.begin() + SamplesToDo; + std::copy(mStablizer->MidDelay.cbegin(), mStablizer->MidDelay.cend(), tmpiter); + for(size_t i{0};i < SamplesToDo;++i) + *--tmpiter = OutBuffer[lidx][i] + OutBuffer[ridx][i]; + /* Save the newest samples for next time. */ + std::copy_n(tmpbuf.cbegin(), mStablizer->MidDelay.size(), mStablizer->MidDelay.begin()); + + /* Apply an all-pass on the reversed signal, then reverse the samples to + * get the forward signal with a reversed phase shift. 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.applyAllpass(tmpbuf); + tmpbuf = tmpbuf.subspan(); + std::reverse(tmpbuf.begin(), tmpbuf.end()); + + /* 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, 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::MathDefs::Pi()*0.5f))}; + const float cos_hf{std::cos(1.0f/4.0f * (al::MathDefs::Pi()*0.5f))}; + const float sin_lf{std::sin(1.0f/3.0f * (al::MathDefs::Pi()*0.5f))}; + const float sin_hf{std::sin(1.0f/4.0f * (al::MathDefs::Pi()*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 AmbDecConf *conf, const bool allow_2band, + const size_t inchans, const uint srate, const uint (&chanmap)[MAX_OUTPUT_CHANNELS], + std::unique_ptr stablizer) +{ + return std::unique_ptr{new(FamCount(inchans)) + BFormatDec{conf, allow_2band, inchans, srate, chanmap, std::move(stablizer)}}; +} +std::unique_ptr BFormatDec::Create(const size_t inchans, + const al::span coeffs, const al::span coeffslf, + std::unique_ptr stablizer) +{ + return std::unique_ptr{new(FamCount(inchans)) + BFormatDec{inchans, coeffs, coeffslf, std::move(stablizer)}}; +} diff --git a/external/openal/core/bformatdec.h b/external/openal/core/bformatdec.h new file mode 100644 index 0000000000..a0ae3f276d --- /dev/null +++ b/external/openal/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" + +struct AmbDecConf; +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}; + + al::FlexArray mChannelDec; + +public: + BFormatDec(const AmbDecConf *conf, const bool allow_2band, const size_t inchans, + const uint srate, const uint (&chanmap)[MAX_OUTPUT_CHANNELS], + std::unique_ptr stablizer); + BFormatDec(const size_t inchans, const al::span coeffs, + const al::span coeffslf, 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 AmbDecConf *conf, const bool allow_2band, + const size_t inchans, const uint srate, const uint (&chanmap)[MAX_OUTPUT_CHANNELS], + std::unique_ptr stablizer); + static std::unique_ptr Create(const size_t inchans, + const al::span coeffs, const al::span coeffslf, + std::unique_ptr stablizer); + + DEF_FAM_NEWDEL(BFormatDec, mChannelDec) +}; + +#endif /* CORE_BFORMATDEC_H */ diff --git a/external/openal/core/bsinc_defs.h b/external/openal/core/bsinc_defs.h index 4386528952..f29582318c 100644 --- a/external/openal/core/bsinc_defs.h +++ b/external/openal/core/bsinc_defs.h @@ -7,10 +7,4 @@ constexpr unsigned int BSincScaleCount{1 << BSincScaleBits}; constexpr unsigned int BSincPhaseBits{5}; constexpr unsigned int BSincPhaseCount{1 << BSincPhaseBits}; -/* The maximum number of sample points for the bsinc filters. The max points - * includes the doubling for downsampling, so the maximum number of base sample - * points is 24, which is 23rd order. - */ -constexpr unsigned int BSincPointsMax{48}; - #endif /* CORE_BSINC_DEFS_H */ diff --git a/external/openal/core/bsinc_tables.cpp b/external/openal/core/bsinc_tables.cpp index 315e14488c..ff73c301e4 100644 --- a/external/openal/core/bsinc_tables.cpp +++ b/external/openal/core/bsinc_tables.cpp @@ -9,6 +9,7 @@ #include #include +#include "core/mixer/defs.h" #include "math_defs.h" @@ -24,7 +25,8 @@ using uint = unsigned int; */ constexpr double Sinc(const double x) { - if(!(x > 1e-15 || x < -1e-15)) + constexpr double epsilon{std::numeric_limits::epsilon()}; + if(!(x > epsilon || x < -epsilon)) return 1.0; return std::sin(al::MathDefs::Pi()*x) / (al::MathDefs::Pi()*x); } @@ -35,7 +37,7 @@ constexpr double Sinc(const double x) * 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) +constexpr double BesselI_0(const double x) noexcept { /* Start at k=1 since k=0 is trivial. */ const double x2{x / 2.0}; @@ -82,7 +84,7 @@ constexpr double Kaiser(const double beta, const double k, const double besseli_ /* Calculates the (normalized frequency) transition width of the Kaiser window. * Rejection is in dB. */ -constexpr double CalcKaiserWidth(const double rejection, const uint order) +constexpr double CalcKaiserWidth(const double rejection, const uint order) noexcept { if(rejection > 21.19) return (rejection - 7.95) / (order * 2.285 * al::MathDefs::Tau()); @@ -122,7 +124,7 @@ struct BSincHeader { uint num_points{Order+1}; for(uint si{0};si < BSincScaleCount;++si) { - const double scale{scaleBase + (scaleRange * si / (BSincScaleCount-1))}; + 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_}; @@ -144,21 +146,33 @@ constexpr BSincHeader bsinc24_hdr{60, 23}; * 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) + BSincFilterArray(const BSincHeader &hdr_) : hdr{hdr_} + { #else template struct BSincFilterArray { - alignas(16) std::array mTable; + alignas(16) std::array mTable{}; BSincFilterArray() -#endif { - using filter_type = double[][BSincPhaseCount+1][BSincPointsMax]; - auto filter = std::make_unique(BSincScaleCount); + 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. @@ -167,38 +181,38 @@ struct BSincFilterArray { { const uint m{hdr.a[si] * 2}; const size_t o{(BSincPointsMax-m) / 2}; - const double scale{hdr.scaleBase + (hdr.scaleRange * si / (BSincScaleCount-1))}; - const double cutoff{scale - (hdr.scaleBase * std::max(0.5, scale) * 2.0)}; + 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}; + 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{l + (pi/double{BSincPhaseCount})}; + 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/a, hdr.besseli_0_beta) * cutoff * + 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-1;++si) + 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) { - /* Write out the filter. Also calculate and write out the phase - * and scale deltas. - */ for(size_t i{0};i < m;++i) mTable[idx++] = static_cast(filter[si][pi][o+i]); @@ -210,11 +224,22 @@ struct BSincFilterArray { 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 points between scales, the destination - * points will be 0, thus: x = a + f (-a) + * 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) { @@ -233,31 +258,11 @@ struct BSincFilterArray { } } } - { - /* The last scale index doesn't have any scale or scale-phase - * deltas. - */ - constexpr size_t si{BSincScaleCount-1}; - const size_t m{((hdr.a[si]*2) + 3) & ~3u}; - const size_t o{(BSincPointsMax-m) / 2}; - - 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]); - 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); - } - for(size_t i{0};i < m;++i) - mTable[idx++] = 0.0f; - for(size_t i{0};i < m;++i) - mTable[idx++] = 0.0f; - } - } 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 @@ -268,9 +273,11 @@ const BSincFilterArray bsinc12_filter{}; const BSincFilterArray bsinc24_filter{}; #endif -constexpr BSincTable GenerateBSincTable(const BSincHeader &hdr, const float *tab) +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) @@ -278,11 +285,11 @@ constexpr BSincTable GenerateBSincTable(const BSincHeader &hdr, const float *tab 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 = tab; + ret.Tab = filter.getTable(); return ret; } } // namespace -const BSincTable bsinc12{GenerateBSincTable(bsinc12_hdr, &bsinc12_filter.mTable.front())}; -const BSincTable bsinc24{GenerateBSincTable(bsinc24_hdr, &bsinc24_filter.mTable.front())}; +const BSincTable bsinc12{GenerateBSincTable(bsinc12_filter)}; +const BSincTable bsinc24{GenerateBSincTable(bsinc24_filter)}; diff --git a/external/openal/core/buffer_storage.cpp b/external/openal/core/buffer_storage.cpp new file mode 100644 index 0000000000..5179db13ba --- /dev/null +++ b/external/openal/core/buffer_storage.cpp @@ -0,0 +1,41 @@ + +#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; + } + return 0; +} diff --git a/external/openal/core/buffer_storage.h b/external/openal/core/buffer_storage.h new file mode 100644 index 0000000000..592803541f --- /dev/null +++ b/external/openal/core/buffer_storage.h @@ -0,0 +1,75 @@ +#ifndef CORE_BUFFER_STORAGE_H +#define CORE_BUFFER_STORAGE_H + +#include + +#include "albyte.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" */ +}; + +enum class AmbiLayout : unsigned char { + FuMa, + ACN, +}; +enum class AmbiScaling : unsigned char { + FuMa, + SN3D, + N3D, +}; + +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); } + + +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 mChannels == FmtBFormat2D || mChannels == FmtBFormat3D; } +}; + +#endif /* CORE_BUFFER_STORAGE_H */ diff --git a/external/openal/core/bufferline.h b/external/openal/core/bufferline.h index 503e208d4c..8b445f3ff5 100644 --- a/external/openal/core/bufferline.h +++ b/external/openal/core/bufferline.h @@ -3,6 +3,8 @@ #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. @@ -10,5 +12,6 @@ constexpr int BufferLineSize{1024}; using FloatBufferLine = std::array; +using FloatBufferSpan = al::span; #endif /* CORE_BUFFERLINE_H */ diff --git a/external/openal/core/context.cpp b/external/openal/core/context.cpp new file mode 100644 index 0000000000..f1c310aa63 --- /dev/null +++ b/external/openal/core/context.cpp @@ -0,0 +1,5 @@ + +#include "config.h" + +#include "context.h" + diff --git a/external/openal/core/context.h b/external/openal/core/context.h new file mode 100644 index 0000000000..bf43905333 --- /dev/null +++ b/external/openal/core/context.h @@ -0,0 +1,171 @@ +#ifndef CORE_CONTEXT_H +#define CORE_CONTEXT_H + +#include +#include +#include +#include +#include + +#include "almalloc.h" +#include "alspan.h" +#include "atomic.h" +#include "core/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}; + +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 { + float DopplerFactor; + float DopplerVelocity; + float SpeedOfSound; + bool SourceDistanceModel; + DistanceModel mDistanceModel; + + std::atomic next; + + DEF_NEWDEL(ContextProps) +}; + +struct ListenerProps { + std::array Position; + std::array Velocity; + std::array OrientAt; + std::array OrientUp; + float Gain; + float MetersPerUnit; + + std::atomic next; + + DEF_NEWDEL(ListenerProps) +}; + +struct ContextParams { + /* Pointer to the most recent property values that are awaiting an update. */ + std::atomic ContextUpdate{nullptr}; + std::atomic ListenerUpdate{nullptr}; + + alu::Matrix Matrix{alu::Matrix::Identity()}; + alu::Vector Velocity{}; + + float Gain{1.0f}; + float MetersPerUnit{1.0f}; + + float DopplerFactor{1.0f}; + float SpeedOfSound{343.3f}; /* 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 mFreeListenerProps{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(size_t addcount); + + + 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; + + + ContextBase(DeviceBase *device); + ContextBase(const ContextBase&) = delete; + ContextBase& operator=(const ContextBase&) = delete; + ~ContextBase(); +}; + +#endif /* CORE_CONTEXT_H */ diff --git a/external/openal/core/converter.cpp b/external/openal/core/converter.cpp new file mode 100644 index 0000000000..6a06b464f6 --- /dev/null +++ b/external/openal/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/external/openal/core/converter.h b/external/openal/core/converter.h new file mode 100644 index 0000000000..2d22ae387b --- /dev/null +++ b/external/openal/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/external/openal/core/dbus_wrap.cpp b/external/openal/core/dbus_wrap.cpp new file mode 100644 index 0000000000..506dd81593 --- /dev/null +++ b/external/openal/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(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/external/openal/core/dbus_wrap.h b/external/openal/core/dbus_wrap.h new file mode 100644 index 0000000000..61dbb97125 --- /dev/null +++ b/external/openal/core/dbus_wrap.h @@ -0,0 +1,75 @@ +#ifndef CORE_DBUS_WRAP_H +#define CORE_DBUS_WRAP_H + +#include + +#include + +#include "dynload.h" + + +#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) + +#ifdef HAVE_DYNLOAD + +#include + +extern void *dbus_handle; +#define DECL_FUNC(x) extern decltype(x) *p##x; +DBUS_FUNCTIONS(DECL_FUNC) +#undef DECL_FUNC + +void PrepareDBus(); + +inline auto HasDBus() +{ + static std::once_flag init_dbus{}; + std::call_once(init_dbus, PrepareDBus); + return dbus_handle; +} + +#else + +#define DECL_FUNC(x) constexpr auto p##x = &x; +DBUS_FUNCTIONS(DECL_FUNC) +#undef DECL_FUNC + +constexpr bool HasDBus() noexcept { return true; } +#endif /* HAVE_DYNLOAD */ + + +namespace dbus { + +struct Error { + Error() { (*pdbus_error_init)(&mError); } + ~Error() { (*pdbus_error_free)(&mError); } + DBusError* operator->() { return &mError; } + DBusError &get() { return mError; } +private: + DBusError mError{}; +}; + +struct ConnectionDeleter { + void operator()(DBusConnection *c) { (*pdbus_connection_unref)(c); } +}; +using ConnectionPtr = std::unique_ptr; + +} // namespace dbus + + +#endif /* CORE_DBUS_WRAP_H */ diff --git a/external/openal/core/device.cpp b/external/openal/core/device.cpp new file mode 100644 index 0000000000..9705c0acdc --- /dev/null +++ b/external/openal/core/device.cpp @@ -0,0 +1,7 @@ + +#include "config.h" + +#include "device.h" + + +al::FlexArray DeviceBase::sEmptyContextArray{0u}; diff --git a/external/openal/core/device.h b/external/openal/core/device.h new file mode 100644 index 0000000000..4cc822cc73 --- /dev/null +++ b/external/openal/core/device.h @@ -0,0 +1,290 @@ +#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 "core/bufferline.h" +#include "devformat.h" +#include "intrusive_ptr.h" +#include "mixer/hrtfdefs.h" +#include "opthelpers.h" +#include "vector.h" + +struct BackendBase; +class BFormatDec; +struct bs2b; +struct Compressor; +struct ContextBase; +struct DirectHrtfState; +struct HrtfStore; +struct UhjEncoder; + +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 +}; + + +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, + + 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{}; + bool IsHeadphones{false}; + 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}; + + uint SamplesDone{0u}; + std::chrono::nanoseconds ClockBase{0}; + std::chrono::nanoseconds FixedLatency{0}; + + /* Temp storage used for mixer processing. */ + 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 + HrtfDirectDelay]; + + /* 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}; + + /* 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; + + + 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(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() +}; + + +/* 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/external/openal/core/front_stablizer.h b/external/openal/core/front_stablizer.h new file mode 100644 index 0000000000..3d328a8d55 --- /dev/null +++ b/external/openal/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/external/openal/core/helpers.cpp b/external/openal/core/helpers.cpp new file mode 100644 index 0000000000..dcb785c919 --- /dev/null +++ b/external/openal/core/helpers.cpp @@ -0,0 +1,514 @@ + +#include "config.h" + +#include "helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "almalloc.h" +#include "alfstream.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; +} + +void SetRTPriority() +{ + if(RTPrioLevel <= 0) + return; + + int err{-ENOTSUP}; +#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) + 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); +#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; + + WARN("pthread_setschedparam failed: %s (%d)\n", std::strerror(err), err); +#endif +#ifdef HAVE_RTKIT + if(HasDBus()) + { + dbus::Error error; + if(dbus::ConnectionPtr conn{(*pdbus_bus_get)(DBUS_BUS_SYSTEM, &error.get())}) + { + using ulonglong = unsigned long long; + auto limit_rttime = [](DBusConnection *c) -> int + { + 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(std::min(umaxtime, + std::numeric_limits::max())); + rlim.rlim_cur = std::min(rlim.rlim_cur, rlim.rlim_max); + if(setrlimit(RLIMIT_RTTIME, &rlim) != 0) + return errno; + } + return 0; + }; + + /* Don't stupidly exit if the connection dies while doing this. */ + (*pdbus_connection_set_exit_on_disconnect)(conn.get(), false); + + int nicemin{}; + 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; + } + int rtmax{rtkit_get_max_realtime_priority(conn.get())}; + TRACE("Maximum real-time priority: %d, minimum niceness: %d\n", rtmax, nicemin); + + err = EINVAL; + 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); + } + + /* Use half the maximum real-time priority allowed. */ + TRACE("Making real-time with priority %d\n", (rtmax+1)/2); + err = rtkit_make_realtime(conn.get(), 0, (rtmax+1)/2); + if(err == 0) return; + + err = std::abs(err); + WARN("Failed to set real-time priority: %s (%d)\n", std::strerror(err), err); + } + 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; + + err = std::abs(err); + WARN("Failed to set high priority: %s (%d)\n", std::strerror(err), err); + } + } + else + WARN("D-Bus connection failed with %s: %s\n", error->name, error->message); + } + else + WARN("D-Bus not available\n"); +#endif + ERR("Could not set elevated priority: %s (%d)\n", std::strerror(err), err); +} + +#endif diff --git a/external/openal/core/helpers.h b/external/openal/core/helpers.h new file mode 100644 index 0000000000..f0bfcf1b5c --- /dev/null +++ b/external/openal/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/external/openal/core/hrtf.cpp b/external/openal/core/hrtf.cpp new file mode 100644 index 0000000000..e0ab8f0a9b --- /dev/null +++ b/external/openal/core/hrtf.cpp @@ -0,0 +1,1447 @@ + +#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 "alnumeric.h" +#include "aloptional.h" +#include "alspan.h" +#include "ambidefs.h" +#include "filters/splitter.h" +#include "helpers.h" +#include "logging.h" +#include "math_defs.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 float PassthruCoeff{0.707106781187f/*sqrt(0.5)*/}; + +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::MathDefs::Pi()*0.5f + ev) * static_cast(evcount-1) / + al::MathDefs::Pi(); + 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::MathDefs::Tau()+az) * static_cast(azcount) / + al::MathDefs::Tau(); + 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 - (spread / al::MathDefs::Tau())}; + + 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) +{ + std::unique_ptr Hrtf; + + 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(HrtfStore::Field)*fields.size(); + total = RoundUp(total, alignof(HrtfStore::Elevation)); /* Align for elevation infos */ + total += sizeof(Hrtf->elev[0])*elevs.size(); + total = RoundUp(total, 16); /* Align for coefficients using SIMD */ + total += sizeof(Hrtf->coeffs[0])*irCount; + total += sizeof(Hrtf->delays[0])*irCount; + + Hrtf.reset(new (al_calloc(16, total)) HrtfStore{}); + 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; + + assert(offset == total); + + /* Copy input data to storage. */ + std::copy(fields.cbegin(), fields.cend(), field_); + std::copy(elevs.cbegin(), elevs.cend(), elev_); + std::copy_n(coeffs, irCount, coeffs_); + std::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 +inline 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_constexpr(al::endian::native == al::endian::little) + { + if(!data.read(reinterpret_cast(&ret), num_bits/8)) + return static_cast(EOF); + } + else + { + 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)); + } + + if_constexpr(std::is_signed::value && num_bits < sizeof(T)*8) + { + constexpr auto signbit = static_cast(1u << (num_bits-1)); + return static_cast((ret^signbit) - signbit); + } + return 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/external/openal/core/hrtf.h b/external/openal/core/hrtf.h new file mode 100644 index 0000000000..61e5bada4a --- /dev/null +++ b/external/openal/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/external/openal/core/logging.h b/external/openal/core/logging.h index b931c27e18..81465929c8 100644 --- a/external/openal/core/logging.h +++ b/external/openal/core/logging.h @@ -35,7 +35,12 @@ extern FILE *gLogFile; #else -[[gnu::format(printf,3,4)]] void al_print(LogLevel level, FILE *logfile, const char *fmt, ...); +#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__) diff --git a/external/openal/core/mixer.cpp b/external/openal/core/mixer.cpp new file mode 100644 index 0000000000..71e48fe3f4 --- /dev/null +++ b/external/openal/core/mixer.cpp @@ -0,0 +1,126 @@ + +#include "config.h" + +#include "mixer.h" + +#include + +#include "devformat.h" +#include "device.h" +#include "math_defs.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] = 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 */ + 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 + spread/al::MathDefs::Tau())}; + + 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/external/openal/core/mixer.h b/external/openal/core/mixer.h new file mode 100644 index 0000000000..309f422498 --- /dev/null +++ b/external/openal/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/external/openal/core/mixer/defs.h b/external/openal/core/mixer/defs.h index acf60350a3..ba304f2227 100644 --- a/external/openal/core/mixer/defs.h +++ b/external/openal/core/mixer/defs.h @@ -6,6 +6,7 @@ #include "alspan.h" #include "core/bufferline.h" +#include "core/resampler_limits.h" struct HrtfChannelState; struct HrtfFilter; @@ -19,12 +20,6 @@ constexpr int MixerFracBits{12}; constexpr int MixerFracOne{1 << MixerFracBits}; constexpr int MixerFracMask{MixerFracOne - 1}; -/* 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 float GainSilenceThreshold{0.00001f}; /* -100dB */ @@ -80,7 +75,7 @@ template void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize); template -void MixDirectHrtf_(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, +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); diff --git a/external/openal/core/mixer/hrtfbase.h b/external/openal/core/mixer/hrtfbase.h index 7419f96035..79b09a3d32 100644 --- a/external/openal/core/mixer/hrtfbase.h +++ b/external/openal/core/mixer/hrtfbase.h @@ -12,7 +12,7 @@ using uint = unsigned int; using ApplyCoeffsT = void(&)(float2 *RESTRICT Values, const size_t irSize, - const HrirArray &Coeffs, const float left, const float right); + const ConstHrirSpan Coeffs, const float left, const float right); template inline void MixHrtfBase(const float *InSamples, float2 *RESTRICT AccumSamples, const size_t IrSize, @@ -20,7 +20,7 @@ inline void MixHrtfBase(const float *InSamples, float2 *RESTRICT AccumSamples, c { ASSUME(BufferSize > 0); - const HrirArray &Coeffs = *hrtfparams->Coeffs; + const ConstHrirSpan Coeffs{hrtfparams->Coeffs}; const float gainstep{hrtfparams->GainStep}; const float gain{hrtfparams->Gain}; @@ -45,9 +45,9 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl { ASSUME(BufferSize > 0); - const auto &OldCoeffs = oldparams->Coeffs; + const ConstHrirSpan OldCoeffs{oldparams->Coeffs}; const float oldGainStep{oldparams->Gain / static_cast(BufferSize)}; - const auto &NewCoeffs = *newparams->Coeffs; + const ConstHrirSpan NewCoeffs{newparams->Coeffs}; const float newGainStep{newparams->GainStep}; if LIKELY(oldparams->Gain > GainSilenceThreshold) @@ -84,7 +84,7 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl } template -inline void MixDirectHrtfBase(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, +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) { @@ -133,7 +133,7 @@ inline void MixDirectHrtfBase(FloatBufferLine &LeftOut, FloatBufferLine &RightOu ChanState->mSplitter.processHfScale(tempbuf, ChanState->mHfScale); /* Now apply the HRIR coefficients to this channel. */ - const auto &Coeffs = ChanState->mCoeffs; + const ConstHrirSpan Coeffs{ChanState->mCoeffs}; for(size_t i{0u};i < BufferSize;++i) { const float insample{tempbuf[i]}; diff --git a/external/openal/core/mixer/hrtfdefs.h b/external/openal/core/mixer/hrtfdefs.h index 89a9bb8df6..7046a31e3b 100644 --- a/external/openal/core/mixer/hrtfdefs.h +++ b/external/openal/core/mixer/hrtfdefs.h @@ -3,6 +3,7 @@ #include +#include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/filters/splitter.h" @@ -28,9 +29,11 @@ constexpr uint MinIrLength{8}; constexpr uint HrtfDirectDelay{256}; using HrirArray = std::array; +using HrirSpan = al::span; +using ConstHrirSpan = al::span; struct MixHrtfFilter { - const HrirArray *Coeffs; + const ConstHrirSpan Coeffs; uint2 Delay; float Gain; float GainStep; diff --git a/external/openal/core/mixer/mixer_c.cpp b/external/openal/core/mixer/mixer_c.cpp index ff9538a45a..f82f7dd19c 100644 --- a/external/openal/core/mixer/mixer_c.cpp +++ b/external/openal/core/mixer/mixer_c.cpp @@ -32,15 +32,16 @@ inline float do_cubic(const InterpState&, const float *RESTRICT vals, const uint 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 *fil{istate.bsinc.filter + m*pi*4}; - const float *phd{fil + m}; - const float *scd{phd + m}; - const float *spd{scd + m}; + 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}; @@ -51,13 +52,14 @@ inline float do_bsinc(const InterpState &istate, const float *RESTRICT vals, con 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 *fil{istate.bsinc.filter + m*pi*4}; - const float *phd{fil + m}; + 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}; @@ -83,7 +85,7 @@ float *DoResample(const InterpState *state, float *RESTRICT src, uint frac, uint return dst.data(); } -inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const HrirArray &Coeffs, +inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, const float left, const float right) { ASSUME(IrSize >= MinIrLength); @@ -149,7 +151,7 @@ void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uin } template<> -void MixDirectHrtf_(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, +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) { diff --git a/external/openal/core/mixer/mixer_neon.cpp b/external/openal/core/mixer/mixer_neon.cpp index f3e5f13037..a3afdc6bf5 100644 --- a/external/openal/core/mixer/mixer_neon.cpp +++ b/external/openal/core/mixer/mixer_neon.cpp @@ -34,7 +34,7 @@ inline float32x4_t set_f4(float l0, float l1, float l2, float l3) constexpr uint FracPhaseBitDiff{MixerFracBits - BSincPhaseBits}; constexpr uint FracPhaseDiffOne{1 << FracPhaseBitDiff}; -inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const HrirArray &Coeffs, +inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, const float left, const float right) { float32x4_t leftright4; @@ -118,6 +118,7 @@ float *Resample_(const InterpState *state, float *RESTRICT src 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) @@ -130,10 +131,10 @@ float *Resample_(const InterpState *state, float *RESTRICT src float32x4_t r4{vdupq_n_f32(0.0f)}; { const float32x4_t pf4{vdupq_n_f32(pf)}; - const float *fil{filter + m*pi*4}; - const float *phd{fil + m}; - const float *scd{phd + m}; - const float *spd{scd + m}; + 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}; @@ -163,6 +164,7 @@ float *Resample_(const InterpState *state, float *RESTRICT { 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) @@ -175,8 +177,8 @@ float *Resample_(const InterpState *state, float *RESTRICT float32x4_t r4{vdupq_n_f32(0.0f)}; { const float32x4_t pf4{vdupq_n_f32(pf)}; - const float *fil{filter + m*pi*4}; - const float *phd{fil + m}; + const float *RESTRICT fil{filter + m*pi*2}; + const float *RESTRICT phd{fil + m}; size_t td{m >> 2}; size_t j{0u}; @@ -213,7 +215,7 @@ void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const } template<> -void MixDirectHrtf_(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, +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) { diff --git a/external/openal/core/mixer/mixer_sse.cpp b/external/openal/core/mixer/mixer_sse.cpp index 23caf79769..3cfb00a5b6 100644 --- a/external/openal/core/mixer/mixer_sse.cpp +++ b/external/openal/core/mixer/mixer_sse.cpp @@ -15,9 +15,8 @@ struct BSincTag; struct FastBSincTag; -/* SSE2 is required for any SSE support. */ -#if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE2__) -#pragma GCC target("sse2") +#if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE__) +#pragma GCC target("sse") #endif namespace { @@ -27,7 +26,7 @@ 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 HrirArray &Coeffs, +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)}; @@ -83,6 +82,7 @@ float *Resample_(const InterpState *state, float *RESTRICT src, 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) @@ -95,10 +95,10 @@ float *Resample_(const InterpState *state, float *RESTRICT src, __m128 r4{_mm_setzero_ps()}; { const __m128 pf4{_mm_set1_ps(pf)}; - const float *fil{filter + m*pi*4}; - const float *phd{fil + m}; - const float *scd{phd + m}; - const float *spd{scd + m}; + 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}; @@ -129,6 +129,7 @@ float *Resample_(const InterpState *state, float *RESTRICT { 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) @@ -141,8 +142,8 @@ float *Resample_(const InterpState *state, float *RESTRICT __m128 r4{_mm_setzero_ps()}; { const __m128 pf4{_mm_set1_ps(pf)}; - const float *fil{filter + m*pi*4}; - const float *phd{fil + m}; + const float *RESTRICT fil{filter + m*pi*2}; + const float *RESTRICT phd{fil + m}; size_t td{m >> 2}; size_t j{0u}; @@ -180,7 +181,7 @@ void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const u } template<> -void MixDirectHrtf_(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, +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) { diff --git a/external/openal/core/mixer/mixer_sse2.cpp b/external/openal/core/mixer/mixer_sse2.cpp index f91d5dcd9f..99d04210ca 100644 --- a/external/openal/core/mixer/mixer_sse2.cpp +++ b/external/openal/core/mixer/mixer_sse2.cpp @@ -52,10 +52,10 @@ float *Resample_(const InterpState*, float *RESTRICT src, uint auto dst_iter = dst.begin(); for(size_t todo{dst.size()>>2};todo;--todo) { - 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 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])}; diff --git a/external/openal/core/resampler_limits.h b/external/openal/core/resampler_limits.h new file mode 100644 index 0000000000..9d4cefdae2 --- /dev/null +++ b/external/openal/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/external/openal/core/rtkit.cpp b/external/openal/core/rtkit.cpp new file mode 100644 index 0000000000..8b489e7185 --- /dev/null +++ b/external/openal/core/rtkit.cpp @@ -0,0 +1,240 @@ +/*-*- 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 + +#ifdef __linux__ + +#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) { (*pdbus_message_unref)(m); } + }; + using MessagePtr = std::unique_ptr; +} // namespace dbus + +namespace { + +inline pid_t _gettid() +{ return static_cast(syscall(SYS_gettid)); } + +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{(*pdbus_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 = (*pdbus_message_append_args)(m.get(), + dbus::TypeString, &interfacestr, + dbus::TypeString, &propname, + dbus::TypeInvalid); + if(!ready) return -ENOMEM; + + dbus::Error error; + dbus::MessagePtr r{(*pdbus_connection_send_with_reply_and_block)(connection, m.get(), -1, + &error.get())}; + if(!r) return translate_error(error->name); + + if((*pdbus_set_error_from_message)(&error.get(), r.get())) + return translate_error(error->name); + + int ret{-EBADMSG}; + DBusMessageIter iter{}; + (*pdbus_message_iter_init)(r.get(), &iter); + while(int curtype{(*pdbus_message_iter_get_arg_type)(&iter)}) + { + if(curtype == dbus::TypeVariant) + { + DBusMessageIter subiter{}; + (*pdbus_message_iter_recurse)(&iter, &subiter); + + while((curtype=(*pdbus_message_iter_get_arg_type)(&subiter)) != dbus::TypeInvalid) + { + if(curtype == dbus::TypeInt32) + { + dbus_int32_t i32{}; + (*pdbus_message_iter_get_basic)(&subiter, &i32); + *propval = i32; + ret = 0; + } + + if(curtype == dbus::TypeInt64) + { + dbus_int64_t i64{}; + (*pdbus_message_iter_get_basic)(&subiter, &i64); + *propval = i64; + ret = 0; + } + + (*pdbus_message_iter_next)(&subiter); + } + } + (*pdbus_message_iter_next)(&iter); + } + + return ret; +} + +} // namespace + +extern "C" { +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(); + + dbus::MessagePtr m{(*pdbus_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 = (*pdbus_message_append_args)(m.get(), + dbus::TypeUInt64, &u64, + dbus::TypeUInt32, &u32, + dbus::TypeInvalid); + if(!ready) return -ENOMEM; + + dbus::Error error; + dbus::MessagePtr r{(*pdbus_connection_send_with_reply_and_block)(connection, m.get(), -1, + &error.get())}; + if(!r) return translate_error(error->name); + + if((*pdbus_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(); + + dbus::MessagePtr m{(*pdbus_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 = (*pdbus_message_append_args)(m.get(), + dbus::TypeUInt64, &u64, + dbus::TypeInt32, &s32, + dbus::TypeInvalid); + if(!ready) return -ENOMEM; + + dbus::Error error; + dbus::MessagePtr r{(*pdbus_connection_send_with_reply_and_block)(connection, m.get(), -1, + &error.get())}; + if(!r) return translate_error(error->name); + + if((*pdbus_set_error_from_message)(&error.get(), r.get())) + return translate_error(error->name); + + return 0; +} +} // extern "C" + +#else + +extern "C" { +int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) +{ return -ENOTSUP; } + +int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) +{ return -ENOTSUP; } + +int rtkit_get_max_realtime_priority(DBusConnection *connection) +{ return -ENOTSUP; } + +int rtkit_get_min_nice_level(DBusConnection *connection, int *min_nice_level) +{ return -ENOTSUP; } + +long long rtkit_get_rttime_usec_max(DBusConnection *connection) +{ return -ENOTSUP; } +} // extern "C" + +#endif diff --git a/external/openal/core/rtkit.h b/external/openal/core/rtkit.h new file mode 100644 index 0000000000..96e81d4a12 --- /dev/null +++ b/external/openal/core/rtkit.h @@ -0,0 +1,80 @@ +/*-*- 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" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 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); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/external/openal/core/uhjfilter.cpp b/external/openal/core/uhjfilter.cpp index 92f3590102..f1af4b94a1 100644 --- a/external/openal/core/uhjfilter.cpp +++ b/external/openal/core/uhjfilter.cpp @@ -3,227 +3,49 @@ #include "uhjfilter.h" -#ifdef HAVE_SSE_INTRINSICS -#include -#elif defined(HAVE_NEON) -#include -#endif - #include #include #include "alcomplex.h" #include "alnumeric.h" #include "opthelpers.h" +#include "phase_shifter.h" namespace { +static_assert(UhjEncoder::sFilterDelay==UhjDecoder::sFilterDelay, "UHJ filter delays mismatch"); + using complex_d = std::complex; -struct PhaseShifterT { - alignas(16) std::array Coeffs; - - /* 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() - { - constexpr size_t fft_size{Uhj2Encoder::sFilterSize * 2}; - constexpr size_t half_size{fft_size / 2}; - - /* Generate a frequency domain impulse with a +90 degree phase offset. - * Reconstruct the mirrored frequencies to convert to the time domain. - */ - 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}); - - /* Reverse the filter for simpler processing, and store only the non-0 - * coefficients. - */ - auto fftiter = fftBuffer.get() + half_size + (Uhj2Encoder::sFilterSize-1); - for(float &coeff : Coeffs) - { - coeff = static_cast(fftiter->real() / double{fft_size}); - fftiter -= 2; - } - } -}; -const PhaseShifterT PShift{}; - -void allpass_process(al::span dst, const float *RESTRICT src) -{ -#ifdef HAVE_SSE_INTRINSICS - size_t pos{0}; - if(size_t todo{dst.size()>>1}) - { - do { - __m128 r04{_mm_setzero_ps()}; - __m128 r14{_mm_setzero_ps()}; - for(size_t j{0};j < PShift.Coeffs.size();j+=4) - { - const __m128 coeffs{_mm_load_ps(&PShift.Coeffs[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)); - } - r04 = _mm_add_ps(r04, _mm_shuffle_ps(r04, r04, _MM_SHUFFLE(0, 1, 2, 3))); - r04 = _mm_add_ps(r04, _mm_movehl_ps(r04, r04)); - dst[pos++] += _mm_cvtss_f32(r04); - - r14 = _mm_add_ps(r14, _mm_shuffle_ps(r14, r14, _MM_SHUFFLE(0, 1, 2, 3))); - r14 = _mm_add_ps(r14, _mm_movehl_ps(r14, r14)); - dst[pos++] += _mm_cvtss_f32(r14); - - src += 2; - } while(--todo); - } - if((dst.size()&1)) - { - __m128 r4{_mm_setzero_ps()}; - for(size_t j{0};j < PShift.Coeffs.size();j+=4) - { - const __m128 coeffs{_mm_load_ps(&PShift.Coeffs[j])}; - /* NOTE: This could alternatively be done with two unaligned loads - * and a shuffle. Which would be better? - */ - 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[pos] += _mm_cvtss_f32(r4); - } - -#elif defined(HAVE_NEON) - - size_t pos{0}; - if(size_t todo{dst.size()>>1}) - { - /* There doesn't seem to be NEON intrinsics to do this kind of stipple - * shuffling, so there's two custom methods for it. - */ - 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; - }; - 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; - }; - do { - float32x4_t r04{vdupq_n_f32(0.0f)}; - float32x4_t r14{vdupq_n_f32(0.0f)}; - for(size_t j{0};j < PShift.Coeffs.size();j+=4) - { - const float32x4_t coeffs{vld1q_f32(&PShift.Coeffs[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); - } - r04 = vaddq_f32(r04, vrev64q_f32(r04)); - dst[pos++] = vget_lane_f32(vadd_f32(vget_low_f32(r04), vget_high_f32(r04)), 0); - - r14 = vaddq_f32(r14, vrev64q_f32(r14)); - dst[pos++] = vget_lane_f32(vadd_f32(vget_low_f32(r14), vget_high_f32(r14)), 0); - - src += 2; - } while(--todo); - } - if((dst.size()&1)) - { - 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; - }; - float32x4_t r4{vdupq_n_f32(0.0f)}; - for(size_t j{0};j < PShift.Coeffs.size();j+=4) - { - const float32x4_t coeffs{vld1q_f32(&PShift.Coeffs[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 < PShift.Coeffs.size();++j) - ret += src[j*2] * PShift.Coeffs[j]; - - output += ret; - ++src; - } -#endif -} +const PhaseShifterT PShift{}; } // namespace -/* Encoding 2-channel UHJ from B-Format is done as: +/* 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.6511746*X) - 0.7071068*Y + * Q = 0.9772*Z * - * where j is a wide-band +90 degree phase shift. + * 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. * * The phase shift is done using a FIR filter derived from an FFT'd impulse * with the desired shift. */ -void Uhj2Encoder::encode(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, +void UhjEncoder::encode(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, const FloatBufferLine *InSamples, const size_t SamplesToDo) { + /* Given FuMa input, a +3dB boost is needed for the expected levels. */ + static constexpr float sqrt2{1.41421356237f}; + ASSUME(SamplesToDo > 0); float *RESTRICT left{al::assume_aligned<16>(LeftOut.data())}; @@ -233,43 +55,120 @@ void Uhj2Encoder::encode(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, 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 mid/side signal with the input. */ + /* 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 = std::copy(mMidDelay.cbegin(), mMidDelay.cend(), mMid.begin()); + 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 = std::copy(mSideDelay.cbegin(), mSideDelay.cend(), mSide.begin()); - std::transform(yinput, yinput+SamplesToDo, sideiter, - [](const float y) noexcept -> float { return 0.6554516f*y; }); - - /* Include any existing direct signal in the mid/side buffers. */ + { return 0.9396926f*sqrt2*w + 0.1855740f*sqrt2*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*sqrt2*y; }); for(size_t i{0};i < SamplesToDo;++i,++sideiter) *sideiter += left[i] - right[i]; - /* Copy the future samples back to the delay buffers for next time. */ - std::copy_n(mMid.cbegin()+SamplesToDo, mMidDelay.size(), mMidDelay.begin()); - std::copy_n(mSide.cbegin()+SamplesToDo, mSideDelay.size(), mSideDelay.begin()); - - /* Now add the all-passed signal into the side signal. */ - /* D += j(-0.3420201*W + 0.5098604*X) */ - auto tmpiter = std::copy(mSideHistory.cbegin(), mSideHistory.cend(), mTemp.begin()); + 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, mSideHistory.size(), mSideHistory.begin()); - allpass_process({mSide.data(), SamplesToDo}, mTemp.data()); + { return -0.3420201f*sqrt2*w + 0.5098604f*sqrt2*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] = (mMid[i] + mSide[i]) * 0.5f; + left[i] = (mS[i] + mD[i]) * 0.5f; /* Right = (S - D)/2.0 */ for(size_t i{0};i < SamplesToDo;i++) - right[i] = (mMid[i] - mSide[i]) * 0.5f; + 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.981530*S + 0.197484*j(0.828347*D + 0.767835*T) + * X = 0.418504*S - j(0.828347*D + 0.767835*T) + * Y = 0.795954*D - 0.676406*T + j(0.186626*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. + */ +void UhjDecoder::decode(const al::span samples, const size_t offset, + const size_t samplesToDo, const size_t forwardSamples) +{ + /* A -3dB attenuation is needed for FuMa output. */ + static constexpr float sqrt1_2{0.707106781187f}; + + ASSUME(samplesToDo > 0); + + { + const float *RESTRICT left{al::assume_aligned<16>(samples[0].data() + offset)}; + const float *RESTRICT right{al::assume_aligned<16>(samples[1].data() + offset)}; + const float *RESTRICT t{al::assume_aligned<16>(samples[2].data() + offset)}; + + /* S = Left + Right */ + for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + mS[i] = (left[i] + right[i]) * sqrt1_2; + + /* D = Left - Right */ + for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + mD[i] = (left[i] - right[i]) * sqrt1_2; + + /* T */ + for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + mT[i] = t[i] * sqrt1_2; + } + + float *RESTRICT woutput{al::assume_aligned<16>(samples[0].data() + offset)}; + float *RESTRICT xoutput{al::assume_aligned<16>(samples[1].data() + offset)}; + float *RESTRICT youtput{al::assume_aligned<16>(samples[2].data() + offset)}; + + /* Precompute j(0.828347*D + 0.767835*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.828347f*d + 0.767835f*t; }); + std::copy_n(mTemp.cbegin()+forwardSamples, mDTHistory.size(), mDTHistory.begin()); + PShift.process({xoutput, samplesToDo}, mTemp.data()); + + /* W = 0.981530*S + 0.197484*j(0.828347*D + 0.767835*T) */ + for(size_t i{0};i < samplesToDo;++i) + woutput[i] = 0.981530f*mS[i] + 0.197484f*xoutput[i]; + /* X = 0.418504*S - j(0.828347*D + 0.767835*T) */ + for(size_t i{0};i < samplesToDo;++i) + xoutput[i] = 0.418504f*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.795954*D - 0.676406*T + j(0.186626*S) */ + for(size_t i{0};i < samplesToDo;++i) + youtput[i] = 0.795954f*mD[i] - 0.676406f*mT[i] + 0.186626f*youtput[i]; + + if(samples.size() > 3) + { + float *RESTRICT zoutput{samples[3].data() + offset}; + /* Z = 1.023332*Q */ + for(size_t i{0};i < samplesToDo;++i) + zoutput[i] = 1.023332f*sqrt1_2*zoutput[i]; + } } diff --git a/external/openal/core/uhjfilter.h b/external/openal/core/uhjfilter.h index c2cb8722af..c04913b4aa 100644 --- a/external/openal/core/uhjfilter.h +++ b/external/openal/core/uhjfilter.h @@ -5,35 +5,60 @@ #include "almalloc.h" #include "bufferline.h" +#include "resampler_limits.h" -struct Uhj2Encoder { - /* A particular property of the filter allows it to cover nearly twice its - * length, so the filter size is also the effective delay (despite being - * center-aligned). +struct UhjEncoder { + /* The filter delay is half it's effective size, so a delay of 128 has a + * FIR length of 256. */ - constexpr static size_t sFilterSize{128}; + constexpr static size_t sFilterDelay{128}; - /* Delays for the unfiltered signal. */ - alignas(16) std::array mMidDelay{}; - alignas(16) std::array mSideDelay{}; - - alignas(16) std::array mMid{}; - alignas(16) std::array mSide{}; + /* 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 mSideHistory{}; + alignas(16) std::array mWXHistory{}; - alignas(16) std::array mTemp{}; + 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 scaling. */ - void encode(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, + void encode(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, const FloatBufferLine *InSamples, const size_t SamplesToDo); - DEF_NEWDEL(Uhj2Encoder) + DEF_NEWDEL(UhjEncoder) +}; + + +struct UhjDecoder { + constexpr static size_t sFilterDelay{128}; + + constexpr static size_t sLineSize{BufferLineSize+MaxResamplerPadding+sFilterDelay}; + using BufferLine = std::array; + + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mT{}; + + alignas(16) std::array mDTHistory{}; + alignas(16) std::array mSHistory{}; + + alignas(16) std::array mTemp{}; + + /** + * Decodes a 3- or 4-channel UHJ signal into a B-Format signal with FuMa + * channel ordering and scaling. For 3-channel, the 3rd channel may be + * attenuated by 'n', where 0 <= n <= 1. So 2-channel UHJ can be decoded by + * leaving the 3rd channel input silent (n=0). + */ + void decode(const al::span samples, const size_t offset, const size_t samplesToDo, + const size_t forwardSamples); + + DEF_NEWDEL(UhjDecoder) }; #endif /* CORE_UHJFILTER_H */ diff --git a/external/openal/core/uiddefs.cpp b/external/openal/core/uiddefs.cpp new file mode 100644 index 0000000000..244c01a5ab --- /dev/null +++ b/external/openal/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/external/openal/core/voice.cpp b/external/openal/core/voice.cpp new file mode 100644 index 0000000000..c764a27749 --- /dev/null +++ b/external/openal/core/voice.cpp @@ -0,0 +1,849 @@ + +#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(Voice::BufferLine)&15), "Voice::BufferLine must be a multiple of 16 bytes"); + +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{::new(evt_vec.first.buf) AsyncEvent{EventType_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(); +} + + +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 samples) noexcept +{ +#define HANDLE_FMT(T) case T: \ + { \ + constexpr size_t sampleSize{sizeof(al::FmtTypeTraits::Type)}; \ + if(srcchans == FmtUHJ2) \ + { \ + constexpr size_t srcstep{2u}; \ + src += srcOffset*srcstep*sampleSize; \ + al::LoadSampleArray(dstSamples[0].data() + dstOffset, src, \ + srcstep, samples); \ + al::LoadSampleArray(dstSamples[1].data() + dstOffset, \ + src + sampleSize, srcstep, samples); \ + std::fill_n(dstSamples[2].data() + dstOffset, samples, 0.0f); \ + } \ + else \ + { \ + const size_t srcstep{dstSamples.size()}; \ + src += srcOffset*srcstep*sampleSize; \ + for(auto &dst : dstSamples) \ + { \ + al::LoadSampleArray(dst.data() + dstOffset, src, srcstep, \ + samples); \ + src += sampleSize; \ + } \ + } \ + } \ + 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 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, MaxResamplerEdge, buffer->mSamples, dataPosInt, sampleType, + sampleChannels, remaining); + + if(const size_t toFill{samplesToLoad - remaining}) + { + for(auto &chanbuffer : voiceSamples) + { + auto srcsamples = chanbuffer.data() + MaxResamplerEdge - 1 + remaining; + 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, MaxResamplerEdge, buffer->mSamples, dataPosInt, sampleType, + sampleChannels, 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, MaxResamplerEdge + samplesLoaded, buffer->mSamples, + loopStart, sampleType, sampleChannels, toFill); + samplesLoaded += toFill; + } + } +} + +void LoadBufferCallback(VoiceBufferItem *buffer, const size_t numCallbackSamples, + const FmtType sampleType, const FmtChannels sampleChannels, 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, MaxResamplerEdge, buffer->mSamples, 0, sampleType, sampleChannels, + remaining); + + if(const size_t toFill{samplesToLoad - remaining}) + { + for(auto &chanbuffer : voiceSamples) + { + auto srcsamples = chanbuffer.data() + MaxResamplerEdge - 1 + remaining; + 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 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, MaxResamplerEdge+samplesLoaded, buffer->mSamples, dataPosInt, + sampleType, sampleChannels, 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.data() + MaxResamplerEdge - 1 + samplesLoaded; + 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, DeviceBase *Device) +{ + const uint IrSize{Device->mIrSize}; + auto &HrtfSamples = Device->HrtfSourceData; + /* Source HRTF mixing needs to include the direct delay so it remains + * aligned with the direct mix's HRTF filtering. + */ + float2 *AccumSamples{Device->HrtfAccumData + HrtfDirectDelay}; + + /* 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. */ + 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 = lerp(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 = lerp(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&VoiceIsFading) ? SamplesToDo : 0}; + if(!Counter) + { + /* No fading, just overwrite the old/current params. */ + for(auto &chandata : mChans) + { + { + DirectParams &parms = chandata.mDryParams; + if(!(mFlags&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); + + const uint PostPadding{MaxResamplerEdge + + ((mFmtChannels==FmtUHJ2 || mFmtChannels==FmtUHJ3 || mFmtChannels==FmtUHJ4) + ? 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+ResamplerPrePadding + * 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 <= LineSize - 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 = LineSize - MaxResamplerEdge; + + DataSize64 = SrcBufferSize - PostPadding; + DataSize64 = ((DataSize64<(DataSize64) & ~3u; + } + ASSUME(DstBufferSize > 0); + } + } + + if((mFlags&(VoiceIsCallback|VoiceCallbackStopped)) == VoiceIsCallback && BufferListItem) + { + if(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 |= VoiceCallbackStopped; + else if(static_cast(gotBytes) < needBytes) + { + mFlags |= VoiceCallbackStopped; + mNumCallbackSamples += static_cast(static_cast(gotBytes) / + mFrameSize); + } + else + mNumCallbackSamples = SrcBufferSize; + } + } + + if UNLIKELY(!BufferListItem) + { + for(auto &chanbuffer : mVoiceSamples) + { + auto srciter = chanbuffer.data() + MaxResamplerEdge; + auto srcend = chanbuffer.data() + MaxResamplerPadding; + + /* 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); }; + srciter = std::min_element(srciter, srcend, abs_lt); + + SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerPadding; + std::fill(srciter+1, chanbuffer.data() + SrcBufferSize, *srciter); + } + } + else + { + if((mFlags&VoiceIsStatic)) + LoadBufferStatic(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, mFmtChannels, + SrcBufferSize, mVoiceSamples); + else if((mFlags&VoiceIsCallback)) + LoadBufferCallback(BufferListItem, mNumCallbackSamples, mFmtType, mFmtChannels, + SrcBufferSize, mVoiceSamples); + else + LoadBufferQueue(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, mFmtChannels, + SrcBufferSize, mVoiceSamples); + + if(mDecoder) + { + const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits}; + SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerEdge; + mDecoder->decode(mVoiceSamples, MaxResamplerEdge, SrcBufferSize, srcOffset); + } + } + + auto voiceSamples = mVoiceSamples.begin(); + for(auto &chandata : mChans) + { + /* Resample, then apply ambisonic upsampling as needed. */ + float *ResampledData{Resample(&mResampleState, + voiceSamples->data() + MaxResamplerEdge, DataPosFrac, increment, + {Device->ResampledData, DstBufferSize})}; + if((mFlags&VoiceIsAmbisonic)) + chandata.mAmbiSplitter.processHfScale({ResampledData, DstBufferSize}, + chandata.mAmbiScale); + + /* 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&VoiceHasHrtf)) + { + const float TargetGain{UNLIKELY(vstate == Stopping) ? 0.0f : + parms.Hrtf.Target.Gain}; + DoHrtfMix(samples, DstBufferSize, parms, TargetGain, Counter, OutPos, Device); + } + else if((mFlags&VoiceHasNfc)) + { + const float *TargetGains{UNLIKELY(vstate == Stopping) ? SilentTarget.data() + : parms.Gains.Target.data()}; + DoNfcMix({samples, DstBufferSize}, mDirect.Buffer.data(), parms, TargetGains, + Counter, OutPos, Device); + } + else + { + const float *TargetGains{UNLIKELY(vstate == Stopping) ? SilentTarget.data() + : parms.Gains.Target.data()}; + 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{UNLIKELY(vstate == Stopping) ? SilentTarget.data() + : parms.Gains.Target.data()}; + MixSamples({samples, DstBufferSize}, mSend[send].Buffer, + parms.Gains.Current.data(), TargetGains, Counter, OutPos); + } + + /* Store the last source samples used for next time. */ + const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits}; + std::copy_n(voiceSamples->data()+srcOffset, MaxResamplerPadding, voiceSamples->data()); + ++voiceSamples; + } + /* 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&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&VoiceIsCallback)) + { + 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 |= 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&EventType_BufferCompleted)) + { + RingBuffer *ring{Context->mAsyncEvents.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); + } + } + + 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&EventType_SourceStateChange)) + SendSourceStoppedEvent(Context, SourceID); + } +} + +void Voice::prepare(DeviceBase *device) +{ + if((mFmtChannels == FmtUHJ2 || mFmtChannels == FmtUHJ3 || mFmtChannels==FmtUHJ4) && !mDecoder) + mDecoder = std::make_unique(); + else if(mFmtChannels != FmtUHJ2 && mFmtChannels != FmtUHJ3 && mFmtChannels != FmtUHJ4) + mDecoder = 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(mVoiceSamples.begin(), mVoiceSamples.end(), BufferLine{}); + + /* 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{(mFmtChannels == FmtBFormat2D) ? + 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.mAmbiScale = scales[*(OrderFromChan++)]; + chandata.mAmbiSplitter = splitter; + chandata.mDryParams = DirectParams{}; + std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); + } + mFlags |= VoiceIsAmbisonic; + } + else + { + for(auto &chandata : mChans) + { + chandata.mDryParams = DirectParams{}; + std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); + } + mFlags &= ~VoiceIsAmbisonic; + } + + if(device->AvgSpeakerDist > 0.0f) + { + const float w1{SpeedOfSoundMetersPerSec / + (device->AvgSpeakerDist * static_cast(device->Frequency))}; + for(auto &chandata : mChans) + chandata.mDryParams.NFCtrlFilter.init(w1); + } +} diff --git a/external/openal/core/voice.h b/external/openal/core/voice.h new file mode 100644 index 0000000000..c3347cda7c --- /dev/null +++ b/external/openal/core/voice.h @@ -0,0 +1,270 @@ +#ifndef CORE_VOICE_H +#define CORE_VOICE_H + +#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; + + /** 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) +}; + +constexpr uint VoiceIsStatic{ 1u<<0}; +constexpr uint VoiceIsCallback{ 1u<<1}; +constexpr uint VoiceIsAmbisonic{ 1u<<2}; /* Needs HF scaling for ambisonic upsampling. */ +constexpr uint VoiceCallbackStopped{1u<<3}; +constexpr uint VoiceIsFading{ 1u<<4}; /* Use gain stepping for smooth transitions. */ +constexpr uint VoiceHasHrtf{ 1u<<5}; +constexpr uint VoiceHasNfc{ 1u<<6}; + +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 mFrameSize; + AmbiLayout mAmbiLayout; + AmbiScaling mAmbiScaling; + uint mAmbiOrder; + + std::unique_ptr mDecoder; + + /** Current target parameters used for mixing. */ + uint mStep{0}; + + ResamplerFunc mResampler; + + InterpState mResampleState; + + uint 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). + */ + static constexpr size_t LineSize{BufferLineSize + MaxResamplerPadding + + UhjDecoder::sFilterDelay}; + using BufferLine = std::array; + al::vector mVoiceSamples{2}; + + struct ChannelData { + float mAmbiScale; + BandSplitter mAmbiSplitter; + + DirectParams mDryParams; + std::array mWetParams; + }; + al::vector mChans{2}; + + Voice() = default; + ~Voice() { delete mUpdate.exchange(nullptr, std::memory_order_acq_rel); } + + 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/external/openal/core/voice_change.h b/external/openal/core/voice_change.h new file mode 100644 index 0000000000..ddc6186f5d --- /dev/null +++ b/external/openal/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/external/openal/examples/common/alhelpers.c b/external/openal/examples/common/alhelpers.c new file mode 100644 index 0000000000..1d498ffa61 --- /dev/null +++ b/external/openal/examples/common/alhelpers.c @@ -0,0 +1,193 @@ +/* + * OpenAL Helpers + * + * Copyright (c) 2011 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 routines to help with some menial OpenAL-related tasks, + * such as opening a device and setting up a context, closing the device and + * destroying its context, converting between frame counts and byte lengths, + * finding an appropriate buffer format, and getting readable strings for + * channel configs and sample types. */ + +#include "alhelpers.h" + +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + + +/* InitAL opens a device and sets up a context using default attributes, making + * the program ready to call OpenAL functions. */ +int InitAL(char ***argv, int *argc) +{ + const ALCchar *name; + ALCdevice *device; + ALCcontext *ctx; + + /* Open and initialize a device */ + device = NULL; + if(argc && argv && *argc > 1 && strcmp((*argv)[0], "-device") == 0) + { + device = alcOpenDevice((*argv)[1]); + if(!device) + fprintf(stderr, "Failed to open \"%s\", trying default\n", (*argv)[1]); + (*argv) += 2; + (*argc) -= 2; + } + if(!device) + device = alcOpenDevice(NULL); + if(!device) + { + fprintf(stderr, "Could not open a device!\n"); + return 1; + } + + ctx = alcCreateContext(device, NULL); + if(ctx == NULL || alcMakeContextCurrent(ctx) == ALC_FALSE) + { + if(ctx != NULL) + alcDestroyContext(ctx); + alcCloseDevice(device); + fprintf(stderr, "Could not set a context!\n"); + return 1; + } + + name = NULL; + if(alcIsExtensionPresent(device, "ALC_ENUMERATE_ALL_EXT")) + name = alcGetString(device, ALC_ALL_DEVICES_SPECIFIER); + if(!name || alcGetError(device) != AL_NO_ERROR) + name = alcGetString(device, ALC_DEVICE_SPECIFIER); + printf("Opened \"%s\"\n", name); + + return 0; +} + +/* CloseAL closes the device belonging to the current context, and destroys the + * context. */ +void CloseAL(void) +{ + ALCdevice *device; + ALCcontext *ctx; + + ctx = alcGetCurrentContext(); + if(ctx == NULL) + return; + + device = alcGetContextsDevice(ctx); + + alcMakeContextCurrent(NULL); + alcDestroyContext(ctx); + alcCloseDevice(device); +} + + +const char *FormatName(ALenum format) +{ + switch(format) + { + case AL_FORMAT_MONO8: return "Mono, U8"; + case AL_FORMAT_MONO16: return "Mono, S16"; + case AL_FORMAT_MONO_FLOAT32: return "Mono, Float32"; + case AL_FORMAT_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"; +} + + +#ifdef _WIN32 + +#define WIN32_LEAN_AND_MEAN +#include +#include + +int altime_get(void) +{ + static int start_time = 0; + int cur_time; + union { + FILETIME ftime; + ULARGE_INTEGER ulint; + } systime; + GetSystemTimeAsFileTime(&systime.ftime); + /* FILETIME is in 100-nanosecond units, or 1/10th of a microsecond. */ + cur_time = (int)(systime.ulint.QuadPart/10000); + + if(!start_time) + start_time = cur_time; + return cur_time - start_time; +} + +void al_nssleep(unsigned long nsec) +{ + Sleep(nsec / 1000000); +} + +#else + +#include +#include +#include + +int altime_get(void) +{ + static int start_time = 0u; + int cur_time; + +#if _POSIX_TIMERS > 0 + struct timespec ts; + int ret = clock_gettime(CLOCK_REALTIME, &ts); + if(ret != 0) return 0; + 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 = (int)(tv.tv_sec*1000 + tv.tv_usec/1000); +#endif + + if(!start_time) + start_time = cur_time; + return cur_time - start_time; +} + +void al_nssleep(unsigned long nsec) +{ + struct timespec ts, rem; + ts.tv_sec = (time_t)(nsec / 1000000000ul); + ts.tv_nsec = (long)(nsec % 1000000000ul); + while(nanosleep(&ts, &rem) == -1 && errno == EINTR) + ts = rem; +} + +#endif diff --git a/external/openal/examples/common/alhelpers.h b/external/openal/examples/common/alhelpers.h new file mode 100644 index 0000000000..3752d2180a --- /dev/null +++ b/external/openal/examples/common/alhelpers.h @@ -0,0 +1,25 @@ +#ifndef ALHELPERS_H +#define ALHELPERS_H + +#include "AL/al.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Some helper functions to get the name from the format enums. */ +const char *FormatName(ALenum type); + +/* Easy device init/deinit functions. InitAL returns 0 on success. */ +int InitAL(char ***argv, int *argc); +void CloseAL(void); + +/* Cross-platform timeget and sleep functions. */ +int altime_get(void); +void al_nssleep(unsigned long nsec); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* ALHELPERS_H */