diff --git a/CMakeLists.txt b/CMakeLists.txt index 609ae6c17..8a4b73947 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,7 +121,11 @@ setupVersionNumbers() if(NOT TORQUE_TEMPLATE) set(TORQUE_TEMPLATE "BaseGame" CACHE STRING "the template to use") endif() -installTemplate(${TORQUE_TEMPLATE}) + +if (NOT TORQUE_INSTALLED_TEMPLATE) + installTemplate(${TORQUE_TEMPLATE}) + set(TORQUE_INSTALLED_TEMPLATE TRUE CACHE BOOL "Whether or not the game template is installed. This may be reset to false (or removed) to force a reinstall.") +endif(NOT TORQUE_INSTALLED_TEMPLATE) # Generate torqueConfig.h in our temp directory CONFIGURE_FILE("${CMAKE_SOURCE_DIR}/Tools/CMake/torqueConfig.h.in" "${CMAKE_BINARY_DIR}/temp/torqueConfig.h") diff --git a/Engine/lib/CMakeLists.txt b/Engine/lib/CMakeLists.txt index bb80991f0..1d4c9f955 100644 --- a/Engine/lib/CMakeLists.txt +++ b/Engine/lib/CMakeLists.txt @@ -1,8 +1,5 @@ # Ask CMake to perform builds in a temporary directory for all of these. # We also use EXCLUDE_FROM_ALL to ensure we only build and install what we want -set(BUILD_SHARED_LIBS off CACHE STRING "") -add_subdirectory(assimp ${CMAKE_BINARY_DIR}/temp/assimp EXCLUDE_FROM_ALL) - set(SDL_SHARED on CACHE BOOL "" FORCE) add_subdirectory(sdl ${CMAKE_BINARY_DIR}/temp/sdl2 EXCLUDE_FROM_ALL) @@ -16,6 +13,14 @@ set(PNG_BUILD_ZLIB on CACHE BOOL "" FORCE) get_filename_component(ZLIB_ROOT "zlib" REALPATH BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(ZLIB_ROOT "${ZLIB_ROOT}" CACHE STRING "ZLib root location" FORCE) +# Assimp depends on zlib +set(BUILD_SHARED_LIBS off CACHE STRING "") +set(ASSIMP_BUILD_ZLIB on CACHE STRING "") +set(ASSIMP_HUNTER_ENABLED off CACHE STRING "") +add_subdirectory(assimp ${CMAKE_BINARY_DIR}/temp/assimp EXCLUDE_FROM_ALL) + + + add_subdirectory(lpng ${CMAKE_BINARY_DIR}/temp/lpng EXCLUDE_FROM_ALL) add_subdirectory(ljpeg ${CMAKE_BINARY_DIR}/temp/ljpeg EXCLUDE_FROM_ALL) diff --git a/Engine/lib/convexDecomp/CMakeLists.txt b/Engine/lib/convexDecomp/CMakeLists.txt index 6328b01db..0a58bb89f 100644 --- a/Engine/lib/convexDecomp/CMakeLists.txt +++ b/Engine/lib/convexDecomp/CMakeLists.txt @@ -1,3 +1,7 @@ file(GLOB CONVEX_DECOMP_SOURCES "*.cpp") add_library(convexDecomp STATIC ${CONVEX_DECOMP_SOURCES}) target_include_directories(convexDecomp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +if (UNIX AND NOT APPLE) + target_compile_definitions(convexDecomp PUBLIC LINUX) +endif (UNIX AND NOT APPLE) \ No newline at end of file diff --git a/Engine/lib/openal-soft/.github/workflows/ci.yml b/Engine/lib/openal-soft/.github/workflows/ci.yml new file mode 100644 index 000000000..7f044ffd7 --- /dev/null +++ b/Engine/lib/openal-soft/.github/workflows/ci.yml @@ -0,0 +1,66 @@ +name: CI + +on: [push] + +jobs: + build: + name: ${{matrix.config.name}} + runs-on: ${{matrix.config.os}} + strategy: + fail-fast: false + matrix: + config: + - { + name: "Visual Studio 64-bit", + os: windows-latest, + cmake_opts: "-A x64 \ + -DALSOFT_BUILD_ROUTER=ON \ + -DALSOFT_REQUIRE_WINMM=ON \ + -DALSOFT_REQUIRE_DSOUND=ON \ + -DALSOFT_REQUIRE_WASAPI=ON", + build_type: "Release" + } + - { + name: "macOS", + os: macos-latest, + cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON", + build_type: "Release" + } + - { + name: "Linux", + os: ubuntu-latest, + cmake_opts: "-DALSOFT_REQUIRE_RTKIT=ON \ + -DALSOFT_REQUIRE_ALSA=ON \ + -DALSOFT_REQUIRE_OSS=ON \ + -DALSOFT_REQUIRE_PORTAUDIO=ON \ + -DALSOFT_REQUIRE_PULSEAUDIO=ON \ + -DALSOFT_REQUIRE_JACK=ON", + deps_cmdline: "sudo apt update && sudo apt-get install -qq \ + libpulse-dev \ + portaudio19-dev \ + libasound2-dev \ + libjack-dev \ + qtbase5-dev \ + libdbus-1-dev", + build_type: "Release" + } + + steps: + - uses: actions/checkout@v1 + + - name: Install Dependencies + shell: bash + run: | + if [[ ! -z "${{matrix.config.deps_cmdline}}" ]]; then + eval ${{matrix.config.deps_cmdline}} + fi + + - name: Configure + shell: bash + run: | + cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} ${{matrix.config.cmake_opts}} . + + - name: Build + shell: bash + run: | + cmake --build build --config ${{matrix.config.build_type}} diff --git a/Engine/lib/openal-soft/.travis.yml b/Engine/lib/openal-soft/.travis.yml index 5931c5217..f85da5935 100644 --- a/Engine/lib/openal-soft/.travis.yml +++ b/Engine/lib/openal-soft/.travis.yml @@ -26,7 +26,8 @@ install: portaudio19-dev \ libasound2-dev \ libjack-dev \ - qtbase5-dev + qtbase5-dev \ + libdbus-1-dev fi - > if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then diff --git a/Engine/lib/openal-soft/Alc/alcontext.h b/Engine/lib/openal-soft/Alc/alcontext.h deleted file mode 100644 index 31160bb27..000000000 --- a/Engine/lib/openal-soft/Alc/alcontext.h +++ /dev/null @@ -1,282 +0,0 @@ -#ifndef ALCONTEXT_H -#define ALCONTEXT_H - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "AL/al.h" -#include "AL/alc.h" - -#include "al/listener.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "alu.h" -#include "atomic.h" -#include "inprogext.h" -#include "intrusive_ptr.h" -#include "threads.h" -#include "vecmat.h" -#include "vector.h" - -struct ALeffectslot; -struct ALsource; -struct EffectSlot; -struct EffectSlotProps; -struct RingBuffer; -struct Voice; -struct VoiceChange; -struct VoicePropsItem; - - -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 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 { - const al::intrusive_ptr 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}; - - 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; - - /* Wet buffers used by effect slots. */ - al::vector mWetBuffers; - - - std::atomic_flag mPropsClean; - 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(); - - [[gnu::format(printf,3,4)]] void setError(ALenum errorCode, const char *msg, ...); - - 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 /* ALCONTEXT_H */ diff --git a/Engine/lib/openal-soft/Alc/backends/oboe.cpp b/Engine/lib/openal-soft/Alc/backends/oboe.cpp deleted file mode 100644 index 5b0d121d5..000000000 --- a/Engine/lib/openal-soft/Alc/backends/oboe.cpp +++ /dev/null @@ -1,250 +0,0 @@ - -#include "config.h" - -#include "oboe.h" - -#include -#include - -#include "alu.h" -#include "core/logging.h" - -#include "oboe/Oboe.h" - - -namespace { - -constexpr char device_name[] = "Oboe Default"; - - -struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback { - OboePlayback(ALCdevice *device) : BackendBase{device} { } - - oboe::ManagedStream mStream; - - oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, - int32_t numFrames) override; - - void open(const char *name) override; - bool reset() override; - void start() override; - void stop() override; -}; - - -oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData, - int32_t numFrames) -{ - assert(numFrames > 0); - const int32_t numChannels{oboeStream->getChannelCount()}; - - if UNLIKELY(numChannels > 2 && mDevice->FmtChans == DevFmtStereo) - { - /* If the device is only mixing stereo but there's more than two - * output channels, there are unused channels that need to be silenced. - */ - if(mStream->getFormat() == oboe::AudioFormat::Float) - memset(audioData, 0, static_cast(numFrames*numChannels)*sizeof(float)); - else - memset(audioData, 0, static_cast(numFrames*numChannels)*sizeof(int16_t)); - } - - mDevice->renderSamples(audioData, static_cast(numFrames), - static_cast(numChannels)); - return oboe::DataCallbackResult::Continue; -} - - -void OboePlayback::open(const char *name) -{ - if(!name) - name = device_name; - else if(std::strcmp(name, device_name) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; - - /* Open a basic output stream, just to ensure it can work. */ - oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output) - ->setPerformanceMode(oboe::PerformanceMode::LowLatency) - ->openManagedStream(mStream)}; - if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", - oboe::convertToText(result)}; - - mDevice->DeviceName = name; -} - -bool OboePlayback::reset() -{ - oboe::AudioStreamBuilder builder; - builder.setDirection(oboe::Direction::Output); - builder.setPerformanceMode(oboe::PerformanceMode::LowLatency); - /* Don't let Oboe convert. We should be able to handle anything it gives - * back. - */ - builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None); - builder.setChannelConversionAllowed(false); - builder.setFormatConversionAllowed(false); - builder.setCallback(this); - - if(mDevice->Flags.test(FrequencyRequest)) - builder.setSampleRate(static_cast(mDevice->Frequency)); - if(mDevice->Flags.test(ChannelsRequest)) - { - /* Only use mono or stereo at user request. There's no telling what - * other counts may be inferred as. - */ - builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono - : (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo - : oboe::ChannelCount::Unspecified); - } - if(mDevice->Flags.test(SampleTypeRequest)) - { - oboe::AudioFormat format{oboe::AudioFormat::Unspecified}; - switch(mDevice->FmtType) - { - case DevFmtByte: - case DevFmtUByte: - case DevFmtShort: - case DevFmtUShort: - format = oboe::AudioFormat::I16; - break; - case DevFmtInt: - case DevFmtUInt: - case DevFmtFloat: - format = oboe::AudioFormat::Float; - break; - } - builder.setFormat(format); - } - - oboe::Result result{builder.openManagedStream(mStream)}; - /* If the format failed, try asking for the defaults. */ - while(result == oboe::Result::ErrorInvalidFormat) - { - if(builder.getFormat() != oboe::AudioFormat::Unspecified) - builder.setFormat(oboe::AudioFormat::Unspecified); - else if(builder.getSampleRate() != oboe::kUnspecified) - builder.setSampleRate(oboe::kUnspecified); - else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified) - builder.setChannelCount(oboe::ChannelCount::Unspecified); - else - break; - result = builder.openManagedStream(mStream); - } - if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", - oboe::convertToText(result)}; - TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); - - switch(mStream->getChannelCount()) - { - case oboe::ChannelCount::Mono: - mDevice->FmtChans = DevFmtMono; - break; - case oboe::ChannelCount::Stereo: - mDevice->FmtChans = DevFmtStereo; - break; - /* Other potential configurations. Could be wrong, but better than failing. - * Assume WFX channel order. - */ - case 4: - mDevice->FmtChans = DevFmtQuad; - break; - case 6: - mDevice->FmtChans = DevFmtX51Rear; - break; - case 7: - mDevice->FmtChans = DevFmtX61; - break; - case 8: - mDevice->FmtChans = DevFmtX71; - break; - default: - if(mStream->getChannelCount() < 1) - throw al::backend_exception{al::backend_error::DeviceError, - "Got unhandled channel count: %d", mStream->getChannelCount()}; - /* Assume first two channels are front left/right. We can do a stereo - * mix and keep the other channels silent. - */ - mDevice->FmtChans = DevFmtStereo; - break; - } - setDefaultWFXChannelOrder(); - - switch(mStream->getFormat()) - { - case oboe::AudioFormat::I16: - mDevice->FmtType = DevFmtShort; - break; - case oboe::AudioFormat::Float: - mDevice->FmtType = DevFmtFloat; - break; - case oboe::AudioFormat::Unspecified: - case oboe::AudioFormat::Invalid: - throw al::backend_exception{al::backend_error::DeviceError, - "Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())}; - } - mDevice->Frequency = static_cast(mStream->getSampleRate()); - - /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0 - * indicating variable updates, but OpenAL should have a reasonable minimum update size set. - * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum - * update size. - */ - mDevice->UpdateSize = maxu(mDevice->Frequency / 100, - static_cast(mStream->getFramesPerBurst())); - mDevice->BufferSize = maxu(mDevice->UpdateSize * 2, - static_cast(mStream->getBufferSizeInFrames())); - - return true; -} - -void OboePlayback::start() -{ - const oboe::Result result{mStream->start()}; - if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s", - oboe::convertToText(result)}; -} - -void OboePlayback::stop() -{ - oboe::Result result{mStream->stop()}; - if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", - oboe::convertToText(result)}; -} - -} // namespace - -bool OboeBackendFactory::init() { return true; } - -bool OboeBackendFactory::querySupport(BackendType type) -{ return type == BackendType::Playback; } - -std::string OboeBackendFactory::probe(BackendType type) -{ - switch(type) - { - case BackendType::Playback: - /* Includes null char. */ - return std::string{device_name, sizeof(device_name)}; - case BackendType::Capture: - break; - } - return std::string{}; -} - -BackendPtr OboeBackendFactory::createBackend(ALCdevice *device, BackendType type) -{ - if(type == BackendType::Playback) - return BackendPtr{new OboePlayback{device}}; - return nullptr; -} - -BackendFactory &OboeBackendFactory::getFactory() -{ - static OboeBackendFactory factory{}; - return factory; -} diff --git a/Engine/lib/openal-soft/Alc/compat.h b/Engine/lib/openal-soft/Alc/compat.h deleted file mode 100644 index 960b4b947..000000000 --- a/Engine/lib/openal-soft/Alc/compat.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef AL_COMPAT_H -#define AL_COMPAT_H - -#include - -struct PathNamePair { std::string path, fname; }; -const PathNamePair &GetProcBinary(void); - -#endif /* AL_COMPAT_H */ diff --git a/Engine/lib/openal-soft/Alc/voice.cpp b/Engine/lib/openal-soft/Alc/voice.cpp deleted file mode 100644 index 3b795b040..000000000 --- a/Engine/lib/openal-soft/Alc/voice.cpp +++ /dev/null @@ -1,793 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include "voice.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alcmain.h" -#include "albyte.h" -#include "alconfig.h" -#include "alcontext.h" -#include "alnumeric.h" -#include "aloptional.h" -#include "alspan.h" -#include "alstring.h" -#include "alu.h" -#include "async_event.h" -#include "buffer_storage.h" -#include "core/cpu_caps.h" -#include "core/devformat.h" -#include "core/filters/biquad.h" -#include "core/filters/nfc.h" -#include "core/filters/splitter.h" -#include "core/fmt_traits.h" -#include "core/logging.h" -#include "core/mixer/defs.h" -#include "core/mixer/hrtfdefs.h" -#include "hrtf.h" -#include "inprogext.h" -#include "opthelpers.h" -#include "ringbuffer.h" -#include "threads.h" -#include "vector.h" -#include "voice_change.h" - -struct CTag; -#ifdef HAVE_SSE -struct SSETag; -#endif -#ifdef HAVE_NEON -struct NEONTag; -#endif -struct CopyTag; - - -Resampler ResamplerDefault{Resampler::Linear}; - -MixerFunc MixSamples{Mix_}; - -namespace { - -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 aluInitMixer() -{ - if(auto resopt = ConfigValueStr(nullptr, nullptr, "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{resopt->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(ALCcontext *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 = VChangeState::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(float *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype, - const size_t samples) noexcept -{ -#define HANDLE_FMT(T) case T: al::LoadSampleArray(dst, src, srcstep, samples); break - switch(srctype) - { - HANDLE_FMT(FmtUByte); - HANDLE_FMT(FmtShort); - HANDLE_FMT(FmtFloat); - HANDLE_FMT(FmtDouble); - HANDLE_FMT(FmtMulaw); - HANDLE_FMT(FmtAlaw); - } -#undef HANDLE_FMT -} - -float *LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *&bufferLoopItem, - const size_t numChannels, const FmtType sampleType, const size_t sampleSize, const size_t chan, - size_t dataPosInt, al::span srcBuffer) -{ - 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) - { - bufferLoopItem = nullptr; - - /* Load what's left to play from the buffer */ - const size_t DataRem{minz(srcBuffer.size(), buffer->mSampleLen-dataPosInt)}; - - const al::byte *Data{buffer->mSamples + (dataPosInt*numChannels + chan)*sampleSize}; - LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataRem); - srcBuffer = srcBuffer.subspan(DataRem); - } - else - { - /* Load what's left of this loop iteration */ - const size_t DataRem{minz(srcBuffer.size(), LoopEnd-dataPosInt)}; - - const al::byte *Data{buffer->mSamples + (dataPosInt*numChannels + chan)*sampleSize}; - LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataRem); - srcBuffer = srcBuffer.subspan(DataRem); - - /* Load any repeats of the loop we can to fill the buffer. */ - const auto LoopSize = static_cast(LoopEnd - LoopStart); - while(!srcBuffer.empty()) - { - const size_t DataSize{minz(srcBuffer.size(), LoopSize)}; - - Data = buffer->mSamples + (LoopStart*numChannels + chan)*sampleSize; - - LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataSize); - srcBuffer = srcBuffer.subspan(DataSize); - } - } - return srcBuffer.begin(); -} - -float *LoadBufferCallback(VoiceBufferItem *buffer, const size_t numChannels, - const FmtType sampleType, const size_t sampleSize, const size_t chan, - size_t numCallbackSamples, al::span srcBuffer) -{ - /* Load what's left to play from the buffer */ - const size_t DataRem{minz(srcBuffer.size(), numCallbackSamples)}; - - const al::byte *Data{buffer->mSamples + chan*sampleSize}; - LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataRem); - srcBuffer = srcBuffer.subspan(DataRem); - - return srcBuffer.begin(); -} - -float *LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, - const size_t numChannels, const FmtType sampleType, const size_t sampleSize, const size_t chan, - size_t dataPosInt, al::span srcBuffer) -{ - /* Crawl the buffer queue to fill in the temp buffer */ - while(buffer && !srcBuffer.empty()) - { - if(dataPosInt >= buffer->mSampleLen) - { - dataPosInt -= buffer->mSampleLen; - buffer = buffer->mNext.load(std::memory_order_acquire); - if(!buffer) buffer = bufferLoopItem; - continue; - } - - const size_t DataSize{minz(srcBuffer.size(), buffer->mSampleLen-dataPosInt)}; - - const al::byte *Data{buffer->mSamples + (dataPosInt*numChannels + chan)*sampleSize}; - LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataSize); - srcBuffer = srcBuffer.subspan(DataSize); - if(srcBuffer.empty()) break; - - dataPosInt = 0; - buffer = buffer->mNext.load(std::memory_order_acquire); - if(!buffer) buffer = bufferLoopItem; - } - - return srcBuffer.begin(); -} - - -void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &parms, - const float TargetGain, const uint Counter, uint OutPos, const uint IrSize, - ALCdevice *Device) -{ - 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; - hrtfparams.Coeffs = &parms.Hrtf.Target.Coeffs; - hrtfparams.Delay = parms.Hrtf.Target.Delay; - hrtfparams.Gain = 0.0f; - hrtfparams.GainStep = 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; - hrtfparams.Coeffs = &parms.Hrtf.Target.Coeffs; - hrtfparams.Delay = parms.Hrtf.Target.Delay; - hrtfparams.Gain = parms.Hrtf.Old.Gain; - hrtfparams.GainStep = (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, ALCdevice *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, ALCcontext *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 FmtType SampleType{mFmtType}; - const uint SampleSize{mSampleSize}; - 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; - } - - ASSUME(SampleSize > 0); - - const size_t FrameSize{mChans.size() * SampleSize}; - ASSUME(FrameSize > 0); - - ALCdevice *Device{Context->mDevice.get()}; - const uint NumSends{Device->NumAuxSends}; - const uint IrSize{Device->mIrSize}; - - 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; - } - } - } - - float fadeCoeff{1.0f}, fadeGain{1.0f}; - if UNLIKELY(vstate == Stopping) - { - /* Calculate the multiplier for fading the resampled signal by -60dB - * over 1ms. - */ - fadeCoeff = std::pow(0.001f, 1000.0f/static_cast(Device->Frequency)); - } - - 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 + MaxResamplerPadding; - - /* Result is guaranteed to be <= BufferLineSize+MaxResamplerPadding - * 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 += MaxResamplerPadding; - - if(DataSize64 <= BufferLineSize + MaxResamplerPadding) - 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 = BufferLineSize + MaxResamplerPadding; - - DataSize64 = SrcBufferSize - MaxResamplerPadding; - DataSize64 = ((DataSize64<(DataSize64) & ~3u; - } - } - } - - if((mFlags&(VoiceIsCallback|VoiceCallbackStopped)) == VoiceIsCallback && BufferListItem) - { - /* Exclude resampler pre-padding from the needed size. */ - const uint toLoad{SrcBufferSize - (MaxResamplerPadding>>1)}; - if(toLoad > mNumCallbackSamples) - { - const size_t byteOffset{mNumCallbackSamples*FrameSize}; - const size_t needBytes{toLoad*FrameSize - byteOffset}; - - const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData, - &BufferListItem->mSamples[byteOffset], static_cast(needBytes))}; - if(gotBytes < 1) - mFlags |= VoiceCallbackStopped; - else if(static_cast(gotBytes) < needBytes) - { - mFlags |= VoiceCallbackStopped; - mNumCallbackSamples += static_cast(static_cast(gotBytes) / - FrameSize); - } - else - mNumCallbackSamples = toLoad; - } - } - - const float fadeVal{fadeGain}; - const size_t num_chans{mChans.size()}; - size_t chan_idx{0}; - ASSUME(DstBufferSize > 0); - for(auto &chandata : mChans) - { - const al::span SrcData{Device->SourceData, SrcBufferSize}; - - /* Load the previous samples into the source data first, then load - * what we can from the buffer queue. - */ - auto srciter = std::copy_n(chandata.mPrevSamples.begin(), MaxResamplerPadding>>1, - SrcData.begin()); - - if UNLIKELY(!BufferListItem) - srciter = std::copy(chandata.mPrevSamples.begin()+(MaxResamplerPadding>>1), - chandata.mPrevSamples.end(), srciter); - else if((mFlags&VoiceIsStatic)) - srciter = LoadBufferStatic(BufferListItem, BufferLoopItem, num_chans, SampleType, - SampleSize, chan_idx, DataPosInt, {srciter, SrcData.end()}); - else if((mFlags&VoiceIsCallback)) - srciter = LoadBufferCallback(BufferListItem, num_chans, SampleType, SampleSize, - chan_idx, mNumCallbackSamples, {srciter, SrcData.end()}); - else - srciter = LoadBufferQueue(BufferListItem, BufferLoopItem, num_chans, SampleType, - SampleSize, chan_idx, DataPosInt, {srciter, SrcData.end()}); - - if UNLIKELY(srciter != SrcData.end()) - { - /* If the source buffer wasn't filled, copy the last sample for - * the remaining buffer. Ideally it should have ended with - * silence, but if not the gain fading should help avoid clicks - * from sudden amplitude changes. - */ - const float sample{*(srciter-1)}; - std::fill(srciter, SrcData.end(), sample); - } - - /* Store the last source samples used for next time. */ - std::copy_n(&SrcData[(increment*DstBufferSize + DataPosFrac)>>MixerFracBits], - chandata.mPrevSamples.size(), chandata.mPrevSamples.begin()); - - /* Resample, then apply ambisonic upsampling as needed. */ - float *ResampledData{Resample(&mResampleState, &SrcData[MaxResamplerPadding>>1], - DataPosFrac, increment, {Device->ResampledData, DstBufferSize})}; - if((mFlags&VoiceIsAmbisonic)) - chandata.mAmbiSplitter.processHfScale({ResampledData, DstBufferSize}, - chandata.mAmbiScale); - - if UNLIKELY(vstate == Stopping) - { - fadeGain = fadeVal; - for(float &sample : al::span{ResampledData, DstBufferSize}) - { - fadeGain *= fadeCoeff; - sample *= fadeGain; - } - } - - /* Now filter and mix to the appropriate outputs. */ - float (&FilterBuf)[BufferLineSize] = Device->FilteredData; - { - DirectParams &parms = chandata.mDryParams; - const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf, - {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, IrSize, - 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, - {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); - } - - ++chan_idx; - } - /* 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*FrameSize}; - const size_t byteEnd{mNumCallbackSamples*FrameSize}; - 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); - } -} diff --git a/Engine/lib/openal-soft/BSD-3Clause b/Engine/lib/openal-soft/BSD-3Clause index 45cc8e6d3..b1c2dbd76 100644 --- a/Engine/lib/openal-soft/BSD-3Clause +++ b/Engine/lib/openal-soft/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/Engine/lib/openal-soft/CMakeLists.txt b/Engine/lib/openal-soft/CMakeLists.txt index ca72f6107..2edb30a49 100644 --- a/Engine/lib/openal-soft/CMakeLists.txt +++ b/Engine/lib/openal-soft/CMakeLists.txt @@ -2,21 +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_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") @@ -69,8 +72,11 @@ include(CheckCXXCompilerFlag) include(CheckCSourceCompiles) include(CheckCXXSourceCompiles) include(CheckStructHasMember) +include(CMakePackageConfigHelpers) include(GNUInstallDirs) +find_package(PkgConfig) + option(ALSOFT_DLOPEN "Check for the dlopen API for loading optional libs" ON) @@ -89,6 +95,8 @@ option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...) option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" ON) option(ALSOFT_UPDATE_BUILD_VERSION "Update git build version info" ON) +option(ALSOFT_EAX "Enable legacy EAX extensions" ${WIN32}) + if(DEFINED SHARE_INSTALL_DIR) message(WARNING "SHARE_INSTALL_DIR is deprecated. Use the variables provided by the GNUInstallDirs module instead") set(CMAKE_INSTALL_DATADIR "${SHARE_INSTALL_DIR}") @@ -115,7 +123,7 @@ set(LINKER_FLAGS ) set(EXTRA_LIBS ) if(WIN32) - set(CPP_DEFS ${CPP_DEFS} _WIN32) + set(CPP_DEFS ${CPP_DEFS} _WIN32 NOMINMAX) if(MINGW) set(CPP_DEFS ${CPP_DEFS} __USE_MINGW_ANSI_STDIO) endif() @@ -142,7 +150,7 @@ if(NOT LIBTYPE) endif() set(LIB_MAJOR_VERSION "1") -set(LIB_MINOR_VERSION "21") +set(LIB_MINOR_VERSION "22") set(LIB_REVISION "0") set(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}") set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0) @@ -196,20 +204,22 @@ else() endif() unset(OLD_REQUIRED_LIBRARIES) -# Include liblog for Android logging -check_library_exists(log __android_log_print "" HAVE_LIBLOG) -if(HAVE_LIBLOG) - set(EXTRA_LIBS log ${EXTRA_LIBS}) - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} log) +if(ANDROID) + # Include liblog for Android logging + check_library_exists(log __android_log_print "" HAVE_LIBLOG) + if(HAVE_LIBLOG) + set(EXTRA_LIBS log ${EXTRA_LIBS}) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} log) + endif() endif() if(MSVC) - set(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS NOMINMAX) + set(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS) check_cxx_compiler_flag(/permissive- HAVE_PERMISSIVE_SWITCH) if(HAVE_PERMISSIVE_SWITCH) set(C_FLAGS ${C_FLAGS} $<$:/permissive->) endif() - set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4268 /wd4324 /wd5030) + set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4127 /wd4268 /wd4324 /wd5030) # Remove /W3, which is added by default, since we set /W4. Some build # generators with MSVC complain about both /W3 and /W4 being specified. foreach(flag_var CMAKE_C_FLAGS CMAKE_CXX_FLAGS) @@ -345,17 +355,17 @@ endif() set(SSE2_SWITCH "") -set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) if(NOT MSVC) + set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) # Yes GCC, really don't accept command line options you don't support set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Werror") + check_c_compiler_flag(-msse2 HAVE_MSSE2_SWITCH) + if(HAVE_MSSE2_SWITCH) + set(SSE2_SWITCH "-msse2") + endif() + set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS}) + unset(OLD_REQUIRED_FLAGS) endif() -check_c_compiler_flag(-msse2 HAVE_MSSE2_SWITCH) -if(HAVE_MSSE2_SWITCH) - set(SSE2_SWITCH "-msse2") -endif() -set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS}) -unset(OLD_REQUIRED_FLAGS) check_include_file(xmmintrin.h HAVE_XMMINTRIN_H) check_include_file(emmintrin.h HAVE_EMMINTRIN_H) @@ -369,20 +379,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() @@ -435,7 +450,14 @@ set(FPMATH_SET "0") if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2) option(ALSOFT_ENABLE_SSE2_CODEGEN "Enable SSE2 code generation instead of x87 for 32-bit targets." TRUE) if(ALSOFT_ENABLE_SSE2_CODEGEN) - if(SSE2_SWITCH OR NOT MSVC) + if(MSVC) + check_c_compiler_flag("/arch:SSE2" HAVE_ARCH_SSE2) + if(HAVE_ARCH_SSE2) + set(SSE_FLAGS ${SSE_FLAGS} "/arch:SSE2") + set(C_FLAGS ${C_FLAGS} ${SSE_FLAGS}) + set(FPMATH_SET 2) + endif() + elseif(SSE2_SWITCH) check_c_compiler_flag("${SSE2_SWITCH} -mfpmath=sse" HAVE_MFPMATH_SSE_2) if(HAVE_MFPMATH_SSE_2) set(SSE_FLAGS ${SSE_FLAGS} ${SSE2_SWITCH} -mfpmath=sse) @@ -447,13 +469,6 @@ if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2) # OSs don't guarantee this on 32-bit, so externally-callable # functions need to ensure an aligned stack. set(EXPORT_DECL "${EXPORT_DECL} __attribute__((force_align_arg_pointer))") - elseif(MSVC) - check_c_compiler_flag("/arch:SSE2" HAVE_ARCH_SSE2) - if(HAVE_ARCH_SSE2) - set(SSE_FLAGS ${SSE_FLAGS} "/arch:SSE2") - set(C_FLAGS ${C_FLAGS} ${SSE_FLAGS}) - set(FPMATH_SET 2) - endif() endif() endif() endif() @@ -551,51 +566,11 @@ if(NOT WIN32) check_symbol_exists(pthread_setname_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SETNAME_NP) if(NOT HAVE_PTHREAD_SETNAME_NP) check_symbol_exists(pthread_set_name_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SET_NAME_NP) - else() - check_c_source_compiles(" -#include -#include -int main() -{ - pthread_setname_np(\"testname\"); - return 0; -}" - PTHREAD_SETNAME_NP_ONE_PARAM - ) - check_c_source_compiles(" -#include -#include -int main() -{ - pthread_setname_np(pthread_self(), \"%s\", \"testname\"); - return 0; -}" - PTHREAD_SETNAME_NP_THREE_PARAMS - ) endif() else() check_symbol_exists(pthread_setname_np pthread.h HAVE_PTHREAD_SETNAME_NP) if(NOT HAVE_PTHREAD_SETNAME_NP) check_symbol_exists(pthread_set_name_np pthread.h HAVE_PTHREAD_SET_NAME_NP) - else() - check_c_source_compiles(" -#include -int main() -{ - pthread_setname_np(\"testname\"); - return 0; -}" - PTHREAD_SETNAME_NP_ONE_PARAM - ) - check_c_source_compiles(" -#include -int main() -{ - pthread_setname_np(pthread_self(), \"%s\", \"testname\"); - return 0; -}" - PTHREAD_SETNAME_NP_THREE_PARAMS - ) endif() endif() endif() @@ -615,17 +590,19 @@ set(COMMON_OBJS common/alfstream.h common/almalloc.cpp common/almalloc.h + common/alnumbers.h common/alnumeric.h common/aloptional.h common/alspan.h common/alstring.cpp common/alstring.h common/atomic.h + common/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 @@ -642,17 +619,32 @@ 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/effects/base.h + core/effectslot.cpp + core/effectslot.h core/except.cpp core/except.h core/filters/biquad.h @@ -665,12 +657,57 @@ 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 QUIET) +if(DBus1_FOUND) + option(ALSOFT_RTKIT "Enable RTKit support" ON) + if(ALSOFT_RTKIT) + set(HAVE_RTKIT 1) + set(CORE_OBJS ${CORE_OBJS} core/dbus_wrap.cpp core/dbus_wrap.h core/rtkit.cpp core/rtkit.h) + if(WIN32 OR HAVE_DLFCN_H) + set(INC_PATHS ${INC_PATHS} ${DBus1_INCLUDE_DIRS}) + set(CPP_DEFS ${CPP_DEFS} ${DBus1_DEFINITIONS}) + else() + set(EXTRA_LIBS ${EXTRA_LIBS} ${DBus1_LIBRARIES}) + endif() + endif() +else() + set(MISSING_VARS "") + if(NOT DBus1_INCLUDE_DIRS) + set(MISSING_VARS "${MISSING_VARS} DBus1_INCLUDE_DIRS") + endif() + if(NOT DBus1_LIBRARIES) + set(MISSING_VARS "${MISSING_VARS} DBus1_LIBRARIES") + endif() + message(STATUS "Could NOT find DBus1 (missing:${MISSING_VARS})") + unset(MISSING_VARS) +endif() +if(ALSOFT_REQUIRE_RTKIT AND NOT HAVE_RTKIT) + message(FATAL_ERROR "Failed to enabled required RTKit support") +endif() + +# Default mixers, always available +set(CORE_OBJS ${CORE_OBJS} core/mixer/defs.h core/mixer/hrtfbase.h core/mixer/hrtfdefs.h @@ -691,6 +728,7 @@ set(OPENAL_OBJS al/effects/dedicated.cpp al/effects/distortion.cpp al/effects/echo.cpp + al/effects/effects.cpp al/effects/effects.h al/effects/equalizer.cpp al/effects/fshifter.cpp @@ -714,22 +752,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/effectslot.cpp - alc/effectslot.h + alc/context.cpp + alc/context.h + alc/device.cpp + alc/device.h alc/effects/base.h alc/effects/autowah.cpp alc/effects/chorus.cpp @@ -745,22 +775,42 @@ 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) + +if (ALSOFT_EAX) + set(OPENAL_OBJS + ${OPENAL_OBJS} + al/eax_api.cpp + al/eax_api.h + al/eax_eax_call.cpp + al/eax_eax_call.h + al/eax_effect.cpp + al/eax_effect.h + al/eax_exception.cpp + al/eax_exception.h + al/eax_fx_slot_index.cpp + al/eax_fx_slot_index.h + al/eax_fx_slots.cpp + al/eax_fx_slots.h + al/eax_globals.cpp + al/eax_globals.h + al/eax_utils.cpp + al/eax_utils.h + al/eax_x_ram.cpp + al/eax_x_ram.h +) +endif () # 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) @@ -778,6 +828,7 @@ endif() set(HAVE_ALSA 0) set(HAVE_OSS 0) +set(HAVE_PIPEWIRE 0) set(HAVE_SOLARIS 0) set(HAVE_SNDIO 0) set(HAVE_DSOUND 0) @@ -849,6 +900,25 @@ if(ALSOFT_REQUIRE_OSS AND NOT HAVE_OSS) message(FATAL_ERROR "Failed to enabled required OSS backend") endif() +# Check PipeWire backend +option(ALSOFT_REQUIRE_PIPEWIRE "Require PipeWire backend" OFF) +if(PkgConfig_FOUND) + pkg_check_modules(PIPEWIRE libpipewire-0.3) + if(PIPEWIRE_FOUND) + option(ALSOFT_BACKEND_PIPEWIRE "Enable PipeWire backend" ON) + if(ALSOFT_BACKEND_PIPEWIRE) + set(HAVE_PIPEWIRE 1) + set(BACKENDS "${BACKENDS} PipeWire${IS_LINKED},") + set(ALC_OBJS ${ALC_OBJS} alc/backends/pipewire.cpp alc/backends/pipewire.h) + add_backend_libs(${PIPEWIRE_LIBRARIES}) + set(INC_PATHS ${INC_PATHS} ${PIPEWIRE_INCLUDE_DIRS}) + endif() + endif() +endif() +if(ALSOFT_REQUIRE_PIPEWIRE AND NOT HAVE_PIPEWIRE) + message(FATAL_ERROR "Failed to enabled required PipeWire backend") +endif() + # Check Solaris backend option(ALSOFT_REQUIRE_SOLARIS "Require Solaris backend" OFF) find_package(AudioIO) @@ -1148,10 +1218,16 @@ 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 QUIET) + if(NOT Qt5Widgets_FOUND) + message(STATUS "Could NOT find Qt5Widgets") + endif() + endif() endif() -if(ALSOFT_EXAMPLES) +if(ALSOFT_UTILS OR ALSOFT_EXAMPLES) find_package(SndFile) find_package(SDL2) if(SDL2_FOUND) @@ -1212,6 +1288,7 @@ if(LIBTYPE STREQUAL "STATIC") add_library(${IMPL_TARGET} STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS}) target_compile_definitions(${IMPL_TARGET} PUBLIC AL_LIBTYPE_STATIC) target_link_libraries(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) + if(WIN32) # This option is for static linking OpenAL Soft into another project # that already defines the IDs. It is up to that project to ensure all @@ -1232,13 +1309,14 @@ 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 PUBLIC $ - $ + $ PRIVATE ${OpenAL_SOURCE_DIR}/common ${OpenAL_BINARY_DIR} @@ -1303,12 +1381,14 @@ endif() target_include_directories(${IMPL_TARGET} PUBLIC $ - $ + INTERFACE + $ + $ + $ PRIVATE ${INC_PATHS} ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR} - ${OpenAL_SOURCE_DIR}/alc ${OpenAL_SOURCE_DIR}/common ) @@ -1317,7 +1397,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) @@ -1351,25 +1432,35 @@ if(HAS_ROUTER) message(STATUS "Building DLL router") endif() -message(STATUS " -Building OpenAL with support for the following backends: - ${BACKENDS} - -Building with support for CPU extensions: - ${CPU_EXTS} -") +message(STATUS "") +message(STATUS "Building OpenAL with support for the following backends:") +message(STATUS " ${BACKENDS}") +message(STATUS "") +message(STATUS "Building with support for CPU extensions:") +message(STATUS " ${CPU_EXTS}") +message(STATUS "") if(FPMATH_SET) - message(STATUS "Building with SSE${FPMATH_SET} codegen -") + message(STATUS "Building with SSE${FPMATH_SET} codegen") + message(STATUS "") +endif() + +if(ALSOFT_EAX) + message(STATUS "Building with legacy EAX extension support") + message(STATUS "") endif() if(ALSOFT_EMBED_HRTF_DATA) - message(STATUS "Embedding HRTF datasets -") + message(STATUS "Embedding HRTF datasets") + message(STATUS "") endif() +# An alias for sub-project builds +add_library(OpenAL::OpenAL ALIAS OpenAL) + # Install main library if(ALSOFT_INSTALL) + configure_package_config_file(OpenALConfig.cmake.in OpenALConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL) install(TARGETS OpenAL EXPORT OpenAL RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -1378,15 +1469,17 @@ if(ALSOFT_INSTALL) INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/AL) export(TARGETS OpenAL NAMESPACE OpenAL:: - FILE OpenALConfig.cmake) + FILE OpenALTargets.cmake) install(EXPORT OpenAL DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL NAMESPACE OpenAL:: - FILE OpenALConfig.cmake) + FILE OpenALTargets.cmake) install(DIRECTORY include/AL DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(FILES "${OpenAL_BINARY_DIR}/openal.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + install(FILES "${OpenAL_BINARY_DIR}/OpenALConfig.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL") if(TARGET soft_oal) install(TARGETS soft_oal RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) @@ -1429,7 +1522,24 @@ 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}) + + add_executable(uhjencoder utils/uhjencoder.cpp) + target_compile_definitions(uhjencoder PRIVATE ${CPP_DEFS}) + target_include_directories(uhjencoder + PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) + target_compile_options(uhjencoder PRIVATE ${C_FLAGS}) + target_link_libraries(uhjencoder PUBLIC common + PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG}) + endif() + if(MYSOFA_FOUND) set(SOFA_SUPPORT_SRCS utils/sofa-support.cpp diff --git a/Engine/lib/openal-soft/ChangeLog b/Engine/lib/openal-soft/ChangeLog index e5bee5cd7..c26f096de 100644 --- a/Engine/lib/openal-soft/ChangeLog +++ b/Engine/lib/openal-soft/ChangeLog @@ -1,3 +1,57 @@ +openal-soft-1.22.0: + + Implemented the ALC_SOFT_reopen_device extension. This allows for moving + devices to different outputs without losing object state. + + Implemented the ALC_SOFT_output_mode extension. + + Implemented the AL_SOFT_callback_buffer extension. + + Implemented the AL_SOFT_UHJ extension. This supports native UHJ buffer + formats and Super Stereo processing. + + Implemented the legacy EAX extensions. Enabled by default only on Windows. + + Improved sound positioning stability when a source is near the listener. + + Improved the default 5.1 output decoder. + + Improved the high frequency response for the HRTF second-order ambisonic + decoder. + + Improved SoundIO capture behavior. + + Fixed UHJ output on NEON-capable CPUs. + + Fixed redundant effect updates when setting an effect property to the + current value. + + Fixed WASAPI capture using really low sample rates, and sources with very + high pitch shifts when using a bsinc resampler. + + Added a PipeWire backend. + + Added enumeration for the JACK and CoreAudio backends. + + Added optional support for RTKit to get real-time priority. Only used as a + backup when pthread_setschedparam fails. + + Added an option for JACK playback to render directly in the real-time + processing callback. For lower playback latency, on by default. + + Added an option for custom JACK devices. + + Added utilities to encode and decode UHJ audio files. Files are decoded to + the .amb format, and are encoded from libsndfile-compatible formats. + + Added an in-progress extension to hold sources in a playing state when a + device disconnects. Allows devices to be reset or reopened and have sources + resume from where they left off. + + Lowered the priority of the JACK backend. To avoid it getting picked when + PipeWire is providing JACK compatibility, since the JACK backend is less + robust with auto-configuration. + openal-soft-1.21.1: Improved alext.h's detection of standard types. @@ -5,6 +59,8 @@ openal-soft-1.21.1: Improved slightly the local source position when the listener and source are near each other. + Improved click/pop prevention for sounds that stop prematurely. + Fixed compilation for Windows ARM targets with MSVC. Fixed ARM NEON detection on Windows. @@ -19,6 +75,8 @@ openal-soft-1.21.1: Fixed missing source stop events when stopping a paused source. + Added capture support to the experimental Oboe backend. + openal-soft-1.21.0: Updated library codebase to C++14. diff --git a/Engine/lib/openal-soft/OpenALConfig.cmake.in b/Engine/lib/openal-soft/OpenALConfig.cmake.in new file mode 100644 index 000000000..128c1a4e0 --- /dev/null +++ b/Engine/lib/openal-soft/OpenALConfig.cmake.in @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.1) + +include("${CMAKE_CURRENT_LIST_DIR}/OpenALTargets.cmake") + +set(OPENAL_FOUND ON) +set(OPENAL_INCLUDE_DIR $) +set(OPENAL_LIBRARY $) +set(OPENAL_DEFINITIONS $) +set(OPENAL_VERSION_STRING @PACKAGE_VERSION@) diff --git a/Engine/lib/openal-soft/XCompile-Android.txt b/Engine/lib/openal-soft/XCompile-Android.txt index 7a660d2a1..693f0ed96 100644 --- a/Engine/lib/openal-soft/XCompile-Android.txt +++ b/Engine/lib/openal-soft/XCompile-Android.txt @@ -7,5 +7,10 @@ # cmake .. -DANDROID_STL=c++_shared \ # -DCMAKE_TOOLCHAIN_FILE=${ndk_root}/build/cmake/android.toolchain.cmake # +# Certain NDK versions may also need to use the lld linker to avoid errors +# about missing liblog.so and libOpenSLES.so. That can be done by: +# cmake .. -DANDROID_LD=lld \ +# -DCMAKE_TOOLCHAIN_FILE=${ndk_root}/build/cmake/android.toolchain.cmake +# MESSAGE(FATAL_ERROR "Use the toolchain provided by the Android NDK") diff --git a/Engine/lib/openal-soft/al/auxeffectslot.cpp b/Engine/lib/openal-soft/al/auxeffectslot.cpp index 10f13be71..c33fb1498 100644 --- a/Engine/lib/openal-soft/al/auxeffectslot.cpp +++ b/Engine/lib/openal-soft/al/auxeffectslot.cpp @@ -36,20 +36,24 @@ #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" +#ifdef ALSOFT_EAX +#include "eax_exception.h" +#include "eax_utils.h" +#endif // ALSOFT_EAX namespace { @@ -277,8 +281,9 @@ ALeffectslot *AllocEffectSlot(ALCcontext *context) { return entry.FreeMask != 0; }); auto lidx = static_cast(std::distance(context->mEffectSlotList.begin(), sublist)); auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); + ASSUME(slidx < 64); - ALeffectslot *slot{::new(sublist->EffectSlots + slidx) ALeffectslot{}}; + ALeffectslot *slot{al::construct_at(sublist->EffectSlots + slidx)}; aluInitEffectPanning(&slot->mSlot, context); /* Add 1 to avoid source ID 0. */ @@ -303,13 +308,15 @@ void FreeEffectSlot(ALCcontext *context, ALeffectslot *slot) } -#define DO_UPDATEPROPS() do { \ - if(!context->mDeferUpdates.load(std::memory_order_acquire) \ - && slot->mState == SlotState::Playing) \ - slot->updateProps(context.get()); \ - else \ - slot->PropsClean.clear(std::memory_order_release); \ -} while(0) +inline void UpdateProps(ALeffectslot *slot, ALCcontext *context) +{ + if(!context->mDeferUpdates && slot->mState == SlotState::Playing) + { + slot->updateProps(context); + return; + } + slot->mPropsDirty = true; +} } // namespace @@ -325,7 +332,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 +467,7 @@ START_API_FUNC if(slot->mState == SlotState::Playing) return; - slot->PropsClean.test_and_set(std::memory_order_acq_rel); + slot->mPropsDirty = false; slot->updateProps(context.get()); AddActiveEffectSlots({&slot, 1}, context.get()); @@ -491,7 +498,7 @@ START_API_FUNC if(slot->mState != SlotState::Playing) { - slot->PropsClean.test_and_set(std::memory_order_acq_rel); + slot->mPropsDirty = false; slot->updateProps(context.get()); } slots[i] = slot; @@ -571,14 +578,19 @@ START_API_FUNC switch(param) { case AL_EFFECTSLOT_EFFECT: - device = context->mDevice.get(); + device = context->mALDevice.get(); { std::lock_guard ___{device->EffectLock}; ALeffect *effect{value ? LookupEffect(device, static_cast(value)) : nullptr}; - if(!(value == 0 || effect != nullptr)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect ID %u", value); - err = slot->initEffect(effect, context.get()); + if(effect) + err = slot->initEffect(effect->type, effect->Props, context.get()); + else + { + if(value != 0) + SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect ID %u", value); + err = slot->initEffect(AL_EFFECT_NULL, EffectProps{}, context.get()); + } } if UNLIKELY(err != AL_NO_ERROR) { @@ -587,8 +599,12 @@ START_API_FUNC } if UNLIKELY(slot->mState == SlotState::Initial) { + slot->mPropsDirty = false; + slot->updateProps(context.get()); + AddActiveEffectSlots({&slot, 1}, context.get()); slot->mState = SlotState::Playing; + return; } break; @@ -596,6 +612,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 +621,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 +651,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{}; @@ -668,7 +696,7 @@ START_API_FUNC SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid effect slot integer property 0x%04x", param); } - DO_UPDATEPROPS(); + UpdateProps(slot, context.get()); } END_API_FUNC @@ -720,6 +748,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; @@ -727,7 +757,7 @@ START_API_FUNC SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid effect slot float property 0x%04x", param); } - DO_UPDATEPROPS(); + UpdateProps(slot, context.get()); } END_API_FUNC @@ -884,10 +914,8 @@ END_API_FUNC ALeffectslot::ALeffectslot() { - PropsClean.test_and_set(std::memory_order_relaxed); - EffectStateFactory *factory{getFactoryByType(EffectSlotType::None)}; - assert(factory != nullptr); + if(!factory) throw std::runtime_error{"Failed to get null effect factory"}; al::intrusive_ptr state{factory->create()}; Effect.State = state; @@ -915,9 +943,10 @@ ALeffectslot::~ALeffectslot() mSlot.mEffectState->release(); } -ALenum ALeffectslot::initEffect(ALeffect *effect, ALCcontext *context) +ALenum ALeffectslot::initEffect(ALenum effectType, const EffectProps &effectProps, + ALCcontext *context) { - EffectSlotType newtype{EffectSlotTypeFromEnum(effect ? effect->type : AL_EFFECT_NULL)}; + EffectSlotType newtype{EffectSlotTypeFromEnum(effectType)}; if(newtype != Effect.Type) { EffectStateFactory *factory{getFactoryByType(newtype)}; @@ -928,7 +957,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; { @@ -937,12 +966,12 @@ ALenum ALeffectslot::initEffect(ALeffect *effect, ALCcontext *context) } Effect.Type = newtype; - Effect.Props = effect ? effect->Props : EffectProps{}; + Effect.Props = effectProps; Effect.State = std::move(state); } - else if(effect) - Effect.Props = effect->Props; + else if(newtype != EffectSlotType::None) + Effect.Props = effectProps; /* Remove state references from old effect slot property updates. */ EffectSlotProps *props{context->mFreeEffectslotProps.load()}; @@ -994,17 +1023,20 @@ void ALeffectslot::updateProps(ALCcontext *context) void UpdateAllEffectSlotProps(ALCcontext *context) { std::lock_guard _{context->mEffectSlotLock}; +#ifdef ALSOFT_EAX + if(context->has_eax()) + context->eax_commit_fx_slots(); +#endif for(auto &sublist : context->mEffectSlotList) { uint64_t usemask{~sublist.FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; - ALeffectslot *slot{sublist.EffectSlots + idx}; usemask &= ~(1_u64 << idx); + ALeffectslot *slot{sublist.EffectSlots + idx}; - if(slot->mState != SlotState::Stopped - && slot->PropsClean.test_and_set(std::memory_order_acq_rel)) + if(slot->mState != SlotState::Stopped && std::exchange(slot->mPropsDirty, false)) slot->updateProps(context); } } @@ -1023,3 +1055,769 @@ EffectSlotSubList::~EffectSlotSubList() al_free(EffectSlots); EffectSlots = nullptr; } + +#ifdef ALSOFT_EAX +namespace { + +class EaxFxSlotException : + public EaxException +{ +public: + explicit EaxFxSlotException( + const char* message) + : + EaxException{"EAX_FX_SLOT", message} + { + } +}; // EaxFxSlotException + + +} // namespace + + +void ALeffectslot::eax_initialize( + ALCcontext& al_context, + EaxFxSlotIndexValue index) +{ + eax_al_context_ = &al_context; + + if (index >= EAX_MAX_FXSLOTS) + { + eax_fail("Index out of range."); + } + + eax_fx_slot_index_ = index; + + eax_initialize_eax(); + eax_initialize_lock(); + eax_initialize_effects(); +} + +const EAX50FXSLOTPROPERTIES& ALeffectslot::eax_get_eax_fx_slot() const noexcept +{ + return eax_eax_fx_slot_; +} + +void ALeffectslot::eax_ensure_is_unlocked() const +{ + if (eax_is_locked_) + eax_fail("Locked."); +} + +void ALeffectslot::eax_validate_fx_slot_effect( + const GUID& eax_effect_id) +{ + eax_ensure_is_unlocked(); + + if (eax_effect_id != EAX_NULL_GUID && + eax_effect_id != EAX_REVERB_EFFECT && + eax_effect_id != EAX_AGCCOMPRESSOR_EFFECT && + eax_effect_id != EAX_AUTOWAH_EFFECT && + eax_effect_id != EAX_CHORUS_EFFECT && + eax_effect_id != EAX_DISTORTION_EFFECT && + eax_effect_id != EAX_ECHO_EFFECT && + eax_effect_id != EAX_EQUALIZER_EFFECT && + eax_effect_id != EAX_FLANGER_EFFECT && + eax_effect_id != EAX_FREQUENCYSHIFTER_EFFECT && + eax_effect_id != EAX_VOCALMORPHER_EFFECT && + eax_effect_id != EAX_PITCHSHIFTER_EFFECT && + eax_effect_id != EAX_RINGMODULATOR_EFFECT) + { + eax_fail("Unsupported EAX effect GUID."); + } +} + +void ALeffectslot::eax_validate_fx_slot_volume( + long eax_volume) +{ + eax_validate_range( + "Volume", + eax_volume, + EAXFXSLOT_MINVOLUME, + EAXFXSLOT_MAXVOLUME); +} + +void ALeffectslot::eax_validate_fx_slot_lock( + long eax_lock) +{ + eax_ensure_is_unlocked(); + + eax_validate_range( + "Lock", + eax_lock, + EAXFXSLOT_MINLOCK, + EAXFXSLOT_MAXLOCK); +} + +void ALeffectslot::eax_validate_fx_slot_flags( + unsigned long eax_flags, + int eax_version) +{ + eax_validate_range( + "Flags", + eax_flags, + 0UL, + ~(eax_version == 4 ? EAX40FXSLOTFLAGS_RESERVED : EAX50FXSLOTFLAGS_RESERVED)); +} + +void ALeffectslot::eax_validate_fx_slot_occlusion( + long eax_occlusion) +{ + eax_validate_range( + "Occlusion", + eax_occlusion, + EAXFXSLOT_MINOCCLUSION, + EAXFXSLOT_MAXOCCLUSION); +} + +void ALeffectslot::eax_validate_fx_slot_occlusion_lf_ratio( + float eax_occlusion_lf_ratio) +{ + eax_validate_range( + "Occlusion LF Ratio", + eax_occlusion_lf_ratio, + EAXFXSLOT_MINOCCLUSIONLFRATIO, + EAXFXSLOT_MAXOCCLUSIONLFRATIO); +} + +void ALeffectslot::eax_validate_fx_slot_all( + const EAX40FXSLOTPROPERTIES& fx_slot, + int eax_version) +{ + eax_validate_fx_slot_effect(fx_slot.guidLoadEffect); + eax_validate_fx_slot_volume(fx_slot.lVolume); + eax_validate_fx_slot_lock(fx_slot.lLock); + eax_validate_fx_slot_flags(fx_slot.ulFlags, eax_version); +} + +void ALeffectslot::eax_validate_fx_slot_all( + const EAX50FXSLOTPROPERTIES& fx_slot, + int eax_version) +{ + eax_validate_fx_slot_all(static_cast(fx_slot), eax_version); + + eax_validate_fx_slot_occlusion(fx_slot.lOcclusion); + eax_validate_fx_slot_occlusion_lf_ratio(fx_slot.flOcclusionLFRatio); +} + +void ALeffectslot::eax_set_fx_slot_effect( + const GUID& eax_effect_id) +{ + if (eax_eax_fx_slot_.guidLoadEffect == eax_effect_id) + { + return; + } + + eax_eax_fx_slot_.guidLoadEffect = eax_effect_id; + + eax_set_fx_slot_effect(); +} + +void ALeffectslot::eax_set_fx_slot_volume( + long eax_volume) +{ + if (eax_eax_fx_slot_.lVolume == eax_volume) + { + return; + } + + eax_eax_fx_slot_.lVolume = eax_volume; + + eax_set_fx_slot_volume(); +} + +void ALeffectslot::eax_set_fx_slot_lock( + long eax_lock) +{ + if (eax_eax_fx_slot_.lLock == eax_lock) + { + return; + } + + eax_eax_fx_slot_.lLock = eax_lock; +} + +void ALeffectslot::eax_set_fx_slot_flags( + unsigned long eax_flags) +{ + if (eax_eax_fx_slot_.ulFlags == eax_flags) + { + return; + } + + eax_eax_fx_slot_.ulFlags = eax_flags; + + eax_set_fx_slot_flags(); +} + +// [[nodiscard]] +bool ALeffectslot::eax_set_fx_slot_occlusion( + long eax_occlusion) +{ + if (eax_eax_fx_slot_.lOcclusion == eax_occlusion) + { + return false; + } + + eax_eax_fx_slot_.lOcclusion = eax_occlusion; + + return true; +} + +// [[nodiscard]] +bool ALeffectslot::eax_set_fx_slot_occlusion_lf_ratio( + float eax_occlusion_lf_ratio) +{ + if (eax_eax_fx_slot_.flOcclusionLFRatio == eax_occlusion_lf_ratio) + { + return false; + } + + eax_eax_fx_slot_.flOcclusionLFRatio = eax_occlusion_lf_ratio; + + return true; +} + +void ALeffectslot::eax_set_fx_slot_all( + const EAX40FXSLOTPROPERTIES& eax_fx_slot) +{ + eax_set_fx_slot_effect(eax_fx_slot.guidLoadEffect); + eax_set_fx_slot_volume(eax_fx_slot.lVolume); + eax_set_fx_slot_lock(eax_fx_slot.lLock); + eax_set_fx_slot_flags(eax_fx_slot.ulFlags); +} + +// [[nodiscard]] +bool ALeffectslot::eax_set_fx_slot_all( + const EAX50FXSLOTPROPERTIES& eax_fx_slot) +{ + eax_set_fx_slot_all(static_cast(eax_fx_slot)); + + const auto is_occlusion_modified = eax_set_fx_slot_occlusion(eax_fx_slot.lOcclusion); + const auto is_occlusion_lf_ratio_modified = eax_set_fx_slot_occlusion_lf_ratio(eax_fx_slot.flOcclusionLFRatio); + + return is_occlusion_modified || is_occlusion_lf_ratio_modified; +} + +void ALeffectslot::eax_unlock_legacy() noexcept +{ + assert(eax_fx_slot_index_ < 2); + eax_is_locked_ = false; + eax_eax_fx_slot_.lLock = EAXFXSLOT_UNLOCKED; +} + +[[noreturn]] +void ALeffectslot::eax_fail( + const char* message) +{ + throw EaxFxSlotException{message}; +} + +GUID ALeffectslot::eax_get_eax_default_effect_guid() const noexcept +{ + switch (eax_fx_slot_index_) + { + case 0: return EAX_REVERB_EFFECT; + case 1: return EAX_CHORUS_EFFECT; + default: return EAX_NULL_GUID; + } +} + +long ALeffectslot::eax_get_eax_default_lock() const noexcept +{ + return eax_fx_slot_index_ < 2 ? EAXFXSLOT_LOCKED : EAXFXSLOT_UNLOCKED; +} + +void ALeffectslot::eax_set_eax_fx_slot_defaults() +{ + eax_eax_fx_slot_.guidLoadEffect = eax_get_eax_default_effect_guid(); + eax_eax_fx_slot_.lVolume = EAXFXSLOT_DEFAULTVOLUME; + eax_eax_fx_slot_.lLock = eax_get_eax_default_lock(); + eax_eax_fx_slot_.ulFlags = EAX40FXSLOT_DEFAULTFLAGS; + eax_eax_fx_slot_.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION; + eax_eax_fx_slot_.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO; +} + +void ALeffectslot::eax_initialize_eax() +{ + eax_set_eax_fx_slot_defaults(); +} + +void ALeffectslot::eax_initialize_lock() +{ + eax_is_locked_ = (eax_fx_slot_index_ < 2); +} + +void ALeffectslot::eax_initialize_effects() +{ + eax_set_fx_slot_effect(); +} + +void ALeffectslot::eax_get_fx_slot_all( + const EaxEaxCall& eax_call) const +{ + switch (eax_call.get_version()) + { + case 4: + eax_call.set_value(eax_eax_fx_slot_); + break; + + case 5: + eax_call.set_value(eax_eax_fx_slot_); + break; + + default: + eax_fail("Unsupported EAX version."); + } +} + +void ALeffectslot::eax_get_fx_slot( + const EaxEaxCall& eax_call) const +{ + switch (eax_call.get_property_id()) + { + case EAXFXSLOT_ALLPARAMETERS: + eax_get_fx_slot_all(eax_call); + break; + + case EAXFXSLOT_LOADEFFECT: + eax_call.set_value(eax_eax_fx_slot_.guidLoadEffect); + break; + + case EAXFXSLOT_VOLUME: + eax_call.set_value(eax_eax_fx_slot_.lVolume); + break; + + case EAXFXSLOT_LOCK: + eax_call.set_value(eax_eax_fx_slot_.lLock); + break; + + case EAXFXSLOT_FLAGS: + eax_call.set_value(eax_eax_fx_slot_.ulFlags); + break; + + case EAXFXSLOT_OCCLUSION: + eax_call.set_value(eax_eax_fx_slot_.lOcclusion); + break; + + case EAXFXSLOT_OCCLUSIONLFRATIO: + eax_call.set_value(eax_eax_fx_slot_.flOcclusionLFRatio); + break; + + default: + eax_fail("Unsupported FX slot property id."); + } +} + +// [[nodiscard]] +bool ALeffectslot::eax_get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_set_id()) + { + case EaxEaxCallPropertySetId::fx_slot: + eax_get_fx_slot(eax_call); + break; + + case EaxEaxCallPropertySetId::fx_slot_effect: + eax_dispatch_effect(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } + + return false; +} + +void ALeffectslot::eax_set_fx_slot_effect( + ALenum al_effect_type) +{ + if(!IsValidEffectType(al_effect_type)) + eax_fail("Unsupported effect."); + + eax_effect_ = nullptr; + eax_effect_ = eax_create_eax_effect(al_effect_type); + + eax_set_effect_slot_effect(*eax_effect_); +} + +void ALeffectslot::eax_set_fx_slot_effect() +{ + auto al_effect_type = ALenum{}; + + if (false) + { + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_NULL_GUID) + { + al_effect_type = AL_EFFECT_NULL; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_AUTOWAH_EFFECT) + { + al_effect_type = AL_EFFECT_AUTOWAH; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_CHORUS_EFFECT) + { + al_effect_type = AL_EFFECT_CHORUS; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_AGCCOMPRESSOR_EFFECT) + { + al_effect_type = AL_EFFECT_COMPRESSOR; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_DISTORTION_EFFECT) + { + al_effect_type = AL_EFFECT_DISTORTION; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_REVERB_EFFECT) + { + al_effect_type = AL_EFFECT_EAXREVERB; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_ECHO_EFFECT) + { + al_effect_type = AL_EFFECT_ECHO; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_EQUALIZER_EFFECT) + { + al_effect_type = AL_EFFECT_EQUALIZER; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_FLANGER_EFFECT) + { + al_effect_type = AL_EFFECT_FLANGER; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_FREQUENCYSHIFTER_EFFECT) + { + al_effect_type = AL_EFFECT_FREQUENCY_SHIFTER; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_PITCHSHIFTER_EFFECT) + { + al_effect_type = AL_EFFECT_PITCH_SHIFTER; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_RINGMODULATOR_EFFECT) + { + al_effect_type = AL_EFFECT_RING_MODULATOR; + } + else if (eax_eax_fx_slot_.guidLoadEffect == EAX_VOCALMORPHER_EFFECT) + { + al_effect_type = AL_EFFECT_VOCAL_MORPHER; + } + else + { + eax_fail("Unsupported effect."); + } + + eax_set_fx_slot_effect(al_effect_type); +} + +void ALeffectslot::eax_set_efx_effect_slot_gain() +{ + const auto gain = level_mb_to_gain( + static_cast(clamp( + eax_eax_fx_slot_.lVolume, + EAXFXSLOT_MINVOLUME, + EAXFXSLOT_MAXVOLUME))); + + eax_set_effect_slot_gain(gain); +} + +void ALeffectslot::eax_set_fx_slot_volume() +{ + eax_set_efx_effect_slot_gain(); +} + +void ALeffectslot::eax_set_effect_slot_send_auto() +{ + eax_set_effect_slot_send_auto((eax_eax_fx_slot_.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0); +} + +void ALeffectslot::eax_set_fx_slot_flags() +{ + eax_set_effect_slot_send_auto(); +} + +void ALeffectslot::eax_set_fx_slot_effect( + const EaxEaxCall& eax_call) +{ + const auto& eax_effect_id = + eax_call.get_value(); + + eax_validate_fx_slot_effect(eax_effect_id); + eax_set_fx_slot_effect(eax_effect_id); +} + +void ALeffectslot::eax_set_fx_slot_volume( + const EaxEaxCall& eax_call) +{ + const auto& eax_volume = + eax_call.get_value(); + + eax_validate_fx_slot_volume(eax_volume); + eax_set_fx_slot_volume(eax_volume); +} + +void ALeffectslot::eax_set_fx_slot_lock( + const EaxEaxCall& eax_call) +{ + const auto& eax_lock = + eax_call.get_value(); + + eax_validate_fx_slot_lock(eax_lock); + eax_set_fx_slot_lock(eax_lock); +} + +void ALeffectslot::eax_set_fx_slot_flags( + const EaxEaxCall& eax_call) +{ + const auto& eax_flags = + eax_call.get_value(); + + eax_validate_fx_slot_flags(eax_flags, eax_call.get_version()); + eax_set_fx_slot_flags(eax_flags); +} + +// [[nodiscard]] +bool ALeffectslot::eax_set_fx_slot_occlusion( + const EaxEaxCall& eax_call) +{ + const auto& eax_occlusion = + eax_call.get_value(); + + eax_validate_fx_slot_occlusion(eax_occlusion); + + return eax_set_fx_slot_occlusion(eax_occlusion); +} + +// [[nodiscard]] +bool ALeffectslot::eax_set_fx_slot_occlusion_lf_ratio( + const EaxEaxCall& eax_call) +{ + const auto& eax_occlusion_lf_ratio = + eax_call.get_value(); + + eax_validate_fx_slot_occlusion_lf_ratio(eax_occlusion_lf_ratio); + + return eax_set_fx_slot_occlusion_lf_ratio(eax_occlusion_lf_ratio); +} + +// [[nodiscard]] +bool ALeffectslot::eax_set_fx_slot_all( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_version()) + { + case 4: + { + const auto& eax_all = + eax_call.get_value(); + + eax_validate_fx_slot_all(eax_all, eax_call.get_version()); + eax_set_fx_slot_all(eax_all); + + return false; + } + + case 5: + { + const auto& eax_all = + eax_call.get_value(); + + eax_validate_fx_slot_all(eax_all, eax_call.get_version()); + return eax_set_fx_slot_all(eax_all); + } + + default: + eax_fail("Unsupported EAX version."); + } +} + +bool ALeffectslot::eax_set_fx_slot( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXFXSLOT_NONE: + return false; + + case EAXFXSLOT_ALLPARAMETERS: + return eax_set_fx_slot_all(eax_call); + + case EAXFXSLOT_LOADEFFECT: + eax_set_fx_slot_effect(eax_call); + return false; + + case EAXFXSLOT_VOLUME: + eax_set_fx_slot_volume(eax_call); + return false; + + case EAXFXSLOT_LOCK: + eax_set_fx_slot_lock(eax_call); + return false; + + case EAXFXSLOT_FLAGS: + eax_set_fx_slot_flags(eax_call); + return false; + + case EAXFXSLOT_OCCLUSION: + return eax_set_fx_slot_occlusion(eax_call); + + case EAXFXSLOT_OCCLUSIONLFRATIO: + return eax_set_fx_slot_occlusion_lf_ratio(eax_call); + + + default: + eax_fail("Unsupported FX slot property id."); + } +} + +// [[nodiscard]] +bool ALeffectslot::eax_set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_set_id()) + { + case EaxEaxCallPropertySetId::fx_slot: + return eax_set_fx_slot(eax_call); + + case EaxEaxCallPropertySetId::fx_slot_effect: + eax_dispatch_effect(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } + + return false; +} + +void ALeffectslot::eax_dispatch_effect(const EaxEaxCall& eax_call) +{ if(eax_effect_) eax_effect_->dispatch(eax_call); } + +void ALeffectslot::eax_apply_deferred() +{ + /* The other FXSlot properties (volume, effect, etc) aren't deferred? */ + + auto is_changed = false; + if(eax_effect_) + is_changed = eax_effect_->apply_deferred(); + if(is_changed) + eax_set_effect_slot_effect(*eax_effect_); +} + + +void ALeffectslot::eax_set_effect_slot_effect(EaxEffect &effect) +{ +#define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_EFFECT] " + + const auto error = initEffect(effect.al_effect_type_, effect.al_effect_props_, eax_al_context_); + if (error != AL_NO_ERROR) + { + ERR(EAX_PREFIX "%s\n", "Failed to initialize an effect."); + return; + } + + if (mState == SlotState::Initial) + { + mPropsDirty = false; + updateProps(eax_al_context_); + + auto effect_slot_ptr = this; + + AddActiveEffectSlots({&effect_slot_ptr, 1}, eax_al_context_); + mState = SlotState::Playing; + + return; + } + + UpdateProps(this, eax_al_context_); + +#undef EAX_PREFIX +} + +void ALeffectslot::eax_set_effect_slot_send_auto( + bool is_send_auto) +{ + if(AuxSendAuto == is_send_auto) + return; + + AuxSendAuto = is_send_auto; + UpdateProps(this, eax_al_context_); +} + +void ALeffectslot::eax_set_effect_slot_gain( + ALfloat gain) +{ +#define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_GAIN] " + + if(gain == Gain) + return; + if(gain < 0.0f || gain > 1.0f) + ERR(EAX_PREFIX "Gain out of range (%f)\n", gain); + + Gain = clampf(gain, 0.0f, 1.0f); + UpdateProps(this, eax_al_context_); + +#undef EAX_PREFIX +} + + +void ALeffectslot::EaxDeleter::operator()(ALeffectslot* effect_slot) +{ + assert(effect_slot); + eax_delete_al_effect_slot(*effect_slot->eax_al_context_, *effect_slot); +} + + +EaxAlEffectSlotUPtr eax_create_al_effect_slot( + ALCcontext& context) +{ +#define EAX_PREFIX "[EAX_MAKE_EFFECT_SLOT] " + + std::unique_lock effect_slot_lock{context.mEffectSlotLock}; + + auto& device = *context.mALDevice; + + if (context.mNumEffectSlots == device.AuxiliaryEffectSlotMax) + { + ERR(EAX_PREFIX "%s\n", "Out of memory."); + return nullptr; + } + + if (!EnsureEffectSlots(&context, 1)) + { + ERR(EAX_PREFIX "%s\n", "Failed to ensure."); + return nullptr; + } + + auto effect_slot = EaxAlEffectSlotUPtr{AllocEffectSlot(&context)}; + if (!effect_slot) + { + ERR(EAX_PREFIX "%s\n", "Failed to allocate."); + return nullptr; + } + + return effect_slot; + +#undef EAX_PREFIX +} + +void eax_delete_al_effect_slot( + ALCcontext& context, + ALeffectslot& effect_slot) +{ +#define EAX_PREFIX "[EAX_DELETE_EFFECT_SLOT] " + + std::lock_guard effect_slot_lock{context.mEffectSlotLock}; + + if (ReadRef(effect_slot.ref) != 0) + { + ERR(EAX_PREFIX "Deleting in-use effect slot %u.\n", effect_slot.id); + return; + } + + auto effect_slot_ptr = &effect_slot; + + RemoveActiveEffectSlots({&effect_slot_ptr, 1}, &context); + FreeEffectSlot(&context, &effect_slot); + +#undef EAX_PREFIX +} +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/auxeffectslot.h b/Engine/lib/openal-soft/al/auxeffectslot.h index c1c9703f7..ca0dcd310 100644 --- a/Engine/lib/openal-soft/al/auxeffectslot.h +++ b/Engine/lib/openal-soft/al/auxeffectslot.h @@ -8,14 +8,22 @@ #include "AL/alc.h" #include "AL/efx.h" -#include "alcmain.h" +#include "alc/device.h" +#include "alc/effects/base.h" #include "almalloc.h" #include "atomic.h" -#include "effectslot.h" -#include "effects/base.h" +#include "core/effectslot.h" #include "intrusive_ptr.h" #include "vector.h" +#ifdef ALSOFT_EAX +#include + +#include "eax_eax_call.h" +#include "eax_effect.h" +#include "eax_fx_slot_index.h" +#endif // ALSOFT_EAX + struct ALbuffer; struct ALeffect; struct WetBuffer; @@ -40,7 +48,7 @@ struct ALeffectslot { al::intrusive_ptr State; } Effect; - std::atomic_flag PropsClean; + bool mPropsDirty{true}; SlotState mState{SlotState::Initial}; @@ -56,13 +64,214 @@ struct ALeffectslot { ALeffectslot& operator=(const ALeffectslot&) = delete; ~ALeffectslot(); - ALenum initEffect(ALeffect *effect, ALCcontext *context); + ALenum initEffect(ALenum effectType, const EffectProps &effectProps, ALCcontext *context); void updateProps(ALCcontext *context); /* This can be new'd for the context's default effect slot. */ DEF_NEWDEL(ALeffectslot) + + +#ifdef ALSOFT_EAX +public: + void eax_initialize( + ALCcontext& al_context, + EaxFxSlotIndexValue index); + + const EAX50FXSLOTPROPERTIES& eax_get_eax_fx_slot() const noexcept; + + + // [[nodiscard]] + bool eax_dispatch(const EaxEaxCall& eax_call) + { return eax_call.is_get() ? eax_get(eax_call) : eax_set(eax_call); } + + + void eax_unlock_legacy() noexcept; + + void eax_commit() { eax_apply_deferred(); } + +private: + ALCcontext* eax_al_context_{}; + + EaxFxSlotIndexValue eax_fx_slot_index_{}; + + EAX50FXSLOTPROPERTIES eax_eax_fx_slot_{}; + + EaxEffectUPtr eax_effect_{}; + bool eax_is_locked_{}; + + + [[noreturn]] + static void eax_fail( + const char* message); + + + GUID eax_get_eax_default_effect_guid() const noexcept; + long eax_get_eax_default_lock() const noexcept; + + void eax_set_eax_fx_slot_defaults(); + + void eax_initialize_eax(); + + void eax_initialize_lock(); + + + void eax_initialize_effects(); + + + void eax_get_fx_slot_all( + const EaxEaxCall& eax_call) const; + + void eax_get_fx_slot( + const EaxEaxCall& eax_call) const; + + // [[nodiscard]] + bool eax_get( + const EaxEaxCall& eax_call); + + + void eax_set_fx_slot_effect( + ALenum effect_type); + + void eax_set_fx_slot_effect(); + + + void eax_set_efx_effect_slot_gain(); + + void eax_set_fx_slot_volume(); + + + void eax_set_effect_slot_send_auto(); + + void eax_set_fx_slot_flags(); + + + void eax_ensure_is_unlocked() const; + + + void eax_validate_fx_slot_effect( + const GUID& eax_effect_id); + + void eax_validate_fx_slot_volume( + long eax_volume); + + void eax_validate_fx_slot_lock( + long eax_lock); + + void eax_validate_fx_slot_flags( + unsigned long eax_flags, + int eax_version); + + void eax_validate_fx_slot_occlusion( + long eax_occlusion); + + void eax_validate_fx_slot_occlusion_lf_ratio( + float eax_occlusion_lf_ratio); + + void eax_validate_fx_slot_all( + const EAX40FXSLOTPROPERTIES& fx_slot, + int eax_version); + + void eax_validate_fx_slot_all( + const EAX50FXSLOTPROPERTIES& fx_slot, + int eax_version); + + + void eax_set_fx_slot_effect( + const GUID& eax_effect_id); + + void eax_set_fx_slot_volume( + long eax_volume); + + void eax_set_fx_slot_lock( + long eax_lock); + + void eax_set_fx_slot_flags( + unsigned long eax_flags); + + // [[nodiscard]] + bool eax_set_fx_slot_occlusion( + long eax_occlusion); + + // [[nodiscard]] + bool eax_set_fx_slot_occlusion_lf_ratio( + float eax_occlusion_lf_ratio); + + void eax_set_fx_slot_all( + const EAX40FXSLOTPROPERTIES& eax_fx_slot); + + // [[nodiscard]] + bool eax_set_fx_slot_all( + const EAX50FXSLOTPROPERTIES& eax_fx_slot); + + + void eax_set_fx_slot_effect( + const EaxEaxCall& eax_call); + + void eax_set_fx_slot_volume( + const EaxEaxCall& eax_call); + + void eax_set_fx_slot_lock( + const EaxEaxCall& eax_call); + + void eax_set_fx_slot_flags( + const EaxEaxCall& eax_call); + + // [[nodiscard]] + bool eax_set_fx_slot_occlusion( + const EaxEaxCall& eax_call); + + // [[nodiscard]] + bool eax_set_fx_slot_occlusion_lf_ratio( + const EaxEaxCall& eax_call); + + // [[nodiscard]] + bool eax_set_fx_slot_all( + const EaxEaxCall& eax_call); + + bool eax_set_fx_slot( + const EaxEaxCall& eax_call); + + void eax_apply_deferred(); + + // [[nodiscard]] + bool eax_set( + const EaxEaxCall& eax_call); + + + void eax_dispatch_effect( + const EaxEaxCall& eax_call); + + + // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_EFFECT, effect)` + void eax_set_effect_slot_effect(EaxEffect &effect); + + // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_AUXILIARY_SEND_AUTO, value)` + void eax_set_effect_slot_send_auto(bool is_send_auto); + + // `alAuxiliaryEffectSlotf(effect_slot, AL_EFFECTSLOT_GAIN, gain)` + void eax_set_effect_slot_gain(ALfloat gain); + +public: + class EaxDeleter { + public: + void operator()(ALeffectslot *effect_slot); + }; // EaxAlEffectSlotDeleter +#endif // ALSOFT_EAX }; void UpdateAllEffectSlotProps(ALCcontext *context); +#ifdef ALSOFT_EAX + +using EaxAlEffectSlotUPtr = std::unique_ptr; + + +EaxAlEffectSlotUPtr eax_create_al_effect_slot( + ALCcontext& context); + +void eax_delete_al_effect_slot( + ALCcontext& context, + ALeffectslot& effect_slot); +#endif // ALSOFT_EAX + #endif diff --git a/Engine/lib/openal-soft/al/buffer.cpp b/Engine/lib/openal-soft/al/buffer.cpp index 14fae1ee8..13407103b 100644 --- a/Engine/lib/openal-soft/al/buffer.cpp +++ b/Engine/lib/openal-soft/al/buffer.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include "AL/al.h" @@ -43,16 +44,23 @@ #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" +#ifdef ALSOFT_EAX +#include "eax_globals.h" +#include "eax_x_ram.h" +#endif // ALSOFT_EAX + namespace { @@ -110,10 +118,10 @@ void DecodeIMA4Block(int16_t *dst, const al::byte *src, size_t numchans, size_t for(size_t c{0};c < numchans;c++) { - sample[c] = al::to_integer(src[0]) | (al::to_integer(src[1])<<8); + sample[c] = src[0] | (src[1]<<8); sample[c] = (sample[c]^0x8000) - 32768; src += 2; - index[c] = al::to_integer(src[0]) | (al::to_integer(src[1])<<8); + index[c] = src[0] | (src[1]<<8); index[c] = clampi((index[c]^0x8000) - 32768, 0, 88); src += 2; @@ -126,8 +134,8 @@ void DecodeIMA4Block(int16_t *dst, const al::byte *src, size_t numchans, size_t { for(size_t c{0};c < numchans;c++) { - code[c] = al::to_integer(src[0]) | (al::to_integer(src[1])<< 8) | - (al::to_integer(src[2])<<16) | (al::to_integer(src[3])<<24); + code[c] = ALuint{src[0]} | (ALuint{src[1]}<< 8) | (ALuint{src[2]}<<16) + | (ALuint{src[3]}<<24); src += 4; } } @@ -156,25 +164,23 @@ void DecodeMSADPCMBlock(int16_t *dst, const al::byte *src, size_t numchans, size for(size_t c{0};c < numchans;c++) { - blockpred[c] = std::min(al::to_integer(src[0]), 6); + blockpred[c] = std::min(src[0], 6); ++src; } for(size_t c{0};c < numchans;c++) { - delta[c] = al::to_integer(src[0]) | (al::to_integer(src[1])<<8); + delta[c] = src[0] | (src[1]<<8); delta[c] = (delta[c]^0x8000) - 32768; src += 2; } for(size_t c{0};c < numchans;c++) { - samples[c][0] = static_cast(al::to_integer(src[0]) | - (al::to_integer(src[1])<<8)); + samples[c][0] = static_cast(src[0] | (src[1]<<8)); src += 2; } for(size_t c{0};c < numchans;c++) { - samples[c][1] = static_cast(al::to_integer(src[0]) | - (al::to_integer(src[1])<<8)); + samples[c][1] = static_cast(src[0] | (src[1]<<8)); src += 2; } @@ -198,13 +204,13 @@ void DecodeMSADPCMBlock(int16_t *dst, const al::byte *src, size_t numchans, size int pred{(samples[c][0]*MSADPCMAdaptionCoeff[blockpred[c]][0] + samples[c][1]*MSADPCMAdaptionCoeff[blockpred[c]][1]) / 256}; - pred += (al::to_integer(nibble^0x08) - 0x08) * delta[c]; + pred += ((nibble^0x08) - 0x08) * delta[c]; pred = clampi(pred, -32768, 32767); samples[c][1] = samples[c][0]; samples[c][0] = static_cast(pred); - delta[c] = (MSADPCMAdaption[al::to_integer(nibble)] * delta[c]) / 256; + delta[c] = (MSADPCMAdaption[nibble] * delta[c]) / 256; delta[c] = maxi(16, delta[c]); *(dst++) = static_cast(pred); @@ -271,6 +277,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; } @@ -310,11 +319,82 @@ ALenum EnumFromAmbiScaling(AmbiScaling scale) { case AmbiScaling::FuMa: return AL_FUMA_SOFT; case AmbiScaling::SN3D: return AL_SN3D_SOFT; - case AmbiScaling::N3D: return AL_SN3D_SOFT; + case AmbiScaling::N3D: return AL_N3D_SOFT; + case AmbiScaling::UHJ: break; } throw std::runtime_error{"Invalid AmbiScaling: "+std::to_string(int(scale))}; } +al::optional FmtFromUserFmt(UserFmtChannels chans) +{ + switch(chans) + { + case UserFmtMono: return al::make_optional(FmtMono); + case UserFmtStereo: return al::make_optional(FmtStereo); + case UserFmtRear: return al::make_optional(FmtRear); + case UserFmtQuad: return al::make_optional(FmtQuad); + case UserFmtX51: return al::make_optional(FmtX51); + case UserFmtX61: return al::make_optional(FmtX61); + case UserFmtX71: return al::make_optional(FmtX71); + case UserFmtBFormat2D: return al::make_optional(FmtBFormat2D); + case UserFmtBFormat3D: return al::make_optional(FmtBFormat3D); + case UserFmtUHJ2: return al::make_optional(FmtUHJ2); + case UserFmtUHJ3: return al::make_optional(FmtUHJ3); + case UserFmtUHJ4: return al::make_optional(FmtUHJ4); + } + return al::nullopt; +} +al::optional FmtFromUserFmt(UserFmtType type) +{ + switch(type) + { + case UserFmtUByte: return al::make_optional(FmtUByte); + case UserFmtShort: return al::make_optional(FmtShort); + case UserFmtFloat: return al::make_optional(FmtFloat); + case UserFmtDouble: return al::make_optional(FmtDouble); + case UserFmtMulaw: return al::make_optional(FmtMulaw); + case UserFmtAlaw: return al::make_optional(FmtAlaw); + /* ADPCM not handled here. */ + case UserFmtIMA4: break; + case UserFmtMSADPCM: break; + } + return al::nullopt; +} + + +#ifdef ALSOFT_EAX +bool eax_x_ram_check_availability(const ALCdevice &device, const ALbuffer &buffer, + const ALuint newsize) noexcept +{ + ALuint freemem{device.eax_x_ram_free_size}; + /* If the buffer is currently in "hardware", add its memory to the free + * pool since it'll be "replaced". + */ + if(buffer.eax_x_ram_is_hardware) + freemem += buffer.OriginalSize; + return freemem >= newsize; +} + +void eax_x_ram_apply(ALCdevice &device, ALbuffer &buffer) noexcept +{ + if(buffer.eax_x_ram_is_hardware) + return; + + if(device.eax_x_ram_free_size >= buffer.OriginalSize) + { + device.eax_x_ram_free_size -= buffer.OriginalSize; + buffer.eax_x_ram_is_hardware = true; + } +} + +void eax_x_ram_clear(ALCdevice& al_device, ALbuffer& al_buffer) +{ + if(al_buffer.eax_x_ram_is_hardware) + al_device.eax_x_ram_free_size += al_buffer.OriginalSize; + al_buffer.eax_x_ram_is_hardware = false; +} +#endif // ALSOFT_EAX + constexpr ALbitfieldSOFT INVALID_STORAGE_MASK{~unsigned(AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT | AL_MAP_PERSISTENT_BIT_SOFT | AL_PRESERVE_DATA_BIT_SOFT)}; @@ -352,13 +432,12 @@ ALbuffer *AllocBuffer(ALCdevice *device) { auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(), [](const BufferSubList &entry) noexcept -> bool - { return entry.FreeMask != 0; } - ); - + { return entry.FreeMask != 0; }); auto lidx = static_cast(std::distance(device->BufferList.begin(), sublist)); auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); + ASSUME(slidx < 64); - ALbuffer *buffer{::new (sublist->Buffers + slidx) ALbuffer{}}; + ALbuffer *buffer{al::construct_at(sublist->Buffers + slidx)}; /* Add 1 to avoid buffer ID 0. */ buffer->id = ((lidx<<6) | slidx) + 1; @@ -370,6 +449,10 @@ ALbuffer *AllocBuffer(ALCdevice *device) void FreeBuffer(ALCdevice *device, ALbuffer *buffer) { +#ifdef ALSOFT_EAX + eax_x_ram_clear(*device, *buffer); +#endif // ALSOFT_EAX + const ALuint id{buffer->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; @@ -453,46 +536,26 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, ALBuf->id); /* Currently no channel configurations need to be converted. */ - FmtChannels DstChannels{FmtMono}; - switch(SrcChannels) - { - case UserFmtMono: DstChannels = FmtMono; break; - case UserFmtStereo: DstChannels = FmtStereo; break; - case UserFmtRear: DstChannels = FmtRear; break; - case UserFmtQuad: DstChannels = FmtQuad; break; - case UserFmtX51: DstChannels = FmtX51; break; - case UserFmtX61: DstChannels = FmtX61; break; - case UserFmtX71: DstChannels = FmtX71; break; - case UserFmtBFormat2D: DstChannels = FmtBFormat2D; break; - case UserFmtBFormat3D: DstChannels = FmtBFormat3D; break; - } - if UNLIKELY(static_cast(SrcChannels) != static_cast(DstChannels)) + auto DstChannels = FmtFromUserFmt(SrcChannels); + if UNLIKELY(!DstChannels) SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format"); - /* IMA4 and MSADPCM convert to 16-bit short. */ - FmtType DstType{FmtUByte}; - switch(SrcType) - { - case UserFmtUByte: DstType = FmtUByte; break; - case UserFmtShort: DstType = FmtShort; break; - case UserFmtFloat: DstType = FmtFloat; break; - case UserFmtDouble: DstType = FmtDouble; break; - case UserFmtAlaw: DstType = FmtAlaw; break; - case UserFmtMulaw: DstType = FmtMulaw; break; - case UserFmtIMA4: DstType = FmtShort; break; - case UserFmtMSADPCM: DstType = FmtShort; break; - } - - /* TODO: Currently we can only map samples when they're not converted. To + /* IMA4 and MSADPCM convert to 16-bit short. + * + * TODO: Currently we can only map samples when they're not converted. To * allow it would need some kind of double-buffering to hold onto a copy of * the original data. */ if((access&MAP_READ_WRITE_FLAGS)) { - if UNLIKELY(static_cast(SrcType) != static_cast(DstType)) + if UNLIKELY(SrcType == UserFmtIMA4 || SrcType == UserFmtMSADPCM) SETERR_RETURN(context, AL_INVALID_VALUE,, "%s samples cannot be mapped", NameFromUserFmtType(SrcType)); } + auto DstType = (SrcType == UserFmtIMA4 || SrcType == UserFmtMSADPCM) + ? al::make_optional(FmtShort) : FmtFromUserFmt(SrcType); + if UNLIKELY(!DstType) + SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format"); const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(SrcType, unpackalign)}; @@ -500,13 +563,13 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid unpack alignment %u for %s samples", unpackalign, NameFromUserFmtType(SrcType)); - const ALuint ambiorder{(DstChannels == FmtBFormat2D || DstChannels == FmtBFormat3D) ? - ALBuf->UnpackAmbiOrder : 0}; + const ALuint ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder : + (IsUHJ(*DstChannels) ? 1 : 0)}; if((access&AL_PRESERVE_DATA_BIT_SOFT)) { /* Can only preserve data with the same format and alignment. */ - if UNLIKELY(ALBuf->mChannels != DstChannels || ALBuf->OriginalType != SrcType) + if UNLIKELY(ALBuf->mChannels != *DstChannels || ALBuf->OriginalType != SrcType) SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched format"); if UNLIKELY(ALBuf->OriginalAlign != align) SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched alignment"); @@ -534,13 +597,23 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, /* Convert the sample frames to the number of bytes needed for internal * storage. */ - ALuint NumChannels{ChannelsFromFmt(DstChannels, ambiorder)}; - ALuint FrameSize{NumChannels * BytesFromFmt(DstType)}; + ALuint NumChannels{ChannelsFromFmt(*DstChannels, ambiorder)}; + ALuint FrameSize{NumChannels * BytesFromFmt(*DstType)}; if UNLIKELY(frames > std::numeric_limits::max()/FrameSize) SETERR_RETURN(context, AL_OUT_OF_MEMORY,, "Buffer size overflow, %d frames x %d bytes per frame", frames, FrameSize); size_t newsize{static_cast(frames) * FrameSize}; +#ifdef ALSOFT_EAX + if(ALBuf->eax_x_ram_mode == AL_STORAGE_HARDWARE) + { + ALCdevice &device = *context->mALDevice; + if(!eax_x_ram_check_availability(device, *ALBuf, size)) + SETERR_RETURN(context, AL_OUT_OF_MEMORY,, + "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, size); + } +#endif + /* Round up to the next 16-byte multiple. This could reallocate only when * increasing or the new size is less than half the current, but then the * buffer's AL_SIZE would not be very reliable for accounting buffer memory @@ -561,7 +634,7 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, if(SrcType == UserFmtIMA4) { - assert(DstType == FmtShort); + assert(*DstType == FmtShort); if(SrcData != nullptr && !ALBuf->mData.empty()) Convert_int16_ima4(reinterpret_cast(ALBuf->mData.data()), SrcData, NumChannels, frames, align); @@ -569,7 +642,7 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, } else if(SrcType == UserFmtMSADPCM) { - assert(DstType == FmtShort); + assert(*DstType == FmtShort); if(SrcData != nullptr && !ALBuf->mData.empty()) Convert_int16_msadpcm(reinterpret_cast(ALBuf->mData.data()), SrcData, NumChannels, frames, align); @@ -577,7 +650,7 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, } else { - assert(static_cast(SrcType) == static_cast(DstType)); + assert(DstType.has_value()); if(SrcData != nullptr && !ALBuf->mData.empty()) std::copy_n(SrcData, frames*FrameSize, ALBuf->mData.begin()); ALBuf->OriginalAlign = 1; @@ -588,8 +661,8 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, ALBuf->Access = access; ALBuf->mSampleRate = static_cast(freq); - ALBuf->mChannels = DstChannels; - ALBuf->mType = DstType; + ALBuf->mChannels = *DstChannels; + ALBuf->mType = *DstType; ALBuf->mAmbiOrder = ambiorder; ALBuf->mCallback = nullptr; @@ -598,11 +671,16 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, ALBuf->mSampleLen = frames; ALBuf->mLoopStart = 0; ALBuf->mLoopEnd = ALBuf->mSampleLen; + +#ifdef ALSOFT_EAX + if(eax_g_is_enabled && ALBuf->eax_x_ram_mode != AL_STORAGE_ACCESSIBLE) + eax_x_ram_apply(*context->mALDevice, *ALBuf); +#endif } /** Prepares the buffer to use the specified callback, using the specified format. */ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, - UserFmtChannels SrcChannels, UserFmtType SrcType, LPALBUFFERCALLBACKTYPESOFT callback, + UserFmtChannels SrcChannels, UserFmtType SrcType, ALBUFFERCALLBACKTYPESOFT callback, void *userptr) { if UNLIKELY(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) @@ -610,43 +688,25 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALBuf->id); /* Currently no channel configurations need to be converted. */ - FmtChannels DstChannels{FmtMono}; - switch(SrcChannels) - { - case UserFmtMono: DstChannels = FmtMono; break; - case UserFmtStereo: DstChannels = FmtStereo; break; - case UserFmtRear: DstChannels = FmtRear; break; - case UserFmtQuad: DstChannels = FmtQuad; break; - case UserFmtX51: DstChannels = FmtX51; break; - case UserFmtX61: DstChannels = FmtX61; break; - case UserFmtX71: DstChannels = FmtX71; break; - case UserFmtBFormat2D: DstChannels = FmtBFormat2D; break; - case UserFmtBFormat3D: DstChannels = FmtBFormat3D; break; - } - if UNLIKELY(static_cast(SrcChannels) != static_cast(DstChannels)) + auto DstChannels = FmtFromUserFmt(SrcChannels); + if UNLIKELY(!DstChannels) SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid format"); /* IMA4 and MSADPCM convert to 16-bit short. Not supported with callbacks. */ - FmtType DstType{FmtUByte}; - switch(SrcType) - { - case UserFmtUByte: DstType = FmtUByte; break; - case UserFmtShort: DstType = FmtShort; break; - case UserFmtFloat: DstType = FmtFloat; break; - case UserFmtDouble: DstType = FmtDouble; break; - case UserFmtAlaw: DstType = FmtAlaw; break; - case UserFmtMulaw: DstType = FmtMulaw; break; - case UserFmtIMA4: DstType = FmtShort; break; - case UserFmtMSADPCM: DstType = FmtShort; break; - } - if UNLIKELY(static_cast(SrcType) != static_cast(DstType)) + auto DstType = FmtFromUserFmt(SrcType); + if UNLIKELY(!DstType) SETERR_RETURN(context, AL_INVALID_ENUM,, "Unsupported callback format"); - const ALuint ambiorder{(DstChannels == FmtBFormat2D || DstChannels == FmtBFormat3D) ? - ALBuf->UnpackAmbiOrder : 0}; + const ALuint ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder : + (IsUHJ(*DstChannels) ? 1 : 0)}; - al::vector(FrameSizeFromFmt(DstChannels, DstType, ambiorder) * - size_t{BufferLineSize + (MaxResamplerPadding>>1)}).swap(ALBuf->mData); + static constexpr uint line_size{BufferLineSize + MaxPostVoiceLoad}; + al::vector(FrameSizeFromFmt(*DstChannels, *DstType, ambiorder) * + size_t{line_size}).swap(ALBuf->mData); + +#ifdef ALSOFT_EAX + eax_x_ram_clear(*context->mALDevice, *ALBuf); +#endif ALBuf->mCallback = callback; ALBuf->mUserData = userptr; @@ -657,8 +717,8 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALBuf->Access = 0; ALBuf->mSampleRate = static_cast(freq); - ALBuf->mChannels = DstChannels; - ALBuf->mType = DstType; + ALBuf->mChannels = *DstChannels; + ALBuf->mType = *DstType; ALBuf->mAmbiOrder = ambiorder; ALBuf->mSampleLen = 0; @@ -675,7 +735,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 +791,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 +826,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 +866,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 +906,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 +927,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); @@ -877,8 +949,10 @@ START_API_FUNC if UNLIKELY(!usrfmt) context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); else + { LoadData(context.get(), albuf, freq, static_cast(size), usrfmt->channels, usrfmt->type, static_cast(data), flags); + } } } END_API_FUNC @@ -889,7 +963,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 +1016,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 +1039,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 +1071,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 +1201,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 +1221,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 +1240,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 +1262,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 +1324,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 +1357,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 +1395,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 +1417,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 +1445,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 +1467,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 +1524,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 +1562,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) @@ -1510,13 +1584,13 @@ END_API_FUNC AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, - LPALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr, ALbitfieldSOFT flags) + ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) 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); @@ -1526,8 +1600,6 @@ START_API_FUNC context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq); else if UNLIKELY(callback == nullptr) context->setError(AL_INVALID_VALUE, "NULL callback"); - else if UNLIKELY(flags != 0) - context->setError(AL_INVALID_VALUE, "Invalid callback flags 0x%x", flags); else { auto usrfmt = DecomposeUserFormat(format); @@ -1546,7 +1618,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 +1646,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 +1674,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); @@ -1630,3 +1702,161 @@ BufferSubList::~BufferSubList() al_free(Buffers); Buffers = nullptr; } + + +#ifdef ALSOFT_EAX +FORCE_ALIGN ALboolean AL_APIENTRY EAXSetBufferMode(ALsizei n, const ALuint* buffers, ALint value) +START_API_FUNC +{ +#define EAX_PREFIX "[EAXSetBufferMode] " + + const auto context = ContextRef{GetContextRef()}; + if(!context) + { + ERR(EAX_PREFIX "%s\n", "No current context."); + return ALC_FALSE; + } + + if(!eax_g_is_enabled) + { + context->setError(AL_INVALID_OPERATION, EAX_PREFIX "%s", "EAX not enabled."); + return ALC_FALSE; + } + + switch(value) + { + case AL_STORAGE_AUTOMATIC: + case AL_STORAGE_HARDWARE: + case AL_STORAGE_ACCESSIBLE: + break; + + default: + context->setError(AL_INVALID_ENUM, EAX_PREFIX "Unsupported X-RAM mode 0x%x", value); + return ALC_FALSE; + } + + if(n == 0) + return ALC_TRUE; + + if(n < 0) + { + context->setError(AL_INVALID_VALUE, EAX_PREFIX "Buffer count %d out of range", n); + return ALC_FALSE; + } + + if(!buffers) + { + context->setError(AL_INVALID_VALUE, EAX_PREFIX "%s", "Null AL buffers"); + return ALC_FALSE; + } + + auto device = context->mALDevice.get(); + std::lock_guard device_lock{device->BufferLock}; + size_t total_needed{0}; + + // Validate the buffers. + // + for(auto i = 0;i < n;++i) + { + const auto buffer = buffers[i]; + if(buffer == AL_NONE) + continue; + + const auto al_buffer = LookupBuffer(device, buffer); + if (!al_buffer) + { + ERR(EAX_PREFIX "Invalid buffer ID %u.\n", buffer); + return ALC_FALSE; + } + + /* TODO: Is the store location allowed to change for in-use buffers, or + * only when not set/queued on a source? + */ + + if(value == AL_STORAGE_HARDWARE && !al_buffer->eax_x_ram_is_hardware) + { + /* FIXME: This doesn't account for duplicate buffers. When the same + * buffer ID is specified multiple times in the provided list, it + * counts each instance as more memory that needs to fit in X-RAM. + */ + if(unlikely(std::numeric_limits::max()-al_buffer->OriginalSize < total_needed)) + { + context->setError(AL_OUT_OF_MEMORY, EAX_PREFIX "Buffer size overflow (%u + %zu)\n", + al_buffer->OriginalSize, total_needed); + return ALC_FALSE; + } + total_needed += al_buffer->OriginalSize; + } + } + if(total_needed > device->eax_x_ram_free_size) + { + context->setError(AL_INVALID_ENUM, EAX_PREFIX "Out of X-RAM memory (need: %zu, avail: %u)", + total_needed, device->eax_x_ram_free_size); + return ALC_FALSE; + } + + // Update the mode. + // + for(auto i = 0;i < n;++i) + { + const auto buffer = buffers[i]; + if(buffer == AL_NONE) + continue; + + const auto al_buffer = LookupBuffer(device, buffer); + assert(al_buffer); + + if(value != AL_STORAGE_ACCESSIBLE) + eax_x_ram_apply(*device, *al_buffer); + else + eax_x_ram_clear(*device, *al_buffer); + al_buffer->eax_x_ram_mode = value; + } + + return AL_TRUE; + +#undef EAX_PREFIX +} +END_API_FUNC + +FORCE_ALIGN ALenum AL_APIENTRY EAXGetBufferMode(ALuint buffer, ALint* pReserved) +START_API_FUNC +{ +#define EAX_PREFIX "[EAXGetBufferMode] " + + const auto context = ContextRef{GetContextRef()}; + if(!context) + { + ERR(EAX_PREFIX "%s\n", "No current context."); + return AL_NONE; + } + + if(!eax_g_is_enabled) + { + context->setError(AL_INVALID_OPERATION, EAX_PREFIX "%s", "EAX not enabled."); + return AL_NONE; + } + + if(pReserved) + { + context->setError(AL_INVALID_VALUE, EAX_PREFIX "%s", "Non-null reserved parameter"); + return AL_NONE; + } + + auto device = context->mALDevice.get(); + std::lock_guard device_lock{device->BufferLock}; + + const auto al_buffer = LookupBuffer(device, buffer); + if(!al_buffer) + { + context->setError(AL_INVALID_NAME, EAX_PREFIX "Invalid buffer ID %u", buffer); + return AL_NONE; + } + + return al_buffer->eax_x_ram_mode; + +#undef EAX_PREFIX +} +END_API_FUNC + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/buffer.h b/Engine/lib/openal-soft/al/buffer.h index 9f72fb1ba..b3a0f0d87 100644 --- a/Engine/lib/openal-soft/al/buffer.h +++ b/Engine/lib/openal-soft/al/buffer.h @@ -6,12 +6,15 @@ #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" +#ifdef ALSOFT_EAX +#include "eax_x_ram.h" +#endif // ALSOFT_EAX /* User formats */ enum UserFmtType : unsigned char { @@ -35,6 +38,9 @@ enum UserFmtChannels : unsigned char { UserFmtX71 = FmtX71, UserFmtBFormat2D = FmtBFormat2D, UserFmtBFormat3D = FmtBFormat3D, + UserFmtUHJ2 = FmtUHJ2, + UserFmtUHJ3 = FmtUHJ3, + UserFmtUHJ4 = FmtUHJ4, }; @@ -65,6 +71,11 @@ struct ALbuffer : public BufferStorage { ALuint id{0}; DISABLE_ALLOC() + +#ifdef ALSOFT_EAX + ALenum eax_x_ram_mode{AL_STORAGE_AUTOMATIC}; + bool eax_x_ram_is_hardware{}; +#endif // ALSOFT_EAX }; #endif diff --git a/Engine/lib/openal-soft/al/eax_api.cpp b/Engine/lib/openal-soft/al/eax_api.cpp new file mode 100644 index 000000000..6b1f7fcf6 --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_api.cpp @@ -0,0 +1,1213 @@ +// +// EAX API. +// +// Based on headers `eax[2-5].h` included in Doom 3 source code: +// https://github.com/id-Software/DOOM-3/tree/master/neo/openal/include +// + +#include "config.h" + +#include + +#include "al/eax_api.h" + + +const GUID DSPROPSETID_EAX_ReverbProperties = +{ + 0x4A4E6FC1, + 0xC341, + 0x11D1, + {0xB7, 0x3A, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00} +}; + +const GUID DSPROPSETID_EAXBUFFER_ReverbProperties = +{ + 0x4A4E6FC0, + 0xC341, + 0x11D1, + {0xB7, 0x3A, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00} +}; + +const GUID DSPROPSETID_EAX20_ListenerProperties = +{ + 0x306A6A8, + 0xB224, + 0x11D2, + {0x99, 0xE5, 0x00, 0x00, 0xE8, 0xD8, 0xC7, 0x22} +}; + +const GUID DSPROPSETID_EAX20_BufferProperties = +{ + 0x306A6A7, + 0xB224, + 0x11D2, + {0x99, 0xE5, 0x00, 0x00, 0xE8, 0xD8, 0xC7, 0x22} +}; + +const GUID DSPROPSETID_EAX30_ListenerProperties = +{ + 0xA8FA6882, + 0xB476, + 0x11D3, + {0xBD, 0xB9, 0x00, 0xC0, 0xF0, 0x2D, 0xDF, 0x87} +}; + +const GUID DSPROPSETID_EAX30_BufferProperties = +{ + 0xA8FA6881, + 0xB476, + 0x11D3, + {0xBD, 0xB9, 0x00, 0xC0, 0xF0, 0x2D, 0xDF, 0x87} +}; + +const GUID EAX_NULL_GUID = +{ + 0x00000000, + 0x0000, + 0x0000, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +const GUID EAX_PrimaryFXSlotID = +{ + 0xF317866D, + 0x924C, + 0x450C, + {0x86, 0x1B, 0xE6, 0xDA, 0xA2, 0x5E, 0x7C, 0x20} +}; + +const GUID EAXPROPERTYID_EAX40_Context = +{ + 0x1D4870AD, + 0xDEF, + 0x43C0, + {0xA4, 0xC, 0x52, 0x36, 0x32, 0x29, 0x63, 0x42} +}; + +const GUID EAXPROPERTYID_EAX50_Context = +{ + 0x57E13437, + 0xB932, + 0x4AB2, + {0xB8, 0xBD, 0x52, 0x66, 0xC1, 0xA8, 0x87, 0xEE} +}; + +const GUID EAXPROPERTYID_EAX40_FXSlot0 = +{ + 0xC4D79F1E, + 0xF1AC, + 0x436B, + {0xA8, 0x1D, 0xA7, 0x38, 0xE7, 0x04, 0x54, 0x69} +}; + +const GUID EAXPROPERTYID_EAX50_FXSlot0 = +{ + 0x91F9590F, + 0xC388, + 0x407A, + {0x84, 0xB0, 0x1B, 0xAE, 0xE, 0xF7, 0x1A, 0xBC} +}; + +const GUID EAXPROPERTYID_EAX40_FXSlot1 = +{ + 0x8C00E96, + 0x74BE, + 0x4491, + {0x93, 0xAA, 0xE8, 0xAD, 0x35, 0xA4, 0x91, 0x17} +}; + +const GUID EAXPROPERTYID_EAX50_FXSlot1 = +{ + 0x8F5F7ACA, + 0x9608, + 0x4965, + {0x81, 0x37, 0x82, 0x13, 0xC7, 0xB9, 0xD9, 0xDE} +}; + +const GUID EAXPROPERTYID_EAX40_FXSlot2 = +{ + 0x1D433B88, + 0xF0F6, + 0x4637, + {0x91, 0x9F, 0x60, 0xE7, 0xE0, 0x6B, 0x5E, 0xDD} +}; + +const GUID EAXPROPERTYID_EAX50_FXSlot2 = +{ + 0x3C0F5252, + 0x9834, + 0x46F0, + {0xA1, 0xD8, 0x5B, 0x95, 0xC4, 0xA0, 0xA, 0x30} +}; + +const GUID EAXPROPERTYID_EAX40_FXSlot3 = +{ + 0xEFFF08EA, + 0xC7D8, + 0x44AB, + {0x93, 0xAD, 0x6D, 0xBD, 0x5F, 0x91, 0x00, 0x64} +}; + +const GUID EAXPROPERTYID_EAX50_FXSlot3 = +{ + 0xE2EB0EAA, + 0xE806, + 0x45E7, + {0x9F, 0x86, 0x06, 0xC1, 0x57, 0x1A, 0x6F, 0xA3} +}; + +const GUID EAXPROPERTYID_EAX40_Source = +{ + 0x1B86B823, + 0x22DF, + 0x4EAE, + {0x8B, 0x3C, 0x12, 0x78, 0xCE, 0x54, 0x42, 0x27} +}; + +const GUID EAXPROPERTYID_EAX50_Source = +{ + 0x5EDF82F0, + 0x24A7, + 0x4F38, + {0x8E, 0x64, 0x2F, 0x09, 0xCA, 0x05, 0xDE, 0xE1} +}; + +const GUID EAX_REVERB_EFFECT = +{ + 0xCF95C8F, + 0xA3CC, + 0x4849, + {0xB0, 0xB6, 0x83, 0x2E, 0xCC, 0x18, 0x22, 0xDF} +}; + +const GUID EAX_AGCCOMPRESSOR_EFFECT = +{ + 0xBFB7A01E, + 0x7825, + 0x4039, + {0x92, 0x7F, 0x03, 0xAA, 0xBD, 0xA0, 0xC5, 0x60} +}; + +const GUID EAX_AUTOWAH_EFFECT = +{ + 0xEC3130C0, + 0xAC7A, + 0x11D2, + {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + +const GUID EAX_CHORUS_EFFECT = +{ + 0xDE6D6FE0, + 0xAC79, + 0x11D2, + {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + +const GUID EAX_DISTORTION_EFFECT = +{ + 0x975A4CE0, + 0xAC7E, + 0x11D2, + {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + +const GUID EAX_ECHO_EFFECT = +{ + 0xE9F1BC0, + 0xAC82, + 0x11D2, + {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + +const GUID EAX_EQUALIZER_EFFECT = +{ + 0x65F94CE0, + 0x9793, + 0x11D3, + {0x93, 0x9D, 0x00, 0xC0, 0xF0, 0x2D, 0xD6, 0xF0} +}; + +const GUID EAX_FLANGER_EFFECT = +{ + 0xA70007C0, + 0x7D2, + 0x11D3, + {0x9B, 0x1E, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + +const GUID EAX_FREQUENCYSHIFTER_EFFECT = +{ + 0xDC3E1880, + 0x9212, + 0x11D3, + {0x93, 0x9D, 0x00, 0xC0, 0xF0, 0x2D, 0xD6, 0xF0} +}; + +const GUID EAX_VOCALMORPHER_EFFECT = +{ + 0xE41CF10C, + 0x3383, + 0x11D2, + {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + +const GUID EAX_PITCHSHIFTER_EFFECT = +{ + 0xE7905100, + 0xAFB2, + 0x11D2, + {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + +const GUID EAX_RINGMODULATOR_EFFECT = +{ + 0xB89FE60, + 0xAFB5, + 0x11D2, + {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} +}; + + +bool operator==( + const EAX40CONTEXTPROPERTIES& lhs, + const EAX40CONTEXTPROPERTIES& rhs) noexcept +{ + return + lhs.guidPrimaryFXSlotID == rhs.guidPrimaryFXSlotID && + lhs.flDistanceFactor == rhs.flDistanceFactor && + lhs.flAirAbsorptionHF == rhs.flAirAbsorptionHF && + lhs.flHFReference == rhs.flHFReference; +} + +bool operator==( + const EAX50CONTEXTPROPERTIES& lhs, + const EAX50CONTEXTPROPERTIES& rhs) noexcept +{ + return + static_cast(lhs) == static_cast(rhs) && + lhs.flMacroFXFactor == rhs.flMacroFXFactor; +} + + +const GUID EAXCONTEXT_DEFAULTPRIMARYFXSLOTID = EAXPROPERTYID_EAX40_FXSlot0; + +bool operator==( + const EAX40FXSLOTPROPERTIES& lhs, + const EAX40FXSLOTPROPERTIES& rhs) noexcept +{ + return + lhs.guidLoadEffect == rhs.guidLoadEffect && + lhs.lVolume == rhs.lVolume && + lhs.lLock == rhs.lLock && + lhs.ulFlags == rhs.ulFlags; +} + +bool operator==( + const EAX50FXSLOTPROPERTIES& lhs, + const EAX50FXSLOTPROPERTIES& rhs) noexcept +{ + return + static_cast(lhs) == static_cast(rhs) && + lhs.lOcclusion == rhs.lOcclusion && + lhs.flOcclusionLFRatio == rhs.flOcclusionLFRatio; +} + +const EAX50ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS +{{ + EAX_NULL_GUID, + EAXPROPERTYID_EAX40_FXSlot0, +}}; + +bool operator==( + const EAX50ACTIVEFXSLOTS& lhs, + const EAX50ACTIVEFXSLOTS& rhs) noexcept +{ + return std::equal( + std::cbegin(lhs.guidActiveFXSlots), + std::cend(lhs.guidActiveFXSlots), + std::begin(rhs.guidActiveFXSlots)); +} + +bool operator!=( + const EAX50ACTIVEFXSLOTS& lhs, + const EAX50ACTIVEFXSLOTS& rhs) noexcept +{ + return !(lhs == rhs); +} + + +const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS +{{ + EAX_NULL_GUID, + EAX_PrimaryFXSlotID, + EAX_NULL_GUID, + EAX_NULL_GUID, +}}; + + +const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS +{{ + EAX_NULL_GUID, + EAX_NULL_GUID, + EAX_NULL_GUID, + EAX_NULL_GUID, +}}; + +bool operator==( + const EAXREVERBPROPERTIES& lhs, + const EAXREVERBPROPERTIES& rhs) noexcept +{ + return + lhs.ulEnvironment == rhs.ulEnvironment && + lhs.flEnvironmentSize == rhs.flEnvironmentSize && + lhs.flEnvironmentDiffusion == rhs.flEnvironmentDiffusion && + lhs.lRoom == rhs.lRoom && + lhs.lRoomHF == rhs.lRoomHF && + lhs.lRoomLF == rhs.lRoomLF && + lhs.flDecayTime == rhs.flDecayTime && + lhs.flDecayHFRatio == rhs.flDecayHFRatio && + lhs.flDecayLFRatio == rhs.flDecayLFRatio && + lhs.lReflections == rhs.lReflections && + lhs.flReflectionsDelay == rhs.flReflectionsDelay && + lhs.vReflectionsPan == rhs.vReflectionsPan && + lhs.lReverb == rhs.lReverb && + lhs.flReverbDelay == rhs.flReverbDelay && + lhs.vReverbPan == rhs.vReverbPan && + lhs.flEchoTime == rhs.flEchoTime && + lhs.flEchoDepth == rhs.flEchoDepth && + lhs.flModulationTime == rhs.flModulationTime && + lhs.flModulationDepth == rhs.flModulationDepth && + lhs.flAirAbsorptionHF == rhs.flAirAbsorptionHF && + lhs.flHFReference == rhs.flHFReference && + lhs.flLFReference == rhs.flLFReference && + lhs.flRoomRolloffFactor == rhs.flRoomRolloffFactor && + lhs.ulFlags == rhs.ulFlags; +} + +bool operator!=( + const EAXREVERBPROPERTIES& lhs, + const EAXREVERBPROPERTIES& rhs) noexcept +{ + return !(lhs == rhs); +} + + +namespace { + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_GENERIC = +{ + EAXREVERB_DEFAULTENVIRONMENT, + EAXREVERB_DEFAULTENVIRONMENTSIZE, + EAXREVERB_DEFAULTENVIRONMENTDIFFUSION, + EAXREVERB_DEFAULTROOM, + EAXREVERB_DEFAULTROOMHF, + EAXREVERB_DEFAULTROOMLF, + EAXREVERB_DEFAULTDECAYTIME, + EAXREVERB_DEFAULTDECAYHFRATIO, + EAXREVERB_DEFAULTDECAYLFRATIO, + EAXREVERB_DEFAULTREFLECTIONS, + EAXREVERB_DEFAULTREFLECTIONSDELAY, + EAXREVERB_DEFAULTREFLECTIONSPAN, + EAXREVERB_DEFAULTREVERB, + EAXREVERB_DEFAULTREVERBDELAY, + EAXREVERB_DEFAULTREVERBPAN, + EAXREVERB_DEFAULTECHOTIME, + EAXREVERB_DEFAULTECHODEPTH, + EAXREVERB_DEFAULTMODULATIONTIME, + EAXREVERB_DEFAULTMODULATIONDEPTH, + EAXREVERB_DEFAULTAIRABSORPTIONHF, + EAXREVERB_DEFAULTHFREFERENCE, + EAXREVERB_DEFAULTLFREFERENCE, + EAXREVERB_DEFAULTROOMROLLOFFFACTOR, + EAXREVERB_DEFAULTFLAGS, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PADDEDCELL = +{ + EAX_ENVIRONMENT_PADDEDCELL, + 1.4F, + 1.0F, + -1'000L, + -6'000L, + 0L, + 0.17F, + 0.10F, + 1.0F, + -1'204L, + 0.001F, + EAXVECTOR{}, + 207L, + 0.002F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ROOM = +{ + EAX_ENVIRONMENT_ROOM, + 1.9F, + 1.0F, + -1'000L, + -454L, + 0L, + 0.40F, + 0.83F, + 1.0F, + -1'646L, + 0.002F, + EAXVECTOR{}, + 53L, + 0.003F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_BATHROOM = +{ + EAX_ENVIRONMENT_BATHROOM, + 1.4F, + 1.0F, + -1'000L, + -1'200L, + 0L, + 1.49F, + 0.54F, + 1.0F, + -370L, + 0.007F, + EAXVECTOR{}, + 1'030L, + 0.011F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_LIVINGROOM = +{ + EAX_ENVIRONMENT_LIVINGROOM, + 2.5F, + 1.0F, + -1'000L, + -6'000L, + 0L, + 0.50F, + 0.10F, + 1.0F, + -1'376, + 0.003F, + EAXVECTOR{}, + -1'104L, + 0.004F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_STONEROOM = +{ + EAX_ENVIRONMENT_STONEROOM, + 11.6F, + 1.0F, + -1'000L, + -300L, + 0L, + 2.31F, + 0.64F, + 1.0F, + -711L, + 0.012F, + EAXVECTOR{}, + 83L, + 0.017F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_AUDITORIUM = +{ + EAX_ENVIRONMENT_AUDITORIUM, + 21.6F, + 1.0F, + -1'000L, + -476L, + 0L, + 4.32F, + 0.59F, + 1.0F, + -789L, + 0.020F, + EAXVECTOR{}, + -289L, + 0.030F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CONCERTHALL = +{ + EAX_ENVIRONMENT_CONCERTHALL, + 19.6F, + 1.0F, + -1'000L, + -500L, + 0L, + 3.92F, + 0.70F, + 1.0F, + -1'230L, + 0.020F, + EAXVECTOR{}, + -2L, + 0.029F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CAVE = +{ + EAX_ENVIRONMENT_CAVE, + 14.6F, + 1.0F, + -1'000L, + 0L, + 0L, + 2.91F, + 1.30F, + 1.0F, + -602L, + 0.015F, + EAXVECTOR{}, + -302L, + 0.022F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x1FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ARENA = +{ + EAX_ENVIRONMENT_ARENA, + 36.2F, + 1.0F, + -1'000L, + -698L, + 0L, + 7.24F, + 0.33F, + 1.0F, + -1'166L, + 0.020F, + EAXVECTOR{}, + 16L, + 0.030F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_HANGAR = +{ + EAX_ENVIRONMENT_HANGAR, + 50.3F, + 1.0F, + -1'000L, + -1'000L, + 0L, + 10.05F, + 0.23F, + 1.0F, + -602L, + 0.020F, + EAXVECTOR{}, + 198L, + 0.030F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CARPETTEDHALLWAY = +{ + EAX_ENVIRONMENT_CARPETEDHALLWAY, + 1.9F, + 1.0F, + -1'000L, + -4'000L, + 0L, + 0.30F, + 0.10F, + 1.0F, + -1'831L, + 0.002F, + EAXVECTOR{}, + -1'630L, + 0.030F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_HALLWAY = +{ + EAX_ENVIRONMENT_HALLWAY, + 1.8F, + 1.0F, + -1'000L, + -300L, + 0L, + 1.49F, + 0.59F, + 1.0F, + -1'219L, + 0.007F, + EAXVECTOR{}, + 441L, + 0.011F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_STONECORRIDOR = +{ + EAX_ENVIRONMENT_STONECORRIDOR, + 13.5F, + 1.0F, + -1'000L, + -237L, + 0L, + 2.70F, + 0.79F, + 1.0F, + -1'214L, + 0.013F, + EAXVECTOR{}, + 395L, + 0.020F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ALLEY = +{ + EAX_ENVIRONMENT_ALLEY, + 7.5F, + 0.300F, + -1'000L, + -270L, + 0L, + 1.49F, + 0.86F, + 1.0F, + -1'204L, + 0.007F, + EAXVECTOR{}, + -4L, + 0.011F, + EAXVECTOR{}, + 0.125F, + 0.950F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_FOREST = +{ + EAX_ENVIRONMENT_FOREST, + 38.0F, + 0.300F, + -1'000L, + -3'300L, + 0L, + 1.49F, + 0.54F, + 1.0F, + -2'560L, + 0.162F, + EAXVECTOR{}, + -229L, + 0.088F, + EAXVECTOR{}, + 0.125F, + 1.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CITY = +{ + EAX_ENVIRONMENT_CITY, + 7.5F, + 0.500F, + -1'000L, + -800L, + 0L, + 1.49F, + 0.67F, + 1.0F, + -2'273L, + 0.007F, + EAXVECTOR{}, + -1'691L, + 0.011F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_MOUNTAINS = +{ + EAX_ENVIRONMENT_MOUNTAINS, + 100.0F, + 0.270F, + -1'000L, + -2'500L, + 0L, + 1.49F, + 0.21F, + 1.0F, + -2'780L, + 0.300F, + EAXVECTOR{}, + -1'434L, + 0.100F, + EAXVECTOR{}, + 0.250F, + 1.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x1FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_QUARRY = +{ + EAX_ENVIRONMENT_QUARRY, + 17.5F, + 1.0F, + -1'000L, + -1'000L, + 0L, + 1.49F, + 0.83F, + 1.0F, + -10'000L, + 0.061F, + EAXVECTOR{}, + 500L, + 0.025F, + EAXVECTOR{}, + 0.125F, + 0.700F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PLAIN = +{ + EAX_ENVIRONMENT_PLAIN, + 42.5F, + 0.210F, + -1'000L, + -2'000L, + 0L, + 1.49F, + 0.50F, + 1.0F, + -2'466L, + 0.179F, + EAXVECTOR{}, + -1'926L, + 0.100F, + EAXVECTOR{}, + 0.250F, + 1.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PARKINGLOT = +{ + EAX_ENVIRONMENT_PARKINGLOT, + 8.3F, + 1.0F, + -1'000L, + 0L, + 0L, + 1.65F, + 1.50F, + 1.0F, + -1'363L, + 0.008F, + EAXVECTOR{}, + -1'153L, + 0.012F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x1FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_SEWERPIPE = +{ + EAX_ENVIRONMENT_SEWERPIPE, + 1.7F, + 0.800F, + -1'000L, + -1'000L, + 0L, + 2.81F, + 0.14F, + 1.0F, + 429L, + 0.014F, + EAXVECTOR{}, + 1'023L, + 0.021F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 0.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_UNDERWATER = +{ + EAX_ENVIRONMENT_UNDERWATER, + 1.8F, + 1.0F, + -1'000L, + -4'000L, + 0L, + 1.49F, + 0.10F, + 1.0F, + -449L, + 0.007F, + EAXVECTOR{}, + 1'700L, + 0.011F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 1.180F, + 0.348F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x3FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_DRUGGED = +{ + EAX_ENVIRONMENT_DRUGGED, + 1.9F, + 0.500F, + -1'000L, + 0L, + 0L, + 8.39F, + 1.39F, + 1.0F, + -115L, + 0.002F, + EAXVECTOR{}, + 985L, + 0.030F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 0.250F, + 1.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x1FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_DIZZY = +{ + EAX_ENVIRONMENT_DIZZY, + 1.8F, + 0.600F, + -1'000L, + -400L, + 0L, + 17.23F, + 0.56F, + 1.0F, + -1'713L, + 0.020F, + EAXVECTOR{}, + -613L, + 0.030F, + EAXVECTOR{}, + 0.250F, + 1.0F, + 0.810F, + 0.310F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x1FUL, +}; + +constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PSYCHOTIC = +{ + EAX_ENVIRONMENT_PSYCHOTIC, + 1.0F, + 0.500F, + -1'000L, + -151L, + 0L, + 7.56F, + 0.91F, + 1.0F, + -626L, + 0.020F, + EAXVECTOR{}, + 774L, + 0.030F, + EAXVECTOR{}, + 0.250F, + 0.0F, + 4.0F, + 1.0F, + -5.0F, + 5'000.0F, + 250.0F, + 0.0F, + 0x1FUL, +}; + +} // namespace + +const EaxReverbPresets EAXREVERB_PRESETS{{ + EAXREVERB_PRESET_GENERIC, + EAXREVERB_PRESET_PADDEDCELL, + EAXREVERB_PRESET_ROOM, + EAXREVERB_PRESET_BATHROOM, + EAXREVERB_PRESET_LIVINGROOM, + EAXREVERB_PRESET_STONEROOM, + EAXREVERB_PRESET_AUDITORIUM, + EAXREVERB_PRESET_CONCERTHALL, + EAXREVERB_PRESET_CAVE, + EAXREVERB_PRESET_ARENA, + EAXREVERB_PRESET_HANGAR, + EAXREVERB_PRESET_CARPETTEDHALLWAY, + EAXREVERB_PRESET_HALLWAY, + EAXREVERB_PRESET_STONECORRIDOR, + EAXREVERB_PRESET_ALLEY, + EAXREVERB_PRESET_FOREST, + EAXREVERB_PRESET_CITY, + EAXREVERB_PRESET_MOUNTAINS, + EAXREVERB_PRESET_QUARRY, + EAXREVERB_PRESET_PLAIN, + EAXREVERB_PRESET_PARKINGLOT, + EAXREVERB_PRESET_SEWERPIPE, + EAXREVERB_PRESET_UNDERWATER, + EAXREVERB_PRESET_DRUGGED, + EAXREVERB_PRESET_DIZZY, + EAXREVERB_PRESET_PSYCHOTIC, +}}; + +namespace { +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_GENERIC = {EAX_ENVIRONMENT_GENERIC, 0.5F, 1.493F, 0.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PADDEDCELL = {EAX_ENVIRONMENT_PADDEDCELL, 0.25F, 0.1F, 0.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ROOM = {EAX_ENVIRONMENT_ROOM, 0.417F, 0.4F, 0.666F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_BATHROOM = {EAX_ENVIRONMENT_BATHROOM, 0.653F, 1.499F, 0.166F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_LIVINGROOM = {EAX_ENVIRONMENT_LIVINGROOM, 0.208F, 0.478F, 0.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_STONEROOM = {EAX_ENVIRONMENT_STONEROOM, 0.5F, 2.309F, 0.888F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_AUDITORIUM = {EAX_ENVIRONMENT_AUDITORIUM, 0.403F, 4.279F, 0.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CONCERTHALL = {EAX_ENVIRONMENT_CONCERTHALL, 0.5F, 3.961F, 0.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CAVE = {EAX_ENVIRONMENT_CAVE, 0.5F, 2.886F, 1.304F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ARENA = {EAX_ENVIRONMENT_ARENA, 0.361F, 7.284F, 0.332F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_HANGAR = {EAX_ENVIRONMENT_HANGAR, 0.5F, 10.0F, 0.3F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CARPETTEDHALLWAY = {EAX_ENVIRONMENT_CARPETEDHALLWAY, 0.153F, 0.259F, 2.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_HALLWAY = {EAX_ENVIRONMENT_HALLWAY, 0.361F, 1.493F, 0.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_STONECORRIDOR = {EAX_ENVIRONMENT_STONECORRIDOR, 0.444F, 2.697F, 0.638F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ALLEY = {EAX_ENVIRONMENT_ALLEY, 0.25F, 1.752F, 0.776F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_FOREST = {EAX_ENVIRONMENT_FOREST, 0.111F, 3.145F, 0.472F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CITY = {EAX_ENVIRONMENT_CITY, 0.111F, 2.767F, 0.224F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_MOUNTAINS = {EAX_ENVIRONMENT_MOUNTAINS, 0.194F, 7.841F, 0.472F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_QUARRY = {EAX_ENVIRONMENT_QUARRY, 1.0F, 1.499F, 0.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PLAIN = {EAX_ENVIRONMENT_PLAIN, 0.097F, 2.767F, 0.224F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PARKINGLOT = {EAX_ENVIRONMENT_PARKINGLOT, 0.208F, 1.652F, 1.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_SEWERPIPE = {EAX_ENVIRONMENT_SEWERPIPE, 0.652F, 2.886F, 0.25F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_UNDERWATER = {EAX_ENVIRONMENT_UNDERWATER, 1.0F, 1.499F, 0.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_DRUGGED = {EAX_ENVIRONMENT_DRUGGED, 0.875F, 8.392F, 1.388F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_DIZZY = {EAX_ENVIRONMENT_DIZZY, 0.139F, 17.234F, 0.666F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PSYCHOTIC = {EAX_ENVIRONMENT_PSYCHOTIC, 0.486F, 7.563F, 0.806F}; +} // namespace + +const Eax1ReverbPresets EAX1REVERB_PRESETS{{ + EAX1REVERB_PRESET_GENERIC, + EAX1REVERB_PRESET_PADDEDCELL, + EAX1REVERB_PRESET_ROOM, + EAX1REVERB_PRESET_BATHROOM, + EAX1REVERB_PRESET_LIVINGROOM, + EAX1REVERB_PRESET_STONEROOM, + EAX1REVERB_PRESET_AUDITORIUM, + EAX1REVERB_PRESET_CONCERTHALL, + EAX1REVERB_PRESET_CAVE, + EAX1REVERB_PRESET_ARENA, + EAX1REVERB_PRESET_HANGAR, + EAX1REVERB_PRESET_CARPETTEDHALLWAY, + EAX1REVERB_PRESET_HALLWAY, + EAX1REVERB_PRESET_STONECORRIDOR, + EAX1REVERB_PRESET_ALLEY, + EAX1REVERB_PRESET_FOREST, + EAX1REVERB_PRESET_CITY, + EAX1REVERB_PRESET_MOUNTAINS, + EAX1REVERB_PRESET_QUARRY, + EAX1REVERB_PRESET_PLAIN, + EAX1REVERB_PRESET_PARKINGLOT, + EAX1REVERB_PRESET_SEWERPIPE, + EAX1REVERB_PRESET_UNDERWATER, + EAX1REVERB_PRESET_DRUGGED, + EAX1REVERB_PRESET_DIZZY, + EAX1REVERB_PRESET_PSYCHOTIC, +}}; diff --git a/Engine/lib/openal-soft/al/eax_api.h b/Engine/lib/openal-soft/al/eax_api.h new file mode 100644 index 000000000..d0737d1d7 --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_api.h @@ -0,0 +1,1557 @@ +#ifndef EAX_API_INCLUDED +#define EAX_API_INCLUDED + + +// +// EAX API. +// +// Based on headers `eax[2-5].h` included in Doom 3 source code: +// https://github.com/id-Software/DOOM-3/tree/master/neo/openal/include +// + + +#include +#include +#include + +#include + +#include "AL/al.h" + + +#ifndef GUID_DEFINED +#define GUID_DEFINED +typedef struct _GUID +{ + std::uint32_t Data1; + std::uint16_t Data2; + std::uint16_t Data3; + std::uint8_t Data4[8]; +} GUID; + +inline bool operator==(const GUID& lhs, const GUID& rhs) noexcept +{ + return std::memcmp(&lhs, &rhs, sizeof(GUID)) == 0; +} + +inline bool operator!=(const GUID& lhs, const GUID& rhs) noexcept +{ + return !(lhs == rhs); +} +#endif // GUID_DEFINED + + +extern const GUID DSPROPSETID_EAX_ReverbProperties; + +enum DSPROPERTY_EAX_REVERBPROPERTY : unsigned int +{ + DSPROPERTY_EAX_ALL, + DSPROPERTY_EAX_ENVIRONMENT, + DSPROPERTY_EAX_VOLUME, + DSPROPERTY_EAX_DECAYTIME, + DSPROPERTY_EAX_DAMPING, +}; // DSPROPERTY_EAX_REVERBPROPERTY + +struct EAX_REVERBPROPERTIES +{ + unsigned long environment; + float fVolume; + float fDecayTime_sec; + float fDamping; +}; // EAX_REVERBPROPERTIES + +inline bool operator==(const EAX_REVERBPROPERTIES& lhs, const EAX_REVERBPROPERTIES& rhs) noexcept +{ + return std::memcmp(&lhs, &rhs, sizeof(EAX_REVERBPROPERTIES)) == 0; +} + +extern const GUID DSPROPSETID_EAXBUFFER_ReverbProperties; + +enum DSPROPERTY_EAXBUFFER_REVERBPROPERTY : unsigned int +{ + DSPROPERTY_EAXBUFFER_ALL, + DSPROPERTY_EAXBUFFER_REVERBMIX, +}; // DSPROPERTY_EAXBUFFER_REVERBPROPERTY + +struct EAXBUFFER_REVERBPROPERTIES +{ + float fMix; +}; + +inline bool operator==(const EAXBUFFER_REVERBPROPERTIES& lhs, const EAXBUFFER_REVERBPROPERTIES& rhs) noexcept +{ + return lhs.fMix == rhs.fMix; +} + +constexpr auto EAX_BUFFER_MINREVERBMIX = 0.0F; +constexpr auto EAX_BUFFER_MAXREVERBMIX = 1.0F; +constexpr auto EAX_REVERBMIX_USEDISTANCE = -1.0F; + + +extern const GUID DSPROPSETID_EAX20_ListenerProperties; + +enum DSPROPERTY_EAX20_LISTENERPROPERTY : + unsigned int +{ + DSPROPERTY_EAX20LISTENER_NONE, + DSPROPERTY_EAX20LISTENER_ALLPARAMETERS, + DSPROPERTY_EAX20LISTENER_ROOM, + DSPROPERTY_EAX20LISTENER_ROOMHF, + DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAX20LISTENER_DECAYTIME, + DSPROPERTY_EAX20LISTENER_DECAYHFRATIO, + DSPROPERTY_EAX20LISTENER_REFLECTIONS, + DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY, + DSPROPERTY_EAX20LISTENER_REVERB, + DSPROPERTY_EAX20LISTENER_REVERBDELAY, + DSPROPERTY_EAX20LISTENER_ENVIRONMENT, + DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE, + DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION, + DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF, + DSPROPERTY_EAX20LISTENER_FLAGS +}; // DSPROPERTY_EAX20_LISTENERPROPERTY + +struct EAX20LISTENERPROPERTIES +{ + long lRoom; // room effect level at low frequencies + long lRoomHF; // room effect high-frequency level re. low frequency level + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + float flDecayTime; // reverberation decay time at low frequencies + float flDecayHFRatio; // high-frequency to low-frequency decay time ratio + long lReflections; // early reflections level relative to room effect + float flReflectionsDelay; // initial reflection delay time + long lReverb; // late reverberation level relative to room effect + float flReverbDelay; // late reverberation delay time relative to initial reflection + unsigned long dwEnvironment; // sets all listener properties + float flEnvironmentSize; // environment size in meters + float flEnvironmentDiffusion; // environment diffusion + float flAirAbsorptionHF; // change in level per meter at 5 kHz + unsigned long dwFlags; // modifies the behavior of properties +}; // EAX20LISTENERPROPERTIES + + +extern const GUID DSPROPSETID_EAX20_BufferProperties; + + +enum DSPROPERTY_EAX20_BUFFERPROPERTY : + unsigned int +{ + DSPROPERTY_EAX20BUFFER_NONE, + DSPROPERTY_EAX20BUFFER_ALLPARAMETERS, + DSPROPERTY_EAX20BUFFER_DIRECT, + DSPROPERTY_EAX20BUFFER_DIRECTHF, + DSPROPERTY_EAX20BUFFER_ROOM, + DSPROPERTY_EAX20BUFFER_ROOMHF, + DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAX20BUFFER_OBSTRUCTION, + DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO, + DSPROPERTY_EAX20BUFFER_OCCLUSION, + DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO, + DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO, + DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF, + DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR, + DSPROPERTY_EAX20BUFFER_FLAGS +}; // DSPROPERTY_EAX20_BUFFERPROPERTY + + +struct EAX20BUFFERPROPERTIES +{ + long lDirect; // direct path level + long lDirectHF; // direct path level at high frequencies + long lRoom; // room effect level + long lRoomHF; // room effect level at high frequencies + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + long lObstruction; // main obstruction control (attenuation at high frequencies) + float flObstructionLFRatio; // obstruction low-frequency level re. main control + long lOcclusion; // main occlusion control (attenuation at high frequencies) + float flOcclusionLFRatio; // occlusion low-frequency level re. main control + float flOcclusionRoomRatio; // occlusion room effect level re. main control + long lOutsideVolumeHF; // outside sound cone level at high frequencies + float flAirAbsorptionFactor; // multiplies DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF + unsigned long dwFlags; // modifies the behavior of properties +}; // EAX20BUFFERPROPERTIES + + +extern const GUID DSPROPSETID_EAX30_ListenerProperties; + +extern const GUID DSPROPSETID_EAX30_BufferProperties; + + +constexpr auto EAX_MAX_FXSLOTS = 4; + +constexpr auto EAX40_MAX_ACTIVE_FXSLOTS = 2; +constexpr auto EAX50_MAX_ACTIVE_FXSLOTS = 4; + + +constexpr auto EAX_OK = 0L; +constexpr auto EAXERR_INVALID_OPERATION = -1L; +constexpr auto EAXERR_INVALID_VALUE = -2L; +constexpr auto EAXERR_NO_EFFECT_LOADED = -3L; +constexpr auto EAXERR_UNKNOWN_EFFECT = -4L; +constexpr auto EAXERR_INCOMPATIBLE_SOURCE_TYPE = -5L; +constexpr auto EAXERR_INCOMPATIBLE_EAX_VERSION = -6L; + + +extern const GUID EAX_NULL_GUID; + +extern const GUID EAX_PrimaryFXSlotID; + + +struct EAXVECTOR +{ + float x; + float y; + float z; +}; // EAXVECTOR + +inline bool operator==(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept +{ return std::memcmp(&lhs, &rhs, sizeof(EAXVECTOR)) == 0; } + +inline bool operator!=(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept +{ return !(lhs == rhs); } + + +extern const GUID EAXPROPERTYID_EAX40_Context; + +extern const GUID EAXPROPERTYID_EAX50_Context; + +// EAX50 +enum : + unsigned long +{ + HEADPHONES = 0, + SPEAKERS_2, + SPEAKERS_4, + SPEAKERS_5, // 5.1 speakers + SPEAKERS_6, // 6.1 speakers + SPEAKERS_7, // 7.1 speakers +}; + +// EAX50 +enum : + unsigned long +{ + EAX_40 = 5, // EAX 4.0 + EAX_50 = 6, // EAX 5.0 +}; + +constexpr auto EAXCONTEXT_MINEAXSESSION = EAX_40; +constexpr auto EAXCONTEXT_MAXEAXSESSION = EAX_50; +constexpr auto EAXCONTEXT_DEFAULTEAXSESSION = EAX_40; + +constexpr auto EAXCONTEXT_MINMAXACTIVESENDS = 2UL; +constexpr auto EAXCONTEXT_MAXMAXACTIVESENDS = 4UL; +constexpr auto EAXCONTEXT_DEFAULTMAXACTIVESENDS = 2UL; + +// EAX50 +struct EAXSESSIONPROPERTIES +{ + unsigned long ulEAXVersion; + unsigned long ulMaxActiveSends; +}; // EAXSESSIONPROPERTIES + +enum EAXCONTEXT_PROPERTY : + unsigned int +{ + EAXCONTEXT_NONE = 0, + EAXCONTEXT_ALLPARAMETERS, + EAXCONTEXT_PRIMARYFXSLOTID, + EAXCONTEXT_DISTANCEFACTOR, + EAXCONTEXT_AIRABSORPTIONHF, + EAXCONTEXT_HFREFERENCE, + EAXCONTEXT_LASTERROR, + + // EAX50 + EAXCONTEXT_SPEAKERCONFIG, + EAXCONTEXT_EAXSESSION, + EAXCONTEXT_MACROFXFACTOR, +}; // EAXCONTEXT_PROPERTY + +struct EAX40CONTEXTPROPERTIES +{ + GUID guidPrimaryFXSlotID; + float flDistanceFactor; + float flAirAbsorptionHF; + float flHFReference; +}; // EAX40CONTEXTPROPERTIES + +struct EAX50CONTEXTPROPERTIES : + public EAX40CONTEXTPROPERTIES +{ + float flMacroFXFactor; +}; // EAX40CONTEXTPROPERTIES + + +bool operator==( + const EAX40CONTEXTPROPERTIES& lhs, + const EAX40CONTEXTPROPERTIES& rhs) noexcept; + +bool operator==( + const EAX50CONTEXTPROPERTIES& lhs, + const EAX50CONTEXTPROPERTIES& rhs) noexcept; + + +constexpr auto EAXCONTEXT_MINDISTANCEFACTOR = FLT_MIN; +constexpr auto EAXCONTEXT_MAXDISTANCEFACTOR = FLT_MAX; +constexpr auto EAXCONTEXT_DEFAULTDISTANCEFACTOR = 1.0F; + +constexpr auto EAXCONTEXT_MINAIRABSORPTIONHF = -100.0F; +constexpr auto EAXCONTEXT_MAXAIRABSORPTIONHF = 0.0F; +constexpr auto EAXCONTEXT_DEFAULTAIRABSORPTIONHF = -5.0F; + +constexpr auto EAXCONTEXT_MINHFREFERENCE = 1000.0F; +constexpr auto EAXCONTEXT_MAXHFREFERENCE = 20000.0F; +constexpr auto EAXCONTEXT_DEFAULTHFREFERENCE = 5000.0F; + +constexpr auto EAXCONTEXT_MINMACROFXFACTOR = 0.0F; +constexpr auto EAXCONTEXT_MAXMACROFXFACTOR = 1.0F; +constexpr auto EAXCONTEXT_DEFAULTMACROFXFACTOR = 0.0F; + + +extern const GUID EAXPROPERTYID_EAX40_FXSlot0; + +extern const GUID EAXPROPERTYID_EAX50_FXSlot0; + +extern const GUID EAXPROPERTYID_EAX40_FXSlot1; + +extern const GUID EAXPROPERTYID_EAX50_FXSlot1; + +extern const GUID EAXPROPERTYID_EAX40_FXSlot2; + +extern const GUID EAXPROPERTYID_EAX50_FXSlot2; + +extern const GUID EAXPROPERTYID_EAX40_FXSlot3; + +extern const GUID EAXPROPERTYID_EAX50_FXSlot3; + +extern const GUID EAXCONTEXT_DEFAULTPRIMARYFXSLOTID; + +enum EAXFXSLOT_PROPERTY : + unsigned int +{ + EAXFXSLOT_PARAMETER = 0, + + EAXFXSLOT_NONE = 0x10000, + EAXFXSLOT_ALLPARAMETERS, + EAXFXSLOT_LOADEFFECT, + EAXFXSLOT_VOLUME, + EAXFXSLOT_LOCK, + EAXFXSLOT_FLAGS, + + // EAX50 + EAXFXSLOT_OCCLUSION, + EAXFXSLOT_OCCLUSIONLFRATIO, +}; // EAXFXSLOT_PROPERTY + +constexpr auto EAXFXSLOTFLAGS_ENVIRONMENT = 0x00000001UL; +// EAX50 +constexpr auto EAXFXSLOTFLAGS_UPMIX = 0x00000002UL; + +constexpr auto EAX40FXSLOTFLAGS_RESERVED = 0xFFFFFFFEUL; // reserved future use +constexpr auto EAX50FXSLOTFLAGS_RESERVED = 0xFFFFFFFCUL; // reserved future use + + +constexpr auto EAXFXSLOT_MINVOLUME = -10'000L; +constexpr auto EAXFXSLOT_MAXVOLUME = 0L; +constexpr auto EAXFXSLOT_DEFAULTVOLUME = 0L; + +constexpr auto EAXFXSLOT_MINLOCK = 0L; +constexpr auto EAXFXSLOT_MAXLOCK = 1L; + +enum : + long +{ + EAXFXSLOT_UNLOCKED = 0, + EAXFXSLOT_LOCKED = 1 +}; + +constexpr auto EAXFXSLOT_MINOCCLUSION = -10'000L; +constexpr auto EAXFXSLOT_MAXOCCLUSION = 0L; +constexpr auto EAXFXSLOT_DEFAULTOCCLUSION = 0L; + +constexpr auto EAXFXSLOT_MINOCCLUSIONLFRATIO = 0.0F; +constexpr auto EAXFXSLOT_MAXOCCLUSIONLFRATIO = 1.0F; +constexpr auto EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO = 0.25F; + +constexpr auto EAX40FXSLOT_DEFAULTFLAGS = EAXFXSLOTFLAGS_ENVIRONMENT; + +constexpr auto EAX50FXSLOT_DEFAULTFLAGS = + EAXFXSLOTFLAGS_ENVIRONMENT | + EAXFXSLOTFLAGS_UPMIX; // ignored for reverb; + +struct EAX40FXSLOTPROPERTIES +{ + GUID guidLoadEffect; + long lVolume; + long lLock; + unsigned long ulFlags; +}; // EAX40FXSLOTPROPERTIES + +struct EAX50FXSLOTPROPERTIES : + public EAX40FXSLOTPROPERTIES +{ + long lOcclusion; + float flOcclusionLFRatio; +}; // EAX50FXSLOTPROPERTIES + +bool operator==( + const EAX40FXSLOTPROPERTIES& lhs, + const EAX40FXSLOTPROPERTIES& rhs) noexcept; + +bool operator==( + const EAX50FXSLOTPROPERTIES& lhs, + const EAX50FXSLOTPROPERTIES& rhs) noexcept; + +extern const GUID EAXPROPERTYID_EAX40_Source; + +extern const GUID EAXPROPERTYID_EAX50_Source; + +// Source object properties +enum EAXSOURCE_PROPERTY : + unsigned int +{ + // EAX30 + + EAXSOURCE_NONE, + EAXSOURCE_ALLPARAMETERS, + EAXSOURCE_OBSTRUCTIONPARAMETERS, + EAXSOURCE_OCCLUSIONPARAMETERS, + EAXSOURCE_EXCLUSIONPARAMETERS, + EAXSOURCE_DIRECT, + EAXSOURCE_DIRECTHF, + EAXSOURCE_ROOM, + EAXSOURCE_ROOMHF, + EAXSOURCE_OBSTRUCTION, + EAXSOURCE_OBSTRUCTIONLFRATIO, + EAXSOURCE_OCCLUSION, + EAXSOURCE_OCCLUSIONLFRATIO, + EAXSOURCE_OCCLUSIONROOMRATIO, + EAXSOURCE_OCCLUSIONDIRECTRATIO, + EAXSOURCE_EXCLUSION, + EAXSOURCE_EXCLUSIONLFRATIO, + EAXSOURCE_OUTSIDEVOLUMEHF, + EAXSOURCE_DOPPLERFACTOR, + EAXSOURCE_ROLLOFFFACTOR, + EAXSOURCE_ROOMROLLOFFFACTOR, + EAXSOURCE_AIRABSORPTIONFACTOR, + EAXSOURCE_FLAGS, + + // EAX40 + + EAXSOURCE_SENDPARAMETERS, + EAXSOURCE_ALLSENDPARAMETERS, + EAXSOURCE_OCCLUSIONSENDPARAMETERS, + EAXSOURCE_EXCLUSIONSENDPARAMETERS, + EAXSOURCE_ACTIVEFXSLOTID, + + // EAX50 + + EAXSOURCE_MACROFXFACTOR, + EAXSOURCE_SPEAKERLEVELS, + EAXSOURCE_ALL2DPARAMETERS, +}; // EAXSOURCE_PROPERTY + + +constexpr auto EAXSOURCEFLAGS_DIRECTHFAUTO = 0x00000001UL; // relates to EAXSOURCE_DIRECTHF +constexpr auto EAXSOURCEFLAGS_ROOMAUTO = 0x00000002UL; // relates to EAXSOURCE_ROOM +constexpr auto EAXSOURCEFLAGS_ROOMHFAUTO = 0x00000004UL; // relates to EAXSOURCE_ROOMHF +// EAX50 +constexpr auto EAXSOURCEFLAGS_3DELEVATIONFILTER = 0x00000008UL; +// EAX50 +constexpr auto EAXSOURCEFLAGS_UPMIX = 0x00000010UL; +// EAX50 +constexpr auto EAXSOURCEFLAGS_APPLYSPEAKERLEVELS = 0x00000020UL; + +constexpr auto EAX20SOURCEFLAGS_RESERVED = 0xFFFFFFF8UL; // reserved future use +constexpr auto EAX50SOURCEFLAGS_RESERVED = 0xFFFFFFC0UL; // reserved future use + + +constexpr auto EAXSOURCE_MINSEND = -10'000L; +constexpr auto EAXSOURCE_MAXSEND = 0L; +constexpr auto EAXSOURCE_DEFAULTSEND = 0L; + +constexpr auto EAXSOURCE_MINSENDHF = -10'000L; +constexpr auto EAXSOURCE_MAXSENDHF = 0L; +constexpr auto EAXSOURCE_DEFAULTSENDHF = 0L; + +constexpr auto EAXSOURCE_MINDIRECT = -10'000L; +constexpr auto EAXSOURCE_MAXDIRECT = 1'000L; +constexpr auto EAXSOURCE_DEFAULTDIRECT = 0L; + +constexpr auto EAXSOURCE_MINDIRECTHF = -10'000L; +constexpr auto EAXSOURCE_MAXDIRECTHF = 0L; +constexpr auto EAXSOURCE_DEFAULTDIRECTHF = 0L; + +constexpr auto EAXSOURCE_MINROOM = -10'000L; +constexpr auto EAXSOURCE_MAXROOM = 1'000L; +constexpr auto EAXSOURCE_DEFAULTROOM = 0L; + +constexpr auto EAXSOURCE_MINROOMHF = -10'000L; +constexpr auto EAXSOURCE_MAXROOMHF = 0L; +constexpr auto EAXSOURCE_DEFAULTROOMHF = 0L; + +constexpr auto EAXSOURCE_MINOBSTRUCTION = -10'000L; +constexpr auto EAXSOURCE_MAXOBSTRUCTION = 0L; +constexpr auto EAXSOURCE_DEFAULTOBSTRUCTION = 0L; + +constexpr auto EAXSOURCE_MINOBSTRUCTIONLFRATIO = 0.0F; +constexpr auto EAXSOURCE_MAXOBSTRUCTIONLFRATIO = 1.0F; +constexpr auto EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO = 0.0F; + +constexpr auto EAXSOURCE_MINOCCLUSION = -10'000L; +constexpr auto EAXSOURCE_MAXOCCLUSION = 0L; +constexpr auto EAXSOURCE_DEFAULTOCCLUSION = 0L; + +constexpr auto EAXSOURCE_MINOCCLUSIONLFRATIO = 0.0F; +constexpr auto EAXSOURCE_MAXOCCLUSIONLFRATIO = 1.0F; +constexpr auto EAXSOURCE_DEFAULTOCCLUSIONLFRATIO = 0.25F; + +constexpr auto EAXSOURCE_MINOCCLUSIONROOMRATIO = 0.0F; +constexpr auto EAXSOURCE_MAXOCCLUSIONROOMRATIO = 10.0F; +constexpr auto EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO = 1.5F; + +constexpr auto EAXSOURCE_MINOCCLUSIONDIRECTRATIO = 0.0F; +constexpr auto EAXSOURCE_MAXOCCLUSIONDIRECTRATIO = 10.0F; +constexpr auto EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO = 1.0F; + +constexpr auto EAXSOURCE_MINEXCLUSION = -10'000L; +constexpr auto EAXSOURCE_MAXEXCLUSION = 0L; +constexpr auto EAXSOURCE_DEFAULTEXCLUSION = 0L; + +constexpr auto EAXSOURCE_MINEXCLUSIONLFRATIO = 0.0F; +constexpr auto EAXSOURCE_MAXEXCLUSIONLFRATIO = 1.0F; +constexpr auto EAXSOURCE_DEFAULTEXCLUSIONLFRATIO = 1.0F; + +constexpr auto EAXSOURCE_MINOUTSIDEVOLUMEHF = -10'000L; +constexpr auto EAXSOURCE_MAXOUTSIDEVOLUMEHF = 0L; +constexpr auto EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF = 0L; + +constexpr auto EAXSOURCE_MINDOPPLERFACTOR = 0.0F; +constexpr auto EAXSOURCE_MAXDOPPLERFACTOR = 10.0F; +constexpr auto EAXSOURCE_DEFAULTDOPPLERFACTOR = 1.0F; + +constexpr auto EAXSOURCE_MINROLLOFFFACTOR = 0.0F; +constexpr auto EAXSOURCE_MAXROLLOFFFACTOR = 10.0F; +constexpr auto EAXSOURCE_DEFAULTROLLOFFFACTOR = 0.0F; + +constexpr auto EAXSOURCE_MINROOMROLLOFFFACTOR = 0.0F; +constexpr auto EAXSOURCE_MAXROOMROLLOFFFACTOR = 10.0F; +constexpr auto EAXSOURCE_DEFAULTROOMROLLOFFFACTOR = 0.0F; + +constexpr auto EAXSOURCE_MINAIRABSORPTIONFACTOR = 0.0F; +constexpr auto EAXSOURCE_MAXAIRABSORPTIONFACTOR = 10.0F; +constexpr auto EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR = 0.0F; + +// EAX50 + +constexpr auto EAXSOURCE_MINMACROFXFACTOR = 0.0F; +constexpr auto EAXSOURCE_MAXMACROFXFACTOR = 1.0F; +constexpr auto EAXSOURCE_DEFAULTMACROFXFACTOR = 1.0F; + +// EAX50 + +constexpr auto EAXSOURCE_MINSPEAKERLEVEL = -10'000L; +constexpr auto EAXSOURCE_MAXSPEAKERLEVEL = 0L; +constexpr auto EAXSOURCE_DEFAULTSPEAKERLEVEL = -10'000L; + +constexpr auto EAXSOURCE_DEFAULTFLAGS = + EAXSOURCEFLAGS_DIRECTHFAUTO | + EAXSOURCEFLAGS_ROOMAUTO | + EAXSOURCEFLAGS_ROOMHFAUTO; + +enum : + long +{ + EAXSPEAKER_FRONT_LEFT = 1, + EAXSPEAKER_FRONT_CENTER = 2, + EAXSPEAKER_FRONT_RIGHT = 3, + EAXSPEAKER_SIDE_RIGHT = 4, + EAXSPEAKER_REAR_RIGHT = 5, + EAXSPEAKER_REAR_CENTER = 6, + EAXSPEAKER_REAR_LEFT = 7, + EAXSPEAKER_SIDE_LEFT = 8, + EAXSPEAKER_LOW_FREQUENCY = 9 +}; + +// EAXSOURCEFLAGS_DIRECTHFAUTO, EAXSOURCEFLAGS_ROOMAUTO and EAXSOURCEFLAGS_ROOMHFAUTO are ignored for 2D sources +// EAXSOURCEFLAGS_UPMIX is ignored for 3D sources +constexpr auto EAX50SOURCE_DEFAULTFLAGS = + EAXSOURCEFLAGS_DIRECTHFAUTO | + EAXSOURCEFLAGS_ROOMAUTO | + EAXSOURCEFLAGS_ROOMHFAUTO | + EAXSOURCEFLAGS_UPMIX; + +struct EAX30SOURCEPROPERTIES +{ + long lDirect; // direct path level (at low and mid frequencies) + long lDirectHF; // relative direct path level at high frequencies + long lRoom; // room effect level (at low and mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lObstruction; // main obstruction control (attenuation at high frequencies) + float flObstructionLFRatio; // obstruction low-frequency level re. main control + long lOcclusion; // main occlusion control (attenuation at high frequencies) + float flOcclusionLFRatio; // occlusion low-frequency level re. main control + float flOcclusionRoomRatio; // relative occlusion control for room effect + float flOcclusionDirectRatio; // relative occlusion control for direct path + long lExclusion; // main exlusion control (attenuation at high frequencies) + float flExclusionLFRatio; // exclusion low-frequency level re. main control + long lOutsideVolumeHF; // outside sound cone level at high frequencies + float flDopplerFactor; // like DS3D flDopplerFactor but per source + float flRolloffFactor; // like DS3D flRolloffFactor but per source + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + float flAirAbsorptionFactor; // multiplies EAXREVERB_AIRABSORPTIONHF + unsigned long ulFlags; // modifies the behavior of properties +}; // EAX30SOURCEPROPERTIES + +struct EAX50SOURCEPROPERTIES : + public EAX30SOURCEPROPERTIES +{ + float flMacroFXFactor; +}; // EAX50SOURCEPROPERTIES + +struct EAXSOURCEALLSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lSend; // send level (at low and mid frequencies) + long lSendHF; // relative send level at high frequencies + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; + long lExclusion; + float flExclusionLFRatio; +}; // EAXSOURCEALLSENDPROPERTIES + +struct EAXSOURCE2DPROPERTIES +{ + long lDirect; // direct path level (at low and mid frequencies) + long lDirectHF; // relative direct path level at high frequencies + long lRoom; // room effect level (at low and mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + unsigned long ulFlags; // modifies the behavior of properties +}; // EAXSOURCE2DPROPERTIES + +struct EAXSPEAKERLEVELPROPERTIES +{ + long lSpeakerID; + long lLevel; +}; // EAXSPEAKERLEVELPROPERTIES + +struct EAX40ACTIVEFXSLOTS +{ + GUID guidActiveFXSlots[EAX40_MAX_ACTIVE_FXSLOTS]; +}; // EAX40ACTIVEFXSLOTS + +struct EAX50ACTIVEFXSLOTS +{ + GUID guidActiveFXSlots[EAX50_MAX_ACTIVE_FXSLOTS]; +}; // EAX50ACTIVEFXSLOTS + +bool operator==( + const EAX50ACTIVEFXSLOTS& lhs, + const EAX50ACTIVEFXSLOTS& rhs) noexcept; + +bool operator!=( + const EAX50ACTIVEFXSLOTS& lhs, + const EAX50ACTIVEFXSLOTS& rhs) noexcept; + +// Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property. +struct EAXOBSTRUCTIONPROPERTIES +{ + long lObstruction; + float flObstructionLFRatio; +}; // EAXOBSTRUCTIONPROPERTIES + +// Use this structure for EAXSOURCE_OCCLUSIONPARAMETERS property. +struct EAXOCCLUSIONPROPERTIES +{ + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; +}; // EAXOCCLUSIONPROPERTIES + +// Use this structure for EAXSOURCE_EXCLUSIONPARAMETERS property. +struct EAXEXCLUSIONPROPERTIES +{ + long lExclusion; + float flExclusionLFRatio; +}; // EAXEXCLUSIONPROPERTIES + +// Use this structure for EAXSOURCE_SENDPARAMETERS properties. +struct EAXSOURCESENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lSend; + long lSendHF; +}; // EAXSOURCESENDPROPERTIES + +// Use this structure for EAXSOURCE_OCCLUSIONSENDPARAMETERS +struct EAXSOURCEOCCLUSIONSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; +}; // EAXSOURCEOCCLUSIONSENDPROPERTIES + +// Use this structure for EAXSOURCE_EXCLUSIONSENDPARAMETERS +struct EAXSOURCEEXCLUSIONSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lExclusion; + float flExclusionLFRatio; +}; // EAXSOURCEEXCLUSIONSENDPROPERTIES + +extern const EAX50ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID; + +extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; + +extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID; + + +// EAX Reverb Effect + +extern const GUID EAX_REVERB_EFFECT; + +// Reverb effect properties +enum EAXREVERB_PROPERTY : + unsigned int +{ + EAXREVERB_NONE, + EAXREVERB_ALLPARAMETERS, + EAXREVERB_ENVIRONMENT, + EAXREVERB_ENVIRONMENTSIZE, + EAXREVERB_ENVIRONMENTDIFFUSION, + EAXREVERB_ROOM, + EAXREVERB_ROOMHF, + EAXREVERB_ROOMLF, + EAXREVERB_DECAYTIME, + EAXREVERB_DECAYHFRATIO, + EAXREVERB_DECAYLFRATIO, + EAXREVERB_REFLECTIONS, + EAXREVERB_REFLECTIONSDELAY, + EAXREVERB_REFLECTIONSPAN, + EAXREVERB_REVERB, + EAXREVERB_REVERBDELAY, + EAXREVERB_REVERBPAN, + EAXREVERB_ECHOTIME, + EAXREVERB_ECHODEPTH, + EAXREVERB_MODULATIONTIME, + EAXREVERB_MODULATIONDEPTH, + EAXREVERB_AIRABSORPTIONHF, + EAXREVERB_HFREFERENCE, + EAXREVERB_LFREFERENCE, + EAXREVERB_ROOMROLLOFFFACTOR, + EAXREVERB_FLAGS, +}; // EAXREVERB_PROPERTY + +// used by EAXREVERB_ENVIRONMENT +enum : + unsigned long +{ + EAX_ENVIRONMENT_GENERIC, + EAX_ENVIRONMENT_PADDEDCELL, + EAX_ENVIRONMENT_ROOM, + EAX_ENVIRONMENT_BATHROOM, + EAX_ENVIRONMENT_LIVINGROOM, + EAX_ENVIRONMENT_STONEROOM, + EAX_ENVIRONMENT_AUDITORIUM, + EAX_ENVIRONMENT_CONCERTHALL, + EAX_ENVIRONMENT_CAVE, + EAX_ENVIRONMENT_ARENA, + EAX_ENVIRONMENT_HANGAR, + EAX_ENVIRONMENT_CARPETEDHALLWAY, + EAX_ENVIRONMENT_HALLWAY, + EAX_ENVIRONMENT_STONECORRIDOR, + EAX_ENVIRONMENT_ALLEY, + EAX_ENVIRONMENT_FOREST, + EAX_ENVIRONMENT_CITY, + EAX_ENVIRONMENT_MOUNTAINS, + EAX_ENVIRONMENT_QUARRY, + EAX_ENVIRONMENT_PLAIN, + EAX_ENVIRONMENT_PARKINGLOT, + EAX_ENVIRONMENT_SEWERPIPE, + EAX_ENVIRONMENT_UNDERWATER, + EAX_ENVIRONMENT_DRUGGED, + EAX_ENVIRONMENT_DIZZY, + EAX_ENVIRONMENT_PSYCHOTIC, + + EAX1_ENVIRONMENT_COUNT, + + // EAX30 + EAX_ENVIRONMENT_UNDEFINED = EAX1_ENVIRONMENT_COUNT, + + EAX3_ENVIRONMENT_COUNT, +}; + + +// reverberation decay time +constexpr auto EAXREVERBFLAGS_DECAYTIMESCALE = 0x00000001UL; + +// reflection level +constexpr auto EAXREVERBFLAGS_REFLECTIONSSCALE = 0x00000002UL; + +// initial reflection delay time +constexpr auto EAXREVERBFLAGS_REFLECTIONSDELAYSCALE = 0x00000004UL; + +// reflections level +constexpr auto EAXREVERBFLAGS_REVERBSCALE = 0x00000008UL; + +// late reverberation delay time +constexpr auto EAXREVERBFLAGS_REVERBDELAYSCALE = 0x00000010UL; + +// echo time +// EAX30+ +constexpr auto EAXREVERBFLAGS_ECHOTIMESCALE = 0x00000040UL; + +// modulation time +// EAX30+ +constexpr auto EAXREVERBFLAGS_MODULATIONTIMESCALE = 0x00000080UL; + +// This flag limits high-frequency decay time according to air absorption. +constexpr auto EAXREVERBFLAGS_DECAYHFLIMIT = 0x00000020UL; + +constexpr auto EAXREVERBFLAGS_RESERVED = 0xFFFFFF00UL; // reserved future use + + +struct EAXREVERBPROPERTIES +{ + unsigned long ulEnvironment; // sets all reverb properties + float flEnvironmentSize; // environment size in meters + float flEnvironmentDiffusion; // environment diffusion + long lRoom; // room effect level (at mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lRoomLF; // relative room effect level at low frequencies + float flDecayTime; // reverberation decay time at mid frequencies + float flDecayHFRatio; // high-frequency to mid-frequency decay time ratio + float flDecayLFRatio; // low-frequency to mid-frequency decay time ratio + long lReflections; // early reflections level relative to room effect + float flReflectionsDelay; // initial reflection delay time + EAXVECTOR vReflectionsPan; // early reflections panning vector + long lReverb; // late reverberation level relative to room effect + float flReverbDelay; // late reverberation delay time relative to initial reflection + EAXVECTOR vReverbPan; // late reverberation panning vector + float flEchoTime; // echo time + float flEchoDepth; // echo depth + float flModulationTime; // modulation time + float flModulationDepth; // modulation depth + float flAirAbsorptionHF; // change in level per meter at high frequencies + float flHFReference; // reference high frequency + float flLFReference; // reference low frequency + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + unsigned long ulFlags; // modifies the behavior of properties +}; // EAXREVERBPROPERTIES + +bool operator==( + const EAXREVERBPROPERTIES& lhs, + const EAXREVERBPROPERTIES& rhs) noexcept; + +bool operator!=( + const EAXREVERBPROPERTIES& lhs, + const EAXREVERBPROPERTIES& rhs) noexcept; + + +constexpr auto EAXREVERB_MINENVIRONMENT = static_cast(EAX_ENVIRONMENT_GENERIC); +constexpr auto EAX1REVERB_MAXENVIRONMENT = static_cast(EAX_ENVIRONMENT_PSYCHOTIC); +constexpr auto EAX30REVERB_MAXENVIRONMENT = static_cast(EAX_ENVIRONMENT_UNDEFINED); +constexpr auto EAXREVERB_DEFAULTENVIRONMENT = static_cast(EAX_ENVIRONMENT_GENERIC); + +constexpr auto EAXREVERB_MINENVIRONMENTSIZE = 1.0F; +constexpr auto EAXREVERB_MAXENVIRONMENTSIZE = 100.0F; +constexpr auto EAXREVERB_DEFAULTENVIRONMENTSIZE = 7.5F; + +constexpr auto EAXREVERB_MINENVIRONMENTDIFFUSION = 0.0F; +constexpr auto EAXREVERB_MAXENVIRONMENTDIFFUSION = 1.0F; +constexpr auto EAXREVERB_DEFAULTENVIRONMENTDIFFUSION = 1.0F; + +constexpr auto EAXREVERB_MINROOM = -10'000L; +constexpr auto EAXREVERB_MAXROOM = 0L; +constexpr auto EAXREVERB_DEFAULTROOM = -1'000L; + +constexpr auto EAXREVERB_MINROOMHF = -10'000L; +constexpr auto EAXREVERB_MAXROOMHF = 0L; +constexpr auto EAXREVERB_DEFAULTROOMHF = -100L; + +constexpr auto EAXREVERB_MINROOMLF = -10'000L; +constexpr auto EAXREVERB_MAXROOMLF = 0L; +constexpr auto EAXREVERB_DEFAULTROOMLF = 0L; + +constexpr auto EAXREVERB_MINDECAYTIME = 0.1F; +constexpr auto EAXREVERB_MAXDECAYTIME = 20.0F; +constexpr auto EAXREVERB_DEFAULTDECAYTIME = 1.49F; + +constexpr auto EAXREVERB_MINDECAYHFRATIO = 0.1F; +constexpr auto EAXREVERB_MAXDECAYHFRATIO = 2.0F; +constexpr auto EAXREVERB_DEFAULTDECAYHFRATIO = 0.83F; + +constexpr auto EAXREVERB_MINDECAYLFRATIO = 0.1F; +constexpr auto EAXREVERB_MAXDECAYLFRATIO = 2.0F; +constexpr auto EAXREVERB_DEFAULTDECAYLFRATIO = 1.0F; + +constexpr auto EAXREVERB_MINREFLECTIONS = -10'000L; +constexpr auto EAXREVERB_MAXREFLECTIONS = 1'000L; +constexpr auto EAXREVERB_DEFAULTREFLECTIONS = -2'602L; + +constexpr auto EAXREVERB_MINREFLECTIONSDELAY = 0.0F; +constexpr auto EAXREVERB_MAXREFLECTIONSDELAY = 0.3F; +constexpr auto EAXREVERB_DEFAULTREFLECTIONSDELAY = 0.007F; + +constexpr auto EAXREVERB_DEFAULTREFLECTIONSPAN = EAXVECTOR{0.0F, 0.0F, 0.0F}; + +constexpr auto EAXREVERB_MINREVERB = -10'000L; +constexpr auto EAXREVERB_MAXREVERB = 2'000L; +constexpr auto EAXREVERB_DEFAULTREVERB = 200L; + +constexpr auto EAXREVERB_MINREVERBDELAY = 0.0F; +constexpr auto EAXREVERB_MAXREVERBDELAY = 0.1F; +constexpr auto EAXREVERB_DEFAULTREVERBDELAY = 0.011F; + +constexpr auto EAXREVERB_DEFAULTREVERBPAN = EAXVECTOR{0.0F, 0.0F, 0.0F}; + +constexpr auto EAXREVERB_MINECHOTIME = 0.075F; +constexpr auto EAXREVERB_MAXECHOTIME = 0.25F; +constexpr auto EAXREVERB_DEFAULTECHOTIME = 0.25F; + +constexpr auto EAXREVERB_MINECHODEPTH = 0.0F; +constexpr auto EAXREVERB_MAXECHODEPTH = 1.0F; +constexpr auto EAXREVERB_DEFAULTECHODEPTH = 0.0F; + +constexpr auto EAXREVERB_MINMODULATIONTIME = 0.04F; +constexpr auto EAXREVERB_MAXMODULATIONTIME = 4.0F; +constexpr auto EAXREVERB_DEFAULTMODULATIONTIME = 0.25F; + +constexpr auto EAXREVERB_MINMODULATIONDEPTH = 0.0F; +constexpr auto EAXREVERB_MAXMODULATIONDEPTH = 1.0F; +constexpr auto EAXREVERB_DEFAULTMODULATIONDEPTH = 0.0F; + +constexpr auto EAXREVERB_MINAIRABSORPTIONHF = -100.0F; +constexpr auto EAXREVERB_MAXAIRABSORPTIONHF = 0.0F; +constexpr auto EAXREVERB_DEFAULTAIRABSORPTIONHF = -5.0F; + +constexpr auto EAXREVERB_MINHFREFERENCE = 1'000.0F; +constexpr auto EAXREVERB_MAXHFREFERENCE = 20'000.0F; +constexpr auto EAXREVERB_DEFAULTHFREFERENCE = 5'000.0F; + +constexpr auto EAXREVERB_MINLFREFERENCE = 20.0F; +constexpr auto EAXREVERB_MAXLFREFERENCE = 1'000.0F; +constexpr auto EAXREVERB_DEFAULTLFREFERENCE = 250.0F; + +constexpr auto EAXREVERB_MINROOMROLLOFFFACTOR = 0.0F; +constexpr auto EAXREVERB_MAXROOMROLLOFFFACTOR = 10.0F; +constexpr auto EAXREVERB_DEFAULTROOMROLLOFFFACTOR = 0.0F; + +constexpr auto EAX1REVERB_MINVOLUME = 0.0F; +constexpr auto EAX1REVERB_MAXVOLUME = 1.0F; + +constexpr auto EAX1REVERB_MINDAMPING = 0.0F; +constexpr auto EAX1REVERB_MAXDAMPING = 2.0F; + +constexpr auto EAXREVERB_DEFAULTFLAGS = + EAXREVERBFLAGS_DECAYTIMESCALE | + EAXREVERBFLAGS_REFLECTIONSSCALE | + EAXREVERBFLAGS_REFLECTIONSDELAYSCALE | + EAXREVERBFLAGS_REVERBSCALE | + EAXREVERBFLAGS_REVERBDELAYSCALE | + EAXREVERBFLAGS_DECAYHFLIMIT; + + +using EaxReverbPresets = std::array; +extern const EaxReverbPresets EAXREVERB_PRESETS; + + +using Eax1ReverbPresets = std::array; +extern const Eax1ReverbPresets EAX1REVERB_PRESETS; + + +// AGC Compressor Effect + +extern const GUID EAX_AGCCOMPRESSOR_EFFECT; + +enum EAXAGCCOMPRESSOR_PROPERTY : + unsigned int +{ + EAXAGCCOMPRESSOR_NONE, + EAXAGCCOMPRESSOR_ALLPARAMETERS, + EAXAGCCOMPRESSOR_ONOFF, +}; // EAXAGCCOMPRESSOR_PROPERTY + +struct EAXAGCCOMPRESSORPROPERTIES +{ + unsigned long ulOnOff; // Switch Compressor on or off +}; // EAXAGCCOMPRESSORPROPERTIES + + +constexpr auto EAXAGCCOMPRESSOR_MINONOFF = 0UL; +constexpr auto EAXAGCCOMPRESSOR_MAXONOFF = 1UL; +constexpr auto EAXAGCCOMPRESSOR_DEFAULTONOFF = EAXAGCCOMPRESSOR_MAXONOFF; + + +// Autowah Effect + +extern const GUID EAX_AUTOWAH_EFFECT; + +enum EAXAUTOWAH_PROPERTY : + unsigned int +{ + EAXAUTOWAH_NONE, + EAXAUTOWAH_ALLPARAMETERS, + EAXAUTOWAH_ATTACKTIME, + EAXAUTOWAH_RELEASETIME, + EAXAUTOWAH_RESONANCE, + EAXAUTOWAH_PEAKLEVEL, +}; // EAXAUTOWAH_PROPERTY + +struct EAXAUTOWAHPROPERTIES +{ + float flAttackTime; // Attack time (seconds) + float flReleaseTime; // Release time (seconds) + long lResonance; // Resonance (mB) + long lPeakLevel; // Peak level (mB) +}; // EAXAUTOWAHPROPERTIES + + +constexpr auto EAXAUTOWAH_MINATTACKTIME = 0.0001F; +constexpr auto EAXAUTOWAH_MAXATTACKTIME = 1.0F; +constexpr auto EAXAUTOWAH_DEFAULTATTACKTIME = 0.06F; + +constexpr auto EAXAUTOWAH_MINRELEASETIME = 0.0001F; +constexpr auto EAXAUTOWAH_MAXRELEASETIME = 1.0F; +constexpr auto EAXAUTOWAH_DEFAULTRELEASETIME = 0.06F; + +constexpr auto EAXAUTOWAH_MINRESONANCE = 600L; +constexpr auto EAXAUTOWAH_MAXRESONANCE = 6000L; +constexpr auto EAXAUTOWAH_DEFAULTRESONANCE = 6000L; + +constexpr auto EAXAUTOWAH_MINPEAKLEVEL = -9000L; +constexpr auto EAXAUTOWAH_MAXPEAKLEVEL = 9000L; +constexpr auto EAXAUTOWAH_DEFAULTPEAKLEVEL = 2100L; + + +// Chorus Effect + +extern const GUID EAX_CHORUS_EFFECT; + + +enum EAXCHORUS_PROPERTY : + unsigned int +{ + EAXCHORUS_NONE, + EAXCHORUS_ALLPARAMETERS, + EAXCHORUS_WAVEFORM, + EAXCHORUS_PHASE, + EAXCHORUS_RATE, + EAXCHORUS_DEPTH, + EAXCHORUS_FEEDBACK, + EAXCHORUS_DELAY, +}; // EAXCHORUS_PROPERTY + +enum : + unsigned long +{ + EAX_CHORUS_SINUSOID, + EAX_CHORUS_TRIANGLE, +}; + +struct EAXCHORUSPROPERTIES +{ + unsigned long ulWaveform; // Waveform selector - see enum above + long lPhase; // Phase (Degrees) + float flRate; // Rate (Hz) + float flDepth; // Depth (0 to 1) + float flFeedback; // Feedback (-1 to 1) + float flDelay; // Delay (seconds) +}; // EAXCHORUSPROPERTIES + + +constexpr auto EAXCHORUS_MINWAVEFORM = 0UL; +constexpr auto EAXCHORUS_MAXWAVEFORM = 1UL; +constexpr auto EAXCHORUS_DEFAULTWAVEFORM = 1UL; + +constexpr auto EAXCHORUS_MINPHASE = -180L; +constexpr auto EAXCHORUS_MAXPHASE = 180L; +constexpr auto EAXCHORUS_DEFAULTPHASE = 90L; + +constexpr auto EAXCHORUS_MINRATE = 0.0F; +constexpr auto EAXCHORUS_MAXRATE = 10.0F; +constexpr auto EAXCHORUS_DEFAULTRATE = 1.1F; + +constexpr auto EAXCHORUS_MINDEPTH = 0.0F; +constexpr auto EAXCHORUS_MAXDEPTH = 1.0F; +constexpr auto EAXCHORUS_DEFAULTDEPTH = 0.1F; + +constexpr auto EAXCHORUS_MINFEEDBACK = -1.0F; +constexpr auto EAXCHORUS_MAXFEEDBACK = 1.0F; +constexpr auto EAXCHORUS_DEFAULTFEEDBACK = 0.25F; + +constexpr auto EAXCHORUS_MINDELAY = 0.0002F; +constexpr auto EAXCHORUS_MAXDELAY = 0.016F; +constexpr auto EAXCHORUS_DEFAULTDELAY = 0.016F; + + +// Distortion Effect + +extern const GUID EAX_DISTORTION_EFFECT; + +enum EAXDISTORTION_PROPERTY : + unsigned int +{ + EAXDISTORTION_NONE, + EAXDISTORTION_ALLPARAMETERS, + EAXDISTORTION_EDGE, + EAXDISTORTION_GAIN, + EAXDISTORTION_LOWPASSCUTOFF, + EAXDISTORTION_EQCENTER, + EAXDISTORTION_EQBANDWIDTH, +}; // EAXDISTORTION_PROPERTY + + +struct EAXDISTORTIONPROPERTIES +{ + float flEdge; // Controls the shape of the distortion (0 to 1) + long lGain; // Controls the post distortion gain (mB) + float flLowPassCutOff; // Controls the cut-off of the filter pre-distortion (Hz) + float flEQCenter; // Controls the center frequency of the EQ post-distortion (Hz) + float flEQBandwidth; // Controls the bandwidth of the EQ post-distortion (Hz) +}; // EAXDISTORTIONPROPERTIES + + +constexpr auto EAXDISTORTION_MINEDGE = 0.0F; +constexpr auto EAXDISTORTION_MAXEDGE = 1.0F; +constexpr auto EAXDISTORTION_DEFAULTEDGE = 0.2F; + +constexpr auto EAXDISTORTION_MINGAIN = -6000L; +constexpr auto EAXDISTORTION_MAXGAIN = 0L; +constexpr auto EAXDISTORTION_DEFAULTGAIN = -2600L; + +constexpr auto EAXDISTORTION_MINLOWPASSCUTOFF = 80.0F; +constexpr auto EAXDISTORTION_MAXLOWPASSCUTOFF = 24000.0F; +constexpr auto EAXDISTORTION_DEFAULTLOWPASSCUTOFF = 8000.0F; + +constexpr auto EAXDISTORTION_MINEQCENTER = 80.0F; +constexpr auto EAXDISTORTION_MAXEQCENTER = 24000.0F; +constexpr auto EAXDISTORTION_DEFAULTEQCENTER = 3600.0F; + +constexpr auto EAXDISTORTION_MINEQBANDWIDTH = 80.0F; +constexpr auto EAXDISTORTION_MAXEQBANDWIDTH = 24000.0F; +constexpr auto EAXDISTORTION_DEFAULTEQBANDWIDTH = 3600.0F; + + +// Echo Effect + +extern const GUID EAX_ECHO_EFFECT; + + +enum EAXECHO_PROPERTY : + unsigned int +{ + EAXECHO_NONE, + EAXECHO_ALLPARAMETERS, + EAXECHO_DELAY, + EAXECHO_LRDELAY, + EAXECHO_DAMPING, + EAXECHO_FEEDBACK, + EAXECHO_SPREAD, +}; // EAXECHO_PROPERTY + + +struct EAXECHOPROPERTIES +{ + float flDelay; // Controls the initial delay time (seconds) + float flLRDelay; // Controls the delay time between the first and second taps (seconds) + float flDamping; // Controls a low-pass filter that dampens the echoes (0 to 1) + float flFeedback; // Controls the duration of echo repetition (0 to 1) + float flSpread; // Controls the left-right spread of the echoes +}; // EAXECHOPROPERTIES + + +constexpr auto EAXECHO_MINDAMPING = 0.0F; +constexpr auto EAXECHO_MAXDAMPING = 0.99F; +constexpr auto EAXECHO_DEFAULTDAMPING = 0.5F; + +constexpr auto EAXECHO_MINDELAY = 0.002F; +constexpr auto EAXECHO_MAXDELAY = 0.207F; +constexpr auto EAXECHO_DEFAULTDELAY = 0.1F; + +constexpr auto EAXECHO_MINLRDELAY = 0.0F; +constexpr auto EAXECHO_MAXLRDELAY = 0.404F; +constexpr auto EAXECHO_DEFAULTLRDELAY = 0.1F; + +constexpr auto EAXECHO_MINFEEDBACK = 0.0F; +constexpr auto EAXECHO_MAXFEEDBACK = 1.0F; +constexpr auto EAXECHO_DEFAULTFEEDBACK = 0.5F; + +constexpr auto EAXECHO_MINSPREAD = -1.0F; +constexpr auto EAXECHO_MAXSPREAD = 1.0F; +constexpr auto EAXECHO_DEFAULTSPREAD = -1.0F; + + +// Equalizer Effect + +extern const GUID EAX_EQUALIZER_EFFECT; + + +enum EAXEQUALIZER_PROPERTY : + unsigned int +{ + EAXEQUALIZER_NONE, + EAXEQUALIZER_ALLPARAMETERS, + EAXEQUALIZER_LOWGAIN, + EAXEQUALIZER_LOWCUTOFF, + EAXEQUALIZER_MID1GAIN, + EAXEQUALIZER_MID1CENTER, + EAXEQUALIZER_MID1WIDTH, + EAXEQUALIZER_MID2GAIN, + EAXEQUALIZER_MID2CENTER, + EAXEQUALIZER_MID2WIDTH, + EAXEQUALIZER_HIGHGAIN, + EAXEQUALIZER_HIGHCUTOFF, +}; // EAXEQUALIZER_PROPERTY + + +struct EAXEQUALIZERPROPERTIES +{ + long lLowGain; // (mB) + float flLowCutOff; // (Hz) + long lMid1Gain; // (mB) + float flMid1Center; // (Hz) + float flMid1Width; // (octaves) + long lMid2Gain; // (mB) + float flMid2Center; // (Hz) + float flMid2Width; // (octaves) + long lHighGain; // (mB) + float flHighCutOff; // (Hz) +}; // EAXEQUALIZERPROPERTIES + + +constexpr auto EAXEQUALIZER_MINLOWGAIN = -1800L; +constexpr auto EAXEQUALIZER_MAXLOWGAIN = 1800L; +constexpr auto EAXEQUALIZER_DEFAULTLOWGAIN = 0L; + +constexpr auto EAXEQUALIZER_MINLOWCUTOFF = 50.0F; +constexpr auto EAXEQUALIZER_MAXLOWCUTOFF = 800.0F; +constexpr auto EAXEQUALIZER_DEFAULTLOWCUTOFF = 200.0F; + +constexpr auto EAXEQUALIZER_MINMID1GAIN = -1800L; +constexpr auto EAXEQUALIZER_MAXMID1GAIN = 1800L; +constexpr auto EAXEQUALIZER_DEFAULTMID1GAIN = 0L; + +constexpr auto EAXEQUALIZER_MINMID1CENTER = 200.0F; +constexpr auto EAXEQUALIZER_MAXMID1CENTER = 3000.0F; +constexpr auto EAXEQUALIZER_DEFAULTMID1CENTER = 500.0F; + +constexpr auto EAXEQUALIZER_MINMID1WIDTH = 0.01F; +constexpr auto EAXEQUALIZER_MAXMID1WIDTH = 1.0F; +constexpr auto EAXEQUALIZER_DEFAULTMID1WIDTH = 1.0F; + +constexpr auto EAXEQUALIZER_MINMID2GAIN = -1800L; +constexpr auto EAXEQUALIZER_MAXMID2GAIN = 1800L; +constexpr auto EAXEQUALIZER_DEFAULTMID2GAIN = 0L; + +constexpr auto EAXEQUALIZER_MINMID2CENTER = 1000.0F; +constexpr auto EAXEQUALIZER_MAXMID2CENTER = 8000.0F; +constexpr auto EAXEQUALIZER_DEFAULTMID2CENTER = 3000.0F; + +constexpr auto EAXEQUALIZER_MINMID2WIDTH = 0.01F; +constexpr auto EAXEQUALIZER_MAXMID2WIDTH = 1.0F; +constexpr auto EAXEQUALIZER_DEFAULTMID2WIDTH = 1.0F; + +constexpr auto EAXEQUALIZER_MINHIGHGAIN = -1800L; +constexpr auto EAXEQUALIZER_MAXHIGHGAIN = 1800L; +constexpr auto EAXEQUALIZER_DEFAULTHIGHGAIN = 0L; + +constexpr auto EAXEQUALIZER_MINHIGHCUTOFF = 4000.0F; +constexpr auto EAXEQUALIZER_MAXHIGHCUTOFF = 16000.0F; +constexpr auto EAXEQUALIZER_DEFAULTHIGHCUTOFF = 6000.0F; + + +// Flanger Effect + +extern const GUID EAX_FLANGER_EFFECT; + +enum EAXFLANGER_PROPERTY : + unsigned int +{ + EAXFLANGER_NONE, + EAXFLANGER_ALLPARAMETERS, + EAXFLANGER_WAVEFORM, + EAXFLANGER_PHASE, + EAXFLANGER_RATE, + EAXFLANGER_DEPTH, + EAXFLANGER_FEEDBACK, + EAXFLANGER_DELAY, +}; // EAXFLANGER_PROPERTY + +enum : + unsigned long +{ + EAX_FLANGER_SINUSOID, + EAX_FLANGER_TRIANGLE, +}; + +struct EAXFLANGERPROPERTIES +{ + unsigned long ulWaveform; // Waveform selector - see enum above + long lPhase; // Phase (Degrees) + float flRate; // Rate (Hz) + float flDepth; // Depth (0 to 1) + float flFeedback; // Feedback (0 to 1) + float flDelay; // Delay (seconds) +}; // EAXFLANGERPROPERTIES + + +constexpr auto EAXFLANGER_MINWAVEFORM = 0UL; +constexpr auto EAXFLANGER_MAXWAVEFORM = 1UL; +constexpr auto EAXFLANGER_DEFAULTWAVEFORM = 1UL; + +constexpr auto EAXFLANGER_MINPHASE = -180L; +constexpr auto EAXFLANGER_MAXPHASE = 180L; +constexpr auto EAXFLANGER_DEFAULTPHASE = 0L; + +constexpr auto EAXFLANGER_MINRATE = 0.0F; +constexpr auto EAXFLANGER_MAXRATE = 10.0F; +constexpr auto EAXFLANGER_DEFAULTRATE = 0.27F; + +constexpr auto EAXFLANGER_MINDEPTH = 0.0F; +constexpr auto EAXFLANGER_MAXDEPTH = 1.0F; +constexpr auto EAXFLANGER_DEFAULTDEPTH = 1.0F; + +constexpr auto EAXFLANGER_MINFEEDBACK = -1.0F; +constexpr auto EAXFLANGER_MAXFEEDBACK = 1.0F; +constexpr auto EAXFLANGER_DEFAULTFEEDBACK = -0.5F; + +constexpr auto EAXFLANGER_MINDELAY = 0.0002F; +constexpr auto EAXFLANGER_MAXDELAY = 0.004F; +constexpr auto EAXFLANGER_DEFAULTDELAY = 0.002F; + + +// Frequency Shifter Effect + +extern const GUID EAX_FREQUENCYSHIFTER_EFFECT; + +enum EAXFREQUENCYSHIFTER_PROPERTY : + unsigned int +{ + EAXFREQUENCYSHIFTER_NONE, + EAXFREQUENCYSHIFTER_ALLPARAMETERS, + EAXFREQUENCYSHIFTER_FREQUENCY, + EAXFREQUENCYSHIFTER_LEFTDIRECTION, + EAXFREQUENCYSHIFTER_RIGHTDIRECTION, +}; // EAXFREQUENCYSHIFTER_PROPERTY + +enum : + unsigned long +{ + EAX_FREQUENCYSHIFTER_DOWN, + EAX_FREQUENCYSHIFTER_UP, + EAX_FREQUENCYSHIFTER_OFF +}; + +struct EAXFREQUENCYSHIFTERPROPERTIES +{ + float flFrequency; // (Hz) + unsigned long ulLeftDirection; // see enum above + unsigned long ulRightDirection; // see enum above +}; // EAXFREQUENCYSHIFTERPROPERTIES + + +constexpr auto EAXFREQUENCYSHIFTER_MINFREQUENCY = 0.0F; +constexpr auto EAXFREQUENCYSHIFTER_MAXFREQUENCY = 24000.0F; +constexpr auto EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY = EAXFREQUENCYSHIFTER_MINFREQUENCY; + +constexpr auto EAXFREQUENCYSHIFTER_MINLEFTDIRECTION = 0UL; +constexpr auto EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION = 2UL; +constexpr auto EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION = EAXFREQUENCYSHIFTER_MINLEFTDIRECTION; + +constexpr auto EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION = 0UL; +constexpr auto EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION = 2UL; +constexpr auto EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION = EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION; + + +// Vocal Morpher Effect + +extern const GUID EAX_VOCALMORPHER_EFFECT; + +enum EAXVOCALMORPHER_PROPERTY : + unsigned int +{ + EAXVOCALMORPHER_NONE, + EAXVOCALMORPHER_ALLPARAMETERS, + EAXVOCALMORPHER_PHONEMEA, + EAXVOCALMORPHER_PHONEMEACOARSETUNING, + EAXVOCALMORPHER_PHONEMEB, + EAXVOCALMORPHER_PHONEMEBCOARSETUNING, + EAXVOCALMORPHER_WAVEFORM, + EAXVOCALMORPHER_RATE, +}; // EAXVOCALMORPHER_PROPERTY + +enum : + unsigned long +{ + A, + E, + I, + O, + U, + AA, + AE, + AH, + AO, + EH, + ER, + IH, + IY, + UH, + UW, + B, + D, + F, + G, + J, + K, + L, + M, + N, + P, + R, + S, + T, + V, + Z, +}; + +enum : + unsigned long +{ + EAX_VOCALMORPHER_SINUSOID, + EAX_VOCALMORPHER_TRIANGLE, + EAX_VOCALMORPHER_SAWTOOTH +}; + +// Use this structure for EAXVOCALMORPHER_ALLPARAMETERS +struct EAXVOCALMORPHERPROPERTIES +{ + unsigned long ulPhonemeA; // see enum above + long lPhonemeACoarseTuning; // (semitones) + unsigned long ulPhonemeB; // see enum above + long lPhonemeBCoarseTuning; // (semitones) + unsigned long ulWaveform; // Waveform selector - see enum above + float flRate; // (Hz) +}; // EAXVOCALMORPHERPROPERTIES + + +constexpr auto EAXVOCALMORPHER_MINPHONEMEA = 0UL; +constexpr auto EAXVOCALMORPHER_MAXPHONEMEA = 29UL; +constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEA = EAXVOCALMORPHER_MINPHONEMEA; + +constexpr auto EAXVOCALMORPHER_MINPHONEMEACOARSETUNING = -24L; +constexpr auto EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING = 24L; +constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING = 0L; + +constexpr auto EAXVOCALMORPHER_MINPHONEMEB = 0UL; +constexpr auto EAXVOCALMORPHER_MAXPHONEMEB = 29UL; +constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEB = 10UL; + +constexpr auto EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING = -24L; +constexpr auto EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING = 24L; +constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING = 0L; + +constexpr auto EAXVOCALMORPHER_MINWAVEFORM = 0UL; +constexpr auto EAXVOCALMORPHER_MAXWAVEFORM = 2UL; +constexpr auto EAXVOCALMORPHER_DEFAULTWAVEFORM = EAXVOCALMORPHER_MINWAVEFORM; + +constexpr auto EAXVOCALMORPHER_MINRATE = 0.0F; +constexpr auto EAXVOCALMORPHER_MAXRATE = 10.0F; +constexpr auto EAXVOCALMORPHER_DEFAULTRATE = 1.41F; + + +// Pitch Shifter Effect + +extern const GUID EAX_PITCHSHIFTER_EFFECT; + +enum EAXPITCHSHIFTER_PROPERTY : + unsigned int +{ + EAXPITCHSHIFTER_NONE, + EAXPITCHSHIFTER_ALLPARAMETERS, + EAXPITCHSHIFTER_COARSETUNE, + EAXPITCHSHIFTER_FINETUNE, +}; // EAXPITCHSHIFTER_PROPERTY + +struct EAXPITCHSHIFTERPROPERTIES +{ + long lCoarseTune; // Amount of pitch shift (semitones) + long lFineTune; // Amount of pitch shift (cents) +}; // EAXPITCHSHIFTERPROPERTIES + + +constexpr auto EAXPITCHSHIFTER_MINCOARSETUNE = -12L; +constexpr auto EAXPITCHSHIFTER_MAXCOARSETUNE = 12L; +constexpr auto EAXPITCHSHIFTER_DEFAULTCOARSETUNE = 12L; + +constexpr auto EAXPITCHSHIFTER_MINFINETUNE = -50L; +constexpr auto EAXPITCHSHIFTER_MAXFINETUNE = 50L; +constexpr auto EAXPITCHSHIFTER_DEFAULTFINETUNE = 0L; + + +// Ring Modulator Effect + +extern const GUID EAX_RINGMODULATOR_EFFECT; + +enum EAXRINGMODULATOR_PROPERTY : + unsigned int +{ + EAXRINGMODULATOR_NONE, + EAXRINGMODULATOR_ALLPARAMETERS, + EAXRINGMODULATOR_FREQUENCY, + EAXRINGMODULATOR_HIGHPASSCUTOFF, + EAXRINGMODULATOR_WAVEFORM, +}; // EAXRINGMODULATOR_PROPERTY + +enum : + unsigned long +{ + EAX_RINGMODULATOR_SINUSOID, + EAX_RINGMODULATOR_SAWTOOTH, + EAX_RINGMODULATOR_SQUARE, +}; + +// Use this structure for EAXRINGMODULATOR_ALLPARAMETERS +struct EAXRINGMODULATORPROPERTIES +{ + float flFrequency; // Frequency of modulation (Hz) + float flHighPassCutOff; // Cut-off frequency of high-pass filter (Hz) + unsigned long ulWaveform; // Waveform selector - see enum above +}; // EAXRINGMODULATORPROPERTIES + + +constexpr auto EAXRINGMODULATOR_MINFREQUENCY = 0.0F; +constexpr auto EAXRINGMODULATOR_MAXFREQUENCY = 8000.0F; +constexpr auto EAXRINGMODULATOR_DEFAULTFREQUENCY = 440.0F; + +constexpr auto EAXRINGMODULATOR_MINHIGHPASSCUTOFF = 0.0F; +constexpr auto EAXRINGMODULATOR_MAXHIGHPASSCUTOFF = 24000.0F; +constexpr auto EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF = 800.0F; + +constexpr auto EAXRINGMODULATOR_MINWAVEFORM = 0UL; +constexpr auto EAXRINGMODULATOR_MAXWAVEFORM = 2UL; +constexpr auto EAXRINGMODULATOR_DEFAULTWAVEFORM = EAXRINGMODULATOR_MINWAVEFORM; + + +using LPEAXSET = ALenum(AL_APIENTRY*)( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size); + +using LPEAXGET = ALenum(AL_APIENTRY*)( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size); + + +#endif // !EAX_API_INCLUDED diff --git a/Engine/lib/openal-soft/al/eax_eax_call.cpp b/Engine/lib/openal-soft/al/eax_eax_call.cpp new file mode 100644 index 000000000..914d2fbfa --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_eax_call.cpp @@ -0,0 +1,324 @@ +#include "config.h" + +#include "al/eax_eax_call.h" + +#include "al/eax_exception.h" + + +namespace { + +constexpr auto deferred_flag = 0x80000000U; + +class EaxEaxCallException : + public EaxException +{ +public: + explicit EaxEaxCallException( + const char* message) + : + EaxException{"EAX_EAX_CALL", message} + { + } +}; // EaxEaxCallException + +} // namespace + + +EaxEaxCall::EaxEaxCall( + bool is_get, + const GUID& property_set_guid, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size) + : is_get_{is_get}, version_{0}, property_set_id_{EaxEaxCallPropertySetId::none} + , property_id_{property_id & ~deferred_flag}, property_source_id_{property_source_id} + , property_buffer_{property_buffer}, property_size_{property_size} +{ + if (false) + { + } + else if (property_set_guid == EAXPROPERTYID_EAX40_Context) + { + version_ = 4; + property_set_id_ = EaxEaxCallPropertySetId::context; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_Context) + { + version_ = 5; + property_set_id_ = EaxEaxCallPropertySetId::context; + } + else if (property_set_guid == DSPROPSETID_EAX20_ListenerProperties) + { + version_ = 2; + fx_slot_index_ = 0u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect; + property_id_ = convert_eax_v2_0_listener_property_id(property_id_); + } + else if (property_set_guid == DSPROPSETID_EAX30_ListenerProperties) + { + version_ = 3; + fx_slot_index_ = 0u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot0) + { + version_ = 4; + fx_slot_index_ = 0u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot0) + { + version_ = 5; + fx_slot_index_ = 0u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot1) + { + version_ = 4; + fx_slot_index_ = 1u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot1) + { + version_ = 5; + fx_slot_index_ = 1u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot2) + { + version_ = 4; + fx_slot_index_ = 2u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot2) + { + version_ = 5; + fx_slot_index_ = 2u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot3) + { + version_ = 4; + fx_slot_index_ = 3u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot3) + { + version_ = 5; + fx_slot_index_ = 3u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == DSPROPSETID_EAX20_BufferProperties) + { + version_ = 2; + property_set_id_ = EaxEaxCallPropertySetId::source; + property_id_ = convert_eax_v2_0_buffer_property_id(property_id_); + } + else if (property_set_guid == DSPROPSETID_EAX30_BufferProperties) + { + version_ = 3; + property_set_id_ = EaxEaxCallPropertySetId::source; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_Source) + { + version_ = 4; + property_set_id_ = EaxEaxCallPropertySetId::source; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_Source) + { + version_ = 5; + property_set_id_ = EaxEaxCallPropertySetId::source; + } + else if (property_set_guid == DSPROPSETID_EAX_ReverbProperties) + { + version_ = 1; + fx_slot_index_ = 0u; + property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect; + } + else if (property_set_guid == DSPROPSETID_EAXBUFFER_ReverbProperties) + { + version_ = 1; + property_set_id_ = EaxEaxCallPropertySetId::source; + } + else + { + fail("Unsupported property set id."); + } + + if (version_ < 1 || version_ > 5) + { + fail("EAX version out of range."); + } + + if(!(property_id&deferred_flag)) + { + if(property_set_id_ != EaxEaxCallPropertySetId::fx_slot && property_id_ != 0) + { + if (!property_buffer) + { + fail("Null property buffer."); + } + + if (property_size == 0) + { + fail("Empty property."); + } + } + } + + if(property_set_id_ == EaxEaxCallPropertySetId::source && property_source_id_ == 0) + { + fail("Null AL source id."); + } + + if (property_set_id_ == EaxEaxCallPropertySetId::fx_slot) + { + if (property_id_ < EAXFXSLOT_NONE) + { + property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect; + } + } +} + +[[noreturn]] +void EaxEaxCall::fail( + const char* message) +{ + throw EaxEaxCallException{message}; +} + +ALuint EaxEaxCall::convert_eax_v2_0_listener_property_id( + ALuint property_id) +{ + switch (property_id) + { + case DSPROPERTY_EAX20LISTENER_NONE: + return EAXREVERB_NONE; + + case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS: + return EAXREVERB_ALLPARAMETERS; + + case DSPROPERTY_EAX20LISTENER_ROOM: + return EAXREVERB_ROOM; + + case DSPROPERTY_EAX20LISTENER_ROOMHF: + return EAXREVERB_ROOMHF; + + case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: + return EAXREVERB_ROOMROLLOFFFACTOR; + + case DSPROPERTY_EAX20LISTENER_DECAYTIME: + return EAXREVERB_DECAYTIME; + + case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: + return EAXREVERB_DECAYHFRATIO; + + case DSPROPERTY_EAX20LISTENER_REFLECTIONS: + return EAXREVERB_REFLECTIONS; + + case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: + return EAXREVERB_REFLECTIONSDELAY; + + case DSPROPERTY_EAX20LISTENER_REVERB: + return EAXREVERB_REVERB; + + case DSPROPERTY_EAX20LISTENER_REVERBDELAY: + return EAXREVERB_REVERBDELAY; + + case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: + return EAXREVERB_ENVIRONMENT; + + case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: + return EAXREVERB_ENVIRONMENTSIZE; + + case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: + return EAXREVERB_ENVIRONMENTDIFFUSION; + + case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: + return EAXREVERB_AIRABSORPTIONHF; + + case DSPROPERTY_EAX20LISTENER_FLAGS: + return EAXREVERB_FLAGS; + + default: + fail("Unsupported EAX 2.0 listener property id."); + } +} + +ALuint EaxEaxCall::convert_eax_v2_0_buffer_property_id( + ALuint property_id) +{ + switch (property_id) + { + case DSPROPERTY_EAX20BUFFER_NONE: + return EAXSOURCE_NONE; + + case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS: + return EAXSOURCE_ALLPARAMETERS; + + case DSPROPERTY_EAX20BUFFER_DIRECT: + return EAXSOURCE_DIRECT; + + case DSPROPERTY_EAX20BUFFER_DIRECTHF: + return EAXSOURCE_DIRECTHF; + + case DSPROPERTY_EAX20BUFFER_ROOM: + return EAXSOURCE_ROOM; + + case DSPROPERTY_EAX20BUFFER_ROOMHF: + return EAXSOURCE_ROOMHF; + + case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR: + return EAXSOURCE_ROOMROLLOFFFACTOR; + + case DSPROPERTY_EAX20BUFFER_OBSTRUCTION: + return EAXSOURCE_OBSTRUCTION; + + case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO: + return EAXSOURCE_OBSTRUCTIONLFRATIO; + + case DSPROPERTY_EAX20BUFFER_OCCLUSION: + return EAXSOURCE_OCCLUSION; + + case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO: + return EAXSOURCE_OCCLUSIONLFRATIO; + + case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO: + return EAXSOURCE_OCCLUSIONROOMRATIO; + + case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF: + return EAXSOURCE_OUTSIDEVOLUMEHF; + + case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR: + return EAXSOURCE_AIRABSORPTIONFACTOR; + + case DSPROPERTY_EAX20BUFFER_FLAGS: + return EAXSOURCE_FLAGS; + + default: + fail("Unsupported EAX 2.0 buffer property id."); + } +} + + +EaxEaxCall create_eax_call( + bool is_get, + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size) +{ + if(!property_set_id) + throw EaxEaxCallException{"Null property set ID."}; + + return EaxEaxCall{ + is_get, + *property_set_id, + property_id, + property_source_id, + property_buffer, + property_size + }; +} diff --git a/Engine/lib/openal-soft/al/eax_eax_call.h b/Engine/lib/openal-soft/al/eax_eax_call.h new file mode 100644 index 000000000..7b990d872 --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_eax_call.h @@ -0,0 +1,117 @@ +#ifndef EAX_EAX_CALL_INCLUDED +#define EAX_EAX_CALL_INCLUDED + + +#include "AL/al.h" + +#include "alspan.h" + +#include "eax_api.h" +#include "eax_fx_slot_index.h" + + +enum class EaxEaxCallPropertySetId +{ + none, + + context, + fx_slot, + source, + fx_slot_effect, +}; // EaxEaxCallPropertySetId + + +class EaxEaxCall +{ +public: + EaxEaxCall( + bool is_get, + const GUID& property_set_guid, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size); + + bool is_get() const noexcept { return is_get_; } + int get_version() const noexcept { return version_; } + EaxEaxCallPropertySetId get_property_set_id() const noexcept { return property_set_id_; } + ALuint get_property_id() const noexcept { return property_id_; } + ALuint get_property_al_name() const noexcept { return property_source_id_; } + EaxFxSlotIndex get_fx_slot_index() const noexcept { return fx_slot_index_; } + + template< + typename TException, + typename TValue + > + TValue& get_value() const + { + if (property_size_ < static_cast(sizeof(TValue))) + { + throw TException{"Property buffer too small."}; + } + + return *static_cast(property_buffer_); + } + + template< + typename TException, + typename TValue + > + al::span get_values() const + { + if (property_size_ < static_cast(sizeof(TValue))) + { + throw TException{"Property buffer too small."}; + } + + const auto count = property_size_ / sizeof(TValue); + + return al::span{static_cast(property_buffer_), count}; + } + + template< + typename TException, + typename TValue + > + void set_value( + const TValue& value) const + { + get_value() = value; + } + + +private: + const bool is_get_; + int version_; + EaxFxSlotIndex fx_slot_index_; + EaxEaxCallPropertySetId property_set_id_; + + ALuint property_id_; + const ALuint property_source_id_; + ALvoid*const property_buffer_; + const ALuint property_size_; + + + [[noreturn]] + static void fail( + const char* message); + + + static ALuint convert_eax_v2_0_listener_property_id( + ALuint property_id); + + static ALuint convert_eax_v2_0_buffer_property_id( + ALuint property_id); +}; // EaxEaxCall + + +EaxEaxCall create_eax_call( + bool is_get, + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size); + + +#endif // !EAX_EAX_CALL_INCLUDED diff --git a/Engine/lib/openal-soft/al/eax_effect.cpp b/Engine/lib/openal-soft/al/eax_effect.cpp new file mode 100644 index 000000000..9cbf4c13b --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_effect.cpp @@ -0,0 +1,3 @@ +#include "config.h" + +#include "eax_effect.h" diff --git a/Engine/lib/openal-soft/al/eax_effect.h b/Engine/lib/openal-soft/al/eax_effect.h new file mode 100644 index 000000000..45315ca6b --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_effect.h @@ -0,0 +1,44 @@ +#ifndef EAX_EFFECT_INCLUDED +#define EAX_EFFECT_INCLUDED + + +#include + +#include "AL/al.h" +#include "core/effects/base.h" +#include "eax_eax_call.h" + +class EaxEffect +{ +public: + EaxEffect(ALenum type) : al_effect_type_{type} { } + virtual ~EaxEffect() = default; + + const ALenum al_effect_type_; + EffectProps al_effect_props_{}; + + virtual void dispatch(const EaxEaxCall& eax_call) = 0; + + // Returns "true" if any immediated property was changed. + // [[nodiscard]] + virtual bool apply_deferred() = 0; +}; // EaxEffect + + +using EaxEffectUPtr = std::unique_ptr; + +EaxEffectUPtr eax_create_eax_null_effect(); +EaxEffectUPtr eax_create_eax_chorus_effect(); +EaxEffectUPtr eax_create_eax_distortion_effect(); +EaxEffectUPtr eax_create_eax_echo_effect(); +EaxEffectUPtr eax_create_eax_flanger_effect(); +EaxEffectUPtr eax_create_eax_frequency_shifter_effect(); +EaxEffectUPtr eax_create_eax_vocal_morpher_effect(); +EaxEffectUPtr eax_create_eax_pitch_shifter_effect(); +EaxEffectUPtr eax_create_eax_ring_modulator_effect(); +EaxEffectUPtr eax_create_eax_auto_wah_effect(); +EaxEffectUPtr eax_create_eax_compressor_effect(); +EaxEffectUPtr eax_create_eax_equalizer_effect(); +EaxEffectUPtr eax_create_eax_reverb_effect(); + +#endif // !EAX_EFFECT_INCLUDED diff --git a/Engine/lib/openal-soft/al/eax_exception.cpp b/Engine/lib/openal-soft/al/eax_exception.cpp new file mode 100644 index 000000000..fdeecaaa8 --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_exception.cpp @@ -0,0 +1,63 @@ +#include "config.h" + +#include "eax_exception.h" + +#include + +#include + + +EaxException::EaxException( + const char* context, + const char* message) + : + std::runtime_error{make_message(context, message)} +{ +} + +std::string EaxException::make_message( + const char* context, + const char* message) +{ + const auto context_size = (context ? std::string::traits_type::length(context) : 0); + const auto has_contex = (context_size > 0); + + const auto message_size = (message ? std::string::traits_type::length(message) : 0); + const auto has_message = (message_size > 0); + + if (!has_contex && !has_message) + { + return std::string{}; + } + + static constexpr char left_prefix[] = "["; + const auto left_prefix_size = std::string::traits_type::length(left_prefix); + + static constexpr char right_prefix[] = "] "; + const auto right_prefix_size = std::string::traits_type::length(right_prefix); + + const auto what_size = + ( + has_contex ? + left_prefix_size + context_size + right_prefix_size : + 0) + + message_size + + 1; + + auto what = std::string{}; + what.reserve(what_size); + + if (has_contex) + { + what.append(left_prefix, left_prefix_size); + what.append(context, context_size); + what.append(right_prefix, right_prefix_size); + } + + if (has_message) + { + what.append(message, message_size); + } + + return what; +} diff --git a/Engine/lib/openal-soft/al/eax_exception.h b/Engine/lib/openal-soft/al/eax_exception.h new file mode 100644 index 000000000..9a7acf715 --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_exception.h @@ -0,0 +1,25 @@ +#ifndef EAX_EXCEPTION_INCLUDED +#define EAX_EXCEPTION_INCLUDED + + +#include +#include + + +class EaxException : + public std::runtime_error +{ +public: + EaxException( + const char* context, + const char* message); + + +private: + static std::string make_message( + const char* context, + const char* message); +}; // EaxException + + +#endif // !EAX_EXCEPTION_INCLUDED diff --git a/Engine/lib/openal-soft/al/eax_fx_slot_index.cpp b/Engine/lib/openal-soft/al/eax_fx_slot_index.cpp new file mode 100644 index 000000000..9aa695ada --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_fx_slot_index.cpp @@ -0,0 +1,71 @@ +#include "config.h" + +#include "eax_fx_slot_index.h" + +#include "eax_exception.h" + + +namespace +{ + + +class EaxFxSlotIndexException : + public EaxException +{ +public: + explicit EaxFxSlotIndexException( + const char* message) + : + EaxException{"EAX_FX_SLOT_INDEX", message} + { + } +}; // EaxFxSlotIndexException + + +} // namespace + + +void EaxFxSlotIndex::set(EaxFxSlotIndexValue index) +{ + if(index >= EaxFxSlotIndexValue{EAX_MAX_FXSLOTS}) + fail("Index out of range."); + + emplace(index); +} + +void EaxFxSlotIndex::set(const GUID &guid) +{ + if (false) + { + } + else if (guid == EAX_NULL_GUID) + { + reset(); + } + else if (guid == EAXPROPERTYID_EAX40_FXSlot0 || guid == EAXPROPERTYID_EAX50_FXSlot0) + { + emplace(0u); + } + else if (guid == EAXPROPERTYID_EAX40_FXSlot1 || guid == EAXPROPERTYID_EAX50_FXSlot1) + { + emplace(1u); + } + else if (guid == EAXPROPERTYID_EAX40_FXSlot2 || guid == EAXPROPERTYID_EAX50_FXSlot2) + { + emplace(2u); + } + else if (guid == EAXPROPERTYID_EAX40_FXSlot3 || guid == EAXPROPERTYID_EAX50_FXSlot3) + { + emplace(3u); + } + else + { + fail("Unsupported GUID."); + } +} + +[[noreturn]] +void EaxFxSlotIndex::fail(const char* message) +{ + throw EaxFxSlotIndexException{message}; +} diff --git a/Engine/lib/openal-soft/al/eax_fx_slot_index.h b/Engine/lib/openal-soft/al/eax_fx_slot_index.h new file mode 100644 index 000000000..e1e5475a9 --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_fx_slot_index.h @@ -0,0 +1,41 @@ +#ifndef EAX_FX_SLOT_INDEX_INCLUDED +#define EAX_FX_SLOT_INDEX_INCLUDED + + +#include + +#include "aloptional.h" +#include "eax_api.h" + + +using EaxFxSlotIndexValue = std::size_t; + +class EaxFxSlotIndex : public al::optional +{ +public: + using al::optional::optional; + + EaxFxSlotIndex& operator=(const EaxFxSlotIndexValue &value) { set(value); return *this; } + EaxFxSlotIndex& operator=(const GUID &guid) { set(guid); return *this; } + + void set(EaxFxSlotIndexValue index); + void set(const GUID& guid); + +private: + [[noreturn]] + static void fail(const char *message); +}; // EaxFxSlotIndex + +inline bool operator==(const EaxFxSlotIndex& lhs, const EaxFxSlotIndex& rhs) noexcept +{ + if(lhs.has_value() != rhs.has_value()) + return false; + if(lhs.has_value()) + return *lhs == *rhs; + return true; +} + +inline bool operator!=(const EaxFxSlotIndex& lhs, const EaxFxSlotIndex& rhs) noexcept +{ return !(lhs == rhs); } + +#endif // !EAX_FX_SLOT_INDEX_INCLUDED diff --git a/Engine/lib/openal-soft/al/eax_fx_slots.cpp b/Engine/lib/openal-soft/al/eax_fx_slots.cpp new file mode 100644 index 000000000..3a27dabd6 --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_fx_slots.cpp @@ -0,0 +1,84 @@ +#include "config.h" + +#include "eax_fx_slots.h" + +#include + +#include "eax_exception.h" + +#include "eax_api.h" + + +namespace +{ + + +class EaxFxSlotsException : + public EaxException +{ +public: + explicit EaxFxSlotsException( + const char* message) + : + EaxException{"EAX_FX_SLOTS", message} + { + } +}; // EaxFxSlotsException + + +} // namespace + + +void EaxFxSlots::initialize( + ALCcontext& al_context) +{ + initialize_fx_slots(al_context); +} + +void EaxFxSlots::uninitialize() noexcept +{ + for (auto& fx_slot : fx_slots_) + { + fx_slot = nullptr; + } +} + +const ALeffectslot& EaxFxSlots::get(EaxFxSlotIndex index) const +{ + if(!index.has_value()) + fail("Empty index."); + return *fx_slots_[index.value()]; +} + +ALeffectslot& EaxFxSlots::get(EaxFxSlotIndex index) +{ + if(!index.has_value()) + fail("Empty index."); + return *fx_slots_[index.value()]; +} + +void EaxFxSlots::unlock_legacy() noexcept +{ + fx_slots_[0]->eax_unlock_legacy(); + fx_slots_[1]->eax_unlock_legacy(); +} + +[[noreturn]] +void EaxFxSlots::fail( + const char* message) +{ + throw EaxFxSlotsException{message}; +} + +void EaxFxSlots::initialize_fx_slots( + ALCcontext& al_context) +{ + auto fx_slot_index = EaxFxSlotIndexValue{}; + + for (auto& fx_slot : fx_slots_) + { + fx_slot = eax_create_al_effect_slot(al_context); + fx_slot->eax_initialize(al_context, fx_slot_index); + fx_slot_index += 1; + } +} diff --git a/Engine/lib/openal-soft/al/eax_fx_slots.h b/Engine/lib/openal-soft/al/eax_fx_slots.h new file mode 100644 index 000000000..a104c6ab6 --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_fx_slots.h @@ -0,0 +1,54 @@ +#ifndef EAX_FX_SLOTS_INCLUDED +#define EAX_FX_SLOTS_INCLUDED + + +#include + +#include "al/auxeffectslot.h" + +#include "eax_api.h" + +#include "eax_fx_slot_index.h" + + +class EaxFxSlots +{ +public: + void initialize( + ALCcontext& al_context); + + void uninitialize() noexcept; + + void commit() + { + for(auto& fx_slot : fx_slots_) + fx_slot->eax_commit(); + } + + + const ALeffectslot& get( + EaxFxSlotIndex index) const; + + ALeffectslot& get( + EaxFxSlotIndex index); + + void unlock_legacy() noexcept; + + +private: + using Items = std::array; + + + Items fx_slots_{}; + + + [[noreturn]] + static void fail( + const char* message); + + void initialize_fx_slots( + ALCcontext& al_context); +}; // EaxFxSlots + + +#endif // !EAX_FX_SLOTS_INCLUDED diff --git a/Engine/lib/openal-soft/al/eax_globals.cpp b/Engine/lib/openal-soft/al/eax_globals.cpp new file mode 100644 index 000000000..07909459e --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_globals.cpp @@ -0,0 +1,21 @@ +#include "config.h" + +#include "eax_globals.h" + + +bool eax_g_is_enabled = true; + + +const char eax1_ext_name[] = "EAX"; +const char eax2_ext_name[] = "EAX2.0"; +const char eax3_ext_name[] = "EAX3.0"; +const char eax4_ext_name[] = "EAX4.0"; +const char eax5_ext_name[] = "EAX5.0"; + +const char eax_x_ram_ext_name[] = "EAX-RAM"; + +const char eax_eax_set_func_name[] = "EAXSet"; +const char eax_eax_get_func_name[] = "EAXGet"; + +const char eax_eax_set_buffer_mode_func_name[] = "EAXSetBufferMode"; +const char eax_eax_get_buffer_mode_func_name[] = "EAXGetBufferMode"; diff --git a/Engine/lib/openal-soft/al/eax_globals.h b/Engine/lib/openal-soft/al/eax_globals.h new file mode 100644 index 000000000..1b4d63b85 --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_globals.h @@ -0,0 +1,22 @@ +#ifndef EAX_GLOBALS_INCLUDED +#define EAX_GLOBALS_INCLUDED + + +extern bool eax_g_is_enabled; + + +extern const char eax1_ext_name[]; +extern const char eax2_ext_name[]; +extern const char eax3_ext_name[]; +extern const char eax4_ext_name[]; +extern const char eax5_ext_name[]; + +extern const char eax_x_ram_ext_name[]; + +extern const char eax_eax_set_func_name[]; +extern const char eax_eax_get_func_name[]; + +extern const char eax_eax_set_buffer_mode_func_name[]; +extern const char eax_eax_get_buffer_mode_func_name[]; + +#endif // !EAX_GLOBALS_INCLUDED diff --git a/Engine/lib/openal-soft/al/eax_utils.cpp b/Engine/lib/openal-soft/al/eax_utils.cpp new file mode 100644 index 000000000..67389de42 --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_utils.cpp @@ -0,0 +1,36 @@ +#include "config.h" + +#include "eax_utils.h" + +#include +#include + +#include "core/logging.h" + + +void eax_log_exception( + const char* message) noexcept +{ + const auto exception_ptr = std::current_exception(); + + assert(exception_ptr); + + if (message) + { + ERR("%s\n", message); + } + + try + { + std::rethrow_exception(exception_ptr); + } + catch (const std::exception& ex) + { + const auto ex_message = ex.what(); + ERR("%s\n", ex_message); + } + catch (...) + { + ERR("%s\n", "Generic exception."); + } +} diff --git a/Engine/lib/openal-soft/al/eax_utils.h b/Engine/lib/openal-soft/al/eax_utils.h new file mode 100644 index 000000000..d3d4a1960 --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_utils.h @@ -0,0 +1,132 @@ +#ifndef EAX_UTILS_INCLUDED +#define EAX_UTILS_INCLUDED + +#include +#include +#include +#include + + +struct EaxAlLowPassParam +{ + float gain; + float gain_hf; +}; // EaxAlLowPassParam + + +void eax_log_exception( + const char* message = nullptr) noexcept; + + +template< + typename TException, + typename TValue +> +void eax_validate_range( + const char* value_name, + const TValue& value, + const TValue& min_value, + const TValue& max_value) +{ + if (value >= min_value && value <= max_value) + { + return; + } + + const auto message = + std::string{value_name} + + " out of range (value: " + + std::to_string(value) + "; min: " + + std::to_string(min_value) + "; max: " + + std::to_string(max_value) + ")."; + + throw TException{message.c_str()}; +} + + +namespace detail +{ + + +template< + typename T +> +struct EaxIsBitFieldStruct +{ +private: + using yes = std::true_type; + using no = std::false_type; + + template< + typename U + > + static auto test(int) -> decltype(std::declval(), yes{}); + + template< + typename + > + static no test(...); + + +public: + static constexpr auto value = std::is_same(0)), yes>::value; +}; // EaxIsBitFieldStruct + + +template< + typename T, + typename TValue +> +inline bool eax_bit_fields_are_equal( + const T& lhs, + const T& rhs) noexcept +{ + static_assert(sizeof(T) == sizeof(TValue), "Invalid type size."); + + return reinterpret_cast(lhs) == reinterpret_cast(rhs); +} + + +} // namespace detail + + +template< + typename T, + std::enable_if_t::value, int> = 0 +> +inline bool operator==( + const T& lhs, + const T& rhs) noexcept +{ + using Value = std::conditional_t< + sizeof(T) == 1, + std::uint8_t, + std::conditional_t< + sizeof(T) == 2, + std::uint16_t, + std::conditional_t< + sizeof(T) == 4, + std::uint32_t, + void + > + > + >; + + static_assert(!std::is_same::value, "Unsupported type."); + + return detail::eax_bit_fields_are_equal(lhs, rhs); +} + +template< + typename T, + std::enable_if_t::value, int> = 0 +> +inline bool operator!=( + const T& lhs, + const T& rhs) noexcept +{ + return !(lhs == rhs); +} + + +#endif // !EAX_UTILS_INCLUDED diff --git a/Engine/lib/openal-soft/al/eax_x_ram.cpp b/Engine/lib/openal-soft/al/eax_x_ram.cpp new file mode 100644 index 000000000..ac3e7ebb7 --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_x_ram.cpp @@ -0,0 +1,3 @@ +#include "config.h" + +#include "eax_x_ram.h" diff --git a/Engine/lib/openal-soft/al/eax_x_ram.h b/Engine/lib/openal-soft/al/eax_x_ram.h new file mode 100644 index 000000000..438b99163 --- /dev/null +++ b/Engine/lib/openal-soft/al/eax_x_ram.h @@ -0,0 +1,38 @@ +#ifndef EAX_X_RAM_INCLUDED +#define EAX_X_RAM_INCLUDED + + +#include "AL/al.h" + + +constexpr auto eax_x_ram_min_size = ALsizei{}; +constexpr auto eax_x_ram_max_size = ALsizei{64 * 1'024 * 1'024}; + + +constexpr auto AL_EAX_RAM_SIZE = ALenum{0x202201}; +constexpr auto AL_EAX_RAM_FREE = ALenum{0x202202}; + +constexpr auto AL_STORAGE_AUTOMATIC = ALenum{0x202203}; +constexpr auto AL_STORAGE_HARDWARE = ALenum{0x202204}; +constexpr auto AL_STORAGE_ACCESSIBLE = ALenum{0x202205}; + + +constexpr auto AL_EAX_RAM_SIZE_NAME = "AL_EAX_RAM_SIZE"; +constexpr auto AL_EAX_RAM_FREE_NAME = "AL_EAX_RAM_FREE"; + +constexpr auto AL_STORAGE_AUTOMATIC_NAME = "AL_STORAGE_AUTOMATIC"; +constexpr auto AL_STORAGE_HARDWARE_NAME = "AL_STORAGE_HARDWARE"; +constexpr auto AL_STORAGE_ACCESSIBLE_NAME = "AL_STORAGE_ACCESSIBLE"; + + +ALboolean AL_APIENTRY EAXSetBufferMode( + ALsizei n, + const ALuint* buffers, + ALint value); + +ALenum AL_APIENTRY EAXGetBufferMode( + ALuint buffer, + ALint* pReserved); + + +#endif // !EAX_X_RAM_INCLUDED diff --git a/Engine/lib/openal-soft/al/effect.cpp b/Engine/lib/openal-soft/al/effect.cpp index 93aa55472..5a74ca534 100644 --- a/Engine/lib/openal-soft/al/effect.cpp +++ b/Engine/lib/openal-soft/al/effect.cpp @@ -39,17 +39,23 @@ #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" +#ifdef ALSOFT_EAX +#include + +#include "eax_exception.h" +#endif // ALSOFT_EAX const EffectList gEffectList[16]{ { "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB }, @@ -181,12 +187,12 @@ ALeffect *AllocEffect(ALCdevice *device) { auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(), [](const EffectSubList &entry) noexcept -> bool - { return entry.FreeMask != 0; } - ); + { return entry.FreeMask != 0; }); auto lidx = static_cast(std::distance(device->EffectList.begin(), sublist)); auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); + ASSUME(slidx < 64); - ALeffect *effect{::new (sublist->Effects + slidx) ALeffect{}}; + ALeffect *effect{al::construct_at(sublist->Effects + slidx)}; InitEffectParams(effect, AL_EFFECT_NULL); /* Add 1 to avoid effect ID 0. */ @@ -233,7 +239,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 +279,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 +310,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 +325,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 +375,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 +398,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 +421,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 +444,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 +476,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 +499,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 +522,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)}; @@ -744,3 +750,16 @@ void LoadReverbPreset(const char *name, ALeffect *effect) WARN("Reverb preset '%s' not found\n", name); } + +bool IsValidEffectType(ALenum type) noexcept +{ + if(type == AL_EFFECT_NULL) + return true; + + for(const auto &effect_item : gEffectList) + { + if(type == effect_item.val && !DisabledEffects[effect_item.type]) + return true; + } + return false; +} diff --git a/Engine/lib/openal-soft/al/effect.h b/Engine/lib/openal-soft/al/effect.h index 10a692c9d..a1d433133 100644 --- a/Engine/lib/openal-soft/al/effect.h +++ b/Engine/lib/openal-soft/al/effect.h @@ -57,4 +57,6 @@ void InitEffect(ALeffect *effect); void LoadReverbPreset(const char *name, ALeffect *effect); +bool IsValidEffectType(ALenum type) noexcept; + #endif diff --git a/Engine/lib/openal-soft/al/effects/autowah.cpp b/Engine/lib/openal-soft/al/effects/autowah.cpp index 65d4b702c..273ec7ae7 100644 --- a/Engine/lib/openal-soft/al/effects/autowah.cpp +++ b/Engine/lib/openal-soft/al/effects/autowah.cpp @@ -8,9 +8,17 @@ #include "AL/efx.h" -#include "effects/base.h" +#include "alc/effects/base.h" #include "effects.h" +#ifdef ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + + namespace { void Autowah_setParamf(EffectProps *props, ALenum param, float val) @@ -107,3 +115,434 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Autowah); const EffectProps AutowahEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxAutoWahEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxAutoWahEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxAutoWahEffectDirtyFlagsValue flAttackTime : 1; + EaxAutoWahEffectDirtyFlagsValue flReleaseTime : 1; + EaxAutoWahEffectDirtyFlagsValue lResonance : 1; + EaxAutoWahEffectDirtyFlagsValue lPeakLevel : 1; +}; // EaxAutoWahEffectDirtyFlags + + +class EaxAutoWahEffect final : + public EaxEffect +{ +public: + EaxAutoWahEffect(); + + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXAUTOWAHPROPERTIES eax_{}; + EAXAUTOWAHPROPERTIES eax_d_{}; + EaxAutoWahEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + + void set_efx_attack_time(); + + void set_efx_release_time(); + + void set_efx_resonance(); + + void set_efx_peak_gain(); + + void set_efx_defaults(); + + + void get(const EaxEaxCall& eax_call); + + + void validate_attack_time( + float flAttackTime); + + void validate_release_time( + float flReleaseTime); + + void validate_resonance( + long lResonance); + + void validate_peak_level( + long lPeakLevel); + + void validate_all( + const EAXAUTOWAHPROPERTIES& eax_all); + + + void defer_attack_time( + float flAttackTime); + + void defer_release_time( + float flReleaseTime); + + void defer_resonance( + long lResonance); + + void defer_peak_level( + long lPeakLevel); + + void defer_all( + const EAXAUTOWAHPROPERTIES& eax_all); + + + void defer_attack_time( + const EaxEaxCall& eax_call); + + void defer_release_time( + const EaxEaxCall& eax_call); + + void defer_resonance( + const EaxEaxCall& eax_call); + + void defer_peak_level( + const EaxEaxCall& eax_call); + + void defer_all( + const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxAutoWahEffect + + +class EaxAutoWahEffectException : + public EaxException +{ +public: + explicit EaxAutoWahEffectException( + const char* message) + : + EaxException{"EAX_AUTO_WAH_EFFECT", message} + { + } +}; // EaxAutoWahEffectException + + +EaxAutoWahEffect::EaxAutoWahEffect() + : EaxEffect{AL_EFFECT_AUTOWAH} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxAutoWahEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxAutoWahEffect::set_eax_defaults() +{ + eax_.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME; + eax_.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME; + eax_.lResonance = EAXAUTOWAH_DEFAULTRESONANCE; + eax_.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL; + + eax_d_ = eax_; +} + +void EaxAutoWahEffect::set_efx_attack_time() +{ + const auto attack_time = clamp( + eax_.flAttackTime, + AL_AUTOWAH_MIN_ATTACK_TIME, + AL_AUTOWAH_MAX_ATTACK_TIME); + + al_effect_props_.Autowah.AttackTime = attack_time; +} + +void EaxAutoWahEffect::set_efx_release_time() +{ + const auto release_time = clamp( + eax_.flReleaseTime, + AL_AUTOWAH_MIN_RELEASE_TIME, + AL_AUTOWAH_MAX_RELEASE_TIME); + + al_effect_props_.Autowah.ReleaseTime = release_time; +} + +void EaxAutoWahEffect::set_efx_resonance() +{ + const auto resonance = clamp( + level_mb_to_gain(static_cast(eax_.lResonance)), + AL_AUTOWAH_MIN_RESONANCE, + AL_AUTOWAH_MAX_RESONANCE); + + al_effect_props_.Autowah.Resonance = resonance; +} + +void EaxAutoWahEffect::set_efx_peak_gain() +{ + const auto peak_gain = clamp( + level_mb_to_gain(static_cast(eax_.lPeakLevel)), + AL_AUTOWAH_MIN_PEAK_GAIN, + AL_AUTOWAH_MAX_PEAK_GAIN); + + al_effect_props_.Autowah.PeakGain = peak_gain; +} + +void EaxAutoWahEffect::set_efx_defaults() +{ + set_efx_attack_time(); + set_efx_release_time(); + set_efx_resonance(); + set_efx_peak_gain(); +} + +void EaxAutoWahEffect::get(const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXAUTOWAH_NONE: + break; + + case EAXAUTOWAH_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXAUTOWAH_ATTACKTIME: + eax_call.set_value(eax_.flAttackTime); + break; + + case EAXAUTOWAH_RELEASETIME: + eax_call.set_value(eax_.flReleaseTime); + break; + + case EAXAUTOWAH_RESONANCE: + eax_call.set_value(eax_.lResonance); + break; + + case EAXAUTOWAH_PEAKLEVEL: + eax_call.set_value(eax_.lPeakLevel); + break; + + default: + throw EaxAutoWahEffectException{"Unsupported property id."}; + } +} + +void EaxAutoWahEffect::validate_attack_time( + float flAttackTime) +{ + eax_validate_range( + "Attack Time", + flAttackTime, + EAXAUTOWAH_MINATTACKTIME, + EAXAUTOWAH_MAXATTACKTIME); +} + +void EaxAutoWahEffect::validate_release_time( + float flReleaseTime) +{ + eax_validate_range( + "Release Time", + flReleaseTime, + EAXAUTOWAH_MINRELEASETIME, + EAXAUTOWAH_MAXRELEASETIME); +} + +void EaxAutoWahEffect::validate_resonance( + long lResonance) +{ + eax_validate_range( + "Resonance", + lResonance, + EAXAUTOWAH_MINRESONANCE, + EAXAUTOWAH_MAXRESONANCE); +} + +void EaxAutoWahEffect::validate_peak_level( + long lPeakLevel) +{ + eax_validate_range( + "Peak Level", + lPeakLevel, + EAXAUTOWAH_MINPEAKLEVEL, + EAXAUTOWAH_MAXPEAKLEVEL); +} + +void EaxAutoWahEffect::validate_all( + const EAXAUTOWAHPROPERTIES& eax_all) +{ + validate_attack_time(eax_all.flAttackTime); + validate_release_time(eax_all.flReleaseTime); + validate_resonance(eax_all.lResonance); + validate_peak_level(eax_all.lPeakLevel); +} + +void EaxAutoWahEffect::defer_attack_time( + float flAttackTime) +{ + eax_d_.flAttackTime = flAttackTime; + eax_dirty_flags_.flAttackTime = (eax_.flAttackTime != eax_d_.flAttackTime); +} + +void EaxAutoWahEffect::defer_release_time( + float flReleaseTime) +{ + eax_d_.flReleaseTime = flReleaseTime; + eax_dirty_flags_.flReleaseTime = (eax_.flReleaseTime != eax_d_.flReleaseTime); +} + +void EaxAutoWahEffect::defer_resonance( + long lResonance) +{ + eax_d_.lResonance = lResonance; + eax_dirty_flags_.lResonance = (eax_.lResonance != eax_d_.lResonance); +} + +void EaxAutoWahEffect::defer_peak_level( + long lPeakLevel) +{ + eax_d_.lPeakLevel = lPeakLevel; + eax_dirty_flags_.lPeakLevel = (eax_.lPeakLevel != eax_d_.lPeakLevel); +} + +void EaxAutoWahEffect::defer_all( + const EAXAUTOWAHPROPERTIES& eax_all) +{ + validate_all(eax_all); + + defer_attack_time(eax_all.flAttackTime); + defer_release_time(eax_all.flReleaseTime); + defer_resonance(eax_all.lResonance); + defer_peak_level(eax_all.lPeakLevel); +} + +void EaxAutoWahEffect::defer_attack_time( + const EaxEaxCall& eax_call) +{ + const auto& attack_time = + eax_call.get_value(); + + validate_attack_time(attack_time); + defer_attack_time(attack_time); +} + +void EaxAutoWahEffect::defer_release_time( + const EaxEaxCall& eax_call) +{ + const auto& release_time = + eax_call.get_value(); + + validate_release_time(release_time); + defer_release_time(release_time); +} + +void EaxAutoWahEffect::defer_resonance( + const EaxEaxCall& eax_call) +{ + const auto& resonance = + eax_call.get_value(); + + validate_resonance(resonance); + defer_resonance(resonance); +} + +void EaxAutoWahEffect::defer_peak_level( + const EaxEaxCall& eax_call) +{ + const auto& peak_level = + eax_call.get_value(); + + validate_peak_level(peak_level); + defer_peak_level(peak_level); +} + +void EaxAutoWahEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxAutoWahEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxAutoWahEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flAttackTime) + { + set_efx_attack_time(); + } + + if (eax_dirty_flags_.flReleaseTime) + { + set_efx_release_time(); + } + + if (eax_dirty_flags_.lResonance) + { + set_efx_resonance(); + } + + if (eax_dirty_flags_.lPeakLevel) + { + set_efx_peak_gain(); + } + + eax_dirty_flags_ = EaxAutoWahEffectDirtyFlags{}; + + return true; +} + +void EaxAutoWahEffect::set(const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXAUTOWAH_NONE: + break; + + case EAXAUTOWAH_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXAUTOWAH_ATTACKTIME: + defer_attack_time(eax_call); + break; + + case EAXAUTOWAH_RELEASETIME: + defer_release_time(eax_call); + break; + + case EAXAUTOWAH_RESONANCE: + defer_resonance(eax_call); + break; + + case EAXAUTOWAH_PEAKLEVEL: + defer_peak_level(eax_call); + break; + + default: + throw EaxAutoWahEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_auto_wah_effect() +{ + return std::make_unique<::EaxAutoWahEffect>(); +} + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/chorus.cpp b/Engine/lib/openal-soft/al/effects/chorus.cpp index b23952831..56318095c 100644 --- a/Engine/lib/openal-soft/al/effects/chorus.cpp +++ b/Engine/lib/openal-soft/al/effects/chorus.cpp @@ -1,17 +1,31 @@ #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" + +#ifdef ALSOFT_EAX +#include + +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX namespace { +static_assert(ChorusMaxDelay >= AL_CHORUS_MAX_DELAY, "Chorus max delay too small"); +static_assert(FlangerMaxDelay >= AL_FLANGER_MAX_DELAY, "Flanger max delay too small"); + static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch"); static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch"); @@ -274,3 +288,1070 @@ const EffectProps ChorusEffectProps{genDefaultChorusProps()}; DEFINE_ALEFFECT_VTABLE(Flanger); const EffectProps FlangerEffectProps{genDefaultFlangerProps()}; + + +#ifdef ALSOFT_EAX +namespace { + +void eax_set_efx_waveform( + ALenum waveform, + EffectProps& al_effect_props) +{ + const auto efx_waveform = WaveformFromEnum(waveform); + assert(efx_waveform.has_value()); + al_effect_props.Chorus.Waveform = *efx_waveform; +} + +void eax_set_efx_phase( + ALint phase, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Phase = phase; +} + +void eax_set_efx_rate( + ALfloat rate, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Rate = rate; +} + +void eax_set_efx_depth( + ALfloat depth, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Depth = depth; +} + +void eax_set_efx_feedback( + ALfloat feedback, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Feedback = feedback; +} + +void eax_set_efx_delay( + ALfloat delay, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Delay = delay; +} + + +using EaxChorusEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxChorusEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxChorusEffectDirtyFlagsValue ulWaveform : 1; + EaxChorusEffectDirtyFlagsValue lPhase : 1; + EaxChorusEffectDirtyFlagsValue flRate : 1; + EaxChorusEffectDirtyFlagsValue flDepth : 1; + EaxChorusEffectDirtyFlagsValue flFeedback : 1; + EaxChorusEffectDirtyFlagsValue flDelay : 1; +}; // EaxChorusEffectDirtyFlags + + +class EaxChorusEffect final : + public EaxEffect +{ +public: + EaxChorusEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXCHORUSPROPERTIES eax_{}; + EAXCHORUSPROPERTIES eax_d_{}; + EaxChorusEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults() noexcept; + + void set_efx_waveform(); + void set_efx_phase(); + void set_efx_rate(); + void set_efx_depth(); + void set_efx_feedback(); + void set_efx_delay(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_waveform(unsigned long ulWaveform); + void validate_phase(long lPhase); + void validate_rate(float flRate); + void validate_depth(float flDepth); + void validate_feedback(float flFeedback); + void validate_delay(float flDelay); + void validate_all(const EAXCHORUSPROPERTIES& eax_all); + + void defer_waveform(unsigned long ulWaveform); + void defer_phase(long lPhase); + void defer_rate(float flRate); + void defer_depth(float flDepth); + void defer_feedback(float flFeedback); + void defer_delay(float flDelay); + void defer_all(const EAXCHORUSPROPERTIES& eax_all); + + void defer_waveform(const EaxEaxCall& eax_call); + void defer_phase(const EaxEaxCall& eax_call); + void defer_rate(const EaxEaxCall& eax_call); + void defer_depth(const EaxEaxCall& eax_call); + void defer_feedback(const EaxEaxCall& eax_call); + void defer_delay(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxChorusEffect + + +class EaxChorusEffectException : + public EaxException +{ +public: + explicit EaxChorusEffectException( + const char* message) + : + EaxException{"EAX_CHORUS_EFFECT", message} + { + } +}; // EaxChorusEffectException + + +EaxChorusEffect::EaxChorusEffect() + : EaxEffect{AL_EFFECT_CHORUS} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxChorusEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxChorusEffect::set_eax_defaults() noexcept +{ + eax_.ulWaveform = EAXCHORUS_DEFAULTWAVEFORM; + eax_.lPhase = EAXCHORUS_DEFAULTPHASE; + eax_.flRate = EAXCHORUS_DEFAULTRATE; + eax_.flDepth = EAXCHORUS_DEFAULTDEPTH; + eax_.flFeedback = EAXCHORUS_DEFAULTFEEDBACK; + eax_.flDelay = EAXCHORUS_DEFAULTDELAY; + + eax_d_ = eax_; +} + +void EaxChorusEffect::set_efx_waveform() +{ + const auto waveform = clamp( + static_cast(eax_.ulWaveform), + AL_CHORUS_MIN_WAVEFORM, + AL_CHORUS_MAX_WAVEFORM); + + eax_set_efx_waveform(waveform, al_effect_props_); +} + +void EaxChorusEffect::set_efx_phase() +{ + const auto phase = clamp( + static_cast(eax_.lPhase), + AL_CHORUS_MIN_PHASE, + AL_CHORUS_MAX_PHASE); + + eax_set_efx_phase(phase, al_effect_props_); +} + +void EaxChorusEffect::set_efx_rate() +{ + const auto rate = clamp( + eax_.flRate, + AL_CHORUS_MIN_RATE, + AL_CHORUS_MAX_RATE); + + eax_set_efx_rate(rate, al_effect_props_); +} + +void EaxChorusEffect::set_efx_depth() +{ + const auto depth = clamp( + eax_.flDepth, + AL_CHORUS_MIN_DEPTH, + AL_CHORUS_MAX_DEPTH); + + eax_set_efx_depth(depth, al_effect_props_); +} + +void EaxChorusEffect::set_efx_feedback() +{ + const auto feedback = clamp( + eax_.flFeedback, + AL_CHORUS_MIN_FEEDBACK, + AL_CHORUS_MAX_FEEDBACK); + + eax_set_efx_feedback(feedback, al_effect_props_); +} + +void EaxChorusEffect::set_efx_delay() +{ + const auto delay = clamp( + eax_.flDelay, + AL_CHORUS_MIN_DELAY, + AL_CHORUS_MAX_DELAY); + + eax_set_efx_delay(delay, al_effect_props_); +} + +void EaxChorusEffect::set_efx_defaults() +{ + set_efx_waveform(); + set_efx_phase(); + set_efx_rate(); + set_efx_depth(); + set_efx_feedback(); + set_efx_delay(); +} + +void EaxChorusEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXCHORUS_NONE: + break; + + case EAXCHORUS_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXCHORUS_WAVEFORM: + eax_call.set_value(eax_.ulWaveform); + break; + + case EAXCHORUS_PHASE: + eax_call.set_value(eax_.lPhase); + break; + + case EAXCHORUS_RATE: + eax_call.set_value(eax_.flRate); + break; + + case EAXCHORUS_DEPTH: + eax_call.set_value(eax_.flDepth); + break; + + case EAXCHORUS_FEEDBACK: + eax_call.set_value(eax_.flFeedback); + break; + + case EAXCHORUS_DELAY: + eax_call.set_value(eax_.flDelay); + break; + + default: + throw EaxChorusEffectException{"Unsupported property id."}; + } +} + +void EaxChorusEffect::validate_waveform( + unsigned long ulWaveform) +{ + eax_validate_range( + "Waveform", + ulWaveform, + EAXCHORUS_MINWAVEFORM, + EAXCHORUS_MAXWAVEFORM); +} + +void EaxChorusEffect::validate_phase( + long lPhase) +{ + eax_validate_range( + "Phase", + lPhase, + EAXCHORUS_MINPHASE, + EAXCHORUS_MAXPHASE); +} + +void EaxChorusEffect::validate_rate( + float flRate) +{ + eax_validate_range( + "Rate", + flRate, + EAXCHORUS_MINRATE, + EAXCHORUS_MAXRATE); +} + +void EaxChorusEffect::validate_depth( + float flDepth) +{ + eax_validate_range( + "Depth", + flDepth, + EAXCHORUS_MINDEPTH, + EAXCHORUS_MAXDEPTH); +} + +void EaxChorusEffect::validate_feedback( + float flFeedback) +{ + eax_validate_range( + "Feedback", + flFeedback, + EAXCHORUS_MINFEEDBACK, + EAXCHORUS_MAXFEEDBACK); +} + +void EaxChorusEffect::validate_delay( + float flDelay) +{ + eax_validate_range( + "Delay", + flDelay, + EAXCHORUS_MINDELAY, + EAXCHORUS_MAXDELAY); +} + +void EaxChorusEffect::validate_all( + const EAXCHORUSPROPERTIES& eax_all) +{ + validate_waveform(eax_all.ulWaveform); + validate_phase(eax_all.lPhase); + validate_rate(eax_all.flRate); + validate_depth(eax_all.flDepth); + validate_feedback(eax_all.flFeedback); + validate_delay(eax_all.flDelay); +} + +void EaxChorusEffect::defer_waveform( + unsigned long ulWaveform) +{ + eax_d_.ulWaveform = ulWaveform; + eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); +} + +void EaxChorusEffect::defer_phase( + long lPhase) +{ + eax_d_.lPhase = lPhase; + eax_dirty_flags_.lPhase = (eax_.lPhase != eax_d_.lPhase); +} + +void EaxChorusEffect::defer_rate( + float flRate) +{ + eax_d_.flRate = flRate; + eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate); +} + +void EaxChorusEffect::defer_depth( + float flDepth) +{ + eax_d_.flDepth = flDepth; + eax_dirty_flags_.flDepth = (eax_.flDepth != eax_d_.flDepth); +} + +void EaxChorusEffect::defer_feedback( + float flFeedback) +{ + eax_d_.flFeedback = flFeedback; + eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback); +} + +void EaxChorusEffect::defer_delay( + float flDelay) +{ + eax_d_.flDelay = flDelay; + eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay); +} + +void EaxChorusEffect::defer_all( + const EAXCHORUSPROPERTIES& eax_all) +{ + defer_waveform(eax_all.ulWaveform); + defer_phase(eax_all.lPhase); + defer_rate(eax_all.flRate); + defer_depth(eax_all.flDepth); + defer_feedback(eax_all.flFeedback); + defer_delay(eax_all.flDelay); +} + +void EaxChorusEffect::defer_waveform( + const EaxEaxCall& eax_call) +{ + const auto& waveform = + eax_call.get_value(); + + validate_waveform(waveform); + defer_waveform(waveform); +} + +void EaxChorusEffect::defer_phase( + const EaxEaxCall& eax_call) +{ + const auto& phase = + eax_call.get_value(); + + validate_phase(phase); + defer_phase(phase); +} + +void EaxChorusEffect::defer_rate( + const EaxEaxCall& eax_call) +{ + const auto& rate = + eax_call.get_value(); + + validate_rate(rate); + defer_rate(rate); +} + +void EaxChorusEffect::defer_depth( + const EaxEaxCall& eax_call) +{ + const auto& depth = + eax_call.get_value(); + + validate_depth(depth); + defer_depth(depth); +} + +void EaxChorusEffect::defer_feedback( + const EaxEaxCall& eax_call) +{ + const auto& feedback = + eax_call.get_value(); + + validate_feedback(feedback); + defer_feedback(feedback); +} + +void EaxChorusEffect::defer_delay( + const EaxEaxCall& eax_call) +{ + const auto& delay = + eax_call.get_value(); + + validate_delay(delay); + defer_delay(delay); +} + +void EaxChorusEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxChorusEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxChorusEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulWaveform) + { + set_efx_waveform(); + } + + if (eax_dirty_flags_.lPhase) + { + set_efx_phase(); + } + + if (eax_dirty_flags_.flRate) + { + set_efx_rate(); + } + + if (eax_dirty_flags_.flDepth) + { + set_efx_depth(); + } + + if (eax_dirty_flags_.flFeedback) + { + set_efx_feedback(); + } + + if (eax_dirty_flags_.flDelay) + { + set_efx_delay(); + } + + eax_dirty_flags_ = EaxChorusEffectDirtyFlags{}; + + return true; +} + +void EaxChorusEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXCHORUS_NONE: + break; + + case EAXCHORUS_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXCHORUS_WAVEFORM: + defer_waveform(eax_call); + break; + + case EAXCHORUS_PHASE: + defer_phase(eax_call); + break; + + case EAXCHORUS_RATE: + defer_rate(eax_call); + break; + + case EAXCHORUS_DEPTH: + defer_depth(eax_call); + break; + + case EAXCHORUS_FEEDBACK: + defer_feedback(eax_call); + break; + + case EAXCHORUS_DELAY: + defer_delay(eax_call); + break; + + default: + throw EaxChorusEffectException{"Unsupported property id."}; + } +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_chorus_effect() +{ + return std::make_unique<::EaxChorusEffect>(); +} + + +namespace +{ + + +using EaxFlangerEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxFlangerEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxFlangerEffectDirtyFlagsValue ulWaveform : 1; + EaxFlangerEffectDirtyFlagsValue lPhase : 1; + EaxFlangerEffectDirtyFlagsValue flRate : 1; + EaxFlangerEffectDirtyFlagsValue flDepth : 1; + EaxFlangerEffectDirtyFlagsValue flFeedback : 1; + EaxFlangerEffectDirtyFlagsValue flDelay : 1; +}; // EaxFlangerEffectDirtyFlags + + +class EaxFlangerEffect final : + public EaxEffect +{ +public: + EaxFlangerEffect(); + + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXFLANGERPROPERTIES eax_{}; + EAXFLANGERPROPERTIES eax_d_{}; + EaxFlangerEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_waveform(); + void set_efx_phase(); + void set_efx_rate(); + void set_efx_depth(); + void set_efx_feedback(); + void set_efx_delay(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_waveform(unsigned long ulWaveform); + void validate_phase(long lPhase); + void validate_rate(float flRate); + void validate_depth(float flDepth); + void validate_feedback(float flFeedback); + void validate_delay(float flDelay); + void validate_all(const EAXFLANGERPROPERTIES& all); + + void defer_waveform(unsigned long ulWaveform); + void defer_phase(long lPhase); + void defer_rate(float flRate); + void defer_depth(float flDepth); + void defer_feedback(float flFeedback); + void defer_delay(float flDelay); + void defer_all(const EAXFLANGERPROPERTIES& all); + + void defer_waveform(const EaxEaxCall& eax_call); + void defer_phase(const EaxEaxCall& eax_call); + void defer_rate(const EaxEaxCall& eax_call); + void defer_depth(const EaxEaxCall& eax_call); + void defer_feedback(const EaxEaxCall& eax_call); + void defer_delay(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxFlangerEffect + + +class EaxFlangerEffectException : + public EaxException +{ +public: + explicit EaxFlangerEffectException( + const char* message) + : + EaxException{"EAX_FLANGER_EFFECT", message} + { + } +}; // EaxFlangerEffectException + + +EaxFlangerEffect::EaxFlangerEffect() + : EaxEffect{AL_EFFECT_FLANGER} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxFlangerEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxFlangerEffect::set_eax_defaults() +{ + eax_.ulWaveform = EAXFLANGER_DEFAULTWAVEFORM; + eax_.lPhase = EAXFLANGER_DEFAULTPHASE; + eax_.flRate = EAXFLANGER_DEFAULTRATE; + eax_.flDepth = EAXFLANGER_DEFAULTDEPTH; + eax_.flFeedback = EAXFLANGER_DEFAULTFEEDBACK; + eax_.flDelay = EAXFLANGER_DEFAULTDELAY; + + eax_d_ = eax_; +} + +void EaxFlangerEffect::set_efx_waveform() +{ + const auto waveform = clamp( + static_cast(eax_.ulWaveform), + AL_FLANGER_MIN_WAVEFORM, + AL_FLANGER_MAX_WAVEFORM); + + eax_set_efx_waveform(waveform, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_phase() +{ + const auto phase = clamp( + static_cast(eax_.lPhase), + AL_FLANGER_MIN_PHASE, + AL_FLANGER_MAX_PHASE); + + eax_set_efx_phase(phase, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_rate() +{ + const auto rate = clamp( + eax_.flRate, + AL_FLANGER_MIN_RATE, + AL_FLANGER_MAX_RATE); + + eax_set_efx_rate(rate, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_depth() +{ + const auto depth = clamp( + eax_.flDepth, + AL_FLANGER_MIN_DEPTH, + AL_FLANGER_MAX_DEPTH); + + eax_set_efx_depth(depth, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_feedback() +{ + const auto feedback = clamp( + eax_.flFeedback, + AL_FLANGER_MIN_FEEDBACK, + AL_FLANGER_MAX_FEEDBACK); + + eax_set_efx_feedback(feedback, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_delay() +{ + const auto delay = clamp( + eax_.flDelay, + AL_FLANGER_MIN_DELAY, + AL_FLANGER_MAX_DELAY); + + eax_set_efx_delay(delay, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_defaults() +{ + set_efx_waveform(); + set_efx_phase(); + set_efx_rate(); + set_efx_depth(); + set_efx_feedback(); + set_efx_delay(); +} + +void EaxFlangerEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXFLANGER_NONE: + break; + + case EAXFLANGER_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXFLANGER_WAVEFORM: + eax_call.set_value(eax_.ulWaveform); + break; + + case EAXFLANGER_PHASE: + eax_call.set_value(eax_.lPhase); + break; + + case EAXFLANGER_RATE: + eax_call.set_value(eax_.flRate); + break; + + case EAXFLANGER_DEPTH: + eax_call.set_value(eax_.flDepth); + break; + + case EAXFLANGER_FEEDBACK: + eax_call.set_value(eax_.flFeedback); + break; + + case EAXFLANGER_DELAY: + eax_call.set_value(eax_.flDelay); + break; + + default: + throw EaxFlangerEffectException{"Unsupported property id."}; + } +} + +void EaxFlangerEffect::validate_waveform( + unsigned long ulWaveform) +{ + eax_validate_range( + "Waveform", + ulWaveform, + EAXFLANGER_MINWAVEFORM, + EAXFLANGER_MAXWAVEFORM); +} + +void EaxFlangerEffect::validate_phase( + long lPhase) +{ + eax_validate_range( + "Phase", + lPhase, + EAXFLANGER_MINPHASE, + EAXFLANGER_MAXPHASE); +} + +void EaxFlangerEffect::validate_rate( + float flRate) +{ + eax_validate_range( + "Rate", + flRate, + EAXFLANGER_MINRATE, + EAXFLANGER_MAXRATE); +} + +void EaxFlangerEffect::validate_depth( + float flDepth) +{ + eax_validate_range( + "Depth", + flDepth, + EAXFLANGER_MINDEPTH, + EAXFLANGER_MAXDEPTH); +} + +void EaxFlangerEffect::validate_feedback( + float flFeedback) +{ + eax_validate_range( + "Feedback", + flFeedback, + EAXFLANGER_MINFEEDBACK, + EAXFLANGER_MAXFEEDBACK); +} + +void EaxFlangerEffect::validate_delay( + float flDelay) +{ + eax_validate_range( + "Delay", + flDelay, + EAXFLANGER_MINDELAY, + EAXFLANGER_MAXDELAY); +} + +void EaxFlangerEffect::validate_all( + const EAXFLANGERPROPERTIES& all) +{ + validate_waveform(all.ulWaveform); + validate_phase(all.lPhase); + validate_rate(all.flRate); + validate_depth(all.flDepth); + validate_feedback(all.flDelay); + validate_delay(all.flDelay); +} + +void EaxFlangerEffect::defer_waveform( + unsigned long ulWaveform) +{ + eax_d_.ulWaveform = ulWaveform; + eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); +} + +void EaxFlangerEffect::defer_phase( + long lPhase) +{ + eax_d_.lPhase = lPhase; + eax_dirty_flags_.lPhase = (eax_.lPhase != eax_d_.lPhase); +} + +void EaxFlangerEffect::defer_rate( + float flRate) +{ + eax_d_.flRate = flRate; + eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate); +} + +void EaxFlangerEffect::defer_depth( + float flDepth) +{ + eax_d_.flDepth = flDepth; + eax_dirty_flags_.flDepth = (eax_.flDepth != eax_d_.flDepth); +} + +void EaxFlangerEffect::defer_feedback( + float flFeedback) +{ + eax_d_.flFeedback = flFeedback; + eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback); +} + +void EaxFlangerEffect::defer_delay( + float flDelay) +{ + eax_d_.flDelay = flDelay; + eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay); +} + +void EaxFlangerEffect::defer_all( + const EAXFLANGERPROPERTIES& all) +{ + defer_waveform(all.ulWaveform); + defer_phase(all.lPhase); + defer_rate(all.flRate); + defer_depth(all.flDepth); + defer_feedback(all.flDelay); + defer_delay(all.flDelay); +} + +void EaxFlangerEffect::defer_waveform( + const EaxEaxCall& eax_call) +{ + const auto& waveform = + eax_call.get_value(); + + validate_waveform(waveform); + defer_waveform(waveform); +} + +void EaxFlangerEffect::defer_phase( + const EaxEaxCall& eax_call) +{ + const auto& phase = + eax_call.get_value(); + + validate_phase(phase); + defer_phase(phase); +} + +void EaxFlangerEffect::defer_rate( + const EaxEaxCall& eax_call) +{ + const auto& rate = + eax_call.get_value(); + + validate_rate(rate); + defer_rate(rate); +} + +void EaxFlangerEffect::defer_depth( + const EaxEaxCall& eax_call) +{ + const auto& depth = + eax_call.get_value(); + + validate_depth(depth); + defer_depth(depth); +} + +void EaxFlangerEffect::defer_feedback( + const EaxEaxCall& eax_call) +{ + const auto& feedback = + eax_call.get_value(); + + validate_feedback(feedback); + defer_feedback(feedback); +} + +void EaxFlangerEffect::defer_delay( + const EaxEaxCall& eax_call) +{ + const auto& delay = + eax_call.get_value(); + + validate_delay(delay); + defer_delay(delay); +} + +void EaxFlangerEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxFlangerEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxFlangerEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulWaveform) + { + set_efx_waveform(); + } + + if (eax_dirty_flags_.lPhase) + { + set_efx_phase(); + } + + if (eax_dirty_flags_.flRate) + { + set_efx_rate(); + } + + if (eax_dirty_flags_.flDepth) + { + set_efx_depth(); + } + + if (eax_dirty_flags_.flFeedback) + { + set_efx_feedback(); + } + + if (eax_dirty_flags_.flDelay) + { + set_efx_delay(); + } + + eax_dirty_flags_ = EaxFlangerEffectDirtyFlags{}; + + return true; +} + +void EaxFlangerEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXFLANGER_NONE: + break; + + case EAXFLANGER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXFLANGER_WAVEFORM: + defer_waveform(eax_call); + break; + + case EAXFLANGER_PHASE: + defer_phase(eax_call); + break; + + case EAXFLANGER_RATE: + defer_rate(eax_call); + break; + + case EAXFLANGER_DEPTH: + defer_depth(eax_call); + break; + + case EAXFLANGER_FEEDBACK: + defer_feedback(eax_call); + break; + + case EAXFLANGER_DELAY: + defer_delay(eax_call); + break; + + default: + throw EaxFlangerEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_flanger_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/compressor.cpp b/Engine/lib/openal-soft/al/effects/compressor.cpp index 94e074314..bb5dfa3e4 100644 --- a/Engine/lib/openal-soft/al/effects/compressor.cpp +++ b/Engine/lib/openal-soft/al/effects/compressor.cpp @@ -4,8 +4,15 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX namespace { @@ -70,3 +77,220 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Compressor); const EffectProps CompressorEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxCompressorEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxCompressorEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxCompressorEffectDirtyFlagsValue ulOnOff : 1; +}; // EaxCompressorEffectDirtyFlags + + +class EaxCompressorEffect final : + public EaxEffect +{ +public: + EaxCompressorEffect(); + + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXAGCCOMPRESSORPROPERTIES eax_{}; + EAXAGCCOMPRESSORPROPERTIES eax_d_{}; + EaxCompressorEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + void set_efx_on_off(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_on_off(unsigned long ulOnOff); + void validate_all(const EAXAGCCOMPRESSORPROPERTIES& eax_all); + + void defer_on_off(unsigned long ulOnOff); + void defer_all(const EAXAGCCOMPRESSORPROPERTIES& eax_all); + + void defer_on_off(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxCompressorEffect + + +class EaxCompressorEffectException : + public EaxException +{ +public: + explicit EaxCompressorEffectException( + const char* message) + : + EaxException{"EAX_COMPRESSOR_EFFECT", message} + { + } +}; // EaxCompressorEffectException + + +EaxCompressorEffect::EaxCompressorEffect() + : EaxEffect{AL_EFFECT_COMPRESSOR} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +// [[nodiscard]] +void EaxCompressorEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxCompressorEffect::set_eax_defaults() +{ + eax_.ulOnOff = EAXAGCCOMPRESSOR_DEFAULTONOFF; + + eax_d_ = eax_; +} + +void EaxCompressorEffect::set_efx_on_off() +{ + const auto on_off = clamp( + static_cast(eax_.ulOnOff), + AL_COMPRESSOR_MIN_ONOFF, + AL_COMPRESSOR_MAX_ONOFF); + + al_effect_props_.Compressor.OnOff = (on_off != AL_FALSE); +} + +void EaxCompressorEffect::set_efx_defaults() +{ + set_efx_on_off(); +} + +void EaxCompressorEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXAGCCOMPRESSOR_NONE: + break; + + case EAXAGCCOMPRESSOR_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXAGCCOMPRESSOR_ONOFF: + eax_call.set_value(eax_.ulOnOff); + break; + + default: + throw EaxCompressorEffectException{"Unsupported property id."}; + } +} + +void EaxCompressorEffect::validate_on_off( + unsigned long ulOnOff) +{ + eax_validate_range( + "On-Off", + ulOnOff, + EAXAGCCOMPRESSOR_MINONOFF, + EAXAGCCOMPRESSOR_MAXONOFF); +} + +void EaxCompressorEffect::validate_all( + const EAXAGCCOMPRESSORPROPERTIES& eax_all) +{ + validate_on_off(eax_all.ulOnOff); +} + +void EaxCompressorEffect::defer_on_off( + unsigned long ulOnOff) +{ + eax_d_.ulOnOff = ulOnOff; + eax_dirty_flags_.ulOnOff = (eax_.ulOnOff != eax_d_.ulOnOff); +} + +void EaxCompressorEffect::defer_all( + const EAXAGCCOMPRESSORPROPERTIES& eax_all) +{ + defer_on_off(eax_all.ulOnOff); +} + +void EaxCompressorEffect::defer_on_off( + const EaxEaxCall& eax_call) +{ + const auto& on_off = + eax_call.get_value(); + + validate_on_off(on_off); + defer_on_off(on_off); +} + +void EaxCompressorEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxCompressorEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxCompressorEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulOnOff) + { + set_efx_on_off(); + } + + eax_dirty_flags_ = EaxCompressorEffectDirtyFlags{}; + + return true; +} + +void EaxCompressorEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXAGCCOMPRESSOR_NONE: + break; + + case EAXAGCCOMPRESSOR_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXAGCCOMPRESSOR_ONOFF: + defer_on_off(eax_call); + break; + + default: + throw EaxCompressorEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_compressor_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/convolution.cpp b/Engine/lib/openal-soft/al/effects/convolution.cpp index 4d87b1c49..8e850fd3e 100644 --- a/Engine/lib/openal-soft/al/effects/convolution.cpp +++ b/Engine/lib/openal-soft/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/Engine/lib/openal-soft/al/effects/dedicated.cpp b/Engine/lib/openal-soft/al/effects/dedicated.cpp index 334d9e56f..db57003c2 100644 --- a/Engine/lib/openal-soft/al/effects/dedicated.cpp +++ b/Engine/lib/openal-soft/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/Engine/lib/openal-soft/al/effects/distortion.cpp b/Engine/lib/openal-soft/al/effects/distortion.cpp index 8961a4d99..13b1f23d9 100644 --- a/Engine/lib/openal-soft/al/effects/distortion.cpp +++ b/Engine/lib/openal-soft/al/effects/distortion.cpp @@ -4,8 +4,15 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX namespace { @@ -112,3 +119,453 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Distortion); const EffectProps DistortionEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxDistortionEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxDistortionEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxDistortionEffectDirtyFlagsValue flEdge : 1; + EaxDistortionEffectDirtyFlagsValue lGain : 1; + EaxDistortionEffectDirtyFlagsValue flLowPassCutOff : 1; + EaxDistortionEffectDirtyFlagsValue flEQCenter : 1; + EaxDistortionEffectDirtyFlagsValue flEQBandwidth : 1; +}; // EaxDistortionEffectDirtyFlags + + +class EaxDistortionEffect final : + public EaxEffect +{ +public: + EaxDistortionEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXDISTORTIONPROPERTIES eax_{}; + EAXDISTORTIONPROPERTIES eax_d_{}; + EaxDistortionEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_edge(); + void set_efx_gain(); + void set_efx_lowpass_cutoff(); + void set_efx_eq_center(); + void set_efx_eq_bandwidth(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_edge(float flEdge); + void validate_gain(long lGain); + void validate_lowpass_cutoff(float flLowPassCutOff); + void validate_eq_center(float flEQCenter); + void validate_eq_bandwidth(float flEQBandwidth); + void validate_all(const EAXDISTORTIONPROPERTIES& eax_all); + + void defer_edge(float flEdge); + void defer_gain(long lGain); + void defer_low_pass_cutoff(float flLowPassCutOff); + void defer_eq_center(float flEQCenter); + void defer_eq_bandwidth(float flEQBandwidth); + void defer_all(const EAXDISTORTIONPROPERTIES& eax_all); + + void defer_edge(const EaxEaxCall& eax_call); + void defer_gain(const EaxEaxCall& eax_call); + void defer_low_pass_cutoff(const EaxEaxCall& eax_call); + void defer_eq_center(const EaxEaxCall& eax_call); + void defer_eq_bandwidth(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxDistortionEffect + + +class EaxDistortionEffectException : + public EaxException +{ +public: + explicit EaxDistortionEffectException( + const char* message) + : + EaxException{"EAX_DISTORTION_EFFECT", message} + { + } +}; // EaxDistortionEffectException + + +EaxDistortionEffect::EaxDistortionEffect() + : EaxEffect{AL_EFFECT_DISTORTION} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxDistortionEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxDistortionEffect::set_eax_defaults() +{ + eax_.flEdge = EAXDISTORTION_DEFAULTEDGE; + eax_.lGain = EAXDISTORTION_DEFAULTGAIN; + eax_.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF; + eax_.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER; + eax_.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH; + + eax_d_ = eax_; +} + +void EaxDistortionEffect::set_efx_edge() +{ + const auto edge = clamp( + eax_.flEdge, + AL_DISTORTION_MIN_EDGE, + AL_DISTORTION_MAX_EDGE); + + al_effect_props_.Distortion.Edge = edge; +} + +void EaxDistortionEffect::set_efx_gain() +{ + const auto gain = clamp( + level_mb_to_gain(static_cast(eax_.lGain)), + AL_DISTORTION_MIN_GAIN, + AL_DISTORTION_MAX_GAIN); + + al_effect_props_.Distortion.Gain = gain; +} + +void EaxDistortionEffect::set_efx_lowpass_cutoff() +{ + const auto lowpass_cutoff = clamp( + eax_.flLowPassCutOff, + AL_DISTORTION_MIN_LOWPASS_CUTOFF, + AL_DISTORTION_MAX_LOWPASS_CUTOFF); + + al_effect_props_.Distortion.LowpassCutoff = lowpass_cutoff; +} + +void EaxDistortionEffect::set_efx_eq_center() +{ + const auto eq_center = clamp( + eax_.flEQCenter, + AL_DISTORTION_MIN_EQCENTER, + AL_DISTORTION_MAX_EQCENTER); + + al_effect_props_.Distortion.EQCenter = eq_center; +} + +void EaxDistortionEffect::set_efx_eq_bandwidth() +{ + const auto eq_bandwidth = clamp( + eax_.flEdge, + AL_DISTORTION_MIN_EQBANDWIDTH, + AL_DISTORTION_MAX_EQBANDWIDTH); + + al_effect_props_.Distortion.EQBandwidth = eq_bandwidth; +} + +void EaxDistortionEffect::set_efx_defaults() +{ + set_efx_edge(); + set_efx_gain(); + set_efx_lowpass_cutoff(); + set_efx_eq_center(); + set_efx_eq_bandwidth(); +} + +void EaxDistortionEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXDISTORTION_NONE: + break; + + case EAXDISTORTION_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXDISTORTION_EDGE: + eax_call.set_value(eax_.flEdge); + break; + + case EAXDISTORTION_GAIN: + eax_call.set_value(eax_.lGain); + break; + + case EAXDISTORTION_LOWPASSCUTOFF: + eax_call.set_value(eax_.flLowPassCutOff); + break; + + case EAXDISTORTION_EQCENTER: + eax_call.set_value(eax_.flEQCenter); + break; + + case EAXDISTORTION_EQBANDWIDTH: + eax_call.set_value(eax_.flEQBandwidth); + break; + + default: + throw EaxDistortionEffectException{"Unsupported property id."}; + } +} + +void EaxDistortionEffect::validate_edge( + float flEdge) +{ + eax_validate_range( + "Edge", + flEdge, + EAXDISTORTION_MINEDGE, + EAXDISTORTION_MAXEDGE); +} + +void EaxDistortionEffect::validate_gain( + long lGain) +{ + eax_validate_range( + "Gain", + lGain, + EAXDISTORTION_MINGAIN, + EAXDISTORTION_MAXGAIN); +} + +void EaxDistortionEffect::validate_lowpass_cutoff( + float flLowPassCutOff) +{ + eax_validate_range( + "Low-pass Cut-off", + flLowPassCutOff, + EAXDISTORTION_MINLOWPASSCUTOFF, + EAXDISTORTION_MAXLOWPASSCUTOFF); +} + +void EaxDistortionEffect::validate_eq_center( + float flEQCenter) +{ + eax_validate_range( + "EQ Center", + flEQCenter, + EAXDISTORTION_MINEQCENTER, + EAXDISTORTION_MAXEQCENTER); +} + +void EaxDistortionEffect::validate_eq_bandwidth( + float flEQBandwidth) +{ + eax_validate_range( + "EQ Bandwidth", + flEQBandwidth, + EAXDISTORTION_MINEQBANDWIDTH, + EAXDISTORTION_MAXEQBANDWIDTH); +} + +void EaxDistortionEffect::validate_all( + const EAXDISTORTIONPROPERTIES& eax_all) +{ + validate_edge(eax_all.flEdge); + validate_gain(eax_all.lGain); + validate_lowpass_cutoff(eax_all.flLowPassCutOff); + validate_eq_center(eax_all.flEQCenter); + validate_eq_bandwidth(eax_all.flEQBandwidth); +} + +void EaxDistortionEffect::defer_edge( + float flEdge) +{ + eax_d_.flEdge = flEdge; + eax_dirty_flags_.flEdge = (eax_.flEdge != eax_d_.flEdge); +} + +void EaxDistortionEffect::defer_gain( + long lGain) +{ + eax_d_.lGain = lGain; + eax_dirty_flags_.lGain = (eax_.lGain != eax_d_.lGain); +} + +void EaxDistortionEffect::defer_low_pass_cutoff( + float flLowPassCutOff) +{ + eax_d_.flLowPassCutOff = flLowPassCutOff; + eax_dirty_flags_.flLowPassCutOff = (eax_.flLowPassCutOff != eax_d_.flLowPassCutOff); +} + +void EaxDistortionEffect::defer_eq_center( + float flEQCenter) +{ + eax_d_.flEQCenter = flEQCenter; + eax_dirty_flags_.flEQCenter = (eax_.flEQCenter != eax_d_.flEQCenter); +} + +void EaxDistortionEffect::defer_eq_bandwidth( + float flEQBandwidth) +{ + eax_d_.flEQBandwidth = flEQBandwidth; + eax_dirty_flags_.flEQBandwidth = (eax_.flEQBandwidth != eax_d_.flEQBandwidth); +} + +void EaxDistortionEffect::defer_all( + const EAXDISTORTIONPROPERTIES& eax_all) +{ + defer_edge(eax_all.flEdge); + defer_gain(eax_all.lGain); + defer_low_pass_cutoff(eax_all.flLowPassCutOff); + defer_eq_center(eax_all.flEQCenter); + defer_eq_bandwidth(eax_all.flEQBandwidth); +} + +void EaxDistortionEffect::defer_edge( + const EaxEaxCall& eax_call) +{ + const auto& edge = + eax_call.get_value(); + + validate_edge(edge); + defer_edge(edge); +} + +void EaxDistortionEffect::defer_gain( + const EaxEaxCall& eax_call) +{ + const auto& gain = + eax_call.get_value(); + + validate_gain(gain); + defer_gain(gain); +} + +void EaxDistortionEffect::defer_low_pass_cutoff( + const EaxEaxCall& eax_call) +{ + const auto& lowpass_cutoff = + eax_call.get_value(); + + validate_lowpass_cutoff(lowpass_cutoff); + defer_low_pass_cutoff(lowpass_cutoff); +} + +void EaxDistortionEffect::defer_eq_center( + const EaxEaxCall& eax_call) +{ + const auto& eq_center = + eax_call.get_value(); + + validate_eq_center(eq_center); + defer_eq_center(eq_center); +} + +void EaxDistortionEffect::defer_eq_bandwidth( + const EaxEaxCall& eax_call) +{ + const auto& eq_bandwidth = + eax_call.get_value(); + + validate_eq_bandwidth(eq_bandwidth); + defer_eq_bandwidth(eq_bandwidth); +} + +void EaxDistortionEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxDistortionEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxDistortionEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flEdge) + { + set_efx_edge(); + } + + if (eax_dirty_flags_.lGain) + { + set_efx_gain(); + } + + if (eax_dirty_flags_.flLowPassCutOff) + { + set_efx_lowpass_cutoff(); + } + + if (eax_dirty_flags_.flEQCenter) + { + set_efx_eq_center(); + } + + if (eax_dirty_flags_.flEQBandwidth) + { + set_efx_eq_bandwidth(); + } + + eax_dirty_flags_ = EaxDistortionEffectDirtyFlags{}; + + return true; +} + +void EaxDistortionEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXDISTORTION_NONE: + break; + + case EAXDISTORTION_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXDISTORTION_EDGE: + defer_edge(eax_call); + break; + + case EAXDISTORTION_GAIN: + defer_gain(eax_call); + break; + + case EAXDISTORTION_LOWPASSCUTOFF: + defer_low_pass_cutoff(eax_call); + break; + + case EAXDISTORTION_EQCENTER: + defer_eq_center(eax_call); + break; + + case EAXDISTORTION_EQBANDWIDTH: + defer_eq_bandwidth(eax_call); + break; + + default: + throw EaxDistortionEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_distortion_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/echo.cpp b/Engine/lib/openal-soft/al/effects/echo.cpp index 79a60521d..61adad7f7 100644 --- a/Engine/lib/openal-soft/al/effects/echo.cpp +++ b/Engine/lib/openal-soft/al/effects/echo.cpp @@ -4,8 +4,15 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX namespace { @@ -109,3 +116,454 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Echo); const EffectProps EchoEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxEchoEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxEchoEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxEchoEffectDirtyFlagsValue flDelay : 1; + EaxEchoEffectDirtyFlagsValue flLRDelay : 1; + EaxEchoEffectDirtyFlagsValue flDamping : 1; + EaxEchoEffectDirtyFlagsValue flFeedback : 1; + EaxEchoEffectDirtyFlagsValue flSpread : 1; +}; // EaxEchoEffectDirtyFlags + + +class EaxEchoEffect final : + public EaxEffect +{ +public: + EaxEchoEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXECHOPROPERTIES eax_{}; + EAXECHOPROPERTIES eax_d_{}; + EaxEchoEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_delay(); + void set_efx_lr_delay(); + void set_efx_damping(); + void set_efx_feedback(); + void set_efx_spread(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_delay(float flDelay); + void validate_lr_delay(float flLRDelay); + void validate_damping(float flDamping); + void validate_feedback(float flFeedback); + void validate_spread(float flSpread); + void validate_all(const EAXECHOPROPERTIES& all); + + void defer_delay(float flDelay); + void defer_lr_delay(float flLRDelay); + void defer_damping(float flDamping); + void defer_feedback(float flFeedback); + void defer_spread(float flSpread); + void defer_all(const EAXECHOPROPERTIES& all); + + void defer_delay(const EaxEaxCall& eax_call); + void defer_lr_delay(const EaxEaxCall& eax_call); + void defer_damping(const EaxEaxCall& eax_call); + void defer_feedback(const EaxEaxCall& eax_call); + void defer_spread(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxEchoEffect + + +class EaxEchoEffectException : + public EaxException +{ +public: + explicit EaxEchoEffectException( + const char* message) + : + EaxException{"EAX_ECHO_EFFECT", message} + { + } +}; // EaxEchoEffectException + + +EaxEchoEffect::EaxEchoEffect() + : EaxEffect{AL_EFFECT_ECHO} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxEchoEffect::dispatch( + const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxEchoEffect::set_eax_defaults() +{ + eax_.flDelay = EAXECHO_DEFAULTDELAY; + eax_.flLRDelay = EAXECHO_DEFAULTLRDELAY; + eax_.flDamping = EAXECHO_DEFAULTDAMPING; + eax_.flFeedback = EAXECHO_DEFAULTFEEDBACK; + eax_.flSpread = EAXECHO_DEFAULTSPREAD; + + eax_d_ = eax_; +} + +void EaxEchoEffect::set_efx_delay() +{ + const auto delay = clamp( + eax_.flDelay, + AL_ECHO_MIN_DELAY, + AL_ECHO_MAX_DELAY); + + al_effect_props_.Echo.Delay = delay; +} + +void EaxEchoEffect::set_efx_lr_delay() +{ + const auto lr_delay = clamp( + eax_.flLRDelay, + AL_ECHO_MIN_LRDELAY, + AL_ECHO_MAX_LRDELAY); + + al_effect_props_.Echo.LRDelay = lr_delay; +} + +void EaxEchoEffect::set_efx_damping() +{ + const auto damping = clamp( + eax_.flDamping, + AL_ECHO_MIN_DAMPING, + AL_ECHO_MAX_DAMPING); + + al_effect_props_.Echo.Damping = damping; +} + +void EaxEchoEffect::set_efx_feedback() +{ + const auto feedback = clamp( + eax_.flFeedback, + AL_ECHO_MIN_FEEDBACK, + AL_ECHO_MAX_FEEDBACK); + + al_effect_props_.Echo.Feedback = feedback; +} + +void EaxEchoEffect::set_efx_spread() +{ + const auto spread = clamp( + eax_.flSpread, + AL_ECHO_MIN_SPREAD, + AL_ECHO_MAX_SPREAD); + + al_effect_props_.Echo.Spread = spread; +} + +void EaxEchoEffect::set_efx_defaults() +{ + set_efx_delay(); + set_efx_lr_delay(); + set_efx_damping(); + set_efx_feedback(); + set_efx_spread(); +} + +void EaxEchoEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXECHO_NONE: + break; + + case EAXECHO_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXECHO_DELAY: + eax_call.set_value(eax_.flDelay); + break; + + case EAXECHO_LRDELAY: + eax_call.set_value(eax_.flLRDelay); + break; + + case EAXECHO_DAMPING: + eax_call.set_value(eax_.flDamping); + break; + + case EAXECHO_FEEDBACK: + eax_call.set_value(eax_.flFeedback); + break; + + case EAXECHO_SPREAD: + eax_call.set_value(eax_.flSpread); + break; + + default: + throw EaxEchoEffectException{"Unsupported property id."}; + } +} + +void EaxEchoEffect::validate_delay( + float flDelay) +{ + eax_validate_range( + "Delay", + flDelay, + EAXECHO_MINDELAY, + EAXECHO_MAXDELAY); +} + +void EaxEchoEffect::validate_lr_delay( + float flLRDelay) +{ + eax_validate_range( + "LR Delay", + flLRDelay, + EAXECHO_MINLRDELAY, + EAXECHO_MAXLRDELAY); +} + +void EaxEchoEffect::validate_damping( + float flDamping) +{ + eax_validate_range( + "Damping", + flDamping, + EAXECHO_MINDAMPING, + EAXECHO_MAXDAMPING); +} + +void EaxEchoEffect::validate_feedback( + float flFeedback) +{ + eax_validate_range( + "Feedback", + flFeedback, + EAXECHO_MINFEEDBACK, + EAXECHO_MAXFEEDBACK); +} + +void EaxEchoEffect::validate_spread( + float flSpread) +{ + eax_validate_range( + "Spread", + flSpread, + EAXECHO_MINSPREAD, + EAXECHO_MAXSPREAD); +} + +void EaxEchoEffect::validate_all( + const EAXECHOPROPERTIES& all) +{ + validate_delay(all.flDelay); + validate_lr_delay(all.flLRDelay); + validate_damping(all.flDamping); + validate_feedback(all.flFeedback); + validate_spread(all.flSpread); +} + +void EaxEchoEffect::defer_delay( + float flDelay) +{ + eax_d_.flDelay = flDelay; + eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay); +} + +void EaxEchoEffect::defer_lr_delay( + float flLRDelay) +{ + eax_d_.flLRDelay = flLRDelay; + eax_dirty_flags_.flLRDelay = (eax_.flLRDelay != eax_d_.flLRDelay); +} + +void EaxEchoEffect::defer_damping( + float flDamping) +{ + eax_d_.flDamping = flDamping; + eax_dirty_flags_.flDamping = (eax_.flDamping != eax_d_.flDamping); +} + +void EaxEchoEffect::defer_feedback( + float flFeedback) +{ + eax_d_.flFeedback = flFeedback; + eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback); +} + +void EaxEchoEffect::defer_spread( + float flSpread) +{ + eax_d_.flSpread = flSpread; + eax_dirty_flags_.flSpread = (eax_.flSpread != eax_d_.flSpread); +} + +void EaxEchoEffect::defer_all( + const EAXECHOPROPERTIES& all) +{ + defer_delay(all.flDelay); + defer_lr_delay(all.flLRDelay); + defer_damping(all.flDamping); + defer_feedback(all.flFeedback); + defer_spread(all.flSpread); +} + +void EaxEchoEffect::defer_delay( + const EaxEaxCall& eax_call) +{ + const auto& delay = + eax_call.get_value(); + + validate_delay(delay); + defer_delay(delay); +} + +void EaxEchoEffect::defer_lr_delay( + const EaxEaxCall& eax_call) +{ + const auto& lr_delay = + eax_call.get_value(); + + validate_lr_delay(lr_delay); + defer_lr_delay(lr_delay); +} + +void EaxEchoEffect::defer_damping( + const EaxEaxCall& eax_call) +{ + const auto& damping = + eax_call.get_value(); + + validate_damping(damping); + defer_damping(damping); +} + +void EaxEchoEffect::defer_feedback( + const EaxEaxCall& eax_call) +{ + const auto& feedback = + eax_call.get_value(); + + validate_feedback(feedback); + defer_feedback(feedback); +} + +void EaxEchoEffect::defer_spread( + const EaxEaxCall& eax_call) +{ + const auto& spread = + eax_call.get_value(); + + validate_spread(spread); + defer_spread(spread); +} + +void EaxEchoEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxEchoEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxEchoEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flDelay) + { + set_efx_delay(); + } + + if (eax_dirty_flags_.flLRDelay) + { + set_efx_lr_delay(); + } + + if (eax_dirty_flags_.flDamping) + { + set_efx_damping(); + } + + if (eax_dirty_flags_.flFeedback) + { + set_efx_feedback(); + } + + if (eax_dirty_flags_.flSpread) + { + set_efx_spread(); + } + + eax_dirty_flags_ = EaxEchoEffectDirtyFlags{}; + + return true; +} + +void EaxEchoEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXECHO_NONE: + break; + + case EAXECHO_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXECHO_DELAY: + defer_delay(eax_call); + break; + + case EAXECHO_LRDELAY: + defer_lr_delay(eax_call); + break; + + case EAXECHO_DAMPING: + defer_damping(eax_call); + break; + + case EAXECHO_FEEDBACK: + defer_feedback(eax_call); + break; + + case EAXECHO_SPREAD: + defer_spread(eax_call); + break; + + default: + throw EaxEchoEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_echo_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/effects.cpp b/Engine/lib/openal-soft/al/effects/effects.cpp new file mode 100644 index 000000000..faf322d2f --- /dev/null +++ b/Engine/lib/openal-soft/al/effects/effects.cpp @@ -0,0 +1,66 @@ + +#include "config.h" + +#ifdef ALSOFT_EAX + +#include "effects.h" + +#include + +#include "AL/efx.h" + + +EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type) +{ +#define EAX_PREFIX "[EAX_MAKE_EAX_EFFECT] " + + switch (al_effect_type) + { + case AL_EFFECT_NULL: + return eax_create_eax_null_effect(); + + case AL_EFFECT_CHORUS: + return eax_create_eax_chorus_effect(); + + case AL_EFFECT_DISTORTION: + return eax_create_eax_distortion_effect(); + + case AL_EFFECT_ECHO: + return eax_create_eax_echo_effect(); + + case AL_EFFECT_FLANGER: + return eax_create_eax_flanger_effect(); + + case AL_EFFECT_FREQUENCY_SHIFTER: + return eax_create_eax_frequency_shifter_effect(); + + case AL_EFFECT_VOCAL_MORPHER: + return eax_create_eax_vocal_morpher_effect(); + + case AL_EFFECT_PITCH_SHIFTER: + return eax_create_eax_pitch_shifter_effect(); + + case AL_EFFECT_RING_MODULATOR: + return eax_create_eax_ring_modulator_effect(); + + case AL_EFFECT_AUTOWAH: + return eax_create_eax_auto_wah_effect(); + + case AL_EFFECT_COMPRESSOR: + return eax_create_eax_compressor_effect(); + + case AL_EFFECT_EQUALIZER: + return eax_create_eax_equalizer_effect(); + + case AL_EFFECT_EAXREVERB: + return eax_create_eax_reverb_effect(); + + default: + assert(false && "Unsupported AL effect type."); + return nullptr; + } + +#undef EAX_PREFIX +} + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/effects.h b/Engine/lib/openal-soft/al/effects/effects.h index d6c88c4f1..830e7191c 100644 --- a/Engine/lib/openal-soft/al/effects/effects.h +++ b/Engine/lib/openal-soft/al/effects/effects.h @@ -5,6 +5,10 @@ #include "core/except.h" +#ifdef ALSOFT_EAX +#include "al/eax_effect.h" +#endif // ALSOFT_EAX + union EffectProps; @@ -12,7 +16,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; } @@ -76,4 +84,9 @@ extern const EffectVtable VmorpherEffectVtable; extern const EffectVtable DedicatedEffectVtable; extern const EffectVtable ConvolutionEffectVtable; + +#ifdef ALSOFT_EAX +EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type); +#endif // ALSOFT_EAX + #endif /* AL_EFFECTS_EFFECTS_H */ diff --git a/Engine/lib/openal-soft/al/effects/equalizer.cpp b/Engine/lib/openal-soft/al/effects/equalizer.cpp index 3a7c0a8f0..f829328c4 100644 --- a/Engine/lib/openal-soft/al/effects/equalizer.cpp +++ b/Engine/lib/openal-soft/al/effects/equalizer.cpp @@ -4,8 +4,15 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX namespace { @@ -167,3 +174,748 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Equalizer); const EffectProps EqualizerEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxEqualizerEffectDirtyFlagsValue = std::uint_least16_t; + +struct EaxEqualizerEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxEqualizerEffectDirtyFlagsValue lLowGain : 1; + EaxEqualizerEffectDirtyFlagsValue flLowCutOff : 1; + EaxEqualizerEffectDirtyFlagsValue lMid1Gain : 1; + EaxEqualizerEffectDirtyFlagsValue flMid1Center : 1; + EaxEqualizerEffectDirtyFlagsValue flMid1Width : 1; + EaxEqualizerEffectDirtyFlagsValue lMid2Gain : 1; + EaxEqualizerEffectDirtyFlagsValue flMid2Center : 1; + EaxEqualizerEffectDirtyFlagsValue flMid2Width : 1; + EaxEqualizerEffectDirtyFlagsValue lHighGain : 1; + EaxEqualizerEffectDirtyFlagsValue flHighCutOff : 1; +}; // EaxEqualizerEffectDirtyFlags + + +class EaxEqualizerEffect final : + public EaxEffect +{ +public: + EaxEqualizerEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXEQUALIZERPROPERTIES eax_{}; + EAXEQUALIZERPROPERTIES eax_d_{}; + EaxEqualizerEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_low_gain(); + void set_efx_low_cutoff(); + void set_efx_mid1_gain(); + void set_efx_mid1_center(); + void set_efx_mid1_width(); + void set_efx_mid2_gain(); + void set_efx_mid2_center(); + void set_efx_mid2_width(); + void set_efx_high_gain(); + void set_efx_high_cutoff(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_low_gain(long lLowGain); + void validate_low_cutoff(float flLowCutOff); + void validate_mid1_gain(long lMid1Gain); + void validate_mid1_center(float flMid1Center); + void validate_mid1_width(float flMid1Width); + void validate_mid2_gain(long lMid2Gain); + void validate_mid2_center(float flMid2Center); + void validate_mid2_width(float flMid2Width); + void validate_high_gain(long lHighGain); + void validate_high_cutoff(float flHighCutOff); + void validate_all(const EAXEQUALIZERPROPERTIES& all); + + void defer_low_gain(long lLowGain); + void defer_low_cutoff(float flLowCutOff); + void defer_mid1_gain(long lMid1Gain); + void defer_mid1_center(float flMid1Center); + void defer_mid1_width(float flMid1Width); + void defer_mid2_gain(long lMid2Gain); + void defer_mid2_center(float flMid2Center); + void defer_mid2_width(float flMid2Width); + void defer_high_gain(long lHighGain); + void defer_high_cutoff(float flHighCutOff); + void defer_all(const EAXEQUALIZERPROPERTIES& all); + + void defer_low_gain(const EaxEaxCall& eax_call); + void defer_low_cutoff(const EaxEaxCall& eax_call); + void defer_mid1_gain(const EaxEaxCall& eax_call); + void defer_mid1_center(const EaxEaxCall& eax_call); + void defer_mid1_width(const EaxEaxCall& eax_call); + void defer_mid2_gain(const EaxEaxCall& eax_call); + void defer_mid2_center(const EaxEaxCall& eax_call); + void defer_mid2_width(const EaxEaxCall& eax_call); + void defer_high_gain(const EaxEaxCall& eax_call); + void defer_high_cutoff(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxEqualizerEffect + + +class EaxEqualizerEffectException : + public EaxException +{ +public: + explicit EaxEqualizerEffectException( + const char* message) + : + EaxException{"EAX_EQUALIZER_EFFECT", message} + { + } +}; // EaxEqualizerEffectException + + +EaxEqualizerEffect::EaxEqualizerEffect() + : EaxEffect{AL_EFFECT_EQUALIZER} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxEqualizerEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxEqualizerEffect::set_eax_defaults() +{ + eax_.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN; + eax_.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF; + eax_.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN; + eax_.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER; + eax_.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH; + eax_.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN; + eax_.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER; + eax_.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH; + eax_.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN; + eax_.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF; + + eax_d_ = eax_; +} + +void EaxEqualizerEffect::set_efx_low_gain() +{ + const auto low_gain = clamp( + level_mb_to_gain(static_cast(eax_.lLowGain)), + AL_EQUALIZER_MIN_LOW_GAIN, + AL_EQUALIZER_MAX_LOW_GAIN); + + al_effect_props_.Equalizer.LowGain = low_gain; +} + +void EaxEqualizerEffect::set_efx_low_cutoff() +{ + const auto low_cutoff = clamp( + eax_.flLowCutOff, + AL_EQUALIZER_MIN_LOW_CUTOFF, + AL_EQUALIZER_MAX_LOW_CUTOFF); + + al_effect_props_.Equalizer.LowCutoff = low_cutoff; +} + +void EaxEqualizerEffect::set_efx_mid1_gain() +{ + const auto mid1_gain = clamp( + level_mb_to_gain(static_cast(eax_.lMid1Gain)), + AL_EQUALIZER_MIN_MID1_GAIN, + AL_EQUALIZER_MAX_MID1_GAIN); + + al_effect_props_.Equalizer.Mid1Gain = mid1_gain; +} + +void EaxEqualizerEffect::set_efx_mid1_center() +{ + const auto mid1_center = clamp( + eax_.flMid1Center, + AL_EQUALIZER_MIN_MID1_CENTER, + AL_EQUALIZER_MAX_MID1_CENTER); + + al_effect_props_.Equalizer.Mid1Center = mid1_center; +} + +void EaxEqualizerEffect::set_efx_mid1_width() +{ + const auto mid1_width = clamp( + eax_.flMid1Width, + AL_EQUALIZER_MIN_MID1_WIDTH, + AL_EQUALIZER_MAX_MID1_WIDTH); + + al_effect_props_.Equalizer.Mid1Width = mid1_width; +} + +void EaxEqualizerEffect::set_efx_mid2_gain() +{ + const auto mid2_gain = clamp( + level_mb_to_gain(static_cast(eax_.lMid2Gain)), + AL_EQUALIZER_MIN_MID2_GAIN, + AL_EQUALIZER_MAX_MID2_GAIN); + + al_effect_props_.Equalizer.Mid2Gain = mid2_gain; +} + +void EaxEqualizerEffect::set_efx_mid2_center() +{ + const auto mid2_center = clamp( + eax_.flMid2Center, + AL_EQUALIZER_MIN_MID2_CENTER, + AL_EQUALIZER_MAX_MID2_CENTER); + + al_effect_props_.Equalizer.Mid2Center = mid2_center; +} + +void EaxEqualizerEffect::set_efx_mid2_width() +{ + const auto mid2_width = clamp( + eax_.flMid2Width, + AL_EQUALIZER_MIN_MID2_WIDTH, + AL_EQUALIZER_MAX_MID2_WIDTH); + + al_effect_props_.Equalizer.Mid2Width = mid2_width; +} + +void EaxEqualizerEffect::set_efx_high_gain() +{ + const auto high_gain = clamp( + level_mb_to_gain(static_cast(eax_.lHighGain)), + AL_EQUALIZER_MIN_HIGH_GAIN, + AL_EQUALIZER_MAX_HIGH_GAIN); + + al_effect_props_.Equalizer.HighGain = high_gain; +} + +void EaxEqualizerEffect::set_efx_high_cutoff() +{ + const auto high_cutoff = clamp( + eax_.flHighCutOff, + AL_EQUALIZER_MIN_HIGH_CUTOFF, + AL_EQUALIZER_MAX_HIGH_CUTOFF); + + al_effect_props_.Equalizer.HighCutoff = high_cutoff; +} + +void EaxEqualizerEffect::set_efx_defaults() +{ + set_efx_low_gain(); + set_efx_low_cutoff(); + set_efx_mid1_gain(); + set_efx_mid1_center(); + set_efx_mid1_width(); + set_efx_mid2_gain(); + set_efx_mid2_center(); + set_efx_mid2_width(); + set_efx_high_gain(); + set_efx_high_cutoff(); +} + +void EaxEqualizerEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXEQUALIZER_NONE: + break; + + case EAXEQUALIZER_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXEQUALIZER_LOWGAIN: + eax_call.set_value(eax_.lLowGain); + break; + + case EAXEQUALIZER_LOWCUTOFF: + eax_call.set_value(eax_.flLowCutOff); + break; + + case EAXEQUALIZER_MID1GAIN: + eax_call.set_value(eax_.lMid1Gain); + break; + + case EAXEQUALIZER_MID1CENTER: + eax_call.set_value(eax_.flMid1Center); + break; + + case EAXEQUALIZER_MID1WIDTH: + eax_call.set_value(eax_.flMid1Width); + break; + + case EAXEQUALIZER_MID2GAIN: + eax_call.set_value(eax_.lMid2Gain); + break; + + case EAXEQUALIZER_MID2CENTER: + eax_call.set_value(eax_.flMid2Center); + break; + + case EAXEQUALIZER_MID2WIDTH: + eax_call.set_value(eax_.flMid2Width); + break; + + case EAXEQUALIZER_HIGHGAIN: + eax_call.set_value(eax_.lHighGain); + break; + + case EAXEQUALIZER_HIGHCUTOFF: + eax_call.set_value(eax_.flHighCutOff); + break; + + default: + throw EaxEqualizerEffectException{"Unsupported property id."}; + } +} + +void EaxEqualizerEffect::validate_low_gain( + long lLowGain) +{ + eax_validate_range( + "Low Gain", + lLowGain, + EAXEQUALIZER_MINLOWGAIN, + EAXEQUALIZER_MAXLOWGAIN); +} + +void EaxEqualizerEffect::validate_low_cutoff( + float flLowCutOff) +{ + eax_validate_range( + "Low Cutoff", + flLowCutOff, + EAXEQUALIZER_MINLOWCUTOFF, + EAXEQUALIZER_MAXLOWCUTOFF); +} + +void EaxEqualizerEffect::validate_mid1_gain( + long lMid1Gain) +{ + eax_validate_range( + "Mid1 Gain", + lMid1Gain, + EAXEQUALIZER_MINMID1GAIN, + EAXEQUALIZER_MAXMID1GAIN); +} + +void EaxEqualizerEffect::validate_mid1_center( + float flMid1Center) +{ + eax_validate_range( + "Mid1 Center", + flMid1Center, + EAXEQUALIZER_MINMID1CENTER, + EAXEQUALIZER_MAXMID1CENTER); +} + +void EaxEqualizerEffect::validate_mid1_width( + float flMid1Width) +{ + eax_validate_range( + "Mid1 Width", + flMid1Width, + EAXEQUALIZER_MINMID1WIDTH, + EAXEQUALIZER_MAXMID1WIDTH); +} + +void EaxEqualizerEffect::validate_mid2_gain( + long lMid2Gain) +{ + eax_validate_range( + "Mid2 Gain", + lMid2Gain, + EAXEQUALIZER_MINMID2GAIN, + EAXEQUALIZER_MAXMID2GAIN); +} + +void EaxEqualizerEffect::validate_mid2_center( + float flMid2Center) +{ + eax_validate_range( + "Mid2 Center", + flMid2Center, + EAXEQUALIZER_MINMID2CENTER, + EAXEQUALIZER_MAXMID2CENTER); +} + +void EaxEqualizerEffect::validate_mid2_width( + float flMid2Width) +{ + eax_validate_range( + "Mid2 Width", + flMid2Width, + EAXEQUALIZER_MINMID2WIDTH, + EAXEQUALIZER_MAXMID2WIDTH); +} + +void EaxEqualizerEffect::validate_high_gain( + long lHighGain) +{ + eax_validate_range( + "High Gain", + lHighGain, + EAXEQUALIZER_MINHIGHGAIN, + EAXEQUALIZER_MAXHIGHGAIN); +} + +void EaxEqualizerEffect::validate_high_cutoff( + float flHighCutOff) +{ + eax_validate_range( + "High Cutoff", + flHighCutOff, + EAXEQUALIZER_MINHIGHCUTOFF, + EAXEQUALIZER_MAXHIGHCUTOFF); +} + +void EaxEqualizerEffect::validate_all( + const EAXEQUALIZERPROPERTIES& all) +{ + validate_low_gain(all.lLowGain); + validate_low_cutoff(all.flLowCutOff); + validate_mid1_gain(all.lMid1Gain); + validate_mid1_center(all.flMid1Center); + validate_mid1_width(all.flMid1Width); + validate_mid2_gain(all.lMid2Gain); + validate_mid2_center(all.flMid2Center); + validate_mid2_width(all.flMid2Width); + validate_high_gain(all.lHighGain); + validate_high_cutoff(all.flHighCutOff); +} + +void EaxEqualizerEffect::defer_low_gain( + long lLowGain) +{ + eax_d_.lLowGain = lLowGain; + eax_dirty_flags_.lLowGain = (eax_.lLowGain != eax_d_.lLowGain); +} + +void EaxEqualizerEffect::defer_low_cutoff( + float flLowCutOff) +{ + eax_d_.flLowCutOff = flLowCutOff; + eax_dirty_flags_.flLowCutOff = (eax_.flLowCutOff != eax_d_.flLowCutOff); +} + +void EaxEqualizerEffect::defer_mid1_gain( + long lMid1Gain) +{ + eax_d_.lMid1Gain = lMid1Gain; + eax_dirty_flags_.lMid1Gain = (eax_.lMid1Gain != eax_d_.lMid1Gain); +} + +void EaxEqualizerEffect::defer_mid1_center( + float flMid1Center) +{ + eax_d_.flMid1Center = flMid1Center; + eax_dirty_flags_.flMid1Center = (eax_.flMid1Center != eax_d_.flMid1Center); +} + +void EaxEqualizerEffect::defer_mid1_width( + float flMid1Width) +{ + eax_d_.flMid1Width = flMid1Width; + eax_dirty_flags_.flMid1Width = (eax_.flMid1Width != eax_d_.flMid1Width); +} + +void EaxEqualizerEffect::defer_mid2_gain( + long lMid2Gain) +{ + eax_d_.lMid2Gain = lMid2Gain; + eax_dirty_flags_.lMid2Gain = (eax_.lMid2Gain != eax_d_.lMid2Gain); +} + +void EaxEqualizerEffect::defer_mid2_center( + float flMid2Center) +{ + eax_d_.flMid2Center = flMid2Center; + eax_dirty_flags_.flMid2Center = (eax_.flMid2Center != eax_d_.flMid2Center); +} + +void EaxEqualizerEffect::defer_mid2_width( + float flMid2Width) +{ + eax_d_.flMid2Width = flMid2Width; + eax_dirty_flags_.flMid2Width = (eax_.flMid2Width != eax_d_.flMid2Width); +} + +void EaxEqualizerEffect::defer_high_gain( + long lHighGain) +{ + eax_d_.lHighGain = lHighGain; + eax_dirty_flags_.lHighGain = (eax_.lHighGain != eax_d_.lHighGain); +} + +void EaxEqualizerEffect::defer_high_cutoff( + float flHighCutOff) +{ + eax_d_.flHighCutOff = flHighCutOff; + eax_dirty_flags_.flHighCutOff = (eax_.flHighCutOff != eax_d_.flHighCutOff); +} + +void EaxEqualizerEffect::defer_all( + const EAXEQUALIZERPROPERTIES& all) +{ + defer_low_gain(all.lLowGain); + defer_low_cutoff(all.flLowCutOff); + defer_mid1_gain(all.lMid1Gain); + defer_mid1_center(all.flMid1Center); + defer_mid1_width(all.flMid1Width); + defer_mid2_gain(all.lMid2Gain); + defer_mid2_center(all.flMid2Center); + defer_mid2_width(all.flMid2Width); + defer_high_gain(all.lHighGain); + defer_high_cutoff(all.flHighCutOff); +} + +void EaxEqualizerEffect::defer_low_gain( + const EaxEaxCall& eax_call) +{ + const auto& low_gain = + eax_call.get_value(); + + validate_low_gain(low_gain); + defer_low_gain(low_gain); +} + +void EaxEqualizerEffect::defer_low_cutoff( + const EaxEaxCall& eax_call) +{ + const auto& low_cutoff = + eax_call.get_value(); + + validate_low_cutoff(low_cutoff); + defer_low_cutoff(low_cutoff); +} + +void EaxEqualizerEffect::defer_mid1_gain( + const EaxEaxCall& eax_call) +{ + const auto& mid1_gain = + eax_call.get_value(); + + validate_mid1_gain(mid1_gain); + defer_mid1_gain(mid1_gain); +} + +void EaxEqualizerEffect::defer_mid1_center( + const EaxEaxCall& eax_call) +{ + const auto& mid1_center = + eax_call.get_value(); + + validate_mid1_center(mid1_center); + defer_mid1_center(mid1_center); +} + +void EaxEqualizerEffect::defer_mid1_width( + const EaxEaxCall& eax_call) +{ + const auto& mid1_width = + eax_call.get_value(); + + validate_mid1_width(mid1_width); + defer_mid1_width(mid1_width); +} + +void EaxEqualizerEffect::defer_mid2_gain( + const EaxEaxCall& eax_call) +{ + const auto& mid2_gain = + eax_call.get_value(); + + validate_mid2_gain(mid2_gain); + defer_mid2_gain(mid2_gain); +} + +void EaxEqualizerEffect::defer_mid2_center( + const EaxEaxCall& eax_call) +{ + const auto& mid2_center = + eax_call.get_value(); + + validate_mid2_center(mid2_center); + defer_mid2_center(mid2_center); +} + +void EaxEqualizerEffect::defer_mid2_width( + const EaxEaxCall& eax_call) +{ + const auto& mid2_width = + eax_call.get_value(); + + validate_mid2_width(mid2_width); + defer_mid2_width(mid2_width); +} + +void EaxEqualizerEffect::defer_high_gain( + const EaxEaxCall& eax_call) +{ + const auto& high_gain = + eax_call.get_value(); + + validate_high_gain(high_gain); + defer_high_gain(high_gain); +} + +void EaxEqualizerEffect::defer_high_cutoff( + const EaxEaxCall& eax_call) +{ + const auto& high_cutoff = + eax_call.get_value(); + + validate_high_cutoff(high_cutoff); + defer_high_cutoff(high_cutoff); +} + +void EaxEqualizerEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxEqualizerEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxEqualizerEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.lLowGain) + { + set_efx_low_gain(); + } + + if (eax_dirty_flags_.flLowCutOff) + { + set_efx_low_cutoff(); + } + + if (eax_dirty_flags_.lMid1Gain) + { + set_efx_mid1_gain(); + } + + if (eax_dirty_flags_.flMid1Center) + { + set_efx_mid1_center(); + } + + if (eax_dirty_flags_.flMid1Width) + { + set_efx_mid1_width(); + } + + if (eax_dirty_flags_.lMid2Gain) + { + set_efx_mid2_gain(); + } + + if (eax_dirty_flags_.flMid2Center) + { + set_efx_mid2_center(); + } + + if (eax_dirty_flags_.flMid2Width) + { + set_efx_mid2_width(); + } + + if (eax_dirty_flags_.lHighGain) + { + set_efx_high_gain(); + } + + if (eax_dirty_flags_.flHighCutOff) + { + set_efx_high_cutoff(); + } + + eax_dirty_flags_ = EaxEqualizerEffectDirtyFlags{}; + + return true; +} + +void EaxEqualizerEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXEQUALIZER_NONE: + break; + + case EAXEQUALIZER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXEQUALIZER_LOWGAIN: + defer_low_gain(eax_call); + break; + + case EAXEQUALIZER_LOWCUTOFF: + defer_low_cutoff(eax_call); + break; + + case EAXEQUALIZER_MID1GAIN: + defer_mid1_gain(eax_call); + break; + + case EAXEQUALIZER_MID1CENTER: + defer_mid1_center(eax_call); + break; + + case EAXEQUALIZER_MID1WIDTH: + defer_mid1_width(eax_call); + break; + + case EAXEQUALIZER_MID2GAIN: + defer_mid2_gain(eax_call); + break; + + case EAXEQUALIZER_MID2CENTER: + defer_mid2_center(eax_call); + break; + + case EAXEQUALIZER_MID2WIDTH: + defer_mid2_width(eax_call); + break; + + case EAXEQUALIZER_HIGHGAIN: + defer_high_gain(eax_call); + break; + + case EAXEQUALIZER_HIGHCUTOFF: + defer_high_cutoff(eax_call); + break; + + default: + throw EaxEqualizerEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_equalizer_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/fshifter.cpp b/Engine/lib/openal-soft/al/effects/fshifter.cpp index 444b02607..d334890be 100644 --- a/Engine/lib/openal-soft/al/effects/fshifter.cpp +++ b/Engine/lib/openal-soft/al/effects/fshifter.cpp @@ -1,12 +1,23 @@ #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" + +#ifdef ALSOFT_EAX +#include + +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX namespace { @@ -126,3 +137,343 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Fshifter); const EffectProps FshifterEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxFrequencyShifterEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxFrequencyShifterEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxFrequencyShifterEffectDirtyFlagsValue flFrequency : 1; + EaxFrequencyShifterEffectDirtyFlagsValue ulLeftDirection : 1; + EaxFrequencyShifterEffectDirtyFlagsValue ulRightDirection : 1; +}; // EaxFrequencyShifterEffectDirtyFlags + + +class EaxFrequencyShifterEffect final : + public EaxEffect +{ +public: + EaxFrequencyShifterEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXFREQUENCYSHIFTERPROPERTIES eax_{}; + EAXFREQUENCYSHIFTERPROPERTIES eax_d_{}; + EaxFrequencyShifterEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_frequency(); + void set_efx_left_direction(); + void set_efx_right_direction(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_frequency(float flFrequency); + void validate_left_direction(unsigned long ulLeftDirection); + void validate_right_direction(unsigned long ulRightDirection); + void validate_all(const EAXFREQUENCYSHIFTERPROPERTIES& all); + + void defer_frequency(float flFrequency); + void defer_left_direction(unsigned long ulLeftDirection); + void defer_right_direction(unsigned long ulRightDirection); + void defer_all(const EAXFREQUENCYSHIFTERPROPERTIES& all); + + void defer_frequency(const EaxEaxCall& eax_call); + void defer_left_direction(const EaxEaxCall& eax_call); + void defer_right_direction(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxFrequencyShifterEffect + + +class EaxFrequencyShifterEffectException : + public EaxException +{ +public: + explicit EaxFrequencyShifterEffectException( + const char* message) + : + EaxException{"EAX_FREQUENCY_SHIFTER_EFFECT", message} + { + } +}; // EaxFrequencyShifterEffectException + + +EaxFrequencyShifterEffect::EaxFrequencyShifterEffect() + : EaxEffect{AL_EFFECT_FREQUENCY_SHIFTER} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxFrequencyShifterEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxFrequencyShifterEffect::set_eax_defaults() +{ + eax_.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY; + eax_.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION; + eax_.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION; + + eax_d_ = eax_; +} + +void EaxFrequencyShifterEffect::set_efx_frequency() +{ + const auto frequency = clamp( + eax_.flFrequency, + AL_FREQUENCY_SHIFTER_MIN_FREQUENCY, + AL_FREQUENCY_SHIFTER_MAX_FREQUENCY); + + al_effect_props_.Fshifter.Frequency = frequency; +} + +void EaxFrequencyShifterEffect::set_efx_left_direction() +{ + const auto left_direction = clamp( + static_cast(eax_.ulLeftDirection), + AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION, + AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION); + + const auto efx_left_direction = DirectionFromEmum(left_direction); + assert(efx_left_direction.has_value()); + al_effect_props_.Fshifter.LeftDirection = *efx_left_direction; +} + +void EaxFrequencyShifterEffect::set_efx_right_direction() +{ + const auto right_direction = clamp( + static_cast(eax_.ulRightDirection), + AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION, + AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION); + + const auto efx_right_direction = DirectionFromEmum(right_direction); + assert(efx_right_direction.has_value()); + al_effect_props_.Fshifter.RightDirection = *efx_right_direction; +} + +void EaxFrequencyShifterEffect::set_efx_defaults() +{ + set_efx_frequency(); + set_efx_left_direction(); + set_efx_right_direction(); +} + +void EaxFrequencyShifterEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXFREQUENCYSHIFTER_NONE: + break; + + case EAXFREQUENCYSHIFTER_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXFREQUENCYSHIFTER_FREQUENCY: + eax_call.set_value(eax_.flFrequency); + break; + + case EAXFREQUENCYSHIFTER_LEFTDIRECTION: + eax_call.set_value(eax_.ulLeftDirection); + break; + + case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: + eax_call.set_value(eax_.ulRightDirection); + break; + + default: + throw EaxFrequencyShifterEffectException{"Unsupported property id."}; + } +} + +void EaxFrequencyShifterEffect::validate_frequency( + float flFrequency) +{ + eax_validate_range( + "Frequency", + flFrequency, + EAXFREQUENCYSHIFTER_MINFREQUENCY, + EAXFREQUENCYSHIFTER_MAXFREQUENCY); +} + +void EaxFrequencyShifterEffect::validate_left_direction( + unsigned long ulLeftDirection) +{ + eax_validate_range( + "Left Direction", + ulLeftDirection, + EAXFREQUENCYSHIFTER_MINLEFTDIRECTION, + EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION); +} + +void EaxFrequencyShifterEffect::validate_right_direction( + unsigned long ulRightDirection) +{ + eax_validate_range( + "Right Direction", + ulRightDirection, + EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION, + EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION); +} + +void EaxFrequencyShifterEffect::validate_all( + const EAXFREQUENCYSHIFTERPROPERTIES& all) +{ + validate_frequency(all.flFrequency); + validate_left_direction(all.ulLeftDirection); + validate_right_direction(all.ulRightDirection); +} + +void EaxFrequencyShifterEffect::defer_frequency( + float flFrequency) +{ + eax_d_.flFrequency = flFrequency; + eax_dirty_flags_.flFrequency = (eax_.flFrequency != eax_d_.flFrequency); +} + +void EaxFrequencyShifterEffect::defer_left_direction( + unsigned long ulLeftDirection) +{ + eax_d_.ulLeftDirection = ulLeftDirection; + eax_dirty_flags_.ulLeftDirection = (eax_.ulLeftDirection != eax_d_.ulLeftDirection); +} + +void EaxFrequencyShifterEffect::defer_right_direction( + unsigned long ulRightDirection) +{ + eax_d_.ulRightDirection = ulRightDirection; + eax_dirty_flags_.ulRightDirection = (eax_.ulRightDirection != eax_d_.ulRightDirection); +} + +void EaxFrequencyShifterEffect::defer_all( + const EAXFREQUENCYSHIFTERPROPERTIES& all) +{ + defer_frequency(all.flFrequency); + defer_left_direction(all.ulLeftDirection); + defer_right_direction(all.ulRightDirection); +} + +void EaxFrequencyShifterEffect::defer_frequency( + const EaxEaxCall& eax_call) +{ + const auto& frequency = + eax_call.get_value< + EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::flFrequency)>(); + + validate_frequency(frequency); + defer_frequency(frequency); +} + +void EaxFrequencyShifterEffect::defer_left_direction( + const EaxEaxCall& eax_call) +{ + const auto& left_direction = + eax_call.get_value< + EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::ulLeftDirection)>(); + + validate_left_direction(left_direction); + defer_left_direction(left_direction); +} + +void EaxFrequencyShifterEffect::defer_right_direction( + const EaxEaxCall& eax_call) +{ + const auto& right_direction = + eax_call.get_value< + EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::ulRightDirection)>(); + + validate_right_direction(right_direction); + defer_right_direction(right_direction); +} + +void EaxFrequencyShifterEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value< + EaxFrequencyShifterEffectException, const EAXFREQUENCYSHIFTERPROPERTIES>(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxFrequencyShifterEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxFrequencyShifterEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flFrequency) + { + set_efx_frequency(); + } + + if (eax_dirty_flags_.ulLeftDirection) + { + set_efx_left_direction(); + } + + if (eax_dirty_flags_.ulRightDirection) + { + set_efx_right_direction(); + } + + eax_dirty_flags_ = EaxFrequencyShifterEffectDirtyFlags{}; + + return true; +} + +void EaxFrequencyShifterEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXFREQUENCYSHIFTER_NONE: + break; + + case EAXFREQUENCYSHIFTER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXFREQUENCYSHIFTER_FREQUENCY: + defer_frequency(eax_call); + break; + + case EAXFREQUENCYSHIFTER_LEFTDIRECTION: + defer_left_direction(eax_call); + break; + + case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: + defer_right_direction(eax_call); + break; + + default: + throw EaxFrequencyShifterEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_frequency_shifter_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/modulator.cpp b/Engine/lib/openal-soft/al/effects/modulator.cpp index 89dcc209f..800b892de 100644 --- a/Engine/lib/openal-soft/al/effects/modulator.cpp +++ b/Engine/lib/openal-soft/al/effects/modulator.cpp @@ -1,12 +1,23 @@ #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" + +#ifdef ALSOFT_EAX +#include + +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX namespace { @@ -132,3 +143,340 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Modulator); const EffectProps ModulatorEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxRingModulatorEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxRingModulatorEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxRingModulatorEffectDirtyFlagsValue flFrequency : 1; + EaxRingModulatorEffectDirtyFlagsValue flHighPassCutOff : 1; + EaxRingModulatorEffectDirtyFlagsValue ulWaveform : 1; +}; // EaxPitchShifterEffectDirtyFlags + + +class EaxRingModulatorEffect final : + public EaxEffect +{ +public: + EaxRingModulatorEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXRINGMODULATORPROPERTIES eax_{}; + EAXRINGMODULATORPROPERTIES eax_d_{}; + EaxRingModulatorEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_frequency(); + void set_efx_high_pass_cutoff(); + void set_efx_waveform(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_frequency(float flFrequency); + void validate_high_pass_cutoff(float flHighPassCutOff); + void validate_waveform(unsigned long ulWaveform); + void validate_all(const EAXRINGMODULATORPROPERTIES& all); + + void defer_frequency(float flFrequency); + void defer_high_pass_cutoff(float flHighPassCutOff); + void defer_waveform(unsigned long ulWaveform); + void defer_all(const EAXRINGMODULATORPROPERTIES& all); + + void defer_frequency(const EaxEaxCall& eax_call); + void defer_high_pass_cutoff(const EaxEaxCall& eax_call); + void defer_waveform(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxRingModulatorEffect + + +class EaxRingModulatorEffectException : + public EaxException +{ +public: + explicit EaxRingModulatorEffectException( + const char* message) + : + EaxException{"EAX_RING_MODULATOR_EFFECT", message} + { + } +}; // EaxRingModulatorEffectException + + +EaxRingModulatorEffect::EaxRingModulatorEffect() + : EaxEffect{AL_EFFECT_RING_MODULATOR} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxRingModulatorEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxRingModulatorEffect::set_eax_defaults() +{ + eax_.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY; + eax_.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF; + eax_.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM; + + eax_d_ = eax_; +} + +void EaxRingModulatorEffect::set_efx_frequency() +{ + const auto frequency = clamp( + eax_.flFrequency, + AL_RING_MODULATOR_MIN_FREQUENCY, + AL_RING_MODULATOR_MAX_FREQUENCY); + + al_effect_props_.Modulator.Frequency = frequency; +} + +void EaxRingModulatorEffect::set_efx_high_pass_cutoff() +{ + const auto high_pass_cutoff = clamp( + eax_.flHighPassCutOff, + AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF, + AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF); + + al_effect_props_.Modulator.HighPassCutoff = high_pass_cutoff; +} + +void EaxRingModulatorEffect::set_efx_waveform() +{ + const auto waveform = clamp( + static_cast(eax_.ulWaveform), + AL_RING_MODULATOR_MIN_WAVEFORM, + AL_RING_MODULATOR_MAX_WAVEFORM); + + const auto efx_waveform = WaveformFromEmum(waveform); + assert(efx_waveform.has_value()); + al_effect_props_.Modulator.Waveform = *efx_waveform; +} + +void EaxRingModulatorEffect::set_efx_defaults() +{ + set_efx_frequency(); + set_efx_high_pass_cutoff(); + set_efx_waveform(); +} + +void EaxRingModulatorEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXRINGMODULATOR_NONE: + break; + + case EAXRINGMODULATOR_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXRINGMODULATOR_FREQUENCY: + eax_call.set_value(eax_.flFrequency); + break; + + case EAXRINGMODULATOR_HIGHPASSCUTOFF: + eax_call.set_value(eax_.flHighPassCutOff); + break; + + case EAXRINGMODULATOR_WAVEFORM: + eax_call.set_value(eax_.ulWaveform); + break; + + default: + throw EaxRingModulatorEffectException{"Unsupported property id."}; + } +} + +void EaxRingModulatorEffect::validate_frequency( + float flFrequency) +{ + eax_validate_range( + "Frequency", + flFrequency, + EAXRINGMODULATOR_MINFREQUENCY, + EAXRINGMODULATOR_MAXFREQUENCY); +} + +void EaxRingModulatorEffect::validate_high_pass_cutoff( + float flHighPassCutOff) +{ + eax_validate_range( + "High-Pass Cutoff", + flHighPassCutOff, + EAXRINGMODULATOR_MINHIGHPASSCUTOFF, + EAXRINGMODULATOR_MAXHIGHPASSCUTOFF); +} + +void EaxRingModulatorEffect::validate_waveform( + unsigned long ulWaveform) +{ + eax_validate_range( + "Waveform", + ulWaveform, + EAXRINGMODULATOR_MINWAVEFORM, + EAXRINGMODULATOR_MAXWAVEFORM); +} + +void EaxRingModulatorEffect::validate_all( + const EAXRINGMODULATORPROPERTIES& all) +{ + validate_frequency(all.flFrequency); + validate_high_pass_cutoff(all.flHighPassCutOff); + validate_waveform(all.ulWaveform); +} + +void EaxRingModulatorEffect::defer_frequency( + float flFrequency) +{ + eax_d_.flFrequency = flFrequency; + eax_dirty_flags_.flFrequency = (eax_.flFrequency != eax_d_.flFrequency); +} + +void EaxRingModulatorEffect::defer_high_pass_cutoff( + float flHighPassCutOff) +{ + eax_d_.flHighPassCutOff = flHighPassCutOff; + eax_dirty_flags_.flHighPassCutOff = (eax_.flHighPassCutOff != eax_d_.flHighPassCutOff); +} + +void EaxRingModulatorEffect::defer_waveform( + unsigned long ulWaveform) +{ + eax_d_.ulWaveform = ulWaveform; + eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); +} + +void EaxRingModulatorEffect::defer_all( + const EAXRINGMODULATORPROPERTIES& all) +{ + defer_frequency(all.flFrequency); + defer_high_pass_cutoff(all.flHighPassCutOff); + defer_waveform(all.ulWaveform); +} + +void EaxRingModulatorEffect::defer_frequency( + const EaxEaxCall& eax_call) +{ + const auto& frequency = + eax_call.get_value< + EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::flFrequency)>(); + + validate_frequency(frequency); + defer_frequency(frequency); +} + +void EaxRingModulatorEffect::defer_high_pass_cutoff( + const EaxEaxCall& eax_call) +{ + const auto& high_pass_cutoff = + eax_call.get_value< + EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::flHighPassCutOff)>(); + + validate_high_pass_cutoff(high_pass_cutoff); + defer_high_pass_cutoff(high_pass_cutoff); +} + +void EaxRingModulatorEffect::defer_waveform( + const EaxEaxCall& eax_call) +{ + const auto& waveform = + eax_call.get_value< + EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::ulWaveform)>(); + + validate_waveform(waveform); + defer_waveform(waveform); +} + +void EaxRingModulatorEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxRingModulatorEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxRingModulatorEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flFrequency) + { + set_efx_frequency(); + } + + if (eax_dirty_flags_.flHighPassCutOff) + { + set_efx_high_pass_cutoff(); + } + + if (eax_dirty_flags_.ulWaveform) + { + set_efx_waveform(); + } + + eax_dirty_flags_ = EaxRingModulatorEffectDirtyFlags{}; + + return true; +} + +void EaxRingModulatorEffect::set(const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXRINGMODULATOR_NONE: + break; + + case EAXRINGMODULATOR_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXRINGMODULATOR_FREQUENCY: + defer_frequency(eax_call); + break; + + case EAXRINGMODULATOR_HIGHPASSCUTOFF: + defer_high_pass_cutoff(eax_call); + break; + + case EAXRINGMODULATOR_WAVEFORM: + defer_waveform(eax_call); + break; + + default: + throw EaxRingModulatorEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_ring_modulator_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/null.cpp b/Engine/lib/openal-soft/al/effects/null.cpp index 0ac5278fa..a0eb22477 100644 --- a/Engine/lib/openal-soft/al/effects/null.cpp +++ b/Engine/lib/openal-soft/al/effects/null.cpp @@ -4,8 +4,12 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" + +#ifdef ALSOFT_EAX +#include "al/eax_exception.h" +#endif // ALSOFT_EAX namespace { @@ -91,3 +95,58 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Null); const EffectProps NullEffectProps{genDefaultProps()}; + + +#ifdef ALSOFT_EAX +namespace { + +class EaxNullEffect final : + public EaxEffect +{ +public: + EaxNullEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; +}; // EaxNullEffect + + +class EaxNullEffectException : + public EaxException +{ +public: + explicit EaxNullEffectException( + const char* message) + : + EaxException{"EAX_NULL_EFFECT", message} + { + } +}; // EaxNullEffectException + + +EaxNullEffect::EaxNullEffect() + : EaxEffect{AL_EFFECT_NULL} +{ +} + +void EaxNullEffect::dispatch(const EaxEaxCall& eax_call) +{ + if(eax_call.get_property_id() != 0) + throw EaxNullEffectException{"Unsupported property id."}; +} + +bool EaxNullEffect::apply_deferred() +{ + return false; +} + +} // namespace + +EaxEffectUPtr eax_create_eax_null_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/pshifter.cpp b/Engine/lib/openal-soft/al/effects/pshifter.cpp index e6b0b3b05..1b2dcff08 100644 --- a/Engine/lib/openal-soft/al/effects/pshifter.cpp +++ b/Engine/lib/openal-soft/al/effects/pshifter.cpp @@ -4,8 +4,15 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX namespace { @@ -82,3 +89,276 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Pshifter); const EffectProps PshifterEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxPitchShifterEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxPitchShifterEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxPitchShifterEffectDirtyFlagsValue lCoarseTune : 1; + EaxPitchShifterEffectDirtyFlagsValue lFineTune : 1; +}; // EaxPitchShifterEffectDirtyFlags + + +class EaxPitchShifterEffect final : + public EaxEffect +{ +public: + EaxPitchShifterEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXPITCHSHIFTERPROPERTIES eax_{}; + EAXPITCHSHIFTERPROPERTIES eax_d_{}; + EaxPitchShifterEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_coarse_tune(); + void set_efx_fine_tune(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_coarse_tune(long lCoarseTune); + void validate_fine_tune(long lFineTune); + void validate_all(const EAXPITCHSHIFTERPROPERTIES& all); + + void defer_coarse_tune(long lCoarseTune); + void defer_fine_tune(long lFineTune); + void defer_all(const EAXPITCHSHIFTERPROPERTIES& all); + + void defer_coarse_tune(const EaxEaxCall& eax_call); + void defer_fine_tune(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxPitchShifterEffect + + +class EaxPitchShifterEffectException : + public EaxException +{ +public: + explicit EaxPitchShifterEffectException( + const char* message) + : + EaxException{"EAX_PITCH_SHIFTER_EFFECT", message} + { + } +}; // EaxPitchShifterEffectException + + +EaxPitchShifterEffect::EaxPitchShifterEffect() + : EaxEffect{AL_EFFECT_PITCH_SHIFTER} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxPitchShifterEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxPitchShifterEffect::set_eax_defaults() +{ + eax_.lCoarseTune = EAXPITCHSHIFTER_DEFAULTCOARSETUNE; + eax_.lFineTune = EAXPITCHSHIFTER_DEFAULTFINETUNE; + + eax_d_ = eax_; +} + +void EaxPitchShifterEffect::set_efx_coarse_tune() +{ + const auto coarse_tune = clamp( + static_cast(eax_.lCoarseTune), + AL_PITCH_SHIFTER_MIN_COARSE_TUNE, + AL_PITCH_SHIFTER_MAX_COARSE_TUNE); + + al_effect_props_.Pshifter.CoarseTune = coarse_tune; +} + +void EaxPitchShifterEffect::set_efx_fine_tune() +{ + const auto fine_tune = clamp( + static_cast(eax_.lFineTune), + AL_PITCH_SHIFTER_MIN_FINE_TUNE, + AL_PITCH_SHIFTER_MAX_FINE_TUNE); + + al_effect_props_.Pshifter.FineTune = fine_tune; +} + +void EaxPitchShifterEffect::set_efx_defaults() +{ + set_efx_coarse_tune(); + set_efx_fine_tune(); +} + +void EaxPitchShifterEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXPITCHSHIFTER_NONE: + break; + + case EAXPITCHSHIFTER_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXPITCHSHIFTER_COARSETUNE: + eax_call.set_value(eax_.lCoarseTune); + break; + + case EAXPITCHSHIFTER_FINETUNE: + eax_call.set_value(eax_.lFineTune); + break; + + default: + throw EaxPitchShifterEffectException{"Unsupported property id."}; + } +} + +void EaxPitchShifterEffect::validate_coarse_tune( + long lCoarseTune) +{ + eax_validate_range( + "Coarse Tune", + lCoarseTune, + EAXPITCHSHIFTER_MINCOARSETUNE, + EAXPITCHSHIFTER_MAXCOARSETUNE); +} + +void EaxPitchShifterEffect::validate_fine_tune( + long lFineTune) +{ + eax_validate_range( + "Fine Tune", + lFineTune, + EAXPITCHSHIFTER_MINFINETUNE, + EAXPITCHSHIFTER_MAXFINETUNE); +} + +void EaxPitchShifterEffect::validate_all( + const EAXPITCHSHIFTERPROPERTIES& all) +{ + validate_coarse_tune(all.lCoarseTune); + validate_fine_tune(all.lFineTune); +} + +void EaxPitchShifterEffect::defer_coarse_tune( + long lCoarseTune) +{ + eax_d_.lCoarseTune = lCoarseTune; + eax_dirty_flags_.lCoarseTune = (eax_.lCoarseTune != eax_d_.lCoarseTune); +} + +void EaxPitchShifterEffect::defer_fine_tune( + long lFineTune) +{ + eax_d_.lFineTune = lFineTune; + eax_dirty_flags_.lFineTune = (eax_.lFineTune != eax_d_.lFineTune); +} + +void EaxPitchShifterEffect::defer_all( + const EAXPITCHSHIFTERPROPERTIES& all) +{ + defer_coarse_tune(all.lCoarseTune); + defer_fine_tune(all.lFineTune); +} + +void EaxPitchShifterEffect::defer_coarse_tune( + const EaxEaxCall& eax_call) +{ + const auto& coarse_tune = + eax_call.get_value(); + + validate_coarse_tune(coarse_tune); + defer_coarse_tune(coarse_tune); +} + +void EaxPitchShifterEffect::defer_fine_tune( + const EaxEaxCall& eax_call) +{ + const auto& fine_tune = + eax_call.get_value(); + + validate_fine_tune(fine_tune); + defer_fine_tune(fine_tune); +} + +void EaxPitchShifterEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxPitchShifterEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxPitchShifterEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.lCoarseTune) + { + set_efx_coarse_tune(); + } + + if (eax_dirty_flags_.lFineTune) + { + set_efx_fine_tune(); + } + + eax_dirty_flags_ = EaxPitchShifterEffectDirtyFlags{}; + + return true; +} + +void EaxPitchShifterEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXPITCHSHIFTER_NONE: + break; + + case EAXPITCHSHIFTER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXPITCHSHIFTER_COARSETUNE: + defer_coarse_tune(eax_call); + break; + + case EAXPITCHSHIFTER_FINETUNE: + defer_fine_tune(eax_call); + break; + + default: + throw EaxPitchShifterEffectException{"Unsupported property id."}; + } +} + +} // namespace + +EaxEffectUPtr eax_create_eax_pitch_shifter_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/reverb.cpp b/Engine/lib/openal-soft/al/effects/reverb.cpp index caa0c81eb..197ea5004 100644 --- a/Engine/lib/openal-soft/al/effects/reverb.cpp +++ b/Engine/lib/openal-soft/al/effects/reverb.cpp @@ -6,8 +6,16 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/effects/base.h" #include "effects.h" -#include "effects/base.h" + +#ifdef ALSOFT_EAX +#include +#include "alnumeric.h" +#include "AL/efx-presets.h" +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX namespace { @@ -554,3 +562,1981 @@ const EffectProps ReverbEffectProps{genDefaultProps()}; DEFINE_ALEFFECT_VTABLE(StdReverb); const EffectProps StdReverbEffectProps{genDefaultStdProps()}; + +#ifdef ALSOFT_EAX +namespace { + +extern const EFXEAXREVERBPROPERTIES eax_efx_reverb_presets[]; + +using EaxReverbEffectDirtyFlagsValue = std::uint_least32_t; + +struct EaxReverbEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxReverbEffectDirtyFlagsValue ulEnvironment : 1; + EaxReverbEffectDirtyFlagsValue flEnvironmentSize : 1; + EaxReverbEffectDirtyFlagsValue flEnvironmentDiffusion : 1; + EaxReverbEffectDirtyFlagsValue lRoom : 1; + EaxReverbEffectDirtyFlagsValue lRoomHF : 1; + EaxReverbEffectDirtyFlagsValue lRoomLF : 1; + EaxReverbEffectDirtyFlagsValue flDecayTime : 1; + EaxReverbEffectDirtyFlagsValue flDecayHFRatio : 1; + EaxReverbEffectDirtyFlagsValue flDecayLFRatio : 1; + EaxReverbEffectDirtyFlagsValue lReflections : 1; + EaxReverbEffectDirtyFlagsValue flReflectionsDelay : 1; + EaxReverbEffectDirtyFlagsValue vReflectionsPan : 1; + EaxReverbEffectDirtyFlagsValue lReverb : 1; + EaxReverbEffectDirtyFlagsValue flReverbDelay : 1; + EaxReverbEffectDirtyFlagsValue vReverbPan : 1; + EaxReverbEffectDirtyFlagsValue flEchoTime : 1; + EaxReverbEffectDirtyFlagsValue flEchoDepth : 1; + EaxReverbEffectDirtyFlagsValue flModulationTime : 1; + EaxReverbEffectDirtyFlagsValue flModulationDepth : 1; + EaxReverbEffectDirtyFlagsValue flAirAbsorptionHF : 1; + EaxReverbEffectDirtyFlagsValue flHFReference : 1; + EaxReverbEffectDirtyFlagsValue flLFReference : 1; + EaxReverbEffectDirtyFlagsValue flRoomRolloffFactor : 1; + EaxReverbEffectDirtyFlagsValue ulFlags : 1; +}; // EaxReverbEffectDirtyFlags + +struct Eax1ReverbEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxReverbEffectDirtyFlagsValue ulEnvironment : 1; + EaxReverbEffectDirtyFlagsValue flVolume : 1; + EaxReverbEffectDirtyFlagsValue flDecayTime : 1; + EaxReverbEffectDirtyFlagsValue flDamping : 1; +}; // Eax1ReverbEffectDirtyFlags + +class EaxReverbEffect final : + public EaxEffect +{ +public: + EaxReverbEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAX_REVERBPROPERTIES eax1_{}; + EAX_REVERBPROPERTIES eax1_d_{}; + Eax1ReverbEffectDirtyFlags eax1_dirty_flags_{}; + EAXREVERBPROPERTIES eax_{}; + EAXREVERBPROPERTIES eax_d_{}; + EaxReverbEffectDirtyFlags eax_dirty_flags_{}; + + [[noreturn]] static void eax_fail(const char* message); + + void set_eax_defaults(); + + void set_efx_density_from_environment_size(); + void set_efx_diffusion(); + void set_efx_gain(); + void set_efx_gain_hf(); + void set_efx_gain_lf(); + void set_efx_decay_time(); + void set_efx_decay_hf_ratio(); + void set_efx_decay_lf_ratio(); + void set_efx_reflections_gain(); + void set_efx_reflections_delay(); + void set_efx_reflections_pan(); + void set_efx_late_reverb_gain(); + void set_efx_late_reverb_delay(); + void set_efx_late_reverb_pan(); + void set_efx_echo_time(); + void set_efx_echo_depth(); + void set_efx_modulation_time(); + void set_efx_modulation_depth(); + void set_efx_air_absorption_gain_hf(); + void set_efx_hf_reference(); + void set_efx_lf_reference(); + void set_efx_room_rolloff_factor(); + void set_efx_flags(); + void set_efx_defaults(); + + void v1_get(const EaxEaxCall& eax_call) const; + + void get_all(const EaxEaxCall& eax_call) const; + + void get(const EaxEaxCall& eax_call) const; + + static void v1_validate_environment(unsigned long environment); + static void v1_validate_volume(float volume); + static void v1_validate_decay_time(float decay_time); + static void v1_validate_damping(float damping); + static void v1_validate_all(const EAX_REVERBPROPERTIES& all); + + void v1_defer_environment(unsigned long environment); + void v1_defer_volume(float volume); + void v1_defer_decay_time(float decay_time); + void v1_defer_damping(float damping); + void v1_defer_all(const EAX_REVERBPROPERTIES& all); + + void v1_defer_environment(const EaxEaxCall& eax_call); + void v1_defer_volume(const EaxEaxCall& eax_call); + void v1_defer_decay_time(const EaxEaxCall& eax_call); + void v1_defer_damping(const EaxEaxCall& eax_call); + void v1_defer_all(const EaxEaxCall& eax_call); + void v1_defer(const EaxEaxCall& eax_call); + + void v1_set_efx(); + + static void validate_environment(unsigned long ulEnvironment, int version, bool is_standalone); + static void validate_environment_size(float flEnvironmentSize); + static void validate_environment_diffusion(float flEnvironmentDiffusion); + static void validate_room(long lRoom); + static void validate_room_hf(long lRoomHF); + static void validate_room_lf(long lRoomLF); + static void validate_decay_time(float flDecayTime); + static void validate_decay_hf_ratio(float flDecayHFRatio); + static void validate_decay_lf_ratio(float flDecayLFRatio); + static void validate_reflections(long lReflections); + static void validate_reflections_delay(float flReflectionsDelay); + static void validate_reflections_pan(const EAXVECTOR& vReflectionsPan); + static void validate_reverb(long lReverb); + static void validate_reverb_delay(float flReverbDelay); + static void validate_reverb_pan(const EAXVECTOR& vReverbPan); + static void validate_echo_time(float flEchoTime); + static void validate_echo_depth(float flEchoDepth); + static void validate_modulation_time(float flModulationTime); + static void validate_modulation_depth(float flModulationDepth); + static void validate_air_absorbtion_hf(float air_absorbtion_hf); + static void validate_hf_reference(float flHFReference); + static void validate_lf_reference(float flLFReference); + static void validate_room_rolloff_factor(float flRoomRolloffFactor); + static void validate_flags(unsigned long ulFlags); + static void validate_all(const EAX20LISTENERPROPERTIES& all, int version); + static void validate_all(const EAXREVERBPROPERTIES& all, int version); + + void defer_environment(unsigned long ulEnvironment); + void defer_environment_size(float flEnvironmentSize); + void defer_environment_diffusion(float flEnvironmentDiffusion); + void defer_room(long lRoom); + void defer_room_hf(long lRoomHF); + void defer_room_lf(long lRoomLF); + void defer_decay_time(float flDecayTime); + void defer_decay_hf_ratio(float flDecayHFRatio); + void defer_decay_lf_ratio(float flDecayLFRatio); + void defer_reflections(long lReflections); + void defer_reflections_delay(float flReflectionsDelay); + void defer_reflections_pan(const EAXVECTOR& vReflectionsPan); + void defer_reverb(long lReverb); + void defer_reverb_delay(float flReverbDelay); + void defer_reverb_pan(const EAXVECTOR& vReverbPan); + void defer_echo_time(float flEchoTime); + void defer_echo_depth(float flEchoDepth); + void defer_modulation_time(float flModulationTime); + void defer_modulation_depth(float flModulationDepth); + void defer_air_absorbtion_hf(float flAirAbsorptionHF); + void defer_hf_reference(float flHFReference); + void defer_lf_reference(float flLFReference); + void defer_room_rolloff_factor(float flRoomRolloffFactor); + void defer_flags(unsigned long ulFlags); + void defer_all(const EAX20LISTENERPROPERTIES& all); + void defer_all(const EAXREVERBPROPERTIES& all); + + void defer_environment(const EaxEaxCall& eax_call); + void defer_environment_size(const EaxEaxCall& eax_call); + void defer_environment_diffusion(const EaxEaxCall& eax_call); + void defer_room(const EaxEaxCall& eax_call); + void defer_room_hf(const EaxEaxCall& eax_call); + void defer_room_lf(const EaxEaxCall& eax_call); + void defer_decay_time(const EaxEaxCall& eax_call); + void defer_decay_hf_ratio(const EaxEaxCall& eax_call); + void defer_decay_lf_ratio(const EaxEaxCall& eax_call); + void defer_reflections(const EaxEaxCall& eax_call); + void defer_reflections_delay(const EaxEaxCall& eax_call); + void defer_reflections_pan(const EaxEaxCall& eax_call); + void defer_reverb(const EaxEaxCall& eax_call); + void defer_reverb_delay(const EaxEaxCall& eax_call); + void defer_reverb_pan(const EaxEaxCall& eax_call); + void defer_echo_time(const EaxEaxCall& eax_call); + void defer_echo_depth(const EaxEaxCall& eax_call); + void defer_modulation_time(const EaxEaxCall& eax_call); + void defer_modulation_depth(const EaxEaxCall& eax_call); + void defer_air_absorbtion_hf(const EaxEaxCall& eax_call); + void defer_hf_reference(const EaxEaxCall& eax_call); + void defer_lf_reference(const EaxEaxCall& eax_call); + void defer_room_rolloff_factor(const EaxEaxCall& eax_call); + void defer_flags(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxReverbEffect + + +class EaxReverbEffectException : + public EaxException +{ +public: + explicit EaxReverbEffectException( + const char* message) + : + EaxException{"EAX_REVERB_EFFECT", message} + { + } +}; // EaxReverbEffectException + + +EaxReverbEffect::EaxReverbEffect() + : EaxEffect{AL_EFFECT_EAXREVERB} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxReverbEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +[[noreturn]] void EaxReverbEffect::eax_fail(const char* message) +{ + throw EaxReverbEffectException{message}; +} + +void EaxReverbEffect::set_eax_defaults() +{ + eax1_ = EAX1REVERB_PRESETS[EAX_ENVIRONMENT_GENERIC]; + eax1_d_ = eax1_; + eax_ = EAXREVERB_PRESETS[EAX_ENVIRONMENT_GENERIC]; + /* HACK: EAX2 has a default room volume of -10,000dB (silence), although + * newer versions use -1,000dB. What should be happening is properties for + * each EAX version is tracked separately, with the last version used for + * the properties to apply (presumably v2 or nothing being the default). + */ + eax_.lRoom = EAXREVERB_MINROOM; + eax_d_ = eax_; +} + +void EaxReverbEffect::set_efx_density_from_environment_size() +{ + const auto eax_environment_size = eax_.flEnvironmentSize; + + const auto efx_density = clamp( + (eax_environment_size * eax_environment_size * eax_environment_size) / 16.0F, + AL_EAXREVERB_MIN_DENSITY, + AL_EAXREVERB_MAX_DENSITY); + + al_effect_props_.Reverb.Density = efx_density; +} + +void EaxReverbEffect::set_efx_diffusion() +{ + const auto efx_diffusion = clamp( + eax_.flEnvironmentDiffusion, + AL_EAXREVERB_MIN_DIFFUSION, + AL_EAXREVERB_MAX_DIFFUSION); + + al_effect_props_.Reverb.Diffusion = efx_diffusion; +} + +void EaxReverbEffect::set_efx_gain() +{ + const auto efx_gain = clamp( + level_mb_to_gain(static_cast(eax_.lRoom)), + AL_EAXREVERB_MIN_GAIN, + AL_EAXREVERB_MAX_GAIN); + + al_effect_props_.Reverb.Gain = efx_gain; +} + +void EaxReverbEffect::set_efx_gain_hf() +{ + const auto efx_gain_hf = clamp( + level_mb_to_gain(static_cast(eax_.lRoomHF)), + AL_EAXREVERB_MIN_GAINHF, + AL_EAXREVERB_MAX_GAINHF); + + al_effect_props_.Reverb.GainHF = efx_gain_hf; +} + +void EaxReverbEffect::set_efx_gain_lf() +{ + const auto efx_gain_lf = clamp( + level_mb_to_gain(static_cast(eax_.lRoomLF)), + AL_EAXREVERB_MIN_GAINLF, + AL_EAXREVERB_MAX_GAINLF); + + al_effect_props_.Reverb.GainLF = efx_gain_lf; +} + +void EaxReverbEffect::set_efx_decay_time() +{ + const auto efx_decay_time = clamp( + eax_.flDecayTime, + AL_EAXREVERB_MIN_DECAY_TIME, + AL_EAXREVERB_MAX_DECAY_TIME); + + al_effect_props_.Reverb.DecayTime = efx_decay_time; +} + +void EaxReverbEffect::set_efx_decay_hf_ratio() +{ + const auto efx_decay_hf_ratio = clamp( + eax_.flDecayHFRatio, + AL_EAXREVERB_MIN_DECAY_HFRATIO, + AL_EAXREVERB_MAX_DECAY_HFRATIO); + + al_effect_props_.Reverb.DecayHFRatio = efx_decay_hf_ratio; +} + +void EaxReverbEffect::set_efx_decay_lf_ratio() +{ + const auto efx_decay_lf_ratio = clamp( + eax_.flDecayLFRatio, + AL_EAXREVERB_MIN_DECAY_LFRATIO, + AL_EAXREVERB_MAX_DECAY_LFRATIO); + + al_effect_props_.Reverb.DecayLFRatio = efx_decay_lf_ratio; +} + +void EaxReverbEffect::set_efx_reflections_gain() +{ + const auto efx_reflections_gain = clamp( + level_mb_to_gain(static_cast(eax_.lReflections)), + AL_EAXREVERB_MIN_REFLECTIONS_GAIN, + AL_EAXREVERB_MAX_REFLECTIONS_GAIN); + + al_effect_props_.Reverb.ReflectionsGain = efx_reflections_gain; +} + +void EaxReverbEffect::set_efx_reflections_delay() +{ + const auto efx_reflections_delay = clamp( + eax_.flReflectionsDelay, + AL_EAXREVERB_MIN_REFLECTIONS_DELAY, + AL_EAXREVERB_MAX_REFLECTIONS_DELAY); + + al_effect_props_.Reverb.ReflectionsDelay = efx_reflections_delay; +} + +void EaxReverbEffect::set_efx_reflections_pan() +{ + al_effect_props_.Reverb.ReflectionsPan[0] = eax_.vReflectionsPan.x; + al_effect_props_.Reverb.ReflectionsPan[1] = eax_.vReflectionsPan.y; + al_effect_props_.Reverb.ReflectionsPan[2] = eax_.vReflectionsPan.z; +} + +void EaxReverbEffect::set_efx_late_reverb_gain() +{ + const auto efx_late_reverb_gain = clamp( + level_mb_to_gain(static_cast(eax_.lReverb)), + AL_EAXREVERB_MIN_LATE_REVERB_GAIN, + AL_EAXREVERB_MAX_LATE_REVERB_GAIN); + + al_effect_props_.Reverb.LateReverbGain = efx_late_reverb_gain; +} + +void EaxReverbEffect::set_efx_late_reverb_delay() +{ + const auto efx_late_reverb_delay = clamp( + eax_.flReverbDelay, + AL_EAXREVERB_MIN_LATE_REVERB_DELAY, + AL_EAXREVERB_MAX_LATE_REVERB_DELAY); + + al_effect_props_.Reverb.LateReverbDelay = efx_late_reverb_delay; +} + +void EaxReverbEffect::set_efx_late_reverb_pan() +{ + al_effect_props_.Reverb.LateReverbPan[0] = eax_.vReverbPan.x; + al_effect_props_.Reverb.LateReverbPan[1] = eax_.vReverbPan.y; + al_effect_props_.Reverb.LateReverbPan[2] = eax_.vReverbPan.z; +} + +void EaxReverbEffect::set_efx_echo_time() +{ + const auto efx_echo_time = clamp( + eax_.flEchoTime, + AL_EAXREVERB_MIN_ECHO_TIME, + AL_EAXREVERB_MAX_ECHO_TIME); + + al_effect_props_.Reverb.EchoTime = efx_echo_time; +} + +void EaxReverbEffect::set_efx_echo_depth() +{ + const auto efx_echo_depth = clamp( + eax_.flEchoDepth, + AL_EAXREVERB_MIN_ECHO_DEPTH, + AL_EAXREVERB_MAX_ECHO_DEPTH); + + al_effect_props_.Reverb.EchoDepth = efx_echo_depth; +} + +void EaxReverbEffect::set_efx_modulation_time() +{ + const auto efx_modulation_time = clamp( + eax_.flModulationTime, + AL_EAXREVERB_MIN_MODULATION_TIME, + AL_EAXREVERB_MAX_MODULATION_TIME); + + al_effect_props_.Reverb.ModulationTime = efx_modulation_time; +} + +void EaxReverbEffect::set_efx_modulation_depth() +{ + const auto efx_modulation_depth = clamp( + eax_.flModulationDepth, + AL_EAXREVERB_MIN_MODULATION_DEPTH, + AL_EAXREVERB_MAX_MODULATION_DEPTH); + + al_effect_props_.Reverb.ModulationDepth = efx_modulation_depth; +} + +void EaxReverbEffect::set_efx_air_absorption_gain_hf() +{ + const auto efx_air_absorption_hf = clamp( + level_mb_to_gain(eax_.flAirAbsorptionHF), + AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF, + AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF); + + al_effect_props_.Reverb.AirAbsorptionGainHF = efx_air_absorption_hf; +} + +void EaxReverbEffect::set_efx_hf_reference() +{ + const auto efx_hf_reference = clamp( + eax_.flHFReference, + AL_EAXREVERB_MIN_HFREFERENCE, + AL_EAXREVERB_MAX_HFREFERENCE); + + al_effect_props_.Reverb.HFReference = efx_hf_reference; +} + +void EaxReverbEffect::set_efx_lf_reference() +{ + const auto efx_lf_reference = clamp( + eax_.flLFReference, + AL_EAXREVERB_MIN_LFREFERENCE, + AL_EAXREVERB_MAX_LFREFERENCE); + + al_effect_props_.Reverb.LFReference = efx_lf_reference; +} + +void EaxReverbEffect::set_efx_room_rolloff_factor() +{ + const auto efx_room_rolloff_factor = clamp( + eax_.flRoomRolloffFactor, + AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR, + AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR); + + al_effect_props_.Reverb.RoomRolloffFactor = efx_room_rolloff_factor; +} + +void EaxReverbEffect::set_efx_flags() +{ + al_effect_props_.Reverb.DecayHFLimit = ((eax_.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0); +} + +void EaxReverbEffect::set_efx_defaults() +{ + set_efx_density_from_environment_size(); + set_efx_diffusion(); + set_efx_gain(); + set_efx_gain_hf(); + set_efx_gain_lf(); + set_efx_decay_time(); + set_efx_decay_hf_ratio(); + set_efx_decay_lf_ratio(); + set_efx_reflections_gain(); + set_efx_reflections_delay(); + set_efx_reflections_pan(); + set_efx_late_reverb_gain(); + set_efx_late_reverb_delay(); + set_efx_late_reverb_pan(); + set_efx_echo_time(); + set_efx_echo_depth(); + set_efx_modulation_time(); + set_efx_modulation_depth(); + set_efx_air_absorption_gain_hf(); + set_efx_hf_reference(); + set_efx_lf_reference(); + set_efx_room_rolloff_factor(); + set_efx_flags(); +} + +void EaxReverbEffect::v1_get(const EaxEaxCall& eax_call) const +{ + switch(eax_call.get_property_id()) + { + case DSPROPERTY_EAX_ALL: + eax_call.set_value(eax1_); + break; + + case DSPROPERTY_EAX_ENVIRONMENT: + eax_call.set_value(eax1_.environment); + break; + + case DSPROPERTY_EAX_VOLUME: + eax_call.set_value(eax1_.fVolume); + break; + + case DSPROPERTY_EAX_DECAYTIME: + eax_call.set_value(eax1_.fDecayTime_sec); + break; + + case DSPROPERTY_EAX_DAMPING: + eax_call.set_value(eax1_.fDamping); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +void EaxReverbEffect::get_all( + const EaxEaxCall& eax_call) const +{ + if (eax_call.get_version() == 2) + { + auto& eax_reverb = eax_call.get_value(); + eax_reverb.lRoom = eax_.lRoom; + eax_reverb.lRoomHF = eax_.lRoomHF; + eax_reverb.flRoomRolloffFactor = eax_.flRoomRolloffFactor; + eax_reverb.flDecayTime = eax_.flDecayTime; + eax_reverb.flDecayHFRatio = eax_.flDecayHFRatio; + eax_reverb.lReflections = eax_.lReflections; + eax_reverb.flReflectionsDelay = eax_.flReflectionsDelay; + eax_reverb.lReverb = eax_.lReverb; + eax_reverb.flReverbDelay = eax_.flReverbDelay; + eax_reverb.dwEnvironment = eax_.ulEnvironment; + eax_reverb.flEnvironmentSize = eax_.flEnvironmentSize; + eax_reverb.flEnvironmentDiffusion = eax_.flEnvironmentDiffusion; + eax_reverb.flAirAbsorptionHF = eax_.flAirAbsorptionHF; + eax_reverb.dwFlags = eax_.ulFlags; + } + else + { + eax_call.set_value(eax_); + } +} + +void EaxReverbEffect::get(const EaxEaxCall& eax_call) const +{ + if(eax_call.get_version() == 1) + v1_get(eax_call); + else switch(eax_call.get_property_id()) + { + case EAXREVERB_NONE: + break; + + case EAXREVERB_ALLPARAMETERS: + get_all(eax_call); + break; + + case EAXREVERB_ENVIRONMENT: + eax_call.set_value(eax_.ulEnvironment); + break; + + case EAXREVERB_ENVIRONMENTSIZE: + eax_call.set_value(eax_.flEnvironmentSize); + break; + + case EAXREVERB_ENVIRONMENTDIFFUSION: + eax_call.set_value(eax_.flEnvironmentDiffusion); + break; + + case EAXREVERB_ROOM: + eax_call.set_value(eax_.lRoom); + break; + + case EAXREVERB_ROOMHF: + eax_call.set_value(eax_.lRoomHF); + break; + + case EAXREVERB_ROOMLF: + eax_call.set_value(eax_.lRoomLF); + break; + + case EAXREVERB_DECAYTIME: + eax_call.set_value(eax_.flDecayTime); + break; + + case EAXREVERB_DECAYHFRATIO: + eax_call.set_value(eax_.flDecayHFRatio); + break; + + case EAXREVERB_DECAYLFRATIO: + eax_call.set_value(eax_.flDecayLFRatio); + break; + + case EAXREVERB_REFLECTIONS: + eax_call.set_value(eax_.lReflections); + break; + + case EAXREVERB_REFLECTIONSDELAY: + eax_call.set_value(eax_.flReflectionsDelay); + break; + + case EAXREVERB_REFLECTIONSPAN: + eax_call.set_value(eax_.vReflectionsPan); + break; + + case EAXREVERB_REVERB: + eax_call.set_value(eax_.lReverb); + break; + + case EAXREVERB_REVERBDELAY: + eax_call.set_value(eax_.flReverbDelay); + break; + + case EAXREVERB_REVERBPAN: + eax_call.set_value(eax_.vReverbPan); + break; + + case EAXREVERB_ECHOTIME: + eax_call.set_value(eax_.flEchoTime); + break; + + case EAXREVERB_ECHODEPTH: + eax_call.set_value(eax_.flEchoDepth); + break; + + case EAXREVERB_MODULATIONTIME: + eax_call.set_value(eax_.flModulationTime); + break; + + case EAXREVERB_MODULATIONDEPTH: + eax_call.set_value(eax_.flModulationDepth); + break; + + case EAXREVERB_AIRABSORPTIONHF: + eax_call.set_value(eax_.flAirAbsorptionHF); + break; + + case EAXREVERB_HFREFERENCE: + eax_call.set_value(eax_.flHFReference); + break; + + case EAXREVERB_LFREFERENCE: + eax_call.set_value(eax_.flLFReference); + break; + + case EAXREVERB_ROOMROLLOFFFACTOR: + eax_call.set_value(eax_.flRoomRolloffFactor); + break; + + case EAXREVERB_FLAGS: + eax_call.set_value(eax_.ulFlags); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +void EaxReverbEffect::v1_validate_environment(unsigned long environment) +{ + validate_environment(environment, 1, true); +} + +void EaxReverbEffect::v1_validate_volume(float volume) +{ + eax_validate_range("Volume", volume, EAX1REVERB_MINVOLUME, EAX1REVERB_MAXVOLUME); +} + +void EaxReverbEffect::v1_validate_decay_time(float decay_time) +{ + validate_decay_time(decay_time); +} + +void EaxReverbEffect::v1_validate_damping(float damping) +{ + eax_validate_range("Damping", damping, EAX1REVERB_MINDAMPING, EAX1REVERB_MAXDAMPING); +} + +void EaxReverbEffect::v1_validate_all(const EAX_REVERBPROPERTIES& all) +{ + v1_validate_environment(all.environment); + v1_validate_volume(all.fVolume); + v1_validate_decay_time(all.fDecayTime_sec); + v1_validate_damping(all.fDamping); +} + +void EaxReverbEffect::validate_environment( + unsigned long ulEnvironment, + int version, + bool is_standalone) +{ + eax_validate_range( + "Environment", + ulEnvironment, + EAXREVERB_MINENVIRONMENT, + (version <= 2 || is_standalone) ? EAX1REVERB_MAXENVIRONMENT : EAX30REVERB_MAXENVIRONMENT); +} + +void EaxReverbEffect::validate_environment_size( + float flEnvironmentSize) +{ + eax_validate_range( + "Environment Size", + flEnvironmentSize, + EAXREVERB_MINENVIRONMENTSIZE, + EAXREVERB_MAXENVIRONMENTSIZE); +} + +void EaxReverbEffect::validate_environment_diffusion( + float flEnvironmentDiffusion) +{ + eax_validate_range( + "Environment Diffusion", + flEnvironmentDiffusion, + EAXREVERB_MINENVIRONMENTDIFFUSION, + EAXREVERB_MAXENVIRONMENTDIFFUSION); +} + +void EaxReverbEffect::validate_room( + long lRoom) +{ + eax_validate_range( + "Room", + lRoom, + EAXREVERB_MINROOM, + EAXREVERB_MAXROOM); +} + +void EaxReverbEffect::validate_room_hf( + long lRoomHF) +{ + eax_validate_range( + "Room HF", + lRoomHF, + EAXREVERB_MINROOMHF, + EAXREVERB_MAXROOMHF); +} + +void EaxReverbEffect::validate_room_lf( + long lRoomLF) +{ + eax_validate_range( + "Room LF", + lRoomLF, + EAXREVERB_MINROOMLF, + EAXREVERB_MAXROOMLF); +} + +void EaxReverbEffect::validate_decay_time( + float flDecayTime) +{ + eax_validate_range( + "Decay Time", + flDecayTime, + EAXREVERB_MINDECAYTIME, + EAXREVERB_MAXDECAYTIME); +} + +void EaxReverbEffect::validate_decay_hf_ratio( + float flDecayHFRatio) +{ + eax_validate_range( + "Decay HF Ratio", + flDecayHFRatio, + EAXREVERB_MINDECAYHFRATIO, + EAXREVERB_MAXDECAYHFRATIO); +} + +void EaxReverbEffect::validate_decay_lf_ratio( + float flDecayLFRatio) +{ + eax_validate_range( + "Decay LF Ratio", + flDecayLFRatio, + EAXREVERB_MINDECAYLFRATIO, + EAXREVERB_MAXDECAYLFRATIO); +} + +void EaxReverbEffect::validate_reflections( + long lReflections) +{ + eax_validate_range( + "Reflections", + lReflections, + EAXREVERB_MINREFLECTIONS, + EAXREVERB_MAXREFLECTIONS); +} + +void EaxReverbEffect::validate_reflections_delay( + float flReflectionsDelay) +{ + eax_validate_range( + "Reflections Delay", + flReflectionsDelay, + EAXREVERB_MINREFLECTIONSDELAY, + EAXREVERB_MAXREFLECTIONSDELAY); +} + +void EaxReverbEffect::validate_reflections_pan( + const EAXVECTOR& vReflectionsPan) +{ + std::ignore = vReflectionsPan; +} + +void EaxReverbEffect::validate_reverb( + long lReverb) +{ + eax_validate_range( + "Reverb", + lReverb, + EAXREVERB_MINREVERB, + EAXREVERB_MAXREVERB); +} + +void EaxReverbEffect::validate_reverb_delay( + float flReverbDelay) +{ + eax_validate_range( + "Reverb Delay", + flReverbDelay, + EAXREVERB_MINREVERBDELAY, + EAXREVERB_MAXREVERBDELAY); +} + +void EaxReverbEffect::validate_reverb_pan( + const EAXVECTOR& vReverbPan) +{ + std::ignore = vReverbPan; +} + +void EaxReverbEffect::validate_echo_time( + float flEchoTime) +{ + eax_validate_range( + "Echo Time", + flEchoTime, + EAXREVERB_MINECHOTIME, + EAXREVERB_MAXECHOTIME); +} + +void EaxReverbEffect::validate_echo_depth( + float flEchoDepth) +{ + eax_validate_range( + "Echo Depth", + flEchoDepth, + EAXREVERB_MINECHODEPTH, + EAXREVERB_MAXECHODEPTH); +} + +void EaxReverbEffect::validate_modulation_time( + float flModulationTime) +{ + eax_validate_range( + "Modulation Time", + flModulationTime, + EAXREVERB_MINMODULATIONTIME, + EAXREVERB_MAXMODULATIONTIME); +} + +void EaxReverbEffect::validate_modulation_depth( + float flModulationDepth) +{ + eax_validate_range( + "Modulation Depth", + flModulationDepth, + EAXREVERB_MINMODULATIONDEPTH, + EAXREVERB_MAXMODULATIONDEPTH); +} + +void EaxReverbEffect::validate_air_absorbtion_hf( + float air_absorbtion_hf) +{ + eax_validate_range( + "Air Absorbtion HF", + air_absorbtion_hf, + EAXREVERB_MINAIRABSORPTIONHF, + EAXREVERB_MAXAIRABSORPTIONHF); +} + +void EaxReverbEffect::validate_hf_reference( + float flHFReference) +{ + eax_validate_range( + "HF Reference", + flHFReference, + EAXREVERB_MINHFREFERENCE, + EAXREVERB_MAXHFREFERENCE); +} + +void EaxReverbEffect::validate_lf_reference( + float flLFReference) +{ + eax_validate_range( + "LF Reference", + flLFReference, + EAXREVERB_MINLFREFERENCE, + EAXREVERB_MAXLFREFERENCE); +} + +void EaxReverbEffect::validate_room_rolloff_factor( + float flRoomRolloffFactor) +{ + eax_validate_range( + "Room Rolloff Factor", + flRoomRolloffFactor, + EAXREVERB_MINROOMROLLOFFFACTOR, + EAXREVERB_MAXROOMROLLOFFFACTOR); +} + +void EaxReverbEffect::validate_flags( + unsigned long ulFlags) +{ + eax_validate_range( + "Flags", + ulFlags, + 0UL, + ~EAXREVERBFLAGS_RESERVED); +} + +void EaxReverbEffect::validate_all( + const EAX20LISTENERPROPERTIES& listener, + int version) +{ + validate_room(listener.lRoom); + validate_room_hf(listener.lRoomHF); + validate_room_rolloff_factor(listener.flRoomRolloffFactor); + validate_decay_time(listener.flDecayTime); + validate_decay_hf_ratio(listener.flDecayHFRatio); + validate_reflections(listener.lReflections); + validate_reflections_delay(listener.flReflectionsDelay); + validate_reverb(listener.lReverb); + validate_reverb_delay(listener.flReverbDelay); + validate_environment(listener.dwEnvironment, version, false); + validate_environment_size(listener.flEnvironmentSize); + validate_environment_diffusion(listener.flEnvironmentDiffusion); + validate_air_absorbtion_hf(listener.flAirAbsorptionHF); + validate_flags(listener.dwFlags); +} + +void EaxReverbEffect::validate_all( + const EAXREVERBPROPERTIES& lReverb, + int version) +{ + validate_environment(lReverb.ulEnvironment, version, false); + validate_environment_size(lReverb.flEnvironmentSize); + validate_environment_diffusion(lReverb.flEnvironmentDiffusion); + validate_room(lReverb.lRoom); + validate_room_hf(lReverb.lRoomHF); + validate_room_lf(lReverb.lRoomLF); + validate_decay_time(lReverb.flDecayTime); + validate_decay_hf_ratio(lReverb.flDecayHFRatio); + validate_decay_lf_ratio(lReverb.flDecayLFRatio); + validate_reflections(lReverb.lReflections); + validate_reflections_delay(lReverb.flReflectionsDelay); + validate_reverb(lReverb.lReverb); + validate_reverb_delay(lReverb.flReverbDelay); + validate_echo_time(lReverb.flEchoTime); + validate_echo_depth(lReverb.flEchoDepth); + validate_modulation_time(lReverb.flModulationTime); + validate_modulation_depth(lReverb.flModulationDepth); + validate_air_absorbtion_hf(lReverb.flAirAbsorptionHF); + validate_hf_reference(lReverb.flHFReference); + validate_lf_reference(lReverb.flLFReference); + validate_room_rolloff_factor(lReverb.flRoomRolloffFactor); + validate_flags(lReverb.ulFlags); +} + +void EaxReverbEffect::v1_defer_environment(unsigned long environment) +{ + eax1_d_ = EAX1REVERB_PRESETS[environment]; + eax1_dirty_flags_.ulEnvironment = true; +} + +void EaxReverbEffect::v1_defer_volume(float volume) +{ + eax1_d_.fVolume = volume; + eax1_dirty_flags_.flVolume = (eax1_.fVolume != eax1_d_.fVolume); +} + +void EaxReverbEffect::v1_defer_decay_time(float decay_time) +{ + eax1_d_.fDecayTime_sec = decay_time; + eax1_dirty_flags_.flDecayTime = (eax1_.fDecayTime_sec != eax1_d_.fDecayTime_sec); +} + +void EaxReverbEffect::v1_defer_damping(float damping) +{ + eax1_d_.fDamping = damping; + eax1_dirty_flags_.flDamping = (eax1_.fDamping != eax1_d_.fDamping); +} + +void EaxReverbEffect::v1_defer_all(const EAX_REVERBPROPERTIES& lReverb) +{ + v1_defer_environment(lReverb.environment); + v1_defer_volume(lReverb.fVolume); + v1_defer_decay_time(lReverb.fDecayTime_sec); + v1_defer_damping(lReverb.fDamping); +} + + +void EaxReverbEffect::v1_set_efx() +{ + auto efx_props = eax_efx_reverb_presets[eax1_.environment]; + efx_props.flGain = eax1_.fVolume; + efx_props.flDecayTime = eax1_.fDecayTime_sec; + efx_props.flDecayHFRatio = clamp(eax1_.fDamping, AL_EAXREVERB_MIN_DECAY_HFRATIO, AL_EAXREVERB_MAX_DECAY_HFRATIO); + + al_effect_props_.Reverb.Density = efx_props.flDensity; + al_effect_props_.Reverb.Diffusion = efx_props.flDiffusion; + al_effect_props_.Reverb.Gain = efx_props.flGain; + al_effect_props_.Reverb.GainHF = efx_props.flGainHF; + al_effect_props_.Reverb.GainLF = efx_props.flGainLF; + al_effect_props_.Reverb.DecayTime = efx_props.flDecayTime; + al_effect_props_.Reverb.DecayHFRatio = efx_props.flDecayHFRatio; + al_effect_props_.Reverb.DecayLFRatio = efx_props.flDecayLFRatio; + al_effect_props_.Reverb.ReflectionsGain = efx_props.flReflectionsGain; + al_effect_props_.Reverb.ReflectionsDelay = efx_props.flReflectionsDelay; + al_effect_props_.Reverb.ReflectionsPan[0] = efx_props.flReflectionsPan[0]; + al_effect_props_.Reverb.ReflectionsPan[1] = efx_props.flReflectionsPan[1]; + al_effect_props_.Reverb.ReflectionsPan[2] = efx_props.flReflectionsPan[2]; + al_effect_props_.Reverb.LateReverbGain = efx_props.flLateReverbGain; + al_effect_props_.Reverb.LateReverbDelay = efx_props.flLateReverbDelay; + al_effect_props_.Reverb.LateReverbPan[0] = efx_props.flLateReverbPan[0]; + al_effect_props_.Reverb.LateReverbPan[1] = efx_props.flLateReverbPan[1]; + al_effect_props_.Reverb.LateReverbPan[2] = efx_props.flLateReverbPan[2]; + al_effect_props_.Reverb.EchoTime = efx_props.flEchoTime; + al_effect_props_.Reverb.EchoDepth = efx_props.flEchoDepth; + al_effect_props_.Reverb.ModulationTime = efx_props.flModulationTime; + al_effect_props_.Reverb.ModulationDepth = efx_props.flModulationDepth; + al_effect_props_.Reverb.HFReference = efx_props.flHFReference; + al_effect_props_.Reverb.LFReference = efx_props.flLFReference; + al_effect_props_.Reverb.RoomRolloffFactor = efx_props.flRoomRolloffFactor; + al_effect_props_.Reverb.AirAbsorptionGainHF = efx_props.flAirAbsorptionGainHF; + al_effect_props_.Reverb.DecayHFLimit = false; +} + +void EaxReverbEffect::defer_environment( + unsigned long ulEnvironment) +{ + eax_d_.ulEnvironment = ulEnvironment; + eax_dirty_flags_.ulEnvironment = (eax_.ulEnvironment != eax_d_.ulEnvironment); +} + +void EaxReverbEffect::defer_environment_size( + float flEnvironmentSize) +{ + eax_d_.flEnvironmentSize = flEnvironmentSize; + eax_dirty_flags_.flEnvironmentSize = (eax_.flEnvironmentSize != eax_d_.flEnvironmentSize); +} + +void EaxReverbEffect::defer_environment_diffusion( + float flEnvironmentDiffusion) +{ + eax_d_.flEnvironmentDiffusion = flEnvironmentDiffusion; + eax_dirty_flags_.flEnvironmentDiffusion = (eax_.flEnvironmentDiffusion != eax_d_.flEnvironmentDiffusion); +} + +void EaxReverbEffect::defer_room( + long lRoom) +{ + eax_d_.lRoom = lRoom; + eax_dirty_flags_.lRoom = (eax_.lRoom != eax_d_.lRoom); +} + +void EaxReverbEffect::defer_room_hf( + long lRoomHF) +{ + eax_d_.lRoomHF = lRoomHF; + eax_dirty_flags_.lRoomHF = (eax_.lRoomHF != eax_d_.lRoomHF); +} + +void EaxReverbEffect::defer_room_lf( + long lRoomLF) +{ + eax_d_.lRoomLF = lRoomLF; + eax_dirty_flags_.lRoomLF = (eax_.lRoomLF != eax_d_.lRoomLF); +} + +void EaxReverbEffect::defer_decay_time( + float flDecayTime) +{ + eax_d_.flDecayTime = flDecayTime; + eax_dirty_flags_.flDecayTime = (eax_.flDecayTime != eax_d_.flDecayTime); +} + +void EaxReverbEffect::defer_decay_hf_ratio( + float flDecayHFRatio) +{ + eax_d_.flDecayHFRatio = flDecayHFRatio; + eax_dirty_flags_.flDecayHFRatio = (eax_.flDecayHFRatio != eax_d_.flDecayHFRatio); +} + +void EaxReverbEffect::defer_decay_lf_ratio( + float flDecayLFRatio) +{ + eax_d_.flDecayLFRatio = flDecayLFRatio; + eax_dirty_flags_.flDecayLFRatio = (eax_.flDecayLFRatio != eax_d_.flDecayLFRatio); +} + +void EaxReverbEffect::defer_reflections( + long lReflections) +{ + eax_d_.lReflections = lReflections; + eax_dirty_flags_.lReflections = (eax_.lReflections != eax_d_.lReflections); +} + +void EaxReverbEffect::defer_reflections_delay( + float flReflectionsDelay) +{ + eax_d_.flReflectionsDelay = flReflectionsDelay; + eax_dirty_flags_.flReflectionsDelay = (eax_.flReflectionsDelay != eax_d_.flReflectionsDelay); +} + +void EaxReverbEffect::defer_reflections_pan( + const EAXVECTOR& vReflectionsPan) +{ + eax_d_.vReflectionsPan = vReflectionsPan; + eax_dirty_flags_.vReflectionsPan = (eax_.vReflectionsPan != eax_d_.vReflectionsPan); +} + +void EaxReverbEffect::defer_reverb( + long lReverb) +{ + eax_d_.lReverb = lReverb; + eax_dirty_flags_.lReverb = (eax_.lReverb != eax_d_.lReverb); +} + +void EaxReverbEffect::defer_reverb_delay( + float flReverbDelay) +{ + eax_d_.flReverbDelay = flReverbDelay; + eax_dirty_flags_.flReverbDelay = (eax_.flReverbDelay != eax_d_.flReverbDelay); +} + +void EaxReverbEffect::defer_reverb_pan( + const EAXVECTOR& vReverbPan) +{ + eax_d_.vReverbPan = vReverbPan; + eax_dirty_flags_.vReverbPan = (eax_.vReverbPan != eax_d_.vReverbPan); +} + +void EaxReverbEffect::defer_echo_time( + float flEchoTime) +{ + eax_d_.flEchoTime = flEchoTime; + eax_dirty_flags_.flEchoTime = (eax_.flEchoTime != eax_d_.flEchoTime); +} + +void EaxReverbEffect::defer_echo_depth( + float flEchoDepth) +{ + eax_d_.flEchoDepth = flEchoDepth; + eax_dirty_flags_.flEchoDepth = (eax_.flEchoDepth != eax_d_.flEchoDepth); +} + +void EaxReverbEffect::defer_modulation_time( + float flModulationTime) +{ + eax_d_.flModulationTime = flModulationTime; + eax_dirty_flags_.flModulationTime = (eax_.flModulationTime != eax_d_.flModulationTime); +} + +void EaxReverbEffect::defer_modulation_depth( + float flModulationDepth) +{ + eax_d_.flModulationDepth = flModulationDepth; + eax_dirty_flags_.flModulationDepth = (eax_.flModulationDepth != eax_d_.flModulationDepth); +} + +void EaxReverbEffect::defer_air_absorbtion_hf( + float flAirAbsorptionHF) +{ + eax_d_.flAirAbsorptionHF = flAirAbsorptionHF; + eax_dirty_flags_.flAirAbsorptionHF = (eax_.flAirAbsorptionHF != eax_d_.flAirAbsorptionHF); +} + +void EaxReverbEffect::defer_hf_reference( + float flHFReference) +{ + eax_d_.flHFReference = flHFReference; + eax_dirty_flags_.flHFReference = (eax_.flHFReference != eax_d_.flHFReference); +} + +void EaxReverbEffect::defer_lf_reference( + float flLFReference) +{ + eax_d_.flLFReference = flLFReference; + eax_dirty_flags_.flLFReference = (eax_.flLFReference != eax_d_.flLFReference); +} + +void EaxReverbEffect::defer_room_rolloff_factor( + float flRoomRolloffFactor) +{ + eax_d_.flRoomRolloffFactor = flRoomRolloffFactor; + eax_dirty_flags_.flRoomRolloffFactor = (eax_.flRoomRolloffFactor != eax_d_.flRoomRolloffFactor); +} + +void EaxReverbEffect::defer_flags( + unsigned long ulFlags) +{ + eax_d_.ulFlags = ulFlags; + eax_dirty_flags_.ulFlags = (eax_.ulFlags != eax_d_.ulFlags); +} + +void EaxReverbEffect::defer_all( + const EAX20LISTENERPROPERTIES& listener) +{ + defer_room(listener.lRoom); + defer_room_hf(listener.lRoomHF); + defer_room_rolloff_factor(listener.flRoomRolloffFactor); + defer_decay_time(listener.flDecayTime); + defer_decay_hf_ratio(listener.flDecayHFRatio); + defer_reflections(listener.lReflections); + defer_reflections_delay(listener.flReflectionsDelay); + defer_reverb(listener.lReverb); + defer_reverb_delay(listener.flReverbDelay); + defer_environment(listener.dwEnvironment); + defer_environment_size(listener.flEnvironmentSize); + defer_environment_diffusion(listener.flEnvironmentDiffusion); + defer_air_absorbtion_hf(listener.flAirAbsorptionHF); + defer_flags(listener.dwFlags); +} + +void EaxReverbEffect::defer_all( + const EAXREVERBPROPERTIES& lReverb) +{ + defer_environment(lReverb.ulEnvironment); + defer_environment_size(lReverb.flEnvironmentSize); + defer_environment_diffusion(lReverb.flEnvironmentDiffusion); + defer_room(lReverb.lRoom); + defer_room_hf(lReverb.lRoomHF); + defer_room_lf(lReverb.lRoomLF); + defer_decay_time(lReverb.flDecayTime); + defer_decay_hf_ratio(lReverb.flDecayHFRatio); + defer_decay_lf_ratio(lReverb.flDecayLFRatio); + defer_reflections(lReverb.lReflections); + defer_reflections_delay(lReverb.flReflectionsDelay); + defer_reflections_pan(lReverb.vReflectionsPan); + defer_reverb(lReverb.lReverb); + defer_reverb_delay(lReverb.flReverbDelay); + defer_reverb_pan(lReverb.vReverbPan); + defer_echo_time(lReverb.flEchoTime); + defer_echo_depth(lReverb.flEchoDepth); + defer_modulation_time(lReverb.flModulationTime); + defer_modulation_depth(lReverb.flModulationDepth); + defer_air_absorbtion_hf(lReverb.flAirAbsorptionHF); + defer_hf_reference(lReverb.flHFReference); + defer_lf_reference(lReverb.flLFReference); + defer_room_rolloff_factor(lReverb.flRoomRolloffFactor); + defer_flags(lReverb.ulFlags); +} + + +void EaxReverbEffect::v1_defer_environment(const EaxEaxCall& eax_call) +{ + const auto& environment = eax_call.get_value(); + + validate_environment(environment, 1, true); + + const auto& reverb_preset = EAX1REVERB_PRESETS[environment]; + v1_defer_all(reverb_preset); +} + +void EaxReverbEffect::v1_defer_volume(const EaxEaxCall& eax_call) +{ + const auto& volume = eax_call.get_value(); + + v1_validate_volume(volume); + v1_defer_volume(volume); +} + +void EaxReverbEffect::v1_defer_decay_time(const EaxEaxCall& eax_call) +{ + const auto& decay_time = eax_call.get_value(); + + v1_validate_decay_time(decay_time); + v1_defer_decay_time(decay_time); +} + +void EaxReverbEffect::v1_defer_damping(const EaxEaxCall& eax_call) +{ + const auto& damping = eax_call.get_value(); + + v1_validate_damping(damping); + v1_defer_damping(damping); +} + +void EaxReverbEffect::v1_defer_all(const EaxEaxCall& eax_call) +{ + const auto& reverb_all = eax_call.get_value(); + + v1_validate_all(reverb_all); + v1_defer_all(reverb_all); +} + + +void EaxReverbEffect::defer_environment( + const EaxEaxCall& eax_call) +{ + const auto& ulEnvironment = + eax_call.get_value(); + + validate_environment(ulEnvironment, eax_call.get_version(), true); + + if (eax_d_.ulEnvironment == ulEnvironment) + { + return; + } + + const auto& reverb_preset = EAXREVERB_PRESETS[ulEnvironment]; + + defer_all(reverb_preset); +} + +void EaxReverbEffect::defer_environment_size( + const EaxEaxCall& eax_call) +{ + const auto& flEnvironmentSize = + eax_call.get_value(); + + validate_environment_size(flEnvironmentSize); + + if (eax_d_.flEnvironmentSize == flEnvironmentSize) + { + return; + } + + const auto scale = flEnvironmentSize / eax_d_.flEnvironmentSize; + + defer_environment_size(flEnvironmentSize); + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) + { + const auto flDecayTime = clamp( + scale * eax_d_.flDecayTime, + EAXREVERB_MINDECAYTIME, + EAXREVERB_MAXDECAYTIME); + + defer_decay_time(flDecayTime); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSSCALE) != 0) + { + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) + { + const auto lReflections = clamp( + eax_d_.lReflections - static_cast(gain_to_level_mb(scale)), + EAXREVERB_MINREFLECTIONS, + EAXREVERB_MAXREFLECTIONS); + + defer_reflections(lReflections); + } + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) + { + const auto flReflectionsDelay = clamp( + eax_d_.flReflectionsDelay * scale, + EAXREVERB_MINREFLECTIONSDELAY, + EAXREVERB_MAXREFLECTIONSDELAY); + + defer_reflections_delay(flReflectionsDelay); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REVERBSCALE) != 0) + { + const auto log_scalar = ((eax_d_.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F; + + const auto lReverb = clamp( + eax_d_.lReverb - static_cast(std::log10(scale) * log_scalar), + EAXREVERB_MINREVERB, + EAXREVERB_MAXREVERB); + + defer_reverb(lReverb); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REVERBDELAYSCALE) != 0) + { + const auto flReverbDelay = clamp( + scale * eax_d_.flReverbDelay, + EAXREVERB_MINREVERBDELAY, + EAXREVERB_MAXREVERBDELAY); + + defer_reverb_delay(flReverbDelay); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_ECHOTIMESCALE) != 0) + { + const auto flEchoTime = clamp( + eax_d_.flEchoTime * scale, + EAXREVERB_MINECHOTIME, + EAXREVERB_MAXECHOTIME); + + defer_echo_time(flEchoTime); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_MODULATIONTIMESCALE) != 0) + { + const auto flModulationTime = clamp( + scale * eax_d_.flModulationTime, + EAXREVERB_MINMODULATIONTIME, + EAXREVERB_MAXMODULATIONTIME); + + defer_modulation_time(flModulationTime); + } +} + +void EaxReverbEffect::defer_environment_diffusion( + const EaxEaxCall& eax_call) +{ + const auto& flEnvironmentDiffusion = + eax_call.get_value(); + + validate_environment_diffusion(flEnvironmentDiffusion); + defer_environment_diffusion(flEnvironmentDiffusion); +} + +void EaxReverbEffect::defer_room( + const EaxEaxCall& eax_call) +{ + const auto& lRoom = + eax_call.get_value(); + + validate_room(lRoom); + defer_room(lRoom); +} + +void EaxReverbEffect::defer_room_hf( + const EaxEaxCall& eax_call) +{ + const auto& lRoomHF = + eax_call.get_value(); + + validate_room_hf(lRoomHF); + defer_room_hf(lRoomHF); +} + +void EaxReverbEffect::defer_room_lf( + const EaxEaxCall& eax_call) +{ + const auto& lRoomLF = + eax_call.get_value(); + + validate_room_lf(lRoomLF); + defer_room_lf(lRoomLF); +} + +void EaxReverbEffect::defer_decay_time( + const EaxEaxCall& eax_call) +{ + const auto& flDecayTime = + eax_call.get_value(); + + validate_decay_time(flDecayTime); + defer_decay_time(flDecayTime); +} + +void EaxReverbEffect::defer_decay_hf_ratio( + const EaxEaxCall& eax_call) +{ + const auto& flDecayHFRatio = + eax_call.get_value(); + + validate_decay_hf_ratio(flDecayHFRatio); + defer_decay_hf_ratio(flDecayHFRatio); +} + +void EaxReverbEffect::defer_decay_lf_ratio( + const EaxEaxCall& eax_call) +{ + const auto& flDecayLFRatio = + eax_call.get_value(); + + validate_decay_lf_ratio(flDecayLFRatio); + defer_decay_lf_ratio(flDecayLFRatio); +} + +void EaxReverbEffect::defer_reflections( + const EaxEaxCall& eax_call) +{ + const auto& lReflections = + eax_call.get_value(); + + validate_reflections(lReflections); + defer_reflections(lReflections); +} + +void EaxReverbEffect::defer_reflections_delay( + const EaxEaxCall& eax_call) +{ + const auto& flReflectionsDelay = + eax_call.get_value(); + + validate_reflections_delay(flReflectionsDelay); + defer_reflections_delay(flReflectionsDelay); +} + +void EaxReverbEffect::defer_reflections_pan( + const EaxEaxCall& eax_call) +{ + const auto& vReflectionsPan = + eax_call.get_value(); + + validate_reflections_pan(vReflectionsPan); + defer_reflections_pan(vReflectionsPan); +} + +void EaxReverbEffect::defer_reverb( + const EaxEaxCall& eax_call) +{ + const auto& lReverb = + eax_call.get_value(); + + validate_reverb(lReverb); + defer_reverb(lReverb); +} + +void EaxReverbEffect::defer_reverb_delay( + const EaxEaxCall& eax_call) +{ + const auto& flReverbDelay = + eax_call.get_value(); + + validate_reverb_delay(flReverbDelay); + defer_reverb_delay(flReverbDelay); +} + +void EaxReverbEffect::defer_reverb_pan( + const EaxEaxCall& eax_call) +{ + const auto& vReverbPan = + eax_call.get_value(); + + validate_reverb_pan(vReverbPan); + defer_reverb_pan(vReverbPan); +} + +void EaxReverbEffect::defer_echo_time( + const EaxEaxCall& eax_call) +{ + const auto& flEchoTime = + eax_call.get_value(); + + validate_echo_time(flEchoTime); + defer_echo_time(flEchoTime); +} + +void EaxReverbEffect::defer_echo_depth( + const EaxEaxCall& eax_call) +{ + const auto& flEchoDepth = + eax_call.get_value(); + + validate_echo_depth(flEchoDepth); + defer_echo_depth(flEchoDepth); +} + +void EaxReverbEffect::defer_modulation_time( + const EaxEaxCall& eax_call) +{ + const auto& flModulationTime = + eax_call.get_value(); + + validate_modulation_time(flModulationTime); + defer_modulation_time(flModulationTime); +} + +void EaxReverbEffect::defer_modulation_depth( + const EaxEaxCall& eax_call) +{ + const auto& flModulationDepth = + eax_call.get_value(); + + validate_modulation_depth(flModulationDepth); + defer_modulation_depth(flModulationDepth); +} + +void EaxReverbEffect::defer_air_absorbtion_hf( + const EaxEaxCall& eax_call) +{ + const auto& air_absorbtion_hf = + eax_call.get_value(); + + validate_air_absorbtion_hf(air_absorbtion_hf); + defer_air_absorbtion_hf(air_absorbtion_hf); +} + +void EaxReverbEffect::defer_hf_reference( + const EaxEaxCall& eax_call) +{ + const auto& flHFReference = + eax_call.get_value(); + + validate_hf_reference(flHFReference); + defer_hf_reference(flHFReference); +} + +void EaxReverbEffect::defer_lf_reference( + const EaxEaxCall& eax_call) +{ + const auto& flLFReference = + eax_call.get_value(); + + validate_lf_reference(flLFReference); + defer_lf_reference(flLFReference); +} + +void EaxReverbEffect::defer_room_rolloff_factor( + const EaxEaxCall& eax_call) +{ + const auto& flRoomRolloffFactor = + eax_call.get_value(); + + validate_room_rolloff_factor(flRoomRolloffFactor); + defer_room_rolloff_factor(flRoomRolloffFactor); +} + +void EaxReverbEffect::defer_flags( + const EaxEaxCall& eax_call) +{ + const auto& ulFlags = + eax_call.get_value(); + + validate_flags(ulFlags); + defer_flags(ulFlags); +} + +void EaxReverbEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto eax_version = eax_call.get_version(); + + if (eax_version == 2) + { + const auto& listener = + eax_call.get_value(); + + validate_all(listener, eax_version); + defer_all(listener); + } + else + { + const auto& reverb_all = + eax_call.get_value(); + + validate_all(reverb_all, eax_version); + defer_all(reverb_all); + } +} + + +void EaxReverbEffect::v1_defer(const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case DSPROPERTY_EAX_ALL: return v1_defer_all(eax_call); + case DSPROPERTY_EAX_ENVIRONMENT: return v1_defer_environment(eax_call); + case DSPROPERTY_EAX_VOLUME: return v1_defer_volume(eax_call); + case DSPROPERTY_EAX_DECAYTIME: return v1_defer_decay_time(eax_call); + case DSPROPERTY_EAX_DAMPING: return v1_defer_damping(eax_call); + default: eax_fail("Unsupported property id."); + } +} + +// [[nodiscard]] +bool EaxReverbEffect::apply_deferred() +{ + bool ret{false}; + + if(unlikely(eax1_dirty_flags_ != Eax1ReverbEffectDirtyFlags{})) + { + eax1_ = eax1_d_; + + v1_set_efx(); + + eax1_dirty_flags_ = Eax1ReverbEffectDirtyFlags{}; + + ret = true; + } + + if(eax_dirty_flags_ == EaxReverbEffectDirtyFlags{}) + return ret; + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulEnvironment) + { + } + + if (eax_dirty_flags_.flEnvironmentSize) + { + set_efx_density_from_environment_size(); + } + + if (eax_dirty_flags_.flEnvironmentDiffusion) + { + set_efx_diffusion(); + } + + if (eax_dirty_flags_.lRoom) + { + set_efx_gain(); + } + + if (eax_dirty_flags_.lRoomHF) + { + set_efx_gain_hf(); + } + + if (eax_dirty_flags_.lRoomLF) + { + set_efx_gain_lf(); + } + + if (eax_dirty_flags_.flDecayTime) + { + set_efx_decay_time(); + } + + if (eax_dirty_flags_.flDecayHFRatio) + { + set_efx_decay_hf_ratio(); + } + + if (eax_dirty_flags_.flDecayLFRatio) + { + set_efx_decay_lf_ratio(); + } + + if (eax_dirty_flags_.lReflections) + { + set_efx_reflections_gain(); + } + + if (eax_dirty_flags_.flReflectionsDelay) + { + set_efx_reflections_delay(); + } + + if (eax_dirty_flags_.vReflectionsPan) + { + set_efx_reflections_pan(); + } + + if (eax_dirty_flags_.lReverb) + { + set_efx_late_reverb_gain(); + } + + if (eax_dirty_flags_.flReverbDelay) + { + set_efx_late_reverb_delay(); + } + + if (eax_dirty_flags_.vReverbPan) + { + set_efx_late_reverb_pan(); + } + + if (eax_dirty_flags_.flEchoTime) + { + set_efx_echo_time(); + } + + if (eax_dirty_flags_.flEchoDepth) + { + set_efx_echo_depth(); + } + + if (eax_dirty_flags_.flModulationTime) + { + set_efx_modulation_time(); + } + + if (eax_dirty_flags_.flModulationDepth) + { + set_efx_modulation_depth(); + } + + if (eax_dirty_flags_.flAirAbsorptionHF) + { + set_efx_air_absorption_gain_hf(); + } + + if (eax_dirty_flags_.flHFReference) + { + set_efx_hf_reference(); + } + + if (eax_dirty_flags_.flLFReference) + { + set_efx_lf_reference(); + } + + if (eax_dirty_flags_.flRoomRolloffFactor) + { + set_efx_room_rolloff_factor(); + } + + if (eax_dirty_flags_.ulFlags) + { + set_efx_flags(); + } + + eax_dirty_flags_ = EaxReverbEffectDirtyFlags{}; + + return true; +} + +void EaxReverbEffect::set(const EaxEaxCall& eax_call) +{ + if(eax_call.get_version() == 1) + v1_defer(eax_call); + else switch(eax_call.get_property_id()) + { + case EAXREVERB_NONE: + break; + + case EAXREVERB_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXREVERB_ENVIRONMENT: + defer_environment(eax_call); + break; + + case EAXREVERB_ENVIRONMENTSIZE: + defer_environment_size(eax_call); + break; + + case EAXREVERB_ENVIRONMENTDIFFUSION: + defer_environment_diffusion(eax_call); + break; + + case EAXREVERB_ROOM: + defer_room(eax_call); + break; + + case EAXREVERB_ROOMHF: + defer_room_hf(eax_call); + break; + + case EAXREVERB_ROOMLF: + defer_room_lf(eax_call); + break; + + case EAXREVERB_DECAYTIME: + defer_decay_time(eax_call); + break; + + case EAXREVERB_DECAYHFRATIO: + defer_decay_hf_ratio(eax_call); + break; + + case EAXREVERB_DECAYLFRATIO: + defer_decay_lf_ratio(eax_call); + break; + + case EAXREVERB_REFLECTIONS: + defer_reflections(eax_call); + break; + + case EAXREVERB_REFLECTIONSDELAY: + defer_reflections_delay(eax_call); + break; + + case EAXREVERB_REFLECTIONSPAN: + defer_reflections_pan(eax_call); + break; + + case EAXREVERB_REVERB: + defer_reverb(eax_call); + break; + + case EAXREVERB_REVERBDELAY: + defer_reverb_delay(eax_call); + break; + + case EAXREVERB_REVERBPAN: + defer_reverb_pan(eax_call); + break; + + case EAXREVERB_ECHOTIME: + defer_echo_time(eax_call); + break; + + case EAXREVERB_ECHODEPTH: + defer_echo_depth(eax_call); + break; + + case EAXREVERB_MODULATIONTIME: + defer_modulation_time(eax_call); + break; + + case EAXREVERB_MODULATIONDEPTH: + defer_modulation_depth(eax_call); + break; + + case EAXREVERB_AIRABSORPTIONHF: + defer_air_absorbtion_hf(eax_call); + break; + + case EAXREVERB_HFREFERENCE: + defer_hf_reference(eax_call); + break; + + case EAXREVERB_LFREFERENCE: + defer_lf_reference(eax_call); + break; + + case EAXREVERB_ROOMROLLOFFFACTOR: + defer_room_rolloff_factor(eax_call); + break; + + case EAXREVERB_FLAGS: + defer_flags(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +const EFXEAXREVERBPROPERTIES eax_efx_reverb_presets[EAX1_ENVIRONMENT_COUNT] = +{ + EFX_REVERB_PRESET_GENERIC, + EFX_REVERB_PRESET_PADDEDCELL, + EFX_REVERB_PRESET_ROOM, + EFX_REVERB_PRESET_BATHROOM, + EFX_REVERB_PRESET_LIVINGROOM, + EFX_REVERB_PRESET_STONEROOM, + EFX_REVERB_PRESET_AUDITORIUM, + EFX_REVERB_PRESET_CONCERTHALL, + EFX_REVERB_PRESET_CAVE, + EFX_REVERB_PRESET_ARENA, + EFX_REVERB_PRESET_HANGAR, + EFX_REVERB_PRESET_CARPETEDHALLWAY, + EFX_REVERB_PRESET_HALLWAY, + EFX_REVERB_PRESET_STONECORRIDOR, + EFX_REVERB_PRESET_ALLEY, + EFX_REVERB_PRESET_FOREST, + EFX_REVERB_PRESET_CITY, + EFX_REVERB_PRESET_MOUNTAINS, + EFX_REVERB_PRESET_QUARRY, + EFX_REVERB_PRESET_PLAIN, + EFX_REVERB_PRESET_PARKINGLOT, + EFX_REVERB_PRESET_SEWERPIPE, + EFX_REVERB_PRESET_UNDERWATER, + EFX_REVERB_PRESET_DRUGGED, + EFX_REVERB_PRESET_DIZZY, + EFX_REVERB_PRESET_PSYCHOTIC, +}; // EFXEAXREVERBPROPERTIES + +} // namespace + +EaxEffectUPtr eax_create_eax_reverb_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/vmorpher.cpp b/Engine/lib/openal-soft/al/effects/vmorpher.cpp index 03eb2c624..8c0b3adba 100644 --- a/Engine/lib/openal-soft/al/effects/vmorpher.cpp +++ b/Engine/lib/openal-soft/al/effects/vmorpher.cpp @@ -1,12 +1,23 @@ #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" + +#ifdef ALSOFT_EAX +#include + +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX namespace { @@ -245,3 +256,531 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Vmorpher); const EffectProps VmorpherEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EaxVocalMorpherEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxVocalMorpherEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeA : 1; + EaxVocalMorpherEffectDirtyFlagsValue lPhonemeACoarseTuning : 1; + EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeB : 1; + EaxVocalMorpherEffectDirtyFlagsValue lPhonemeBCoarseTuning : 1; + EaxVocalMorpherEffectDirtyFlagsValue ulWaveform : 1; + EaxVocalMorpherEffectDirtyFlagsValue flRate : 1; +}; // EaxPitchShifterEffectDirtyFlags + + +class EaxVocalMorpherEffect final : + public EaxEffect +{ +public: + EaxVocalMorpherEffect(); + + void dispatch(const EaxEaxCall& eax_call) override; + + // [[nodiscard]] + bool apply_deferred() override; + +private: + EAXVOCALMORPHERPROPERTIES eax_{}; + EAXVOCALMORPHERPROPERTIES eax_d_{}; + EaxVocalMorpherEffectDirtyFlags eax_dirty_flags_{}; + + void set_eax_defaults(); + + void set_efx_phoneme_a(); + void set_efx_phoneme_a_coarse_tuning(); + void set_efx_phoneme_b(); + void set_efx_phoneme_b_coarse_tuning(); + void set_efx_waveform(); + void set_efx_rate(); + void set_efx_defaults(); + + void get(const EaxEaxCall& eax_call); + + void validate_phoneme_a(unsigned long ulPhonemeA); + void validate_phoneme_a_coarse_tuning(long lPhonemeACoarseTuning); + void validate_phoneme_b(unsigned long ulPhonemeB); + void validate_phoneme_b_coarse_tuning(long lPhonemeBCoarseTuning); + void validate_waveform(unsigned long ulWaveform); + void validate_rate(float flRate); + void validate_all(const EAXVOCALMORPHERPROPERTIES& all); + + void defer_phoneme_a(unsigned long ulPhonemeA); + void defer_phoneme_a_coarse_tuning(long lPhonemeACoarseTuning); + void defer_phoneme_b(unsigned long ulPhonemeB); + void defer_phoneme_b_coarse_tuning(long lPhonemeBCoarseTuning); + void defer_waveform(unsigned long ulWaveform); + void defer_rate(float flRate); + void defer_all(const EAXVOCALMORPHERPROPERTIES& all); + + void defer_phoneme_a(const EaxEaxCall& eax_call); + void defer_phoneme_a_coarse_tuning(const EaxEaxCall& eax_call); + void defer_phoneme_b(const EaxEaxCall& eax_call); + void defer_phoneme_b_coarse_tuning(const EaxEaxCall& eax_call); + void defer_waveform(const EaxEaxCall& eax_call); + void defer_rate(const EaxEaxCall& eax_call); + void defer_all(const EaxEaxCall& eax_call); + + void set(const EaxEaxCall& eax_call); +}; // EaxVocalMorpherEffect + + +class EaxVocalMorpherEffectException : + public EaxException +{ +public: + explicit EaxVocalMorpherEffectException( + const char* message) + : + EaxException{"EAX_VOCAL_MORPHER_EFFECT", message} + { + } +}; // EaxVocalMorpherEffectException + + +EaxVocalMorpherEffect::EaxVocalMorpherEffect() + : EaxEffect{AL_EFFECT_VOCAL_MORPHER} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +void EaxVocalMorpherEffect::dispatch(const EaxEaxCall& eax_call) +{ + eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxVocalMorpherEffect::set_eax_defaults() +{ + eax_.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA; + eax_.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING; + eax_.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB; + eax_.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING; + eax_.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM; + eax_.flRate = EAXVOCALMORPHER_DEFAULTRATE; + + eax_d_ = eax_; +} + +void EaxVocalMorpherEffect::set_efx_phoneme_a() +{ + const auto phoneme_a = clamp( + static_cast(eax_.ulPhonemeA), + AL_VOCAL_MORPHER_MIN_PHONEMEA, + AL_VOCAL_MORPHER_MAX_PHONEMEA); + + const auto efx_phoneme_a = PhenomeFromEnum(phoneme_a); + assert(efx_phoneme_a.has_value()); + al_effect_props_.Vmorpher.PhonemeA = *efx_phoneme_a; +} + +void EaxVocalMorpherEffect::set_efx_phoneme_a_coarse_tuning() +{ + const auto phoneme_a_coarse_tuning = clamp( + static_cast(eax_.lPhonemeACoarseTuning), + AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING, + AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING); + + al_effect_props_.Vmorpher.PhonemeACoarseTuning = phoneme_a_coarse_tuning; +} + +void EaxVocalMorpherEffect::set_efx_phoneme_b() +{ + const auto phoneme_b = clamp( + static_cast(eax_.ulPhonemeB), + AL_VOCAL_MORPHER_MIN_PHONEMEB, + AL_VOCAL_MORPHER_MAX_PHONEMEB); + + const auto efx_phoneme_b = PhenomeFromEnum(phoneme_b); + assert(efx_phoneme_b.has_value()); + al_effect_props_.Vmorpher.PhonemeB = *efx_phoneme_b; +} + +void EaxVocalMorpherEffect::set_efx_phoneme_b_coarse_tuning() +{ + const auto phoneme_b_coarse_tuning = clamp( + static_cast(eax_.lPhonemeBCoarseTuning), + AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING, + AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING); + + al_effect_props_.Vmorpher.PhonemeBCoarseTuning = phoneme_b_coarse_tuning; +} + +void EaxVocalMorpherEffect::set_efx_waveform() +{ + const auto waveform = clamp( + static_cast(eax_.ulWaveform), + AL_VOCAL_MORPHER_MIN_WAVEFORM, + AL_VOCAL_MORPHER_MAX_WAVEFORM); + + const auto wfx_waveform = WaveformFromEmum(waveform); + assert(wfx_waveform.has_value()); + al_effect_props_.Vmorpher.Waveform = *wfx_waveform; +} + +void EaxVocalMorpherEffect::set_efx_rate() +{ + const auto rate = clamp( + eax_.flRate, + AL_VOCAL_MORPHER_MIN_RATE, + AL_VOCAL_MORPHER_MAX_RATE); + + al_effect_props_.Vmorpher.Rate = rate; +} + +void EaxVocalMorpherEffect::set_efx_defaults() +{ + set_efx_phoneme_a(); + set_efx_phoneme_a_coarse_tuning(); + set_efx_phoneme_b(); + set_efx_phoneme_b_coarse_tuning(); + set_efx_waveform(); + set_efx_rate(); +} + +void EaxVocalMorpherEffect::get(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXVOCALMORPHER_NONE: + break; + + case EAXVOCALMORPHER_ALLPARAMETERS: + eax_call.set_value(eax_); + break; + + case EAXVOCALMORPHER_PHONEMEA: + eax_call.set_value(eax_.ulPhonemeA); + break; + + case EAXVOCALMORPHER_PHONEMEACOARSETUNING: + eax_call.set_value(eax_.lPhonemeACoarseTuning); + break; + + case EAXVOCALMORPHER_PHONEMEB: + eax_call.set_value(eax_.ulPhonemeB); + break; + + case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: + eax_call.set_value(eax_.lPhonemeBCoarseTuning); + break; + + case EAXVOCALMORPHER_WAVEFORM: + eax_call.set_value(eax_.ulWaveform); + break; + + case EAXVOCALMORPHER_RATE: + eax_call.set_value(eax_.flRate); + break; + + default: + throw EaxVocalMorpherEffectException{"Unsupported property id."}; + } +} + +void EaxVocalMorpherEffect::validate_phoneme_a( + unsigned long ulPhonemeA) +{ + eax_validate_range( + "Phoneme A", + ulPhonemeA, + EAXVOCALMORPHER_MINPHONEMEA, + EAXVOCALMORPHER_MAXPHONEMEA); +} + +void EaxVocalMorpherEffect::validate_phoneme_a_coarse_tuning( + long lPhonemeACoarseTuning) +{ + eax_validate_range( + "Phoneme A Coarse Tuning", + lPhonemeACoarseTuning, + EAXVOCALMORPHER_MINPHONEMEACOARSETUNING, + EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING); +} + +void EaxVocalMorpherEffect::validate_phoneme_b( + unsigned long ulPhonemeB) +{ + eax_validate_range( + "Phoneme B", + ulPhonemeB, + EAXVOCALMORPHER_MINPHONEMEB, + EAXVOCALMORPHER_MAXPHONEMEB); +} + +void EaxVocalMorpherEffect::validate_phoneme_b_coarse_tuning( + long lPhonemeBCoarseTuning) +{ + eax_validate_range( + "Phoneme B Coarse Tuning", + lPhonemeBCoarseTuning, + EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING, + EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING); +} + +void EaxVocalMorpherEffect::validate_waveform( + unsigned long ulWaveform) +{ + eax_validate_range( + "Waveform", + ulWaveform, + EAXVOCALMORPHER_MINWAVEFORM, + EAXVOCALMORPHER_MAXWAVEFORM); +} + +void EaxVocalMorpherEffect::validate_rate( + float flRate) +{ + eax_validate_range( + "Rate", + flRate, + EAXVOCALMORPHER_MINRATE, + EAXVOCALMORPHER_MAXRATE); +} + +void EaxVocalMorpherEffect::validate_all( + const EAXVOCALMORPHERPROPERTIES& all) +{ + validate_phoneme_a(all.ulPhonemeA); + validate_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning); + validate_phoneme_b(all.ulPhonemeB); + validate_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning); + validate_waveform(all.ulWaveform); + validate_rate(all.flRate); +} + +void EaxVocalMorpherEffect::defer_phoneme_a( + unsigned long ulPhonemeA) +{ + eax_d_.ulPhonemeA = ulPhonemeA; + eax_dirty_flags_.ulPhonemeA = (eax_.ulPhonemeA != eax_d_.ulPhonemeA); +} + +void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning( + long lPhonemeACoarseTuning) +{ + eax_d_.lPhonemeACoarseTuning = lPhonemeACoarseTuning; + eax_dirty_flags_.lPhonemeACoarseTuning = (eax_.lPhonemeACoarseTuning != eax_d_.lPhonemeACoarseTuning); +} + +void EaxVocalMorpherEffect::defer_phoneme_b( + unsigned long ulPhonemeB) +{ + eax_d_.ulPhonemeB = ulPhonemeB; + eax_dirty_flags_.ulPhonemeB = (eax_.ulPhonemeB != eax_d_.ulPhonemeB); +} + +void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning( + long lPhonemeBCoarseTuning) +{ + eax_d_.lPhonemeBCoarseTuning = lPhonemeBCoarseTuning; + eax_dirty_flags_.lPhonemeBCoarseTuning = (eax_.lPhonemeBCoarseTuning != eax_d_.lPhonemeBCoarseTuning); +} + +void EaxVocalMorpherEffect::defer_waveform( + unsigned long ulWaveform) +{ + eax_d_.ulWaveform = ulWaveform; + eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); +} + +void EaxVocalMorpherEffect::defer_rate( + float flRate) +{ + eax_d_.flRate = flRate; + eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate); +} + +void EaxVocalMorpherEffect::defer_all( + const EAXVOCALMORPHERPROPERTIES& all) +{ + defer_phoneme_a(all.ulPhonemeA); + defer_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning); + defer_phoneme_b(all.ulPhonemeB); + defer_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning); + defer_waveform(all.ulWaveform); + defer_rate(all.flRate); +} + +void EaxVocalMorpherEffect::defer_phoneme_a( + const EaxEaxCall& eax_call) +{ + const auto& phoneme_a = eax_call.get_value(); + + validate_phoneme_a(phoneme_a); + defer_phoneme_a(phoneme_a); +} + +void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning( + const EaxEaxCall& eax_call) +{ + const auto& phoneme_a_coarse_tuning = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeACoarseTuning) + >(); + + validate_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning); + defer_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning); +} + +void EaxVocalMorpherEffect::defer_phoneme_b( + const EaxEaxCall& eax_call) +{ + const auto& phoneme_b = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::ulPhonemeB) + >(); + + validate_phoneme_b(phoneme_b); + defer_phoneme_b(phoneme_b); +} + +void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning( + const EaxEaxCall& eax_call) +{ + const auto& phoneme_b_coarse_tuning = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeBCoarseTuning) + >(); + + validate_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning); + defer_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning); +} + +void EaxVocalMorpherEffect::defer_waveform( + const EaxEaxCall& eax_call) +{ + const auto& waveform = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::ulWaveform) + >(); + + validate_waveform(waveform); + defer_waveform(waveform); +} + +void EaxVocalMorpherEffect::defer_rate( + const EaxEaxCall& eax_call) +{ + const auto& rate = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::flRate) + >(); + + validate_rate(rate); + defer_rate(rate); +} + +void EaxVocalMorpherEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = eax_call.get_value< + EaxVocalMorpherEffectException, + const EAXVOCALMORPHERPROPERTIES + >(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxVocalMorpherEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxVocalMorpherEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulPhonemeA) + { + set_efx_phoneme_a(); + } + + if (eax_dirty_flags_.lPhonemeACoarseTuning) + { + set_efx_phoneme_a_coarse_tuning(); + } + + if (eax_dirty_flags_.ulPhonemeB) + { + set_efx_phoneme_b(); + } + + if (eax_dirty_flags_.lPhonemeBCoarseTuning) + { + set_efx_phoneme_b_coarse_tuning(); + } + + if (eax_dirty_flags_.ulWaveform) + { + set_efx_waveform(); + } + + if (eax_dirty_flags_.flRate) + { + set_efx_rate(); + } + + eax_dirty_flags_ = EaxVocalMorpherEffectDirtyFlags{}; + + return true; +} + +void EaxVocalMorpherEffect::set(const EaxEaxCall& eax_call) +{ + switch(eax_call.get_property_id()) + { + case EAXVOCALMORPHER_NONE: + break; + + case EAXVOCALMORPHER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXVOCALMORPHER_PHONEMEA: + defer_phoneme_a(eax_call); + break; + + case EAXVOCALMORPHER_PHONEMEACOARSETUNING: + defer_phoneme_a_coarse_tuning(eax_call); + break; + + case EAXVOCALMORPHER_PHONEMEB: + defer_phoneme_b(eax_call); + break; + + case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: + defer_phoneme_b_coarse_tuning(eax_call); + break; + + case EAXVOCALMORPHER_WAVEFORM: + defer_waveform(eax_call); + break; + + case EAXVOCALMORPHER_RATE: + defer_rate(eax_call); + break; + + default: + throw EaxVocalMorpherEffectException{"Unsupported property id."}; + } +} + +} // namespace + + +EaxEffectUPtr eax_create_eax_vocal_morpher_effect() +{ + return std::make_unique(); +} + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/error.cpp b/Engine/lib/openal-soft/al/error.cpp index 444b55aae..906710113 100644 --- a/Engine/lib/openal-soft/al/error.cpp +++ b/Engine/lib/openal-soft/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" @@ -85,9 +85,9 @@ AL_API ALenum AL_APIENTRY alGetError(void) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) + if(unlikely(!context)) { - constexpr ALenum deferror{AL_INVALID_OPERATION}; + static constexpr ALenum deferror{AL_INVALID_OPERATION}; WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror); if(TrapALError) { diff --git a/Engine/lib/openal-soft/al/event.cpp b/Engine/lib/openal-soft/al/event.cpp index a5d7be38c..e5923c436 100644 --- a/Engine/lib/openal-soft/al/event.cpp +++ b/Engine/lib/openal-soft/al/event.cpp @@ -18,24 +18,24 @@ #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) { RingBuffer *ring{context->mAsyncEvents.get()}; bool quitnow{false}; - while LIKELY(!quitnow) + while(likely(!quitnow)) { auto evt_data = ring->getReadVector().first; if(evt_data.len == 0) @@ -54,10 +54,10 @@ static int EventThread(ALCcontext *context) al::destroy_at(evt_ptr); ring->readAdvance(1); - quitnow = evt.EnumType == EventType_KillThread; - if UNLIKELY(quitnow) break; + quitnow = evt.EnumType == AsyncEvent::KillThread; + if(unlikely(quitnow)) break; - if(evt.EnumType == EventType_ReleaseEffectState) + if(evt.EnumType == AsyncEvent::ReleaseEffectState) { evt.u.mEffectState->release(); continue; @@ -66,41 +66,38 @@ static int EventThread(ALCcontext *context) uint enabledevts{context->mEnabledEvts.load(std::memory_order_acquire)}; if(!context->mEventCb) continue; - if(evt.EnumType == EventType_SourceStateChange) + if(evt.EnumType == AsyncEvent::SourceStateChange) { - if(!(enabledevts&EventType_SourceStateChange)) + if(!(enabledevts&AsyncEvent::SourceStateChange)) continue; ALuint state{}; std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)}; msg += " state has changed to "; switch(evt.u.srcstate.state) { - case 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); } - else if(evt.EnumType == EventType_BufferCompleted) + else if(evt.EnumType == AsyncEvent::BufferCompleted) { - if(!(enabledevts&EventType_BufferCompleted)) + if(!(enabledevts&AsyncEvent::BufferCompleted)) continue; std::string msg{std::to_string(evt.u.bufcomp.count)}; if(evt.u.bufcomp.count == 1) msg += " buffer completed"; @@ -109,9 +106,9 @@ static int EventThread(ALCcontext *context) evt.u.bufcomp.count, static_cast(msg.length()), msg.c_str(), context->mEventParam); } - else if(evt.EnumType == EventType_Disconnected) + else if(evt.EnumType == AsyncEvent::Disconnected) { - if(!(enabledevts&EventType_Disconnected)) + if(!(enabledevts&AsyncEvent::Disconnected)) continue; context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, static_cast(strlen(evt.u.disconnect.msg)), evt.u.disconnect.msg, @@ -146,7 +143,7 @@ void StopEventThrd(ALCcontext *ctx) evt_data = ring->getWriteVector().first; } while(evt_data.len == 0); } - ::new(evt_data.buf) AsyncEvent{EventType_KillThread}; + al::construct_at(reinterpret_cast(evt_data.buf), AsyncEvent::KillThread); ring->writeAdvance(1); ctx->mEventSem.post(); @@ -158,7 +155,7 @@ AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, A START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(unlikely(!context)) return; if(count < 0) context->setError(AL_INVALID_VALUE, "Controlling %d events", count); if(count <= 0) return; @@ -170,11 +167,11 @@ START_API_FUNC [&flags](ALenum type) noexcept -> bool { if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT) - flags |= EventType_BufferCompleted; + flags |= AsyncEvent::BufferCompleted; else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT) - flags |= EventType_SourceStateChange; + flags |= AsyncEvent::SourceStateChange; else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT) - flags |= EventType_Disconnected; + flags |= AsyncEvent::Disconnected; else return false; return true; @@ -204,7 +201,7 @@ START_API_FUNC /* Wait to ensure the event handler sees the changed flags before * returning. */ - std::lock_guard{context->mEventCbLock}; + std::lock_guard _{context->mEventCbLock}; } } END_API_FUNC @@ -213,7 +210,7 @@ AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *user START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(unlikely(!context)) return; std::lock_guard _{context->mPropLock}; std::lock_guard __{context->mEventCbLock}; diff --git a/Engine/lib/openal-soft/al/extension.cpp b/Engine/lib/openal-soft/al/extension.cpp index 7346a5c6a..5dda2a86d 100644 --- a/Engine/lib/openal-soft/al/extension.cpp +++ b/Engine/lib/openal-soft/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" @@ -37,7 +37,7 @@ AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName) START_API_FUNC { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return AL_FALSE; + if(unlikely(!context)) return AL_FALSE; if(!extName) SETERR_RETURN(context, AL_INVALID_VALUE, AL_FALSE, "NULL pointer"); diff --git a/Engine/lib/openal-soft/al/filter.cpp b/Engine/lib/openal-soft/al/filter.cpp index 0bcfe4086..f4b0ac9de 100644 --- a/Engine/lib/openal-soft/al/filter.cpp +++ b/Engine/lib/openal-soft/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; @@ -63,8 +67,6 @@ public: ALenum errorCode() const noexcept { return mErrorCode; } }; -#define FILTER_MIN_GAIN 0.0f -#define FILTER_MAX_GAIN 4.0f /* +12dB */ #define DEFINE_ALFILTER_VTABLE(T) \ const ALfilter::Vtable T##_vtable = { \ @@ -84,7 +86,7 @@ void ALlowpass_setParamf(ALfilter *filter, ALenum param, float val) switch(param) { case AL_LOWPASS_GAIN: - if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN)) + if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN)) throw filter_exception{AL_INVALID_VALUE, "Low-pass gain %f out of range", val}; filter->Gain = val; break; @@ -143,7 +145,7 @@ void ALhighpass_setParamf(ALfilter *filter, ALenum param, float val) switch(param) { case AL_HIGHPASS_GAIN: - if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN)) + if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN)) throw filter_exception{AL_INVALID_VALUE, "High-pass gain %f out of range", val}; filter->Gain = val; break; @@ -202,7 +204,7 @@ void ALbandpass_setParamf(ALfilter *filter, ALenum param, float val) switch(param) { case AL_BANDPASS_GAIN: - if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN)) + if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN)) throw filter_exception{AL_INVALID_VALUE, "Band-pass gain %f out of range", val}; filter->Gain = val; break; @@ -351,12 +353,12 @@ ALfilter *AllocFilter(ALCdevice *device) { auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(), [](const FilterSubList &entry) noexcept -> bool - { return entry.FreeMask != 0; } - ); + { return entry.FreeMask != 0; }); auto lidx = static_cast(std::distance(device->FilterList.begin(), sublist)); auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); + ASSUME(slidx < 64); - ALfilter *filter{::new(sublist->Filters + slidx) ALfilter{}}; + ALfilter *filter{al::construct_at(sublist->Filters + slidx)}; InitFilterParams(filter, AL_FILTER_NULL); /* Add 1 to avoid filter ID 0. */ @@ -404,8 +406,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 +446,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 +477,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 +493,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 +534,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 +557,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 +580,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 +603,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 +638,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 +661,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 +684,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/Engine/lib/openal-soft/al/filter.h b/Engine/lib/openal-soft/al/filter.h index 123e64b0c..65a9e30fd 100644 --- a/Engine/lib/openal-soft/al/filter.h +++ b/Engine/lib/openal-soft/al/filter.h @@ -1,13 +1,13 @@ #ifndef AL_FILTER_H #define AL_FILTER_H + #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "almalloc.h" - #define LOWPASSFREQREF 5000.0f #define HIGHPASSFREQREF 250.0f diff --git a/Engine/lib/openal-soft/al/listener.cpp b/Engine/lib/openal-soft/al/listener.cpp index 4bbc145c3..9484d9b1a 100644 --- a/Engine/lib/openal-soft/al/listener.cpp +++ b/Engine/lib/openal-soft/al/listener.cpp @@ -29,20 +29,53 @@ #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" #include "opthelpers.h" -#define DO_UPDATEPROPS() do { \ - if(!context->mDeferUpdates.load(std::memory_order_acquire)) \ - UpdateListenerProps(context.get()); \ - else \ - listener.PropsClean.clear(std::memory_order_release); \ -} while(0) +namespace { +inline void UpdateProps(ALCcontext *context) +{ + if(!context->mDeferUpdates) + { + UpdateContextProps(context); + return; + } + context->mPropsDirty = true; +} + +#ifdef ALSOFT_EAX +inline void CommitAndUpdateProps(ALCcontext *context) +{ + if(!context->mDeferUpdates) + { + if(context->has_eax()) + { + context->mHoldUpdates.store(true, std::memory_order_release); + while((context->mUpdateCount.load(std::memory_order_acquire)&1) != 0) { + /* busy-wait */ + } + + context->eax_commit_and_update_sources(); + } + UpdateContextProps(context); + context->mHoldUpdates.store(false, std::memory_order_release); + return; + } + context->mPropsDirty = true; +} + +#else + +inline void CommitAndUpdateProps(ALCcontext *context) +{ UpdateProps(context); } +#endif + +} // namespace AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value) START_API_FUNC @@ -58,14 +91,14 @@ START_API_FUNC if(!(value >= 0.0f && std::isfinite(value))) SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener gain out of range"); listener.Gain = value; - DO_UPDATEPROPS(); + UpdateProps(context.get()); break; case AL_METERS_PER_UNIT: if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT)) SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener meters per unit out of range"); listener.mMetersPerUnit = value; - DO_UPDATEPROPS(); + UpdateProps(context.get()); break; default: @@ -90,7 +123,7 @@ START_API_FUNC listener.Position[0] = value1; listener.Position[1] = value2; listener.Position[2] = value3; - DO_UPDATEPROPS(); + CommitAndUpdateProps(context.get()); break; case AL_VELOCITY: @@ -99,7 +132,7 @@ START_API_FUNC listener.Velocity[0] = value1; listener.Velocity[1] = value2; listener.Velocity[2] = value3; - DO_UPDATEPROPS(); + CommitAndUpdateProps(context.get()); break; default: @@ -146,7 +179,7 @@ START_API_FUNC listener.OrientUp[0] = values[3]; listener.OrientUp[1] = values[4]; listener.OrientUp[2] = values[5]; - DO_UPDATEPROPS(); + CommitAndUpdateProps(context.get()); break; default: @@ -414,39 +447,3 @@ START_API_FUNC } } END_API_FUNC - - -void UpdateListenerProps(ALCcontext *context) -{ - /* Get an unused proprty container, or allocate a new one as needed. */ - ListenerProps *props{context->mFreeListenerProps.load(std::memory_order_acquire)}; - if(!props) - props = new ListenerProps{}; - else - { - ListenerProps *next; - do { - next = props->next.load(std::memory_order_relaxed); - } while(context->mFreeListenerProps.compare_exchange_weak(props, next, - std::memory_order_seq_cst, std::memory_order_acquire) == 0); - } - - /* Copy in current property values. */ - ALlistener &listener = context->mListener; - props->Position = listener.Position; - props->Velocity = listener.Velocity; - props->OrientAt = listener.OrientAt; - props->OrientUp = listener.OrientUp; - props->Gain = listener.Gain; - props->MetersPerUnit = listener.mMetersPerUnit; - - /* Set the new container for updating internal parameters. */ - props = context->mParams.ListenerUpdate.exchange(props, std::memory_order_acq_rel); - if(props) - { - /* If there was an unused update container, put it back in the - * freelist. - */ - AtomicReplaceHead(context->mFreeListenerProps, props); - } -} diff --git a/Engine/lib/openal-soft/al/listener.h b/Engine/lib/openal-soft/al/listener.h index 5f3ce3ec8..815328771 100644 --- a/Engine/lib/openal-soft/al/listener.h +++ b/Engine/lib/openal-soft/al/listener.h @@ -2,7 +2,6 @@ #define AL_LISTENER_H #include -#include #include "AL/al.h" #include "AL/alc.h" @@ -19,13 +18,7 @@ struct ALlistener { float Gain{1.0f}; float mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT}; - std::atomic_flag PropsClean; - - ALlistener() { PropsClean.test_and_set(std::memory_order_relaxed); } - DISABLE_ALLOC() }; -void UpdateListenerProps(ALCcontext *context); - #endif diff --git a/Engine/lib/openal-soft/al/source.cpp b/Engine/lib/openal-soft/al/source.cpp index e17632f4a..d2ccfe08f 100644 --- a/Engine/lib/openal-soft/al/source.cpp +++ b/Engine/lib/openal-soft/al/source.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -46,32 +47,34 @@ #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" +#ifdef ALSOFT_EAX +#include "eax_exception.h" +#endif // ALSOFT_EAX namespace { @@ -99,15 +102,15 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context /* Get an unused property container, or allocate a new one as needed. */ VoicePropsItem *props{context->mFreeVoiceProps.load(std::memory_order_acquire)}; if(!props) - props = new VoicePropsItem{}; - else { - VoicePropsItem *next; - do { - next = props->next.load(std::memory_order_relaxed); - } while(context->mFreeVoiceProps.compare_exchange_weak(props, next, - std::memory_order_acq_rel, std::memory_order_acquire) == 0); + context->allocVoiceProps(); + props = context->mFreeVoiceProps.load(std::memory_order_acquire); } + VoicePropsItem *next; + do { + next = props->next.load(std::memory_order_relaxed); + } while(unlikely(context->mFreeVoiceProps.compare_exchange_weak(props, next, + std::memory_order_acq_rel, std::memory_order_acquire) == false)); props->Pitch = source->Pitch; props->Gain = source->Gain; @@ -118,7 +121,11 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context props->OuterAngle = source->OuterAngle; props->RefDistance = source->RefDistance; props->MaxDistance = source->MaxDistance; - props->RolloffFactor = source->RolloffFactor; + props->RolloffFactor = source->RolloffFactor +#ifdef ALSOFT_EAX + + source->RolloffFactor2 +#endif + ; props->Position = source->Position; props->Velocity = source->Velocity; props->Direction = source->Direction; @@ -142,6 +149,7 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context props->StereoPan = source->StereoPan; props->Radius = source->Radius; + props->EnhWidth = source->EnhWidth; props->Direct.Gain = source->Direct.Gain; props->Direct.GainHF = source->Direct.GainHF; @@ -183,7 +191,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 +230,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; @@ -258,7 +266,7 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl BufferFmt = BufferList->mBuffer; ++BufferList; } - assert(BufferFmt != nullptr); + ASSUME(BufferFmt != nullptr); return static_cast(readPos) / double{MixerFracOne} / BufferFmt->mSampleRate; } @@ -271,7 +279,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{}; @@ -307,7 +315,7 @@ double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) BufferFmt = BufferList->mBuffer; ++BufferList; } - assert(BufferFmt != nullptr); + ASSUME(BufferFmt != nullptr); double offset{}; switch(name) @@ -349,10 +357,61 @@ 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; + + ASSUME(BufferFmt != nullptr); + switch(name) + { + case AL_SEC_LENGTH_SOFT: + return static_cast(length) / BufferFmt->mSampleRate; + + case AL_SAMPLE_LENGTH_SOFT: + return static_cast(length); + + case AL_BYTE_LENGTH_SOFT: + if(BufferFmt->OriginalType == UserFmtIMA4) + { + ALuint FrameBlockSize{BufferFmt->OriginalAlign}; + ALuint align{(BufferFmt->OriginalAlign-1)/2 + 4}; + ALuint BlockSize{align * BufferFmt->channelsFromFmt()}; + + /* Round down to nearest ADPCM block */ + return static_cast(length / FrameBlockSize) * BlockSize; + } + else if(BufferFmt->OriginalType == UserFmtMSADPCM) + { + ALuint FrameBlockSize{BufferFmt->OriginalAlign}; + ALuint align{(FrameBlockSize-2)/2 + 7}; + ALuint BlockSize{align * BufferFmt->channelsFromFmt()}; + + /* Round down to nearest ADPCM block */ + return static_cast(length / FrameBlockSize) * BlockSize; + } + return static_cast(length) * BufferFmt->frameSizeFromFmt(); + } + return 0.0; +} + struct VoicePos { ALuint pos, frac; - VoiceBufferItem *bufferitem; + ALbufferQueueItem *bufferitem; }; /** @@ -372,7 +431,7 @@ al::optional GetSampleOffset(al::deque &BufferList, BufferFmt = item.mBuffer; if(BufferFmt) break; } - if(!BufferFmt) + if(!BufferFmt || BufferFmt->mCallback) return al::nullopt; /* Get sample frame offset */ @@ -422,7 +481,7 @@ al::optional GetSampleOffset(al::deque &BufferList, if(item.mSampleLen > offset-totalBufferLen) { /* Offset is in this buffer */ - return al::make_optional(VoicePos{offset-totalBufferLen, frac, &item}); + return VoicePos{offset-totalBufferLen, frac, &item}; } totalBufferLen += item.mSampleLen; } @@ -439,72 +498,24 @@ void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, AL std::memory_order_relaxed); ALbuffer *buffer{BufferList->mBuffer}; - ALuint num_channels{buffer->channelsFromFmt()}; voice->mFrequency = buffer->mSampleRate; - voice->mFmtChannels = buffer->mChannels; + voice->mFmtChannels = + (buffer->mChannels == FmtStereo && source->mStereoMode == SourceStereo::Enhanced) ? + FmtSuperStereo : buffer->mChannels; voice->mFmtType = buffer->mType; - voice->mSampleSize = buffer->bytesFromFmt(); - voice->mAmbiLayout = buffer->mAmbiLayout; - voice->mAmbiScaling = buffer->mAmbiScaling; - voice->mAmbiOrder = buffer->mAmbiOrder; + voice->mFrameStep = buffer->channelsFromFmt(); + voice->mFrameSize = buffer->frameSizeFromFmt(); + voice->mAmbiLayout = IsUHJ(voice->mFmtChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout; + voice->mAmbiScaling = IsUHJ(voice->mFmtChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling; + voice->mAmbiOrder = (voice->mFmtChannels == FmtSuperStereo) ? 1 : buffer->mAmbiOrder; - if(buffer->mCallback) voice->mFlags |= VoiceIsCallback; - else if(source->SourceType == AL_STATIC) voice->mFlags |= VoiceIsStatic; + if(buffer->mCallback) voice->mFlags.set(VoiceIsCallback); + else if(source->SourceType == AL_STATIC) voice->mFlags.set(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; + voice->prepare(device); - if(voice->mChans.capacity() > 2 && num_channels < voice->mChans.capacity()) - al::vector{}.swap(voice->mChans); - voice->mChans.reserve(maxu(2, num_channels)); - voice->mChans.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); - - 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 = false; UpdateSourceProps(source, voice, context); voice->mSourceID.store(source->id, std::memory_order_release); @@ -516,7 +527,7 @@ VoiceChange *GetVoiceChanger(ALCcontext *ctx) VoiceChange *vchg{ctx->mVoiceChangeTail}; if UNLIKELY(vchg == ctx->mCurrentVoiceChange.load(std::memory_order_acquire)) { - ctx->allocVoiceChanges(1); + ctx->allocVoiceChanges(); vchg = ctx->mVoiceChangeTail; } @@ -527,7 +538,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 +549,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); } } @@ -569,7 +585,7 @@ bool SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALC } ++vidx; } - if UNLIKELY(!newvoice) + if(unlikely(!newvoice)) { auto &allvoices = *context->mVoices.load(std::memory_order_relaxed); if(allvoices.size() == voicelist.size()) @@ -589,6 +605,7 @@ bool SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALC } ++vidx; } + ASSUME(newvoice != nullptr); } /* Initialize the new voice and set its starting offset. @@ -602,10 +619,10 @@ bool SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALC newvoice->mPosition.store(vpos.pos, std::memory_order_relaxed); newvoice->mPositionFrac.store(vpos.frac, std::memory_order_relaxed); newvoice->mCurrentBuffer.store(vpos.bufferitem, std::memory_order_relaxed); - newvoice->mFlags = 0u; + newvoice->mFlags.reset(); if(vpos.pos > 0 || vpos.frac > 0 || vpos.bufferitem != &source->mQueue.front()) - newvoice->mFlags |= VoiceIsFading; - InitVoice(newvoice, source, &source->mQueue.front(), context, device); + newvoice->mFlags.set(VoiceIsFading); + InitVoice(newvoice, source, vpos.bufferitem, context, device); source->VoiceIdx = vidx; /* Set the old voice as having a pending change, and send it off with the @@ -666,16 +683,6 @@ inline ALenum GetSourceState(ALsource *source, Voice *voice) return source->state; } -/** - * Returns if the source should specify an update, given the context's - * deferring state and the source's last known state. - */ -inline bool SourceShouldUpdate(ALsource *source, ALCcontext *context) -{ - return !context->mDeferUpdates.load(std::memory_order_acquire) && - IsPlayingOrPaused(source); -} - bool EnsureSources(ALCcontext *context, size_t needed) { @@ -707,12 +714,12 @@ ALsource *AllocSource(ALCcontext *context) { auto sublist = std::find_if(context->mSourceList.begin(), context->mSourceList.end(), [](const SourceSubList &entry) noexcept -> bool - { return entry.FreeMask != 0; } - ); + { return entry.FreeMask != 0; }); auto lidx = static_cast(std::distance(context->mSourceList.begin(), sublist)); auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); + ASSUME(slidx < 64); - ALsource *source{::new(sublist->Sources + slidx) ALsource{}}; + ALsource *source{al::construct_at(sublist->Sources + slidx)}; /* Add 1 to avoid source ID 0. */ source->id = ((lidx<<6) | slidx) + 1; @@ -729,19 +736,16 @@ void FreeSource(ALCcontext *context, ALsource *source) const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; - if(IsPlayingOrPaused(source)) + if(Voice *voice{GetSourceVoice(source, context)}) { - if(Voice *voice{GetSourceVoice(source, context)}) - { - VoiceChange *vchg{GetVoiceChanger(context)}; + VoiceChange *vchg{GetVoiceChanger(context)}; - voice->mPendingChange.store(true, std::memory_order_relaxed); - vchg->mVoice = voice; - vchg->mSourceID = source->id; - vchg->mState = VChangeState::Stop; + voice->mPendingChange.store(true, std::memory_order_relaxed); + vchg->mVoice = voice; + vchg->mSourceID = source->id; + vchg->mState = VChangeState::Stop; - SendVoiceChanges(context, vchg); - } + SendVoiceChanges(context, vchg); } al::destroy_at(source); @@ -804,6 +808,26 @@ inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept } +al::optional StereoModeFromEnum(ALenum mode) +{ + switch(mode) + { + case AL_NORMAL_SOFT: return al::make_optional(SourceStereo::Normal); + case AL_SUPER_STEREO_SOFT: return al::make_optional(SourceStereo::Enhanced); + } + WARN("Unsupported stereo mode: 0x%04x\n", mode); + return al::nullopt; +} +ALenum EnumFromStereoMode(SourceStereo mode) +{ + switch(mode) + { + case SourceStereo::Normal: return AL_NORMAL_SOFT; + case SourceStereo::Enhanced: return AL_SUPER_STEREO_SOFT; + } + throw std::runtime_error{"Invalid SourceStereo: "+std::to_string(int(mode))}; +} + al::optional SpatializeModeFromEnum(ALenum mode) { switch(mode) @@ -934,6 +958,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, @@ -943,6 +972,10 @@ enum SourceProp : ALenum { /* ALC_SOFT_device_clock */ srcSampleOffsetClockSOFT = AL_SAMPLE_OFFSET_CLOCK_SOFT, srcSecOffsetClockSOFT = AL_SEC_OFFSET_CLOCK_SOFT, + + /* AL_SOFT_UHJ */ + srcStereoMode = AL_STEREO_MODE_SOFT, + srcSuperStereoWidth = AL_SUPER_STEREO_WIDTH_SOFT, }; @@ -983,6 +1016,11 @@ 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: + case AL_STEREO_MODE_SOFT: + case AL_SUPER_STEREO_WIDTH_SOFT: return 1; case AL_STEREO_ANGLES: @@ -1045,6 +1083,11 @@ 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: + case AL_STEREO_MODE_SOFT: + case AL_SUPER_STEREO_WIDTH_SOFT: return 1; case AL_SEC_OFFSET_LATENCY_SOFT: @@ -1072,43 +1115,70 @@ ALuint DoubleValsByProp(ALenum prop) } -bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); -bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); -bool SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); +void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); +void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); +void SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); #define CHECKSIZE(v, s) do { \ if LIKELY((v).size() == (s) || (v).size() == MaxValues) break; \ Context->setError(AL_INVALID_ENUM, \ "Property 0x%04x expects %d value(s), got %zu", prop, (s), \ (v).size()); \ - return false; \ + return; \ } while(0) #define CHECKVAL(x) do { \ if LIKELY(x) break; \ Context->setError(AL_INVALID_VALUE, "Value out of range"); \ - return false; \ + return; \ } while(0) -bool UpdateSourceProps(ALsource *source, ALCcontext *context) +void UpdateSourceProps(ALsource *source, ALCcontext *context) { - Voice *voice; - if(SourceShouldUpdate(source, context) && (voice=GetSourceVoice(source, context)) != nullptr) - UpdateSourceProps(source, voice, context); - else - source->PropsClean.clear(std::memory_order_release); - return true; + if(!context->mDeferUpdates) + { + if(Voice *voice{GetSourceVoice(source, context)}) + { + UpdateSourceProps(source, voice, context); + return; + } + } + source->mPropsDirty = true; +} +#ifdef ALSOFT_EAX +void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) +{ + if(!context->mDeferUpdates) + { + if(source->eax_is_initialized()) + source->eax_commit(); + if(Voice *voice{GetSourceVoice(source, context)}) + { + UpdateSourceProps(source, voice, context); + return; + } + } + source->mPropsDirty = true; } -bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values) +#else + +inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) +{ UpdateSourceProps(source, context); } +#endif + + +void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, + const al::span values) { int ival; switch(prop) { + case AL_SEC_LENGTH_SOFT: case AL_SEC_OFFSET_LATENCY_SOFT: case AL_SEC_OFFSET_CLOCK_SOFT: /* Query only */ - SETERR_RETURN(Context, AL_INVALID_OPERATION, false, + SETERR_RETURN(Context, AL_INVALID_OPERATION,, "Setting read-only source property 0x%04x", prop); case AL_PITCH: @@ -1123,14 +1193,14 @@ bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a CHECKVAL(values[0] >= 0.0f && values[0] <= 360.0f); Source->InnerAngle = values[0]; - return UpdateSourceProps(Source, Context); + return CommitAndUpdateSourceProps(Source, Context); case AL_CONE_OUTER_ANGLE: CHECKSIZE(values, 1); CHECKVAL(values[0] >= 0.0f && values[0] <= 360.0f); Source->OuterAngle = values[0]; - return UpdateSourceProps(Source, Context); + return CommitAndUpdateSourceProps(Source, Context); case AL_GAIN: CHECKSIZE(values, 1); @@ -1144,21 +1214,21 @@ bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a CHECKVAL(values[0] >= 0.0f); Source->MaxDistance = values[0]; - return UpdateSourceProps(Source, Context); + return CommitAndUpdateSourceProps(Source, Context); case AL_ROLLOFF_FACTOR: CHECKSIZE(values, 1); CHECKVAL(values[0] >= 0.0f); Source->RolloffFactor = values[0]; - return UpdateSourceProps(Source, Context); + return CommitAndUpdateSourceProps(Source, Context); case AL_REFERENCE_DISTANCE: CHECKSIZE(values, 1); CHECKVAL(values[0] >= 0.0f); Source->RefDistance = values[0]; - return UpdateSourceProps(Source, Context); + return CommitAndUpdateSourceProps(Source, Context); case AL_MIN_GAIN: CHECKSIZE(values, 1); @@ -1217,18 +1287,15 @@ bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a if(Voice *voice{GetSourceVoice(Source, Context)}) { - if((voice->mFlags&VoiceIsCallback)) - SETERR_RETURN(Context, AL_INVALID_VALUE, false, - "Source offset for callback is invalid"); auto vpos = GetSampleOffset(Source->mQueue, prop, values[0]); - if(!vpos) SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid offset"); + if(!vpos) SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid offset"); - if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mDevice.get())) - return true; + if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mALDevice.get())) + return; } Source->OffsetType = prop; Source->Offset = values[0]; - return true; + return; case AL_SOURCE_RADIUS: CHECKSIZE(values, 1); @@ -1237,6 +1304,13 @@ bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a Source->Radius = values[0]; return UpdateSourceProps(Source, Context); + case AL_SUPER_STEREO_WIDTH_SOFT: + CHECKSIZE(values, 1); + CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f); + + Source->EnhWidth = values[0]; + return UpdateSourceProps(Source, Context); + case AL_STEREO_ANGLES: CHECKSIZE(values, 2); CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1])); @@ -1253,7 +1327,7 @@ bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a Source->Position[0] = values[0]; Source->Position[1] = values[1]; Source->Position[2] = values[2]; - return UpdateSourceProps(Source, Context); + return CommitAndUpdateSourceProps(Source, Context); case AL_VELOCITY: CHECKSIZE(values, 3); @@ -1262,7 +1336,7 @@ bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a Source->Velocity[0] = values[0]; Source->Velocity[1] = values[1]; Source->Velocity[2] = values[2]; - return UpdateSourceProps(Source, Context); + return CommitAndUpdateSourceProps(Source, Context); case AL_DIRECTION: CHECKSIZE(values, 3); @@ -1271,7 +1345,7 @@ bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a Source->Direction[0] = values[0]; Source->Direction[1] = values[1]; Source->Direction[2] = values[2]; - return UpdateSourceProps(Source, Context); + return CommitAndUpdateSourceProps(Source, Context); case AL_ORIENTATION: CHECKSIZE(values, 6); @@ -1298,6 +1372,9 @@ 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: + case AL_STEREO_MODE_SOFT: CHECKSIZE(values, 1); ival = static_cast(values[0]); return SetSourceiv(Source, Context, prop, {&ival, 1u}); @@ -1318,12 +1395,13 @@ bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a ERR("Unexpected property: 0x%04x\n", prop); Context->setError(AL_INVALID_ENUM, "Invalid source float property 0x%04x", prop); - return false; + return; } -bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values) +void 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,8 +1413,10 @@ 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, + SETERR_RETURN(Context, AL_INVALID_OPERATION,, "Setting read-only source property 0x%04x", prop); case AL_SOURCE_RELATIVE: @@ -1344,37 +1424,33 @@ bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); Source->HeadRelative = values[0] != AL_FALSE; - return UpdateSourceProps(Source, Context); + return CommitAndUpdateSourceProps(Source, Context); case AL_LOOPING: CHECKSIZE(values, 1); CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); Source->Looping = values[0] != AL_FALSE; - if(IsPlayingOrPaused(Source)) + if(Voice *voice{GetSourceVoice(Source, Context)}) { - if(Voice *voice{GetSourceVoice(Source, Context)}) - { - if(Source->Looping) - voice->mLoopBuffer.store(&Source->mQueue.front(), std::memory_order_release); - else - voice->mLoopBuffer.store(nullptr, std::memory_order_release); + if(Source->Looping) + voice->mLoopBuffer.store(&Source->mQueue.front(), std::memory_order_release); + else + voice->mLoopBuffer.store(nullptr, std::memory_order_release); - /* If the source is playing, wait for the current mix to finish - * to ensure it isn't currently looping back or reaching the - * end. - */ - device->waitForMix(); - } + /* If the source is playing, wait for the current mix to finish to + * ensure it isn't currently looping back or reaching the end. + */ + device->waitForMix(); } - return true; + return; case AL_BUFFER: CHECKSIZE(values, 1); { const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; if(state == AL_PLAYING || state == AL_PAUSED) - SETERR_RETURN(Context, AL_INVALID_OPERATION, false, + SETERR_RETURN(Context, AL_INVALID_OPERATION,, "Setting buffer on playing or paused source %u", Source->id); } if(values[0]) @@ -1382,13 +1458,13 @@ bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a std::lock_guard _{device->BufferLock}; ALbuffer *buffer{LookupBuffer(device, static_cast(values[0]))}; if(!buffer) - SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid buffer ID %u", + SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid buffer ID %u", static_cast(values[0])); if(buffer->MappedAccess && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) - SETERR_RETURN(Context, AL_INVALID_OPERATION, false, + SETERR_RETURN(Context, AL_INVALID_OPERATION,, "Setting non-persistently mapped buffer %u", buffer->id); if(buffer->mCallback && ReadRef(buffer->ref) != 0) - SETERR_RETURN(Context, AL_INVALID_OPERATION, false, + SETERR_RETURN(Context, AL_INVALID_OPERATION,, "Setting already-set callback buffer %u", buffer->id); /* Add the selected buffer to a one-item queue */ @@ -1421,7 +1497,7 @@ bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a if(ALbuffer *buffer{item.mBuffer}) DecrementRef(buffer->ref); } - return true; + return; case AL_SEC_OFFSET: case AL_SAMPLE_OFFSET: @@ -1431,18 +1507,15 @@ bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a if(Voice *voice{GetSourceVoice(Source, Context)}) { - if((voice->mFlags&VoiceIsCallback)) - SETERR_RETURN(Context, AL_INVALID_VALUE, false, - "Source offset for callback is invalid"); auto vpos = GetSampleOffset(Source->mQueue, prop, values[0]); - if(!vpos) SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid source offset"); + if(!vpos) SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid source offset"); if(SetVoiceOffset(voice, *vpos, Source, Context, device)) - return true; + return; } Source->OffsetType = prop; Source->Offset = values[0]; - return true; + return; case AL_DIRECT_FILTER: CHECKSIZE(values, 1); @@ -1451,7 +1524,7 @@ bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a std::lock_guard _{device->FilterLock}; ALfilter *filter{LookupFilter(device, static_cast(values[0]))}; if(!filter) - SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid filter ID %u", + SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid filter ID %u", static_cast(values[0])); Source->Direct.Gain = filter->Gain; Source->Direct.GainHF = filter->GainHF; @@ -1499,7 +1572,7 @@ bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a } Context->setError(AL_INVALID_VALUE, "Unsupported AL_DIRECT_CHANNELS_SOFT: 0x%04x\n", values[0]); - return false; + return; case AL_DISTANCE_MODEL: CHECKSIZE(values, 1); @@ -1507,11 +1580,11 @@ bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a { Source->mDistanceModel = *model; if(Context->mSourceDistanceModel) - return UpdateSourceProps(Source, Context); - return true; + UpdateSourceProps(Source, Context); + return; } Context->setError(AL_INVALID_VALUE, "Distance model out of range: 0x%04x", values[0]); - return false; + return; case AL_SOURCE_RESAMPLER_SOFT: CHECKSIZE(values, 1); @@ -1529,22 +1602,39 @@ bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a } Context->setError(AL_INVALID_VALUE, "Unsupported AL_SOURCE_SPATIALIZE_SOFT: 0x%04x\n", values[0]); - return false; + return; + + case AL_STEREO_MODE_SOFT: + CHECKSIZE(values, 1); + { + const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; + if(state == AL_PLAYING || state == AL_PAUSED) + SETERR_RETURN(Context, AL_INVALID_OPERATION,, + "Modifying stereo mode on playing or paused source %u", Source->id); + } + if(auto mode = StereoModeFromEnum(values[0])) + { + Source->mStereoMode = *mode; + return; + } + Context->setError(AL_INVALID_VALUE, "Unsupported AL_STEREO_MODE_SOFT: 0x%04x\n", + values[0]); + return; case AL_AUXILIARY_SEND_FILTER: CHECKSIZE(values, 3); slotlock = std::unique_lock{Context->mEffectSlotLock}; if(values[0] && (slot=LookupEffectSlot(Context, static_cast(values[0]))) == nullptr) - SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid effect ID %u", values[0]); + SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid effect ID %u", values[0]); if(static_cast(values[1]) >= device->NumAuxSends) - SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid send %u", values[1]); + SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid send %u", values[1]); if(values[2]) { std::lock_guard _{device->FilterLock}; ALfilter *filter{LookupFilter(device, static_cast(values[2]))}; if(!filter) - SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid filter ID %u", values[2]); + SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid filter ID %u", values[2]); auto &send = Source->Send[static_cast(values[1])]; send.Gain = filter->Gain; @@ -1577,7 +1667,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 = true; } else { @@ -1587,7 +1677,7 @@ bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a Source->Send[static_cast(values[1])].Slot = slot; UpdateSourceProps(Source, Context); } - return true; + return; /* 1x float */ @@ -1606,6 +1696,8 @@ 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: + case AL_SUPER_STEREO_WIDTH_SOFT: CHECKSIZE(values, 1); fvals[0] = static_cast(values[0]); return SetSourcefv(Source, Context, prop, {fvals, 1u}); @@ -1641,10 +1733,11 @@ bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a ERR("Unexpected property: 0x%04x\n", prop); Context->setError(AL_INVALID_ENUM, "Invalid source integer property 0x%04x", prop); - return false; + return; } -bool SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values) +void SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, + const al::span values) { float fvals[MaxValues]; int ivals[MaxValues]; @@ -1655,10 +1748,12 @@ 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 */ - SETERR_RETURN(Context, AL_INVALID_OPERATION, false, + SETERR_RETURN(Context, AL_INVALID_OPERATION,, "Setting read-only source property 0x%04x", prop); /* 1x int */ @@ -1674,6 +1769,7 @@ bool SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const case AL_DISTANCE_MODEL: case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: + case AL_STEREO_MODE_SOFT: CHECKSIZE(values, 1); CHECKVAL(values[0] <= INT_MAX && values[0] >= INT_MIN); @@ -1716,6 +1812,8 @@ 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: + case AL_SUPER_STEREO_WIDTH_SOFT: CHECKSIZE(values, 1); fvals[0] = static_cast(values[0]); return SetSourcefv(Source, Context, prop, {fvals, 1u}); @@ -1749,11 +1847,19 @@ bool SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const ERR("Unexpected property: 0x%04x\n", prop); Context->setError(AL_INVALID_ENUM, "Invalid source integer64 property 0x%04x", prop); - return false; + return; } #undef CHECKVAL +#undef CHECKSIZE +#define CHECKSIZE(v, s) do { \ + if LIKELY((v).size() == (s) || (v).size() == MaxValues) break; \ + Context->setError(AL_INVALID_ENUM, \ + "Property 0x%04x expects %d value(s), got %zu", prop, (s), \ + (v).size()); \ + return false; \ +} while(0) bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); @@ -1761,7 +1867,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 +1957,18 @@ bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a values[0] = Source->Radius; return true; + case AL_SUPER_STEREO_WIDTH_SOFT: + CHECKSIZE(values, 1); + values[0] = Source->EnhWidth; + return true; + + case AL_BYTE_LENGTH_SOFT: + case AL_SAMPLE_LENGTH_SOFT: + case AL_SEC_LENGTH_SOFT: + CHECKSIZE(values, 1); + values[0] = GetSourceLength(Source, prop); + return true; + case AL_STEREO_ANGLES: CHECKSIZE(values, 2); values[0] = Source->StereoPan[0]; @@ -1865,7 +1983,7 @@ bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a values[0] = GetSourceSecOffset(Source, Context, &srcclock); { std::lock_guard _{device->StateLock}; - clocktime = GetClockLatency(device); + clocktime = GetClockLatency(device, device->Backend.get()); } if(srcclock == clocktime.ClockTime) values[1] = static_cast(clocktime.Latency.count()) / 1000000000.0; @@ -1931,6 +2049,7 @@ bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a case AL_DISTANCE_MODEL: case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: + case AL_STEREO_MODE_SOFT: CHECKSIZE(values, 1); if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) values[0] = static_cast(ivals[0]); @@ -2044,6 +2163,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); @@ -2054,6 +2181,11 @@ bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a values[0] = EnumFromSpatializeMode(Source->mSpatialize); return true; + case AL_STEREO_MODE_SOFT: + CHECKSIZE(values, 1); + values[0] = EnumFromStereoMode(Source->mStereoMode); + return true; + /* 1x float/double */ case AL_CONE_INNER_ANGLE: case AL_CONE_OUTER_ANGLE: @@ -2073,6 +2205,7 @@ bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a case AL_ROOM_ROLLOFF_FACTOR: case AL_CONE_OUTER_GAINHF: case AL_SOURCE_RADIUS: + case AL_SUPER_STEREO_WIDTH_SOFT: CHECKSIZE(values, 1); if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false) values[0] = static_cast(dvals[0]); @@ -2126,7 +2259,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 +2268,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 @@ -2143,7 +2283,7 @@ bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const values[0] = GetSourceSampleOffset(Source, Context, &srcclock); { std::lock_guard _{device->StateLock}; - clocktime = GetClockLatency(device); + clocktime = GetClockLatency(device, device->Backend.get()); } if(srcclock == clocktime.ClockTime) values[1] = clocktime.Latency.count(); @@ -2182,6 +2322,7 @@ bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const case AL_ROOM_ROLLOFF_FACTOR: case AL_CONE_OUTER_GAINHF: case AL_SOURCE_RADIUS: + case AL_SUPER_STEREO_WIDTH_SOFT: CHECKSIZE(values, 1); if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false) values[0] = static_cast(dvals[0]); @@ -2228,6 +2369,7 @@ bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const case AL_DISTANCE_MODEL: case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: + case AL_STEREO_MODE_SOFT: CHECKSIZE(values, 1); if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) values[0] = ivals[0]; @@ -2276,8 +2418,14 @@ START_API_FUNC context->setError(AL_INVALID_VALUE, "Generating %d sources", n); if UNLIKELY(n <= 0) return; +#ifdef ALSOFT_EAX + const bool has_eax{context->has_eax()}; + std::unique_lock proplock{}; + if(has_eax) + proplock = std::unique_lock{context->mPropLock}; +#endif std::unique_lock srclock{context->mSourceLock}; - ALCdevice *device{context->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)", @@ -2294,16 +2442,37 @@ START_API_FUNC { ALsource *source{AllocSource(context.get())}; sources[0] = source->id; + +#ifdef ALSOFT_EAX + if(has_eax) + source->eax_initialize(context.get()); +#endif // ALSOFT_EAX } else { +#ifdef ALSOFT_EAX + auto eax_sources = al::vector{}; + if(has_eax) + eax_sources.reserve(static_cast(n)); +#endif // ALSOFT_EAX + al::vector ids; ids.reserve(static_cast(n)); do { ALsource *source{AllocSource(context.get())}; ids.emplace_back(source->id); + +#ifdef ALSOFT_EAX + if(has_eax) + eax_sources.emplace_back(source); +#endif // ALSOFT_EAX } while(--n); std::copy(ids.cbegin(), ids.cend(), sources); + +#ifdef ALSOFT_EAX + for(auto& eax_source : eax_sources) + eax_source->eax_initialize(context.get()); +#endif // ALSOFT_EAX } } END_API_FUNC @@ -2869,18 +3038,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. */ @@ -2942,6 +3116,7 @@ START_API_FUNC cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed); cur = cur->mNext.load(std::memory_order_relaxed); } + Voice *voice{GetSourceVoice(source, context.get())}; switch(GetSourceState(source, voice)) { @@ -2955,6 +3130,10 @@ START_API_FUNC cur->mSourceID = source->id; cur->mState = VChangeState::Play; source->state = AL_PLAYING; +#ifdef ALSOFT_EAX + if(source->eax_is_initialized()) + source->eax_commit(); +#endif // ALSOFT_EAX continue; case AL_PLAYING: @@ -2971,6 +3150,10 @@ START_API_FUNC default: assert(voice == nullptr); cur->mOldVoice = nullptr; +#ifdef ALSOFT_EAX + if(source->eax_is_initialized()) + source->eax_commit(); +#endif // ALSOFT_EAX break; } @@ -2986,11 +3169,12 @@ START_API_FUNC break; } } + ASSUME(voice != nullptr); voice->mPosition.store(0u, std::memory_order_relaxed); voice->mPositionFrac.store(0, std::memory_order_relaxed); voice->mCurrentBuffer.store(&source->mQueue.front(), std::memory_order_relaxed); - voice->mFlags = 0; + voice->mFlags.reset(); /* A source that's not playing or paused has any offset applied when it * starts playing. */ @@ -3005,7 +3189,7 @@ START_API_FUNC voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed); voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_relaxed); if(vpos->pos!=0 || vpos->frac!=0 || vpos->bufferitem!=&source->mQueue.front()) - voice->mFlags |= VoiceIsFading; + voice->mFlags.set(VoiceIsFading); } } InitVoice(voice, source, std::addressof(*BufferList), context.get(), device); @@ -3247,7 +3431,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 +3580,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; @@ -3412,8 +3607,6 @@ ALsource::ALsource() send.GainLF = 1.0f; send.LFReference = HIGHPASSFREQREF; } - - PropsClean.test_and_set(std::memory_order_relaxed); } ALsource::~ALsource() @@ -3432,18 +3625,47 @@ ALsource::~ALsource() void UpdateAllSourceProps(ALCcontext *context) { std::lock_guard _{context->mSourceLock}; - auto voicelist = context->getVoicesSpan(); - ALuint vidx{0u}; - for(Voice *voice : voicelist) +#ifdef ALSOFT_EAX + if(context->has_eax()) { - ALuint sid{voice->mSourceID.load(std::memory_order_acquire)}; - ALsource *source = sid ? LookupSource(context, sid) : nullptr; - if(source && source->VoiceIdx == vidx) + /* If EAX is enabled, we need to go through and commit all sources' EAX + * changes, along with updating its voice, if any. + */ + for(auto &sublist : context->mSourceList) { - if(!source->PropsClean.test_and_set(std::memory_order_acq_rel)) - UpdateSourceProps(source, voice, context); + uint64_t usemask{~sublist.FreeMask}; + while(usemask) + { + const int idx{al::countr_zero(usemask)}; + usemask &= ~(1_u64 << idx); + + ALsource *source{sublist.Sources + idx}; + source->eax_commit(); + + if(Voice *voice{GetSourceVoice(source, context)}) + { + if(std::exchange(source->mPropsDirty, false)) + UpdateSourceProps(source, voice, context); + } + } + } + } + else +#endif + { + auto voicelist = context->getVoicesSpan(); + ALuint vidx{0u}; + for(Voice *voice : voicelist) + { + ALuint sid{voice->mSourceID.load(std::memory_order_acquire)}; + ALsource *source = sid ? LookupSource(context, sid) : nullptr; + if(source && source->VoiceIdx == vidx) + { + if(std::exchange(source->mPropsDirty, false)) + UpdateSourceProps(source, voice, context); + } + ++vidx; } - ++vidx; } } @@ -3453,10 +3675,2394 @@ SourceSubList::~SourceSubList() while(usemask) { const int idx{al::countr_zero(usemask)}; - al::destroy_at(Sources+idx); usemask &= ~(1_u64 << idx); + al::destroy_at(Sources+idx); } FreeMask = ~usemask; al_free(Sources); Sources = nullptr; } + + +#ifdef ALSOFT_EAX +class EaxSourceException : + public EaxException +{ +public: + explicit EaxSourceException( + const char* message) + : + EaxException{"EAX_SOURCE", message} + { + } +}; // EaxSourceException + + +class EaxSourceActiveFxSlotsException : + public EaxException +{ +public: + explicit EaxSourceActiveFxSlotsException( + const char* message) + : + EaxException{"EAX_SOURCE_ACTIVE_FX_SLOTS", message} + { + } +}; // EaxSourceActiveFxSlotsException + + +class EaxSourceSendException : + public EaxException +{ +public: + explicit EaxSourceSendException( + const char* message) + : + EaxException{"EAX_SOURCE_SEND", message} + { + } +}; // EaxSourceSendException + + +void EaxUpdateSourceVoice(ALsource *source, ALCcontext *context) +{ + if(Voice *voice{GetSourceVoice(source, context)}) + { + if(std::exchange(source->mPropsDirty, false)) + UpdateSourceProps(source, voice, context); + } +} + + +void ALsource::eax_initialize(ALCcontext *context) noexcept +{ + assert(context); + eax_al_context_ = context; + eax_set_defaults(); + eax_initialize_fx_slots(); + + eax_d_ = eax_; +} + +void ALsource::eax_update_filters() +{ + eax_update_filters_internal(); +} + +void ALsource::eax_update(EaxContextSharedDirtyFlags) +{ + /* NOTE: EaxContextSharedDirtyFlags only has one flag (primary_fx_slot_id), + * which must be true for this to be called. + */ + if(eax_uses_primary_id_) + eax_update_primary_fx_slot_id(); +} + +void ALsource::eax_commit_and_update() +{ + eax_apply_deferred(); + EaxUpdateSourceVoice(this, eax_al_context_); +} + +ALsource* ALsource::eax_lookup_source( + ALCcontext& al_context, + ALuint source_id) noexcept +{ + return LookupSource(&al_context, source_id); +} + +[[noreturn]] +void ALsource::eax_fail( + const char* message) +{ + throw EaxSourceException{message}; +} + +void ALsource::eax_set_source_defaults() noexcept +{ + eax1_.fMix = EAX_REVERBMIX_USEDISTANCE; + + eax_.source.lDirect = EAXSOURCE_DEFAULTDIRECT; + eax_.source.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF; + eax_.source.lRoom = EAXSOURCE_DEFAULTROOM; + eax_.source.lRoomHF = EAXSOURCE_DEFAULTROOMHF; + eax_.source.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION; + eax_.source.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO; + eax_.source.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; + eax_.source.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; + eax_.source.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; + eax_.source.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; + eax_.source.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; + eax_.source.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO; + eax_.source.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF; + eax_.source.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR; + eax_.source.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR; + eax_.source.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR; + eax_.source.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR; + eax_.source.ulFlags = EAXSOURCE_DEFAULTFLAGS; + eax_.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; +} + +void ALsource::eax_set_active_fx_slots_defaults() noexcept +{ + eax_.active_fx_slots = EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; +} + +void ALsource::eax_set_send_defaults(EAXSOURCEALLSENDPROPERTIES& eax_send) noexcept +{ + eax_send.guidReceivingFXSlotID = EAX_NULL_GUID; + eax_send.lSend = EAXSOURCE_DEFAULTSEND; + eax_send.lSendHF = EAXSOURCE_DEFAULTSENDHF; + eax_send.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; + eax_send.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; + eax_send.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; + eax_send.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; + eax_send.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; + eax_send.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO; +} + +void ALsource::eax_set_sends_defaults() noexcept +{ + for (auto& eax_send : eax_.sends) + { + eax_set_send_defaults(eax_send); + } +} + +void ALsource::eax_set_speaker_levels_defaults() noexcept +{ + std::fill(eax_.speaker_levels.begin(), eax_.speaker_levels.end(), EAXSOURCE_DEFAULTSPEAKERLEVEL); +} + +void ALsource::eax_set_defaults() noexcept +{ + eax_set_source_defaults(); + eax_set_active_fx_slots_defaults(); + eax_set_sends_defaults(); + eax_set_speaker_levels_defaults(); +} + +float ALsource::eax_calculate_dst_occlusion_mb( + long src_occlusion_mb, + float path_ratio, + float lf_ratio) noexcept +{ + const auto ratio_1 = path_ratio + lf_ratio - 1.0F; + const auto ratio_2 = path_ratio * lf_ratio; + const auto ratio = (ratio_2 > ratio_1) ? ratio_2 : ratio_1; + const auto dst_occlustion_mb = static_cast(src_occlusion_mb) * ratio; + + return dst_occlustion_mb; +} + +EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept +{ + auto gain_mb = + static_cast(eax_.source.lDirect) + + + (static_cast(eax_.source.lObstruction) * eax_.source.flObstructionLFRatio) + + + eax_calculate_dst_occlusion_mb( + eax_.source.lOcclusion, + eax_.source.flOcclusionDirectRatio, + eax_.source.flOcclusionLFRatio); + + auto gain_hf_mb = + static_cast(eax_.source.lDirectHF) + + + static_cast(eax_.source.lObstruction) + + + (static_cast(eax_.source.lOcclusion) * eax_.source.flOcclusionDirectRatio); + + for (auto i = std::size_t{}; i < EAX_MAX_FXSLOTS; ++i) + { + if (eax_active_fx_slots_[i]) + { + const auto& send = eax_.sends[i]; + + gain_mb += eax_calculate_dst_occlusion_mb( + send.lOcclusion, + send.flOcclusionDirectRatio, + send.flOcclusionLFRatio); + + gain_hf_mb += static_cast(send.lOcclusion) * send.flOcclusionDirectRatio; + } + } + + const auto al_low_pass_param = EaxAlLowPassParam + { + level_mb_to_gain(gain_mb), + minf(level_mb_to_gain(gain_hf_mb), 1.0f) + }; + + return al_low_pass_param; +} + +EaxAlLowPassParam ALsource::eax_create_room_filter_param( + const ALeffectslot& fx_slot, + const EAXSOURCEALLSENDPROPERTIES& send) const noexcept +{ + const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot(); + + const auto gain_mb = + static_cast( + eax_.source.lRoom + + send.lSend) + + + eax_calculate_dst_occlusion_mb( + eax_.source.lOcclusion, + eax_.source.flOcclusionRoomRatio, + eax_.source.flOcclusionLFRatio + ) + + + eax_calculate_dst_occlusion_mb( + send.lOcclusion, + send.flOcclusionRoomRatio, + send.flOcclusionLFRatio + ) + + + (static_cast(eax_.source.lExclusion) * eax_.source.flExclusionLFRatio) + + (static_cast(send.lExclusion) * send.flExclusionLFRatio) + + + 0.0F; + + const auto gain_hf_mb = + static_cast( + eax_.source.lRoomHF + + send.lSendHF) + + + (static_cast(fx_slot_eax.lOcclusion + eax_.source.lOcclusion) * eax_.source.flOcclusionRoomRatio) + + (static_cast(send.lOcclusion) * send.flOcclusionRoomRatio) + + + static_cast( + eax_.source.lExclusion + + send.lExclusion) + + + 0.0F; + + const auto al_low_pass_param = EaxAlLowPassParam + { + level_mb_to_gain(gain_mb), + minf(level_mb_to_gain(gain_hf_mb), 1.0f) + }; + + return al_low_pass_param; +} + +void ALsource::eax_set_fx_slots() +{ + eax_uses_primary_id_ = false; + eax_has_active_fx_slots_ = false; + eax_active_fx_slots_.fill(false); + + for(const auto& eax_active_fx_slot_id : eax_.active_fx_slots.guidActiveFXSlots) + { + auto fx_slot_index = EaxFxSlotIndex{}; + + if(eax_active_fx_slot_id == EAX_PrimaryFXSlotID) + { + eax_uses_primary_id_ = true; + fx_slot_index = eax_al_context_->eax_get_primary_fx_slot_index(); + } + else + { + fx_slot_index = eax_active_fx_slot_id; + } + + if(fx_slot_index.has_value()) + { + eax_has_active_fx_slots_ = true; + eax_active_fx_slots_[*fx_slot_index] = true; + } + } + + for(auto i = 0u;i < eax_active_fx_slots_.size();++i) + { + if(!eax_active_fx_slots_[i]) + eax_set_al_source_send(nullptr, i, EaxAlLowPassParam{1.0f, 1.0f}); + } +} + +void ALsource::eax_initialize_fx_slots() +{ + eax_set_fx_slots(); + eax_update_filters_internal(); +} + +void ALsource::eax_update_direct_filter_internal() +{ + const auto& direct_param = eax_create_direct_filter_param(); + + Direct.Gain = direct_param.gain; + Direct.GainHF = direct_param.gain_hf; + Direct.HFReference = LOWPASSFREQREF; + Direct.GainLF = 1.0f; + Direct.LFReference = HIGHPASSFREQREF; + mPropsDirty = true; +} + +void ALsource::eax_update_room_filters_internal() +{ + if (!eax_has_active_fx_slots_) + { + return; + } + + for (auto i = 0u; i < EAX_MAX_FXSLOTS; ++i) + { + if (eax_active_fx_slots_[i]) + { + auto& fx_slot = eax_al_context_->eax_get_fx_slot(static_cast(i)); + const auto& send = eax_.sends[i]; + const auto& room_param = eax_create_room_filter_param(fx_slot, send); + + eax_set_al_source_send(&fx_slot, i, room_param); + } + } +} + +void ALsource::eax_update_filters_internal() +{ + eax_update_direct_filter_internal(); + eax_update_room_filters_internal(); +} + +void ALsource::eax_update_primary_fx_slot_id() +{ + const auto& previous_primary_fx_slot_index = eax_al_context_->eax_get_previous_primary_fx_slot_index(); + const auto& primary_fx_slot_index = eax_al_context_->eax_get_primary_fx_slot_index(); + + if (previous_primary_fx_slot_index == primary_fx_slot_index) + { + return; + } + + if (previous_primary_fx_slot_index.has_value()) + { + const auto fx_slot_index = previous_primary_fx_slot_index.value(); + eax_active_fx_slots_[fx_slot_index] = false; + + eax_set_al_source_send(nullptr, fx_slot_index, EaxAlLowPassParam{1.0f, 1.0f}); + } + + if (primary_fx_slot_index.has_value()) + { + const auto fx_slot_index = primary_fx_slot_index.value(); + eax_active_fx_slots_[fx_slot_index] = true; + + auto& fx_slot = eax_al_context_->eax_get_fx_slot(fx_slot_index); + const auto& send = eax_.sends[fx_slot_index]; + const auto& room_param = eax_create_room_filter_param(fx_slot, send); + + eax_set_al_source_send(&fx_slot, fx_slot_index, room_param); + } + + eax_has_active_fx_slots_ = std::any_of( + eax_active_fx_slots_.cbegin(), + eax_active_fx_slots_.cend(), + [](const auto& item) + { + return item; + } + ); +} + +void ALsource::eax_defer_active_fx_slots( + const EaxEaxCall& eax_call) +{ + const auto active_fx_slots_span = + eax_call.get_values(); + + const auto fx_slot_count = active_fx_slots_span.size(); + + if (fx_slot_count <= 0 || fx_slot_count > EAX_MAX_FXSLOTS) + { + throw EaxSourceActiveFxSlotsException{"Count out of range."}; + } + + for (auto i = std::size_t{}; i < fx_slot_count; ++i) + { + const auto& fx_slot_guid = active_fx_slots_span[i]; + + if (fx_slot_guid != EAX_NULL_GUID && + fx_slot_guid != EAX_PrimaryFXSlotID && + fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot0 && + fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot0 && + fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot1 && + fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot1 && + fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot2 && + fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot2 && + fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot3 && + fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot3) + { + throw EaxSourceActiveFxSlotsException{"Unsupported GUID."}; + } + } + + for (auto i = std::size_t{}; i < fx_slot_count; ++i) + { + eax_d_.active_fx_slots.guidActiveFXSlots[i] = active_fx_slots_span[i]; + } + + for (auto i = fx_slot_count; i < EAX_MAX_FXSLOTS; ++i) + { + eax_d_.active_fx_slots.guidActiveFXSlots[i] = EAX_NULL_GUID; + } + + eax_are_active_fx_slots_dirty_ = (eax_d_.active_fx_slots != eax_.active_fx_slots); +} + + +const char* ALsource::eax_get_exclusion_name() noexcept +{ + return "Exclusion"; +} + +const char* ALsource::eax_get_exclusion_lf_ratio_name() noexcept +{ + return "Exclusion LF Ratio"; +} + +const char* ALsource::eax_get_occlusion_name() noexcept +{ + return "Occlusion"; +} + +const char* ALsource::eax_get_occlusion_lf_ratio_name() noexcept +{ + return "Occlusion LF Ratio"; +} + +const char* ALsource::eax_get_occlusion_direct_ratio_name() noexcept +{ + return "Occlusion Direct Ratio"; +} + +const char* ALsource::eax_get_occlusion_room_ratio_name() noexcept +{ + return "Occlusion Room Ratio"; +} + +void ALsource::eax1_validate_reverb_mix(float reverb_mix) +{ + if (reverb_mix == EAX_REVERBMIX_USEDISTANCE) + return; + + eax_validate_range("Reverb Mix", reverb_mix, EAX_BUFFER_MINREVERBMIX, EAX_BUFFER_MAXREVERBMIX); +} + +void ALsource::eax_validate_send_receiving_fx_slot_guid( + const GUID& guidReceivingFXSlotID) +{ + if (guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot0 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot0 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot1 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot1 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot2 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot2 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot3 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot3) + { + throw EaxSourceSendException{"Unsupported receiving FX slot GUID."}; + } +} + +void ALsource::eax_validate_send_send( + long lSend) +{ + eax_validate_range( + "Send", + lSend, + EAXSOURCE_MINSEND, + EAXSOURCE_MAXSEND); +} + +void ALsource::eax_validate_send_send_hf( + long lSendHF) +{ + eax_validate_range( + "Send HF", + lSendHF, + EAXSOURCE_MINSENDHF, + EAXSOURCE_MAXSENDHF); +} + +void ALsource::eax_validate_send_occlusion( + long lOcclusion) +{ + eax_validate_range( + eax_get_occlusion_name(), + lOcclusion, + EAXSOURCE_MINOCCLUSION, + EAXSOURCE_MAXOCCLUSION); +} + +void ALsource::eax_validate_send_occlusion_lf_ratio( + float flOcclusionLFRatio) +{ + eax_validate_range( + eax_get_occlusion_lf_ratio_name(), + flOcclusionLFRatio, + EAXSOURCE_MINOCCLUSIONLFRATIO, + EAXSOURCE_MAXOCCLUSIONLFRATIO); +} + +void ALsource::eax_validate_send_occlusion_room_ratio( + float flOcclusionRoomRatio) +{ + eax_validate_range( + eax_get_occlusion_room_ratio_name(), + flOcclusionRoomRatio, + EAXSOURCE_MINOCCLUSIONROOMRATIO, + EAXSOURCE_MAXOCCLUSIONROOMRATIO); +} + +void ALsource::eax_validate_send_occlusion_direct_ratio( + float flOcclusionDirectRatio) +{ + eax_validate_range( + eax_get_occlusion_direct_ratio_name(), + flOcclusionDirectRatio, + EAXSOURCE_MINOCCLUSIONDIRECTRATIO, + EAXSOURCE_MAXOCCLUSIONDIRECTRATIO); +} + +void ALsource::eax_validate_send_exclusion( + long lExclusion) +{ + eax_validate_range( + eax_get_exclusion_name(), + lExclusion, + EAXSOURCE_MINEXCLUSION, + EAXSOURCE_MAXEXCLUSION); +} + +void ALsource::eax_validate_send_exclusion_lf_ratio( + float flExclusionLFRatio) +{ + eax_validate_range( + eax_get_exclusion_lf_ratio_name(), + flExclusionLFRatio, + EAXSOURCE_MINEXCLUSIONLFRATIO, + EAXSOURCE_MAXEXCLUSIONLFRATIO); +} + +void ALsource::eax_validate_send( + const EAXSOURCESENDPROPERTIES& all) +{ + eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID); + eax_validate_send_send(all.lSend); + eax_validate_send_send_hf(all.lSendHF); +} + +void ALsource::eax_validate_send_exclusion_all( + const EAXSOURCEEXCLUSIONSENDPROPERTIES& all) +{ + eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID); + eax_validate_send_exclusion(all.lExclusion); + eax_validate_send_exclusion_lf_ratio(all.flExclusionLFRatio); +} + +void ALsource::eax_validate_send_occlusion_all( + const EAXSOURCEOCCLUSIONSENDPROPERTIES& all) +{ + eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID); + eax_validate_send_occlusion(all.lOcclusion); + eax_validate_send_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_validate_send_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_validate_send_occlusion_direct_ratio(all.flOcclusionDirectRatio); +} + +void ALsource::eax_validate_send_all( + const EAXSOURCEALLSENDPROPERTIES& all) +{ + eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID); + eax_validate_send_send(all.lSend); + eax_validate_send_send_hf(all.lSendHF); + eax_validate_send_occlusion(all.lOcclusion); + eax_validate_send_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_validate_send_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_validate_send_occlusion_direct_ratio(all.flOcclusionDirectRatio); + eax_validate_send_exclusion(all.lExclusion); + eax_validate_send_exclusion_lf_ratio(all.flExclusionLFRatio); +} + +EaxFxSlotIndexValue ALsource::eax_get_send_index( + const GUID& send_guid) +{ + if (false) + { + } + else if (send_guid == EAXPROPERTYID_EAX40_FXSlot0 || send_guid == EAXPROPERTYID_EAX50_FXSlot0) + { + return 0; + } + else if (send_guid == EAXPROPERTYID_EAX40_FXSlot1 || send_guid == EAXPROPERTYID_EAX50_FXSlot1) + { + return 1; + } + else if (send_guid == EAXPROPERTYID_EAX40_FXSlot2 || send_guid == EAXPROPERTYID_EAX50_FXSlot2) + { + return 2; + } + else if (send_guid == EAXPROPERTYID_EAX40_FXSlot3 || send_guid == EAXPROPERTYID_EAX50_FXSlot3) + { + return 3; + } + else + { + throw EaxSourceSendException{"Unsupported receiving FX slot GUID."}; + } +} + +void ALsource::eax_defer_send_send( + long lSend, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].lSend = lSend; + + eax_sends_dirty_flags_.sends[index].lSend = + (eax_.sends[index].lSend != eax_d_.sends[index].lSend); +} + +void ALsource::eax_defer_send_send_hf( + long lSendHF, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].lSendHF = lSendHF; + + eax_sends_dirty_flags_.sends[index].lSendHF = + (eax_.sends[index].lSendHF != eax_d_.sends[index].lSendHF); +} + +void ALsource::eax_defer_send_occlusion( + long lOcclusion, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].lOcclusion = lOcclusion; + + eax_sends_dirty_flags_.sends[index].lOcclusion = + (eax_.sends[index].lOcclusion != eax_d_.sends[index].lOcclusion); +} + +void ALsource::eax_defer_send_occlusion_lf_ratio( + float flOcclusionLFRatio, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].flOcclusionLFRatio = flOcclusionLFRatio; + + eax_sends_dirty_flags_.sends[index].flOcclusionLFRatio = + (eax_.sends[index].flOcclusionLFRatio != eax_d_.sends[index].flOcclusionLFRatio); +} + +void ALsource::eax_defer_send_occlusion_room_ratio( + float flOcclusionRoomRatio, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].flOcclusionRoomRatio = flOcclusionRoomRatio; + + eax_sends_dirty_flags_.sends[index].flOcclusionRoomRatio = + (eax_.sends[index].flOcclusionRoomRatio != eax_d_.sends[index].flOcclusionRoomRatio); +} + +void ALsource::eax_defer_send_occlusion_direct_ratio( + float flOcclusionDirectRatio, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].flOcclusionDirectRatio = flOcclusionDirectRatio; + + eax_sends_dirty_flags_.sends[index].flOcclusionDirectRatio = + (eax_.sends[index].flOcclusionDirectRatio != eax_d_.sends[index].flOcclusionDirectRatio); +} + +void ALsource::eax_defer_send_exclusion( + long lExclusion, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].lExclusion = lExclusion; + + eax_sends_dirty_flags_.sends[index].lExclusion = + (eax_.sends[index].lExclusion != eax_d_.sends[index].lExclusion); +} + +void ALsource::eax_defer_send_exclusion_lf_ratio( + float flExclusionLFRatio, + EaxFxSlotIndexValue index) +{ + eax_d_.sends[index].flExclusionLFRatio = flExclusionLFRatio; + + eax_sends_dirty_flags_.sends[index].flExclusionLFRatio = + (eax_.sends[index].flExclusionLFRatio != eax_d_.sends[index].flExclusionLFRatio); +} + +void ALsource::eax_defer_send( + const EAXSOURCESENDPROPERTIES& all, + EaxFxSlotIndexValue index) +{ + eax_defer_send_send(all.lSend, index); + eax_defer_send_send_hf(all.lSendHF, index); +} + +void ALsource::eax_defer_send_exclusion_all( + const EAXSOURCEEXCLUSIONSENDPROPERTIES& all, + EaxFxSlotIndexValue index) +{ + eax_defer_send_exclusion(all.lExclusion, index); + eax_defer_send_exclusion_lf_ratio(all.flExclusionLFRatio, index); +} + +void ALsource::eax_defer_send_occlusion_all( + const EAXSOURCEOCCLUSIONSENDPROPERTIES& all, + EaxFxSlotIndexValue index) +{ + eax_defer_send_occlusion(all.lOcclusion, index); + eax_defer_send_occlusion_lf_ratio(all.flOcclusionLFRatio, index); + eax_defer_send_occlusion_room_ratio(all.flOcclusionRoomRatio, index); + eax_defer_send_occlusion_direct_ratio(all.flOcclusionDirectRatio, index); +} + +void ALsource::eax_defer_send_all( + const EAXSOURCEALLSENDPROPERTIES& all, + EaxFxSlotIndexValue index) +{ + eax_defer_send_send(all.lSend, index); + eax_defer_send_send_hf(all.lSendHF, index); + eax_defer_send_occlusion(all.lOcclusion, index); + eax_defer_send_occlusion_lf_ratio(all.flOcclusionLFRatio, index); + eax_defer_send_occlusion_room_ratio(all.flOcclusionRoomRatio, index); + eax_defer_send_occlusion_direct_ratio(all.flOcclusionDirectRatio, index); + eax_defer_send_exclusion(all.lExclusion, index); + eax_defer_send_exclusion_lf_ratio(all.flExclusionLFRatio, index); +} + +void ALsource::eax_defer_send( + const EaxEaxCall& eax_call) +{ + const auto eax_all_span = + eax_call.get_values(); + + const auto count = eax_all_span.size(); + + if (count <= 0 || count > EAX_MAX_FXSLOTS) + { + throw EaxSourceSendException{"Send count out of range."}; + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + eax_validate_send(all); + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID); + eax_defer_send(all, send_index); + } +} + +void ALsource::eax_defer_send_exclusion_all( + const EaxEaxCall& eax_call) +{ + const auto eax_all_span = + eax_call.get_values(); + + const auto count = eax_all_span.size(); + + if (count <= 0 || count > EAX_MAX_FXSLOTS) + { + throw EaxSourceSendException{"Send exclusion all count out of range."}; + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + eax_validate_send_exclusion_all(all); + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID); + eax_defer_send_exclusion_all(all, send_index); + } +} + +void ALsource::eax_defer_send_occlusion_all( + const EaxEaxCall& eax_call) +{ + const auto eax_all_span = + eax_call.get_values(); + + const auto count = eax_all_span.size(); + + if (count <= 0 || count > EAX_MAX_FXSLOTS) + { + throw EaxSourceSendException{"Send occlusion all count out of range."}; + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + eax_validate_send_occlusion_all(all); + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID); + eax_defer_send_occlusion_all(all, send_index); + } +} + +void ALsource::eax_defer_send_all( + const EaxEaxCall& eax_call) +{ + const auto eax_all_span = + eax_call.get_values(); + + const auto count = eax_all_span.size(); + + if (count <= 0 || count > EAX_MAX_FXSLOTS) + { + throw EaxSourceSendException{"Send all count out of range."}; + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + eax_validate_send_all(all); + } + + for (auto i = std::size_t{}; i < count; ++i) + { + const auto& all = eax_all_span[i]; + const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID); + eax_defer_send_all(all, send_index); + } +} + + +void ALsource::eax_validate_source_direct( + long direct) +{ + eax_validate_range( + "Direct", + direct, + EAXSOURCE_MINDIRECT, + EAXSOURCE_MAXDIRECT); +} + +void ALsource::eax_validate_source_direct_hf( + long direct_hf) +{ + eax_validate_range( + "Direct HF", + direct_hf, + EAXSOURCE_MINDIRECTHF, + EAXSOURCE_MAXDIRECTHF); +} + +void ALsource::eax_validate_source_room( + long room) +{ + eax_validate_range( + "Room", + room, + EAXSOURCE_MINROOM, + EAXSOURCE_MAXROOM); +} + +void ALsource::eax_validate_source_room_hf( + long room_hf) +{ + eax_validate_range( + "Room HF", + room_hf, + EAXSOURCE_MINROOMHF, + EAXSOURCE_MAXROOMHF); +} + +void ALsource::eax_validate_source_obstruction( + long obstruction) +{ + eax_validate_range( + "Obstruction", + obstruction, + EAXSOURCE_MINOBSTRUCTION, + EAXSOURCE_MAXOBSTRUCTION); +} + +void ALsource::eax_validate_source_obstruction_lf_ratio( + float obstruction_lf_ratio) +{ + eax_validate_range( + "Obstruction LF Ratio", + obstruction_lf_ratio, + EAXSOURCE_MINOBSTRUCTIONLFRATIO, + EAXSOURCE_MAXOBSTRUCTIONLFRATIO); +} + +void ALsource::eax_validate_source_occlusion( + long occlusion) +{ + eax_validate_range( + eax_get_occlusion_name(), + occlusion, + EAXSOURCE_MINOCCLUSION, + EAXSOURCE_MAXOCCLUSION); +} + +void ALsource::eax_validate_source_occlusion_lf_ratio( + float occlusion_lf_ratio) +{ + eax_validate_range( + eax_get_occlusion_lf_ratio_name(), + occlusion_lf_ratio, + EAXSOURCE_MINOCCLUSIONLFRATIO, + EAXSOURCE_MAXOCCLUSIONLFRATIO); +} + +void ALsource::eax_validate_source_occlusion_room_ratio( + float occlusion_room_ratio) +{ + eax_validate_range( + eax_get_occlusion_room_ratio_name(), + occlusion_room_ratio, + EAXSOURCE_MINOCCLUSIONROOMRATIO, + EAXSOURCE_MAXOCCLUSIONROOMRATIO); +} + +void ALsource::eax_validate_source_occlusion_direct_ratio( + float occlusion_direct_ratio) +{ + eax_validate_range( + eax_get_occlusion_direct_ratio_name(), + occlusion_direct_ratio, + EAXSOURCE_MINOCCLUSIONDIRECTRATIO, + EAXSOURCE_MAXOCCLUSIONDIRECTRATIO); +} + +void ALsource::eax_validate_source_exclusion( + long exclusion) +{ + eax_validate_range( + eax_get_exclusion_name(), + exclusion, + EAXSOURCE_MINEXCLUSION, + EAXSOURCE_MAXEXCLUSION); +} + +void ALsource::eax_validate_source_exclusion_lf_ratio( + float exclusion_lf_ratio) +{ + eax_validate_range( + eax_get_exclusion_lf_ratio_name(), + exclusion_lf_ratio, + EAXSOURCE_MINEXCLUSIONLFRATIO, + EAXSOURCE_MAXEXCLUSIONLFRATIO); +} + +void ALsource::eax_validate_source_outside_volume_hf( + long outside_volume_hf) +{ + eax_validate_range( + "Outside Volume HF", + outside_volume_hf, + EAXSOURCE_MINOUTSIDEVOLUMEHF, + EAXSOURCE_MAXOUTSIDEVOLUMEHF); +} + +void ALsource::eax_validate_source_doppler_factor( + float doppler_factor) +{ + eax_validate_range( + "Doppler Factor", + doppler_factor, + EAXSOURCE_MINDOPPLERFACTOR, + EAXSOURCE_MAXDOPPLERFACTOR); +} + +void ALsource::eax_validate_source_rolloff_factor( + float rolloff_factor) +{ + eax_validate_range( + "Rolloff Factor", + rolloff_factor, + EAXSOURCE_MINROLLOFFFACTOR, + EAXSOURCE_MAXROLLOFFFACTOR); +} + +void ALsource::eax_validate_source_room_rolloff_factor( + float room_rolloff_factor) +{ + eax_validate_range( + "Room Rolloff Factor", + room_rolloff_factor, + EAXSOURCE_MINROOMROLLOFFFACTOR, + EAXSOURCE_MAXROOMROLLOFFFACTOR); +} + +void ALsource::eax_validate_source_air_absorption_factor( + float air_absorption_factor) +{ + eax_validate_range( + "Air Absorption Factor", + air_absorption_factor, + EAXSOURCE_MINAIRABSORPTIONFACTOR, + EAXSOURCE_MAXAIRABSORPTIONFACTOR); +} + +void ALsource::eax_validate_source_flags( + unsigned long flags, + int eax_version) +{ + eax_validate_range( + "Flags", + flags, + 0UL, + ~((eax_version == 5) ? EAX50SOURCEFLAGS_RESERVED : EAX20SOURCEFLAGS_RESERVED)); +} + +void ALsource::eax_validate_source_macro_fx_factor( + float macro_fx_factor) +{ + eax_validate_range( + "Macro FX Factor", + macro_fx_factor, + EAXSOURCE_MINMACROFXFACTOR, + EAXSOURCE_MAXMACROFXFACTOR); +} + +void ALsource::eax_validate_source_2d_all( + const EAXSOURCE2DPROPERTIES& all, + int eax_version) +{ + eax_validate_source_direct(all.lDirect); + eax_validate_source_direct_hf(all.lDirectHF); + eax_validate_source_room(all.lRoom); + eax_validate_source_room_hf(all.lRoomHF); + eax_validate_source_flags(all.ulFlags, eax_version); +} + +void ALsource::eax_validate_source_obstruction_all( + const EAXOBSTRUCTIONPROPERTIES& all) +{ + eax_validate_source_obstruction(all.lObstruction); + eax_validate_source_obstruction_lf_ratio(all.flObstructionLFRatio); +} + +void ALsource::eax_validate_source_exclusion_all( + const EAXEXCLUSIONPROPERTIES& all) +{ + eax_validate_source_exclusion(all.lExclusion); + eax_validate_source_exclusion_lf_ratio(all.flExclusionLFRatio); +} + +void ALsource::eax_validate_source_occlusion_all( + const EAXOCCLUSIONPROPERTIES& all) +{ + eax_validate_source_occlusion(all.lOcclusion); + eax_validate_source_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_validate_source_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_validate_source_occlusion_direct_ratio(all.flOcclusionDirectRatio); +} + +void ALsource::eax_validate_source_all( + const EAX20BUFFERPROPERTIES& all, + int eax_version) +{ + eax_validate_source_direct(all.lDirect); + eax_validate_source_direct_hf(all.lDirectHF); + eax_validate_source_room(all.lRoom); + eax_validate_source_room_hf(all.lRoomHF); + eax_validate_source_obstruction(all.lObstruction); + eax_validate_source_obstruction_lf_ratio(all.flObstructionLFRatio); + eax_validate_source_occlusion(all.lOcclusion); + eax_validate_source_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_validate_source_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_validate_source_outside_volume_hf(all.lOutsideVolumeHF); + eax_validate_source_room_rolloff_factor(all.flRoomRolloffFactor); + eax_validate_source_air_absorption_factor(all.flAirAbsorptionFactor); + eax_validate_source_flags(all.dwFlags, eax_version); +} + +void ALsource::eax_validate_source_all( + const EAX30SOURCEPROPERTIES& all, + int eax_version) +{ + eax_validate_source_direct(all.lDirect); + eax_validate_source_direct_hf(all.lDirectHF); + eax_validate_source_room(all.lRoom); + eax_validate_source_room_hf(all.lRoomHF); + eax_validate_source_obstruction(all.lObstruction); + eax_validate_source_obstruction_lf_ratio(all.flObstructionLFRatio); + eax_validate_source_occlusion(all.lOcclusion); + eax_validate_source_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_validate_source_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_validate_source_occlusion_direct_ratio(all.flOcclusionDirectRatio); + eax_validate_source_exclusion(all.lExclusion); + eax_validate_source_exclusion_lf_ratio(all.flExclusionLFRatio); + eax_validate_source_outside_volume_hf(all.lOutsideVolumeHF); + eax_validate_source_doppler_factor(all.flDopplerFactor); + eax_validate_source_rolloff_factor(all.flRolloffFactor); + eax_validate_source_room_rolloff_factor(all.flRoomRolloffFactor); + eax_validate_source_air_absorption_factor(all.flAirAbsorptionFactor); + eax_validate_source_flags(all.ulFlags, eax_version); +} + +void ALsource::eax_validate_source_all( + const EAX50SOURCEPROPERTIES& all, + int eax_version) +{ + eax_validate_source_all(static_cast(all), eax_version); + eax_validate_source_macro_fx_factor(all.flMacroFXFactor); +} + +void ALsource::eax_validate_source_speaker_id( + long speaker_id) +{ + eax_validate_range( + "Speaker Id", + speaker_id, + static_cast(EAXSPEAKER_FRONT_LEFT), + static_cast(EAXSPEAKER_LOW_FREQUENCY)); +} + +void ALsource::eax_validate_source_speaker_level( + long speaker_level) +{ + eax_validate_range( + "Speaker Level", + speaker_level, + EAXSOURCE_MINSPEAKERLEVEL, + EAXSOURCE_MAXSPEAKERLEVEL); +} + +void ALsource::eax_validate_source_speaker_level_all( + const EAXSPEAKERLEVELPROPERTIES& all) +{ + eax_validate_source_speaker_id(all.lSpeakerID); + eax_validate_source_speaker_level(all.lLevel); +} + +void ALsource::eax_defer_source_direct( + long lDirect) +{ + eax_d_.source.lDirect = lDirect; + eax_source_dirty_filter_flags_.lDirect = (eax_.source.lDirect != eax_d_.source.lDirect); +} + +void ALsource::eax_defer_source_direct_hf( + long lDirectHF) +{ + eax_d_.source.lDirectHF = lDirectHF; + eax_source_dirty_filter_flags_.lDirectHF = (eax_.source.lDirectHF != eax_d_.source.lDirectHF); +} + +void ALsource::eax_defer_source_room( + long lRoom) +{ + eax_d_.source.lRoom = lRoom; + eax_source_dirty_filter_flags_.lRoom = (eax_.source.lRoom != eax_d_.source.lRoom); +} + +void ALsource::eax_defer_source_room_hf( + long lRoomHF) +{ + eax_d_.source.lRoomHF = lRoomHF; + eax_source_dirty_filter_flags_.lRoomHF = (eax_.source.lRoomHF != eax_d_.source.lRoomHF); +} + +void ALsource::eax_defer_source_obstruction( + long lObstruction) +{ + eax_d_.source.lObstruction = lObstruction; + eax_source_dirty_filter_flags_.lObstruction = (eax_.source.lObstruction != eax_d_.source.lObstruction); +} + +void ALsource::eax_defer_source_obstruction_lf_ratio( + float flObstructionLFRatio) +{ + eax_d_.source.flObstructionLFRatio = flObstructionLFRatio; + eax_source_dirty_filter_flags_.flObstructionLFRatio = (eax_.source.flObstructionLFRatio != eax_d_.source.flObstructionLFRatio); +} + +void ALsource::eax_defer_source_occlusion( + long lOcclusion) +{ + eax_d_.source.lOcclusion = lOcclusion; + eax_source_dirty_filter_flags_.lOcclusion = (eax_.source.lOcclusion != eax_d_.source.lOcclusion); +} + +void ALsource::eax_defer_source_occlusion_lf_ratio( + float flOcclusionLFRatio) +{ + eax_d_.source.flOcclusionLFRatio = flOcclusionLFRatio; + eax_source_dirty_filter_flags_.flOcclusionLFRatio = (eax_.source.flOcclusionLFRatio != eax_d_.source.flOcclusionLFRatio); +} + +void ALsource::eax_defer_source_occlusion_room_ratio( + float flOcclusionRoomRatio) +{ + eax_d_.source.flOcclusionRoomRatio = flOcclusionRoomRatio; + eax_source_dirty_filter_flags_.flOcclusionRoomRatio = (eax_.source.flOcclusionRoomRatio != eax_d_.source.flOcclusionRoomRatio); +} + +void ALsource::eax_defer_source_occlusion_direct_ratio( + float flOcclusionDirectRatio) +{ + eax_d_.source.flOcclusionDirectRatio = flOcclusionDirectRatio; + eax_source_dirty_filter_flags_.flOcclusionDirectRatio = (eax_.source.flOcclusionDirectRatio != eax_d_.source.flOcclusionDirectRatio); +} + +void ALsource::eax_defer_source_exclusion( + long lExclusion) +{ + eax_d_.source.lExclusion = lExclusion; + eax_source_dirty_filter_flags_.lExclusion = (eax_.source.lExclusion != eax_d_.source.lExclusion); +} + +void ALsource::eax_defer_source_exclusion_lf_ratio( + float flExclusionLFRatio) +{ + eax_d_.source.flExclusionLFRatio = flExclusionLFRatio; + eax_source_dirty_filter_flags_.flExclusionLFRatio = (eax_.source.flExclusionLFRatio != eax_d_.source.flExclusionLFRatio); +} + +void ALsource::eax_defer_source_outside_volume_hf( + long lOutsideVolumeHF) +{ + eax_d_.source.lOutsideVolumeHF = lOutsideVolumeHF; + eax_source_dirty_misc_flags_.lOutsideVolumeHF = (eax_.source.lOutsideVolumeHF != eax_d_.source.lOutsideVolumeHF); +} + +void ALsource::eax_defer_source_doppler_factor( + float flDopplerFactor) +{ + eax_d_.source.flDopplerFactor = flDopplerFactor; + eax_source_dirty_misc_flags_.flDopplerFactor = (eax_.source.flDopplerFactor != eax_d_.source.flDopplerFactor); +} + +void ALsource::eax_defer_source_rolloff_factor( + float flRolloffFactor) +{ + eax_d_.source.flRolloffFactor = flRolloffFactor; + eax_source_dirty_misc_flags_.flRolloffFactor = (eax_.source.flRolloffFactor != eax_d_.source.flRolloffFactor); +} + +void ALsource::eax_defer_source_room_rolloff_factor( + float flRoomRolloffFactor) +{ + eax_d_.source.flRoomRolloffFactor = flRoomRolloffFactor; + eax_source_dirty_misc_flags_.flRoomRolloffFactor = (eax_.source.flRoomRolloffFactor != eax_d_.source.flRoomRolloffFactor); +} + +void ALsource::eax_defer_source_air_absorption_factor( + float flAirAbsorptionFactor) +{ + eax_d_.source.flAirAbsorptionFactor = flAirAbsorptionFactor; + eax_source_dirty_misc_flags_.flAirAbsorptionFactor = (eax_.source.flAirAbsorptionFactor != eax_d_.source.flAirAbsorptionFactor); +} + +void ALsource::eax_defer_source_flags( + unsigned long ulFlags) +{ + eax_d_.source.ulFlags = ulFlags; + eax_source_dirty_misc_flags_.ulFlags = (eax_.source.ulFlags != eax_d_.source.ulFlags); +} + +void ALsource::eax_defer_source_macro_fx_factor( + float flMacroFXFactor) +{ + eax_d_.source.flMacroFXFactor = flMacroFXFactor; + eax_source_dirty_misc_flags_.flMacroFXFactor = (eax_.source.flMacroFXFactor != eax_d_.source.flMacroFXFactor); +} + +void ALsource::eax_defer_source_2d_all( + const EAXSOURCE2DPROPERTIES& all) +{ + eax_defer_source_direct(all.lDirect); + eax_defer_source_direct_hf(all.lDirectHF); + eax_defer_source_room(all.lRoom); + eax_defer_source_room_hf(all.lRoomHF); + eax_defer_source_flags(all.ulFlags); +} + +void ALsource::eax_defer_source_obstruction_all( + const EAXOBSTRUCTIONPROPERTIES& all) +{ + eax_defer_source_obstruction(all.lObstruction); + eax_defer_source_obstruction_lf_ratio(all.flObstructionLFRatio); +} + +void ALsource::eax_defer_source_exclusion_all( + const EAXEXCLUSIONPROPERTIES& all) +{ + eax_defer_source_exclusion(all.lExclusion); + eax_defer_source_exclusion_lf_ratio(all.flExclusionLFRatio); +} + +void ALsource::eax_defer_source_occlusion_all( + const EAXOCCLUSIONPROPERTIES& all) +{ + eax_defer_source_occlusion(all.lOcclusion); + eax_defer_source_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_defer_source_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_defer_source_occlusion_direct_ratio(all.flOcclusionDirectRatio); +} + +void ALsource::eax_defer_source_all( + const EAX20BUFFERPROPERTIES& all) +{ + eax_defer_source_direct(all.lDirect); + eax_defer_source_direct_hf(all.lDirectHF); + eax_defer_source_room(all.lRoom); + eax_defer_source_room_hf(all.lRoomHF); + eax_defer_source_obstruction(all.lObstruction); + eax_defer_source_obstruction_lf_ratio(all.flObstructionLFRatio); + eax_defer_source_occlusion(all.lOcclusion); + eax_defer_source_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_defer_source_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_defer_source_outside_volume_hf(all.lOutsideVolumeHF); + eax_defer_source_room_rolloff_factor(all.flRoomRolloffFactor); + eax_defer_source_air_absorption_factor(all.flAirAbsorptionFactor); + eax_defer_source_flags(all.dwFlags); +} + +void ALsource::eax_defer_source_all( + const EAX30SOURCEPROPERTIES& all) +{ + eax_defer_source_direct(all.lDirect); + eax_defer_source_direct_hf(all.lDirectHF); + eax_defer_source_room(all.lRoom); + eax_defer_source_room_hf(all.lRoomHF); + eax_defer_source_obstruction(all.lObstruction); + eax_defer_source_obstruction_lf_ratio(all.flObstructionLFRatio); + eax_defer_source_occlusion(all.lOcclusion); + eax_defer_source_occlusion_lf_ratio(all.flOcclusionLFRatio); + eax_defer_source_occlusion_room_ratio(all.flOcclusionRoomRatio); + eax_defer_source_occlusion_direct_ratio(all.flOcclusionDirectRatio); + eax_defer_source_exclusion(all.lExclusion); + eax_defer_source_exclusion_lf_ratio(all.flExclusionLFRatio); + eax_defer_source_outside_volume_hf(all.lOutsideVolumeHF); + eax_defer_source_doppler_factor(all.flDopplerFactor); + eax_defer_source_rolloff_factor(all.flRolloffFactor); + eax_defer_source_room_rolloff_factor(all.flRoomRolloffFactor); + eax_defer_source_air_absorption_factor(all.flAirAbsorptionFactor); + eax_defer_source_flags(all.ulFlags); +} + +void ALsource::eax_defer_source_all( + const EAX50SOURCEPROPERTIES& all) +{ + eax_defer_source_all(static_cast(all)); + eax_defer_source_macro_fx_factor(all.flMacroFXFactor); +} + +void ALsource::eax_defer_source_speaker_level_all( + const EAXSPEAKERLEVELPROPERTIES& all) +{ + const auto speaker_index = static_cast(all.lSpeakerID - 1); + auto& speaker_level_d = eax_d_.speaker_levels[speaker_index]; + const auto& speaker_level = eax_.speaker_levels[speaker_index]; + + if (speaker_level != speaker_level_d) + { + eax_source_dirty_misc_flags_.speaker_levels = true; + } +} + +void ALsource::eax1_set_efx() +{ + const auto primary_fx_slot_index = eax_al_context_->eax_get_primary_fx_slot_index(); + + if (!primary_fx_slot_index.has_value()) + return; + + WetGainAuto = (eax1_.fMix == EAX_REVERBMIX_USEDISTANCE); + const auto filter_gain = (WetGainAuto ? 1.0F : eax1_.fMix); + auto& fx_slot = eax_al_context_->eax_get_fx_slot(*primary_fx_slot_index); + eax_set_al_source_send(&fx_slot, *primary_fx_slot_index, EaxAlLowPassParam{filter_gain, 1.0F}); + mPropsDirty = true; +} + +void ALsource::eax1_set_reverb_mix(const EaxEaxCall& eax_call) +{ + const auto reverb_mix = eax_call.get_value(); + eax1_validate_reverb_mix(reverb_mix); + + if (eax1_.fMix == reverb_mix) + return; + + eax1_.fMix = reverb_mix; + eax1_set_efx(); +} + +void ALsource::eax_defer_source_direct( + const EaxEaxCall& eax_call) +{ + const auto direct = + eax_call.get_value(); + + eax_validate_source_direct(direct); + eax_defer_source_direct(direct); +} + +void ALsource::eax_defer_source_direct_hf( + const EaxEaxCall& eax_call) +{ + const auto direct_hf = + eax_call.get_value(); + + eax_validate_source_direct_hf(direct_hf); + eax_defer_source_direct_hf(direct_hf); +} + +void ALsource::eax_defer_source_room( + const EaxEaxCall& eax_call) +{ + const auto room = + eax_call.get_value(); + + eax_validate_source_room(room); + eax_defer_source_room(room); +} + +void ALsource::eax_defer_source_room_hf( + const EaxEaxCall& eax_call) +{ + const auto room_hf = + eax_call.get_value(); + + eax_validate_source_room_hf(room_hf); + eax_defer_source_room_hf(room_hf); +} + +void ALsource::eax_defer_source_obstruction( + const EaxEaxCall& eax_call) +{ + const auto obstruction = + eax_call.get_value(); + + eax_validate_source_obstruction(obstruction); + eax_defer_source_obstruction(obstruction); +} + +void ALsource::eax_defer_source_obstruction_lf_ratio( + const EaxEaxCall& eax_call) +{ + const auto obstruction_lf_ratio = + eax_call.get_value(); + + eax_validate_source_obstruction_lf_ratio(obstruction_lf_ratio); + eax_defer_source_obstruction_lf_ratio(obstruction_lf_ratio); +} + +void ALsource::eax_defer_source_occlusion( + const EaxEaxCall& eax_call) +{ + const auto occlusion = + eax_call.get_value(); + + eax_validate_source_occlusion(occlusion); + eax_defer_source_occlusion(occlusion); +} + +void ALsource::eax_defer_source_occlusion_lf_ratio( + const EaxEaxCall& eax_call) +{ + const auto occlusion_lf_ratio = + eax_call.get_value(); + + eax_validate_source_occlusion_lf_ratio(occlusion_lf_ratio); + eax_defer_source_occlusion_lf_ratio(occlusion_lf_ratio); +} + +void ALsource::eax_defer_source_occlusion_room_ratio( + const EaxEaxCall& eax_call) +{ + const auto occlusion_room_ratio = + eax_call.get_value(); + + eax_validate_source_occlusion_room_ratio(occlusion_room_ratio); + eax_defer_source_occlusion_room_ratio(occlusion_room_ratio); +} + +void ALsource::eax_defer_source_occlusion_direct_ratio( + const EaxEaxCall& eax_call) +{ + const auto occlusion_direct_ratio = + eax_call.get_value(); + + eax_validate_source_occlusion_direct_ratio(occlusion_direct_ratio); + eax_defer_source_occlusion_direct_ratio(occlusion_direct_ratio); +} + +void ALsource::eax_defer_source_exclusion( + const EaxEaxCall& eax_call) +{ + const auto exclusion = + eax_call.get_value(); + + eax_validate_source_exclusion(exclusion); + eax_defer_source_exclusion(exclusion); +} + +void ALsource::eax_defer_source_exclusion_lf_ratio( + const EaxEaxCall& eax_call) +{ + const auto exclusion_lf_ratio = + eax_call.get_value(); + + eax_validate_source_exclusion_lf_ratio(exclusion_lf_ratio); + eax_defer_source_exclusion_lf_ratio(exclusion_lf_ratio); +} + +void ALsource::eax_defer_source_outside_volume_hf( + const EaxEaxCall& eax_call) +{ + const auto outside_volume_hf = + eax_call.get_value(); + + eax_validate_source_outside_volume_hf(outside_volume_hf); + eax_defer_source_outside_volume_hf(outside_volume_hf); +} + +void ALsource::eax_defer_source_doppler_factor( + const EaxEaxCall& eax_call) +{ + const auto doppler_factor = + eax_call.get_value(); + + eax_validate_source_doppler_factor(doppler_factor); + eax_defer_source_doppler_factor(doppler_factor); +} + +void ALsource::eax_defer_source_rolloff_factor( + const EaxEaxCall& eax_call) +{ + const auto rolloff_factor = + eax_call.get_value(); + + eax_validate_source_rolloff_factor(rolloff_factor); + eax_defer_source_rolloff_factor(rolloff_factor); +} + +void ALsource::eax_defer_source_room_rolloff_factor( + const EaxEaxCall& eax_call) +{ + const auto room_rolloff_factor = + eax_call.get_value(); + + eax_validate_source_room_rolloff_factor(room_rolloff_factor); + eax_defer_source_room_rolloff_factor(room_rolloff_factor); +} + +void ALsource::eax_defer_source_air_absorption_factor( + const EaxEaxCall& eax_call) +{ + const auto air_absorption_factor = + eax_call.get_value(); + + eax_validate_source_air_absorption_factor(air_absorption_factor); + eax_defer_source_air_absorption_factor(air_absorption_factor); +} + +void ALsource::eax_defer_source_flags( + const EaxEaxCall& eax_call) +{ + const auto flags = + eax_call.get_value(); + + eax_validate_source_flags(flags, eax_call.get_version()); + eax_defer_source_flags(flags); +} + +void ALsource::eax_defer_source_macro_fx_factor( + const EaxEaxCall& eax_call) +{ + const auto macro_fx_factor = + eax_call.get_value(); + + eax_validate_source_macro_fx_factor(macro_fx_factor); + eax_defer_source_macro_fx_factor(macro_fx_factor); +} + +void ALsource::eax_defer_source_2d_all( + const EaxEaxCall& eax_call) +{ + const auto all = eax_call.get_value(); + + eax_validate_source_2d_all(all, eax_call.get_version()); + eax_defer_source_2d_all(all); +} + +void ALsource::eax_defer_source_obstruction_all( + const EaxEaxCall& eax_call) +{ + const auto all = eax_call.get_value(); + + eax_validate_source_obstruction_all(all); + eax_defer_source_obstruction_all(all); +} + +void ALsource::eax_defer_source_exclusion_all( + const EaxEaxCall& eax_call) +{ + const auto all = eax_call.get_value(); + + eax_validate_source_exclusion_all(all); + eax_defer_source_exclusion_all(all); +} + +void ALsource::eax_defer_source_occlusion_all( + const EaxEaxCall& eax_call) +{ + const auto all = eax_call.get_value(); + + eax_validate_source_occlusion_all(all); + eax_defer_source_occlusion_all(all); +} + +void ALsource::eax_defer_source_all( + const EaxEaxCall& eax_call) +{ + const auto eax_version = eax_call.get_version(); + + if (eax_version == 2) + { + const auto all = eax_call.get_value(); + + eax_validate_source_all(all, eax_version); + eax_defer_source_all(all); + } + else if (eax_version < 5) + { + const auto all = eax_call.get_value(); + + eax_validate_source_all(all, eax_version); + eax_defer_source_all(all); + } + else + { + const auto all = eax_call.get_value(); + + eax_validate_source_all(all, eax_version); + eax_defer_source_all(all); + } +} + +void ALsource::eax_defer_source_speaker_level_all( + const EaxEaxCall& eax_call) +{ + const auto speaker_level_properties = eax_call.get_value(); + + eax_validate_source_speaker_level_all(speaker_level_properties); + eax_defer_source_speaker_level_all(speaker_level_properties); +} + +void ALsource::eax_set_outside_volume_hf() +{ + const auto efx_gain_hf = clamp( + level_mb_to_gain(static_cast(eax_.source.lOutsideVolumeHF)), + AL_MIN_CONE_OUTER_GAINHF, + AL_MAX_CONE_OUTER_GAINHF + ); + + OuterGainHF = efx_gain_hf; +} + +void ALsource::eax_set_doppler_factor() +{ + DopplerFactor = eax_.source.flDopplerFactor; +} + +void ALsource::eax_set_rolloff_factor() +{ + RolloffFactor2 = eax_.source.flRolloffFactor; +} + +void ALsource::eax_set_room_rolloff_factor() +{ + RoomRolloffFactor = eax_.source.flRoomRolloffFactor; +} + +void ALsource::eax_set_air_absorption_factor() +{ + AirAbsorptionFactor = eax_.source.flAirAbsorptionFactor; +} + +void ALsource::eax_set_direct_hf_auto_flag() +{ + const auto is_enable = (eax_.source.ulFlags & EAXSOURCEFLAGS_DIRECTHFAUTO) != 0; + + DryGainHFAuto = is_enable; +} + +void ALsource::eax_set_room_auto_flag() +{ + const auto is_enable = (eax_.source.ulFlags & EAXSOURCEFLAGS_ROOMAUTO) != 0; + + WetGainAuto = is_enable; +} + +void ALsource::eax_set_room_hf_auto_flag() +{ + const auto is_enable = (eax_.source.ulFlags & EAXSOURCEFLAGS_ROOMHFAUTO) != 0; + + WetGainHFAuto = is_enable; +} + +void ALsource::eax_set_flags() +{ + eax_set_direct_hf_auto_flag(); + eax_set_room_auto_flag(); + eax_set_room_hf_auto_flag(); + eax_set_speaker_levels(); +} + +void ALsource::eax_set_macro_fx_factor() +{ + // TODO +} + +void ALsource::eax_set_speaker_levels() +{ + // TODO +} + +void ALsource::eax1_set(const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case DSPROPERTY_EAXBUFFER_ALL: + case DSPROPERTY_EAXBUFFER_REVERBMIX: + eax1_set_reverb_mix(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +void ALsource::eax_apply_deferred() +{ + if (!eax_are_active_fx_slots_dirty_ && + eax_sends_dirty_flags_ == EaxSourceSendsDirtyFlags{} && + eax_source_dirty_filter_flags_ == EaxSourceSourceFilterDirtyFlags{} && + eax_source_dirty_misc_flags_ == EaxSourceSourceMiscDirtyFlags{}) + { + return; + } + + eax_ = eax_d_; + + if (eax_are_active_fx_slots_dirty_) + { + eax_are_active_fx_slots_dirty_ = false; + eax_set_fx_slots(); + eax_update_filters_internal(); + } + else if (eax_has_active_fx_slots_) + { + if (eax_source_dirty_filter_flags_ != EaxSourceSourceFilterDirtyFlags{}) + { + eax_update_filters_internal(); + } + else if (eax_sends_dirty_flags_ != EaxSourceSendsDirtyFlags{}) + { + for (auto i = std::size_t{}; i < EAX_MAX_FXSLOTS; ++i) + { + if (eax_active_fx_slots_[i]) + { + if (eax_sends_dirty_flags_.sends[i] != EaxSourceSendDirtyFlags{}) + { + eax_update_filters_internal(); + break; + } + } + } + } + } + + if (eax_source_dirty_misc_flags_ != EaxSourceSourceMiscDirtyFlags{}) + { + if (eax_source_dirty_misc_flags_.lOutsideVolumeHF) + { + eax_set_outside_volume_hf(); + } + + if (eax_source_dirty_misc_flags_.flDopplerFactor) + { + eax_set_doppler_factor(); + } + + if (eax_source_dirty_misc_flags_.flRolloffFactor) + { + eax_set_rolloff_factor(); + } + + if (eax_source_dirty_misc_flags_.flRoomRolloffFactor) + { + eax_set_room_rolloff_factor(); + } + + if (eax_source_dirty_misc_flags_.flAirAbsorptionFactor) + { + eax_set_air_absorption_factor(); + } + + if (eax_source_dirty_misc_flags_.ulFlags) + { + eax_set_flags(); + } + + if (eax_source_dirty_misc_flags_.flMacroFXFactor) + { + eax_set_macro_fx_factor(); + } + + mPropsDirty = true; + + eax_source_dirty_misc_flags_ = EaxSourceSourceMiscDirtyFlags{}; + } + + eax_sends_dirty_flags_ = EaxSourceSendsDirtyFlags{}; + eax_source_dirty_filter_flags_ = EaxSourceSourceFilterDirtyFlags{}; +} + +void ALsource::eax_set( + const EaxEaxCall& eax_call) +{ + if (eax_call.get_version() == 1) + { + eax1_set(eax_call); + return; + } + + switch (eax_call.get_property_id()) + { + case EAXSOURCE_NONE: + break; + + case EAXSOURCE_ALLPARAMETERS: + eax_defer_source_all(eax_call); + break; + + case EAXSOURCE_OBSTRUCTIONPARAMETERS: + eax_defer_source_obstruction_all(eax_call); + break; + + case EAXSOURCE_OCCLUSIONPARAMETERS: + eax_defer_source_occlusion_all(eax_call); + break; + + case EAXSOURCE_EXCLUSIONPARAMETERS: + eax_defer_source_exclusion_all(eax_call); + break; + + case EAXSOURCE_DIRECT: + eax_defer_source_direct(eax_call); + break; + + case EAXSOURCE_DIRECTHF: + eax_defer_source_direct_hf(eax_call); + break; + + case EAXSOURCE_ROOM: + eax_defer_source_room(eax_call); + break; + + case EAXSOURCE_ROOMHF: + eax_defer_source_room_hf(eax_call); + break; + + case EAXSOURCE_OBSTRUCTION: + eax_defer_source_obstruction(eax_call); + break; + + case EAXSOURCE_OBSTRUCTIONLFRATIO: + eax_defer_source_obstruction_lf_ratio(eax_call); + break; + + case EAXSOURCE_OCCLUSION: + eax_defer_source_occlusion(eax_call); + break; + + case EAXSOURCE_OCCLUSIONLFRATIO: + eax_defer_source_occlusion_lf_ratio(eax_call); + break; + + case EAXSOURCE_OCCLUSIONROOMRATIO: + eax_defer_source_occlusion_room_ratio(eax_call); + break; + + case EAXSOURCE_OCCLUSIONDIRECTRATIO: + eax_defer_source_occlusion_direct_ratio(eax_call); + break; + + case EAXSOURCE_EXCLUSION: + eax_defer_source_exclusion(eax_call); + break; + + case EAXSOURCE_EXCLUSIONLFRATIO: + eax_defer_source_exclusion_lf_ratio(eax_call); + break; + + case EAXSOURCE_OUTSIDEVOLUMEHF: + eax_defer_source_outside_volume_hf(eax_call); + break; + + case EAXSOURCE_DOPPLERFACTOR: + eax_defer_source_doppler_factor(eax_call); + break; + + case EAXSOURCE_ROLLOFFFACTOR: + eax_defer_source_rolloff_factor(eax_call); + break; + + case EAXSOURCE_ROOMROLLOFFFACTOR: + eax_defer_source_room_rolloff_factor(eax_call); + break; + + case EAXSOURCE_AIRABSORPTIONFACTOR: + eax_defer_source_air_absorption_factor(eax_call); + break; + + case EAXSOURCE_FLAGS: + eax_defer_source_flags(eax_call); + break; + + case EAXSOURCE_SENDPARAMETERS: + eax_defer_send(eax_call); + break; + + case EAXSOURCE_ALLSENDPARAMETERS: + eax_defer_send_all(eax_call); + break; + + case EAXSOURCE_OCCLUSIONSENDPARAMETERS: + eax_defer_send_occlusion_all(eax_call); + break; + + case EAXSOURCE_EXCLUSIONSENDPARAMETERS: + eax_defer_send_exclusion_all(eax_call); + break; + + case EAXSOURCE_ACTIVEFXSLOTID: + eax_defer_active_fx_slots(eax_call); + break; + + case EAXSOURCE_MACROFXFACTOR: + eax_defer_source_macro_fx_factor(eax_call); + break; + + case EAXSOURCE_SPEAKERLEVELS: + eax_defer_source_speaker_level_all(eax_call); + break; + + case EAXSOURCE_ALL2DPARAMETERS: + eax_defer_source_2d_all(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +const GUID& ALsource::eax_get_send_fx_slot_guid( + int eax_version, + EaxFxSlotIndexValue fx_slot_index) +{ + switch (eax_version) + { + case 4: + switch (fx_slot_index) + { + case 0: + return EAXPROPERTYID_EAX40_FXSlot0; + + case 1: + return EAXPROPERTYID_EAX40_FXSlot1; + + case 2: + return EAXPROPERTYID_EAX40_FXSlot2; + + case 3: + return EAXPROPERTYID_EAX40_FXSlot3; + + default: + eax_fail("FX slot index out of range."); + } + + case 5: + switch (fx_slot_index) + { + case 0: + return EAXPROPERTYID_EAX50_FXSlot0; + + case 1: + return EAXPROPERTYID_EAX50_FXSlot1; + + case 2: + return EAXPROPERTYID_EAX50_FXSlot2; + + case 3: + return EAXPROPERTYID_EAX50_FXSlot3; + + default: + eax_fail("FX slot index out of range."); + } + + default: + eax_fail("Unsupported EAX version."); + } +} + +void ALsource::eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCESENDPROPERTIES& dst_send) +{ + dst_send.lSend = src_send.lSend; + dst_send.lSendHF = src_send.lSendHF; +} + +void ALsource::eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCEALLSENDPROPERTIES& dst_send) +{ + dst_send = src_send; +} + +void ALsource::eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCEOCCLUSIONSENDPROPERTIES& dst_send) +{ + dst_send.lOcclusion = src_send.lOcclusion; + dst_send.flOcclusionLFRatio = src_send.flOcclusionLFRatio; + dst_send.flOcclusionRoomRatio = src_send.flOcclusionRoomRatio; + dst_send.flOcclusionDirectRatio = src_send.flOcclusionDirectRatio; +} + +void ALsource::eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCEEXCLUSIONSENDPROPERTIES& dst_send) +{ + dst_send.lExclusion = src_send.lExclusion; + dst_send.flExclusionLFRatio = src_send.flExclusionLFRatio; +} + +void ALsource::eax1_get(const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case DSPROPERTY_EAXBUFFER_ALL: + case DSPROPERTY_EAXBUFFER_REVERBMIX: + eax_call.set_value(eax1_); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +void ALsource::eax_api_get_source_all_v2( + const EaxEaxCall& eax_call) +{ + auto eax_2_all = EAX20BUFFERPROPERTIES{}; + eax_2_all.lDirect = eax_.source.lDirect; + eax_2_all.lDirectHF = eax_.source.lDirectHF; + eax_2_all.lRoom = eax_.source.lRoom; + eax_2_all.lRoomHF = eax_.source.lRoomHF; + eax_2_all.flRoomRolloffFactor = eax_.source.flRoomRolloffFactor; + eax_2_all.lObstruction = eax_.source.lObstruction; + eax_2_all.flObstructionLFRatio = eax_.source.flObstructionLFRatio; + eax_2_all.lOcclusion = eax_.source.lOcclusion; + eax_2_all.flOcclusionLFRatio = eax_.source.flOcclusionLFRatio; + eax_2_all.flOcclusionRoomRatio = eax_.source.flOcclusionRoomRatio; + eax_2_all.lOutsideVolumeHF = eax_.source.lOutsideVolumeHF; + eax_2_all.flAirAbsorptionFactor = eax_.source.flAirAbsorptionFactor; + eax_2_all.dwFlags = eax_.source.ulFlags; + + eax_call.set_value(eax_2_all); +} + +void ALsource::eax_api_get_source_all_v3( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(static_cast(eax_.source)); +} + +void ALsource::eax_api_get_source_all_v5( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_.source); +} + +void ALsource::eax_api_get_source_all( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_version()) + { + case 2: + eax_api_get_source_all_v2(eax_call); + break; + + case 3: + case 4: + eax_api_get_source_all_v3(eax_call); + break; + + case 5: + eax_api_get_source_all_v5(eax_call); + break; + + default: + eax_fail("Unsupported EAX version."); + } +} + +void ALsource::eax_api_get_source_all_obstruction( + const EaxEaxCall& eax_call) +{ + auto eax_obstruction_all = EAXOBSTRUCTIONPROPERTIES{}; + eax_obstruction_all.lObstruction = eax_.source.lObstruction; + eax_obstruction_all.flObstructionLFRatio = eax_.source.flObstructionLFRatio; + + eax_call.set_value(eax_obstruction_all); +} + +void ALsource::eax_api_get_source_all_occlusion( + const EaxEaxCall& eax_call) +{ + auto eax_occlusion_all = EAXOCCLUSIONPROPERTIES{}; + eax_occlusion_all.lOcclusion = eax_.source.lOcclusion; + eax_occlusion_all.flOcclusionLFRatio = eax_.source.flOcclusionLFRatio; + eax_occlusion_all.flOcclusionRoomRatio = eax_.source.flOcclusionRoomRatio; + eax_occlusion_all.flOcclusionDirectRatio = eax_.source.flOcclusionDirectRatio; + + eax_call.set_value(eax_occlusion_all); +} + +void ALsource::eax_api_get_source_all_exclusion( + const EaxEaxCall& eax_call) +{ + auto eax_exclusion_all = EAXEXCLUSIONPROPERTIES{}; + eax_exclusion_all.lExclusion = eax_.source.lExclusion; + eax_exclusion_all.flExclusionLFRatio = eax_.source.flExclusionLFRatio; + + eax_call.set_value(eax_exclusion_all); +} + +void ALsource::eax_api_get_source_active_fx_slot_id( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_version()) + { + case 4: + { + const auto& active_fx_slots = reinterpret_cast(eax_.active_fx_slots); + eax_call.set_value(active_fx_slots); + } + break; + + case 5: + { + const auto& active_fx_slots = reinterpret_cast(eax_.active_fx_slots); + eax_call.set_value(active_fx_slots); + } + break; + + default: + eax_fail("Unsupported EAX version."); + } +} + +void ALsource::eax_api_get_source_all_2d( + const EaxEaxCall& eax_call) +{ + auto eax_2d_all = EAXSOURCE2DPROPERTIES{}; + eax_2d_all.lDirect = eax_.source.lDirect; + eax_2d_all.lDirectHF = eax_.source.lDirectHF; + eax_2d_all.lRoom = eax_.source.lRoom; + eax_2d_all.lRoomHF = eax_.source.lRoomHF; + eax_2d_all.ulFlags = eax_.source.ulFlags; + + eax_call.set_value(eax_2d_all); +} + +void ALsource::eax_api_get_source_speaker_level_all( + const EaxEaxCall& eax_call) +{ + auto& all = eax_call.get_value(); + + eax_validate_source_speaker_id(all.lSpeakerID); + const auto speaker_index = static_cast(all.lSpeakerID - 1); + all.lLevel = eax_.speaker_levels[speaker_index]; +} + +void ALsource::eax_get( + const EaxEaxCall& eax_call) +{ + if (eax_call.get_version() == 1) + { + eax1_get(eax_call); + return; + } + + switch (eax_call.get_property_id()) + { + case EAXSOURCE_NONE: + break; + + case EAXSOURCE_ALLPARAMETERS: + eax_api_get_source_all(eax_call); + break; + + case EAXSOURCE_OBSTRUCTIONPARAMETERS: + eax_api_get_source_all_obstruction(eax_call); + break; + + case EAXSOURCE_OCCLUSIONPARAMETERS: + eax_api_get_source_all_occlusion(eax_call); + break; + + case EAXSOURCE_EXCLUSIONPARAMETERS: + eax_api_get_source_all_exclusion(eax_call); + break; + + case EAXSOURCE_DIRECT: + eax_call.set_value(eax_.source.lDirect); + break; + + case EAXSOURCE_DIRECTHF: + eax_call.set_value(eax_.source.lDirectHF); + break; + + case EAXSOURCE_ROOM: + eax_call.set_value(eax_.source.lRoom); + break; + + case EAXSOURCE_ROOMHF: + eax_call.set_value(eax_.source.lRoomHF); + break; + + case EAXSOURCE_OBSTRUCTION: + eax_call.set_value(eax_.source.lObstruction); + break; + + case EAXSOURCE_OBSTRUCTIONLFRATIO: + eax_call.set_value(eax_.source.flObstructionLFRatio); + break; + + case EAXSOURCE_OCCLUSION: + eax_call.set_value(eax_.source.lOcclusion); + break; + + case EAXSOURCE_OCCLUSIONLFRATIO: + eax_call.set_value(eax_.source.flOcclusionLFRatio); + break; + + case EAXSOURCE_OCCLUSIONROOMRATIO: + eax_call.set_value(eax_.source.flOcclusionRoomRatio); + break; + + case EAXSOURCE_OCCLUSIONDIRECTRATIO: + eax_call.set_value(eax_.source.flOcclusionDirectRatio); + break; + + case EAXSOURCE_EXCLUSION: + eax_call.set_value(eax_.source.lExclusion); + break; + + case EAXSOURCE_EXCLUSIONLFRATIO: + eax_call.set_value(eax_.source.flExclusionLFRatio); + break; + + case EAXSOURCE_OUTSIDEVOLUMEHF: + eax_call.set_value(eax_.source.lOutsideVolumeHF); + break; + + case EAXSOURCE_DOPPLERFACTOR: + eax_call.set_value(eax_.source.flDopplerFactor); + break; + + case EAXSOURCE_ROLLOFFFACTOR: + eax_call.set_value(eax_.source.flRolloffFactor); + break; + + case EAXSOURCE_ROOMROLLOFFFACTOR: + eax_call.set_value(eax_.source.flRoomRolloffFactor); + break; + + case EAXSOURCE_AIRABSORPTIONFACTOR: + eax_call.set_value(eax_.source.flAirAbsorptionFactor); + break; + + case EAXSOURCE_FLAGS: + eax_call.set_value(eax_.source.ulFlags); + break; + + case EAXSOURCE_SENDPARAMETERS: + eax_api_get_send_properties(eax_call); + break; + + case EAXSOURCE_ALLSENDPARAMETERS: + eax_api_get_send_properties(eax_call); + break; + + case EAXSOURCE_OCCLUSIONSENDPARAMETERS: + eax_api_get_send_properties(eax_call); + break; + + case EAXSOURCE_EXCLUSIONSENDPARAMETERS: + eax_api_get_send_properties(eax_call); + break; + + case EAXSOURCE_ACTIVEFXSLOTID: + eax_api_get_source_active_fx_slot_id(eax_call); + break; + + case EAXSOURCE_MACROFXFACTOR: + eax_call.set_value(eax_.source.flMacroFXFactor); + break; + + case EAXSOURCE_SPEAKERLEVELS: + eax_api_get_source_speaker_level_all(eax_call); + break; + + case EAXSOURCE_ALL2DPARAMETERS: + eax_api_get_source_all_2d(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +void ALsource::eax_set_al_source_send( + ALeffectslot *slot, + size_t sendidx, + const EaxAlLowPassParam &filter) +{ + if(sendidx >= EAX_MAX_FXSLOTS) + return; + + auto &send = Send[sendidx]; + send.Gain = filter.gain; + send.GainHF = filter.gain_hf; + send.HFReference = LOWPASSFREQREF; + send.GainLF = 1.0f; + send.LFReference = HIGHPASSFREQREF; + + if(slot) IncrementRef(slot->ref); + if(auto *oldslot = send.Slot) + DecrementRef(oldslot->ref); + send.Slot = slot; + + mPropsDirty = true; +} + +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/source.h b/Engine/lib/openal-soft/al/source.h index 6572864fa..6db6bfa7a 100644 --- a/Engine/lib/openal-soft/al/source.h +++ b/Engine/lib/openal-soft/al/source.h @@ -11,19 +11,31 @@ #include "AL/al.h" #include "AL/alc.h" -#include "alcontext.h" +#include "alc/alu.h" +#include "alc/context.h" +#include "alc/inprogext.h" #include "aldeque.h" #include "almalloc.h" #include "alnumeric.h" -#include "alu.h" -#include "math_defs.h" +#include "atomic.h" +#include "core/voice.h" #include "vector.h" -#include "voice.h" + +#ifdef ALSOFT_EAX +#include "eax_eax_call.h" +#include "eax_fx_slot_index.h" +#include "eax_utils.h" +#endif // ALSOFT_EAX struct ALbuffer; struct ALeffectslot; +enum class SourceStereo : bool { + Normal = AL_NORMAL_SOFT, + Enhanced = AL_SUPER_STEREO_SOFT +}; + #define DEFAULT_SENDS 2 #define INVALID_VOICE_IDX static_cast(-1) @@ -35,6 +47,70 @@ struct ALbufferQueueItem : public VoiceBufferItem { }; +#ifdef ALSOFT_EAX +using EaxSourceSourceFilterDirtyFlagsValue = std::uint_least16_t; + +struct EaxSourceSourceFilterDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxSourceSourceFilterDirtyFlagsValue lDirect : 1; + EaxSourceSourceFilterDirtyFlagsValue lDirectHF : 1; + EaxSourceSourceFilterDirtyFlagsValue lRoom : 1; + EaxSourceSourceFilterDirtyFlagsValue lRoomHF : 1; + EaxSourceSourceFilterDirtyFlagsValue lObstruction : 1; + EaxSourceSourceFilterDirtyFlagsValue flObstructionLFRatio : 1; + EaxSourceSourceFilterDirtyFlagsValue lOcclusion : 1; + EaxSourceSourceFilterDirtyFlagsValue flOcclusionLFRatio : 1; + EaxSourceSourceFilterDirtyFlagsValue flOcclusionRoomRatio : 1; + EaxSourceSourceFilterDirtyFlagsValue flOcclusionDirectRatio : 1; + EaxSourceSourceFilterDirtyFlagsValue lExclusion : 1; + EaxSourceSourceFilterDirtyFlagsValue flExclusionLFRatio : 1; +}; // EaxSourceSourceFilterDirtyFlags + + +using EaxSourceSourceMiscDirtyFlagsValue = std::uint_least8_t; + +struct EaxSourceSourceMiscDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxSourceSourceMiscDirtyFlagsValue lOutsideVolumeHF : 1; + EaxSourceSourceMiscDirtyFlagsValue flDopplerFactor : 1; + EaxSourceSourceMiscDirtyFlagsValue flRolloffFactor : 1; + EaxSourceSourceMiscDirtyFlagsValue flRoomRolloffFactor : 1; + EaxSourceSourceMiscDirtyFlagsValue flAirAbsorptionFactor : 1; + EaxSourceSourceMiscDirtyFlagsValue ulFlags : 1; + EaxSourceSourceMiscDirtyFlagsValue flMacroFXFactor : 1; + EaxSourceSourceMiscDirtyFlagsValue speaker_levels : 1; +}; // EaxSourceSourceMiscDirtyFlags + + +using EaxSourceSendDirtyFlagsValue = std::uint_least8_t; + +struct EaxSourceSendDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxSourceSendDirtyFlagsValue lSend : 1; + EaxSourceSendDirtyFlagsValue lSendHF : 1; + EaxSourceSendDirtyFlagsValue lOcclusion : 1; + EaxSourceSendDirtyFlagsValue flOcclusionLFRatio : 1; + EaxSourceSendDirtyFlagsValue flOcclusionRoomRatio : 1; + EaxSourceSendDirtyFlagsValue flOcclusionDirectRatio : 1; + EaxSourceSendDirtyFlagsValue lExclusion : 1; + EaxSourceSendDirtyFlagsValue flExclusionLFRatio : 1; +}; // EaxSourceSendDirtyFlags + + +struct EaxSourceSendsDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxSourceSendDirtyFlags sends[EAX_MAX_FXSLOTS]; +}; // EaxSourceSendsDirtyFlags +#endif // ALSOFT_EAX + struct ALsource { /** Source properties. */ float Pitch{1.0f}; @@ -47,6 +123,11 @@ struct ALsource { float RefDistance{1.0f}; float MaxDistance{std::numeric_limits::max()}; float RolloffFactor{1.0f}; +#ifdef ALSOFT_EAX + // For EAXSOURCE_ROLLOFFFACTOR, which is distinct from and added to + // AL_ROLLOFF_FACTOR + float RolloffFactor2{0.0f}; +#endif std::array Position{{0.0f, 0.0f, 0.0f}}; std::array Velocity{{0.0f, 0.0f, 0.0f}}; std::array Direction{{0.0f, 0.0f, 0.0f}}; @@ -58,6 +139,7 @@ struct ALsource { Resampler mResampler{ResamplerDefault}; DirectMode DirectChannels{DirectMode::Off}; SpatializeMode mSpatialize{SpatializeMode::Auto}; + SourceStereo mStereoMode{SourceStereo::Normal}; bool DryGainHFAuto{true}; bool WetGainAuto{true}; @@ -71,9 +153,10 @@ struct ALsource { /* NOTE: Stereo pan angles are specified in radians, counter-clockwise * rather than clockwise. */ - std::array StereoPan{{Deg2Rad( 30.0f), Deg2Rad(-30.0f)}}; + std::array StereoPan{{al::numbers::pi_v/6.0f, -al::numbers::pi_v/6.0f}}; float Radius{0.0f}; + float EnhWidth{0.593f}; /** Direct filter and auxiliary send info. */ struct { @@ -109,7 +192,7 @@ struct ALsource { /** Source Buffer Queue head. */ al::deque mQueue; - std::atomic_flag PropsClean; + bool mPropsDirty{true}; /* Index into the context's Voices array. Lazily updated, only checked and * reset when looking up the voice. @@ -127,6 +210,598 @@ struct ALsource { ALsource& operator=(const ALsource&) = delete; DISABLE_ALLOC() + +#ifdef ALSOFT_EAX +public: + void eax_initialize(ALCcontext *context) noexcept; + + + void eax_dispatch(const EaxEaxCall& eax_call) + { eax_call.is_get() ? eax_get(eax_call) : eax_set(eax_call); } + + + void eax_update_filters(); + + void eax_update( + EaxContextSharedDirtyFlags dirty_flags); + + void eax_commit() { eax_apply_deferred(); } + void eax_commit_and_update(); + + bool eax_is_initialized() const noexcept { return eax_al_context_; } + + + static ALsource* eax_lookup_source( + ALCcontext& al_context, + ALuint source_id) noexcept; + + +private: + static constexpr auto eax_max_speakers = 9; + + + using EaxActiveFxSlots = std::array; + using EaxSpeakerLevels = std::array; + + struct Eax + { + using Sends = std::array; + + EAX50ACTIVEFXSLOTS active_fx_slots{}; + EAX50SOURCEPROPERTIES source{}; + Sends sends{}; + EaxSpeakerLevels speaker_levels{}; + }; // Eax + + + bool eax_uses_primary_id_{}; + bool eax_has_active_fx_slots_{}; + bool eax_are_active_fx_slots_dirty_{}; + + ALCcontext* eax_al_context_{}; + + EAXBUFFER_REVERBPROPERTIES eax1_{}; + Eax eax_{}; + Eax eax_d_{}; + EaxActiveFxSlots eax_active_fx_slots_{}; + + EaxSourceSendsDirtyFlags eax_sends_dirty_flags_{}; + EaxSourceSourceFilterDirtyFlags eax_source_dirty_filter_flags_{}; + EaxSourceSourceMiscDirtyFlags eax_source_dirty_misc_flags_{}; + + + [[noreturn]] + static void eax_fail( + const char* message); + + + void eax_set_source_defaults() noexcept; + void eax_set_active_fx_slots_defaults() noexcept; + void eax_set_send_defaults(EAXSOURCEALLSENDPROPERTIES& eax_send) noexcept; + void eax_set_sends_defaults() noexcept; + void eax_set_speaker_levels_defaults() noexcept; + void eax_set_defaults() noexcept; + + + static float eax_calculate_dst_occlusion_mb( + long src_occlusion_mb, + float path_ratio, + float lf_ratio) noexcept; + + EaxAlLowPassParam eax_create_direct_filter_param() const noexcept; + + EaxAlLowPassParam eax_create_room_filter_param( + const ALeffectslot& fx_slot, + const EAXSOURCEALLSENDPROPERTIES& send) const noexcept; + + void eax_set_fx_slots(); + + void eax_initialize_fx_slots(); + + void eax_update_direct_filter_internal(); + + void eax_update_room_filters_internal(); + + void eax_update_filters_internal(); + + void eax_update_primary_fx_slot_id(); + + + void eax_defer_active_fx_slots( + const EaxEaxCall& eax_call); + + + static const char* eax_get_exclusion_name() noexcept; + + static const char* eax_get_exclusion_lf_ratio_name() noexcept; + + + static const char* eax_get_occlusion_name() noexcept; + + static const char* eax_get_occlusion_lf_ratio_name() noexcept; + + static const char* eax_get_occlusion_direct_ratio_name() noexcept; + + static const char* eax_get_occlusion_room_ratio_name() noexcept; + + + static void eax1_validate_reverb_mix(float reverb_mix); + + static void eax_validate_send_receiving_fx_slot_guid( + const GUID& guidReceivingFXSlotID); + + static void eax_validate_send_send( + long lSend); + + static void eax_validate_send_send_hf( + long lSendHF); + + static void eax_validate_send_occlusion( + long lOcclusion); + + static void eax_validate_send_occlusion_lf_ratio( + float flOcclusionLFRatio); + + static void eax_validate_send_occlusion_room_ratio( + float flOcclusionRoomRatio); + + static void eax_validate_send_occlusion_direct_ratio( + float flOcclusionDirectRatio); + + static void eax_validate_send_exclusion( + long lExclusion); + + static void eax_validate_send_exclusion_lf_ratio( + float flExclusionLFRatio); + + static void eax_validate_send( + const EAXSOURCESENDPROPERTIES& all); + + static void eax_validate_send_exclusion_all( + const EAXSOURCEEXCLUSIONSENDPROPERTIES& all); + + static void eax_validate_send_occlusion_all( + const EAXSOURCEOCCLUSIONSENDPROPERTIES& all); + + static void eax_validate_send_all( + const EAXSOURCEALLSENDPROPERTIES& all); + + + static EaxFxSlotIndexValue eax_get_send_index( + const GUID& send_guid); + + + void eax_defer_send_send( + long lSend, + EaxFxSlotIndexValue index); + + void eax_defer_send_send_hf( + long lSendHF, + EaxFxSlotIndexValue index); + + void eax_defer_send_occlusion( + long lOcclusion, + EaxFxSlotIndexValue index); + + void eax_defer_send_occlusion_lf_ratio( + float flOcclusionLFRatio, + EaxFxSlotIndexValue index); + + void eax_defer_send_occlusion_room_ratio( + float flOcclusionRoomRatio, + EaxFxSlotIndexValue index); + + void eax_defer_send_occlusion_direct_ratio( + float flOcclusionDirectRatio, + EaxFxSlotIndexValue index); + + void eax_defer_send_exclusion( + long lExclusion, + EaxFxSlotIndexValue index); + + void eax_defer_send_exclusion_lf_ratio( + float flExclusionLFRatio, + EaxFxSlotIndexValue index); + + void eax_defer_send( + const EAXSOURCESENDPROPERTIES& all, + EaxFxSlotIndexValue index); + + void eax_defer_send_exclusion_all( + const EAXSOURCEEXCLUSIONSENDPROPERTIES& all, + EaxFxSlotIndexValue index); + + void eax_defer_send_occlusion_all( + const EAXSOURCEOCCLUSIONSENDPROPERTIES& all, + EaxFxSlotIndexValue index); + + void eax_defer_send_all( + const EAXSOURCEALLSENDPROPERTIES& all, + EaxFxSlotIndexValue index); + + + void eax_defer_send( + const EaxEaxCall& eax_call); + + void eax_defer_send_exclusion_all( + const EaxEaxCall& eax_call); + + void eax_defer_send_occlusion_all( + const EaxEaxCall& eax_call); + + void eax_defer_send_all( + const EaxEaxCall& eax_call); + + + static void eax_validate_source_direct( + long direct); + + static void eax_validate_source_direct_hf( + long direct_hf); + + static void eax_validate_source_room( + long room); + + static void eax_validate_source_room_hf( + long room_hf); + + static void eax_validate_source_obstruction( + long obstruction); + + static void eax_validate_source_obstruction_lf_ratio( + float obstruction_lf_ratio); + + static void eax_validate_source_occlusion( + long occlusion); + + static void eax_validate_source_occlusion_lf_ratio( + float occlusion_lf_ratio); + + static void eax_validate_source_occlusion_room_ratio( + float occlusion_room_ratio); + + static void eax_validate_source_occlusion_direct_ratio( + float occlusion_direct_ratio); + + static void eax_validate_source_exclusion( + long exclusion); + + static void eax_validate_source_exclusion_lf_ratio( + float exclusion_lf_ratio); + + static void eax_validate_source_outside_volume_hf( + long outside_volume_hf); + + static void eax_validate_source_doppler_factor( + float doppler_factor); + + static void eax_validate_source_rolloff_factor( + float rolloff_factor); + + static void eax_validate_source_room_rolloff_factor( + float room_rolloff_factor); + + static void eax_validate_source_air_absorption_factor( + float air_absorption_factor); + + static void eax_validate_source_flags( + unsigned long flags, + int eax_version); + + static void eax_validate_source_macro_fx_factor( + float macro_fx_factor); + + static void eax_validate_source_2d_all( + const EAXSOURCE2DPROPERTIES& all, + int eax_version); + + static void eax_validate_source_obstruction_all( + const EAXOBSTRUCTIONPROPERTIES& all); + + static void eax_validate_source_exclusion_all( + const EAXEXCLUSIONPROPERTIES& all); + + static void eax_validate_source_occlusion_all( + const EAXOCCLUSIONPROPERTIES& all); + + static void eax_validate_source_all( + const EAX20BUFFERPROPERTIES& all, + int eax_version); + + static void eax_validate_source_all( + const EAX30SOURCEPROPERTIES& all, + int eax_version); + + static void eax_validate_source_all( + const EAX50SOURCEPROPERTIES& all, + int eax_version); + + static void eax_validate_source_speaker_id( + long speaker_id); + + static void eax_validate_source_speaker_level( + long speaker_level); + + static void eax_validate_source_speaker_level_all( + const EAXSPEAKERLEVELPROPERTIES& all); + + + void eax_defer_source_direct( + long lDirect); + + void eax_defer_source_direct_hf( + long lDirectHF); + + void eax_defer_source_room( + long lRoom); + + void eax_defer_source_room_hf( + long lRoomHF); + + void eax_defer_source_obstruction( + long lObstruction); + + void eax_defer_source_obstruction_lf_ratio( + float flObstructionLFRatio); + + void eax_defer_source_occlusion( + long lOcclusion); + + void eax_defer_source_occlusion_lf_ratio( + float flOcclusionLFRatio); + + void eax_defer_source_occlusion_room_ratio( + float flOcclusionRoomRatio); + + void eax_defer_source_occlusion_direct_ratio( + float flOcclusionDirectRatio); + + void eax_defer_source_exclusion( + long lExclusion); + + void eax_defer_source_exclusion_lf_ratio( + float flExclusionLFRatio); + + void eax_defer_source_outside_volume_hf( + long lOutsideVolumeHF); + + void eax_defer_source_doppler_factor( + float flDopplerFactor); + + void eax_defer_source_rolloff_factor( + float flRolloffFactor); + + void eax_defer_source_room_rolloff_factor( + float flRoomRolloffFactor); + + void eax_defer_source_air_absorption_factor( + float flAirAbsorptionFactor); + + void eax_defer_source_flags( + unsigned long ulFlags); + + void eax_defer_source_macro_fx_factor( + float flMacroFXFactor); + + void eax_defer_source_2d_all( + const EAXSOURCE2DPROPERTIES& all); + + void eax_defer_source_obstruction_all( + const EAXOBSTRUCTIONPROPERTIES& all); + + void eax_defer_source_exclusion_all( + const EAXEXCLUSIONPROPERTIES& all); + + void eax_defer_source_occlusion_all( + const EAXOCCLUSIONPROPERTIES& all); + + void eax_defer_source_all( + const EAX20BUFFERPROPERTIES& all); + + void eax_defer_source_all( + const EAX30SOURCEPROPERTIES& all); + + void eax_defer_source_all( + const EAX50SOURCEPROPERTIES& all); + + void eax_defer_source_speaker_level_all( + const EAXSPEAKERLEVELPROPERTIES& all); + + + void eax_defer_source_direct( + const EaxEaxCall& eax_call); + + void eax_defer_source_direct_hf( + const EaxEaxCall& eax_call); + + void eax_defer_source_room( + const EaxEaxCall& eax_call); + + void eax_defer_source_room_hf( + const EaxEaxCall& eax_call); + + void eax_defer_source_obstruction( + const EaxEaxCall& eax_call); + + void eax_defer_source_obstruction_lf_ratio( + const EaxEaxCall& eax_call); + + void eax_defer_source_occlusion( + const EaxEaxCall& eax_call); + + void eax_defer_source_occlusion_lf_ratio( + const EaxEaxCall& eax_call); + + void eax_defer_source_occlusion_room_ratio( + const EaxEaxCall& eax_call); + + void eax_defer_source_occlusion_direct_ratio( + const EaxEaxCall& eax_call); + + void eax_defer_source_exclusion( + const EaxEaxCall& eax_call); + + void eax_defer_source_exclusion_lf_ratio( + const EaxEaxCall& eax_call); + + void eax_defer_source_outside_volume_hf( + const EaxEaxCall& eax_call); + + void eax_defer_source_doppler_factor( + const EaxEaxCall& eax_call); + + void eax_defer_source_rolloff_factor( + const EaxEaxCall& eax_call); + + void eax_defer_source_room_rolloff_factor( + const EaxEaxCall& eax_call); + + void eax_defer_source_air_absorption_factor( + const EaxEaxCall& eax_call); + + void eax_defer_source_flags( + const EaxEaxCall& eax_call); + + void eax_defer_source_macro_fx_factor( + const EaxEaxCall& eax_call); + + void eax_defer_source_2d_all( + const EaxEaxCall& eax_call); + + void eax_defer_source_obstruction_all( + const EaxEaxCall& eax_call); + + void eax_defer_source_exclusion_all( + const EaxEaxCall& eax_call); + + void eax_defer_source_occlusion_all( + const EaxEaxCall& eax_call); + + void eax_defer_source_all( + const EaxEaxCall& eax_call); + + void eax_defer_source_speaker_level_all( + const EaxEaxCall& eax_call); + + + void eax_set_outside_volume_hf(); + + void eax_set_doppler_factor(); + + void eax_set_rolloff_factor(); + + void eax_set_room_rolloff_factor(); + + void eax_set_air_absorption_factor(); + + + void eax_set_direct_hf_auto_flag(); + + void eax_set_room_auto_flag(); + + void eax_set_room_hf_auto_flag(); + + void eax_set_flags(); + + + void eax_set_macro_fx_factor(); + + void eax_set_speaker_levels(); + + + void eax1_set_efx(); + void eax1_set_reverb_mix(const EaxEaxCall& eax_call); + void eax1_set(const EaxEaxCall& eax_call); + + void eax_apply_deferred(); + + void eax_set( + const EaxEaxCall& eax_call); + + + static const GUID& eax_get_send_fx_slot_guid( + int eax_version, + EaxFxSlotIndexValue fx_slot_index); + + static void eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCESENDPROPERTIES& dst_send); + + static void eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCEALLSENDPROPERTIES& dst_send); + + static void eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCEOCCLUSIONSENDPROPERTIES& dst_send); + + static void eax_copy_send( + const EAXSOURCEALLSENDPROPERTIES& src_send, + EAXSOURCEEXCLUSIONSENDPROPERTIES& dst_send); + + template< + typename TException, + typename TSrcSend + > + void eax_api_get_send_properties( + const EaxEaxCall& eax_call) const + { + const auto eax_version = eax_call.get_version(); + const auto dst_sends = eax_call.get_values(); + const auto send_count = dst_sends.size(); + + for (auto fx_slot_index = EaxFxSlotIndexValue{}; fx_slot_index < send_count; ++fx_slot_index) + { + auto& dst_send = dst_sends[fx_slot_index]; + const auto& src_send = eax_.sends[fx_slot_index]; + + eax_copy_send(src_send, dst_send); + + dst_send.guidReceivingFXSlotID = eax_get_send_fx_slot_guid(eax_version, fx_slot_index); + } + } + + + void eax1_get(const EaxEaxCall& eax_call); + + void eax_api_get_source_all_v2( + const EaxEaxCall& eax_call); + + void eax_api_get_source_all_v3( + const EaxEaxCall& eax_call); + + void eax_api_get_source_all_v5( + const EaxEaxCall& eax_call); + + void eax_api_get_source_all( + const EaxEaxCall& eax_call); + + void eax_api_get_source_all_obstruction( + const EaxEaxCall& eax_call); + + void eax_api_get_source_all_occlusion( + const EaxEaxCall& eax_call); + + void eax_api_get_source_all_exclusion( + const EaxEaxCall& eax_call); + + void eax_api_get_source_active_fx_slot_id( + const EaxEaxCall& eax_call); + + void eax_api_get_source_all_2d( + const EaxEaxCall& eax_call); + + void eax_api_get_source_speaker_level_all( + const EaxEaxCall& eax_call); + + void eax_get( + const EaxEaxCall& eax_call); + + + // `alSource3i(source, AL_AUXILIARY_SEND_FILTER, ...)` + void eax_set_al_source_send(ALeffectslot *slot, size_t sendidx, + const EaxAlLowPassParam &filter); +#endif // ALSOFT_EAX }; void UpdateAllSourceProps(ALCcontext *context); diff --git a/Engine/lib/openal-soft/al/state.cpp b/Engine/lib/openal-soft/al/state.cpp index ae761fb87..8142890ee 100644 --- a/Engine/lib/openal-soft/al/state.cpp +++ b/Engine/lib/openal-soft/al/state.cpp @@ -24,26 +24,34 @@ #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" + +#ifdef ALSOFT_EAX +#include "alc/device.h" + +#include "eax_globals.h" +#include "eax_x_ram.h" +#endif // ALSOFT_EAX namespace { @@ -129,7 +137,7 @@ ALenum ALenumFromDistanceModel(DistanceModel model) /* WARNING: Non-standard export! Not part of any extension, or exposed in the * alcFunctions list. */ -extern "C" AL_API const ALchar* AL_APIENTRY alsoft_get_version(void) +AL_API const ALchar* AL_APIENTRY alsoft_get_version(void) START_API_FUNC { static const auto spoof = al::getenv("ALSOFT_SPOOF_VERSION"); @@ -139,10 +147,10 @@ START_API_FUNC END_API_FUNC #define DO_UPDATEPROPS() do { \ - if(!context->mDeferUpdates.load(std::memory_order_acquire)) \ + if(!context->mDeferUpdates) \ UpdateContextProps(context.get()); \ else \ - context->mPropsClean.clear(std::memory_order_release); \ + context->mPropsDirty = true; \ } while(0) @@ -152,12 +160,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 +186,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 +220,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); } @@ -239,7 +263,7 @@ START_API_FUNC break; case AL_DEFERRED_UPDATES_SOFT: - if(context->mDeferUpdates.load(std::memory_order_acquire)) + if(context->mDeferUpdates) value = AL_TRUE; break; @@ -292,7 +316,7 @@ START_API_FUNC break; case AL_DEFERRED_UPDATES_SOFT: - if(context->mDeferUpdates.load(std::memory_order_acquire)) + if(context->mDeferUpdates) value = static_cast(AL_TRUE); break; @@ -343,7 +367,7 @@ START_API_FUNC break; case AL_DEFERRED_UPDATES_SOFT: - if(context->mDeferUpdates.load(std::memory_order_acquire)) + if(context->mDeferUpdates) value = static_cast(AL_TRUE); break; @@ -394,7 +418,7 @@ START_API_FUNC break; case AL_DEFERRED_UPDATES_SOFT: - if(context->mDeferUpdates.load(std::memory_order_acquire)) + if(context->mDeferUpdates) value = AL_TRUE; break; @@ -410,6 +434,41 @@ START_API_FUNC value = static_cast(ResamplerDefault); break; +#ifdef ALSOFT_EAX + +#define EAX_ERROR "[alGetInteger] EAX not enabled." + + case AL_EAX_RAM_SIZE: + if (eax_g_is_enabled) + { + value = eax_x_ram_max_size; + } + else + { + context->setError(AL_INVALID_VALUE, EAX_ERROR); + } + + break; + + case AL_EAX_RAM_FREE: + if (eax_g_is_enabled) + { + auto device = context->mALDevice.get(); + std::lock_guard device_lock{device->BufferLock}; + + value = static_cast(device->eax_x_ram_free_size); + } + else + { + context->setError(AL_INVALID_VALUE, EAX_ERROR); + } + + break; + +#undef EAX_ERROR + +#endif // ALSOFT_EAX + default: context->setError(AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname); } @@ -418,7 +477,7 @@ START_API_FUNC } END_API_FUNC -extern "C" AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) +AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) START_API_FUNC { ContextRef context{GetContextRef()}; @@ -445,7 +504,7 @@ START_API_FUNC break; case AL_DEFERRED_UPDATES_SOFT: - if(context->mDeferUpdates.load(std::memory_order_acquire)) + if(context->mDeferUpdates) value = AL_TRUE; break; @@ -627,7 +686,7 @@ START_API_FUNC } END_API_FUNC -extern "C" AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) +AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) START_API_FUNC { if(values) @@ -819,6 +878,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; + std::lock_guard _{context->mPropLock}; context->deferUpdates(); } END_API_FUNC @@ -829,6 +889,7 @@ START_API_FUNC ContextRef context{GetContextRef()}; if UNLIKELY(!context) return; + std::lock_guard _{context->mPropLock}; context->processUpdates(); } END_API_FUNC @@ -874,6 +935,14 @@ void UpdateContextProps(ALCcontext *context) } /* Copy in current property values. */ + ALlistener &listener = context->mListener; + props->Position = listener.Position; + props->Velocity = listener.Velocity; + props->OrientAt = listener.OrientAt; + props->OrientUp = listener.OrientUp; + props->Gain = listener.Gain; + props->MetersPerUnit = listener.mMetersPerUnit; + props->DopplerFactor = context->mDopplerFactor; props->DopplerVelocity = context->mDopplerVelocity; props->SpeedOfSound = context->mSpeedOfSound; diff --git a/Engine/lib/openal-soft/Alc/alc.cpp b/Engine/lib/openal-soft/alc/alc.cpp similarity index 75% rename from Engine/lib/openal-soft/Alc/alc.cpp rename to Engine/lib/openal-soft/alc/alc.cpp index cc2a95369..3bbe43d05 100644 --- a/Engine/lib/openal-soft/Alc/alc.cpp +++ b/Engine/lib/openal-soft/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,53 +62,53 @@ #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 "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 "context.h" #include "core/ambidefs.h" +#include "core/bformatdec.h" #include "core/bs2b.h" +#include "core/context.h" #include "core/cpu_caps.h" #include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" #include "core/except.h" +#include "core/helpers.h" #include "core/mastering.h" -#include "core/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" #include "backends/loopback.h" +#ifdef HAVE_PIPEWIRE +#include "backends/pipewire.h" +#endif #ifdef HAVE_JACK #include "backends/jack.h" #endif @@ -153,6 +155,36 @@ #include "backends/wave.h" #endif +#ifdef ALSOFT_EAX +#include "al/eax_globals.h" +#include "al/eax_x_ram.h" +#endif // ALSOFT_EAX + + +FILE *gLogFile{stderr}; +#ifdef _DEBUG +LogLevel gLogLevel{LogLevel::Warning}; +#else +LogLevel gLogLevel{LogLevel::Error}; +#endif + +/************************************************ + * Library initialization + ************************************************/ +#if defined(_WIN32) && !defined(AL_LIBTYPE_STATIC) +BOOL APIENTRY DllMain(HINSTANCE module, DWORD reason, LPVOID /*reserved*/) +{ + switch(reason) + { + case DLL_PROCESS_ATTACH: + /* Pin the DLL so we won't get unloaded until the process terminates */ + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + reinterpret_cast(module), &module); + break; + } + return TRUE; +} +#endif namespace { @@ -161,6 +193,7 @@ using std::chrono::seconds; using std::chrono::nanoseconds; using voidp = void*; +using float2 = std::array; /************************************************ @@ -172,15 +205,12 @@ struct BackendInfo { }; BackendInfo BackendList[] = { -#ifdef HAVE_JACK - { "jack", JackBackendFactory::getFactory }, +#ifdef HAVE_PIPEWIRE + { "pipewire", PipeWireBackendFactory::getFactory }, #endif #ifdef HAVE_PULSEAUDIO { "pulse", PulseBackendFactory::getFactory }, #endif -#ifdef HAVE_ALSA - { "alsa", AlsaBackendFactory::getFactory }, -#endif #ifdef HAVE_WASAPI { "wasapi", WasapiBackendFactory::getFactory }, #endif @@ -199,9 +229,15 @@ BackendInfo BackendList[] = { #ifdef HAVE_SNDIO { "sndio", SndIOBackendFactory::getFactory }, #endif +#ifdef HAVE_ALSA + { "alsa", AlsaBackendFactory::getFactory }, +#endif #ifdef HAVE_OSS { "oss", OSSBackendFactory::getFactory }, #endif +#ifdef HAVE_JACK + { "jack", JackBackendFactory::getFactory }, +#endif #ifdef HAVE_DSOUND { "dsound", DSoundBackendFactory::getFactory }, #endif @@ -269,6 +305,8 @@ const struct { DECL(alcGetInteger64vSOFT), + DECL(alcReopenDeviceSOFT), + DECL(alEnable), DECL(alDisable), DECL(alIsEnabled), @@ -414,6 +452,13 @@ const struct { DECL(alAuxiliaryEffectSlotPlayvSOFT), DECL(alAuxiliaryEffectSlotStopSOFT), DECL(alAuxiliaryEffectSlotStopvSOFT), +#ifdef ALSOFT_EAX +}, eaxFunctions[] = { + DECL(EAXGet), + DECL(EAXSet), + DECL(EAXGetBufferMode), + DECL(EAXSetBufferMode), +#endif }; #undef DECL @@ -491,6 +536,15 @@ constexpr struct { DECL(ALC_OUTPUT_LIMITER_SOFT), + DECL(ALC_OUTPUT_MODE_SOFT), + DECL(ALC_ANY_SOFT), + DECL(ALC_STEREO_BASIC_SOFT), + DECL(ALC_STEREO_UHJ_SOFT), + DECL(ALC_STEREO_HRTF_SOFT), + DECL(ALC_SURROUND_5_1_SOFT), + DECL(ALC_SURROUND_6_1_SOFT), + DECL(ALC_SURROUND_7_1_SOFT), + DECL(ALC_NO_ERROR), DECL(ALC_INVALID_DEVICE), DECL(ALC_INVALID_CONTEXT), @@ -821,6 +875,31 @@ 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_STEREO_MODE_SOFT), + DECL(AL_NORMAL_SOFT), + DECL(AL_SUPER_STEREO_SOFT), + DECL(AL_SUPER_STEREO_WIDTH_SOFT), + + DECL(AL_STOP_SOURCES_ON_DISCONNECT_SOFT), + +#ifdef ALSOFT_EAX +}, eaxEnumerations[] = { + DECL(AL_EAX_RAM_SIZE), + DECL(AL_EAX_RAM_FREE), + DECL(AL_STORAGE_AUTOMATIC), + DECL(AL_STORAGE_HARDWARE), + DECL(AL_STORAGE_ACCESSIBLE), +#endif // ALSOFT_EAX }; #undef DECL @@ -843,86 +922,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. */ @@ -939,9 +949,11 @@ constexpr ALCchar alcNoDeviceExtList[] = "ALC_ENUMERATE_ALL_EXT " "ALC_ENUMERATION_EXT " "ALC_EXT_CAPTURE " + "ALC_EXT_EFX " "ALC_EXT_thread_local_context " "ALC_SOFT_loopback " - "ALC_SOFT_loopback_bformat"; + "ALC_SOFT_loopback_bformat " + "ALC_SOFT_reopen_device"; constexpr ALCchar alcExtensionList[] = "ALC_ENUMERATE_ALL_EXT " "ALC_ENUMERATION_EXT " @@ -955,7 +967,9 @@ constexpr ALCchar alcExtensionList[] = "ALC_SOFT_loopback " "ALC_SOFT_loopback_bformat " "ALC_SOFT_output_limiter " - "ALC_SOFT_pause_device"; + "ALC_SOFT_output_mode " + "ALC_SOFT_pause_device " + "ALC_SOFT_reopen_device"; constexpr int alcMajorVersion{1}; constexpr int alcMinorVersion{1}; @@ -963,12 +977,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; @@ -1017,7 +1025,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 @@ -1113,9 +1121,27 @@ 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(); + CompatFlagBitset compatflags{}; + auto checkflag = [](const char *envname, const char *optname) -> bool + { + if(auto optval = al::getenv(envname)) + { + if(al::strcasecmp(optval->c_str(), "true") == 0 + || strtol(optval->c_str(), nullptr, 0) == 1) + return true; + return false; + } + return GetConfigValueBool(nullptr, "game_compat", optname, false); + }; + compatflags.set(CompatFlags::ReverseX, checkflag("__ALSOFT_REVERSE_X", "reverse-x")); + compatflags.set(CompatFlags::ReverseY, checkflag("__ALSOFT_REVERSE_Y", "reverse-y")); + compatflags.set(CompatFlags::ReverseZ, checkflag("__ALSOFT_REVERSE_Z", "reverse-z")); + + aluInit(compatflags); + Voice::InitMixer(ConfigValueStr(nullptr, nullptr, "resampler")); auto traperr = al::getenv("ALSOFT_TRAP_ERROR"); if(traperr && (al::strcasecmp(traperr->c_str(), "true") == 0 @@ -1255,10 +1281,31 @@ 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); + +#ifdef ALSOFT_EAX + { + static constexpr char eax_block_name[] = "eax"; + + if(const auto eax_enable_opt = ConfigValueBool(nullptr, eax_block_name, "enable")) + { + eax_g_is_enabled = *eax_enable_opt; + if(!eax_g_is_enabled) + TRACE("%s\n", "EAX disabled by a configuration."); + } + else + eax_g_is_enabled = true; + + if(eax_g_is_enabled && DisabledEffects[EAXREVERB_EFFECT]) + { + eax_g_is_enabled = false; + TRACE("%s\n", "EAX disabled because EAXReverb is disabled."); + } + } +#endif // ALSOFT_EAX } #define DO_INITCONFIG() std::call_once(alc_config_once, [](){alc_initconfig();}) @@ -1295,40 +1342,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) @@ -1424,8 +1437,7 @@ ALCenum EnumFromDevFmt(DevFmtChannels channels) case DevFmtMono: return ALC_MONO_SOFT; case DevFmtStereo: return ALC_STEREO_SOFT; case DevFmtQuad: return ALC_QUAD_SOFT; - case DevFmtX51: /* fall-through */ - case DevFmtX51Rear: return ALC_5POINT1_SOFT; + case DevFmtX51: return ALC_5POINT1_SOFT; case DevFmtX61: return ALC_6POINT1_SOFT; case DevFmtX71: return ALC_7POINT1_SOFT; case DevFmtAmbi3D: return ALC_BFORMAT3D_SOFT; @@ -1480,15 +1492,6 @@ ALCenum EnumFromDevAmbi(DevAmbiScaling scaling) * existing ones. Based on Wine's DSound downmix values, which are based on * PulseAudio's. */ -const std::array MonoDownmix{{ - { FrontLeft, {{{FrontCenter, 0.5f}, {LFE, 0.0f}}} }, - { FrontRight, {{{FrontCenter, 0.5f}, {LFE, 0.0f}}} }, - { SideLeft, {{{FrontCenter, 0.5f/9.0f}, {LFE, 0.0f}}} }, - { SideRight, {{{FrontCenter, 0.5f/9.0f}, {LFE, 0.0f}}} }, - { BackLeft, {{{FrontCenter, 0.5f/9.0f}, {LFE, 0.0f}}} }, - { BackRight, {{{FrontCenter, 0.5f/9.0f}, {LFE, 0.0f}}} }, - { BackCenter, {{{FrontCenter, 1.0f/9.0f}, {LFE, 0.0f}}} }, -}}; const std::array StereoDownmix{{ { FrontCenter, {{{FrontLeft, 0.5f}, {FrontRight, 0.5f}}} }, { SideLeft, {{{FrontLeft, 1.0f/9.0f}, {FrontRight, 0.0f}}} }, @@ -1508,11 +1511,6 @@ const std::array X51Downmix{{ { BackRight, {{{SideLeft, 0.0f}, {SideRight, 1.0f}}} }, { BackCenter, {{{SideLeft, 0.5f}, {SideRight, 0.5f}}} }, }}; -const std::array X51RearDownmix{{ - { SideLeft, {{{BackLeft, 1.0f}, {BackRight, 0.0f}}} }, - { SideRight, {{{BackLeft, 0.0f}, {BackRight, 1.0f}}} }, - { BackCenter, {{{BackLeft, 0.5f}, {BackRight, 0.5f}}} }, -}}; const std::array X61Downmix{{ { BackLeft, {{{BackCenter, 0.5f}, {SideLeft, 0.5f}}} }, { BackRight, {{{BackCenter, 0.5f}, {SideRight, 0.5f}}} }, @@ -1521,92 +1519,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) @@ -1627,21 +1542,21 @@ 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}; - constexpr bool AutoRelease{true}; - constexpr bool AutoPostGain{true}; - constexpr bool AutoDeclip{true}; - constexpr float LookAheadTime{0.001f}; - constexpr float HoldTime{0.002f}; - constexpr float PreGainDb{0.0f}; - constexpr float PostGainDb{0.0f}; - constexpr float Ratio{std::numeric_limits::infinity()}; - constexpr float KneeDb{0.0f}; - constexpr float AttackTime{0.02f}; - constexpr float ReleaseTime{0.2f}; + static constexpr bool AutoKnee{true}; + static constexpr bool AutoAttack{true}; + static constexpr bool AutoRelease{true}; + static constexpr bool AutoPostGain{true}; + static constexpr bool AutoDeclip{true}; + static constexpr float LookAheadTime{0.001f}; + static constexpr float HoldTime{0.002f}; + static constexpr float PreGainDb{0.0f}; + static constexpr float PostGainDb{0.0f}; + static constexpr float Ratio{std::numeric_limits::infinity()}; + static constexpr float KneeDb{0.0f}; + static constexpr float AttackTime{0.02f}; + static constexpr float ReleaseTime{0.2f}; return Compressor::Create(device->RealOut.Buffer.size(), static_cast(device->Frequency), AutoKnee, AutoAttack, AutoRelease, AutoPostGain, AutoDeclip, LookAheadTime, HoldTime, @@ -1666,23 +1581,18 @@ 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}; - ALCenum gainLimiter{device->LimiterState}; - uint new_sends{device->NumAuxSends}; - DevFmtChannels oldChans; - DevFmtType oldType; - int hrtf_id{-1}; - uint oldFreq; - if((!attrList || !attrList[0]) && device->Type == DeviceType::Loopback) { WARN("Missing attributes for loopback device\n"); return ALC_INVALID_VALUE; } + al::optional stereomode{}; + al::optional optlimit{}; + int hrtf_id{-1}; + // Check for attributes if(attrList && attrList[0]) { @@ -1694,83 +1604,82 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) al::optional opttype; al::optional optlayout; al::optional optscale; + al::optional opthrtf; + ALenum outmode{ALC_ANY_SOFT}; uint aorder{0u}; uint freq{0u}; -#define TRACE_ATTR(a, v) TRACE("%s = %d\n", #a, v) +#define ATTRIBUTE(a) a: TRACE("%s = %d\n", #a, attrList[attrIdx + 1]); size_t attrIdx{0}; while(attrList[attrIdx]) { switch(attrList[attrIdx]) { - case ALC_FORMAT_CHANNELS_SOFT: - TRACE_ATTR(ALC_FORMAT_CHANNELS_SOFT, attrList[attrIdx + 1]); + case ATTRIBUTE(ALC_FORMAT_CHANNELS_SOFT) optchans = DevFmtChannelsFromEnum(attrList[attrIdx + 1]); break; - case ALC_FORMAT_TYPE_SOFT: - TRACE_ATTR(ALC_FORMAT_TYPE_SOFT, attrList[attrIdx + 1]); + case ATTRIBUTE(ALC_FORMAT_TYPE_SOFT) opttype = DevFmtTypeFromEnum(attrList[attrIdx + 1]); break; - case ALC_FREQUENCY: + case ATTRIBUTE(ALC_FREQUENCY) freq = static_cast(attrList[attrIdx + 1]); - TRACE_ATTR(ALC_FREQUENCY, freq); break; - case ALC_AMBISONIC_LAYOUT_SOFT: - TRACE_ATTR(ALC_AMBISONIC_LAYOUT_SOFT, attrList[attrIdx + 1]); + case ATTRIBUTE(ALC_AMBISONIC_LAYOUT_SOFT) optlayout = DevAmbiLayoutFromEnum(attrList[attrIdx + 1]); break; - case ALC_AMBISONIC_SCALING_SOFT: - TRACE_ATTR(ALC_AMBISONIC_SCALING_SOFT, attrList[attrIdx + 1]); + case ATTRIBUTE(ALC_AMBISONIC_SCALING_SOFT) optscale = DevAmbiScalingFromEnum(attrList[attrIdx + 1]); break; - case ALC_AMBISONIC_ORDER_SOFT: + case ATTRIBUTE(ALC_AMBISONIC_ORDER_SOFT) aorder = static_cast(attrList[attrIdx + 1]); - TRACE_ATTR(ALC_AMBISONIC_ORDER_SOFT, aorder); break; - case ALC_MONO_SOURCES: + case ATTRIBUTE(ALC_MONO_SOURCES) numMono = static_cast(attrList[attrIdx + 1]); - TRACE_ATTR(ALC_MONO_SOURCES, numMono); if(numMono > INT_MAX) numMono = 0; break; - case ALC_STEREO_SOURCES: + case ATTRIBUTE(ALC_STEREO_SOURCES) numStereo = static_cast(attrList[attrIdx + 1]); - TRACE_ATTR(ALC_STEREO_SOURCES, numStereo); if(numStereo > INT_MAX) numStereo = 0; break; - case ALC_MAX_AUXILIARY_SENDS: + case ATTRIBUTE(ALC_MAX_AUXILIARY_SENDS) numSends = static_cast(attrList[attrIdx + 1]); - TRACE_ATTR(ALC_MAX_AUXILIARY_SENDS, numSends); if(numSends > INT_MAX) numSends = 0; else numSends = minu(numSends, MAX_SENDS); break; - case ALC_HRTF_SOFT: - TRACE_ATTR(ALC_HRTF_SOFT, attrList[attrIdx + 1]); + case ATTRIBUTE(ALC_HRTF_SOFT) if(attrList[attrIdx + 1] == ALC_FALSE) - hrtf_appreq = Hrtf_Disable; + opthrtf = false; else if(attrList[attrIdx + 1] == ALC_TRUE) - hrtf_appreq = Hrtf_Enable; - else - hrtf_appreq = Hrtf_Default; + opthrtf = true; + else if(attrList[attrIdx + 1] == ALC_DONT_CARE_SOFT) + opthrtf = al::nullopt; break; - case ALC_HRTF_ID_SOFT: + case ATTRIBUTE(ALC_HRTF_ID_SOFT) hrtf_id = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_HRTF_ID_SOFT, hrtf_id); break; - case ALC_OUTPUT_LIMITER_SOFT: - gainLimiter = attrList[attrIdx + 1]; - TRACE_ATTR(ALC_OUTPUT_LIMITER_SOFT, gainLimiter); + case ATTRIBUTE(ALC_OUTPUT_LIMITER_SOFT) + if(attrList[attrIdx + 1] == ALC_FALSE) + optlimit = false; + else if(attrList[attrIdx + 1] == ALC_TRUE) + optlimit = true; + else if(attrList[attrIdx + 1] == ALC_DONT_CARE_SOFT) + optlimit = al::nullopt; + break; + + case ATTRIBUTE(ALC_OUTPUT_MODE_SOFT) + outmode = attrList[attrIdx + 1]; break; default: @@ -1781,7 +1690,7 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) attrIdx += 2; } -#undef TRACE_ATTR +#undef ATTRIBUTE const bool loopback{device->Type == DeviceType::Loopback}; if(loopback) @@ -1811,7 +1720,30 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) UpdateClockBase(device); - const char *devname{nullptr}; + /* Calculate the max number of sources, and split them between the mono + * and stereo count given the requested number of stereo sources. + */ + if(auto srcsopt = device->configValue(nullptr, "sources")) + { + if(*srcsopt <= 0) numMono = 256; + else numMono = *srcsopt; + } + else + { + if(numMono > INT_MAX-numStereo) + numMono = INT_MAX-numStereo; + numMono = maxu(numMono+numStereo, 256); + } + numStereo = minu(numStereo, numMono); + numMono -= numStereo; + device->SourcesMax = numMono + numStereo; + device->NumMonoSources = numMono; + device->NumStereoSources = numStereo; + + if(auto sendsopt = device->configValue(nullptr, "sends")) + numSends = minu(numSends, static_cast(clampi(*sendsopt, 0, MAX_SENDS))); + device->NumAuxSends = numSends; + if(loopback) { device->Frequency = freq; @@ -1823,19 +1755,32 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) device->mAmbiLayout = *optlayout; device->mAmbiScale = *optscale; } + else if(device->FmtChans == DevFmtStereo) + { + if(opthrtf) + stereomode = *opthrtf ? StereoEncoding::Hrtf : StereoEncoding::Default; + + if(outmode == ALC_STEREO_BASIC_SOFT) + stereomode = StereoEncoding::Basic; + else if(outmode == ALC_STEREO_UHJ_SOFT) + stereomode = StereoEncoding::Uhj; + else if(outmode == ALC_STEREO_HRTF_SOFT) + stereomode = StereoEncoding::Hrtf; + } + device->Flags.set(FrequencyRequest).set(ChannelsRequest).set(SampleTypeRequest); } else { - devname = device->DeviceName.c_str(); - + device->Flags.reset(FrequencyRequest).reset(ChannelsRequest).reset(SampleTypeRequest); + device->FmtType = DevFmtTypeDefault; + device->FmtChans = DevFmtChannelsDefault; + device->mAmbiOrder = 0; device->BufferSize = DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES; device->UpdateSize = DEFAULT_UPDATE_SIZE; device->Frequency = DEFAULT_OUTPUT_RATE; - freq = ConfigValueUInt(devname, nullptr, "frequency").value_or(freq); - if(freq < 1) - device->Flags.reset(FrequencyRequest); - else + freq = device->configValue(nullptr, "frequency").value_or(freq); + if(freq > 0) { freq = clampu(freq, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE); @@ -1847,43 +1792,54 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) device->Flags.set(FrequencyRequest); } - if(auto persizeopt = ConfigValueUInt(devname, nullptr, "period_size")) - device->UpdateSize = clampu(*persizeopt, 64, 8192); + auto set_device_mode = [device](DevFmtChannels chans) noexcept + { + device->FmtChans = chans; + device->Flags.set(ChannelsRequest); + }; + if(opthrtf) + { + if(*opthrtf) + { + set_device_mode(DevFmtStereo); + stereomode = StereoEncoding::Hrtf; + } + else + stereomode = StereoEncoding::Default; + } - if(auto peropt = ConfigValueUInt(devname, nullptr, "periods")) - device->BufferSize = device->UpdateSize * clampu(*peropt, 2, 16); - else - device->BufferSize = maxu(device->BufferSize, device->UpdateSize*2); + using OutputMode = ALCdevice::OutputMode; + switch(OutputMode(outmode)) + { + case OutputMode::Any: break; + case OutputMode::Mono: set_device_mode(DevFmtMono); break; + case OutputMode::Stereo: set_device_mode(DevFmtStereo); break; + case OutputMode::StereoBasic: + set_device_mode(DevFmtStereo); + stereomode = StereoEncoding::Basic; + break; + case OutputMode::Uhj2: + set_device_mode(DevFmtStereo); + stereomode = StereoEncoding::Uhj; + break; + case OutputMode::Hrtf: + set_device_mode(DevFmtStereo); + stereomode = StereoEncoding::Hrtf; + break; + case OutputMode::Quad: set_device_mode(DevFmtQuad); break; + case OutputMode::X51: set_device_mode(DevFmtX51); break; + case OutputMode::X61: set_device_mode(DevFmtX61); break; + case OutputMode::X71: set_device_mode(DevFmtX71); break; + } } - - if(numMono > INT_MAX-numStereo) - numMono = INT_MAX-numStereo; - numMono += numStereo; - if(auto srcsopt = ConfigValueUInt(devname, nullptr, "sources")) - { - if(*srcsopt <= 0) numMono = 256; - else numMono = *srcsopt; - } - else - numMono = maxu(numMono, 256); - numStereo = minu(numStereo, numMono); - numMono -= numStereo; - device->SourcesMax = numMono + numStereo; - - device->NumMonoSources = numMono; - device->NumStereoSources = numStereo; - - if(auto sendsopt = ConfigValueInt(devname, nullptr, "sends")) - new_sends = minu(numSends, static_cast(clampi(*sendsopt, 0, MAX_SENDS))); - else - new_sends = numSends; } if(device->Flags.test(DeviceRunning)) return ALC_NO_ERROR; device->AvgSpeakerDist = 0.0f; - device->Uhj_Encoder = nullptr; + device->mNFCtrlFilter = NfcFilter{}; + device->mUhjEncoder = nullptr; device->AmbiDecoder = nullptr; device->Bs2b = nullptr; device->PostProcess = nullptr; @@ -1908,46 +1864,162 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) device->DitherDepth = 0.0f; device->DitherSeed = DitherRNGSeed; + device->mHrtfStatus = ALC_HRTF_DISABLED_SOFT; + /************************************************************************* - * Update device format request if HRTF is requested + * Update device format request from the user configuration */ - device->HrtfStatus = ALC_HRTF_DISABLED_SOFT; if(device->Type != DeviceType::Loopback) { - if(auto hrtfopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "hrtf")) + if(auto typeopt = device->configValue(nullptr, "sample-type")) + { + static constexpr struct TypeMap { + const char name[8]; + DevFmtType type; + } typelist[] = { + { "int8", DevFmtByte }, + { "uint8", DevFmtUByte }, + { "int16", DevFmtShort }, + { "uint16", DevFmtUShort }, + { "int32", DevFmtInt }, + { "uint32", DevFmtUInt }, + { "float32", DevFmtFloat }, + }; + + const ALCchar *fmt{typeopt->c_str()}; + auto iter = std::find_if(std::begin(typelist), std::end(typelist), + [fmt](const TypeMap &entry) -> bool + { return al::strcasecmp(entry.name, fmt) == 0; }); + if(iter == std::end(typelist)) + ERR("Unsupported sample-type: %s\n", fmt); + else + { + device->FmtType = iter->type; + device->Flags.set(SampleTypeRequest); + } + } + if(auto chanopt = device->configValue(nullptr, "channels")) + { + static constexpr struct ChannelMap { + const char name[16]; + DevFmtChannels chans; + uint8_t order; + } chanlist[] = { + { "mono", DevFmtMono, 0 }, + { "stereo", DevFmtStereo, 0 }, + { "quad", DevFmtQuad, 0 }, + { "surround51", DevFmtX51, 0 }, + { "surround61", DevFmtX61, 0 }, + { "surround71", DevFmtX71, 0 }, + { "surround51rear", DevFmtX51, 0 }, + { "ambi1", DevFmtAmbi3D, 1 }, + { "ambi2", DevFmtAmbi3D, 2 }, + { "ambi3", DevFmtAmbi3D, 3 }, + }; + + const ALCchar *fmt{chanopt->c_str()}; + auto iter = std::find_if(std::begin(chanlist), std::end(chanlist), + [fmt](const ChannelMap &entry) -> bool + { return al::strcasecmp(entry.name, fmt) == 0; }); + if(iter == std::end(chanlist)) + ERR("Unsupported channels: %s\n", fmt); + else + { + device->FmtChans = iter->chans; + device->mAmbiOrder = iter->order; + device->Flags.set(ChannelsRequest); + } + } + if(auto ambiopt = device->configValue(nullptr, "ambi-format")) + { + const ALCchar *fmt{ambiopt->c_str()}; + if(al::strcasecmp(fmt, "fuma") == 0) + { + if(device->mAmbiOrder > 3) + ERR("FuMa is incompatible with %d%s order ambisonics (up to 3rd order only)\n", + device->mAmbiOrder, + (((device->mAmbiOrder%100)/10) == 1) ? "th" : + ((device->mAmbiOrder%10) == 1) ? "st" : + ((device->mAmbiOrder%10) == 2) ? "nd" : + ((device->mAmbiOrder%10) == 3) ? "rd" : "th"); + else + { + device->mAmbiLayout = DevAmbiLayout::FuMa; + device->mAmbiScale = DevAmbiScaling::FuMa; + } + } + else if(al::strcasecmp(fmt, "acn+fuma") == 0) + { + if(device->mAmbiOrder > 3) + ERR("FuMa is incompatible with %d%s order ambisonics (up to 3rd order only)\n", + device->mAmbiOrder, + (((device->mAmbiOrder%100)/10) == 1) ? "th" : + ((device->mAmbiOrder%10) == 1) ? "st" : + ((device->mAmbiOrder%10) == 2) ? "nd" : + ((device->mAmbiOrder%10) == 3) ? "rd" : "th"); + else + { + device->mAmbiLayout = DevAmbiLayout::ACN; + device->mAmbiScale = DevAmbiScaling::FuMa; + } + } + else if(al::strcasecmp(fmt, "ambix") == 0 || al::strcasecmp(fmt, "acn+sn3d") == 0) + { + device->mAmbiLayout = DevAmbiLayout::ACN; + device->mAmbiScale = DevAmbiScaling::SN3D; + } + else if(al::strcasecmp(fmt, "acn+n3d") == 0) + { + device->mAmbiLayout = DevAmbiLayout::ACN; + device->mAmbiScale = DevAmbiScaling::N3D; + } + else + ERR("Unsupported ambi-format: %s\n", fmt); + } + + if(auto persizeopt = device->configValue(nullptr, "period_size")) + device->UpdateSize = clampu(*persizeopt, 64, 8192); + + if(auto peropt = device->configValue(nullptr, "periods")) + device->BufferSize = device->UpdateSize * clampu(*peropt, 2, 16); + else + device->BufferSize = maxu(device->BufferSize, device->UpdateSize*2); + + if(auto hrtfopt = device->configValue(nullptr, "hrtf")) { const char *hrtf{hrtfopt->c_str()}; if(al::strcasecmp(hrtf, "true") == 0) - hrtf_userreq = Hrtf_Enable; + { + stereomode = StereoEncoding::Hrtf; + device->FmtChans = DevFmtStereo; + device->Flags.set(ChannelsRequest); + } else if(al::strcasecmp(hrtf, "false") == 0) - hrtf_userreq = Hrtf_Disable; + { + if(!stereomode || *stereomode == StereoEncoding::Hrtf) + stereomode = StereoEncoding::Default; + } else if(al::strcasecmp(hrtf, "auto") != 0) ERR("Unexpected hrtf value: %s\n", hrtf); } - - if(hrtf_userreq == Hrtf_Enable || (hrtf_userreq != Hrtf_Disable && hrtf_appreq == Hrtf_Enable)) - { - device->FmtChans = DevFmtStereo; - device->Flags.set(ChannelsRequest); - } } - oldFreq = device->Frequency; - oldChans = device->FmtChans; - oldType = device->FmtType; - TRACE("Pre-reset: %s%s, %s%s, %s%uhz, %u / %u buffer\n", device->Flags.test(ChannelsRequest)?"*":"", DevFmtChannelsString(device->FmtChans), device->Flags.test(SampleTypeRequest)?"*":"", DevFmtTypeString(device->FmtType), device->Flags.test(FrequencyRequest)?"*":"", device->Frequency, device->UpdateSize, device->BufferSize); + const uint oldFreq{device->Frequency}; + const DevFmtChannels oldChans{device->FmtChans}; + const DevFmtType oldType{device->FmtType}; try { auto backend = device->Backend.get(); if(!backend->reset()) throw al::backend_exception{al::backend_error::DeviceError, "Device reset failure"}; } catch(std::exception &e) { + ERR("Device error: %s\n", e.what()); device->handleDisconnect("%s", e.what()); return ALC_INVALID_DEVICE; } @@ -1974,39 +2046,65 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), device->Frequency, device->UpdateSize, device->BufferSize); + if(device->Type != DeviceType::Loopback) + { + if(auto modeopt = device->configValue(nullptr, "stereo-mode")) + { + const char *mode{modeopt->c_str()}; + if(al::strcasecmp(mode, "headphones") == 0) + device->Flags.set(DirectEar); + else if(al::strcasecmp(mode, "speakers") == 0) + device->Flags.reset(DirectEar); + else if(al::strcasecmp(mode, "auto") != 0) + ERR("Unexpected stereo-mode: %s\n", mode); + } + + if(auto encopt = device->configValue(nullptr, "stereo-encoding")) + { + const char *mode{encopt->c_str()}; + if(al::strcasecmp(mode, "panpot") == 0) + stereomode = al::make_optional(StereoEncoding::Basic); + else if(al::strcasecmp(mode, "uhj") == 0) + stereomode = al::make_optional(StereoEncoding::Uhj); + else if(al::strcasecmp(mode, "hrtf") == 0) + stereomode = al::make_optional(StereoEncoding::Hrtf); + else + ERR("Unexpected stereo-encoding: %s\n", mode); + } + } + + aluInitRenderer(device, hrtf_id, stereomode); + + TRACE("Max sources: %d (%d + %d), effect slots: %d, sends: %d\n", + device->SourcesMax, device->NumMonoSources, device->NumStereoSources, + device->AuxiliaryEffectSlotMax, device->NumAuxSends); + switch(device->FmtChans) { - case DevFmtMono: device->RealOut.RemixMap = MonoDownmix; break; - case DevFmtStereo: device->RealOut.RemixMap = StereoDownmix; break; + case DevFmtMono: break; + case DevFmtStereo: + if(!device->mUhjEncoder) + device->RealOut.RemixMap = StereoDownmix; + break; case DevFmtQuad: device->RealOut.RemixMap = QuadDownmix; break; case DevFmtX51: device->RealOut.RemixMap = X51Downmix; break; - case DevFmtX51Rear: device->RealOut.RemixMap = X51RearDownmix; break; case DevFmtX61: device->RealOut.RemixMap = X61Downmix; break; case DevFmtX71: device->RealOut.RemixMap = X71Downmix; break; case DevFmtAmbi3D: break; } - aluInitRenderer(device, hrtf_id, hrtf_appreq, hrtf_userreq); - - device->NumAuxSends = new_sends; - TRACE("Max sources: %d (%d + %d), effect slots: %d, sends: %d\n", - device->SourcesMax, device->NumMonoSources, device->NumStereoSources, - device->AuxiliaryEffectSlotMax, device->NumAuxSends); - nanoseconds::rep sample_delay{0}; - if(device->Uhj_Encoder) - sample_delay += Uhj2Encoder::sFilterSize; - if(device->mHrtfState) - sample_delay += HrtfDirectDelay; + if(device->mUhjEncoder) + sample_delay += UhjEncoder::sFilterDelay; if(auto *ambidec = device->AmbiDecoder.get()) { if(ambidec->hasStablizer()) sample_delay += FrontStablizer::DelayLength; } - if(GetConfigValueBool(device->DeviceName.c_str(), nullptr, "dither", 1)) + if(device->getConfigValueBool(nullptr, "dither", true)) { - int depth{ConfigValueInt(device->DeviceName.c_str(), nullptr, "dither-depth").value_or(0)}; + int depth{device->configValue(nullptr, "dither-depth").value_or(0)}; if(depth <= 0) { switch(device->FmtType) @@ -2038,51 +2136,48 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) TRACE("Dithering enabled (%d-bit, %g)\n", float2int(std::log2(device->DitherDepth)+0.5f)+1, device->DitherDepth); - device->LimiterState = gainLimiter; - if(auto limopt = ConfigValueBool(device->DeviceName.c_str(), nullptr, "output-limiter")) - gainLimiter = *limopt ? ALC_TRUE : ALC_FALSE; + if(auto limopt = device->configValue(nullptr, "output-limiter")) + optlimit = limopt; - /* Valid values for gainLimiter are ALC_DONT_CARE_SOFT, ALC_TRUE, and - * ALC_FALSE. For ALC_DONT_CARE_SOFT, use the limiter for integer-based - * output (where samples must be clamped), and don't for floating-point - * (which can take unclamped samples). + /* If the gain limiter is unset, use the limiter for integer-based output + * (where samples must be clamped), and don't for floating-point (which can + * take unclamped samples). */ - if(gainLimiter == ALC_DONT_CARE_SOFT) + if(!optlimit) { switch(device->FmtType) { - case DevFmtByte: - case DevFmtUByte: - case DevFmtShort: - case DevFmtUShort: - case DevFmtInt: - case DevFmtUInt: - gainLimiter = ALC_TRUE; - break; - case DevFmtFloat: - gainLimiter = ALC_FALSE; - break; + case DevFmtByte: + case DevFmtUByte: + case DevFmtShort: + case DevFmtUShort: + case DevFmtInt: + case DevFmtUInt: + optlimit = true; + break; + case DevFmtFloat: + break; } } - if(gainLimiter == ALC_FALSE) + if(optlimit.value_or(false) == false) TRACE("Output limiter disabled\n"); else { float thrshld{1.0f}; switch(device->FmtType) { - case DevFmtByte: - case DevFmtUByte: - thrshld = 127.0f / 128.0f; - break; - case DevFmtShort: - case DevFmtUShort: - thrshld = 32767.0f / 32768.0f; - break; - case DevFmtInt: - case DevFmtUInt: - case DevFmtFloat: - break; + case DevFmtByte: + case DevFmtUByte: + thrshld = 127.0f / 128.0f; + break; + case DevFmtShort: + case DevFmtUShort: + thrshld = 32767.0f / 32768.0f; + break; + case DevFmtInt: + case DevFmtUInt: + case DevFmtFloat: + break; } if(device->DitherDepth > 0.0f) thrshld -= 1.0f / device->DitherDepth; @@ -2100,8 +2195,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{}; @@ -2173,22 +2270,10 @@ 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 = true; } } - /* Clear any pre-existing voice property structs, in case the number of - * auxiliary sends is changing. Active sources will have updates - * respecified in UpdateAllSourceProps. - */ - VoicePropsItem *vprops{context->mFreeVoiceProps.exchange(nullptr, std::memory_order_acq_rel)}; - while(vprops) - { - VoicePropsItem *next = vprops->next.load(std::memory_order_relaxed); - delete vprops; - vprops = next; - } - auto voicelist = context->getVoicesSpan(); for(Voice *voice : voicelist) { @@ -2203,7 +2288,8 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) SendParams{}); } - delete voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel); + if(VoicePropsItem *props{voice->mUpdate.exchange(nullptr, std::memory_order_relaxed)}) + AtomicReplaceHead(context->mFreeVoiceProps, props); /* Force the voice to stopped if it was stopping. */ Voice::State vstate{Voice::Stopping}; @@ -2212,59 +2298,15 @@ 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); } + /* Clear all voice props to let them get allocated again. */ + context->mVoicePropClusters.clear(); + context->mFreeVoiceProps.store(nullptr, std::memory_order_relaxed); srclock.unlock(); - context->mPropsClean.test_and_set(std::memory_order_release); + context->mPropsDirty = false; UpdateContextProps(context); - context->mListener.PropsClean.test_and_set(std::memory_order_release); - UpdateListenerProps(context); UpdateAllSourceProps(context); } mixer_mode.leave(); @@ -2277,52 +2319,66 @@ 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; } + TRACE("Post-start: %s, %s, %uhz, %u / %u buffer\n", + DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), + device->Frequency, device->UpdateSize, device->BufferSize); } return ALC_NO_ERROR; } - -ALCdevice::ALCdevice(DeviceType type) : Type{type}, 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->mVoicePropClusters.clear(); + ctx->mFreeVoiceProps.store(nullptr, 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"); + ctx->mVoiceClusters.clear(); + ctx->allocVoices(std::max(256, + ctx->mActiveVoiceCount.load(std::memory_order_relaxed))); + } - 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"); + device->Connected.store(true); + } - mHrtf = nullptr; + ALCenum err{UpdateDeviceParams(device, attrList)}; + if LIKELY(err == ALC_NO_ERROR) return ALC_TRUE; - 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); @@ -2335,217 +2391,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); @@ -2557,16 +2406,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::getThreadContext()}; 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}; @@ -2597,7 +2448,10 @@ START_API_FUNC if(!ctx) alcSetError(nullptr, ALC_INVALID_CONTEXT); else + { + std::lock_guard _{ctx->mPropLock}; ctx->deferUpdates(); + } } END_API_FUNC @@ -2611,7 +2465,10 @@ START_API_FUNC if(!ctx) alcSetError(nullptr, ALC_INVALID_CONTEXT); else + { + std::lock_guard _{ctx->mPropLock}; ctx->processUpdates(); + } } END_API_FUNC @@ -2653,7 +2510,17 @@ START_API_FUNC case ALC_ALL_DEVICES_SPECIFIER: if(DeviceRef dev{VerifyDevice(Device)}) - value = dev->DeviceName.c_str(); + { + if(dev->Type == DeviceType::Capture) + alcSetError(dev.get(), ALC_INVALID_ENUM); + else if(dev->Type == DeviceType::Loopback) + value = alcDefaultName; + else + { + std::lock_guard _{dev->StateLock}; + value = dev->DeviceName.c_str(); + } + } else { ProbeAllDevicesList(); @@ -2663,7 +2530,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(); @@ -2705,7 +2580,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); @@ -2721,15 +2596,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; @@ -2751,6 +2617,16 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[0] = alcMinorVersion; return 1; + case ALC_EFX_MAJOR_VERSION: + values[0] = alcEFXMajorVersion; + return 1; + case ALC_EFX_MINOR_VERSION: + values[0] = alcEFXMinorVersion; + return 1; + case ALC_MAX_AUXILIARY_SENDS: + values[0] = MAX_SENDS; + return 1; + case ALC_ATTRIBUTES_SIZE: case ALC_ALL_ATTRIBUTES: case ALC_FREQUENCY: @@ -2774,21 +2650,21 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span return 0; } + std::lock_guard _{device->StateLock}; if(device->Type == DeviceType::Capture) { + static constexpr int MaxCaptureAttributes{9}; switch(param) { case ALC_ATTRIBUTES_SIZE: - values[0] = NumAttrsForDevice(device); + 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 { - std::lock_guard _{device->StateLock}; values[i++] = ALC_MAJOR_VERSION; values[i++] = alcMajorVersion; values[i++] = ALC_MINOR_VERSION; @@ -2798,6 +2674,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; @@ -2809,17 +2686,11 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span return 1; case ALC_CAPTURE_SAMPLES: - { - std::lock_guard _{device->StateLock}; - values[0] = static_cast(device->Backend->availableSamples()); - } + values[0] = static_cast(device->Backend->availableSamples()); return 1; case ALC_CONNECTED: - { - std::lock_guard _{device->StateLock}; - values[0] = device->Connected.load(std::memory_order_acquire); - } + values[0] = device->Connected.load(std::memory_order_acquire); return 1; default: @@ -2829,6 +2700,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 37; + return 31; + }; switch(param) { case ALC_ATTRIBUTES_SIZE: @@ -2841,7 +2718,6 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span alcSetError(device, ALC_INVALID_VALUE); else { - std::lock_guard _{device->StateLock}; values[i++] = ALC_MAJOR_VERSION; values[i++] = alcMajorVersion; values[i++] = ALC_MINOR_VERSION; @@ -2895,7 +2771,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; @@ -2903,6 +2779,9 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[i++] = ALC_MAX_AMBISONIC_ORDER_SOFT; values[i++] = MaxAmbiOrder; + values[i++] = ALC_OUTPUT_MODE_SOFT; + values[i++] = static_cast(device->getOutputMode1()); + values[i++] = 0; } return i; @@ -2933,10 +2812,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span alcSetError(device, ALC_INVALID_DEVICE); return 0; } - { - std::lock_guard _{device->StateLock}; - values[0] = static_cast(device->Frequency / device->UpdateSize); - } + values[0] = static_cast(device->Frequency / device->UpdateSize); return 1; case ALC_SYNC: @@ -3006,10 +2882,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span return 1; case ALC_CONNECTED: - { - std::lock_guard _{device->StateLock}; - values[0] = device->Connected.load(std::memory_order_acquire); - } + values[0] = device->Connected.load(std::memory_order_acquire); return 1; case ALC_HRTF_SOFT: @@ -3017,16 +2890,13 @@ 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(), - std::numeric_limits::max())); - } + device->enumerateHrtfs(); + values[0] = static_cast(minz(device->mHrtfList.size(), + std::numeric_limits::max())); return 1; case ALC_OUTPUT_LIMITER_SOFT: @@ -3037,6 +2907,10 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[0] = MaxAmbiOrder; return 1; + case ALC_OUTPUT_MODE_SOFT: + values[0] = static_cast(device->getOutputMode1()); + return 1; + default: alcSetError(device, ALC_INVALID_ENUM); } @@ -3066,24 +2940,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 41; + return 35; + }; + std::lock_guard _{dev->StateLock}; 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}; values[i++] = ALC_FREQUENCY; values[i++] = dev->Frequency; @@ -3097,6 +2977,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; @@ -3108,12 +2994,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; @@ -3129,25 +3009,27 @@ 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; - ClockLatency clock{GetClockLatency(dev.get())}; + ClockLatency clock{GetClockLatency(dev.get(), dev->Backend.get())}; values[i++] = ALC_DEVICE_CLOCK_SOFT; values[i++] = clock.ClockTime.count(); values[i++] = ALC_DEVICE_LATENCY_SOFT; values[i++] = clock.Latency.count(); + values[i++] = ALC_OUTPUT_MODE_SOFT; + values[i++] = static_cast(device->getOutputMode1()); + values[i++] = 0; } break; case ALC_DEVICE_CLOCK_SOFT: { - std::lock_guard _{dev->StateLock}; uint samplecount, refcount; nanoseconds basecount; do { @@ -3161,11 +3043,7 @@ START_API_FUNC break; case ALC_DEVICE_LATENCY_SOFT: - { - std::lock_guard _{dev->StateLock}; - ClockLatency clock{GetClockLatency(dev.get())}; - *values = clock.Latency.count(); - } + *values = GetClockLatency(dev.get(), dev->Backend.get()).Latency.count(); break; case ALC_DEVICE_CLOCK_LATENCY_SOFT: @@ -3173,8 +3051,7 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_VALUE); else { - std::lock_guard _{dev->StateLock}; - ClockLatency clock{GetClockLatency(dev.get())}; + ClockLatency clock{GetClockLatency(dev.get(), dev->Backend.get())}; values[0] = clock.ClockTime.count(); values[1] = clock.Latency.count(); } @@ -3182,8 +3059,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; } } @@ -3225,15 +3102,23 @@ START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; alcSetError(dev.get(), ALC_INVALID_VALUE); + return nullptr; } - else +#ifdef ALSOFT_EAX + if(eax_g_is_enabled) { - for(const auto &func : alcFunctions) + for(const auto &func : eaxFunctions) { if(strcmp(func.funcName, funcName) == 0) return func.address; } } +#endif + for(const auto &func : alcFunctions) + { + if(strcmp(func.funcName, funcName) == 0) + return func.address; + } return nullptr; } END_API_FUNC @@ -3246,15 +3131,24 @@ START_API_FUNC { DeviceRef dev{VerifyDevice(device)}; alcSetError(dev.get(), ALC_INVALID_VALUE); + return 0; } - else +#ifdef ALSOFT_EAX + if(eax_g_is_enabled) { - for(const auto &enm : alcEnumerations) + for(const auto &enm : eaxEnumerations) { if(strcmp(enm.enumName, enumName) == 0) return enm.value; } } +#endif + for(const auto &enm : alcEnumerations) + { + if(strcmp(enm.enumName, enumName) == 0) + return enm.value; + } + return 0; } END_API_FUNC @@ -3290,7 +3184,7 @@ START_API_FUNC ContextRef context{new ALCcontext{dev}}; context->init(); - if(auto volopt = ConfigValueFloat(dev->DeviceName.c_str(), nullptr, "volume-adjust")) + if(auto volopt = dev->configValue(nullptr, "volume-adjust")) { const float valf{*volopt}; if(!std::isfinite(valf)) @@ -3304,10 +3198,9 @@ START_API_FUNC TRACE("volume-adjust gain: %f\n", context->mGainBoost); } } - 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. @@ -3326,7 +3219,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; @@ -3342,7 +3235,9 @@ START_API_FUNC if(ALeffectslot *slot{context->mDefaultSlot.get()}) { - if(slot->initEffect(&DefaultEffect, context.get()) == AL_NO_ERROR) + ALenum sloterr{slot->initEffect(ALCcontext::sDefaultEffect.type, + ALCcontext::sDefaultEffect.Props, context.get())}; + if(sloterr == AL_NO_ERROR) slot->updateProps(context.get()); else ERR("Failed to initialize the default effect\n"); @@ -3364,13 +3259,14 @@ START_API_FUNC alcSetError(nullptr, ALC_INVALID_CONTEXT); return; } + /* Hold a reference to this context so it remains valid until the ListLock * is released. */ 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)) @@ -3385,8 +3281,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::getThreadContext()}; + if(!Context) Context = ALCcontext::sGlobalContext.load(); return Context; } END_API_FUNC @@ -3394,7 +3290,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::getThreadContext(); } END_API_FUNC ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) @@ -3415,14 +3311,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::getThreadContext()}; + if(ctx) ALCcontext::setThreadContext(nullptr); /* Reset (decrement) the previous thread-local reference. */ return ALC_TRUE; @@ -3445,8 +3341,8 @@ START_API_FUNC } } /* context's reference count is already incremented */ - ContextRef old{LocalContext}; - ThreadContext.set(ctx.release()); + ContextRef old{ALCcontext::getThreadContext()}; + ALCcontext::setThreadContext(ctx.release()); return ALC_TRUE; } @@ -3462,7 +3358,7 @@ START_API_FUNC alcSetError(nullptr, ALC_INVALID_CONTEXT); return nullptr; } - return ctx->mDevice.get(); + return ctx->mALDevice.get(); } END_API_FUNC @@ -3490,6 +3386,11 @@ START_API_FUNC || al::strcasecmp(deviceName, "DirectSound") == 0 || al::strcasecmp(deviceName, "MMSYSTEM") == 0 #endif + /* Some old Linux apps hardcode configuration strings that were + * supported by the OpenAL SI. We can't really do anything useful + * with them, so just ignore. + */ + || (deviceName[0] == '\'' && deviceName[1] == '(') || al::strcasecmp(deviceName, "openal-soft") == 0) deviceName = nullptr; } @@ -3506,6 +3407,10 @@ START_API_FUNC device->SourcesMax = 256; device->AuxiliaryEffectSlotMax = 64; device->NumAuxSends = DEFAULT_SENDS; +#ifdef ALSOFT_EAX + if(eax_g_is_enabled) + device->NumAuxSends = EAX_MAX_FXSLOTS; +#endif // ALSOFT_EAX try { auto backend = PlaybackFactory->createBackend(device.get(), BackendType::Playback); @@ -3520,70 +3425,7 @@ START_API_FUNC return nullptr; } - deviceName = device->DeviceName.c_str(); - if(auto chanopt = ConfigValueStr(deviceName, nullptr, "channels")) - { - static const struct ChannelMap { - const char name[16]; - DevFmtChannels chans; - uint order; - } chanlist[] = { - { "mono", DevFmtMono, 0 }, - { "stereo", DevFmtStereo, 0 }, - { "quad", DevFmtQuad, 0 }, - { "surround51", DevFmtX51, 0 }, - { "surround61", DevFmtX61, 0 }, - { "surround71", DevFmtX71, 0 }, - { "surround51rear", DevFmtX51Rear, 0 }, - { "ambi1", DevFmtAmbi3D, 1 }, - { "ambi2", DevFmtAmbi3D, 2 }, - { "ambi3", DevFmtAmbi3D, 3 }, - }; - - const ALCchar *fmt{chanopt->c_str()}; - auto iter = std::find_if(std::begin(chanlist), std::end(chanlist), - [fmt](const ChannelMap &entry) -> bool - { return al::strcasecmp(entry.name, fmt) == 0; } - ); - if(iter == std::end(chanlist)) - ERR("Unsupported channels: %s\n", fmt); - else - { - device->FmtChans = iter->chans; - device->mAmbiOrder = iter->order; - device->Flags.set(ChannelsRequest); - } - } - if(auto typeopt = ConfigValueStr(deviceName, nullptr, "sample-type")) - { - static const struct TypeMap { - const char name[16]; - DevFmtType type; - } typelist[] = { - { "int8", DevFmtByte }, - { "uint8", DevFmtUByte }, - { "int16", DevFmtShort }, - { "uint16", DevFmtUShort }, - { "int32", DevFmtInt }, - { "uint32", DevFmtUInt }, - { "float32", DevFmtFloat }, - }; - - const ALCchar *fmt{typeopt->c_str()}; - auto iter = std::find_if(std::begin(typelist), std::end(typelist), - [fmt](const TypeMap &entry) -> bool - { return al::strcasecmp(entry.name, fmt) == 0; } - ); - if(iter == std::end(typelist)) - ERR("Unsupported sample-type: %s\n", fmt); - else - { - device->FmtType = iter->type; - device->Flags.set(SampleTypeRequest); - } - } - - if(uint freq{ConfigValueUInt(deviceName, nullptr, "frequency").value_or(0u)}) + if(uint freq{device->configValue(nullptr, "frequency").value_or(0u)}) { if(freq < MIN_OUTPUT_RATE || freq > MAX_OUTPUT_RATE) { @@ -3598,59 +3440,19 @@ START_API_FUNC device->Flags.set(FrequencyRequest); } - if(auto persizeopt = ConfigValueUInt(deviceName, nullptr, "period_size")) - device->UpdateSize = clampu(*persizeopt, 64, 8192); - - if(auto peropt = ConfigValueUInt(deviceName, nullptr, "periods")) - device->BufferSize = device->UpdateSize * clampu(*peropt, 2, 16); - else - device->BufferSize = maxu(device->BufferSize, device->UpdateSize*2); - - if(auto srcsmax = ConfigValueUInt(deviceName, nullptr, "sources").value_or(0)) + if(auto srcsmax = device->configValue(nullptr, "sources").value_or(0)) device->SourcesMax = srcsmax; - if(auto slotsmax = ConfigValueUInt(deviceName, nullptr, "slots").value_or(0)) + if(auto slotsmax = device->configValue(nullptr, "slots").value_or(0)) device->AuxiliaryEffectSlotMax = minu(slotsmax, INT_MAX); - if(auto sendsopt = ConfigValueInt(deviceName, nullptr, "sends")) + if(auto sendsopt = device->configValue(nullptr, "sends")) device->NumAuxSends = minu(DEFAULT_SENDS, static_cast(clampi(*sendsopt, 0, MAX_SENDS))); device->NumStereoSources = 1; device->NumMonoSources = device->SourcesMax - device->NumStereoSources; - if(auto ambiopt = ConfigValueStr(deviceName, nullptr, "ambi-format")) - { - const ALCchar *fmt{ambiopt->c_str()}; - if(al::strcasecmp(fmt, "fuma") == 0) - { - if(device->mAmbiOrder > 3) - ERR("FuMa is incompatible with %d%s order ambisonics (up to third-order only)\n", - device->mAmbiOrder, - (((device->mAmbiOrder%100)/10) == 1) ? "th" : - ((device->mAmbiOrder%10) == 1) ? "st" : - ((device->mAmbiOrder%10) == 2) ? "nd" : - ((device->mAmbiOrder%10) == 3) ? "rd" : "th"); - else - { - device->mAmbiLayout = DevAmbiLayout::FuMa; - device->mAmbiScale = DevAmbiScaling::FuMa; - } - } - else if(al::strcasecmp(fmt, "ambix") == 0 || al::strcasecmp(fmt, "acn+sn3d") == 0) - { - device->mAmbiLayout = DevAmbiLayout::ACN; - device->mAmbiScale = DevAmbiScaling::SN3D; - } - else if(al::strcasecmp(fmt, "acn+n3d") == 0) - { - device->mAmbiLayout = DevAmbiLayout::ACN; - device->mAmbiScale = DevAmbiScaling::N3D; - } - else - ERR("Unsupported ambi-format: %s\n", fmt); - } - { std::lock_guard _{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); @@ -3686,7 +3488,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) @@ -3837,6 +3639,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); } @@ -3990,13 +3793,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 @@ -4047,9 +3849,14 @@ 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); + return; } + TRACE("Post-resume: %s, %s, %uhz, %u / %u buffer\n", + DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), + device->Frequency, device->UpdateSize, device->BufferSize); } END_API_FUNC @@ -4068,8 +3875,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; @@ -4103,34 +3910,92 @@ 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}; + + /* Force the backend to stop mixing first since we're reopening. */ + if(dev->Flags.test(DeviceRunning)) + { + auto backend = dev->Backend.get(); + backend->stop(); + dev->Flags.reset(DeviceRunning); + } + + BackendPtr newbackend; + try { + newbackend = PlaybackFactory->createBackend(dev.get(), BackendType::Playback); + newbackend->open(deviceName); + } + catch(al::backend_exception &e) { + listlock.unlock(); + newbackend = nullptr; + + WARN("Failed to reopen playback device: %s\n", e.what()); + alcSetError(dev.get(), (e.errorCode() == al::backend_error::OutOfMemory) + ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); + + /* If the device is connected, not paused, and has contexts, ensure it + * continues playing. + */ + if(dev->Connected.load(std::memory_order_relaxed) && !dev->Flags.test(DevicePaused) + && !dev->mContexts.load(std::memory_order_relaxed)->empty()) + { + try { + auto backend = dev->Backend.get(); + backend->start(); + dev->Flags.set(DeviceRunning); + } + catch(al::backend_exception &be) { + ERR("%s\n", be.what()); + dev->handleDisconnect("%s", be.what()); + } + } + return ALC_FALSE; + } + listlock.unlock(); + dev->Backend = std::move(newbackend); + TRACE("Reopened device %p, \"%s\"\n", voidp{dev.get()}, dev->DeviceName.c_str()); + + /* Always return true even if resetting fails. It shouldn't fail, but this + * is primarily to avoid confusion by the app seeing the function return + * false while the device is on the new output anyway. We could try to + * restore the old backend if this fails, but the configuration would be + * changed with the new backend and would need to be reset again with the + * old one, and the provided attributes may not be appropriate or desirable + * for the old device. + * + * In this way, we essentially act as if the function succeeded, but + * immediately disconnects following it. + */ + ResetDeviceParams(dev.get(), attribs); + return ALC_TRUE; } END_API_FUNC diff --git a/Engine/lib/openal-soft/Alc/alconfig.cpp b/Engine/lib/openal-soft/alc/alconfig.cpp similarity index 88% rename from Engine/lib/openal-soft/Alc/alconfig.cpp rename to Engine/lib/openal-soft/alc/alconfig.cpp index 634679aab..7c1eec6db 100644 --- a/Engine/lib/openal-soft/Alc/alconfig.cpp +++ b/Engine/lib/openal-soft/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" @@ -277,6 +277,52 @@ void LoadConfigFromFile(std::istream &f) ConfOpts.shrink_to_fit(); } +const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName) +{ + if(!keyName) + return nullptr; + + std::string key; + if(blockName && al::strcasecmp(blockName, "general") != 0) + { + key = blockName; + if(devName) + { + key += '/'; + key += devName; + } + key += '/'; + key += keyName; + } + else + { + if(devName) + { + key = devName; + key += '/'; + } + key += keyName; + } + + auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(), + [&key](const ConfigEntry &entry) -> bool + { return entry.key == key; }); + if(iter != ConfOpts.cend()) + { + TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str()); + if(!iter->value.empty()) + return iter->value.c_str(); + return nullptr; + } + + if(!devName) + { + TRACE("Key %s not found\n", key.c_str()); + return nullptr; + } + return GetConfigValue(nullptr, blockName, keyName); +} + } // namespace @@ -437,106 +483,46 @@ void ReadALConfig() } #endif -const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def) -{ - if(!keyName) - return def; - - std::string key; - if(blockName && al::strcasecmp(blockName, "general") != 0) - { - key = blockName; - if(devName) - { - key += '/'; - key += devName; - } - key += '/'; - key += keyName; - } - else - { - if(devName) - { - key = devName; - key += '/'; - } - key += keyName; - } - - auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(), - [&key](const ConfigEntry &entry) -> bool - { return entry.key == key; } - ); - if(iter != ConfOpts.cend()) - { - TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str()); - if(!iter->value.empty()) - return iter->value.c_str(); - return def; - } - - if(!devName) - { - TRACE("Key %s not found\n", key.c_str()); - return def; - } - return GetConfigValue(nullptr, blockName, keyName, def); -} - -int ConfigValueExists(const char *devName, const char *blockName, const char *keyName) -{ - const char *val = GetConfigValue(devName, blockName, keyName, ""); - return val[0] != 0; -} - al::optional ConfigValueStr(const char *devName, const char *blockName, const char *keyName) { - const char *val = GetConfigValue(devName, blockName, keyName, ""); - if(!val[0]) return al::nullopt; - - return al::make_optional(val); + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return al::make_optional(val); + return al::nullopt; } al::optional ConfigValueInt(const char *devName, const char *blockName, const char *keyName) { - const char *val = GetConfigValue(devName, blockName, keyName, ""); - if(!val[0]) return al::nullopt; - - return al::make_optional(static_cast(std::strtol(val, nullptr, 0))); + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return al::make_optional(static_cast(std::strtol(val, nullptr, 0))); + return al::nullopt; } al::optional ConfigValueUInt(const char *devName, const char *blockName, const char *keyName) { - const char *val = GetConfigValue(devName, blockName, keyName, ""); - if(!val[0]) return al::nullopt; - - return al::make_optional(static_cast(std::strtoul(val, nullptr, 0))); + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return al::make_optional(static_cast(std::strtoul(val, nullptr, 0))); + return al::nullopt; } al::optional ConfigValueFloat(const char *devName, const char *blockName, const char *keyName) { - const char *val = GetConfigValue(devName, blockName, keyName, ""); - if(!val[0]) return al::nullopt; - - return al::make_optional(std::strtof(val, nullptr)); + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return al::make_optional(std::strtof(val, nullptr)); + return al::nullopt; } al::optional ConfigValueBool(const char *devName, const char *blockName, const char *keyName) { - const char *val = GetConfigValue(devName, blockName, keyName, ""); - if(!val[0]) return al::nullopt; - - return al::make_optional( - al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 || - al::strcasecmp(val, "on") == 0 || atoi(val) != 0); + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return al::make_optional(al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 + || al::strcasecmp(val, "true")==0 || atoi(val) != 0); + return al::nullopt; } -int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def) +bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def) { - const char *val = GetConfigValue(devName, blockName, keyName, ""); - - if(!val[0]) return def != 0; - return (al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 || - al::strcasecmp(val, "on") == 0 || atoi(val) != 0); + if(const char *val{GetConfigValue(devName, blockName, keyName)}) + return (al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 + || al::strcasecmp(val, "true") == 0 || atoi(val) != 0); + return def; } diff --git a/Engine/lib/openal-soft/Alc/alconfig.h b/Engine/lib/openal-soft/alc/alconfig.h similarity index 68% rename from Engine/lib/openal-soft/Alc/alconfig.h rename to Engine/lib/openal-soft/alc/alconfig.h index ffc7adad7..df2830ccc 100644 --- a/Engine/lib/openal-soft/Alc/alconfig.h +++ b/Engine/lib/openal-soft/alc/alconfig.h @@ -7,9 +7,7 @@ void ReadALConfig(); -int ConfigValueExists(const char *devName, const char *blockName, const char *keyName); -const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def); -int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def); +bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def); al::optional ConfigValueStr(const char *devName, const char *blockName, const char *keyName); al::optional ConfigValueInt(const char *devName, const char *blockName, const char *keyName); diff --git a/Engine/lib/openal-soft/Alc/alu.cpp b/Engine/lib/openal-soft/alc/alu.cpp similarity index 80% rename from Engine/lib/openal-soft/Alc/alu.cpp rename to Engine/lib/openal-soft/alc/alu.cpp index 0cbfefec3..ef885152e 100644 --- a/Engine/lib/openal-soft/Alc/alu.cpp +++ b/Engine/lib/openal-soft/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 "alnumbers.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/effects/base.h" +#include "core/effectslot.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 "effects/base.h" -#include "effectslot.h" -#include "front_stablizer.h" -#include "hrtf.h" -#include "inprogext.h" -#include "math_defs.h" +#include "core/voice.h" +#include "core/voice_change.h" +#include "intrusive_ptr.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!"); @@ -125,26 +125,17 @@ float InitConeScale() } return ret; } - -float InitZScale() -{ - float ret{1.0f}; - if(auto optval = al::getenv("__ALSOFT_REVERSE_Z")) - { - if(al::strcasecmp(optval->c_str(), "true") == 0 - || strtol(optval->c_str(), nullptr, 0) == 1) - ret *= -1.0f; - } - return ret; -} - -} // namespace - /* Cone scalar */ const float ConeScale{InitConeScale()}; -/* Localized Z scalar for mono sources */ -const float ZScale{InitZScale()}; +/* Localized scalars for mono sources (initialized in aluInit, after + * configuration is loaded). + */ +float XScale{1.0f}; +float YScale{1.0f}; +float ZScale{1.0f}; + +} // namespace namespace { @@ -154,9 +145,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 +173,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 @@ -253,9 +244,12 @@ inline ResamplerFunc SelectResampler(Resampler resampler, uint increment) } // namespace -void aluInit(void) +void aluInit(CompatFlagBitset flags) { MixDirectHrtf = SelectHrtfMixer(); + XScale = flags.test(CompatFlags::ReverseX) ? -1.0f : 1.0f; + YScale = flags.test(CompatFlags::ReverseY) ? -1.0f : 1.0f; + ZScale = flags.test(CompatFlags::ReverseZ) ? -1.0f : 1.0f; } @@ -280,7 +274,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 +284,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 +300,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(), - SamplesToDo); + mUhjEncoder->encode(RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), + 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); @@ -347,8 +341,13 @@ inline uint dither_rng(uint *seed) noexcept inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept { - if(scaletype == AmbiScaling::FuMa) return AmbiScale::FromFuMa(); - if(scaletype == AmbiScaling::SN3D) return AmbiScale::FromSN3D(); + switch(scaletype) + { + case AmbiScaling::FuMa: return AmbiScale::FromFuMa(); + case AmbiScaling::SN3D: return AmbiScale::FromSN3D(); + case AmbiScaling::UHJ: return AmbiScale::FromUHJ(); + case AmbiScaling::N3D: break; + } return AmbiScale::FromN3D(); } @@ -365,11 +364,37 @@ 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; + const alu::Vector pos{props->Position[0], props->Position[1], props->Position[2], 1.0f}; + ctx->mParams.Position = pos; + + /* AT then UP */ + alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f}; + N.normalize(); + alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f}; + V.normalize(); + /* Build and normalize right-vector */ + alu::Vector U{N.cross_product(V)}; + U.normalize(); + + const alu::Matrix rot{ + U[0], V[0], -N[0], 0.0, + U[1], V[1], -N[1], 0.0, + U[2], V[2], -N[2], 0.0, + 0.0, 0.0, 0.0, 1.0}; + const alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0}; + + ctx->mParams.Matrix = rot; + ctx->mParams.Velocity = rot * vel; + + ctx->mParams.Gain = props->Gain * ctx->mGainBoost; + ctx->mParams.MetersPerUnit = props->MetersPerUnit; + ctx->mParams.AirAbsorptionGainHF = props->AirAbsorptionGainHF; + ctx->mParams.DopplerFactor = props->DopplerFactor; ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity; @@ -380,45 +405,7 @@ bool CalcContextParams(ALCcontext *ctx) return true; } -bool CalcListenerParams(ALCcontext *ctx) -{ - ListenerProps *props{ctx->mParams.ListenerUpdate.exchange(nullptr, - std::memory_order_acq_rel)}; - if(!props) return false; - - /* AT then UP */ - alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f}; - N.normalize(); - alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f}; - V.normalize(); - /* Build and normalize right-vector */ - alu::Vector U{N.cross_product(V)}; - U.normalize(); - - const alu::MatrixR rot{ - U[0], V[0], -N[0], 0.0, - U[1], V[1], -N[1], 0.0, - U[2], V[2], -N[2], 0.0, - 0.0, 0.0, 0.0, 1.0}; - const alu::VectorR pos{props->Position[0],props->Position[1],props->Position[2],1.0}; - const alu::VectorR vel{props->Velocity[0],props->Velocity[1],props->Velocity[2],0.0}; - const alu::Vector P{alu::cast_to(rot * pos)}; - - ctx->mParams.Matrix = alu::Matrix{ - U[0], V[0], -N[0], 0.0f, - U[1], V[1], -N[1], 0.0f, - U[2], V[2], -N[2], 0.0f, - -P[0], -P[1], -P[2], 1.0f}; - ctx->mParams.Velocity = alu::cast_to(rot * vel); - - ctx->mParams.Gain = props->Gain * ctx->mGainBoost; - ctx->mParams.MetersPerUnit = props->MetersPerUnit; - - AtomicReplaceHead(ctx->mFreeListenerProps, props); - 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; @@ -466,7 +453,8 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ALCcontex auto evt_vec = ring->getWriteVector(); if LIKELY(evt_vec.first.len > 0) { - AsyncEvent *evt{::new(evt_vec.first.buf) AsyncEvent{EventType_ReleaseEffectState}}; + AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), + AsyncEvent::ReleaseEffectState)}; evt->u.mEffectState = oldstate; ring->writeAdvance(1); } @@ -488,7 +476,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); @@ -502,16 +490,16 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ALCcontex inline float ScaleAzimuthFront(float azimuth, float scale) { const float abs_azi{std::fabs(azimuth)}; - if(!(abs_azi >= al::MathDefs::Pi()*0.5f)) - return std::copysign(minf(abs_azi*scale, al::MathDefs::Pi()*0.5f), azimuth); + if(!(abs_azi >= al::numbers::pi_v*0.5f)) + return std::copysign(minf(abs_azi*scale, al::numbers::pi_v*0.5f), azimuth); return azimuth; } /* Wraps the given value in radians to stay between [-pi,+pi] */ inline float WrapRadians(float r) { - constexpr float Pi{al::MathDefs::Pi()}; - constexpr float Pi2{al::MathDefs::Tau()}; + static constexpr float Pi{al::numbers::pi_v}; + static constexpr float Pi2{Pi*2.0f}; if(r > Pi) return std::fmod(Pi+r, Pi2) - Pi; if(r < -Pi) return Pi - std::fmod(Pi-r, Pi2); return r; @@ -614,17 +602,18 @@ void AmbiRotator(std::array,MaxAmbiChannels> & auto V = [P](const int l, const int m, const int n, const size_t last_band, const std::array,MaxAmbiChannels> &R) { + using namespace al::numbers; if(m > 0) { const bool d{m == 1}; const float p0{P( 1, l, m-1, n, last_band, R)}; const float p1{P(-1, l, -m+1, n, last_band, R)}; - return d ? p0*std::sqrt(2.0f) : (p0 - p1); + return d ? p0*sqrt2_v : (p0 - p1); } const bool d{m == -1}; const float p0{P( 1, l, m+1, n, last_band, R)}; const float p1{P(-1, l, -m-1, n, last_band, R)}; - return d ? p1*std::sqrt(2.0f) : (p0 + p1); + return d ? p1*sqrt2_v : (p0 + p1); }; auto W = [P](const int l, const int m, const int n, const size_t last_band, const std::array,MaxAmbiChannels> &R) @@ -673,14 +662,17 @@ void AmbiRotator(std::array,MaxAmbiChannels> & /* End ambisonic rotation helpers. */ +constexpr float Deg2Rad(float x) noexcept +{ return static_cast(al::numbers::pi / 180.0 * x); } + struct GainTriplet { float Base, HF, LF; }; void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, const float zpos, const float Distance, const float Spread, const GainTriplet &DryGain, const al::span WetGain, EffectSlot *(&SendSlots)[MAX_SENDS], - const VoiceProps *props, const ContextParams &Context, const ALCdevice *Device) + const VoiceProps *props, const ContextParams &Context, const DeviceBase *Device) { - static const ChanMap MonoMap[1]{ + static constexpr ChanMap MonoMap[1]{ { FrontCenter, 0.0f, 0.0f } }, RearMap[2]{ { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) }, @@ -737,7 +729,6 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con DirectMode DirectChannels{props->DirectChannels}; const ChanMap *chans{nullptr}; - float downmix_gain{1.0f}; switch(voice->mFmtChannels) { case FmtMono: @@ -755,51 +746,35 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con StereoMap[0].angle = WrapRadians(-props->StereoPan[0]); StereoMap[1].angle = WrapRadians(-props->StereoPan[1]); } - chans = StereoMap; - downmix_gain = 1.0f / 2.0f; break; - case FmtRear: - chans = RearMap; - downmix_gain = 1.0f / 2.0f; - break; - - case FmtQuad: - chans = QuadMap; - downmix_gain = 1.0f / 4.0f; - break; - - case FmtX51: - chans = X51Map; - /* NOTE: Excludes LFE. */ - downmix_gain = 1.0f / 5.0f; - break; - - case FmtX61: - chans = X61Map; - /* NOTE: Excludes LFE. */ - downmix_gain = 1.0f / 6.0f; - break; - - case FmtX71: - chans = X71Map; - /* NOTE: Excludes LFE. */ - downmix_gain = 1.0f / 7.0f; - break; + case FmtRear: chans = RearMap; break; + case FmtQuad: chans = QuadMap; break; + case FmtX51: chans = X51Map; break; + case FmtX61: chans = X61Map; break; + case FmtX71: chans = X71Map; break; case FmtBFormat2D: case FmtBFormat3D: + case FmtUHJ2: + case FmtUHJ3: + case FmtUHJ4: + case FmtSuperStereo: DirectChannels = DirectMode::Off; break; } - voice->mFlags &= ~(VoiceHasHrtf | VoiceHasNfc); - if(voice->mFmtChannels == FmtBFormat2D || voice->mFmtChannels == FmtBFormat3D) - { - /* Special handling for B-Format sources. */ + voice->mFlags.reset(VoiceHasHrtf).reset(VoiceHasNfc); + if(auto *decoder{voice->mDecoder.get()}) + decoder->mWidthControl = minf(props->EnhWidth, 0.7f); - if(Device->AvgSpeakerDist > 0.0f) + if(IsAmbisonic(voice->mFmtChannels)) + { + /* Special handling for B-Format and UHJ sources. */ + + if(Device->AvgSpeakerDist > 0.0f && voice->mFmtChannels != FmtUHJ2 + && voice->mFmtChannels != FmtSuperStereo) { if(!(Distance > std::numeric_limits::epsilon())) { @@ -821,7 +796,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con voice->mChans[0].mDryParams.NFCtrlFilter.adjust(w0); } - voice->mFlags |= VoiceHasNfc; + voice->mFlags.set(VoiceHasNfc); } /* Panning a B-Format sound toward some direction is easy. Just pan the @@ -830,7 +805,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * panning. */ const float coverage{!(Distance > std::numeric_limits::epsilon()) ? 1.0f : - (Spread * (1.0f/al::MathDefs::Tau()))}; + (al::numbers::inv_pi_v/2.0f * Spread)}; auto calc_coeffs = [xpos,ypos,zpos](RenderMode mode) { @@ -898,7 +873,7 @@ 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{Is2DAmbisonic(voice->mFmtChannels) ? GetAmbi2DLayout(voice->mAmbiLayout).data() : GetAmbiLayout(voice->mAmbiLayout).data()}; @@ -929,7 +904,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con } } } - else if(DirectChannels != DirectMode::Off && Device->FmtChans != DevFmtAmbi3D) + else if(DirectChannels != DirectMode::Off && !Device->RealOut.RemixMap.empty()) { /* Direct source channels always play local. Skip the virtual channels * and write inputs to the matching real outputs. @@ -948,6 +923,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con auto remap = std::find_if(Device->RealOut.RemixMap.cbegin(), Device->RealOut.RemixMap.cend(), match_channel); if(remap != Device->RealOut.RemixMap.cend()) + { for(const auto &target : remap->targets) { idx = GetChannelIdxByName(Device->RealOut, target.channel); @@ -955,6 +931,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * target.mix; } + } } } @@ -991,7 +968,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con GetHrtfCoeffs(Device->mHrtf.get(), ev, az, Distance, Spread, voice->mChans[0].mDryParams.Hrtf.Target.Coeffs, voice->mChans[0].mDryParams.Hrtf.Target.Delay); - voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain.Base * downmix_gain; + voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain.Base; /* Remaining channels use the same results as the first. */ for(size_t c{1};c < num_channels;c++) @@ -1014,7 +991,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base * downmix_gain, + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, voice->mChans[c].mWetParams[i].Gains.Target); } } @@ -1052,7 +1029,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con } } - voice->mFlags |= VoiceHasHrtf; + voice->mFlags.set(VoiceHasHrtf); } else { @@ -1073,7 +1050,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con for(size_t c{0};c < num_channels;c++) voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0); - voice->mFlags |= VoiceHasNfc; + voice->mFlags.set(VoiceHasNfc); } /* Calculate the directional coefficients once, which apply to all @@ -1103,12 +1080,12 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con continue; } - ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base * downmix_gain, + ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, voice->mChans[c].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base * downmix_gain, + ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, voice->mChans[c].mWetParams[i].Gains.Target); } } @@ -1120,11 +1097,11 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con /* If the source distance is 0, simulate a plane-wave by using * infinite distance, which results in a w0 of 0. */ - constexpr float w0{0.0f}; + static constexpr float w0{0.0f}; for(size_t c{0};c < num_channels;c++) voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0); - voice->mFlags |= VoiceHasNfc; + voice->mFlags.set(VoiceHasNfc); } for(size_t c{0};c < num_channels;c++) @@ -1196,9 +1173,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,60 +1219,26 @@ 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. */ voice->mDirect.Buffer = Device->Dry.Buffer; EffectSlot *SendSlots[MAX_SENDS]; - float RoomRolloff[MAX_SENDS]; - GainTriplet DecayDistance[MAX_SENDS]; + uint UseDryAttnForRoom{0}; for(uint i{0};i < NumSends;i++) { SendSlots[i] = props->Send[i].Slot; if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None) - { SendSlots[i] = nullptr; - RoomRolloff[i] = 0.0f; - DecayDistance[i].Base = 0.0f; - DecayDistance[i].LF = 0.0f; - DecayDistance[i].HF = 0.0f; - } - else if(SendSlots[i]->AuxSendAuto) - { - RoomRolloff[i] = SendSlots[i]->RoomRolloff + props->RoomRolloffFactor; - /* Calculate the distances to where this effect's decay reaches - * -60dB. - */ - DecayDistance[i].Base = SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec; - DecayDistance[i].LF = DecayDistance[i].Base * SendSlots[i]->DecayLFRatio; - DecayDistance[i].HF = DecayDistance[i].Base * SendSlots[i]->DecayHFRatio; - if(SendSlots[i]->DecayHFLimit) - { - const float airAbsorption{SendSlots[i]->AirAbsorptionGainHF}; - if(airAbsorption < 1.0f) - { - /* Calculate the distance to where this effect's air - * absorption reaches -60dB, and limit the effect's HF - * decay distance (so it doesn't take any longer to decay - * than the air would allow). - */ - constexpr float log10_decaygain{-3.0f/*std::log10(ReverbDecayGain)*/}; - const float absorb_dist{log10_decaygain / std::log10(airAbsorption)}; - DecayDistance[i].HF = minf(absorb_dist, DecayDistance[i].HF); - } - } - } - else + else if(!SendSlots[i]->AuxSendAuto) { /* If the slot's auxiliary send auto is off, the data sent to the - * effect slot is the same as the dry path, sans filter effects */ - RoomRolloff[i] = props->RolloffFactor; - DecayDistance[i].Base = 0.0f; - DecayDistance[i].LF = 0.0f; - DecayDistance[i].HF = 0.0f; + * effect slot is the same as the dry path, sans filter effects. + */ + UseDryAttnForRoom |= 1u<HeadRelative) { /* Transform source vectors */ - Position = context->mParams.Matrix * Position; + Position = context->mParams.Matrix * (Position - context->mParams.Position); Velocity = context->mParams.Matrix * Velocity; Direction = context->mParams.Matrix * Direction; } @@ -1325,163 +1268,197 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ALCcontex alu::Vector ToSource{Position[0], Position[1], Position[2], 0.0f}; const float Distance{ToSource.normalize()}; - /* Initial source gain */ - GainTriplet DryGain{props->Gain, 1.0f, 1.0f}; - GainTriplet WetGain[MAX_SENDS]; - for(uint i{0};i < NumSends;i++) - WetGain[i] = DryGain; - /* Calculate distance attenuation */ float ClampedDist{Distance}; + float DryGainBase{props->Gain}; + float WetGainBase{props->Gain}; switch(context->mParams.SourceDistanceModel ? props->mDistanceModel : context->mParams.mDistanceModel) { case DistanceModel::InverseClamped: - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); if(props->MaxDistance < props->RefDistance) break; + ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); /*fall-through*/ case DistanceModel::Inverse: - if(!(props->RefDistance > 0.0f)) - ClampedDist = props->RefDistance; - else + if(props->RefDistance > 0.0f) { - float dist{lerp(props->RefDistance, ClampedDist, props->RolloffFactor)}; - if(dist > 0.0f) DryGain.Base *= props->RefDistance / dist; - for(uint i{0};i < NumSends;i++) - { - dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]); - if(dist > 0.0f) WetGain[i].Base *= props->RefDistance / dist; - } + float dist{lerpf(props->RefDistance, ClampedDist, props->RolloffFactor)}; + if(dist > 0.0f) DryGainBase *= props->RefDistance / dist; + + dist = lerpf(props->RefDistance, ClampedDist, props->RoomRolloffFactor); + if(dist > 0.0f) WetGainBase *= props->RefDistance / dist; } break; case DistanceModel::LinearClamped: - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); if(props->MaxDistance < props->RefDistance) break; + ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); /*fall-through*/ case DistanceModel::Linear: - if(!(props->MaxDistance != props->RefDistance)) - ClampedDist = props->RefDistance; - else + if(props->MaxDistance != props->RefDistance) { - float attn{props->RolloffFactor * (ClampedDist-props->RefDistance) / - (props->MaxDistance-props->RefDistance)}; - DryGain.Base *= maxf(1.0f - attn, 0.0f); - for(uint i{0};i < NumSends;i++) - { - attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) / - (props->MaxDistance-props->RefDistance); - WetGain[i].Base *= maxf(1.0f - attn, 0.0f); - } + float attn{(ClampedDist-props->RefDistance) / + (props->MaxDistance-props->RefDistance) * props->RolloffFactor}; + DryGainBase *= maxf(1.0f - attn, 0.0f); + + attn = (ClampedDist-props->RefDistance) / + (props->MaxDistance-props->RefDistance) * props->RoomRolloffFactor; + WetGainBase *= maxf(1.0f - attn, 0.0f); } break; case DistanceModel::ExponentClamped: - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); if(props->MaxDistance < props->RefDistance) break; + ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); /*fall-through*/ case DistanceModel::Exponent: - if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f)) - ClampedDist = props->RefDistance; - else + if(ClampedDist > 0.0f && props->RefDistance > 0.0f) { const float dist_ratio{ClampedDist/props->RefDistance}; - DryGain.Base *= std::pow(dist_ratio, -props->RolloffFactor); - for(uint i{0};i < NumSends;i++) - WetGain[i].Base *= std::pow(dist_ratio, -RoomRolloff[i]); + DryGainBase *= std::pow(dist_ratio, -props->RolloffFactor); + WetGainBase *= std::pow(dist_ratio, -props->RoomRolloffFactor); } break; case DistanceModel::Disable: - ClampedDist = props->RefDistance; break; } /* Calculate directional soundcones */ + float ConeHF{1.0f}, WetConeHF{1.0f}; if(directional && props->InnerAngle < 360.0f) { - const float Angle{Rad2Deg(std::acos(Direction.dot_product(ToSource)) * ConeScale * -2.0f)}; + static constexpr float Rad2Deg{static_cast(180.0 / al::numbers::pi)}; + const float Angle{Rad2Deg*2.0f * std::acos(-Direction.dot_product(ToSource)) * ConeScale}; - float ConeGain, ConeHF; - if(!(Angle > props->InnerAngle)) - { - ConeGain = 1.0f; - ConeHF = 1.0f; - } - else if(Angle < props->OuterAngle) - { - const float scale{(Angle-props->InnerAngle) / (props->OuterAngle-props->InnerAngle)}; - ConeGain = lerp(1.0f, props->OuterGain, scale); - ConeHF = lerp(1.0f, props->OuterGainHF, scale); - } - else + float ConeGain{1.0f}; + if(Angle >= props->OuterAngle) { ConeGain = props->OuterGain; - ConeHF = props->OuterGainHF; + ConeHF = lerpf(1.0f, props->OuterGainHF, props->DryGainHFAuto); + } + else if(Angle >= props->InnerAngle) + { + const float scale{(Angle-props->InnerAngle) / (props->OuterAngle-props->InnerAngle)}; + ConeGain = lerpf(1.0f, props->OuterGain, scale); + ConeHF = lerpf(1.0f, props->OuterGainHF, scale * props->DryGainHFAuto); } - DryGain.Base *= ConeGain; - if(props->DryGainHFAuto) - DryGain.HF *= ConeHF; - if(props->WetGainAuto) - std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends, - [ConeGain](GainTriplet &gain) noexcept -> void { gain.Base *= ConeGain; }); - if(props->WetGainHFAuto) - std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends, - [ConeHF](GainTriplet &gain) noexcept -> void { gain.HF *= ConeHF; }); + DryGainBase *= ConeGain; + WetGainBase *= lerpf(1.0f, ConeGain, props->WetGainAuto); + + WetConeHF = lerpf(1.0f, ConeHF, props->WetGainHFAuto); } /* Apply gain and frequency filters */ - DryGain.Base = minf(clampf(DryGain.Base, props->MinGain, props->MaxGain) * props->Direct.Gain * - context->mParams.Gain, GainMixMax); - DryGain.HF *= props->Direct.GainHF; - DryGain.LF *= props->Direct.GainLF; + DryGainBase = clampf(DryGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; + WetGainBase = clampf(WetGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; + + GainTriplet DryGain{}; + DryGain.Base = minf(DryGainBase * props->Direct.Gain, GainMixMax); + DryGain.HF = ConeHF * props->Direct.GainHF; + DryGain.LF = props->Direct.GainLF; + GainTriplet WetGain[MAX_SENDS]{}; for(uint i{0};i < NumSends;i++) { - WetGain[i].Base = minf(clampf(WetGain[i].Base, props->MinGain, props->MaxGain) * - props->Send[i].Gain * context->mParams.Gain, GainMixMax); - WetGain[i].HF *= props->Send[i].GainHF; - WetGain[i].LF *= props->Send[i].GainLF; + /* If this effect slot's Auxiliary Send Auto is off, then use the dry + * path distance and cone attenuation, otherwise use the wet (room) + * path distance and cone attenuation. The send filter is used instead + * of the direct filter, regardless. + */ + const bool use_room{!(UseDryAttnForRoom&(1u<Send[i].Gain, GainMixMax); + WetGain[i].HF = (use_room ? WetConeHF : ConeHF) * props->Send[i].GainHF; + WetGain[i].LF = props->Send[i].GainLF; } /* Distance-based air absorption and initial send decay. */ - if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f) + if(likely(Distance > props->RefDistance)) { - const float meters_base{(ClampedDist-props->RefDistance) * props->RolloffFactor * - context->mParams.MetersPerUnit}; - if(props->AirAbsorptionFactor > 0.0f) + const float distance_base{(Distance-props->RefDistance) * props->RolloffFactor}; + const float absorption{distance_base * context->mParams.MetersPerUnit * + props->AirAbsorptionFactor}; + if(absorption > std::numeric_limits::epsilon()) { - const float hfattn{std::pow(AirAbsorbGainHF, meters_base*props->AirAbsorptionFactor)}; + const float hfattn{std::pow(context->mParams.AirAbsorptionGainHF, absorption)}; DryGain.HF *= hfattn; - std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends, - [hfattn](GainTriplet &gain) noexcept -> void { gain.HF *= hfattn; }); + for(uint i{0u};i < NumSends;++i) + WetGain[i].HF *= hfattn; } - if(props->WetGainAuto) + /* If the source's Auxiliary Send Filter Gain Auto is off, no extra + * adjustment is applied to the send gains. + */ + for(uint i{props->WetGainAuto ? 0u : NumSends};i < NumSends;++i) { - /* Apply a decay-time transformation to the wet path, based on the - * source distance in meters. The initial decay of the reverb - * effect is calculated and applied to the wet path. - */ - for(uint i{0};i < NumSends;i++) - { - if(!(DecayDistance[i].Base > 0.0f)) - continue; + if(!SendSlots[i]) + continue; - const float gain{std::pow(ReverbDecayGain, meters_base/DecayDistance[i].Base)}; - WetGain[i].Base *= gain; - /* Yes, the wet path's air absorption is applied with - * WetGainAuto on, rather than WetGainHFAuto. - */ - if(gain > 0.0f) + auto calc_attenuation = [](float distance, float refdist, float rolloff) noexcept + { + const float dist{lerpf(refdist, distance, rolloff)}; + if(dist > refdist) return refdist / dist; + return 1.0f; + }; + + /* The reverb effect's room rolloff factor always applies to an + * inverse distance rolloff model. + */ + WetGain[i].Base *= calc_attenuation(Distance, props->RefDistance, + SendSlots[i]->RoomRolloff); + + /* If this effect slot's Auxiliary Send Auto is off, don't apply + * the automatic initial reverb decay (should the reverb's room + * rolloff still apply?). + */ + if(!SendSlots[i]->AuxSendAuto) + continue; + + GainTriplet DecayDistance; + /* Calculate the distances to where this effect's decay reaches + * -60dB. + */ + DecayDistance.Base = SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec; + DecayDistance.LF = DecayDistance.Base * SendSlots[i]->DecayLFRatio; + DecayDistance.HF = DecayDistance.Base * SendSlots[i]->DecayHFRatio; + if(SendSlots[i]->DecayHFLimit) + { + const float airAbsorption{SendSlots[i]->AirAbsorptionGainHF}; + if(airAbsorption < 1.0f) { - float gainhf{std::pow(ReverbDecayGain, meters_base/DecayDistance[i].HF)}; - WetGain[i].HF *= minf(gainhf / gain, 1.0f); - float gainlf{std::pow(ReverbDecayGain, meters_base/DecayDistance[i].LF)}; - WetGain[i].LF *= minf(gainlf / gain, 1.0f); + /* Calculate the distance to where this effect's air + * absorption reaches -60dB, and limit the effect's HF + * decay distance (so it doesn't take any longer to decay + * than the air would allow). + */ + static constexpr float log10_decaygain{-3.0f/*std::log10(ReverbDecayGain)*/}; + const float absorb_dist{log10_decaygain / std::log10(airAbsorption)}; + DecayDistance.HF = minf(absorb_dist, DecayDistance.HF); } } + + const float baseAttn = calc_attenuation(Distance, props->RefDistance, + props->RolloffFactor); + + /* Apply a decay-time transformation to the wet path, based on the + * source distance. The initial decay of the reverb effect is + * calculated and applied to the wet path. + */ + const float fact{distance_base / DecayDistance.Base}; + const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn}; + WetGain[i].Base *= gain; + + if(gain > 0.0f) + { + const float hffact{distance_base / DecayDistance.HF}; + const float gainhf{std::pow(ReverbDecayGain, hffact)*(1.0f-baseAttn) + baseAttn}; + WetGain[i].HF *= minf(gainhf/gain, 1.0f); + const float lffact{distance_base / DecayDistance.LF}; + const float gainlf{std::pow(ReverbDecayGain, lffact)*(1.0f-baseAttn) + baseAttn}; + WetGain[i].LF *= minf(gainlf/gain, 1.0f); + } } } @@ -1533,16 +1510,16 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ALCcontex float spread{0.0f}; if(props->Radius > Distance) - spread = al::MathDefs::Tau() - Distance/props->Radius*al::MathDefs::Pi(); + spread = al::numbers::pi_v*2.0f - Distance/props->Radius*al::numbers::pi_v; else if(Distance > 0.0f) spread = std::asin(props->Radius/Distance) * 2.0f; - CalcPanningAndFilters(voice, ToSource[0], ToSource[1], ToSource[2]*ZScale, + CalcPanningAndFilters(voice, ToSource[0]*XScale, ToSource[1]*YScale, ToSource[2]*ZScale, Distance*context->mParams.MetersPerUnit, spread, DryGain, WetGain, SendSlots, props, context->mParams, Device); } -void CalcSourceParams(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,8 +1532,8 @@ void CalcSourceParams(Voice *voice, ALCcontext *context, bool force) } if((voice->mProps.DirectChannels != DirectMode::Off && voice->mFmtChannels != FmtMono - && voice->mFmtChannels != FmtBFormat2D && voice->mFmtChannels != FmtBFormat3D) - || voice->mProps.mSpatializeMode==SpatializeMode::Off + && !IsAmbisonic(voice->mFmtChannels)) + || voice->mProps.mSpatializeMode == SpatializeMode::Off || (voice->mProps.mSpatializeMode==SpatializeMode::Auto && voice->mFmtChannels != FmtMono)) CalcNonAttnSourceParams(voice, &voice->mProps, context); else @@ -1564,20 +1541,38 @@ 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(); if(evt_vec.first.len < 1) return; - AsyncEvent *evt{::new(evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}}; + AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), + AsyncEvent::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)}; @@ -1664,7 +1659,7 @@ void ProcessVoiceChanges(ALCcontext *ctx) } oldvoice->mPendingChange.store(false, std::memory_order_release); } - if(sendevt && (enabledevt&EventType_SourceStateChange)) + if(sendevt && (enabledevt&AsyncEvent::SourceStateChange)) SendSourceStateEvent(ctx, cur->mSourceID, cur->mState); next = cur->mNext.load(std::memory_order_acquire); @@ -1672,7 +1667,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); @@ -1681,7 +1676,6 @@ void ProcessParamUpdates(ALCcontext *ctx, const EffectSlotArray &slots, if LIKELY(!ctx->mHoldUpdates.load(std::memory_order_acquire)) { bool force{CalcContextParams(ctx)}; - force |= CalcListenerParams(ctx); auto sorted_slots = const_cast(slots.data() + slots.size()); for(EffectSlot *slot : slots) force |= CalcEffectSlotParams(slot, sorted_slots, ctx); @@ -1696,11 +1690,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()}; @@ -1887,7 +1881,8 @@ void Write(const al::span InBuffer, void *OutBuffer, cons ASSUME(FrameStep > 0); ASSUME(SamplesToDo > 0); - DevFmtType_t *outbase = static_cast*>(OutBuffer) + Offset*FrameStep; + DevFmtType_t *outbase{static_cast*>(OutBuffer) + Offset*FrameStep}; + size_t c{0}; for(const FloatBufferLine &inbuf : InBuffer) { DevFmtType_t *out{outbase++}; @@ -1897,57 +1892,93 @@ void Write(const al::span InBuffer, void *OutBuffer, cons out += FrameStep; }; std::for_each(inbuf.begin(), inbuf.begin()+SamplesToDo, conv_sample); + ++c; + } + if(const size_t extra{FrameStep - c}) + { + const auto silence = SampleConv>(0.0f); + for(size_t i{0};i < SamplesToDo;++i) + { + std::fill_n(outbase, extra, silence); + outbase += FrameStep; + } } } } // namespace -void ALCdevice::renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep) +uint DeviceBase::renderSamples(const uint numSamples) +{ + const uint samplesToDo{minu(numSamples, BufferLineSize)}; + + /* Clear main mixing buffers. */ + for(FloatBufferLine &buffer : MixBuffer) + buffer.fill(0.0f); + + /* Increment the mix count at the start (lsb should now be 1). */ + IncrementRef(MixCount); + + /* Process and mix each context's sources and effects. */ + ProcessContexts(this, samplesToDo); + + /* Increment the clock time. Every second's worth of samples is converted + * and added to clock base so that large sample counts don't overflow + * during conversion. This also guarantees a stable conversion. + */ + SamplesDone += samplesToDo; + ClockBase += std::chrono::seconds{SamplesDone / Frequency}; + SamplesDone %= Frequency; + + /* Increment the mix count at the end (lsb should now be 0). */ + IncrementRef(MixCount); + + /* Apply any needed post-process for finalizing the Dry mix to the RealOut + * (Ambisonic decode, UHJ encode, etc). + */ + postProcess(samplesToDo); + + /* Apply compression, limiting sample amplitude if needed or desired. */ + if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data()); + + /* Apply delays and attenuation for mismatched speaker distances. */ + if(ChannelDelays) + ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelays->mChannels.data()); + + /* Apply dithering. The compressor should have left enough headroom for the + * dither noise to not saturate. + */ + if(DitherDepth > 0.0f) + ApplyDither(RealOut.Buffer, &DitherSeed, DitherDepth, samplesToDo); + + return samplesToDo; +} + +void DeviceBase::renderSamples(const al::span outBuffers, const uint numSamples) { FPUCtl mixer_mode{}; - for(uint written{0u};written < numSamples;) + uint total{0}; + while(const uint todo{numSamples - total}) { - const uint samplesToDo{minu(numSamples-written, BufferLineSize)}; + const uint samplesToDo{renderSamples(todo)}; - /* Clear main mixing buffers. */ - for(FloatBufferLine &buffer : MixBuffer) - buffer.fill(0.0f); + auto *srcbuf = RealOut.Buffer.data(); + for(auto *dstbuf : outBuffers) + { + std::copy_n(srcbuf->data(), samplesToDo, dstbuf + total); + ++srcbuf; + } - /* Increment the mix count at the start (lsb should now be 1). */ - IncrementRef(MixCount); + total += samplesToDo; + } +} - /* Process and mix each context's sources and effects. */ - ProcessContexts(this, samplesToDo); - - /* Increment the clock time. Every second's worth of samples is - * converted and added to clock base so that large sample counts don't - * overflow during conversion. This also guarantees a stable - * conversion. - */ - SamplesDone += samplesToDo; - ClockBase += std::chrono::seconds{SamplesDone / Frequency}; - SamplesDone %= Frequency; - - /* Increment the mix count at the end (lsb should now be 0). */ - IncrementRef(MixCount); - - /* Apply any needed post-process for finalizing the Dry mix to the - * RealOut (Ambisonic decode, UHJ encode, etc). - */ - postProcess(samplesToDo); - - /* Apply compression, limiting sample amplitude if needed or desired. */ - if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data()); - - /* Apply delays and attenuation for mismatched speaker distances. */ - if(ChannelDelays) - ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelays->mChannels.data()); - - /* Apply dithering. The compressor should have left enough headroom for - * the dither noise to not saturate. - */ - if(DitherDepth > 0.0f) - ApplyDither(RealOut.Buffer, &DitherSeed, DitherDepth, samplesToDo); +void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep) +{ + FPUCtl mixer_mode{}; + uint total{0}; + while(const uint todo{numSamples - total}) + { + const uint samplesToDo{renderSamples(todo)}; if LIKELY(outBuffer) { @@ -1957,7 +1988,7 @@ void ALCdevice::renderSamples(void *outBuffer, const uint numSamples, const size switch(FmtType) { #define HANDLE_WRITE(T) case T: \ - Write(RealOut.Buffer, outBuffer, written, samplesToDo, frameStep); break; + Write(RealOut.Buffer, outBuffer, total, samplesToDo, frameStep); break; HANDLE_WRITE(DevFmtByte) HANDLE_WRITE(DevFmtUByte) HANDLE_WRITE(DevFmtShort) @@ -1969,16 +2000,16 @@ void ALCdevice::renderSamples(void *outBuffer, const uint numSamples, const size } } - written += samplesToDo; + total += samplesToDo; } } -void ALCdevice::handleDisconnect(const char *msg, ...) +void DeviceBase::handleDisconnect(const char *msg, ...) { if(!Connected.exchange(false, std::memory_order_acq_rel)) return; - AsyncEvent evt{EventType_Disconnected}; + AsyncEvent evt{AsyncEvent::Disconnected}; va_list args; va_start(args, msg); @@ -1989,21 +2020,27 @@ 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)) + if((enabledevt&AsyncEvent::Disconnected)) { RingBuffer *ring{ctx->mAsyncEvents.get()}; auto evt_data = ring->getWriteVector().first; if(evt_data.len > 0) { - ::new(evt_data.buf) AsyncEvent{evt}; + al::construct_at(reinterpret_cast(evt_data.buf), evt); ring->writeAdvance(1); ctx->mEventSem.post(); } } + if(!ctx->mStopVoicesOnDisconnect) + { + ProcessVoiceChanges(ctx); + continue; + } + auto voicelist = ctx->getVoicesSpanAcquired(); auto stop_voice = [](Voice *voice) -> void { diff --git a/Engine/lib/openal-soft/alc/alu.h b/Engine/lib/openal-soft/alc/alu.h new file mode 100644 index 000000000..f3796a898 --- /dev/null +++ b/Engine/lib/openal-soft/alc/alu.h @@ -0,0 +1,38 @@ +#ifndef ALU_H +#define ALU_H + +#include + +#include "aloptional.h" + +struct ALCcontext; +struct ALCdevice; +struct EffectSlot; + +enum class StereoEncoding : unsigned char; + + +constexpr float GainMixMax{1000.0f}; /* +60dB */ + + +enum CompatFlags : uint8_t { + ReverseX, + ReverseY, + ReverseZ, + + Count +}; +using CompatFlagBitset = std::bitset; + +void aluInit(CompatFlagBitset flags); + +/* aluInitRenderer + * + * Set up the appropriate panning method and mixing method given the device + * properties. + */ +void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional stereomode); + +void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context); + +#endif diff --git a/Engine/lib/openal-soft/Alc/backends/alsa.cpp b/Engine/lib/openal-soft/alc/backends/alsa.cpp similarity index 92% rename from Engine/lib/openal-soft/Alc/backends/alsa.cpp rename to Engine/lib/openal-soft/alc/backends/alsa.cpp index bdfdd040e..9c78b6c6a 100644 --- a/Engine/lib/openal-soft/Alc/backends/alsa.cpp +++ b/Engine/lib/openal-soft/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" @@ -68,35 +68,37 @@ constexpr char alsaDevice[] = "ALSA Default"; MAGIC(snd_pcm_hw_params_free); \ MAGIC(snd_pcm_hw_params_any); \ MAGIC(snd_pcm_hw_params_current); \ + MAGIC(snd_pcm_hw_params_get_access); \ + MAGIC(snd_pcm_hw_params_get_buffer_size); \ + MAGIC(snd_pcm_hw_params_get_buffer_time_min); \ + MAGIC(snd_pcm_hw_params_get_buffer_time_max); \ + MAGIC(snd_pcm_hw_params_get_channels); \ + MAGIC(snd_pcm_hw_params_get_period_size); \ + MAGIC(snd_pcm_hw_params_get_period_time_max); \ + MAGIC(snd_pcm_hw_params_get_period_time_min); \ + MAGIC(snd_pcm_hw_params_get_periods); \ MAGIC(snd_pcm_hw_params_set_access); \ - MAGIC(snd_pcm_hw_params_set_format); \ + MAGIC(snd_pcm_hw_params_set_buffer_size_min); \ + MAGIC(snd_pcm_hw_params_set_buffer_size_near); \ + MAGIC(snd_pcm_hw_params_set_buffer_time_near); \ MAGIC(snd_pcm_hw_params_set_channels); \ + MAGIC(snd_pcm_hw_params_set_channels_near); \ + MAGIC(snd_pcm_hw_params_set_format); \ + MAGIC(snd_pcm_hw_params_set_period_time_near); \ + MAGIC(snd_pcm_hw_params_set_period_size_near); \ MAGIC(snd_pcm_hw_params_set_periods_near); \ MAGIC(snd_pcm_hw_params_set_rate_near); \ MAGIC(snd_pcm_hw_params_set_rate); \ MAGIC(snd_pcm_hw_params_set_rate_resample); \ - MAGIC(snd_pcm_hw_params_set_buffer_time_near); \ - MAGIC(snd_pcm_hw_params_set_period_time_near); \ - MAGIC(snd_pcm_hw_params_set_buffer_size_near); \ - MAGIC(snd_pcm_hw_params_set_period_size_near); \ - MAGIC(snd_pcm_hw_params_set_buffer_size_min); \ - MAGIC(snd_pcm_hw_params_get_buffer_time_min); \ - MAGIC(snd_pcm_hw_params_get_buffer_time_max); \ - MAGIC(snd_pcm_hw_params_get_period_time_min); \ - MAGIC(snd_pcm_hw_params_get_period_time_max); \ - MAGIC(snd_pcm_hw_params_get_buffer_size); \ - MAGIC(snd_pcm_hw_params_get_period_size); \ - MAGIC(snd_pcm_hw_params_get_access); \ - MAGIC(snd_pcm_hw_params_get_periods); \ MAGIC(snd_pcm_hw_params_test_format); \ MAGIC(snd_pcm_hw_params_test_channels); \ MAGIC(snd_pcm_hw_params); \ - MAGIC(snd_pcm_sw_params_malloc); \ + MAGIC(snd_pcm_sw_params); \ MAGIC(snd_pcm_sw_params_current); \ + MAGIC(snd_pcm_sw_params_free); \ + MAGIC(snd_pcm_sw_params_malloc); \ MAGIC(snd_pcm_sw_params_set_avail_min); \ MAGIC(snd_pcm_sw_params_set_stop_threshold); \ - MAGIC(snd_pcm_sw_params); \ - MAGIC(snd_pcm_sw_params_free); \ MAGIC(snd_pcm_prepare); \ MAGIC(snd_pcm_start); \ MAGIC(snd_pcm_resume); \ @@ -105,7 +107,6 @@ constexpr char alsaDevice[] = "ALSA Default"; MAGIC(snd_pcm_delay); \ MAGIC(snd_pcm_state); \ MAGIC(snd_pcm_avail_update); \ - MAGIC(snd_pcm_areas_silence); \ MAGIC(snd_pcm_mmap_begin); \ MAGIC(snd_pcm_mmap_commit); \ MAGIC(snd_pcm_readi); \ @@ -150,6 +151,7 @@ ALSA_FUNCS(MAKE_FUNC); #define snd_pcm_hw_params_set_access psnd_pcm_hw_params_set_access #define snd_pcm_hw_params_set_format psnd_pcm_hw_params_set_format #define snd_pcm_hw_params_set_channels psnd_pcm_hw_params_set_channels +#define snd_pcm_hw_params_set_channels_near psnd_pcm_hw_params_set_channels_near #define snd_pcm_hw_params_set_periods_near psnd_pcm_hw_params_set_periods_near #define snd_pcm_hw_params_set_rate_near psnd_pcm_hw_params_set_rate_near #define snd_pcm_hw_params_set_rate psnd_pcm_hw_params_set_rate @@ -167,6 +169,7 @@ ALSA_FUNCS(MAKE_FUNC); #define snd_pcm_hw_params_get_period_size psnd_pcm_hw_params_get_period_size #define snd_pcm_hw_params_get_access psnd_pcm_hw_params_get_access #define snd_pcm_hw_params_get_periods psnd_pcm_hw_params_get_periods +#define snd_pcm_hw_params_get_channels psnd_pcm_hw_params_get_channels #define snd_pcm_hw_params_test_format psnd_pcm_hw_params_test_format #define snd_pcm_hw_params_test_channels psnd_pcm_hw_params_test_channels #define snd_pcm_hw_params psnd_pcm_hw_params @@ -184,7 +187,6 @@ ALSA_FUNCS(MAKE_FUNC); #define snd_pcm_delay psnd_pcm_delay #define snd_pcm_state psnd_pcm_state #define snd_pcm_avail_update psnd_pcm_avail_update -#define snd_pcm_areas_silence psnd_pcm_areas_silence #define snd_pcm_mmap_begin psnd_pcm_mmap_begin #define snd_pcm_mmap_commit psnd_pcm_mmap_commit #define snd_pcm_readi psnd_pcm_readi @@ -260,29 +262,36 @@ al::vector probe_devices(snd_pcm_stream_t stream) snd_pcm_info_t *pcminfo; snd_pcm_info_malloc(&pcminfo); - devlist.emplace_back(DevMap{alsaDevice, - GetConfigValue(nullptr, "alsa", (stream==SND_PCM_STREAM_PLAYBACK) ? "device" : "capture", - "default")}); + auto defname = ConfigValueStr(nullptr, "alsa", + (stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture"); + devlist.emplace_back(DevMap{alsaDevice, defname ? defname->c_str() : "default"}); - const char *customdevs{GetConfigValue(nullptr, "alsa", - (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures", "")}; - while(const char *curdev{customdevs}) + if(auto customdevs = ConfigValueStr(nullptr, "alsa", + (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures")) { - if(!curdev[0]) break; - customdevs = strchr(curdev, ';'); - const char *sep{strchr(curdev, '=')}; - if(!sep) + size_t nextpos{customdevs->find_first_not_of(';')}; + size_t curpos; + while((curpos=nextpos) < customdevs->length()) { - std::string spec{customdevs ? std::string(curdev, customdevs++) : std::string(curdev)}; - ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str()); - continue; - } + nextpos = customdevs->find_first_of(';', curpos+1); - const char *oldsep{sep++}; - devlist.emplace_back(DevMap{std::string(curdev, oldsep), - customdevs ? std::string(sep, customdevs++) : std::string(sep)}); - const auto &entry = devlist.back(); - TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); + size_t seppos{customdevs->find_first_of('=', curpos)}; + if(seppos == curpos || seppos >= nextpos) + { + std::string spec{customdevs->substr(curpos, nextpos-curpos)}; + ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str()); + } + else + { + devlist.emplace_back(DevMap{customdevs->substr(curpos, seppos-curpos), + customdevs->substr(seppos+1, nextpos-seppos-1)}); + const auto &entry = devlist.back(); + TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); + } + + if(nextpos < customdevs->length()) + nextpos = customdevs->find_first_not_of(';', nextpos+1); + } } const std::string main_prefix{ @@ -407,7 +416,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(); @@ -424,6 +433,7 @@ struct AlsaPlayback final : public BackendBase { std::mutex mMutex; + uint mFrameStep{}; al::vector mBuffer; std::atomic mKillNow{true}; @@ -445,7 +455,6 @@ int AlsaPlayback::mixerProc() SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - const size_t samplebits{mDevice->bytesFromFmt() * 8}; const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; while(!mKillNow.load(std::memory_order_acquire)) @@ -507,7 +516,7 @@ int AlsaPlayback::mixerProc() } char *WritePtr{static_cast(areas->addr) + (offset * areas->step / 8)}; - mDevice->renderSamples(WritePtr, static_cast(frames), areas->step/samplebits); + mDevice->renderSamples(WritePtr, static_cast(frames), mFrameStep); snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)}; if(commitres < 0 || static_cast(commitres) != frames) @@ -529,7 +538,6 @@ int AlsaPlayback::mixerNoMMapProc() SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - const size_t frame_step{mDevice->channelsFromFmt()}; const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; while(!mKillNow.load(std::memory_order_acquire)) @@ -575,7 +583,7 @@ int AlsaPlayback::mixerNoMMapProc() al::byte *WritePtr{mBuffer.data()}; avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast(mBuffer.size())); std::lock_guard _{mMutex}; - mDevice->renderSamples(WritePtr, static_cast(avail), frame_step); + mDevice->renderSamples(WritePtr, static_cast(avail), mFrameStep); while(avail > 0) { snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr, @@ -615,16 +623,15 @@ int AlsaPlayback::mixerNoMMapProc() void AlsaPlayback::open(const char *name) { - const char *driver{}; + al::optional driveropt; + const char *driver{"default"}; if(name) { if(PlaybackDevices.empty()) PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); + [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; @@ -633,14 +640,19 @@ void AlsaPlayback::open(const char *name) else { name = alsaDevice; - driver = GetConfigValue(nullptr, "alsa", "device", "default"); + if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "device")}) + driver = driveropt->c_str(); } - TRACE("Opening device \"%s\"\n", driver); - int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)}; + + snd_pcm_t *pcmHandle{}; + int err{snd_pcm_open(&pcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)}; if(err < 0) 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(); @@ -723,37 +735,25 @@ bool AlsaPlayback::reset() } } CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp.get(), format)); - /* test and set channels (implicitly sets frame bits) */ - if(snd_pcm_hw_params_test_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()) < 0) + /* set channels (implicitly sets frame bits) */ + if(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()) < 0) { - static const DevFmtChannels channellist[] = { - DevFmtStereo, - DevFmtQuad, - DevFmtX51, - DevFmtX71, - DevFmtMono, - }; - - for(const auto &chan : channellist) - { - if(snd_pcm_hw_params_test_channels(mPcmHandle, hp.get(), ChannelsFromDevFmt(chan, 0)) >= 0) - { - mDevice->FmtChans = chan; - mDevice->mAmbiOrder = 0; - break; - } - } + uint numchans{2u}; + CHECK(snd_pcm_hw_params_set_channels_near(mPcmHandle, hp.get(), &numchans)); + if(numchans < 1) + throw al::backend_exception{al::backend_error::DeviceError, "Got 0 device channels"}; + if(numchans == 1) mDevice->FmtChans = DevFmtMono; + else mDevice->FmtChans = DevFmtStereo; } - CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt())); /* set rate (implicitly constrains period/buffer parameters) */ if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", 0) || !mDevice->Flags.test(FrequencyRequest)) { if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0) - ERR("Failed to disable ALSA resampler\n"); + WARN("Failed to disable ALSA resampler\n"); } else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 1) < 0) - ERR("Failed to enable ALSA resampler\n"); + WARN("Failed to enable ALSA resampler\n"); CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr)); /* set period time (implicitly constrains period/buffer parameters) */ if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)) < 0) @@ -772,6 +772,7 @@ bool AlsaPlayback::reset() CHECK(snd_pcm_hw_params_get_access(hp.get(), &access)); CHECK(snd_pcm_hw_params_get_period_size(hp.get(), &periodSizeInFrames, nullptr)); CHECK(snd_pcm_hw_params_get_buffer_size(hp.get(), &bufferSizeInFrames)); + CHECK(snd_pcm_hw_params_get_channels(hp.get(), &mFrameStep)); hp = nullptr; SwParamsPtr sp{CreateSwParams()}; @@ -809,8 +810,8 @@ void AlsaPlayback::start() int (AlsaPlayback::*thread_func)(){}; if(access == SND_PCM_ACCESS_RW_INTERLEAVED) { - mBuffer.resize( - static_cast(snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize))); + auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize); + mBuffer.resize(static_cast(datalen)); thread_func = &AlsaPlayback::mixerNoMMapProc; } else @@ -863,7 +864,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; @@ -895,16 +896,15 @@ AlsaCapture::~AlsaCapture() void AlsaCapture::open(const char *name) { - const char *driver{}; + al::optional driveropt; + const char *driver{"default"}; if(name) { if(CaptureDevices.empty()) CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name; } - ); + [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; @@ -913,7 +913,8 @@ void AlsaCapture::open(const char *name) else { name = alsaDevice; - driver = GetConfigValue(nullptr, "alsa", "capture", "default"); + if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "capture")}) + driver = driveropt->c_str(); } TRACE("Opening device \"%s\"\n", driver); @@ -1252,7 +1253,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/Engine/lib/openal-soft/Alc/backends/alsa.h b/Engine/lib/openal-soft/alc/backends/alsa.h similarity index 75% rename from Engine/lib/openal-soft/Alc/backends/alsa.h rename to Engine/lib/openal-soft/alc/backends/alsa.h index 12023cb6c..b256dcf5a 100644 --- a/Engine/lib/openal-soft/Alc/backends/alsa.h +++ b/Engine/lib/openal-soft/alc/backends/alsa.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_ALSA_H #define BACKENDS_ALSA_H -#include "backends/base.h" +#include "base.h" struct AlsaBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/Alc/backends/base.cpp b/Engine/lib/openal-soft/alc/backends/base.cpp similarity index 90% rename from Engine/lib/openal-soft/Alc/backends/base.cpp rename to Engine/lib/openal-soft/alc/backends/base.cpp index c4a4abebd..cd1b76bab 100644 --- a/Engine/lib/openal-soft/Alc/backends/base.cpp +++ b/Engine/lib/openal-soft/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() @@ -78,14 +79,6 @@ void BackendBase::setDefaultWFXChannelOrder() mDevice->RealOut.ChannelIndex[SideLeft] = 4; mDevice->RealOut.ChannelIndex[SideRight] = 5; break; - case DevFmtX51Rear: - mDevice->RealOut.ChannelIndex[FrontLeft] = 0; - mDevice->RealOut.ChannelIndex[FrontRight] = 1; - mDevice->RealOut.ChannelIndex[FrontCenter] = 2; - mDevice->RealOut.ChannelIndex[LFE] = 3; - mDevice->RealOut.ChannelIndex[BackLeft] = 4; - mDevice->RealOut.ChannelIndex[BackRight] = 5; - break; case DevFmtX61: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; @@ -116,11 +109,11 @@ void BackendBase::setDefaultChannelOrder() switch(mDevice->FmtChans) { - case DevFmtX51Rear: + case DevFmtX51: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; - mDevice->RealOut.ChannelIndex[BackLeft] = 2; - mDevice->RealOut.ChannelIndex[BackRight] = 3; + mDevice->RealOut.ChannelIndex[SideLeft] = 2; + mDevice->RealOut.ChannelIndex[SideRight] = 3; mDevice->RealOut.ChannelIndex[FrontCenter] = 4; mDevice->RealOut.ChannelIndex[LFE] = 5; return; @@ -139,7 +132,6 @@ void BackendBase::setDefaultChannelOrder() case DevFmtMono: case DevFmtStereo: case DevFmtQuad: - case DevFmtX51: case DevFmtX61: case DevFmtAmbi3D: setDefaultWFXChannelOrder(); @@ -150,6 +142,13 @@ void BackendBase::setDefaultChannelOrder() #ifdef _WIN32 void BackendBase::setChannelOrderFromWFXMask(uint chanmask) { + static constexpr uint x51{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER + | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT}; + static constexpr uint x51rear{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER + | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT}; + /* Swap a 5.1 mask using the back channels for one with the sides. */ + if(chanmask == x51rear) chanmask = x51; + auto get_channel = [](const DWORD chanbit) noexcept -> al::optional { switch(chanbit) diff --git a/Engine/lib/openal-soft/Alc/backends/base.h b/Engine/lib/openal-soft/alc/backends/base.h similarity index 83% rename from Engine/lib/openal-soft/Alc/backends/base.h rename to Engine/lib/openal-soft/alc/backends/base.h index df076276b..a3562f542 100644 --- a/Engine/lib/openal-soft/Alc/backends/base.h +++ b/Engine/lib/openal-soft/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,9 +70,8 @@ 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) { - BackendBase *backend{device->Backend.get()}; ClockLatency ret{backend->getClockLatency()}; ret.Latency += device->FixedLatency; return ret; @@ -85,7 +85,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 +103,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/Engine/lib/openal-soft/Alc/backends/coreaudio.cpp b/Engine/lib/openal-soft/alc/backends/coreaudio.cpp similarity index 61% rename from Engine/lib/openal-soft/Alc/backends/coreaudio.cpp rename to Engine/lib/openal-soft/alc/backends/coreaudio.cpp index 8e38a777d..ed85e2a92 100644 --- a/Engine/lib/openal-soft/Alc/backends/coreaudio.cpp +++ b/Engine/lib/openal-soft/alc/backends/coreaudio.cpp @@ -20,34 +20,239 @@ #include "config.h" -#include "backends/coreaudio.h" +#include "coreaudio.h" #include +#include #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 namespace { -static const char ca_device[] = "CoreAudio Default"; +#if TARGET_OS_IOS || TARGET_OS_TV +#define CAN_ENUMERATE 0 +#else +#define CAN_ENUMERATE 1 +#endif + + +#if CAN_ENUMERATE +struct DeviceEntry { + AudioDeviceID mId; + std::string mName; +}; + +std::vector PlaybackList; +std::vector CaptureList; + + +OSStatus GetHwProperty(AudioHardwarePropertyID propId, UInt32 dataSize, void *propData) +{ + const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster}; + return AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, nullptr, &dataSize, + propData); +} + +OSStatus GetHwPropertySize(AudioHardwarePropertyID propId, UInt32 *outSize) +{ + const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster}; + return AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, nullptr, outSize); +} + +OSStatus GetDevProperty(AudioDeviceID devId, AudioDevicePropertyID propId, bool isCapture, + UInt32 elem, UInt32 dataSize, void *propData) +{ + static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput, + kAudioDevicePropertyScopeInput}; + const AudioObjectPropertyAddress addr{propId, scopes[isCapture], elem}; + return AudioObjectGetPropertyData(devId, &addr, 0, nullptr, &dataSize, propData); +} + +OSStatus GetDevPropertySize(AudioDeviceID devId, AudioDevicePropertyID inPropertyID, + bool isCapture, UInt32 elem, UInt32 *outSize) +{ + static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput, + kAudioDevicePropertyScopeInput}; + const AudioObjectPropertyAddress addr{inPropertyID, scopes[isCapture], elem}; + return AudioObjectGetPropertyDataSize(devId, &addr, 0, nullptr, outSize); +} + + +std::string GetDeviceName(AudioDeviceID devId) +{ + std::string devname; + CFStringRef nameRef; + + /* Try to get the device name as a CFString, for Unicode name support. */ + OSStatus err{GetDevProperty(devId, kAudioDevicePropertyDeviceNameCFString, false, 0, + sizeof(nameRef), &nameRef)}; + if(err == noErr) + { + const CFIndex propSize{CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef), + kCFStringEncodingUTF8)}; + devname.resize(static_cast(propSize)+1, '\0'); + + CFStringGetCString(nameRef, &devname[0], propSize+1, kCFStringEncodingUTF8); + CFRelease(nameRef); + } + else + { + /* If that failed, just get the C string. Hopefully there's nothing bad + * with this. + */ + UInt32 propSize{}; + if(GetDevPropertySize(devId, kAudioDevicePropertyDeviceName, false, 0, &propSize)) + return devname; + + devname.resize(propSize+1, '\0'); + if(GetDevProperty(devId, kAudioDevicePropertyDeviceName, false, 0, propSize, &devname[0])) + { + devname.clear(); + return devname; + } + } + + /* Clear extraneous nul chars that may have been written with the name + * string, and return it. + */ + while(!devname.back()) + devname.pop_back(); + return devname; +} + +UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) +{ + UInt32 propSize{}; + auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, + &propSize); + if(err) + { + ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err); + return 0; + } + + auto buflist_data = std::make_unique(propSize); + auto *buflist = reinterpret_cast(buflist_data.get()); + + err = GetDevProperty(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, propSize, + buflist); + if(err) + { + ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err); + return 0; + } + + UInt32 numChannels{0}; + for(size_t i{0};i < buflist->mNumberBuffers;++i) + numChannels += buflist->mBuffers[i].mNumberChannels; + + return numChannels; +} + + +void EnumerateDevices(std::vector &list, bool isCapture) +{ + UInt32 propSize{}; + if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize)) + { + ERR("Failed to get device list size: %u\n", err); + return; + } + + auto devIds = std::vector(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown); + if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data())) + { + ERR("Failed to get device list: %u\n", err); + return; + } + + std::vector newdevs; + newdevs.reserve(devIds.size()); + + AudioDeviceID defaultId{kAudioDeviceUnknown}; + GetHwProperty(isCapture ? kAudioHardwarePropertyDefaultInputDevice : + kAudioHardwarePropertyDefaultOutputDevice, sizeof(defaultId), &defaultId); + + if(defaultId != kAudioDeviceUnknown) + { + newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)}); + const auto &entry = newdevs.back(); + TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId); + } + for(const AudioDeviceID devId : devIds) + { + if(devId == kAudioDeviceUnknown) + continue; + + auto match_devid = [devId](const DeviceEntry &entry) noexcept -> bool + { return entry.mId == devId; }; + auto match = std::find_if(newdevs.cbegin(), newdevs.cend(), match_devid); + if(match != newdevs.cend()) continue; + + auto numChannels = GetDeviceChannelCount(devId, isCapture); + if(numChannels > 0) + { + newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)}); + const auto &entry = newdevs.back(); + TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId); + } + } + + if(newdevs.size() > 1) + { + /* Rename entries that have matching names, by appending '#2', '#3', + * etc, as needed. + */ + for(auto curitem = newdevs.begin()+1;curitem != newdevs.end();++curitem) + { + auto check_match = [curitem](const DeviceEntry &entry) -> bool + { return entry.mName == curitem->mName; }; + if(std::find_if(newdevs.begin(), curitem, check_match) != curitem) + { + std::string name{curitem->mName}; + size_t count{1}; + auto check_name = [&name](const DeviceEntry &entry) -> bool + { return entry.mName == name; }; + do { + name = curitem->mName; + name += " #"; + name += std::to_string(++count); + } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem); + curitem->mName = std::move(name); + } + } + } + + newdevs.shrink_to_fit(); + newdevs.swap(list); +} + +#else + +static constexpr char ca_device[] = "CoreAudio Default"; +#endif struct CoreAudioPlayback final : public BackendBase { - CoreAudioPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~CoreAudioPlayback() override; OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags, @@ -96,19 +301,41 @@ OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTi void CoreAudioPlayback::open(const char *name) { +#if CAN_ENUMERATE + AudioDeviceID audioDevice{kAudioDeviceUnknown}; + if(!name) + GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice), + &audioDevice); + else + { + if(PlaybackList.empty()) + EnumerateDevices(PlaybackList, false); + + auto find_name = [name](const DeviceEntry &entry) -> bool + { return entry.mName == name; }; + auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name); + if(devmatch == PlaybackList.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + + audioDevice = devmatch->mId; + } +#else if(!name) name = ca_device; else if(strcmp(name, ca_device) != 0) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; +#endif /* open the default output unit */ AudioComponentDescription desc{}; desc.componentType = kAudioUnitType_Output; -#if TARGET_OS_IOS - desc.componentSubType = kAudioUnitSubType_RemoteIO; +#if CAN_ENUMERATE + desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ? + kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput; #else - desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentSubType = kAudioUnitSubType_RemoteIO; #endif desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; @@ -118,18 +345,50 @@ 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); +#if CAN_ENUMERATE + if(audioDevice != kAudioDeviceUnknown) + AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID)); +#endif + + err = AudioUnitInitialize(audioUnit); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, "Could not initialize audio unit: %u", err}; + /* WARNING: I don't know if "valid" audio unit values are guaranteed to be + * non-0. If not, this logic is broken. + */ + if(mAudioUnit) + { + AudioUnitUninitialize(mAudioUnit); + AudioComponentInstanceDispose(mAudioUnit); + } + mAudioUnit = audioUnit; + +#if CAN_ENUMERATE + if(name) + mDevice->DeviceName = name; + else + { + UInt32 propSize{sizeof(audioDevice)}; + audioDevice = kAudioDeviceUnknown; + AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, 0, &audioDevice, &propSize); + + std::string devname{GetDeviceName(audioDevice)}; + if(!devname.empty()) mDevice->DeviceName = std::move(devname); + else mDevice->DeviceName = "Unknown Device Name"; + } +#else mDevice->DeviceName = name; +#endif } bool CoreAudioPlayback::reset() @@ -140,10 +399,10 @@ bool CoreAudioPlayback::reset() /* retrieve default output unit's properties (output side) */ AudioStreamBasicDescription streamFormat{}; - auto size = static_cast(sizeof(AudioStreamBasicDescription)); + UInt32 size{sizeof(streamFormat)}; err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamFormat, &size); - if(err != noErr || size != sizeof(AudioStreamBasicDescription)) + if(err != noErr || size != sizeof(streamFormat)) { ERR("AudioUnitGetProperty failed\n"); return false; @@ -159,15 +418,9 @@ bool CoreAudioPlayback::reset() TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate); #endif - /* set default output unit's input side to match output side */ - err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, - 0, &streamFormat, size); - if(err != noErr) - { - ERR("AudioUnitSetProperty failed\n"); - return false; - } - + /* Use the sample rate from the output unit's current parameters, but reset + * everything else. + */ if(mDevice->Frequency != streamFormat.mSampleRate) { mDevice->BufferSize = static_cast(uint64_t{mDevice->BufferSize} * @@ -176,82 +429,54 @@ bool CoreAudioPlayback::reset() } /* FIXME: How to tell what channels are what in the output device, and how - * to specify what we're giving? eg, 6.0 vs 5.1 */ - switch(streamFormat.mChannelsPerFrame) - { - case 1: - mDevice->FmtChans = DevFmtMono; - break; - case 2: - mDevice->FmtChans = DevFmtStereo; - break; - case 4: - mDevice->FmtChans = DevFmtQuad; - break; - case 6: - mDevice->FmtChans = DevFmtX51; - break; - case 7: - mDevice->FmtChans = DevFmtX61; - break; - case 8: - mDevice->FmtChans = DevFmtX71; - break; - default: - ERR("Unhandled channel count (%d), using Stereo\n", streamFormat.mChannelsPerFrame); - mDevice->FmtChans = DevFmtStereo; - streamFormat.mChannelsPerFrame = 2; - break; - } - setDefaultWFXChannelOrder(); + * to specify what we're giving? e.g. 6.0 vs 5.1 + */ + streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt(); - /* use channel count and sample rate from the default output unit's current - * parameters, but reset everything else */ streamFormat.mFramesPerPacket = 1; - streamFormat.mFormatFlags = 0; + streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked; + streamFormat.mFormatID = kAudioFormatLinearPCM; switch(mDevice->FmtType) { case DevFmtUByte: mDevice->FmtType = DevFmtByte; /* fall-through */ case DevFmtByte: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; streamFormat.mBitsPerChannel = 8; break; case DevFmtUShort: mDevice->FmtType = DevFmtShort; /* fall-through */ case DevFmtShort: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; streamFormat.mBitsPerChannel = 16; break; case DevFmtUInt: mDevice->FmtType = DevFmtInt; /* fall-through */ case DevFmtInt: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; streamFormat.mBitsPerChannel = 32; break; case DevFmtFloat: - streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat; + streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat; streamFormat.mBitsPerChannel = 32; break; } - streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame * - streamFormat.mBitsPerChannel / 8; - streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame; - streamFormat.mFormatID = kAudioFormatLinearPCM; - streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian | - kLinearPCMFormatFlagIsPacked; + streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame*streamFormat.mBitsPerChannel/8; + streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket; err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, - 0, &streamFormat, sizeof(AudioStreamBasicDescription)); + 0, &streamFormat, sizeof(streamFormat)); if(err != noErr) { ERR("AudioUnitSetProperty failed\n"); return false; } + setDefaultWFXChannelOrder(); + /* setup callback */ mFrameSize = mDevice->frameSizeFromFmt(); AURenderCallbackStruct input{}; @@ -294,7 +519,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, @@ -383,45 +608,64 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*, void CoreAudioCapture::open(const char *name) { - AudioStreamBasicDescription requestedFormat; // The application requested format - AudioStreamBasicDescription hardwareFormat; // The hardware format - AudioStreamBasicDescription outputFormat; // The AudioUnit output format - AURenderCallbackStruct input; - AudioComponentDescription desc; - UInt32 propertySize; - UInt32 enableIO; - AudioComponent comp; - OSStatus err; +#if CAN_ENUMERATE + AudioDeviceID audioDevice{kAudioDeviceUnknown}; + if(!name) + GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice), + &audioDevice); + else + { + if(CaptureList.empty()) + EnumerateDevices(CaptureList, true); + auto find_name = [name](const DeviceEntry &entry) -> bool + { return entry.mName == name; }; + auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name); + if(devmatch == CaptureList.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + + audioDevice = devmatch->mId; + } +#else if(!name) name = ca_device; else if(strcmp(name, ca_device) != 0) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; +#endif + AudioComponentDescription desc{}; desc.componentType = kAudioUnitType_Output; -#if TARGET_OS_IOS - desc.componentSubType = kAudioUnitSubType_RemoteIO; +#if CAN_ENUMERATE + desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ? + kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput; #else - desc.componentSubType = kAudioUnitSubType_HALOutput; + desc.componentSubType = kAudioUnitSubType_RemoteIO; #endif desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; // Search for component with given description - comp = AudioComponentFindNext(NULL, &desc); + AudioComponent comp{AudioComponentFindNext(NULL, &desc)}; if(comp == NULL) throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"}; // Open the component - err = AudioComponentInstanceNew(comp, &mAudioUnit); + OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, "Could not create component instance: %u", err}; +#if CAN_ENUMERATE + if(audioDevice != kAudioDeviceUnknown) + AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID)); +#endif + // Turn off AudioUnit output - enableIO = 0; + UInt32 enableIO{0}; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &enableIO, sizeof(enableIO)); if(err != noErr) @@ -436,35 +680,8 @@ 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 - { - // Get the default input device - AudioDeviceID inputDevice = kAudioDeviceUnknown; - - propertySize = sizeof(AudioDeviceID); - AudioObjectPropertyAddress propertyAddress{}; - propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; - propertyAddress.mScope = kAudioObjectPropertyScopeGlobal; - propertyAddress.mElement = kAudioObjectPropertyElementMaster; - - err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr, - &propertySize, &inputDevice); - if(err != noErr) - throw al::backend_exception{al::backend_error::NoDevice, - "Could not get input device: %u", err}; - if(inputDevice == kAudioDeviceUnknown) - throw al::backend_exception{al::backend_error::NoDevice, "Unknown input device"}; - - // Track the input device - err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID)); - if(err != noErr) - throw al::backend_exception{al::backend_error::NoDevice, - "Could not set input device: %u", err}; - } -#endif - // set capture callback + AURenderCallbackStruct input{}; input.inputProc = CoreAudioCapture::RecordProcC; input.inputProcRefCon = this; @@ -489,14 +706,16 @@ void CoreAudioCapture::open(const char *name) "Could not initialize audio unit: %u", err}; // Get the hardware format - propertySize = sizeof(AudioStreamBasicDescription); + AudioStreamBasicDescription hardwareFormat{}; + UInt32 propertySize{sizeof(hardwareFormat)}; err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1, &hardwareFormat, &propertySize); - if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription)) + if(err != noErr || propertySize != sizeof(hardwareFormat)) throw al::backend_exception{al::backend_error::DeviceError, "Could not get input format: %u", err}; // Set up the requested format description + AudioStreamBasicDescription requestedFormat{}; switch(mDevice->FmtType) { case DevFmtByte: @@ -509,7 +728,8 @@ void CoreAudioCapture::open(const char *name) break; case DevFmtShort: requestedFormat.mBitsPerChannel = 16; - requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger + | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; break; case DevFmtUShort: requestedFormat.mBitsPerChannel = 16; @@ -517,7 +737,8 @@ void CoreAudioCapture::open(const char *name) break; case DevFmtInt: requestedFormat.mBitsPerChannel = 32; - requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger + | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; break; case DevFmtUInt: requestedFormat.mBitsPerChannel = 32; @@ -525,7 +746,8 @@ void CoreAudioCapture::open(const char *name) break; case DevFmtFloat: requestedFormat.mBitsPerChannel = 32; - requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian + | kAudioFormatFlagIsPacked; break; } @@ -540,7 +762,6 @@ void CoreAudioCapture::open(const char *name) case DevFmtQuad: case DevFmtX51: - case DevFmtX51Rear: case DevFmtX61: case DevFmtX71: case DevFmtAmbi3D: @@ -561,7 +782,7 @@ void CoreAudioCapture::open(const char *name) // Use intermediate format for sample rate conversion (outputFormat) // Set sample rate to the same as hardware for resampling later - outputFormat = requestedFormat; + AudioStreamBasicDescription outputFormat{requestedFormat}; outputFormat.mSampleRate = hardwareFormat.mSampleRate; // The output format should be the requested format, but using the hardware sample rate @@ -600,7 +821,23 @@ void CoreAudioCapture::open(const char *name) mFormat.mChannelsPerFrame, static_cast(hardwareFormat.mSampleRate), mDevice->Frequency, Resampler::FastBSinc24); +#if CAN_ENUMERATE + if(name) + mDevice->DeviceName = name; + else + { + UInt32 propSize{sizeof(audioDevice)}; + audioDevice = kAudioDeviceUnknown; + AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, 0, &audioDevice, &propSize); + + std::string devname{GetDeviceName(audioDevice)}; + if(!devname.empty()) mDevice->DeviceName = std::move(devname); + else mDevice->DeviceName = "Unknown Device Name"; + } +#else mDevice->DeviceName = name; +#endif } @@ -665,6 +902,26 @@ bool CoreAudioBackendFactory::querySupport(BackendType type) std::string CoreAudioBackendFactory::probe(BackendType type) { std::string outnames; +#if CAN_ENUMERATE + auto append_name = [&outnames](const DeviceEntry &entry) -> void + { + /* Includes null char. */ + outnames.append(entry.mName.c_str(), entry.mName.length()+1); + }; + switch(type) + { + case BackendType::Playback: + EnumerateDevices(PlaybackList, false); + std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); + break; + case BackendType::Capture: + EnumerateDevices(CaptureList, true); + std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name); + break; + } + +#else + switch(type) { case BackendType::Playback: @@ -673,10 +930,11 @@ std::string CoreAudioBackendFactory::probe(BackendType type) outnames.append(ca_device, sizeof(ca_device)); break; } +#endif return outnames; } -BackendPtr CoreAudioBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new CoreAudioPlayback{device}}; diff --git a/Engine/lib/openal-soft/Alc/backends/coreaudio.h b/Engine/lib/openal-soft/alc/backends/coreaudio.h similarity index 76% rename from Engine/lib/openal-soft/Alc/backends/coreaudio.h rename to Engine/lib/openal-soft/alc/backends/coreaudio.h index 169578068..1252edde3 100644 --- a/Engine/lib/openal-soft/Alc/backends/coreaudio.h +++ b/Engine/lib/openal-soft/alc/backends/coreaudio.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_COREAUDIO_H #define BACKENDS_COREAUDIO_H -#include "backends/base.h" +#include "base.h" struct CoreAudioBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/Alc/backends/dsound.cpp b/Engine/lib/openal-soft/alc/backends/dsound.cpp similarity index 91% rename from Engine/lib/openal-soft/Alc/backends/dsound.cpp rename to Engine/lib/openal-soft/alc/backends/dsound.cpp index aa3a92a01..0edc286ff 100644 --- a/Engine/lib/openal-soft/Alc/backends/dsound.cpp +++ b/Engine/lib/openal-soft/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) @@ -406,17 +402,14 @@ bool DSoundPlayback::reset() mDevice->FmtChans = DevFmtStereo; else if(speakers == DSSPEAKER_QUAD) mDevice->FmtChans = DevFmtQuad; - else if(speakers == DSSPEAKER_5POINT1_SURROUND) + else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK) mDevice->FmtChans = DevFmtX51; - else if(speakers == DSSPEAKER_5POINT1_BACK) - mDevice->FmtChans = DevFmtX51Rear; else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND) mDevice->FmtChans = DevFmtX71; else ERR("Unknown system speaker config: 0x%lx\n", speakers); } - mDevice->IsHeadphones = mDevice->FmtChans == DevFmtStereo - && speakers == DSSPEAKER_HEADPHONE; + mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE)); switch(mDevice->FmtChans) { @@ -426,7 +419,6 @@ bool DSoundPlayback::reset() case DevFmtStereo: OutputType.dwChannelMask = STEREO; break; case DevFmtQuad: OutputType.dwChannelMask = QUAD; break; case DevFmtX51: OutputType.dwChannelMask = X5DOT1; break; - case DevFmtX51Rear: OutputType.dwChannelMask = X5DOT1REAR; break; case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break; case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break; } @@ -454,8 +446,6 @@ retry_open: else OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); mPrimaryBuffer = nullptr; } else @@ -465,7 +455,7 @@ retry_open: DSBUFFERDESC DSBDescription{}; DSBDescription.dwSize = sizeof(DSBDescription); DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; - hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr); + hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr); } if(SUCCEEDED(hr)) hr = mPrimaryBuffer->SetFormat(&OutputType.Format); @@ -485,7 +475,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 +489,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 +507,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 +542,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 +551,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 +566,8 @@ DSoundCapture::~DSoundCapture() if(mDSCbuffer) { mDSCbuffer->Stop(); - mDSCbuffer->Release(); mDSCbuffer = nullptr; } - - if(mDSC) - mDSC->Release(); mDSC = nullptr; } @@ -653,7 +633,6 @@ void DSoundCapture::open(const char *name) case DevFmtStereo: InputType.dwChannelMask = STEREO; break; case DevFmtQuad: InputType.dwChannelMask = QUAD; break; case DevFmtX51: InputType.dwChannelMask = X5DOT1; break; - case DevFmtX51Rear: InputType.dwChannelMask = X5DOT1REAR; break; case DevFmtX61: InputType.dwChannelMask = X6DOT1; break; case DevFmtX71: InputType.dwChannelMask = X7DOT1; break; case DevFmtAmbi3D: @@ -693,20 +672,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 +833,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/Engine/lib/openal-soft/Alc/backends/dsound.h b/Engine/lib/openal-soft/alc/backends/dsound.h similarity index 76% rename from Engine/lib/openal-soft/Alc/backends/dsound.h rename to Engine/lib/openal-soft/alc/backends/dsound.h index 83f7d5c77..787f227a0 100644 --- a/Engine/lib/openal-soft/Alc/backends/dsound.h +++ b/Engine/lib/openal-soft/alc/backends/dsound.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_DSOUND_H #define BACKENDS_DSOUND_H -#include "backends/base.h" +#include "base.h" struct DSoundBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/Alc/backends/jack.cpp b/Engine/lib/openal-soft/alc/backends/jack.cpp similarity index 55% rename from Engine/lib/openal-soft/Alc/backends/jack.cpp rename to Engine/lib/openal-soft/alc/backends/jack.cpp index 17b1f1395..2b0131b3b 100644 --- a/Engine/lib/openal-soft/Alc/backends/jack.cpp +++ b/Engine/lib/openal-soft/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); \ @@ -101,6 +99,8 @@ decltype(jack_error_callback) * pjack_error_callback; #endif +constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE; + jack_options_t ClientOptions = JackNullOption; bool jack_load() @@ -152,6 +152,11 @@ bool jack_load() } +struct JackDeleter { + void operator()(void *ptr) { jack_free(ptr); } +}; +using JackPortsPtr = std::unique_ptr; + struct DeviceEntry { std::string mName; std::string mPattern; @@ -160,53 +165,125 @@ 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()) + if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)}) { - size_t nextpos{customList.find(';', strpos)}; - size_t seppos{customList.find('=', strpos)}; - if(seppos >= nextpos || seppos == strpos) + for(size_t i{0};ports[i];++i) { - const std::string entry{customList.substr(strpos, nextpos-strpos)}; - ERR("Invalid device entry: \"%s\"\n", entry.c_str()); + const char *sep{std::strchr(ports[i], ':')}; + if(!sep || ports[i] == sep) continue; + + const al::span portdev{ports[i], sep}; + auto check_name = [portdev](const DeviceEntry &entry) -> bool + { + const size_t len{portdev.size()}; + return entry.mName.length() == len + && entry.mName.compare(0, len, portdev.data(), len) == 0; + }; + if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend()) + continue; + + std::string name{portdev.data(), portdev.size()}; + list.emplace_back(DeviceEntry{name, name+":"}); + const auto &entry = list.back(); + TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); + } + /* There are ports but couldn't get device names from them. Add a + * generic entry. + */ + if(ports[0] && list.empty()) + { + WARN("No device names found in available ports, adding a generic name.\n"); + list.emplace_back(DeviceEntry{"JACK", ""}); + } + } + + if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices")) + { + for(size_t strpos{0};strpos < listopt->size();) + { + size_t nextpos{listopt->find(';', strpos)}; + size_t seppos{listopt->find('=', strpos)}; + if(seppos >= nextpos || seppos == strpos) + { + const std::string entry{listopt->substr(strpos, nextpos-strpos)}; + ERR("Invalid device entry: \"%s\"\n", entry.c_str()); + if(nextpos != std::string::npos) ++nextpos; + strpos = nextpos; + continue; + } + + const al::span name{listopt->data()+strpos, seppos-strpos}; + const al::span pattern{listopt->data()+(seppos+1), + std::min(nextpos, listopt->size())-(seppos+1)}; + + /* Check if this custom pattern already exists in the list. */ + auto check_pattern = [pattern](const DeviceEntry &entry) -> bool + { + const size_t len{pattern.size()}; + return entry.mPattern.length() == len + && entry.mPattern.compare(0, len, pattern.data(), len) == 0; + }; + auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern); + if(itemmatch != list.end()) + { + /* If so, replace the name with this custom one. */ + itemmatch->mName.assign(name.data(), name.size()); + TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(), + itemmatch->mPattern.c_str()); + } + else + { + /* Otherwise, add a new device entry. */ + list.emplace_back(DeviceEntry{std::string{name.data(), name.size()}, + std::string{pattern.data(), pattern.size()}}); + const auto &entry = list.back(); + TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); + } + if(nextpos != std::string::npos) ++nextpos; strpos = nextpos; - continue; } + } - size_t count{1}; - std::string name{customList.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()) + if(list.size() > 1) + { + /* Rename entries that have matching names, by appending '#2', '#3', + * etc, as needed. + */ + for(auto curitem = list.begin()+1;curitem != list.end();++curitem) { - name = customList.substr(strpos, seppos-strpos); - name += " #"; - name += std::to_string(++count); + auto check_match = [curitem](const DeviceEntry &entry) -> bool + { return entry.mName == curitem->mName; }; + if(std::find_if(list.begin(), curitem, check_match) != curitem) + { + std::string name{curitem->mName}; + size_t count{1}; + auto check_name = [&name](const DeviceEntry &entry) -> bool + { return entry.mName == name; }; + do { + name = curitem->mName; + name += " #"; + name += std::to_string(++count); + } while(std::find_if(list.begin(), curitem, check_name) != curitem); + curitem->mName = std::move(name); + } } - - ++seppos; - list.emplace_back(DeviceEntry{std::move(name), customList.substr(seppos, nextpos-seppos)}); - const auto &entry = list.back(); - TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); - - if(nextpos != std::string::npos) ++nextpos; - strpos = nextpos; } } struct JackPlayback final : public BackendBase { - JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~JackPlayback() override; + int processRt(jack_nframes_t numframes) noexcept; + static int processRtC(jack_nframes_t numframes, void *arg) noexcept + { return static_cast(arg)->processRt(numframes); } + int process(jack_nframes_t numframes) noexcept; static int processC(jack_nframes_t numframes, void *arg) noexcept { return static_cast(arg)->process(numframes); } @@ -227,6 +304,7 @@ struct JackPlayback final : public BackendBase { std::mutex mMutex; std::atomic mPlaying{false}; + bool mRTMixing{false}; RingBufferPtr mRing; al::semaphore mSem; @@ -241,16 +319,40 @@ JackPlayback::~JackPlayback() if(!mClient) return; - std::for_each(mPort.begin(), mPort.end(), - [this](jack_port_t *port) -> void - { if(port) jack_port_unregister(mClient, port); } - ); + auto unregister_port = [this](jack_port_t *port) -> void + { if(port) jack_port_unregister(mClient, port); }; + std::for_each(mPort.begin(), mPort.end(), unregister_port); mPort.fill(nullptr); + jack_client_close(mClient); mClient = nullptr; } +int JackPlayback::processRt(jack_nframes_t numframes) noexcept +{ + std::array out; + size_t numchans{0}; + for(auto port : mPort) + { + if(!port || numchans == mDevice->RealOut.Buffer.size()) + break; + out[numchans++] = static_cast(jack_port_get_buffer(port, numframes)); + } + + if LIKELY(mPlaying.load(std::memory_order_acquire)) + mDevice->renderSamples({out.data(), numchans}, static_cast(numframes)); + else + { + auto clear_buf = [numframes](float *outbuf) -> void + { std::fill_n(outbuf, numframes, 0.0f); }; + std::for_each(out.begin(), out.begin()+numchans, clear_buf); + } + + return 0; +} + + int JackPlayback::process(jack_nframes_t numframes) noexcept { std::array out; @@ -352,49 +454,56 @@ 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); + mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", 1); + jack_set_process_callback(mClient, + mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); mDevice->DeviceName = name; } bool JackPlayback::reset() { - std::for_each(mPort.begin(), mPort.end(), - [this](jack_port_t *port) -> void - { if(port) jack_port_unregister(mClient, port); }); + auto unregister_port = [this](jack_port_t *port) -> void + { if(port) jack_port_unregister(mClient, port); }; + std::for_each(mPort.begin(), mPort.end(), unregister_port); mPort.fill(nullptr); /* Ignore the requested buffer metrics and just keep one JACK-sized buffer @@ -402,28 +511,39 @@ bool JackPlayback::reset() */ mDevice->Frequency = jack_get_sample_rate(mClient); mDevice->UpdateSize = jack_get_buffer_size(mClient); - mDevice->BufferSize = mDevice->UpdateSize * 2; - - const char *devname{mDevice->DeviceName.c_str()}; - uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); - mDevice->BufferSize = bufsize + mDevice->UpdateSize; + if(mRTMixing) + { + /* Assume only two periods when directly mixing. Should try to query + * the total port latency when connected. + */ + mDevice->BufferSize = mDevice->UpdateSize * 2; + } + else + { + const char *devname{mDevice->DeviceName.c_str()}; + uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; + bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); + mDevice->BufferSize = bufsize + mDevice->UpdateSize; + } /* Force 32-bit float output. */ mDevice->FmtType = DevFmtFloat; + int port_num{0}; auto ports_end = mPort.begin() + mDevice->channelsFromFmt(); - auto bad_port = std::find_if_not(mPort.begin(), ports_end, - [this](jack_port_t *&port) -> bool - { - std::string name{"channel_" + std::to_string(&port - &mPort[0] + 1)}; - port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - return port != nullptr; - }); + auto bad_port = mPort.begin(); + while(bad_port != ports_end) + { + std::string name{"channel_" + std::to_string(++port_num)}; + *bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType, + JackPortIsOutput | JackPortIsTerminal, 0); + if(!*bad_port) break; + ++bad_port; + } if(bad_port != ports_end) { - ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans)); + ERR("Failed to register enough JACK ports for %s output\n", + DevFmtChannelsString(mDevice->FmtChans)); if(bad_port == mPort.begin()) return false; if(bad_port == mPort.begin()+1) @@ -453,29 +573,25 @@ 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)}; - if(ports == nullptr) + JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType, + JackPortIsInput)}; + if(!pnames) { jack_deactivate(mClient); - throw al::backend_exception{al::backend_error::DeviceError, - "No physical playback ports found"}; + throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"}; } - auto connect_port = [this](const jack_port_t *port, const char *pname) -> bool + + for(size_t i{0};i < al::size(mPort) && mPort[i];++i) { - if(!port) return false; - if(!pname) + if(!pnames[i]) { - ERR("No physical playback port for \"%s\"\n", jack_port_name(port)); - return false; + ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i])); + break; } - if(jack_connect(mClient, jack_port_name(port), pname)) - ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(port), - pname); - return true; - }; - std::mismatch(mPort.begin(), mPort.end(), ports, connect_port); - jack_free(ports); + if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i])) + ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort[i]), + pnames[i]); + } } /* Reconfigure buffer metrics in case the server changed it since the reset @@ -486,36 +602,45 @@ void JackPlayback::start() mDevice->UpdateSize = jack_get_buffer_size(mClient); mDevice->BufferSize = mDevice->UpdateSize * 2; - uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); - mDevice->BufferSize = bufsize + mDevice->UpdateSize; - mRing = nullptr; - mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true); - - try { + if(mRTMixing) mPlaying.store(true, std::memory_order_release); - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this}; - } - catch(std::exception& e) { - jack_deactivate(mClient); - mPlaying.store(false, std::memory_order_release); - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + else + { + uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; + bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); + mDevice->BufferSize = bufsize + mDevice->UpdateSize; + + mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true); + + try { + mPlaying.store(true, std::memory_order_release); + mKillNow.store(false, std::memory_order_release); + mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this}; + } + catch(std::exception& e) { + jack_deactivate(mClient); + mPlaying.store(false, std::memory_order_release); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start mixing thread: %s", e.what()}; + } } } void JackPlayback::stop() { - if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) - return; + if(mPlaying.load(std::memory_order_acquire)) + { + mKillNow.store(true, std::memory_order_release); + if(mThread.joinable()) + { + mSem.post(); + mThread.join(); + } - mSem.post(); - mThread.join(); - - jack_deactivate(mClient); - mPlaying.store(false, std::memory_order_release); + jack_deactivate(mClient); + mPlaying.store(false, std::memory_order_release); + } } @@ -525,7 +650,7 @@ ClockLatency JackPlayback::getClockLatency() std::lock_guard _{mMutex}; ret.ClockTime = GetDeviceClockTime(mDevice); - ret.Latency = std::chrono::seconds{mRing->readSpace()}; + ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize}; ret.Latency /= mDevice->Frequency; return ret; @@ -547,10 +672,13 @@ bool JackBackendFactory::init() if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0)) ClientOptions = static_cast(ClientOptions | JackNoStartServer); + const PathNamePair &binname = GetProcBinary(); + const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; + void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr}; jack_set_error_function(jack_msg_handler); jack_status_t status; - jack_client_t *client{jack_client_open("alsoft", ClientOptions, &status, nullptr)}; + jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)}; jack_set_error_function(old_error_cb); if(!client) { @@ -575,10 +703,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 +725,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/Engine/lib/openal-soft/Alc/backends/jack.h b/Engine/lib/openal-soft/alc/backends/jack.h similarity index 75% rename from Engine/lib/openal-soft/Alc/backends/jack.h rename to Engine/lib/openal-soft/alc/backends/jack.h index f966c8f0b..b83f24dda 100644 --- a/Engine/lib/openal-soft/Alc/backends/jack.h +++ b/Engine/lib/openal-soft/alc/backends/jack.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_JACK_H #define BACKENDS_JACK_H -#include "backends/base.h" +#include "base.h" struct JackBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/Alc/backends/loopback.cpp b/Engine/lib/openal-soft/alc/backends/loopback.cpp similarity index 84% rename from Engine/lib/openal-soft/Alc/backends/loopback.cpp rename to Engine/lib/openal-soft/alc/backends/loopback.cpp index 7f421ea77..bf4ab2467 100644 --- a/Engine/lib/openal-soft/Alc/backends/loopback.cpp +++ b/Engine/lib/openal-soft/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/Engine/lib/openal-soft/Alc/backends/loopback.h b/Engine/lib/openal-soft/alc/backends/loopback.h similarity index 76% rename from Engine/lib/openal-soft/Alc/backends/loopback.h rename to Engine/lib/openal-soft/alc/backends/loopback.h index d000e0336..cb42b3c8e 100644 --- a/Engine/lib/openal-soft/Alc/backends/loopback.h +++ b/Engine/lib/openal-soft/alc/backends/loopback.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_LOOPBACK_H #define BACKENDS_LOOPBACK_H -#include "backends/base.h" +#include "base.h" struct LoopbackBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/Alc/backends/null.cpp b/Engine/lib/openal-soft/alc/backends/null.cpp similarity index 95% rename from Engine/lib/openal-soft/Alc/backends/null.cpp rename to Engine/lib/openal-soft/alc/backends/null.cpp index 15a36ddd4..5a8fc255c 100644 --- a/Engine/lib/openal-soft/Alc/backends/null.cpp +++ b/Engine/lib/openal-soft/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/Engine/lib/openal-soft/Alc/backends/null.h b/Engine/lib/openal-soft/alc/backends/null.h similarity index 75% rename from Engine/lib/openal-soft/Alc/backends/null.h rename to Engine/lib/openal-soft/alc/backends/null.h index 8e9c59e50..7048cad6f 100644 --- a/Engine/lib/openal-soft/Alc/backends/null.h +++ b/Engine/lib/openal-soft/alc/backends/null.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_NULL_H #define BACKENDS_NULL_H -#include "backends/base.h" +#include "base.h" struct NullBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/alc/backends/oboe.cpp b/Engine/lib/openal-soft/alc/backends/oboe.cpp new file mode 100644 index 000000000..38f048cb3 --- /dev/null +++ b/Engine/lib/openal-soft/alc/backends/oboe.cpp @@ -0,0 +1,384 @@ + +#include "config.h" + +#include "oboe.h" + +#include +#include +#include + +#include "alnumeric.h" +#include "core/device.h" +#include "core/logging.h" + +#include "oboe/Oboe.h" + + +namespace { + +constexpr char device_name[] = "Oboe Default"; + + +struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback { + OboePlayback(DeviceBase *device) : BackendBase{device} { } + + oboe::ManagedStream mStream; + + oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, + int32_t numFrames) override; + + void open(const char *name) override; + bool reset() override; + void start() override; + void stop() override; +}; + + +oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData, + int32_t numFrames) +{ + assert(numFrames > 0); + const int32_t numChannels{oboeStream->getChannelCount()}; + + mDevice->renderSamples(audioData, static_cast(numFrames), + static_cast(numChannels)); + return oboe::DataCallbackResult::Continue; +} + + +void OboePlayback::open(const char *name) +{ + if(!name) + name = device_name; + else if(std::strcmp(name, device_name) != 0) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; + + /* Open a basic output stream, just to ensure it can work. */ + oboe::ManagedStream stream; + oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output) + ->setPerformanceMode(oboe::PerformanceMode::LowLatency) + ->openManagedStream(stream)}; + if(result != oboe::Result::OK) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", + oboe::convertToText(result)}; + + mDevice->DeviceName = name; +} + +bool OboePlayback::reset() +{ + oboe::AudioStreamBuilder builder; + builder.setDirection(oboe::Direction::Output); + builder.setPerformanceMode(oboe::PerformanceMode::LowLatency); + /* Don't let Oboe convert. We should be able to handle anything it gives + * back. + */ + builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None); + builder.setChannelConversionAllowed(false); + builder.setFormatConversionAllowed(false); + builder.setCallback(this); + + if(mDevice->Flags.test(FrequencyRequest)) + builder.setSampleRate(static_cast(mDevice->Frequency)); + if(mDevice->Flags.test(ChannelsRequest)) + { + /* Only use mono or stereo at user request. There's no telling what + * other counts may be inferred as. + */ + builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono + : (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo + : oboe::ChannelCount::Unspecified); + } + if(mDevice->Flags.test(SampleTypeRequest)) + { + oboe::AudioFormat format{oboe::AudioFormat::Unspecified}; + switch(mDevice->FmtType) + { + case DevFmtByte: + case DevFmtUByte: + case DevFmtShort: + case DevFmtUShort: + format = oboe::AudioFormat::I16; + break; + case DevFmtInt: + case DevFmtUInt: + case DevFmtFloat: + format = oboe::AudioFormat::Float; + break; + } + builder.setFormat(format); + } + + oboe::Result result{builder.openManagedStream(mStream)}; + /* If the format failed, try asking for the defaults. */ + while(result == oboe::Result::ErrorInvalidFormat) + { + if(builder.getFormat() != oboe::AudioFormat::Unspecified) + builder.setFormat(oboe::AudioFormat::Unspecified); + else if(builder.getSampleRate() != oboe::kUnspecified) + builder.setSampleRate(oboe::kUnspecified); + else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified) + builder.setChannelCount(oboe::ChannelCount::Unspecified); + else + break; + result = builder.openManagedStream(mStream); + } + if(result != oboe::Result::OK) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", + oboe::convertToText(result)}; + mStream->setBufferSizeInFrames(mini(static_cast(mDevice->BufferSize), + mStream->getBufferCapacityInFrames())); + TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); + + if(static_cast(mStream->getChannelCount()) != mDevice->channelsFromFmt()) + { + if(mStream->getChannelCount() >= 2) + mDevice->FmtChans = DevFmtStereo; + else if(mStream->getChannelCount() == 1) + mDevice->FmtChans = DevFmtMono; + else + throw al::backend_exception{al::backend_error::DeviceError, + "Got unhandled channel count: %d", mStream->getChannelCount()}; + } + setDefaultWFXChannelOrder(); + + switch(mStream->getFormat()) + { + case oboe::AudioFormat::I16: + mDevice->FmtType = DevFmtShort; + break; + case oboe::AudioFormat::Float: + mDevice->FmtType = DevFmtFloat; + break; + case oboe::AudioFormat::Unspecified: + case oboe::AudioFormat::Invalid: + throw al::backend_exception{al::backend_error::DeviceError, + "Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())}; + } + mDevice->Frequency = static_cast(mStream->getSampleRate()); + + /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0 + * indicating variable updates, but OpenAL should have a reasonable minimum update size set. + * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum + * update size. + */ + mDevice->UpdateSize = maxu(mDevice->Frequency / 100, + static_cast(mStream->getFramesPerBurst())); + mDevice->BufferSize = maxu(mDevice->UpdateSize * 2, + static_cast(mStream->getBufferSizeInFrames())); + + return true; +} + +void OboePlayback::start() +{ + const oboe::Result result{mStream->start()}; + if(result != oboe::Result::OK) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s", + oboe::convertToText(result)}; +} + +void OboePlayback::stop() +{ + oboe::Result result{mStream->stop()}; + if(result != oboe::Result::OK) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", + oboe::convertToText(result)}; +} + + +struct OboeCapture final : public BackendBase { + OboeCapture(DeviceBase *device) : BackendBase{device} { } + + oboe::ManagedStream mStream; + + std::vector mSamples; + uint mLastAvail{0u}; + + void open(const char *name) override; + void start() override; + void stop() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; +}; + +void OboeCapture::open(const char *name) +{ + if(!name) + name = device_name; + else if(std::strcmp(name, device_name) != 0) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", + name}; + + oboe::AudioStreamBuilder builder; + builder.setDirection(oboe::Direction::Input) + ->setPerformanceMode(oboe::PerformanceMode::LowLatency) + ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High) + ->setChannelConversionAllowed(true) + ->setFormatConversionAllowed(true) + ->setBufferCapacityInFrames(static_cast(mDevice->BufferSize)) + ->setSampleRate(static_cast(mDevice->Frequency)); + /* Only use mono or stereo at user request. There's no telling what + * other counts may be inferred as. + */ + switch(mDevice->FmtChans) + { + case DevFmtMono: + builder.setChannelCount(oboe::ChannelCount::Mono); + break; + case DevFmtStereo: + builder.setChannelCount(oboe::ChannelCount::Stereo); + break; + case DevFmtQuad: + case DevFmtX51: + case DevFmtX61: + case DevFmtX71: + case DevFmtAmbi3D: + throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", + DevFmtChannelsString(mDevice->FmtChans)}; + } + + /* FIXME: This really should support UByte, but Oboe doesn't. We'll need to + * use a temp buffer and convert. + */ + switch(mDevice->FmtType) + { + case DevFmtShort: + builder.setFormat(oboe::AudioFormat::I16); + break; + case DevFmtFloat: + builder.setFormat(oboe::AudioFormat::Float); + break; + case DevFmtByte: + case DevFmtUByte: + case DevFmtUShort: + case DevFmtInt: + case DevFmtUInt: + throw al::backend_exception{al::backend_error::DeviceError, + "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; + } + + oboe::Result result{builder.openManagedStream(mStream)}; + if(result != oboe::Result::OK) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", + oboe::convertToText(result)}; + if(static_cast(mDevice->BufferSize) > mStream->getBufferCapacityInFrames()) + throw al::backend_exception{al::backend_error::DeviceError, + "Buffer size too large (%u > %d)", mDevice->BufferSize, + mStream->getBufferCapacityInFrames()}; + auto buffer_result = mStream->setBufferSizeInFrames(static_cast(mDevice->BufferSize)); + if(!buffer_result) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set buffer size: %s", oboe::convertToText(buffer_result.error())}; + else if(buffer_result.value() < static_cast(mDevice->BufferSize)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set large enough buffer size (%u > %d)", mDevice->BufferSize, + buffer_result.value()}; + mDevice->BufferSize = static_cast(buffer_result.value()); + + TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); + + mDevice->DeviceName = name; +} + +void OboeCapture::start() +{ + const oboe::Result result{mStream->start()}; + if(result != oboe::Result::OK) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s", + oboe::convertToText(result)}; +} + +void OboeCapture::stop() +{ + /* Capture any unread samples before stopping. Oboe drops whatever's left + * in the stream. + */ + if(auto availres = mStream->getAvailableFrames()) + { + const auto avail = std::max(static_cast(availres.value()), mLastAvail); + const size_t frame_size{static_cast(mStream->getBytesPerFrame())}; + const size_t pos{mSamples.size()}; + mSamples.resize(pos + avail*frame_size); + + auto result = mStream->read(&mSamples[pos], availres.value(), 0); + uint got{bool{result} ? static_cast(result.value()) : 0u}; + if(got < avail) + std::fill_n(&mSamples[pos + got*frame_size], (avail-got)*frame_size, al::byte{}); + mLastAvail = 0; + } + + const oboe::Result result{mStream->stop()}; + if(result != oboe::Result::OK) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", + oboe::convertToText(result)}; +} + +uint OboeCapture::availableSamples() +{ + /* Keep track of the max available frame count, to ensure it doesn't go + * backwards. + */ + if(auto result = mStream->getAvailableFrames()) + mLastAvail = std::max(static_cast(result.value()), mLastAvail); + + const auto frame_size = static_cast(mStream->getBytesPerFrame()); + return static_cast(mSamples.size()/frame_size) + mLastAvail; +} + +void OboeCapture::captureSamples(al::byte *buffer, uint samples) +{ + const auto frame_size = static_cast(mStream->getBytesPerFrame()); + if(const size_t storelen{mSamples.size()}) + { + const auto instore = static_cast(storelen / frame_size); + const uint tocopy{std::min(samples, instore) * frame_size}; + std::copy_n(mSamples.begin(), tocopy, buffer); + mSamples.erase(mSamples.begin(), mSamples.begin() + tocopy); + + buffer += tocopy; + samples -= tocopy/frame_size; + if(!samples) return; + } + + auto result = mStream->read(buffer, static_cast(samples), 0); + uint got{bool{result} ? static_cast(result.value()) : 0u}; + if(got < samples) + std::fill_n(buffer + got*frame_size, (samples-got)*frame_size, al::byte{}); + mLastAvail = std::max(mLastAvail, samples) - samples; +} + +} // namespace + +bool OboeBackendFactory::init() { return true; } + +bool OboeBackendFactory::querySupport(BackendType type) +{ return type == BackendType::Playback || type == BackendType::Capture; } + +std::string OboeBackendFactory::probe(BackendType type) +{ + switch(type) + { + case BackendType::Playback: + case BackendType::Capture: + /* Includes null char. */ + return std::string{device_name, sizeof(device_name)}; + } + return std::string{}; +} + +BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type) +{ + if(type == BackendType::Playback) + return BackendPtr{new OboePlayback{device}}; + if(type == BackendType::Capture) + return BackendPtr{new OboeCapture{device}}; + return BackendPtr{}; +} + +BackendFactory &OboeBackendFactory::getFactory() +{ + static OboeBackendFactory factory{}; + return factory; +} diff --git a/Engine/lib/openal-soft/Alc/backends/oboe.h b/Engine/lib/openal-soft/alc/backends/oboe.h similarity index 75% rename from Engine/lib/openal-soft/Alc/backends/oboe.h rename to Engine/lib/openal-soft/alc/backends/oboe.h index 93b98e37c..a39c24454 100644 --- a/Engine/lib/openal-soft/Alc/backends/oboe.h +++ b/Engine/lib/openal-soft/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/Engine/lib/openal-soft/Alc/backends/opensl.cpp b/Engine/lib/openal-soft/alc/backends/opensl.cpp similarity index 96% rename from Engine/lib/openal-soft/Alc/backends/opensl.cpp rename to Engine/lib/openal-soft/alc/backends/opensl.cpp index 917e097f6..85a5f483b 100644 --- a/Engine/lib/openal-soft/Alc/backends/opensl.cpp +++ b/Engine/lib/openal-soft/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" @@ -68,9 +68,6 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept case DevFmtX51: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT; - case DevFmtX51Rear: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | - SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT | - SL_SPEAKER_BACK_RIGHT; case DevFmtX61: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT; @@ -105,7 +102,7 @@ constexpr SLuint32 GetTypeRepresentation(DevFmtType type) noexcept constexpr SLuint32 GetByteOrderEndianness() noexcept { - if_constexpr(al::endian::native == al::endian::little) + if(al::endian::native == al::endian::little) return SL_BYTEORDER_LITTLEENDIAN; return SL_BYTEORDER_BIGENDIAN; } @@ -151,7 +148,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 +313,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 +629,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; @@ -810,8 +810,11 @@ void OpenSLCapture::open(const char* name) if(SL_RESULT_SUCCESS == result) { const uint chunk_size{mDevice->UpdateSize * mFrameSize}; + const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0}; auto data = mRing->getWriteVector(); + std::fill_n(data.first.buf, data.first.len*chunk_size, silence); + std::fill_n(data.second.buf, data.second.len*chunk_size, silence); for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size); @@ -875,6 +878,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) { const uint update_size{mDevice->UpdateSize}; const uint chunk_size{update_size * mFrameSize}; + const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0}; /* Read the desired samples from the ring buffer then advance its read * pointer. @@ -922,15 +926,20 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) { SLresult result{SL_RESULT_SUCCESS}; auto wdata = mRing->getWriteVector(); + std::fill_n(wdata.first.buf, wdata.first.len*chunk_size, silence); for(size_t i{0u};i < wdata.first.len && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(wdata.first.buf + chunk_size*i, chunk_size); PRINTERR(result, "bufferQueue->Enqueue"); } - for(size_t i{0u};i < wdata.second.len && SL_RESULT_SUCCESS == result;i++) + if(wdata.second.len > 0) { - result = VCALL(bufferQueue,Enqueue)(wdata.second.buf + chunk_size*i, chunk_size); - PRINTERR(result, "bufferQueue->Enqueue"); + std::fill_n(wdata.second.buf, wdata.second.len*chunk_size, silence); + for(size_t i{0u};i < wdata.second.len && SL_RESULT_SUCCESS == result;i++) + { + result = VCALL(bufferQueue,Enqueue)(wdata.second.buf + chunk_size*i, chunk_size); + PRINTERR(result, "bufferQueue->Enqueue"); + } } } } @@ -959,7 +968,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/Engine/lib/openal-soft/Alc/backends/opensl.h b/Engine/lib/openal-soft/alc/backends/opensl.h similarity index 75% rename from Engine/lib/openal-soft/Alc/backends/opensl.h rename to Engine/lib/openal-soft/alc/backends/opensl.h index b1c5cf5a9..b81624476 100644 --- a/Engine/lib/openal-soft/Alc/backends/opensl.h +++ b/Engine/lib/openal-soft/alc/backends/opensl.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_OSL_H #define BACKENDS_OSL_H -#include "backends/base.h" +#include "base.h" struct OSLBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/Alc/backends/oss.cpp b/Engine/lib/openal-soft/alc/backends/oss.cpp similarity index 97% rename from Engine/lib/openal-soft/Alc/backends/oss.cpp rename to Engine/lib/openal-soft/alc/backends/oss.cpp index 4cff59599..6d4fa2615 100644 --- a/Engine/lib/openal-soft/Alc/backends/oss.cpp +++ b/Engine/lib/openal-soft/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/Engine/lib/openal-soft/Alc/backends/oss.h b/Engine/lib/openal-soft/alc/backends/oss.h similarity index 75% rename from Engine/lib/openal-soft/Alc/backends/oss.h rename to Engine/lib/openal-soft/alc/backends/oss.h index dd92efc3e..4f2c00b96 100644 --- a/Engine/lib/openal-soft/Alc/backends/oss.h +++ b/Engine/lib/openal-soft/alc/backends/oss.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_OSS_H #define BACKENDS_OSS_H -#include "backends/base.h" +#include "base.h" struct OSSBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/alc/backends/pipewire.cpp b/Engine/lib/openal-soft/alc/backends/pipewire.cpp new file mode 100644 index 000000000..a19dcb617 --- /dev/null +++ b/Engine/lib/openal-soft/alc/backends/pipewire.cpp @@ -0,0 +1,2008 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2010 by Chris Robinson + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "pipewire.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "albyte.h" +#include "alc/alconfig.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "aloptional.h" +#include "alspan.h" +#include "alstring.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "dynload.h" +#include "opthelpers.h" +#include "ringbuffer.h" + +/* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). */ +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Weverything\"") +#include "pipewire/pipewire.h" +#include "pipewire/extensions/metadata.h" +#include "spa/buffer/buffer.h" +#include "spa/param/audio/format-utils.h" +#include "spa/param/audio/raw.h" +#include "spa/param/param.h" +#include "spa/pod/builder.h" +#include "spa/utils/json.h" + +namespace { +/* Wrap some nasty macros here too... */ +template +auto ppw_core_add_listener(pw_core *core, Args&& ...args) +{ return pw_core_add_listener(core, std::forward(args)...); } +template +auto ppw_core_sync(pw_core *core, Args&& ...args) +{ return pw_core_sync(core, std::forward(args)...); } +template +auto ppw_registry_add_listener(pw_registry *reg, Args&& ...args) +{ return pw_registry_add_listener(reg, std::forward(args)...); } +template +auto ppw_node_add_listener(pw_node *node, Args&& ...args) +{ return pw_node_add_listener(node, std::forward(args)...); } +template +auto ppw_node_subscribe_params(pw_node *node, Args&& ...args) +{ return pw_node_subscribe_params(node, std::forward(args)...); } +template +auto ppw_metadata_add_listener(pw_metadata *mdata, Args&& ...args) +{ return pw_metadata_add_listener(mdata, std::forward(args)...); } + + +constexpr auto get_pod_type(const spa_pod *pod) noexcept +{ return SPA_POD_TYPE(pod); } + +template +constexpr auto get_pod_body(const spa_pod *pod, size_t count) noexcept +{ return al::span{static_cast(SPA_POD_BODY(pod)), count}; } +template +constexpr auto get_pod_body(const spa_pod *pod) noexcept +{ return al::span{static_cast(SPA_POD_BODY(pod)), N}; } + +constexpr auto make_pod_builder(void *data, uint32_t size) noexcept +{ return SPA_POD_BUILDER_INIT(data, size); } + +constexpr auto get_array_value_type(const spa_pod *pod) noexcept +{ return SPA_POD_ARRAY_VALUE_TYPE(pod); } + +constexpr auto PwIdAny = PW_ID_ANY; + +} // namespace +_Pragma("GCC diagnostic pop") + +namespace { + +using std::chrono::seconds; +using std::chrono::nanoseconds; +using uint = unsigned int; + +constexpr char pwireDevice[] = "PipeWire Output"; +constexpr char pwireInput[] = "PipeWire Input"; + + +#ifdef HAVE_DYNLOAD +#define PWIRE_FUNCS(MAGIC) \ + MAGIC(pw_context_connect) \ + MAGIC(pw_context_destroy) \ + MAGIC(pw_context_new) \ + MAGIC(pw_core_disconnect) \ + MAGIC(pw_init) \ + MAGIC(pw_properties_free) \ + MAGIC(pw_properties_new) \ + MAGIC(pw_properties_set) \ + MAGIC(pw_properties_setf) \ + MAGIC(pw_proxy_add_object_listener) \ + MAGIC(pw_proxy_destroy) \ + MAGIC(pw_proxy_get_user_data) \ + MAGIC(pw_stream_add_listener) \ + MAGIC(pw_stream_connect) \ + MAGIC(pw_stream_dequeue_buffer) \ + MAGIC(pw_stream_destroy) \ + MAGIC(pw_stream_get_state) \ + MAGIC(pw_stream_get_time) \ + MAGIC(pw_stream_new) \ + MAGIC(pw_stream_queue_buffer) \ + MAGIC(pw_stream_set_active) \ + MAGIC(pw_thread_loop_new) \ + MAGIC(pw_thread_loop_destroy) \ + MAGIC(pw_thread_loop_get_loop) \ + MAGIC(pw_thread_loop_start) \ + MAGIC(pw_thread_loop_stop) \ + MAGIC(pw_thread_loop_lock) \ + MAGIC(pw_thread_loop_wait) \ + MAGIC(pw_thread_loop_signal) \ + MAGIC(pw_thread_loop_unlock) \ + +void *pwire_handle; +#define MAKE_FUNC(f) decltype(f) * p##f; +PWIRE_FUNCS(MAKE_FUNC) +#undef MAKE_FUNC + +bool pwire_load() +{ + if(pwire_handle) + return true; + + static constexpr char pwire_library[] = "libpipewire-0.3.so.0"; + std::string missing_funcs; + + pwire_handle = LoadLib(pwire_library); + if(!pwire_handle) + { + WARN("Failed to load %s\n", pwire_library); + return false; + } + +#define LOAD_FUNC(f) do { \ + p##f = reinterpret_cast(GetSymbol(pwire_handle, #f)); \ + if(p##f == nullptr) missing_funcs += "\n" #f; \ +} while(0); + PWIRE_FUNCS(LOAD_FUNC) +#undef LOAD_FUNC + + if(!missing_funcs.empty()) + { + WARN("Missing expected functions:%s\n", missing_funcs.c_str()); + CloseLib(pwire_handle); + pwire_handle = nullptr; + return false; + } + + return true; +} + +#ifndef IN_IDE_PARSER +#define pw_context_connect ppw_context_connect +#define pw_context_destroy ppw_context_destroy +#define pw_context_new ppw_context_new +#define pw_core_disconnect ppw_core_disconnect +#define pw_init ppw_init +#define pw_properties_free ppw_properties_free +#define pw_properties_new ppw_properties_new +#define pw_properties_set ppw_properties_set +#define pw_properties_setf ppw_properties_setf +#define pw_proxy_add_object_listener ppw_proxy_add_object_listener +#define pw_proxy_destroy ppw_proxy_destroy +#define pw_proxy_get_user_data ppw_proxy_get_user_data +#define pw_stream_add_listener ppw_stream_add_listener +#define pw_stream_connect ppw_stream_connect +#define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer +#define pw_stream_destroy ppw_stream_destroy +#define pw_stream_get_state ppw_stream_get_state +#define pw_stream_get_time ppw_stream_get_time +#define pw_stream_new ppw_stream_new +#define pw_stream_queue_buffer ppw_stream_queue_buffer +#define pw_stream_set_active ppw_stream_set_active +#define pw_thread_loop_destroy ppw_thread_loop_destroy +#define pw_thread_loop_get_loop ppw_thread_loop_get_loop +#define pw_thread_loop_lock ppw_thread_loop_lock +#define pw_thread_loop_new ppw_thread_loop_new +#define pw_thread_loop_signal ppw_thread_loop_signal +#define pw_thread_loop_start ppw_thread_loop_start +#define pw_thread_loop_stop ppw_thread_loop_stop +#define pw_thread_loop_unlock ppw_thread_loop_unlock +#define pw_thread_loop_wait ppw_thread_loop_wait +#endif + +#else + +constexpr bool pwire_load() { return true; } +#endif + +/* Helpers for retrieving values from params */ +template struct PodInfo { }; + +template<> +struct PodInfo { + using Type = int32_t; + static auto get_value(const spa_pod *pod, int32_t *val) + { return spa_pod_get_int(pod, val); } +}; +template<> +struct PodInfo { + using Type = uint32_t; + static auto get_value(const spa_pod *pod, uint32_t *val) + { return spa_pod_get_id(pod, val); } +}; + +template +using Pod_t = typename PodInfo::Type; + +template +al::span> get_array_span(const spa_pod *pod) +{ + uint32_t nvals; + if(void *v{spa_pod_get_array(pod, &nvals)}) + { + if(get_array_value_type(pod) == T) + return {static_cast*>(v), nvals}; + } + return {}; +} + +template +al::optional> get_value(const spa_pod *value) +{ + Pod_t val{}; + if(PodInfo::get_value(value, &val) == 0) + return val; + return al::nullopt; +} + +/* Internally, PipeWire types "inherit" from each other, but this is hidden + * from the API and the caller is expected to C-style cast to inherited types + * as needed. It's also not made very clear what types a given type can be + * casted to. To make it a bit safer, this as() method allows casting pw_* + * types to known inherited types, generating a compile-time error for + * unexpected/invalid casts. + */ +template +To as(From) noexcept = delete; + +/* pw_proxy + * - pw_registry + * - pw_node + * - pw_metadata + */ +template<> +pw_proxy* as(pw_registry *reg) noexcept { return reinterpret_cast(reg); } +template<> +pw_proxy* as(pw_node *node) noexcept { return reinterpret_cast(node); } +template<> +pw_proxy* as(pw_metadata *mdata) noexcept { return reinterpret_cast(mdata); } + + +struct PwContextDeleter { + void operator()(pw_context *context) const { pw_context_destroy(context); } +}; +using PwContextPtr = std::unique_ptr; + +struct PwCoreDeleter { + void operator()(pw_core *core) const { pw_core_disconnect(core); } +}; +using PwCorePtr = std::unique_ptr; + +struct PwRegistryDeleter { + void operator()(pw_registry *reg) const { pw_proxy_destroy(as(reg)); } +}; +using PwRegistryPtr = std::unique_ptr; + +struct PwNodeDeleter { + void operator()(pw_node *node) const { pw_proxy_destroy(as(node)); } +}; +using PwNodePtr = std::unique_ptr; + +struct PwMetadataDeleter { + void operator()(pw_metadata *mdata) const { pw_proxy_destroy(as(mdata)); } +}; +using PwMetadataPtr = std::unique_ptr; + +struct PwStreamDeleter { + void operator()(pw_stream *stream) const { pw_stream_destroy(stream); } +}; +using PwStreamPtr = std::unique_ptr; + +/* Enums for bitflags... again... *sigh* */ +constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept +{ return static_cast(lhs | std::underlying_type_t{rhs}); } + +class ThreadMainloop { + pw_thread_loop *mLoop{}; + +public: + ThreadMainloop() = default; + ThreadMainloop(const ThreadMainloop&) = delete; + ThreadMainloop(ThreadMainloop&& rhs) noexcept : mLoop{rhs.mLoop} { rhs.mLoop = nullptr; } + explicit ThreadMainloop(pw_thread_loop *loop) noexcept : mLoop{loop} { } + ~ThreadMainloop() { if(mLoop) pw_thread_loop_destroy(mLoop); } + + ThreadMainloop& operator=(const ThreadMainloop&) = delete; + ThreadMainloop& operator=(ThreadMainloop&& rhs) noexcept + { std::swap(mLoop, rhs.mLoop); return *this; } + ThreadMainloop& operator=(std::nullptr_t) noexcept + { + if(mLoop) + pw_thread_loop_destroy(mLoop); + mLoop = nullptr; + return *this; + } + + explicit operator bool() const noexcept { return mLoop != nullptr; } + + auto start() const { return pw_thread_loop_start(mLoop); } + auto stop() const { return pw_thread_loop_stop(mLoop); } + + auto getLoop() const { return pw_thread_loop_get_loop(mLoop); } + + auto lock() const { return pw_thread_loop_lock(mLoop); } + auto unlock() const { return pw_thread_loop_unlock(mLoop); } + + auto signal(bool wait) const { return pw_thread_loop_signal(mLoop, wait); } + + auto newContext(pw_properties *props=nullptr, size_t user_data_size=0) + { return PwContextPtr{pw_context_new(getLoop(), props, user_data_size)}; } + + static auto Create(const char *name, spa_dict *props=nullptr) + { return ThreadMainloop{pw_thread_loop_new(name, props)}; } + + friend struct MainloopUniqueLock; +}; +struct MainloopUniqueLock : public std::unique_lock { + using std::unique_lock::unique_lock; + MainloopUniqueLock& operator=(MainloopUniqueLock&&) = default; + + auto wait() const -> void + { pw_thread_loop_wait(mutex()->mLoop); } + + template + auto wait(Predicate done_waiting) const -> void + { while(!done_waiting()) wait(); } +}; +using MainloopLockGuard = std::lock_guard; + + +/* There's quite a mess here, but the purpose is to track active devices and + * their default formats, so playback devices can be configured to match. The + * device list is updated asynchronously, so it will have the latest list of + * devices provided by the server. + */ + +struct NodeProxy; +struct MetadataProxy; + +/* The global thread watching for global events. This particular class responds + * to objects being added to or removed from the registry. + */ +struct EventManager { + ThreadMainloop mLoop{}; + PwContextPtr mContext{}; + PwCorePtr mCore{}; + PwRegistryPtr mRegistry{}; + spa_hook mRegistryListener{}; + spa_hook mCoreListener{}; + + /* A list of proxy objects watching for events about changes to objects in + * the registry. + */ + std::vector mNodeList; + MetadataProxy *mDefaultMetadata{nullptr}; + + /* Initialization handling. When init() is called, mInitSeq is set to a + * SequenceID that marks the end of populating the registry. As objects of + * interest are found, events to parse them are generated and mInitSeq is + * updated with a newer ID. When mInitSeq stops being updated and the event + * corresponding to it is reached, mInitDone will be set to true. + */ + std::atomic mInitDone{false}; + std::atomic mHasAudio{false}; + int mInitSeq{}; + + bool init(); + ~EventManager(); + + void kill(); + + auto lock() const { return mLoop.lock(); } + auto unlock() const { return mLoop.unlock(); } + + /** + * Waits for initialization to finish. The event manager must *NOT* be + * locked when calling this. + */ + void waitForInit() + { + if(unlikely(!mInitDone.load(std::memory_order_acquire))) + { + MainloopUniqueLock plock{mLoop}; + plock.wait([this](){ return mInitDone.load(std::memory_order_acquire); }); + } + } + + /** + * Waits for audio support to be detected, or initialization to finish, + * whichever is first. Returns true if audio support was detected. The + * event manager must *NOT* be locked when calling this. + */ + bool waitForAudio() + { + MainloopUniqueLock plock{mLoop}; + bool has_audio{}; + plock.wait([this,&has_audio]() + { + has_audio = mHasAudio.load(std::memory_order_acquire); + return has_audio || mInitDone.load(std::memory_order_acquire); + }); + return has_audio; + } + + void syncInit() + { + /* If initialization isn't done, update the sequence ID so it won't + * complete until after currently scheduled events. + */ + if(!mInitDone.load(std::memory_order_relaxed)) + mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, mInitSeq); + } + + void addCallback(uint32_t id, uint32_t permissions, const char *type, uint32_t version, + const spa_dict *props); + static void addCallbackC(void *object, uint32_t id, uint32_t permissions, const char *type, + uint32_t version, const spa_dict *props) + { static_cast(object)->addCallback(id, permissions, type, version, props); } + + void removeCallback(uint32_t id); + static void removeCallbackC(void *object, uint32_t id) + { static_cast(object)->removeCallback(id); } + + static constexpr pw_registry_events CreateRegistryEvents() + { + pw_registry_events ret{}; + ret.version = PW_VERSION_REGISTRY_EVENTS; + ret.global = &EventManager::addCallbackC; + ret.global_remove = &EventManager::removeCallbackC; + return ret; + } + + void coreCallback(uint32_t id, int seq); + static void coreCallbackC(void *object, uint32_t id, int seq) + { static_cast(object)->coreCallback(id, seq); } + + static constexpr pw_core_events CreateCoreEvents() + { + pw_core_events ret{}; + ret.version = PW_VERSION_CORE_EVENTS; + ret.done = &EventManager::coreCallbackC; + return ret; + } +}; +using EventWatcherUniqueLock = std::unique_lock; +using EventWatcherLockGuard = std::lock_guard; + +EventManager gEventHandler; + +/* Enumerated devices. This is updated asynchronously as the app runs, and the + * gEventHandler thread loop must be locked when accessing the list. + */ +enum class NodeType : unsigned char { + Sink, Source, Duplex +}; +constexpr auto InvalidChannelConfig = DevFmtChannels(255); +struct DeviceNode { + std::string mName; + std::string mDevName; + + uint32_t mId{}; + NodeType mType{}; + bool mIsHeadphones{}; + bool mIs51Rear{}; + + uint mSampleRate{}; + DevFmtChannels mChannels{InvalidChannelConfig}; + + static std::vector sList; + static DeviceNode &Add(uint32_t id); + static DeviceNode *Find(uint32_t id); + static void Remove(uint32_t id); + static std::vector &GetList() noexcept { return sList; } + + void parseSampleRate(const spa_pod *value) noexcept; + void parsePositions(const spa_pod *value) noexcept; + void parseChannelCount(const spa_pod *value) noexcept; +}; +std::vector DeviceNode::sList; +std::string DefaultSinkDevice; +std::string DefaultSourceDevice; + +const char *AsString(NodeType type) noexcept +{ + switch(type) + { + case NodeType::Sink: return "sink"; + case NodeType::Source: return "source"; + case NodeType::Duplex: return "duplex"; + } + return ""; +} + +DeviceNode &DeviceNode::Add(uint32_t id) +{ + auto match_id = [id](DeviceNode &n) noexcept -> bool + { return n.mId == id; }; + + /* If the node is already in the list, return the existing entry. */ + auto match = std::find_if(sList.begin(), sList.end(), match_id); + if(match != sList.end()) return *match; + + sList.emplace_back(); + auto &n = sList.back(); + n.mId = id; + return n; +} + +DeviceNode *DeviceNode::Find(uint32_t id) +{ + auto match_id = [id](DeviceNode &n) noexcept -> bool + { return n.mId == id; }; + + auto match = std::find_if(sList.begin(), sList.end(), match_id); + if(match != sList.end()) return std::addressof(*match); + + return nullptr; +} + +void DeviceNode::Remove(uint32_t id) +{ + auto match_id = [id](DeviceNode &n) noexcept -> bool + { + if(n.mId != id) + return false; + TRACE("Removing device \"%s\"\n", n.mDevName.c_str()); + return true; + }; + + auto end = std::remove_if(sList.begin(), sList.end(), match_id); + sList.erase(end, sList.end()); +} + + +const spa_audio_channel MonoMap[]{ + SPA_AUDIO_CHANNEL_MONO +}, StereoMap[] { + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR +}, QuadMap[]{ + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR +}, X51Map[]{ + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR +}, X51RearMap[]{ + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR +}, X61Map[]{ + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR +}, X71Map[]{ + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR +}; + +/** + * Checks if every channel in 'map1' exists in 'map0' (that is, map0 is equal + * to or a superset of map1). + */ +template +bool MatchChannelMap(const al::span map0, const spa_audio_channel (&map1)[N]) +{ + if(map0.size() < N) + return false; + for(const spa_audio_channel chid : map1) + { + if(std::find(map0.begin(), map0.end(), chid) == map0.end()) + return false; + } + return true; +} + +void DeviceNode::parseSampleRate(const spa_pod *value) noexcept +{ + /* TODO: Can this be anything else? Long, Float, Double? */ + uint32_t nvals{}, choiceType{}; + value = spa_pod_get_values(value, &nvals, &choiceType); + + const uint podType{get_pod_type(value)}; + if(podType != SPA_TYPE_Int) + { + WARN("Unhandled sample rate POD type: %u\n", podType); + return; + } + + if(choiceType == SPA_CHOICE_Range) + { + if(nvals != 3) + { + WARN("Unexpected SPA_CHOICE_Range count: %u\n", nvals); + return; + } + auto srates = get_pod_body(value); + + /* [0] is the default, [1] is the min, and [2] is the max. */ + TRACE("Device ID %u sample rate: %d (range: %d -> %d)\n", mId, srates[0], srates[1], + srates[2]); + mSampleRate = static_cast(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)); + return; + } + + if(choiceType == SPA_CHOICE_Enum) + { + if(nvals == 0) + { + WARN("Unexpected SPA_CHOICE_Enum count: %u\n", nvals); + return; + } + auto srates = get_pod_body(value, nvals); + + /* [0] is the default, [1...size()-1] are available selections. */ + std::string others{(srates.size() > 1) ? std::to_string(srates[1]) : std::string{}}; + for(size_t i{2};i < srates.size();++i) + { + others += ", "; + others += std::to_string(srates[i]); + } + TRACE("Device ID %u sample rate: %d (%s)\n", mId, srates[0], others.c_str()); + /* Pick the first rate listed that's within the allowed range (default + * rate if possible). + */ + for(const auto &rate : srates) + { + if(rate >= MIN_OUTPUT_RATE && rate <= MAX_OUTPUT_RATE) + { + mSampleRate = static_cast(rate); + break; + } + } + return; + } + + if(choiceType == SPA_CHOICE_None) + { + if(nvals != 1) + { + WARN("Unexpected SPA_CHOICE_None count: %u\n", nvals); + return; + } + auto srates = get_pod_body(value); + + TRACE("Device ID %u sample rate: %d\n", mId, srates[0]); + mSampleRate = static_cast(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)); + return; + } + + WARN("Unhandled sample rate choice type: %u\n", choiceType); +} + +void DeviceNode::parsePositions(const spa_pod *value) noexcept +{ + const auto chanmap = get_array_span(value); + if(chanmap.empty()) return; + + mIs51Rear = false; + + if(MatchChannelMap(chanmap, X71Map)) + mChannels = DevFmtX71; + else if(MatchChannelMap(chanmap, X61Map)) + mChannels = DevFmtX61; + else if(MatchChannelMap(chanmap, X51Map)) + mChannels = DevFmtX51; + else if(MatchChannelMap(chanmap, X51RearMap)) + { + mChannels = DevFmtX51; + mIs51Rear = true; + } + else if(MatchChannelMap(chanmap, QuadMap)) + mChannels = DevFmtQuad; + else if(MatchChannelMap(chanmap, StereoMap)) + mChannels = DevFmtStereo; + else + mChannels = DevFmtMono; + TRACE("Device ID %u got %zu position%s for %s%s\n", mId, chanmap.size(), + (chanmap.size()==1)?"":"s", DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":""); +} + +void DeviceNode::parseChannelCount(const spa_pod *value) noexcept +{ + /* As a fallback with just a channel count, just assume mono or stereo. */ + const auto chancount = get_value(value); + if(!chancount) return; + + mIs51Rear = false; + + if(*chancount >= 2) + mChannels = DevFmtStereo; + else if(*chancount >= 1) + mChannels = DevFmtMono; + TRACE("Device ID %u got %d channel%s for %s\n", mId, *chancount, (*chancount==1)?"":"s", + DevFmtChannelsString(mChannels)); +} + + +constexpr char MonitorPrefix[]{"Monitor of "}; +constexpr auto MonitorPrefixLen = al::size(MonitorPrefix) - 1; +constexpr char AudioSinkClass[]{"Audio/Sink"}; +constexpr char AudioSourceClass[]{"Audio/Source"}; +constexpr char AudioDuplexClass[]{"Audio/Duplex"}; +constexpr char StreamClass[]{"Stream/"}; + +/* A generic PipeWire node proxy object used to track changes to sink and + * source nodes. + */ +struct NodeProxy { + static constexpr pw_node_events CreateNodeEvents() + { + pw_node_events ret{}; + ret.version = PW_VERSION_NODE_EVENTS; + ret.info = &NodeProxy::infoCallbackC; + ret.param = &NodeProxy::paramCallbackC; + return ret; + } + + uint32_t mId{}; + + PwNodePtr mNode{}; + spa_hook mListener{}; + + NodeProxy(uint32_t id, PwNodePtr node) + : mId{id}, mNode{std::move(node)} + { + static constexpr pw_node_events nodeEvents{CreateNodeEvents()}; + ppw_node_add_listener(mNode.get(), &mListener, &nodeEvents, this); + + /* Track changes to the enumerable formats (indicates the default + * format, which is what we're interested in). + */ + uint32_t fmtids[]{SPA_PARAM_EnumFormat}; + ppw_node_subscribe_params(mNode.get(), al::data(fmtids), al::size(fmtids)); + } + ~NodeProxy() + { spa_hook_remove(&mListener); } + + + void infoCallback(const pw_node_info *info); + static void infoCallbackC(void *object, const pw_node_info *info) + { static_cast(object)->infoCallback(info); } + + void paramCallback(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param); + static void paramCallbackC(void *object, int seq, uint32_t id, uint32_t index, uint32_t next, + const spa_pod *param) + { static_cast(object)->paramCallback(seq, id, index, next, param); } +}; + +void NodeProxy::infoCallback(const pw_node_info *info) +{ + /* We only care about property changes here (media class, name/desc). + * Format changes will automatically invoke the param callback. + * + * TODO: Can the media class or name/desc change without being removed and + * readded? + */ + if((info->change_mask&PW_NODE_CHANGE_MASK_PROPS)) + { + /* Can this actually change? */ + const char *media_class{spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS)}; + if(unlikely(!media_class)) return; + + NodeType ntype{}; + if(al::strcasecmp(media_class, AudioSinkClass) == 0) + ntype = NodeType::Sink; + else if(al::strcasecmp(media_class, AudioSourceClass) == 0) + ntype = NodeType::Source; + else if(al::strcasecmp(media_class, AudioDuplexClass) == 0) + ntype = NodeType::Duplex; + else + { + TRACE("Dropping device node %u which became type \"%s\"\n", info->id, media_class); + DeviceNode::Remove(info->id); + return; + } + + const char *devName{spa_dict_lookup(info->props, PW_KEY_NODE_NAME)}; + const char *nodeName{spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION)}; + if(!nodeName || !*nodeName) nodeName = spa_dict_lookup(info->props, PW_KEY_NODE_NICK); + if(!nodeName || !*nodeName) nodeName = devName; + + const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)}; + TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)", + form_factor?" (":"", form_factor?form_factor:"", form_factor?")":""); + TRACE(" \"%s\" = ID %u\n", nodeName ? nodeName : "(nil)", info->id); + + DeviceNode &node = DeviceNode::Add(info->id); + if(nodeName && *nodeName) node.mName = nodeName; + else node.mName = "PipeWire node #"+std::to_string(info->id); + node.mDevName = devName ? devName : ""; + node.mType = ntype; + node.mIsHeadphones = form_factor && (al::strcasecmp(form_factor, "headphones") == 0 + || al::strcasecmp(form_factor, "headset") == 0); + } +} + +void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_pod *param) +{ + if(id == SPA_PARAM_EnumFormat) + { + DeviceNode *node{DeviceNode::Find(mId)}; + if(unlikely(!node)) return; + + if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)}) + node->parseSampleRate(&prop->value); + + if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_position)}) + node->parsePositions(&prop->value); + else if((prop=spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_channels)) != nullptr) + node->parseChannelCount(&prop->value); + } +} + + +/* A metadata proxy object used to query the default sink and source. */ +struct MetadataProxy { + static constexpr pw_metadata_events CreateMetadataEvents() + { + pw_metadata_events ret{}; + ret.version = PW_VERSION_METADATA_EVENTS; + ret.property = &MetadataProxy::propertyCallbackC; + return ret; + } + + uint32_t mId{}; + + PwMetadataPtr mMetadata{}; + spa_hook mListener{}; + + MetadataProxy(uint32_t id, PwMetadataPtr mdata) + : mId{id}, mMetadata{std::move(mdata)} + { + static constexpr pw_metadata_events metadataEvents{CreateMetadataEvents()}; + ppw_metadata_add_listener(mMetadata.get(), &mListener, &metadataEvents, this); + } + ~MetadataProxy() + { spa_hook_remove(&mListener); } + + + int propertyCallback(uint32_t id, const char *key, const char *type, const char *value); + static int propertyCallbackC(void *object, uint32_t id, const char *key, const char *type, + const char *value) + { return static_cast(object)->propertyCallback(id, key, type, value); } +}; + +int MetadataProxy::propertyCallback(uint32_t id, const char *key, const char *type, + const char *value) +{ + if(id != PW_ID_CORE) + return 0; + + bool isCapture{}; + if(std::strcmp(key, "default.audio.sink") == 0) + isCapture = false; + else if(std::strcmp(key, "default.audio.source") == 0) + isCapture = true; + else + return 0; + + if(!type) + { + TRACE("Default %s device cleared\n", isCapture ? "capture" : "playback"); + if(!isCapture) DefaultSinkDevice.clear(); + else DefaultSourceDevice.clear(); + return 0; + } + if(std::strcmp(type, "Spa:String:JSON") != 0) + { + ERR("Unexpected %s property type: %s\n", key, type); + return 0; + } + + spa_json it[2]{}; + spa_json_init(&it[0], value, strlen(value)); + if(spa_json_enter_object(&it[0], &it[1]) <= 0) + return 0; + + auto get_json_string = [](spa_json *iter) + { + al::optional str; + + const char *val{}; + int len{spa_json_next(iter, &val)}; + if(len <= 0) return str; + + str.emplace().resize(static_cast(len), '\0'); + if(spa_json_parse_string(val, len, &str->front()) <= 0) + str.reset(); + else while(!str->empty() && str->back() == '\0') + str->pop_back(); + return str; + }; + while(auto propKey = get_json_string(&it[1])) + { + if(*propKey == "name") + { + auto propValue = get_json_string(&it[1]); + if(!propValue) break; + + TRACE("Got default %s device \"%s\"\n", isCapture ? "capture" : "playback", + propValue->c_str()); + if(!isCapture) + DefaultSinkDevice = std::move(*propValue); + else + DefaultSourceDevice = std::move(*propValue); + } + else + { + const char *v{}; + if(spa_json_next(&it[1], &v) <= 0) + break; + } + } + return 0; +} + + +bool EventManager::init() +{ + mLoop = ThreadMainloop::Create("PWEventThread"); + if(!mLoop) + { + ERR("Failed to create PipeWire event thread loop (errno: %d)\n", errno); + return false; + } + + mContext = mLoop.newContext(pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)); + if(!mContext) + { + ERR("Failed to create PipeWire event context (errno: %d)\n", errno); + return false; + } + + mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)}; + if(!mCore) + { + ERR("Failed to connect PipeWire event context (errno: %d)\n", errno); + return false; + } + + mRegistry = PwRegistryPtr{pw_core_get_registry(mCore.get(), PW_VERSION_REGISTRY, 0)}; + if(!mRegistry) + { + ERR("Failed to get PipeWire event registry (errno: %d)\n", errno); + return false; + } + + static constexpr pw_core_events coreEvents{CreateCoreEvents()}; + static constexpr pw_registry_events registryEvents{CreateRegistryEvents()}; + + ppw_core_add_listener(mCore.get(), &mCoreListener, &coreEvents, this); + ppw_registry_add_listener(mRegistry.get(), &mRegistryListener, ®istryEvents, this); + + /* Set an initial sequence ID for initialization, to trigger after the + * registry is first populated. + */ + mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, 0); + + if(int res{mLoop.start()}) + { + ERR("Failed to start PipeWire event thread loop (res: %d)\n", res); + return false; + } + + return true; +} + +EventManager::~EventManager() +{ + if(mLoop) mLoop.stop(); + + for(NodeProxy *node : mNodeList) + al::destroy_at(node); + if(mDefaultMetadata) + al::destroy_at(mDefaultMetadata); +} + +void EventManager::kill() +{ + if(mLoop) mLoop.stop(); + + for(NodeProxy *node : mNodeList) + al::destroy_at(node); + mNodeList.clear(); + if(mDefaultMetadata) + al::destroy_at(mDefaultMetadata); + mDefaultMetadata = nullptr; + + mRegistry = nullptr; + mCore = nullptr; + mContext = nullptr; + mLoop = nullptr; +} + +void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t version, + const spa_dict *props) +{ + /* We're only interested in interface nodes. */ + if(std::strcmp(type, PW_TYPE_INTERFACE_Node) == 0) + { + const char *media_class{spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)}; + if(!media_class) return; + + /* Specifically, audio sinks and sources (and duplexes). */ + const bool isGood{al::strcasecmp(media_class, AudioSinkClass) == 0 + || al::strcasecmp(media_class, AudioSourceClass) == 0 + || al::strcasecmp(media_class, AudioDuplexClass) == 0}; + if(!isGood) + { + if(std::strstr(media_class, "/Video") == nullptr + && std::strncmp(media_class, StreamClass, sizeof(StreamClass)-1) != 0) + TRACE("Ignoring node class %s\n", media_class); + return; + } + + /* Create the proxy object. */ + auto node = PwNodePtr{static_cast(pw_registry_bind(mRegistry.get(), id, type, + version, sizeof(NodeProxy)))}; + if(!node) + { + ERR("Failed to create node proxy object (errno: %d)\n", errno); + return; + } + + /* Initialize the NodeProxy to hold the node object, add it to the + * active node list, and update the sync point. + */ + auto *proxy = static_cast(pw_proxy_get_user_data(as(node.get()))); + mNodeList.emplace_back(al::construct_at(proxy, id, std::move(node))); + syncInit(); + + /* Signal any waiters that we have found a source or sink for audio + * support. + */ + if(!mHasAudio.exchange(true, std::memory_order_acq_rel)) + mLoop.signal(false); + } + else if(std::strcmp(type, PW_TYPE_INTERFACE_Metadata) == 0) + { + const char *data_class{spa_dict_lookup(props, PW_KEY_METADATA_NAME)}; + if(!data_class) return; + + if(std::strcmp(data_class, "default") != 0) + { + TRACE("Ignoring metadata \"%s\"\n", data_class); + return; + } + + if(mDefaultMetadata) + { + ERR("Duplicate default metadata\n"); + return; + } + + auto mdata = PwMetadataPtr{static_cast(pw_registry_bind(mRegistry.get(), id, + type, version, sizeof(MetadataProxy)))}; + if(!mdata) + { + ERR("Failed to create metadata proxy object (errno: %d)\n", errno); + return; + } + + auto *proxy = static_cast( + pw_proxy_get_user_data(as(mdata.get()))); + mDefaultMetadata = al::construct_at(proxy, id, std::move(mdata)); + syncInit(); + } +} + +void EventManager::removeCallback(uint32_t id) +{ + DeviceNode::Remove(id); + + auto clear_node = [id](NodeProxy *node) noexcept + { + if(node->mId != id) + return false; + al::destroy_at(node); + return true; + }; + auto node_end = std::remove_if(mNodeList.begin(), mNodeList.end(), clear_node); + mNodeList.erase(node_end, mNodeList.end()); + + if(mDefaultMetadata && mDefaultMetadata->mId == id) + { + al::destroy_at(mDefaultMetadata); + mDefaultMetadata = nullptr; + } +} + +void EventManager::coreCallback(uint32_t id, int seq) +{ + if(id == PW_ID_CORE && seq == mInitSeq) + { + /* Initialization done. Remove this callback and signal anyone that may + * be waiting. + */ + spa_hook_remove(&mCoreListener); + + mInitDone.store(true); + mLoop.signal(false); + } +} + + +enum use_f32p_e : bool { UseDevType=false, ForceF32Planar=true }; +spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e use_f32p) +{ + spa_audio_info_raw info{}; + if(use_f32p) + { + device->FmtType = DevFmtFloat; + info.format = SPA_AUDIO_FORMAT_F32P; + } + else switch(device->FmtType) + { + case DevFmtByte: info.format = SPA_AUDIO_FORMAT_S8; break; + case DevFmtUByte: info.format = SPA_AUDIO_FORMAT_U8; break; + case DevFmtShort: info.format = SPA_AUDIO_FORMAT_S16; break; + case DevFmtUShort: info.format = SPA_AUDIO_FORMAT_U16; break; + case DevFmtInt: info.format = SPA_AUDIO_FORMAT_S32; break; + case DevFmtUInt: info.format = SPA_AUDIO_FORMAT_U32; break; + case DevFmtFloat: info.format = SPA_AUDIO_FORMAT_F32; break; + } + + info.rate = device->Frequency; + + al::span map{}; + switch(device->FmtChans) + { + case DevFmtMono: map = MonoMap; break; + case DevFmtStereo: map = StereoMap; break; + case DevFmtQuad: map = QuadMap; break; + case DevFmtX51: + if(is51rear) map = X51RearMap; + else map = X51Map; + break; + case DevFmtX61: map = X61Map; break; + case DevFmtX71: map = X71Map; break; + case DevFmtAmbi3D: + info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED; + info.channels = device->channelsFromFmt(); + break; + } + if(!map.empty()) + { + info.channels = static_cast(map.size()); + std::copy(map.begin(), map.end(), info.position); + } + + return info; +} + +class PipeWirePlayback final : public BackendBase { + void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error); + static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state, + const char *error) + { static_cast(data)->stateChangedCallback(old, state, error); } + + void ioChangedCallback(uint32_t id, void *area, uint32_t size); + static void ioChangedCallbackC(void *data, uint32_t id, void *area, uint32_t size) + { static_cast(data)->ioChangedCallback(id, area, size); } + + void outputCallback(); + static void outputCallbackC(void *data) + { static_cast(data)->outputCallback(); } + + void open(const char *name) override; + bool reset() override; + void start() override; + void stop() override; + ClockLatency getClockLatency() override; + + uint32_t mTargetId{PwIdAny}; + nanoseconds mTimeBase{0}; + ThreadMainloop mLoop; + PwContextPtr mContext; + PwCorePtr mCore; + PwStreamPtr mStream; + spa_hook mStreamListener{}; + spa_io_rate_match *mRateMatch{}; + std::unique_ptr mChannelPtrs; + uint mNumChannels{}; + + static constexpr pw_stream_events CreateEvents() + { + pw_stream_events ret{}; + ret.version = PW_VERSION_STREAM_EVENTS; + ret.state_changed = &PipeWirePlayback::stateChangedCallbackC; + ret.io_changed = &PipeWirePlayback::ioChangedCallbackC; + ret.process = &PipeWirePlayback::outputCallbackC; + return ret; + } + +public: + PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { } + ~PipeWirePlayback() + { + /* Stop the mainloop so the stream can be properly destroyed. */ + if(mLoop) mLoop.stop(); + } + + DEF_NEWDEL(PipeWirePlayback) +}; + + +void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) +{ mLoop.signal(false); } + +void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size) +{ + switch(id) + { + case SPA_IO_RateMatch: + if(size >= sizeof(spa_io_rate_match)) + mRateMatch = static_cast(area); + break; + } +} + +void PipeWirePlayback::outputCallback() +{ + pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())}; + if(unlikely(!pw_buf)) return; + + /* For planar formats, each datas[] seems to contain one channel, so store + * the pointers in an array. Limit the render length in case the available + * buffer length in any one channel is smaller than we wanted (shouldn't + * be, but just in case). + */ + spa_data *datas{pw_buf->buffer->datas}; + const size_t chancount{minu(mNumChannels, pw_buf->buffer->n_datas)}; + /* TODO: How many samples should actually be written? 'maxsize' can be 16k + * samples, which is excessive (~341ms @ 48khz). SPA_IO_RateMatch contains + * a 'size' field that apparently indicates how many samples should be + * written per update, but it's not obviously right. + */ + uint length{mRateMatch ? mRateMatch->size : mDevice->UpdateSize}; + for(size_t i{0};i < chancount;++i) + { + length = minu(length, datas[i].maxsize/sizeof(float)); + mChannelPtrs[i] = static_cast(datas[i].data); + } + + mDevice->renderSamples({mChannelPtrs.get(), chancount}, length); + + for(size_t i{0};i < chancount;++i) + { + datas[i].chunk->offset = 0; + datas[i].chunk->stride = sizeof(float); + datas[i].chunk->size = length * sizeof(float); + } + pw_buf->size = length; + pw_stream_queue_buffer(mStream.get(), pw_buf); +} + + +void PipeWirePlayback::open(const char *name) +{ + static std::atomic OpenCount{0}; + + uint32_t targetid{PwIdAny}; + std::string devname{}; + gEventHandler.waitForInit(); + if(!name) + { + EventWatcherLockGuard _{gEventHandler}; + auto&& devlist = DeviceNode::GetList(); + + auto match = devlist.cend(); + if(!DefaultSinkDevice.empty()) + { + auto match_default = [](const DeviceNode &n) -> bool + { return n.mDevName == DefaultSinkDevice; }; + match = std::find_if(devlist.cbegin(), devlist.cend(), match_default); + } + if(match == devlist.cend()) + { + auto match_playback = [](const DeviceNode &n) -> bool + { return n.mType != NodeType::Source; }; + match = std::find_if(devlist.cbegin(), devlist.cend(), match_playback); + if(match == devlist.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "No PipeWire playback device found"}; + } + + targetid = match->mId; + devname = match->mName; + } + else + { + EventWatcherLockGuard _{gEventHandler}; + auto&& devlist = DeviceNode::GetList(); + + auto match_name = [name](const DeviceNode &n) -> bool + { return n.mType != NodeType::Source && n.mName == name; }; + auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name); + if(match == devlist.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + + targetid = match->mId; + devname = match->mName; + } + + if(!mLoop) + { + const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)}; + const std::string thread_name{"ALSoftP" + std::to_string(count)}; + mLoop = ThreadMainloop::Create(thread_name.c_str()); + if(!mLoop) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create PipeWire mainloop (errno: %d)", errno}; + if(int res{mLoop.start()}) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start PipeWire mainloop (res: %d)", res}; + } + MainloopUniqueLock mlock{mLoop}; + if(!mContext) + { + pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)}; + mContext = mLoop.newContext(cprops); + if(!mContext) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create PipeWire event context (errno: %d)\n", errno}; + } + if(!mCore) + { + mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)}; + if(!mCore) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to connect PipeWire event context (errno: %d)\n", errno}; + } + mlock.unlock(); + + /* TODO: Ensure the target ID is still valid/usable and accepts streams. */ + + mTargetId = targetid; + if(!devname.empty()) + mDevice->DeviceName = std::move(devname); + else + mDevice->DeviceName = pwireDevice; +} + +bool PipeWirePlayback::reset() +{ + if(mStream) + { + MainloopLockGuard _{mLoop}; + mStream = nullptr; + } + mStreamListener = {}; + mRateMatch = nullptr; + mTimeBase = GetDeviceClockTime(mDevice); + + /* If connecting to a specific device, update various device parameters to + * match its format. + */ + bool is51rear{false}; + mDevice->Flags.reset(DirectEar); + if(mTargetId != PwIdAny) + { + EventWatcherLockGuard _{gEventHandler}; + auto&& devlist = DeviceNode::GetList(); + + auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool + { return targetid == n.mId; }; + auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id); + if(match != devlist.cend()) + { + if(!mDevice->Flags.test(FrequencyRequest) && match->mSampleRate > 0) + { + /* Scale the update size if the sample rate changes. */ + const double scale{static_cast(match->mSampleRate) / mDevice->Frequency}; + mDevice->Frequency = match->mSampleRate; + mDevice->UpdateSize = static_cast(clampd(mDevice->UpdateSize*scale + 0.5, + 64.0, 8192.0)); + mDevice->BufferSize = mDevice->UpdateSize * 2; + } + if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig) + mDevice->FmtChans = match->mChannels; + if(match->mChannels == DevFmtStereo && match->mIsHeadphones) + mDevice->Flags.set(DirectEar); + is51rear = match->mIs51Rear; + } + } + /* Force planar 32-bit float output for playback. This is what PipeWire + * handles internally, and it's easier for us too. + */ + spa_audio_info_raw info{make_spa_info(mDevice, is51rear, ForceF32Planar)}; + + /* TODO: How to tell what an appropriate size is? Examples just use this + * magic value. + */ + constexpr uint32_t pod_buffer_size{1024}; + auto pod_buffer = std::make_unique(pod_buffer_size); + spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)}; + + const spa_pod *params{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)}; + if(!params) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set PipeWire audio format parameters"}; + + pw_properties *props{pw_properties_new( + PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Playback", + PW_KEY_MEDIA_ROLE, "Game", + PW_KEY_NODE_ALWAYS_PROCESS, "true", + nullptr)}; + if(!props) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create PipeWire stream properties (errno: %d)", errno}; + + auto&& binary = GetProcBinary(); + const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; + /* TODO: Which properties are actually needed here? Any others that could + * be useful? + */ + pw_properties_set(props, PW_KEY_NODE_NAME, appname); + pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, appname); + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->UpdateSize, + mDevice->Frequency); + pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency); + + MainloopUniqueLock plock{mLoop}; + /* The stream takes overship of 'props', even in the case of failure. */ + mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Playback Stream", props)}; + if(!mStream) + throw al::backend_exception{al::backend_error::NoDevice, + "Failed to create PipeWire stream (errno: %d)", errno}; + static constexpr pw_stream_events streamEvents{CreateEvents()}; + pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this); + + constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE + | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS}; + if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, mTargetId, Flags, ¶ms, 1)}) + throw al::backend_exception{al::backend_error::DeviceError, + "Error connecting PipeWire stream (res: %d)", res}; + + /* Wait for the stream to become paused (ready to start streaming). */ + pw_stream_state state{}; + const char *error{}; + plock.wait([stream=mStream.get(),&state,&error]() + { + state = pw_stream_get_state(stream, &error); + if(state == PW_STREAM_STATE_ERROR) + throw al::backend_exception{al::backend_error::DeviceError, + "Error connecting PipeWire stream: \"%s\"", error}; + return state == PW_STREAM_STATE_PAUSED; + }); + + /* TODO: Update mDevice->BufferSize with the total known buffering delay + * from the head of this playback stream to the tail of the device output. + */ + mDevice->BufferSize = mDevice->UpdateSize * 2; + plock.unlock(); + + mNumChannels = mDevice->channelsFromFmt(); + mChannelPtrs = std::make_unique(mNumChannels); + + setDefaultWFXChannelOrder(); + + return true; +} + +void PipeWirePlayback::start() +{ + MainloopUniqueLock plock{mLoop}; + if(int res{pw_stream_set_active(mStream.get(), true)}) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start PipeWire stream (res: %d)", res}; + + /* Wait for the stream to start playing (would be nice to not, but we need + * the actual update size which is only available after starting). + */ + pw_stream_state state{}; + const char *error{}; + plock.wait([stream=mStream.get(),&state,&error]() + { + state = pw_stream_get_state(stream, &error); + return state != PW_STREAM_STATE_PAUSED; + }); + + if(state == PW_STREAM_STATE_ERROR) + throw al::backend_exception{al::backend_error::DeviceError, + "PipeWire stream error: %s", error ? error : "(unknown)"}; + if(state == PW_STREAM_STATE_STREAMING && mRateMatch && mRateMatch->size) + { + mDevice->UpdateSize = mRateMatch->size; + mDevice->BufferSize = mDevice->UpdateSize * 2; + } +} + +void PipeWirePlayback::stop() +{ + MainloopUniqueLock plock{mLoop}; + if(int res{pw_stream_set_active(mStream.get(), false)}) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to stop PipeWire stream (res: %d)", res}; + + /* Wait for the stream to stop playing. */ + plock.wait([stream=mStream.get()]() + { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; }); +} + +ClockLatency PipeWirePlayback::getClockLatency() +{ + /* Given a real-time low-latency output, this is rather complicated to get + * accurate timing. So, here we go. + */ + + /* First, get the stream time info (tick delay, ticks played, and the + * CLOCK_MONOTONIC time closest to when that last tick was played). + */ + pw_time ptime{}; + if(mStream) + { + MainloopLockGuard _{mLoop}; + if(int res{pw_stream_get_time(mStream.get(), &ptime)}) + ERR("Failed to get PipeWire stream time (res: %d)\n", res); + } + + /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the + * monotonic clock closest to 'now', and the last mixer time at 'now'). + */ + nanoseconds mixtime{}; + timespec tspec{}; + uint refcount; + do { + refcount = mDevice->waitForMix(); + mixtime = GetDeviceClockTime(mDevice); + clock_gettime(CLOCK_MONOTONIC, &tspec); + std::atomic_thread_fence(std::memory_order_acquire); + } while(refcount != ReadRef(mDevice->MixCount)); + + /* Convert the monotonic clock, stream ticks, and stream delay to + * nanoseconds. + */ + nanoseconds monoclock{seconds{tspec.tv_sec} + nanoseconds{tspec.tv_nsec}}; + nanoseconds curtic{}, delay{}; + if(unlikely(ptime.rate.denom < 1)) + { + /* If there's no stream rate, the stream hasn't had a chance to get + * going and return time info yet. Just use dummy values. + */ + ptime.now = monoclock.count(); + curtic = mixtime; + delay = nanoseconds{seconds{mDevice->BufferSize}} / mDevice->Frequency; + } + else + { + /* The stream gets recreated with each reset, so include the time that + * had already passed with previous streams. + */ + curtic = mTimeBase; + /* More safely scale the ticks to avoid overflowing the pre-division + * temporary as it gets larger. + */ + curtic += seconds{ptime.ticks / ptime.rate.denom} * ptime.rate.num; + curtic += nanoseconds{seconds{ptime.ticks%ptime.rate.denom} * ptime.rate.num} / + ptime.rate.denom; + + /* The delay should be small enough to not worry about overflow. */ + delay = nanoseconds{seconds{ptime.delay} * ptime.rate.num} / ptime.rate.denom; + } + + /* If the mixer time is ahead of the stream time, there's that much more + * delay relative to the stream delay. + */ + if(mixtime > curtic) + delay += mixtime - curtic; + /* Reduce the delay according to how much time has passed since the known + * stream time. This isn't 100% accurate since the system monotonic clock + * doesn't tick at the exact same rate as the audio device, but it should + * be good enough with ptime.now being constantly updated every few + * milliseconds with ptime.ticks. + */ + delay -= monoclock - nanoseconds{ptime.now}; + + /* Return the mixer time and delay. Clamp the delay to no less than 0, + * incase timer drift got that severe. + */ + ClockLatency ret{}; + ret.ClockTime = mixtime; + ret.Latency = std::max(delay, nanoseconds{}); + + return ret; +} + + +class PipeWireCapture final : public BackendBase { + void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error); + static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state, + const char *error) + { static_cast(data)->stateChangedCallback(old, state, error); } + + void inputCallback(); + static void inputCallbackC(void *data) + { static_cast(data)->inputCallback(); } + + void open(const char *name) override; + void start() override; + void stop() override; + void captureSamples(al::byte *buffer, uint samples) override; + uint availableSamples() override; + + uint32_t mTargetId{PwIdAny}; + ThreadMainloop mLoop; + PwContextPtr mContext; + PwCorePtr mCore; + PwStreamPtr mStream; + spa_hook mStreamListener{}; + + RingBufferPtr mRing{}; + + static constexpr pw_stream_events CreateEvents() + { + pw_stream_events ret{}; + ret.version = PW_VERSION_STREAM_EVENTS; + ret.state_changed = &PipeWireCapture::stateChangedCallbackC; + ret.process = &PipeWireCapture::inputCallbackC; + return ret; + } + +public: + PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { } + ~PipeWireCapture() { if(mLoop) mLoop.stop(); } + + DEF_NEWDEL(PipeWireCapture) +}; + + +void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) +{ mLoop.signal(false); } + +void PipeWireCapture::inputCallback() +{ + pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())}; + if(unlikely(!pw_buf)) return; + + spa_data *bufdata{pw_buf->buffer->datas}; + const uint offset{minu(bufdata->chunk->offset, bufdata->maxsize)}; + const uint size{minu(bufdata->chunk->size, bufdata->maxsize - offset)}; + + mRing->write(static_cast(bufdata->data) + offset, size / mRing->getElemSize()); + + pw_stream_queue_buffer(mStream.get(), pw_buf); +} + + +void PipeWireCapture::open(const char *name) +{ + static std::atomic OpenCount{0}; + + uint32_t targetid{PwIdAny}; + std::string devname{}; + gEventHandler.waitForInit(); + if(!name) + { + EventWatcherLockGuard _{gEventHandler}; + auto&& devlist = DeviceNode::GetList(); + + auto match = devlist.cend(); + if(!DefaultSourceDevice.empty()) + { + auto match_default = [](const DeviceNode &n) -> bool + { return n.mDevName == DefaultSourceDevice; }; + match = std::find_if(devlist.cbegin(), devlist.cend(), match_default); + } + if(match == devlist.cend()) + { + auto match_capture = [](const DeviceNode &n) -> bool + { return n.mType != NodeType::Sink; }; + match = std::find_if(devlist.cbegin(), devlist.cend(), match_capture); + } + if(match == devlist.cend()) + { + match = devlist.cbegin(); + if(match == devlist.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "No PipeWire capture device found"}; + } + + targetid = match->mId; + if(match->mType != NodeType::Sink) devname = match->mName; + else devname = MonitorPrefix+match->mName; + } + else + { + EventWatcherLockGuard _{gEventHandler}; + auto&& devlist = DeviceNode::GetList(); + + auto match_name = [name](const DeviceNode &n) -> bool + { return n.mType != NodeType::Sink && n.mName == name; }; + auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name); + if(match == devlist.cend() && std::strncmp(name, MonitorPrefix, MonitorPrefixLen) == 0) + { + const char *sinkname{name + MonitorPrefixLen}; + auto match_sinkname = [sinkname](const DeviceNode &n) -> bool + { return n.mType == NodeType::Sink && n.mName == sinkname; }; + match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname); + } + if(match == devlist.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%s\" not found", name}; + + targetid = match->mId; + devname = name; + } + + if(!mLoop) + { + const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)}; + const std::string thread_name{"ALSoftC" + std::to_string(count)}; + mLoop = ThreadMainloop::Create(thread_name.c_str()); + if(!mLoop) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create PipeWire mainloop (errno: %d)", errno}; + if(int res{mLoop.start()}) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start PipeWire mainloop (res: %d)", res}; + } + MainloopUniqueLock mlock{mLoop}; + if(!mContext) + { + pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)}; + mContext = mLoop.newContext(cprops); + if(!mContext) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create PipeWire event context (errno: %d)\n", errno}; + } + if(!mCore) + { + mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)}; + if(!mCore) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to connect PipeWire event context (errno: %d)\n", errno}; + } + mlock.unlock(); + + /* TODO: Ensure the target ID is still valid/usable and accepts streams. */ + + mTargetId = targetid; + if(!devname.empty()) + mDevice->DeviceName = std::move(devname); + else + mDevice->DeviceName = pwireInput; + + + bool is51rear{false}; + if(mTargetId != PwIdAny) + { + EventWatcherLockGuard _{gEventHandler}; + auto&& devlist = DeviceNode::GetList(); + + auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool + { return targetid == n.mId; }; + auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id); + if(match != devlist.cend()) + is51rear = match->mIs51Rear; + } + spa_audio_info_raw info{make_spa_info(mDevice, is51rear, UseDevType)}; + + constexpr uint32_t pod_buffer_size{1024}; + auto pod_buffer = std::make_unique(pod_buffer_size); + spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)}; + + const spa_pod *params[]{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)}; + if(!params[0]) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set PipeWire audio format parameters"}; + + pw_properties *props{pw_properties_new( + PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Game", + PW_KEY_NODE_ALWAYS_PROCESS, "true", + nullptr)}; + if(!props) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create PipeWire stream properties (errno: %d)", errno}; + + auto&& binary = GetProcBinary(); + const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; + pw_properties_set(props, PW_KEY_NODE_NAME, appname); + pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, appname); + /* We don't actually care what the latency/update size is, as long as it's + * reasonable. Unfortunately, when unspecified PipeWire seems to default to + * around 40ms, which isn't great. So request 20ms instead. + */ + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->Frequency+25) / 50, + mDevice->Frequency); + pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency); + + MainloopUniqueLock plock{mLoop}; + mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Capture Stream", props)}; + if(!mStream) + throw al::backend_exception{al::backend_error::NoDevice, + "Failed to create PipeWire stream (errno: %d)", errno}; + static constexpr pw_stream_events streamEvents{CreateEvents()}; + pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this); + + constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE + | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS}; + if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, mTargetId, Flags, params, 1)}) + throw al::backend_exception{al::backend_error::DeviceError, + "Error connecting PipeWire stream (res: %d)", res}; + + /* Wait for the stream to become paused (ready to start streaming). */ + pw_stream_state state{}; + const char *error{}; + plock.wait([stream=mStream.get(),&state,&error]() + { + state = pw_stream_get_state(stream, &error); + if(state == PW_STREAM_STATE_ERROR) + throw al::backend_exception{al::backend_error::DeviceError, + "Error connecting PipeWire stream: \"%s\"", error}; + return state == PW_STREAM_STATE_PAUSED; + }); + plock.unlock(); + + setDefaultWFXChannelOrder(); + + /* Ensure at least a 100ms capture buffer. */ + mRing = RingBuffer::Create(maxu(mDevice->Frequency/10, mDevice->BufferSize), + mDevice->frameSizeFromFmt(), false); +} + + +void PipeWireCapture::start() +{ + MainloopUniqueLock plock{mLoop}; + if(int res{pw_stream_set_active(mStream.get(), true)}) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start PipeWire stream (res: %d)", res}; + + pw_stream_state state{}; + const char *error{}; + plock.wait([stream=mStream.get(),&state,&error]() + { + state = pw_stream_get_state(stream, &error); + return state != PW_STREAM_STATE_PAUSED; + }); + + if(state == PW_STREAM_STATE_ERROR) + throw al::backend_exception{al::backend_error::DeviceError, + "PipeWire stream error: %s", error ? error : "(unknown)"}; +} + +void PipeWireCapture::stop() +{ + MainloopUniqueLock plock{mLoop}; + if(int res{pw_stream_set_active(mStream.get(), false)}) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to stop PipeWire stream (res: %d)", res}; + + plock.wait([stream=mStream.get()]() + { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; }); +} + +uint PipeWireCapture::availableSamples() +{ return static_cast(mRing->readSpace()); } + +void PipeWireCapture::captureSamples(al::byte *buffer, uint samples) +{ mRing->read(buffer, samples); } + +} // namespace + + +bool PipeWireBackendFactory::init() +{ + if(!pwire_load()) + return false; + + pw_init(0, nullptr); + if(!gEventHandler.init()) + return false; + + if(!GetConfigValueBool(nullptr, "pipewire", "assume-audio", false) + && !gEventHandler.waitForAudio()) + { + gEventHandler.kill(); + /* TODO: Temporary warning, until PipeWire gets a proper way to report + * audio support. + */ + WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong.\n"); + return false; + } + return true; +} + +bool PipeWireBackendFactory::querySupport(BackendType type) +{ return type == BackendType::Playback || type == BackendType::Capture; } + +std::string PipeWireBackendFactory::probe(BackendType type) +{ + std::string outnames; + + gEventHandler.waitForInit(); + EventWatcherLockGuard _{gEventHandler}; + auto&& devlist = DeviceNode::GetList(); + + auto match_defsink = [](const DeviceNode &n) -> bool + { return n.mDevName == DefaultSinkDevice; }; + auto match_defsource = [](const DeviceNode &n) -> bool + { return n.mDevName == DefaultSourceDevice; }; + + auto sort_devnode = [](DeviceNode &lhs, DeviceNode &rhs) noexcept -> bool + { return lhs.mId < rhs.mId; }; + std::sort(devlist.begin(), devlist.end(), sort_devnode); + + auto defmatch = devlist.cbegin(); + switch(type) + { + case BackendType::Playback: + defmatch = std::find_if(defmatch, devlist.cend(), match_defsink); + if(defmatch != devlist.cend()) + { + /* Includes null char. */ + outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1); + } + for(auto iter = devlist.cbegin();iter != devlist.cend();++iter) + { + if(iter != defmatch && iter->mType != NodeType::Source) + outnames.append(iter->mName.c_str(), iter->mName.length()+1); + } + break; + case BackendType::Capture: + defmatch = std::find_if(defmatch, devlist.cend(), match_defsource); + if(defmatch != devlist.cend()) + { + if(defmatch->mType == NodeType::Sink) + outnames.append(MonitorPrefix); + outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1); + } + for(auto iter = devlist.cbegin();iter != devlist.cend();++iter) + { + if(iter != defmatch) + { + if(iter->mType == NodeType::Sink) + outnames.append(MonitorPrefix); + outnames.append(iter->mName.c_str(), iter->mName.length()+1); + } + } + break; + } + + return outnames; +} + +BackendPtr PipeWireBackendFactory::createBackend(DeviceBase *device, BackendType type) +{ + if(type == BackendType::Playback) + return BackendPtr{new PipeWirePlayback{device}}; + if(type == BackendType::Capture) + return BackendPtr{new PipeWireCapture{device}}; + return nullptr; +} + +BackendFactory &PipeWireBackendFactory::getFactory() +{ + static PipeWireBackendFactory factory{}; + return factory; +} diff --git a/Engine/lib/openal-soft/alc/backends/pipewire.h b/Engine/lib/openal-soft/alc/backends/pipewire.h new file mode 100644 index 000000000..5f930239c --- /dev/null +++ b/Engine/lib/openal-soft/alc/backends/pipewire.h @@ -0,0 +1,23 @@ +#ifndef BACKENDS_PIPEWIRE_H +#define BACKENDS_PIPEWIRE_H + +#include + +#include "base.h" + +struct DeviceBase; + +struct PipeWireBackendFactory final : public BackendFactory { +public: + bool init() override; + + bool querySupport(BackendType type) override; + + std::string probe(BackendType type) override; + + BackendPtr createBackend(DeviceBase *device, BackendType type) override; + + static BackendFactory &getFactory(); +}; + +#endif /* BACKENDS_PIPEWIRE_H */ diff --git a/Engine/lib/openal-soft/Alc/backends/portaudio.cpp b/Engine/lib/openal-soft/alc/backends/portaudio.cpp similarity index 91% rename from Engine/lib/openal-soft/Alc/backends/portaudio.cpp rename to Engine/lib/openal-soft/alc/backends/portaudio.cpp index d2e6a5a44..9c94587da 100644 --- a/Engine/lib/openal-soft/Alc/backends/portaudio.cpp +++ b/Engine/lib/openal-soft/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; } @@ -195,7 +200,7 @@ bool PortPlayback::reset() return false; } - if(mParams.channelCount == 2) + if(mParams.channelCount >= 2) mDevice->FmtChans = DevFmtStereo; else if(mParams.channelCount == 1) mDevice->FmtChans = DevFmtMono; @@ -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/Engine/lib/openal-soft/Alc/backends/portaudio.h b/Engine/lib/openal-soft/alc/backends/portaudio.h similarity index 76% rename from Engine/lib/openal-soft/Alc/backends/portaudio.h rename to Engine/lib/openal-soft/alc/backends/portaudio.h index 9dbd6b946..c35ccff2d 100644 --- a/Engine/lib/openal-soft/Alc/backends/portaudio.h +++ b/Engine/lib/openal-soft/alc/backends/portaudio.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_PORTAUDIO_H #define BACKENDS_PORTAUDIO_H -#include "backends/base.h" +#include "base.h" struct PortBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/Alc/backends/pulseaudio.cpp b/Engine/lib/openal-soft/alc/backends/pulseaudio.cpp similarity index 90% rename from Engine/lib/openal-soft/Alc/backends/pulseaudio.cpp rename to Engine/lib/openal-soft/alc/backends/pulseaudio.cpp index a6aa93a5e..67e002346 100644 --- a/Engine/lib/openal-soft/Alc/backends/pulseaudio.cpp +++ b/Engine/lib/openal-soft/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); \ @@ -220,78 +236,6 @@ constexpr pa_channel_map MonoChanMap{ } }; -al::optional ChannelFromPulse(pa_channel_position_t chan) -{ - switch(chan) - { - case PA_CHANNEL_POSITION_INVALID: break; - case PA_CHANNEL_POSITION_MONO: return al::make_optional(FrontCenter); - case PA_CHANNEL_POSITION_FRONT_LEFT: return al::make_optional(FrontLeft); - case PA_CHANNEL_POSITION_FRONT_RIGHT: return al::make_optional(FrontRight); - case PA_CHANNEL_POSITION_FRONT_CENTER: return al::make_optional(FrontCenter); - case PA_CHANNEL_POSITION_REAR_CENTER: return al::make_optional(BackCenter); - case PA_CHANNEL_POSITION_REAR_LEFT: return al::make_optional(BackLeft); - case PA_CHANNEL_POSITION_REAR_RIGHT: return al::make_optional(BackRight); - case PA_CHANNEL_POSITION_LFE: return al::make_optional(LFE); - case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: break; - case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: break; - case PA_CHANNEL_POSITION_SIDE_LEFT: return al::make_optional(SideLeft); - case PA_CHANNEL_POSITION_SIDE_RIGHT: return al::make_optional(SideRight); - case PA_CHANNEL_POSITION_AUX0: break; - case PA_CHANNEL_POSITION_AUX1: break; - case PA_CHANNEL_POSITION_AUX2: break; - case PA_CHANNEL_POSITION_AUX3: break; - case PA_CHANNEL_POSITION_AUX4: break; - case PA_CHANNEL_POSITION_AUX5: break; - case PA_CHANNEL_POSITION_AUX6: break; - case PA_CHANNEL_POSITION_AUX7: break; - case PA_CHANNEL_POSITION_AUX8: break; - case PA_CHANNEL_POSITION_AUX9: break; - case PA_CHANNEL_POSITION_AUX10: break; - case PA_CHANNEL_POSITION_AUX11: break; - case PA_CHANNEL_POSITION_AUX12: break; - case PA_CHANNEL_POSITION_AUX13: break; - case PA_CHANNEL_POSITION_AUX14: break; - case PA_CHANNEL_POSITION_AUX15: break; - case PA_CHANNEL_POSITION_AUX16: break; - case PA_CHANNEL_POSITION_AUX17: break; - case PA_CHANNEL_POSITION_AUX18: break; - case PA_CHANNEL_POSITION_AUX19: break; - case PA_CHANNEL_POSITION_AUX20: break; - case PA_CHANNEL_POSITION_AUX21: break; - case PA_CHANNEL_POSITION_AUX22: break; - case PA_CHANNEL_POSITION_AUX23: break; - case PA_CHANNEL_POSITION_AUX24: break; - case PA_CHANNEL_POSITION_AUX25: break; - case PA_CHANNEL_POSITION_AUX26: break; - case PA_CHANNEL_POSITION_AUX27: break; - case PA_CHANNEL_POSITION_AUX28: break; - case PA_CHANNEL_POSITION_AUX29: break; - case PA_CHANNEL_POSITION_AUX30: break; - case PA_CHANNEL_POSITION_AUX31: break; - case PA_CHANNEL_POSITION_TOP_CENTER: return al::make_optional(TopCenter); - case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return al::make_optional(TopFrontLeft); - case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return al::make_optional(TopFrontRight); - case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return al::make_optional(TopFrontCenter); - case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return al::make_optional(TopBackLeft); - case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return al::make_optional(TopBackRight); - case PA_CHANNEL_POSITION_TOP_REAR_CENTER: return al::make_optional(TopBackCenter); - case PA_CHANNEL_POSITION_MAX: break; - } - WARN("Unexpected channel enum %d (%s)\n", chan, pa_channel_position_to_string(chan)); - return al::nullopt; -} - -void SetChannelOrderFromMap(ALCdevice *device, const pa_channel_map &chanmap) -{ - device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); - for(uint i{0};i < chanmap.channels;++i) - { - if(auto label = ChannelFromPulse(chanmap.map[i])) - device->RealOut.ChannelIndex[*label] = i; - } -} - /* *grumble* Don't use enums for bitflags. */ constexpr inline pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs) @@ -497,19 +441,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 +599,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; @@ -698,6 +636,7 @@ struct PulsePlayback final : public BackendBase { al::optional mDeviceName{al::nullopt}; + bool mIs51Rear{false}; pa_buffer_attr mAttr; pa_sample_spec mSpec; @@ -770,15 +709,16 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int struct ChannelMap { DevFmtChannels fmt; pa_channel_map map; + bool is_51rear; }; static constexpr std::array chanmaps{{ - { DevFmtX71, X71ChanMap }, - { DevFmtX61, X61ChanMap }, - { DevFmtX51, X51ChanMap }, - { DevFmtX51Rear, X51RearChanMap }, - { DevFmtQuad, QuadChanMap }, - { DevFmtStereo, StereoChanMap }, - { DevFmtMono, MonoChanMap } + { DevFmtX71, X71ChanMap, false }, + { DevFmtX61, X61ChanMap, false }, + { DevFmtX51, X51ChanMap, false }, + { DevFmtX51, X51RearChanMap, true }, + { DevFmtQuad, QuadChanMap, false }, + { DevFmtStereo, StereoChanMap, false }, + { DevFmtMono, MonoChanMap, false } }}; if(eol) @@ -795,9 +735,11 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int { if(!mDevice->Flags.test(ChannelsRequest)) mDevice->FmtChans = chaniter->fmt; + mIs51Rear = chaniter->is_51rear; } else { + mIs51Rear = false; char chanmap_str[PA_CHANNEL_MAP_SNPRINT_MAX]{}; pa_channel_map_snprint(chanmap_str, sizeof(chanmap_str), &info->channel_map); WARN("Failed to find format for channel map:\n %s\n", chanmap_str); @@ -805,8 +747,8 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int if(info->active_port) TRACE("Active port: %s (%s)\n", info->active_port->name, info->active_port->description); - mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo - && info->active_port && strcmp(info->active_port->name, "analog-output-headphones") == 0); + mDevice->Flags.set(DirectEar, (info->active_port + && strcmp(info->active_port->name, "analog-output-headphones") == 0)); } void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int eol) noexcept @@ -846,7 +788,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 +807,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))); @@ -934,10 +887,7 @@ bool PulsePlayback::reset() chanmap = QuadChanMap; break; case DevFmtX51: - chanmap = X51ChanMap; - break; - case DevFmtX51Rear: - chanmap = X51RearChanMap; + chanmap = (mIs51Rear ? X51RearChanMap : X51ChanMap); break; case DevFmtX61: chanmap = X61ChanMap; @@ -946,7 +896,7 @@ bool PulsePlayback::reset() chanmap = X71ChanMap; break; } - SetChannelOrderFromMap(mDevice, chanmap); + setDefaultWFXChannelOrder(); switch(mDevice->FmtType) { @@ -1029,34 +979,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 +1053,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; @@ -1217,9 +1167,6 @@ void PulseCapture::open(const char *name) case DevFmtX51: chanmap = X51ChanMap; break; - case DevFmtX51Rear: - chanmap = X51RearChanMap; - break; case DevFmtX61: chanmap = X61ChanMap; break; @@ -1230,7 +1177,7 @@ void PulseCapture::open(const char *name) throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } - SetChannelOrderFromMap(mDevice, chanmap); + setDefaultWFXChannelOrder(); switch(mDevice->FmtType) { @@ -1505,7 +1452,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/Engine/lib/openal-soft/Alc/backends/pulseaudio.h b/Engine/lib/openal-soft/alc/backends/pulseaudio.h similarity index 76% rename from Engine/lib/openal-soft/Alc/backends/pulseaudio.h rename to Engine/lib/openal-soft/alc/backends/pulseaudio.h index 86b815021..6690fe8a9 100644 --- a/Engine/lib/openal-soft/Alc/backends/pulseaudio.h +++ b/Engine/lib/openal-soft/alc/backends/pulseaudio.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_PULSEAUDIO_H #define BACKENDS_PULSEAUDIO_H -#include "backends/base.h" +#include "base.h" class PulseBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/Alc/backends/sdl2.cpp b/Engine/lib/openal-soft/alc/backends/sdl2.cpp similarity index 76% rename from Engine/lib/openal-soft/Alc/backends/sdl2.cpp rename to Engine/lib/openal-soft/alc/backends/sdl2.cpp index 084b51c05..c0726033b 100644 --- a/Engine/lib/openal-soft/Alc/backends/sdl2.cpp +++ b/Engine/lib/openal-soft/alc/backends/sdl2.cpp @@ -20,16 +20,16 @@ #include "config.h" -#include "backends/sdl2.h" +#include "sdl2.h" #include #include #include #include -#include "alcmain.h" #include "almalloc.h" -#include "alu.h" +#include "alnumeric.h" +#include "core/device.h" #include "core/logging.h" #include @@ -46,7 +46,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; @@ -99,59 +99,64 @@ void Sdl2Backend::open(const char *name) case DevFmtFloat: want.format = AUDIO_F32; break; } want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2; - want.samples = static_cast(mDevice->UpdateSize); + want.samples = static_cast(minu(mDevice->UpdateSize, 8192)); want.callback = &Sdl2Backend::audioCallbackC; want.userdata = this; /* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't * necessarily the first in the list. */ + SDL_AudioDeviceID devid; if(!name || strcmp(name, defaultDeviceName) == 0) - mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); + devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); else { const size_t prefix_len = strlen(DEVNAME_PREFIX); if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0) - mDeviceID = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have, + 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); - - if(have.channels == 1) - mDevice->FmtChans = DevFmtMono; - else if(have.channels == 2) - mDevice->FmtChans = DevFmtStereo; + DevFmtChannels devchans{}; + if(have.channels >= 2) + devchans = DevFmtStereo; + else if(have.channels == 1) + devchans = DevFmtMono; else + { + SDL_CloseAudioDevice(devid); throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL channel count: %d", int{have.channels}}; + } + DevFmtType devtype{}; switch(have.format) { - case AUDIO_U8: mDevice->FmtType = DevFmtUByte; break; - case AUDIO_S8: mDevice->FmtType = DevFmtByte; break; - case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break; - case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort; break; - case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break; - case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat; break; + case AUDIO_U8: devtype = DevFmtUByte; break; + case AUDIO_S8: devtype = DevFmtByte; break; + case AUDIO_U16SYS: devtype = DevFmtUShort; break; + case AUDIO_S16SYS: devtype = DevFmtShort; break; + case AUDIO_S32SYS: devtype = DevFmtInt; break; + case AUDIO_F32SYS: devtype = DevFmtFloat; break; default: + SDL_CloseAudioDevice(devid); throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: 0x%04x", have.format}; } - mDevice->UpdateSize = have.samples; - mDevice->BufferSize = have.samples * 2; /* SDL always (tries to) use two periods. */ - mFrameSize = mDevice->frameSizeFromFmt(); - mFrequency = mDevice->Frequency; - mFmtChans = mDevice->FmtChans; - mFmtType = mDevice->FmtType; - mUpdateSize = mDevice->UpdateSize; + if(mDeviceID) + SDL_CloseAudioDevice(mDeviceID); + mDeviceID = devid; + + mFrameSize = BytesFromDevFmt(devtype) * have.channels; + mFrequency = static_cast(have.freq); + mFmtChans = devchans; + mFmtType = devtype; + mUpdateSize = have.samples; mDevice->DeviceName = name ? name : defaultDeviceName; } @@ -162,7 +167,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 +213,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/Engine/lib/openal-soft/Alc/backends/sdl2.h b/Engine/lib/openal-soft/alc/backends/sdl2.h similarity index 75% rename from Engine/lib/openal-soft/Alc/backends/sdl2.h rename to Engine/lib/openal-soft/alc/backends/sdl2.h index ce79d52ed..3bd8df863 100644 --- a/Engine/lib/openal-soft/Alc/backends/sdl2.h +++ b/Engine/lib/openal-soft/alc/backends/sdl2.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_SDL2_H #define BACKENDS_SDL2_H -#include "backends/base.h" +#include "base.h" struct SDL2BackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/Alc/backends/sndio.cpp b/Engine/lib/openal-soft/alc/backends/sndio.cpp similarity index 60% rename from Engine/lib/openal-soft/Alc/backends/sndio.cpp rename to Engine/lib/openal-soft/alc/backends/sndio.cpp index 41bdb73b1..48387c651 100644 --- a/Engine/lib/openal-soft/Alc/backends/sndio.cpp +++ b/Engine/lib/openal-soft/alc/backends/sndio.cpp @@ -20,8 +20,9 @@ #include "config.h" -#include "backends/sndio.h" +#include "sndio.h" +#include #include #include #include @@ -29,8 +30,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" @@ -43,9 +45,14 @@ namespace { static const char sndio_device[] = "SndIO Default"; +struct SioPar : public sio_par { + SioPar() { sio_initpar(this); } + + void clear() { sio_initpar(this); } +}; struct SndioPlayback final : public BackendBase { - SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { } + SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~SndioPlayback() override; int mixerProc(); @@ -56,6 +63,7 @@ struct SndioPlayback final : public BackendBase { void stop() override; sio_hdl *mSndHandle{nullptr}; + uint mFrameStep{}; al::vector mBuffer; @@ -74,16 +82,8 @@ SndioPlayback::~SndioPlayback() int SndioPlayback::mixerProc() { - sio_par par; - sio_initpar(&par); - if(!sio_getpar(mSndHandle, &par)) - { - mDevice->handleDisconnect("Failed to get device parameters"); - return 1; - } - - const size_t frameStep{par.pchan}; - const size_t frameSize{frameStep * par.bps}; + const size_t frameStep{mFrameStep}; + const size_t frameSize{frameStep * mDevice->bytesFromFmt()}; SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); @@ -91,22 +91,20 @@ int SndioPlayback::mixerProc() while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { - al::byte *WritePtr{mBuffer.data()}; - size_t len{mBuffer.size()}; + al::span buffer{mBuffer}; - mDevice->renderSamples(WritePtr, static_cast(len/frameSize), frameStep); - while(len > 0 && !mKillNow.load(std::memory_order_acquire)) + mDevice->renderSamples(buffer.data(), static_cast(buffer.size() / frameSize), + frameStep); + while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire)) { - size_t wrote{sio_write(mSndHandle, WritePtr, len)}; + size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())}; if(wrote == 0) { ERR("sio_write failed\n"); mDevice->handleDisconnect("Failed to write playback samples"); break; } - - len -= wrote; - WritePtr += wrote; + buffer = buffer.subspan(wrote); } } @@ -122,34 +120,24 @@ 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; } bool SndioPlayback::reset() { - sio_par par; - sio_initpar(&par); + SioPar par; - par.rate = mDevice->Frequency; - switch(mDevice->FmtChans) - { - case DevFmtMono : par.pchan = 1; break; - case DevFmtQuad : par.pchan = 4; break; - case DevFmtX51Rear: // fall-through - "Similar to 5.1, except using rear channels instead of sides" - case DevFmtX51 : par.pchan = 6; break; - case DevFmtX61 : par.pchan = 7; break; - case DevFmtX71 : par.pchan = 8; break; - - // fall back to stereo for Ambi3D - case DevFmtAmbi3D : // fall-through - case DevFmtStereo : par.pchan = 2; break; - } - - switch(mDevice->FmtType) + auto tryfmt = mDevice->FmtType; +retry_params: + switch(tryfmt) { case DevFmtByte: par.bits = 8; @@ -159,7 +147,6 @@ bool SndioPlayback::reset() par.bits = 8; par.sig = 0; break; - case DevFmtFloat: case DevFmtShort: par.bits = 16; par.sig = 1; @@ -168,6 +155,7 @@ bool SndioPlayback::reset() par.bits = 16; par.sig = 0; break; + case DevFmtFloat: case DevFmtInt: par.bits = 32; par.sig = 1; @@ -177,70 +165,64 @@ bool SndioPlayback::reset() par.sig = 0; break; } + par.bps = SIO_BPS(par.bits); par.le = SIO_LE_NATIVE; + par.msb = 1; + + par.rate = mDevice->Frequency; + par.pchan = mDevice->channelsFromFmt(); par.round = mDevice->UpdateSize; par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize; if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize; - if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) - { - ERR("Failed to set device parameters\n"); - return false; + try { + if(!sio_setpar(mSndHandle, &par)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set device parameters"}; + + par.clear(); + if(!sio_getpar(mSndHandle, &par)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to get device parameters"}; + + if(par.bps > 1 && par.le != SIO_LE_NATIVE) + throw al::backend_exception{al::backend_error::DeviceError, + "%s-endian samples not supported", par.le ? "Little" : "Big"}; + if(par.bits < par.bps*8 && !par.msb) + throw al::backend_exception{al::backend_error::DeviceError, + "MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8}; + if(par.pchan < 1) + throw al::backend_exception{al::backend_error::DeviceError, + "No playback channels on device"}; + } + catch(al::backend_exception &e) { + if(tryfmt == DevFmtShort) + throw; + par.clear(); + tryfmt = DevFmtShort; + goto retry_params; } - if(par.bits != par.bps*8) - { - ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8); - return false; - } - if(par.le != SIO_LE_NATIVE) - { - ERR("Non-native-endian samples not supported (got %s-endian)\n", - par.le ? "little" : "big"); - return false; - } - - mDevice->Frequency = par.rate; - - if(par.pchan < 2) - { - if(mDevice->FmtChans != DevFmtMono) - { - WARN("Got %u channel for %s\n", par.pchan, DevFmtChannelsString(mDevice->FmtChans)); - mDevice->FmtChans = DevFmtMono; - } - } - else if((par.pchan == 2 && mDevice->FmtChans != DevFmtStereo) - || par.pchan == 3 - || (par.pchan == 4 && mDevice->FmtChans != DevFmtQuad) - || par.pchan == 5 - || (par.pchan == 6 && mDevice->FmtChans != DevFmtX51 && mDevice->FmtChans != DevFmtX51Rear) - || (par.pchan == 7 && mDevice->FmtChans != DevFmtX61) - || (par.pchan == 8 && mDevice->FmtChans != DevFmtX71) - || par.pchan > 8) - { - WARN("Got %u channels for %s\n", par.pchan, DevFmtChannelsString(mDevice->FmtChans)); - mDevice->FmtChans = DevFmtStereo; - } - - if(par.bits == 8 && par.sig == 1) - mDevice->FmtType = DevFmtByte; - else if(par.bits == 8 && par.sig == 0) - mDevice->FmtType = DevFmtUByte; - else if(par.bits == 16 && par.sig == 1) - mDevice->FmtType = DevFmtShort; - else if(par.bits == 16 && par.sig == 0) - mDevice->FmtType = DevFmtUShort; - else if(par.bits == 32 && par.sig == 1) - mDevice->FmtType = DevFmtInt; - else if(par.bits == 32 && par.sig == 0) - mDevice->FmtType = DevFmtUInt; + if(par.bps == 1) + mDevice->FmtType = (par.sig==1) ? DevFmtByte : DevFmtUByte; + else if(par.bps == 2) + mDevice->FmtType = (par.sig==1) ? DevFmtShort : DevFmtUShort; + else if(par.bps == 4) + mDevice->FmtType = (par.sig==1) ? DevFmtInt : DevFmtUInt; else + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled sample format: %s %u-bit", (par.sig?"signed":"unsigned"), par.bps*8}; + + mFrameStep = par.pchan; + if(par.pchan != mDevice->channelsFromFmt()) { - ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits); - return false; + WARN("Got %u channel%s for %s\n", par.pchan, (par.pchan==1)?"":"s", + DevFmtChannelsString(mDevice->FmtChans)); + if(par.pchan < 2) mDevice->FmtChans = DevFmtMono; + else mDevice->FmtChans = DevFmtStereo; } + mDevice->Frequency = par.rate; setDefaultChannelOrder(); @@ -287,8 +269,13 @@ void SndioPlayback::stop() } +/* TODO: This could be improved by avoiding the ring buffer and record thread, + * counting the available samples with the sio_onmove callback and reading + * directly from the device. However, this depends on reasonable support for + * capture buffer sizes apps may request. + */ struct SndioCapture final : public BackendBase { - SndioCapture(ALCdevice *device) noexcept : BackendBase{device} { } + SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~SndioCapture() override; int recordProc(); @@ -323,40 +310,65 @@ int SndioCapture::recordProc() const uint frameSize{mDevice->frameSizeFromFmt()}; + int nfds_pre{sio_nfds(mSndHandle)}; + if(nfds_pre <= 0) + { + mDevice->handleDisconnect("Incorrect return value from sio_nfds(): %d", nfds_pre); + return 1; + } + + auto fds = std::make_unique(static_cast(nfds_pre)); + while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { - auto data = mRing->getWriteVector(); - size_t todo{data.first.len + data.second.len}; - if(todo == 0) + /* Wait until there's some samples to read. */ + const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)}; + if(nfds <= 0) { - static char junk[4096]; - sio_read(mSndHandle, junk, - minz(sizeof(junk)/frameSize, mDevice->UpdateSize)*frameSize); + mDevice->handleDisconnect("Failed to get polling fds: %d", nfds); + break; + } + int pollres{::poll(fds.get(), static_cast(nfds), 2000)}; + if(pollres < 0) + { + if(errno == EINTR) continue; + mDevice->handleDisconnect("Poll error: %s", strerror(errno)); + break; + } + if(pollres == 0) continue; - } - size_t total{0u}; - data.first.len *= frameSize; - data.second.len *= frameSize; - todo = minz(todo, mDevice->UpdateSize) * frameSize; - while(total < todo) + const int revents{sio_revents(mSndHandle, fds.get())}; + if((revents&POLLHUP)) { - if(!data.first.len) - data.first = data.second; - - size_t got{sio_read(mSndHandle, data.first.buf, minz(todo-total, data.first.len))}; - if(!got) - { - mDevice->handleDisconnect("Failed to read capture samples"); - break; - } - - data.first.buf += got; - data.first.len -= got; - total += got; + mDevice->handleDisconnect("Got POLLHUP from poll events"); + break; + } + if(!(revents&POLLIN)) + continue; + + auto data = mRing->getWriteVector(); + al::span buffer{data.first.buf, data.first.len*frameSize}; + while(!buffer.empty()) + { + size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())}; + if(got == 0) break; + + mRing->writeAdvance(got / frameSize); + buffer = buffer.subspan(got); + if(buffer.empty()) + { + data = mRing->getWriteVector(); + buffer = {data.first.buf, data.first.len*frameSize}; + } + } + if(buffer.empty()) + { + /* Got samples to read, but no place to store it. Drop it. */ + static char junk[4096]; + sio_read(mSndHandle, junk, sizeof(junk) - (sizeof(junk)%frameSize)); } - mRing->writeAdvance(total / frameSize); } return 0; @@ -371,76 +383,80 @@ void SndioCapture::open(const char *name) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; - mSndHandle = sio_open(nullptr, SIO_REC, 0); + mSndHandle = sio_open(nullptr, SIO_REC, true); if(mSndHandle == nullptr) throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"}; - sio_par par; - sio_initpar(&par); - + SioPar par; switch(mDevice->FmtType) { case DevFmtByte: - par.bps = 1; + par.bits = 8; par.sig = 1; break; case DevFmtUByte: - par.bps = 1; + par.bits = 8; par.sig = 0; break; case DevFmtShort: - par.bps = 2; + par.bits = 16; par.sig = 1; break; case DevFmtUShort: - par.bps = 2; + par.bits = 16; par.sig = 0; break; case DevFmtInt: - par.bps = 4; + par.bits = 32; par.sig = 1; break; case DevFmtUInt: - par.bps = 4; + par.bits = 32; par.sig = 0; break; case DevFmtFloat: throw al::backend_exception{al::backend_error::DeviceError, "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } - par.bits = par.bps * 8; + par.bps = SIO_BPS(par.bits); par.le = SIO_LE_NATIVE; - par.msb = SIO_LE_NATIVE ? 0 : 1; + par.msb = 1; par.rchan = mDevice->channelsFromFmt(); par.rate = mDevice->Frequency; par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10); - par.round = minu(par.appbufsz, mDevice->Frequency/40); - - mDevice->UpdateSize = par.round; - mDevice->BufferSize = par.appbufsz; + par.round = minu(par.appbufsz/2, mDevice->Frequency/40); if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set device praameters"}; - if(par.bits != par.bps*8) + if(par.bps > 1 && par.le != SIO_LE_NATIVE) + throw al::backend_exception{al::backend_error::DeviceError, + "%s-endian samples not supported", par.le ? "Little" : "Big"}; + if(par.bits < par.bps*8 && !par.msb) throw al::backend_exception{al::backend_error::DeviceError, "Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8}; - if(!((mDevice->FmtType == DevFmtByte && par.bits == 8 && par.sig != 0) - || (mDevice->FmtType == DevFmtUByte && par.bits == 8 && par.sig == 0) - || (mDevice->FmtType == DevFmtShort && par.bits == 16 && par.sig != 0) - || (mDevice->FmtType == DevFmtUShort && par.bits == 16 && par.sig == 0) - || (mDevice->FmtType == DevFmtInt && par.bits == 32 && par.sig != 0) - || (mDevice->FmtType == DevFmtUInt && par.bits == 32 && par.sig == 0)) - || mDevice->channelsFromFmt() != par.rchan || mDevice->Frequency != par.rate) + auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool + { + return (fmttype == DevFmtByte && p.bps == 1 && p.sig != 0) + || (fmttype == DevFmtUByte && p.bps == 1 && p.sig == 0) + || (fmttype == DevFmtShort && p.bps == 2 && p.sig != 0) + || (fmttype == DevFmtUShort && p.bps == 2 && p.sig == 0) + || (fmttype == DevFmtInt && p.bps == 4 && p.sig != 0) + || (fmttype == DevFmtUInt && p.bps == 4 && p.sig == 0); + }; + if(!match_fmt(mDevice->FmtType, par) || mDevice->channelsFromFmt() != par.rchan + || mDevice->Frequency != par.rate) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead", DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans), - mDevice->Frequency, par.sig?'s':'u', par.bits, par.rchan, par.rate}; + mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate}; mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false); + mDevice->BufferSize = static_cast(mRing->writeSpace()); + mDevice->UpdateSize = par.round; setDefaultChannelOrder(); @@ -507,7 +523,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/Engine/lib/openal-soft/Alc/backends/sndio.h b/Engine/lib/openal-soft/alc/backends/sndio.h similarity index 75% rename from Engine/lib/openal-soft/Alc/backends/sndio.h rename to Engine/lib/openal-soft/alc/backends/sndio.h index 83d02906c..d94331911 100644 --- a/Engine/lib/openal-soft/Alc/backends/sndio.h +++ b/Engine/lib/openal-soft/alc/backends/sndio.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_SNDIO_H #define BACKENDS_SNDIO_H -#include "backends/base.h" +#include "base.h" struct SndIOBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/Alc/backends/solaris.cpp b/Engine/lib/openal-soft/alc/backends/solaris.cpp similarity index 79% rename from Engine/lib/openal-soft/Alc/backends/solaris.cpp rename to Engine/lib/openal-soft/alc/backends/solaris.cpp index f27633572..791609ce7 100644 --- a/Engine/lib/openal-soft/Alc/backends/solaris.cpp +++ b/Engine/lib/openal-soft/alc/backends/solaris.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/solaris.h" +#include "solaris.h" #include #include @@ -34,15 +34,15 @@ #include #include #include +#include #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" @@ -58,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(); @@ -70,6 +70,7 @@ struct SolarisBackend final : public BackendBase { int mFd{-1}; + uint mFrameStep{}; al::vector mBuffer; std::atomic mKillNow{true}; @@ -147,11 +148,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; } @@ -161,12 +166,7 @@ bool SolarisBackend::reset() AUDIO_INITINFO(&info); info.play.sample_rate = mDevice->Frequency; - - if(mDevice->FmtChans != DevFmtMono) - mDevice->FmtChans = DevFmtStereo; - uint numChannels{mDevice->channelsFromFmt()}; - info.play.channels = numChannels; - + info.play.channels = mDevice->channelsFromFmt(); switch(mDevice->FmtType) { case DevFmtByte: @@ -188,9 +188,7 @@ bool SolarisBackend::reset() info.play.encoding = AUDIO_ENCODING_LINEAR; break; } - - uint frameSize{numChannels * mDevice->bytesFromFmt()}; - info.play.buffer_size = mDevice->BufferSize * frameSize; + info.play.buffer_size = mDevice->BufferSize * mDevice->frameSizeFromFmt(); if(ioctl(mFd, AUDIO_SETINFO, &info) < 0) { @@ -200,28 +198,39 @@ bool SolarisBackend::reset() if(mDevice->channelsFromFmt() != info.play.channels) { - ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(mDevice->FmtChans), - info.play.channels); - return false; + if(info.play.channels >= 2) + mDevice->FmtChans = DevFmtStereo; + else if(info.play.channels == 1) + mDevice->FmtChans = DevFmtMono; + else + throw al::backend_exception{al::backend_error::DeviceError, + "Got %u device channels", info.play.channels}; } - if(!((info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && mDevice->FmtType == DevFmtUByte) || - (info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtByte) || - (info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtShort) || - (info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtInt))) + if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8) + mDevice->FmtType = DevFmtUByte; + else if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR) + mDevice->FmtType = DevFmtByte; + else if(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR) + mDevice->FmtType = DevFmtShort; + else if(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR) + mDevice->FmtType = DevFmtInt; + else { - ERR("Could not set %s samples, got %d (0x%x)\n", DevFmtTypeString(mDevice->FmtType), - info.play.precision, info.play.encoding); + ERR("Got unhandled sample type: %d (0x%x)\n", info.play.precision, info.play.encoding); return false; } + uint frame_size{mDevice->bytesFromFmt() * info.play.channels}; + mFrameStep = info.play.channels; mDevice->Frequency = info.play.sample_rate; - mDevice->BufferSize = info.play.buffer_size / frameSize; + mDevice->BufferSize = info.play.buffer_size / frame_size; + /* How to get the actual period size/count? */ mDevice->UpdateSize = mDevice->BufferSize / 2; setDefaultChannelOrder(); - mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); + mBuffer.resize(mDevice->UpdateSize * size_t{frame_size}); std::fill(mBuffer.begin(), mBuffer.end(), al::byte{}); return true; @@ -286,7 +295,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/Engine/lib/openal-soft/Alc/backends/solaris.h b/Engine/lib/openal-soft/alc/backends/solaris.h similarity index 76% rename from Engine/lib/openal-soft/Alc/backends/solaris.h rename to Engine/lib/openal-soft/alc/backends/solaris.h index 4a9e505b0..5da6ad3a9 100644 --- a/Engine/lib/openal-soft/Alc/backends/solaris.h +++ b/Engine/lib/openal-soft/alc/backends/solaris.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_SOLARIS_H #define BACKENDS_SOLARIS_H -#include "backends/base.h" +#include "base.h" struct SolarisBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/Alc/backends/wasapi.cpp b/Engine/lib/openal-soft/alc/backends/wasapi.cpp similarity index 84% rename from Engine/lib/openal-soft/Alc/backends/wasapi.cpp rename to Engine/lib/openal-soft/alc/backends/wasapi.cpp index 0786a7d7c..063fca989 100644 --- a/Engine/lib/openal-soft/Alc/backends/wasapi.cpp +++ b/Engine/lib/openal-soft/alc/backends/wasapi.cpp @@ -20,7 +20,7 @@ #include "config.h" -#include "backends/wasapi.h" +#include "wasapi.h" #define WIN32_LEAN_AND_MEAN #include @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -56,10 +57,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" @@ -118,7 +120,8 @@ constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)}; constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)}; constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)}; -#define DEVNAME_HEAD "OpenAL Soft on " +constexpr char DevNameHead[] = "OpenAL Soft on "; +constexpr size_t DevNameHeadLen{al::size(DevNameHead) - 1}; /* Scales the given reftime value, rounding the result. */ @@ -129,69 +132,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 +178,9 @@ struct DevMap { bool checkName(const al::vector &list, const std::string &name) { - return std::find_if(list.cbegin(), list.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ) != list.cend(); + auto match_name = [&name](const DevMap &entry) -> bool + { return entry.name == name; }; + return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } al::vector PlaybackDevices; @@ -253,8 +192,7 @@ NameGUIDPair get_device_name_and_guid(IMMDevice *device) { static constexpr char UnknownName[]{"Unknown Device Name"}; static constexpr char UnknownGuid[]{"Unknown Device GUID"}; - std::string name{DEVNAME_HEAD}; - std::string guid; + std::string name, guid; ComPtr ps; HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr()); @@ -297,26 +235,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 +422,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 +450,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 +460,22 @@ struct WasapiProxy { struct Msg { MsgType mType; WasapiProxy *mProxy; + const char *mParam; std::promise mPromise; + + explicit operator bool() const noexcept { return mType != MsgType::QuitThread; } }; static std::deque mMsgQueue; static std::mutex mMsgQueueLock; static std::condition_variable mMsgQueueCond; - std::future pushMessage(MsgType type) + std::future pushMessage(MsgType type, const char *param=nullptr) { std::promise promise; std::future future{promise.get_future()}; { std::lock_guard _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, this, std::move(promise)}); + mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)}); } mMsgQueueCond.notify_one(); return future; @@ -545,19 +487,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 +539,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 +552,7 @@ int WasapiProxy::messageHandler(std::promise *promise) if(++deviceCount == 1) hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if(SUCCEEDED(hr)) - hr = msg.mProxy->openProxy(); + hr = msg.mProxy->openProxy(msg.mParam); msg.mPromise.set_value(hr); if(FAILED(hr)) @@ -621,6 +562,11 @@ int WasapiProxy::messageHandler(std::promise *promise) } continue; + case MsgType::ReopenDevice: + hr = msg.mProxy->openProxy(msg.mParam); + msg.mPromise.set_value(hr); + continue; + case MsgType::ResetDevice: hr = msg.mProxy->resetProxy(); msg.mPromise.set_value(hr); @@ -669,11 +615,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 +628,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 +646,6 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { ClockLatency getClockLatency() override; - std::wstring mDevId; - HRESULT mOpenStatus{E_FAIL}; ComPtr mMMDev{nullptr}; ComPtr mClient{nullptr}; @@ -796,11 +740,14 @@ void WasapiPlayback::open(const char *name) { HRESULT hr{S_OK}; - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(mNotifyEvent == nullptr) + if(!mNotifyEvent) { - ERR("Failed to create notify events: %lu\n", GetLastError()); - hr = E_FAIL; + mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if(mNotifyEvent == nullptr) + { + ERR("Failed to create notify events: %lu\n", GetLastError()); + hr = E_FAIL; + } } if(SUCCEEDED(hr)) @@ -808,71 +755,75 @@ void WasapiPlayback::open(const char *name) if(name) { if(PlaybackDevices.empty()) - pushMessage(MsgType::EnumeratePlayback).wait(); + pushMessage(MsgType::EnumeratePlayback); + if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) + { + name += DevNameHeadLen; + if(*name == '\0') + name = nullptr; + } + } - 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(mOpenStatus)) + hr = pushMessage(MsgType::ReopenDevice, name).get(); + else + { + hr = pushMessage(MsgType::OpenDevice, name).get(); + mOpenStatus = hr; } } - 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}; - } } -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 = std::string{DevNameHead} + name; + else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first; return hr; } @@ -935,10 +886,9 @@ HRESULT WasapiPlayback::resetProxy() mDevice->FmtChans = DevFmtX71; else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) mDevice->FmtChans = DevFmtX61; - else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1) + else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 + || (chanmask&X51RearMask) == X5DOT1REAR)) mDevice->FmtChans = DevFmtX51; - else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR) - mDevice->FmtChans = DevFmtX51Rear; else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) mDevice->FmtChans = DevFmtQuad; else if(chancount >= 2 && (chanmask&StereoMask) == STEREO) @@ -971,10 +921,6 @@ HRESULT WasapiPlayback::resetProxy() OutputType.Format.nChannels = 6; OutputType.dwChannelMask = X5DOT1; break; - case DevFmtX51Rear: - OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1REAR; - break; case DevFmtX61: OutputType.Format.nChannels = 7; OutputType.dwChannelMask = X6DOT1; @@ -1050,27 +996,60 @@ HRESULT WasapiPlayback::resetProxy() mDevice->Frequency = OutputType.Format.nSamplesPerSec; const uint32_t chancount{OutputType.Format.nChannels}; const DWORD chanmask{OutputType.dwChannelMask}; - if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) - mDevice->FmtChans = DevFmtX71; - else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) - mDevice->FmtChans = DevFmtX61; - else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1) - mDevice->FmtChans = DevFmtX51; - else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR) - mDevice->FmtChans = DevFmtX51Rear; - else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(chancount >= 2 && (chanmask&StereoMask) == STEREO) - mDevice->FmtChans = DevFmtStereo; - else if(chancount >= 1 && (chanmask&MonoMask) == MONO) - mDevice->FmtChans = DevFmtMono; - else + /* Don't update the channel format if the requested format fits what's + * supported. + */ + bool chansok{false}; + if(mDevice->Flags.test(ChannelsRequest)) { - ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, - OutputType.dwChannelMask); - mDevice->FmtChans = DevFmtStereo; - OutputType.Format.nChannels = 2; - OutputType.dwChannelMask = STEREO; + switch(mDevice->FmtChans) + { + case DevFmtMono: + chansok = (chancount >= 1 && (chanmask&MonoMask) == MONO); + break; + case DevFmtStereo: + chansok = (chancount >= 2 && (chanmask&StereoMask) == STEREO); + break; + case DevFmtQuad: + chansok = (chancount >= 4 && (chanmask&QuadMask) == QUAD); + break; + case DevFmtX51: + chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 + || (chanmask&X51RearMask) == X5DOT1REAR)); + break; + case DevFmtX61: + chansok = (chancount >= 7 && (chanmask&X61Mask) == X6DOT1); + break; + case DevFmtX71: + chansok = (chancount >= 8 && (chanmask&X71Mask) == X7DOT1); + break; + case DevFmtAmbi3D: + break; + } + } + if(!chansok) + { + if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) + mDevice->FmtChans = DevFmtX71; + else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) + mDevice->FmtChans = DevFmtX61; + else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 + || (chanmask&X51RearMask) == X5DOT1REAR)) + mDevice->FmtChans = DevFmtX51; + else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) + mDevice->FmtChans = DevFmtQuad; + else if(chancount >= 2 && (chanmask&StereoMask) == STEREO) + mDevice->FmtChans = DevFmtStereo; + else if(chancount >= 1 && (chanmask&MonoMask) == MONO) + mDevice->FmtChans = DevFmtMono; + else + { + ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, + OutputType.dwChannelMask); + mDevice->FmtChans = DevFmtStereo; + OutputType.Format.nChannels = 2; + OutputType.dwChannelMask = STEREO; + } } if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) @@ -1105,10 +1084,8 @@ HRESULT WasapiPlayback::resetProxy() } mFrameStep = OutputType.Format.nChannels; - EndpointFormFactor formfactor{UnknownFormFactor}; - get_device_formfactor(mMMDev.get(), &formfactor); - mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo - && (formfactor == Headphones || formfactor == Headset)); + const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())}; + mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset)); setChannelOrderFromWFXMask(OutputType.dwChannelMask); @@ -1220,13 +1197,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 +1215,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}; @@ -1376,45 +1351,21 @@ void WasapiCapture::open(const char *name) if(name) { if(CaptureDevices.empty()) - pushMessage(MsgType::EnumerateCapture).wait(); - - hr = E_FAIL; - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; }); - if(iter == CaptureDevices.cend()) + pushMessage(MsgType::EnumerateCapture); + if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) { - 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; + name += DevNameHeadLen; + if(*name == '\0') + name = nullptr; } } + hr = pushMessage(MsgType::OpenDevice, name).get(); } - - if(SUCCEEDED(hr)) - hr = pushMessage(MsgType::OpenDevice).get(); mOpenStatus = hr; if(FAILED(hr)) - { - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; - - mDevId.clear(); - throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", hr}; - } hr = pushMessage(MsgType::ResetDevice).get(); if(FAILED(hr)) @@ -1425,30 +1376,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 = std::string{DevNameHead} + name; + else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first; return hr; } @@ -1496,10 +1467,6 @@ HRESULT WasapiCapture::resetProxy() InputType.Format.nChannels = 6; InputType.dwChannelMask = X5DOT1; break; - case DevFmtX51Rear: - InputType.Format.nChannels = 6; - InputType.dwChannelMask = X5DOT1REAR; - break; case DevFmtX61: InputType.Format.nChannels = 7; InputType.dwChannelMask = X6DOT1; @@ -1567,7 +1534,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) @@ -1584,14 +1551,14 @@ HRESULT WasapiCapture::resetProxy() return (chancount == 4 && (chanmask == 0 || (chanmask&QuadMask) == QUAD)); /* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */ case DevFmtX51: - case DevFmtX51Rear: return (chancount == 6 && (chanmask == 0 || (chanmask&X51Mask) == X5DOT1 || (chanmask&X51RearMask) == X5DOT1REAR)); case DevFmtX61: return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1)); case DevFmtX71: return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1)); - case DevFmtAmbi3D: return (chanmask == 0 && device->channelsFromFmt()); + case DevFmtAmbi3D: + return (chanmask == 0 && chancount == device->channelsFromFmt()); } return false; }; @@ -1808,31 +1775,30 @@ bool WasapiBackendFactory::querySupport(BackendType type) std::string WasapiBackendFactory::probe(BackendType type) { std::string outnames; - auto add_device = [&outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames.append(entry.name.c_str(), entry.name.length()+1); - }; - switch(type) { case BackendType::Playback: WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).wait(); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); + for(const DevMap &entry : PlaybackDevices) + { + /* +1 to also append the null char (to ensure a null-separated list + * and double-null terminated list). + */ + outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1); + } break; case BackendType::Capture: WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).wait(); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); + for(const DevMap &entry : CaptureDevices) + outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1); break; } return outnames; } -BackendPtr WasapiBackendFactory::createBackend(ALCdevice *device, BackendType type) +BackendPtr WasapiBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new WasapiPlayback{device}}; diff --git a/Engine/lib/openal-soft/Alc/backends/wasapi.h b/Engine/lib/openal-soft/alc/backends/wasapi.h similarity index 76% rename from Engine/lib/openal-soft/Alc/backends/wasapi.h rename to Engine/lib/openal-soft/alc/backends/wasapi.h index 97143c1ac..bb2671ee8 100644 --- a/Engine/lib/openal-soft/Alc/backends/wasapi.h +++ b/Engine/lib/openal-soft/alc/backends/wasapi.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_WASAPI_H #define BACKENDS_WASAPI_H -#include "backends/base.h" +#include "base.h" struct WasapiBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/Alc/backends/wave.cpp b/Engine/lib/openal-soft/alc/backends/wave.cpp similarity index 87% rename from Engine/lib/openal-soft/Alc/backends/wave.cpp rename to Engine/lib/openal-soft/alc/backends/wave.cpp index 4f7382306..6360166ca 100644 --- a/Engine/lib/openal-soft/Alc/backends/wave.cpp +++ b/Engine/lib/openal-soft/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(); @@ -150,29 +149,23 @@ int WaveBackend::mixerProc() mDevice->renderSamples(mBuffer.data(), mDevice->UpdateSize, frameStep); done += mDevice->UpdateSize; - if_constexpr(al::endian::native != al::endian::little) + if(al::endian::native != al::endian::little) { const uint bytesize{mDevice->bytesFromFmt()}; if(bytesize == 2) { - ushort *samples = reinterpret_cast(mBuffer.data()); - const size_t len{mBuffer.size() / 2}; - for(size_t i{0};i < len;i++) - { - const ushort samp{samples[i]}; - samples[i] = static_cast((samp>>8) | (samp<<8)); - } + const size_t len{mBuffer.size() & ~size_t{1}}; + for(size_t i{0};i < len;i+=2) + std::swap(mBuffer[i], mBuffer[i+1]); } else if(bytesize == 4) { - uint *samples = reinterpret_cast(mBuffer.data()); - const size_t len{mBuffer.size() / 4}; - for(size_t i{0};i < len;i++) + const size_t len{mBuffer.size() & ~size_t{3}}; + for(size_t i{0};i < len;i+=4) { - const uint samp{samples[i]}; - samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) | - ((samp<<8)&0x00ff0000) | (samp<<24); + std::swap(mBuffer[i ], mBuffer[i+3]); + std::swap(mBuffer[i+1], mBuffer[i+2]); } } } @@ -204,8 +197,8 @@ int WaveBackend::mixerProc() void WaveBackend::open(const char *name) { - const char *fname{GetConfigValue(nullptr, "wave", "file", "")}; - if(!fname[0]) throw al::backend_exception{al::backend_error::NoDevice, + auto fname = ConfigValueStr(nullptr, "wave", "file"); + if(!fname) throw al::backend_exception{al::backend_error::NoDevice, "No wave output filename"}; if(!name) @@ -214,17 +207,20 @@ 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->c_str())}; mFile = _wfopen(wname.c_str(), L"wb"); } #else - mFile = fopen(fname, "wb"); + mFile = fopen(fname->c_str(), "wb"); #endif if(!mFile) throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s", - fname, strerror(errno)}; + fname->c_str(), strerror(errno)}; mDevice->DeviceName = name; } @@ -267,7 +263,6 @@ bool WaveBackend::reset() case DevFmtStereo: chanmask = 0x01 | 0x02; break; case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break; case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break; - case DevFmtX51Rear: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020; break; case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break; case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; case DevFmtAmbi3D: @@ -392,7 +387,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/Engine/lib/openal-soft/Alc/backends/wave.h b/Engine/lib/openal-soft/alc/backends/wave.h similarity index 75% rename from Engine/lib/openal-soft/Alc/backends/wave.h rename to Engine/lib/openal-soft/alc/backends/wave.h index 5719961fe..e768d3361 100644 --- a/Engine/lib/openal-soft/Alc/backends/wave.h +++ b/Engine/lib/openal-soft/alc/backends/wave.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_WAVE_H #define BACKENDS_WAVE_H -#include "backends/base.h" +#include "base.h" struct WaveBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/Alc/backends/winmm.cpp b/Engine/lib/openal-soft/alc/backends/winmm.cpp similarity index 90% rename from Engine/lib/openal-soft/Alc/backends/winmm.cpp rename to Engine/lib/openal-soft/alc/backends/winmm.cpp index 9b88f12eb..0fdd8a021 100644 --- a/Engine/lib/openal-soft/Alc/backends/winmm.cpp +++ b/Engine/lib/openal-soft/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; @@ -181,8 +181,6 @@ FORCE_ALIGN int WinMMPlayback::mixerProc() SetRTPriority(); althrd_setname(MIXER_THREAD_NAME); - const size_t frame_step{mDevice->channelsFromFmt()}; - while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { @@ -196,9 +194,9 @@ FORCE_ALIGN int WinMMPlayback::mixerProc() size_t widx{mIdx}; do { WAVEHDR &waveHdr = mWaveBuffer[widx]; - widx = (widx+1) % mWaveBuffer.size(); + if(++widx == mWaveBuffer.size()) widx = 0; - mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, frame_step); + mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, mFormat.nChannels); mWritable.fetch_sub(1, std::memory_order_acq_rel); waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR)); } while(--todo); @@ -223,40 +221,47 @@ void WinMMPlayback::open(const char *name) name}; auto DeviceID = static_cast(std::distance(PlaybackDevices.cbegin(), iter)); + DevFmtType fmttype{mDevice->FmtType}; retry_open: - mFormat = WAVEFORMATEX{}; - if(mDevice->FmtType == DevFmtFloat) + WAVEFORMATEX format{}; + if(fmttype == DevFmtFloat) { - mFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; - mFormat.wBitsPerSample = 32; + format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + format.wBitsPerSample = 32; } else { - mFormat.wFormatTag = WAVE_FORMAT_PCM; - if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte) - mFormat.wBitsPerSample = 8; + format.wFormatTag = WAVE_FORMAT_PCM; + if(fmttype == DevFmtUByte || fmttype == DevFmtByte) + format.wBitsPerSample = 8; else - mFormat.wBitsPerSample = 16; + format.wBitsPerSample = 16; } - mFormat.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); - mFormat.nBlockAlign = 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) { - if(mDevice->FmtType == DevFmtFloat) + if(fmttype == DevFmtFloat) { - mDevice->FmtType = DevFmtShort; + fmttype = DevFmtShort; goto 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]; } @@ -297,7 +302,7 @@ bool WinMMPlayback::reset() } uint chanmask{}; - if(mFormat.nChannels == 2) + if(mFormat.nChannels >= 2) { mDevice->FmtChans = DevFmtStereo; chanmask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; @@ -314,7 +319,7 @@ bool WinMMPlayback::reset() } setChannelOrderFromWFXMask(chanmask); - uint BufferSize{mDevice->UpdateSize * mDevice->frameSizeFromFmt()}; + uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; al_free(mWaveBuffer[0].lpData); mWaveBuffer[0] = WAVEHDR{}; @@ -334,9 +339,8 @@ bool WinMMPlayback::reset() void WinMMPlayback::start() { try { - std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(), - [this](WAVEHDR &waveHdr) -> void - { waveOutPrepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); }); + for(auto &waveHdr : mWaveBuffer) + waveOutPrepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); mWritable.store(static_cast(mWaveBuffer.size()), std::memory_order_release); mKillNow.store(false, std::memory_order_release); @@ -356,15 +360,14 @@ void WinMMPlayback::stop() while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size()) mSem.wait(); - std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(), - [this](WAVEHDR &waveHdr) -> void - { waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); }); + for(auto &waveHdr : mWaveBuffer) + waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); mWritable.store(0, std::memory_order_release); } struct WinMMCapture final : public BackendBase { - WinMMCapture(ALCdevice *device) noexcept : BackendBase{device} { } + WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~WinMMCapture() override; void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept; @@ -471,7 +474,6 @@ void WinMMCapture::open(const char *name) case DevFmtQuad: case DevFmtX51: - case DevFmtX51Rear: case DevFmtX61: case DevFmtX71: case DevFmtAmbi3D: @@ -615,7 +617,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/Engine/lib/openal-soft/Alc/backends/winmm.h b/Engine/lib/openal-soft/alc/backends/winmm.h similarity index 75% rename from Engine/lib/openal-soft/Alc/backends/winmm.h rename to Engine/lib/openal-soft/alc/backends/winmm.h index bf09ddd9b..45a706aa3 100644 --- a/Engine/lib/openal-soft/Alc/backends/winmm.h +++ b/Engine/lib/openal-soft/alc/backends/winmm.h @@ -1,7 +1,7 @@ #ifndef BACKENDS_WINMM_H #define BACKENDS_WINMM_H -#include "backends/base.h" +#include "base.h" struct WinMMBackendFactory final : public BackendFactory { public: @@ -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/Engine/lib/openal-soft/alc/context.cpp b/Engine/lib/openal-soft/alc/context.cpp new file mode 100644 index 000000000..456c054ea --- /dev/null +++ b/Engine/lib/openal-soft/alc/context.cpp @@ -0,0 +1,1299 @@ + +#include "config.h" + +#include "context.h" + +#include +#include +#include +#include +#include +#include + +#include "AL/efx.h" + +#include "al/auxeffectslot.h" +#include "al/source.h" +#include "al/effect.h" +#include "al/event.h" +#include "al/listener.h" +#include "albit.h" +#include "alc/alu.h" +#include "core/async_event.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/logging.h" +#include "core/voice.h" +#include "core/voice_change.h" +#include "device.h" +#include "ringbuffer.h" +#include "vecmat.h" + +#ifdef ALSOFT_EAX +#include +#include + +#include "alstring.h" +#include "al/eax_exception.h" +#include "al/eax_globals.h" +#endif // ALSOFT_EAX + +namespace { + +using namespace std::placeholders; + +using voidp = void*; + +/* Default context extensions */ +constexpr ALchar alExtList[] = + "AL_EXT_ALAW " + "AL_EXT_BFORMAT " + "AL_EXT_DOUBLE " + "AL_EXT_EXPONENT_DISTANCE " + "AL_EXT_FLOAT32 " + "AL_EXT_IMA4 " + "AL_EXT_LINEAR_DISTANCE " + "AL_EXT_MCFORMATS " + "AL_EXT_MULAW " + "AL_EXT_MULAW_BFORMAT " + "AL_EXT_MULAW_MCFORMATS " + "AL_EXT_OFFSET " + "AL_EXT_source_distance_model " + "AL_EXT_SOURCE_RADIUS " + "AL_EXT_STEREO_ANGLES " + "AL_LOKI_quadriphonic " + "AL_SOFT_bformat_ex " + "AL_SOFTX_bformat_hoa " + "AL_SOFT_block_alignment " + "AL_SOFT_callback_buffer " + "AL_SOFTX_convolution_reverb " + "AL_SOFT_deferred_updates " + "AL_SOFT_direct_channels " + "AL_SOFT_direct_channels_remix " + "AL_SOFT_effect_target " + "AL_SOFT_events " + "AL_SOFT_gain_clamp_ex " + "AL_SOFTX_hold_on_disconnect " + "AL_SOFT_loop_points " + "AL_SOFTX_map_buffer " + "AL_SOFT_MSADPCM " + "AL_SOFT_source_latency " + "AL_SOFT_source_length " + "AL_SOFT_source_resampler " + "AL_SOFT_source_spatialize " + "AL_SOFT_UHJ"; + +} // namespace + + +std::atomic ALCcontext::sGlobalContext{nullptr}; + +thread_local ALCcontext *ALCcontext::sLocalContext{nullptr}; +ALCcontext::ThreadCtx::~ThreadCtx() +{ + if(ALCcontext *ctx{ALCcontext::sLocalContext}) + { + const bool result{ctx->releaseIfNoDelete()}; + ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx}, + result ? "" : ", leak detected"); + } +} +thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext; + +ALeffect ALCcontext::sDefaultEffect; + + +#ifdef __MINGW32__ +ALCcontext *ALCcontext::getThreadContext() noexcept +{ return sLocalContext; } +void ALCcontext::setThreadContext(ALCcontext *context) noexcept +{ sThreadContext.set(context); } +#endif + +ALCcontext::ALCcontext(al::intrusive_ptr device) + : ContextBase{device.get()}, mALDevice{std::move(device)} +{ +} + +ALCcontext::~ALCcontext() +{ + TRACE("Freeing context %p\n", voidp{this}); + + size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), size_t{0u}, + [](size_t cur, const SourceSubList &sublist) noexcept -> size_t + { return cur + static_cast(al::popcount(~sublist.FreeMask)); })}; + if(count > 0) + WARN("%zu Source%s not deleted\n", count, (count==1)?"":"s"); + mSourceList.clear(); + mNumSources = 0; + +#ifdef ALSOFT_EAX + eax_uninitialize(); +#endif // ALSOFT_EAX + + mDefaultSlot = nullptr; + count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), size_t{0u}, + [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t + { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); + if(count > 0) + WARN("%zu AuxiliaryEffectSlot%s not deleted\n", count, (count==1)?"":"s"); + mEffectSlotList.clear(); + mNumEffectSlots = 0; +} + +void ALCcontext::init() +{ + if(sDefaultEffect.type != AL_EFFECT_NULL && mDevice->Type == DeviceType::Playback) + { + mDefaultSlot = std::make_unique(); + aluInitEffectPanning(&mDefaultSlot->mSlot, this); + } + + EffectSlotArray *auxslots; + if(!mDefaultSlot) + auxslots = EffectSlot::CreatePtrArray(0); + else + { + auxslots = EffectSlot::CreatePtrArray(1); + (*auxslots)[0] = &mDefaultSlot->mSlot; + mDefaultSlot->mState = SlotState::Playing; + } + mActiveAuxSlots.store(auxslots, std::memory_order_relaxed); + + allocVoiceChanges(); + { + VoiceChange *cur{mVoiceChangeTail}; + while(VoiceChange *next{cur->mNext.load(std::memory_order_relaxed)}) + cur = next; + mCurrentVoiceChange.store(cur, std::memory_order_relaxed); + } + + mExtensionList = alExtList; + +#ifdef ALSOFT_EAX + eax_initialize_extensions(); +#endif // ALSOFT_EAX + + mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f}; + mParams.Matrix = alu::Matrix::Identity(); + mParams.Velocity = alu::Vector{}; + mParams.Gain = mListener.Gain; + mParams.MetersPerUnit = mListener.mMetersPerUnit; + mParams.AirAbsorptionGainHF = mAirAbsorptionGainHF; + mParams.DopplerFactor = mDopplerFactor; + mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity; + mParams.SourceDistanceModel = mSourceDistanceModel; + mParams.mDistanceModel = mDistanceModel; + + + mAsyncEvents = RingBuffer::Create(511, sizeof(AsyncEvent), false); + StartEventThrd(this); + + + allocVoices(256); + mActiveVoiceCount.store(64, std::memory_order_relaxed); +} + +bool ALCcontext::deinit() +{ + if(sLocalContext == this) + { + WARN("%p released while current on thread\n", voidp{this}); + sThreadContext.set(nullptr); + release(); + } + + ALCcontext *origctx{this}; + if(sGlobalContext.compare_exchange_strong(origctx, nullptr)) + release(); + + bool ret{}; + /* First make sure this context exists in the device's list. */ + auto *oldarray = mDevice->mContexts.load(std::memory_order_acquire); + if(auto toremove = static_cast(std::count(oldarray->begin(), oldarray->end(), this))) + { + using ContextArray = al::FlexArray; + auto alloc_ctx_array = [](const size_t count) -> ContextArray* + { + if(count == 0) return &DeviceBase::sEmptyContextArray; + return ContextArray::Create(count).release(); + }; + auto *newarray = alloc_ctx_array(oldarray->size() - toremove); + + /* Copy the current/old context handles to the new array, excluding the + * given context. + */ + std::copy_if(oldarray->begin(), oldarray->end(), newarray->begin(), + std::bind(std::not_equal_to<>{}, _1, this)); + + /* Store the new context array in the device. Wait for any current mix + * to finish before deleting the old array. + */ + mDevice->mContexts.store(newarray); + if(oldarray != &DeviceBase::sEmptyContextArray) + { + mDevice->waitForMix(); + delete oldarray; + } + + ret = !newarray->empty(); + } + else + ret = !oldarray->empty(); + + StopEventThrd(this); + + return ret; +} + +void ALCcontext::applyAllUpdates() +{ + /* Tell the mixer to stop applying updates, then wait for any active + * updating to finish, before providing updates. + */ + mHoldUpdates.store(true, std::memory_order_release); + while((mUpdateCount.load(std::memory_order_acquire)&1) != 0) { + /* busy-wait */ + } + +#ifdef ALSOFT_EAX + eax_apply_deferred(); +#endif + if(std::exchange(mPropsDirty, false)) + UpdateContextProps(this); + UpdateAllEffectSlotProps(this); + UpdateAllSourceProps(this); + + /* Now with all updates declared, let the mixer continue applying them so + * they all happen at once. + */ + mHoldUpdates.store(false, std::memory_order_release); +} + +#ifdef ALSOFT_EAX +namespace { + +class ContextException : + public EaxException +{ +public: + explicit ContextException( + const char* message) + : + EaxException{"EAX_CONTEXT", message} + { + } +}; // ContextException + + +template +void ForEachSource(ALCcontext *context, F func) +{ + for(auto &sublist : context->mSourceList) + { + uint64_t usemask{~sublist.FreeMask}; + while(usemask) + { + const int idx{al::countr_zero(usemask)}; + usemask &= ~(1_u64 << idx); + + func(sublist.Sources[idx]); + } + } +} + +} // namespace + + +bool ALCcontext::eax_is_capable() const noexcept +{ + return eax_has_enough_aux_sends(); +} + +void ALCcontext::eax_uninitialize() noexcept +{ + if (!eax_is_initialized_) + { + return; + } + + eax_is_initialized_ = true; + eax_is_tried_ = false; + + eax_fx_slots_.uninitialize(); +} + +ALenum ALCcontext::eax_eax_set( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size) +{ + eax_initialize(); + + const auto eax_call = create_eax_call( + false, + property_set_id, + property_id, + property_source_id, + property_value, + property_value_size + ); + + eax_unlock_legacy_fx_slots(eax_call); + + switch (eax_call.get_property_set_id()) + { + case EaxEaxCallPropertySetId::context: + eax_set(eax_call); + break; + + case EaxEaxCallPropertySetId::fx_slot: + case EaxEaxCallPropertySetId::fx_slot_effect: + eax_dispatch_fx_slot(eax_call); + break; + + case EaxEaxCallPropertySetId::source: + eax_dispatch_source(eax_call); + break; + + default: + eax_fail("Unsupported property set id."); + } + + static constexpr auto deferred_flag = 0x80000000u; + if(!(property_id&deferred_flag) && !mDeferUpdates) + applyAllUpdates(); + + return AL_NO_ERROR; +} + +ALenum ALCcontext::eax_eax_get( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size) +{ + eax_initialize(); + + const auto eax_call = create_eax_call( + true, + property_set_id, + property_id, + property_source_id, + property_value, + property_value_size + ); + + eax_unlock_legacy_fx_slots(eax_call); + + switch (eax_call.get_property_set_id()) + { + case EaxEaxCallPropertySetId::context: + eax_get(eax_call); + break; + + case EaxEaxCallPropertySetId::fx_slot: + case EaxEaxCallPropertySetId::fx_slot_effect: + eax_dispatch_fx_slot(eax_call); + break; + + case EaxEaxCallPropertySetId::source: + eax_dispatch_source(eax_call); + break; + + default: + eax_fail("Unsupported property set id."); + } + + return AL_NO_ERROR; +} + +void ALCcontext::eax_update_filters() +{ + ForEachSource(this, std::mem_fn(&ALsource::eax_update_filters)); +} + +void ALCcontext::eax_commit_and_update_sources() +{ + std::unique_lock source_lock{mSourceLock}; + ForEachSource(this, std::mem_fn(&ALsource::eax_commit_and_update)); +} + +void ALCcontext::eax_set_last_error() noexcept +{ + eax_last_error_ = EAXERR_INVALID_OPERATION; +} + +[[noreturn]] +void ALCcontext::eax_fail( + const char* message) +{ + throw ContextException{message}; +} + +void ALCcontext::eax_initialize_extensions() +{ + if (!eax_g_is_enabled) + { + return; + } + + const auto string_max_capacity = + std::strlen(mExtensionList) + 1 + + std::strlen(eax1_ext_name) + 1 + + std::strlen(eax2_ext_name) + 1 + + std::strlen(eax3_ext_name) + 1 + + std::strlen(eax4_ext_name) + 1 + + std::strlen(eax5_ext_name) + 1 + + std::strlen(eax_x_ram_ext_name) + 1 + + 0; + + eax_extension_list_.reserve(string_max_capacity); + + if (eax_is_capable()) + { + eax_extension_list_ += eax1_ext_name; + eax_extension_list_ += ' '; + + eax_extension_list_ += eax2_ext_name; + eax_extension_list_ += ' '; + + eax_extension_list_ += eax3_ext_name; + eax_extension_list_ += ' '; + + eax_extension_list_ += eax4_ext_name; + eax_extension_list_ += ' '; + + eax_extension_list_ += eax5_ext_name; + eax_extension_list_ += ' '; + } + + eax_extension_list_ += eax_x_ram_ext_name; + eax_extension_list_ += ' '; + + eax_extension_list_ += mExtensionList; + mExtensionList = eax_extension_list_.c_str(); +} + +void ALCcontext::eax_initialize() +{ + if (eax_is_initialized_) + { + return; + } + + if (eax_is_tried_) + { + eax_fail("No EAX."); + } + + eax_is_tried_ = true; + + if (!eax_g_is_enabled) + { + eax_fail("EAX disabled by a configuration."); + } + + eax_ensure_compatibility(); + eax_set_defaults(); + eax_set_air_absorbtion_hf(); + eax_update_speaker_configuration(); + eax_initialize_fx_slots(); + eax_initialize_sources(); + + eax_is_initialized_ = true; +} + +bool ALCcontext::eax_has_no_default_effect_slot() const noexcept +{ + return mDefaultSlot == nullptr; +} + +void ALCcontext::eax_ensure_no_default_effect_slot() const +{ + if (!eax_has_no_default_effect_slot()) + { + eax_fail("There is a default effect slot in the context."); + } +} + +bool ALCcontext::eax_has_enough_aux_sends() const noexcept +{ + return mALDevice->NumAuxSends >= EAX_MAX_FXSLOTS; +} + +void ALCcontext::eax_ensure_enough_aux_sends() const +{ + if (!eax_has_enough_aux_sends()) + { + eax_fail("Not enough aux sends."); + } +} + +void ALCcontext::eax_ensure_compatibility() +{ + eax_ensure_enough_aux_sends(); +} + +unsigned long ALCcontext::eax_detect_speaker_configuration() const +{ +#define EAX_PREFIX "[EAX_DETECT_SPEAKER_CONFIG]" + + switch(mDevice->FmtChans) + { + case DevFmtMono: return SPEAKERS_2; + case DevFmtStereo: + /* Pretend 7.1 if using UHJ output, since they both provide full + * horizontal surround. + */ + if(mDevice->mUhjEncoder) + return SPEAKERS_7; + if(mDevice->Flags.test(DirectEar)) + return HEADPHONES; + return SPEAKERS_2; + case DevFmtQuad: return SPEAKERS_4; + case DevFmtX51: return SPEAKERS_5; + case DevFmtX61: return SPEAKERS_6; + case DevFmtX71: return SPEAKERS_7; + /* This could also be HEADPHONES, since headphones-based HRTF and Ambi3D + * provide full-sphere surround sound. Depends if apps are more likely to + * consider headphones or 7.1 for surround sound support. + */ + case DevFmtAmbi3D: return SPEAKERS_7; + } + ERR(EAX_PREFIX "Unexpected device channel format 0x%x.\n", mDevice->FmtChans); + return HEADPHONES; + +#undef EAX_PREFIX +} + +void ALCcontext::eax_update_speaker_configuration() +{ + eax_speaker_config_ = eax_detect_speaker_configuration(); +} + +void ALCcontext::eax_set_last_error_defaults() noexcept +{ + eax_last_error_ = EAX_OK; +} + +void ALCcontext::eax_set_session_defaults() noexcept +{ + eax_session_.ulEAXVersion = EAXCONTEXT_MINEAXSESSION; + eax_session_.ulMaxActiveSends = EAXCONTEXT_DEFAULTMAXACTIVESENDS; +} + +void ALCcontext::eax_set_context_defaults() noexcept +{ + eax_.context.guidPrimaryFXSlotID = EAXCONTEXT_DEFAULTPRIMARYFXSLOTID; + eax_.context.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR; + eax_.context.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF; + eax_.context.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE; +} + +void ALCcontext::eax_set_defaults() noexcept +{ + eax_set_last_error_defaults(); + eax_set_session_defaults(); + eax_set_context_defaults(); + + eax_d_ = eax_; +} + +void ALCcontext::eax_unlock_legacy_fx_slots(const EaxEaxCall& eax_call) noexcept +{ + if (eax_call.get_version() != 5 || eax_are_legacy_fx_slots_unlocked_) + return; + + eax_are_legacy_fx_slots_unlocked_ = true; + eax_fx_slots_.unlock_legacy(); +} + +void ALCcontext::eax_dispatch_fx_slot( + const EaxEaxCall& eax_call) +{ + const auto fx_slot_index = eax_call.get_fx_slot_index(); + if(!fx_slot_index.has_value()) + eax_fail("Invalid fx slot index."); + + auto& fx_slot = eax_get_fx_slot(*fx_slot_index); + if(fx_slot.eax_dispatch(eax_call)) + { + std::lock_guard source_lock{mSourceLock}; + eax_update_filters(); + } +} + +void ALCcontext::eax_dispatch_source( + const EaxEaxCall& eax_call) +{ + const auto source_id = eax_call.get_property_al_name(); + + std::lock_guard source_lock{mSourceLock}; + + const auto source = ALsource::eax_lookup_source(*this, source_id); + + if (!source) + { + eax_fail("Source not found."); + } + + source->eax_dispatch(eax_call); +} + +void ALCcontext::eax_get_primary_fx_slot_id( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_.context.guidPrimaryFXSlotID); +} + +void ALCcontext::eax_get_distance_factor( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_.context.flDistanceFactor); +} + +void ALCcontext::eax_get_air_absorption_hf( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_.context.flAirAbsorptionHF); +} + +void ALCcontext::eax_get_hf_reference( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_.context.flHFReference); +} + +void ALCcontext::eax_get_last_error( + const EaxEaxCall& eax_call) +{ + const auto eax_last_error = eax_last_error_; + eax_last_error_ = EAX_OK; + eax_call.set_value(eax_last_error); +} + +void ALCcontext::eax_get_speaker_config( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_speaker_config_); +} + +void ALCcontext::eax_get_session( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_session_); +} + +void ALCcontext::eax_get_macro_fx_factor( + const EaxEaxCall& eax_call) +{ + eax_call.set_value(eax_.context.flMacroFXFactor); +} + +void ALCcontext::eax_get_context_all( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_version()) + { + case 4: + eax_call.set_value(static_cast(eax_.context)); + break; + + case 5: + eax_call.set_value(static_cast(eax_.context)); + break; + + default: + eax_fail("Unsupported EAX version."); + } +} + +void ALCcontext::eax_get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXCONTEXT_NONE: + break; + + case EAXCONTEXT_ALLPARAMETERS: + eax_get_context_all(eax_call); + break; + + case EAXCONTEXT_PRIMARYFXSLOTID: + eax_get_primary_fx_slot_id(eax_call); + break; + + case EAXCONTEXT_DISTANCEFACTOR: + eax_get_distance_factor(eax_call); + break; + + case EAXCONTEXT_AIRABSORPTIONHF: + eax_get_air_absorption_hf(eax_call); + break; + + case EAXCONTEXT_HFREFERENCE: + eax_get_hf_reference(eax_call); + break; + + case EAXCONTEXT_LASTERROR: + eax_get_last_error(eax_call); + break; + + case EAXCONTEXT_SPEAKERCONFIG: + eax_get_speaker_config(eax_call); + break; + + case EAXCONTEXT_EAXSESSION: + eax_get_session(eax_call); + break; + + case EAXCONTEXT_MACROFXFACTOR: + eax_get_macro_fx_factor(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +void ALCcontext::eax_set_primary_fx_slot_id() +{ + eax_previous_primary_fx_slot_index_ = eax_primary_fx_slot_index_; + eax_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID; +} + +void ALCcontext::eax_set_distance_factor() +{ + mListener.mMetersPerUnit = eax_.context.flDistanceFactor; + mPropsDirty = true; +} + +void ALCcontext::eax_set_air_absorbtion_hf() +{ + mAirAbsorptionGainHF = level_mb_to_gain(eax_.context.flAirAbsorptionHF); + mPropsDirty = true; +} + +void ALCcontext::eax_set_hf_reference() +{ + // TODO +} + +void ALCcontext::eax_set_macro_fx_factor() +{ + // TODO +} + +void ALCcontext::eax_set_context() +{ + eax_set_primary_fx_slot_id(); + eax_set_distance_factor(); + eax_set_air_absorbtion_hf(); + eax_set_hf_reference(); +} + +void ALCcontext::eax_initialize_fx_slots() +{ + eax_fx_slots_.initialize(*this); + eax_previous_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID; + eax_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID; +} + +void ALCcontext::eax_initialize_sources() +{ + std::unique_lock source_lock{mSourceLock}; + auto init_source = [this](ALsource &source) noexcept + { source.eax_initialize(this); }; + ForEachSource(this, init_source); +} + +void ALCcontext::eax_update_sources() +{ + std::unique_lock source_lock{mSourceLock}; + auto update_source = [this](ALsource &source) + { source.eax_update(eax_context_shared_dirty_flags_); }; + ForEachSource(this, update_source); +} + +void ALCcontext::eax_validate_primary_fx_slot_id( + const GUID& primary_fx_slot_id) +{ + if (primary_fx_slot_id != EAX_NULL_GUID && + primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot0 && + primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot0 && + primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot1 && + primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot1 && + primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot2 && + primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot2 && + primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot3 && + primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot3) + { + eax_fail("Unsupported primary FX slot id."); + } +} + +void ALCcontext::eax_validate_distance_factor( + float distance_factor) +{ + eax_validate_range( + "Distance Factor", + distance_factor, + EAXCONTEXT_MINDISTANCEFACTOR, + EAXCONTEXT_MAXDISTANCEFACTOR); +} + +void ALCcontext::eax_validate_air_absorption_hf( + float air_absorption_hf) +{ + eax_validate_range( + "Air Absorption HF", + air_absorption_hf, + EAXCONTEXT_MINAIRABSORPTIONHF, + EAXCONTEXT_MAXAIRABSORPTIONHF); +} + +void ALCcontext::eax_validate_hf_reference( + float hf_reference) +{ + eax_validate_range( + "HF Reference", + hf_reference, + EAXCONTEXT_MINHFREFERENCE, + EAXCONTEXT_MAXHFREFERENCE); +} + +void ALCcontext::eax_validate_speaker_config( + unsigned long speaker_config) +{ + switch (speaker_config) + { + case HEADPHONES: + case SPEAKERS_2: + case SPEAKERS_4: + case SPEAKERS_5: + case SPEAKERS_6: + case SPEAKERS_7: + break; + + default: + eax_fail("Unsupported speaker configuration."); + } +} + +void ALCcontext::eax_validate_session_eax_version( + unsigned long eax_version) +{ + switch (eax_version) + { + case EAX_40: + case EAX_50: + break; + + default: + eax_fail("Unsupported session EAX version."); + } +} + +void ALCcontext::eax_validate_session_max_active_sends( + unsigned long max_active_sends) +{ + eax_validate_range( + "Max Active Sends", + max_active_sends, + EAXCONTEXT_MINMAXACTIVESENDS, + EAXCONTEXT_MAXMAXACTIVESENDS); +} + +void ALCcontext::eax_validate_session( + const EAXSESSIONPROPERTIES& eax_session) +{ + eax_validate_session_eax_version(eax_session.ulEAXVersion); + eax_validate_session_max_active_sends(eax_session.ulMaxActiveSends); +} + +void ALCcontext::eax_validate_macro_fx_factor( + float macro_fx_factor) +{ + eax_validate_range( + "Macro FX Factor", + macro_fx_factor, + EAXCONTEXT_MINMACROFXFACTOR, + EAXCONTEXT_MAXMACROFXFACTOR); +} + +void ALCcontext::eax_validate_context_all( + const EAX40CONTEXTPROPERTIES& context_all) +{ + eax_validate_primary_fx_slot_id(context_all.guidPrimaryFXSlotID); + eax_validate_distance_factor(context_all.flDistanceFactor); + eax_validate_air_absorption_hf(context_all.flAirAbsorptionHF); + eax_validate_hf_reference(context_all.flHFReference); +} + +void ALCcontext::eax_validate_context_all( + const EAX50CONTEXTPROPERTIES& context_all) +{ + eax_validate_context_all(static_cast(context_all)); + eax_validate_macro_fx_factor(context_all.flMacroFXFactor); +} + +void ALCcontext::eax_defer_primary_fx_slot_id( + const GUID& primary_fx_slot_id) +{ + eax_d_.context.guidPrimaryFXSlotID = primary_fx_slot_id; + + eax_context_dirty_flags_.guidPrimaryFXSlotID = + (eax_.context.guidPrimaryFXSlotID != eax_d_.context.guidPrimaryFXSlotID); +} + +void ALCcontext::eax_defer_distance_factor( + float distance_factor) +{ + eax_d_.context.flDistanceFactor = distance_factor; + + eax_context_dirty_flags_.flDistanceFactor = + (eax_.context.flDistanceFactor != eax_d_.context.flDistanceFactor); +} + +void ALCcontext::eax_defer_air_absorption_hf( + float air_absorption_hf) +{ + eax_d_.context.flAirAbsorptionHF = air_absorption_hf; + + eax_context_dirty_flags_.flAirAbsorptionHF = + (eax_.context.flAirAbsorptionHF != eax_d_.context.flAirAbsorptionHF); +} + +void ALCcontext::eax_defer_hf_reference( + float hf_reference) +{ + eax_d_.context.flHFReference = hf_reference; + + eax_context_dirty_flags_.flHFReference = + (eax_.context.flHFReference != eax_d_.context.flHFReference); +} + +void ALCcontext::eax_defer_macro_fx_factor( + float macro_fx_factor) +{ + eax_d_.context.flMacroFXFactor = macro_fx_factor; + + eax_context_dirty_flags_.flMacroFXFactor = + (eax_.context.flMacroFXFactor != eax_d_.context.flMacroFXFactor); +} + +void ALCcontext::eax_defer_context_all( + const EAX40CONTEXTPROPERTIES& context_all) +{ + eax_defer_primary_fx_slot_id(context_all.guidPrimaryFXSlotID); + eax_defer_distance_factor(context_all.flDistanceFactor); + eax_defer_air_absorption_hf(context_all.flAirAbsorptionHF); + eax_defer_hf_reference(context_all.flHFReference); +} + +void ALCcontext::eax_defer_context_all( + const EAX50CONTEXTPROPERTIES& context_all) +{ + eax_defer_context_all(static_cast(context_all)); + eax_defer_macro_fx_factor(context_all.flMacroFXFactor); +} + +void ALCcontext::eax_defer_context_all( + const EaxEaxCall& eax_call) +{ + switch(eax_call.get_version()) + { + case 4: + { + const auto& context_all = + eax_call.get_value(); + + eax_validate_context_all(context_all); + eax_defer_context_all(context_all); + } + break; + + case 5: + { + const auto& context_all = + eax_call.get_value(); + + eax_validate_context_all(context_all); + eax_defer_context_all(context_all); + } + break; + + default: + eax_fail("Unsupported EAX version."); + } +} + +void ALCcontext::eax_defer_primary_fx_slot_id( + const EaxEaxCall& eax_call) +{ + const auto& primary_fx_slot_id = + eax_call.get_value(); + + eax_validate_primary_fx_slot_id(primary_fx_slot_id); + eax_defer_primary_fx_slot_id(primary_fx_slot_id); +} + +void ALCcontext::eax_defer_distance_factor( + const EaxEaxCall& eax_call) +{ + const auto& distance_factor = + eax_call.get_value(); + + eax_validate_distance_factor(distance_factor); + eax_defer_distance_factor(distance_factor); +} + +void ALCcontext::eax_defer_air_absorption_hf( + const EaxEaxCall& eax_call) +{ + const auto& air_absorption_hf = + eax_call.get_value(); + + eax_validate_air_absorption_hf(air_absorption_hf); + eax_defer_air_absorption_hf(air_absorption_hf); +} + +void ALCcontext::eax_defer_hf_reference( + const EaxEaxCall& eax_call) +{ + const auto& hf_reference = + eax_call.get_value(); + + eax_validate_hf_reference(hf_reference); + eax_defer_hf_reference(hf_reference); +} + +void ALCcontext::eax_set_session( + const EaxEaxCall& eax_call) +{ + const auto& eax_session = + eax_call.get_value(); + + eax_validate_session(eax_session); + + eax_session_ = eax_session; +} + +void ALCcontext::eax_defer_macro_fx_factor( + const EaxEaxCall& eax_call) +{ + const auto& macro_fx_factor = + eax_call.get_value(); + + eax_validate_macro_fx_factor(macro_fx_factor); + eax_defer_macro_fx_factor(macro_fx_factor); +} + +void ALCcontext::eax_set( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXCONTEXT_NONE: + break; + + case EAXCONTEXT_ALLPARAMETERS: + eax_defer_context_all(eax_call); + break; + + case EAXCONTEXT_PRIMARYFXSLOTID: + eax_defer_primary_fx_slot_id(eax_call); + break; + + case EAXCONTEXT_DISTANCEFACTOR: + eax_defer_distance_factor(eax_call); + break; + + case EAXCONTEXT_AIRABSORPTIONHF: + eax_defer_air_absorption_hf(eax_call); + break; + + case EAXCONTEXT_HFREFERENCE: + eax_defer_hf_reference(eax_call); + break; + + case EAXCONTEXT_LASTERROR: + eax_fail("Last error is read-only."); + + case EAXCONTEXT_SPEAKERCONFIG: + eax_fail("Speaker configuration is read-only."); + + case EAXCONTEXT_EAXSESSION: + eax_set_session(eax_call); + break; + + case EAXCONTEXT_MACROFXFACTOR: + eax_defer_macro_fx_factor(eax_call); + break; + + default: + eax_fail("Unsupported property id."); + } +} + +void ALCcontext::eax_apply_deferred() +{ + if (eax_context_dirty_flags_ == ContextDirtyFlags{}) + { + return; + } + + eax_ = eax_d_; + + if (eax_context_dirty_flags_.guidPrimaryFXSlotID) + { + eax_context_shared_dirty_flags_.primary_fx_slot_id = true; + eax_set_primary_fx_slot_id(); + } + + if (eax_context_dirty_flags_.flDistanceFactor) + { + eax_set_distance_factor(); + } + + if (eax_context_dirty_flags_.flAirAbsorptionHF) + { + eax_set_air_absorbtion_hf(); + } + + if (eax_context_dirty_flags_.flHFReference) + { + eax_set_hf_reference(); + } + + if (eax_context_dirty_flags_.flMacroFXFactor) + { + eax_set_macro_fx_factor(); + } + + if (eax_context_shared_dirty_flags_ != EaxContextSharedDirtyFlags{}) + { + eax_update_sources(); + } + + eax_context_shared_dirty_flags_ = EaxContextSharedDirtyFlags{}; + eax_context_dirty_flags_ = ContextDirtyFlags{}; +} + + +namespace +{ + + +class EaxSetException : + public EaxException +{ +public: + explicit EaxSetException( + const char* message) + : + EaxException{"EAX_SET", message} + { + } +}; // EaxSetException + + +[[noreturn]] +void eax_fail_set( + const char* message) +{ + throw EaxSetException{message}; +} + + +class EaxGetException : + public EaxException +{ +public: + explicit EaxGetException( + const char* message) + : + EaxException{"EAX_GET", message} + { + } +}; // EaxGetException + + +[[noreturn]] +void eax_fail_get( + const char* message) +{ + throw EaxGetException{message}; +} + + +} // namespace + + +FORCE_ALIGN ALenum AL_APIENTRY EAXSet( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size) noexcept +try +{ + auto context = GetContextRef(); + + if (!context) + { + eax_fail_set("No current context."); + } + + std::lock_guard prop_lock{context->mPropLock}; + + return context->eax_eax_set( + property_set_id, + property_id, + property_source_id, + property_value, + property_value_size + ); +} +catch (...) +{ + eax_log_exception(__func__); + return AL_INVALID_OPERATION; +} + +FORCE_ALIGN ALenum AL_APIENTRY EAXGet( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size) noexcept +try +{ + auto context = GetContextRef(); + + if (!context) + { + eax_fail_get("No current context."); + } + + std::lock_guard prop_lock{context->mPropLock}; + + return context->eax_eax_get( + property_set_id, + property_id, + property_source_id, + property_value, + property_value_size + ); +} +catch (...) +{ + eax_log_exception(__func__); + return AL_INVALID_OPERATION; +} +#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/alc/context.h b/Engine/lib/openal-soft/alc/context.h new file mode 100644 index 000000000..72b259e97 --- /dev/null +++ b/Engine/lib/openal-soft/alc/context.h @@ -0,0 +1,504 @@ +#ifndef ALC_CONTEXT_H +#define ALC_CONTEXT_H + +#include +#include +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "al/listener.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "atomic.h" +#include "core/context.h" +#include "intrusive_ptr.h" +#include "vector.h" + +#ifdef ALSOFT_EAX +#include "al/eax_eax_call.h" +#include "al/eax_fx_slot_index.h" +#include "al/eax_fx_slots.h" +#include "al/eax_utils.h" + + +using EaxContextSharedDirtyFlagsValue = std::uint_least8_t; + +struct EaxContextSharedDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxContextSharedDirtyFlagsValue primary_fx_slot_id : 1; +}; // EaxContextSharedDirtyFlags + + +using ContextDirtyFlagsValue = std::uint_least8_t; + +struct ContextDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + ContextDirtyFlagsValue guidPrimaryFXSlotID : 1; + ContextDirtyFlagsValue flDistanceFactor : 1; + ContextDirtyFlagsValue flAirAbsorptionHF : 1; + ContextDirtyFlagsValue flHFReference : 1; + ContextDirtyFlagsValue flMacroFXFactor : 1; +}; // ContextDirtyFlags + + +struct EaxAlIsExtensionPresentResult +{ + ALboolean is_present; + bool is_return; +}; // EaxAlIsExtensionPresentResult +#endif // ALSOFT_EAX + +struct ALeffect; +struct ALeffectslot; +struct ALsource; + +using uint = unsigned int; + + +struct SourceSubList { + uint64_t FreeMask{~0_u64}; + ALsource *Sources{nullptr}; /* 64 */ + + SourceSubList() noexcept = default; + SourceSubList(const SourceSubList&) = delete; + SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources} + { rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; } + ~SourceSubList(); + + SourceSubList& operator=(const SourceSubList&) = delete; + SourceSubList& operator=(SourceSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; } +}; + +struct EffectSlotSubList { + uint64_t FreeMask{~0_u64}; + ALeffectslot *EffectSlots{nullptr}; /* 64 */ + + EffectSlotSubList() noexcept = default; + EffectSlotSubList(const EffectSlotSubList&) = delete; + EffectSlotSubList(EffectSlotSubList&& rhs) noexcept + : FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots} + { rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; } + ~EffectSlotSubList(); + + EffectSlotSubList& operator=(const EffectSlotSubList&) = delete; + EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; } +}; + +struct ALCcontext : public al::intrusive_ref, ContextBase { + const al::intrusive_ptr mALDevice; + + /* Wet buffers used by effect slots. */ + al::vector mWetBuffers; + + + bool mPropsDirty{true}; + bool mDeferUpdates{false}; + + std::mutex mPropLock; + + std::atomic mLastError{AL_NO_ERROR}; + + DistanceModel mDistanceModel{DistanceModel::Default}; + bool mSourceDistanceModel{false}; + + float mDopplerFactor{1.0f}; + float mDopplerVelocity{1.0f}; + float mSpeedOfSound{SpeedOfSoundMetersPerSec}; + float mAirAbsorptionGainHF{AirAbsorbGainHF}; + + std::mutex mEventCbLock; + ALEVENTPROCSOFT mEventCb{}; + void *mEventParam{nullptr}; + + ALlistener mListener{}; + + al::vector mSourceList; + ALuint mNumSources{0}; + std::mutex mSourceLock; + + al::vector mEffectSlotList; + ALuint mNumEffectSlots{0u}; + std::mutex mEffectSlotLock; + + /* Default effect slot */ + std::unique_ptr mDefaultSlot; + + const char *mExtensionList{nullptr}; + + + ALCcontext(al::intrusive_ptr device); + ALCcontext(const ALCcontext&) = delete; + ALCcontext& operator=(const ALCcontext&) = delete; + ~ALCcontext(); + + void init(); + /** + * Removes the context from its device and removes it from being current on + * the running thread or globally. Returns true if other contexts still + * exist on the device. + */ + bool deinit(); + + /** + * Defers/suspends updates for the given context's listener and sources. + * This does *NOT* stop mixing, but rather prevents certain property + * changes from taking effect. mPropLock must be held when called. + */ + void deferUpdates() noexcept { mDeferUpdates = true; } + + /** + * Resumes update processing after being deferred. mPropLock must be held + * when called. + */ + void processUpdates() + { + if(std::exchange(mDeferUpdates, false)) + applyAllUpdates(); + } + + /** + * Applies all pending updates for the context, listener, effect slots, and + * sources. + */ + void applyAllUpdates(); + +#ifdef __USE_MINGW_ANSI_STDIO + [[gnu::format(gnu_printf, 3, 4)]] +#else + [[gnu::format(printf, 3, 4)]] +#endif + void setError(ALenum errorCode, const char *msg, ...); + + /* Process-wide current context */ + static std::atomic sGlobalContext; + +private: + /* Thread-local current context. */ + static thread_local ALCcontext *sLocalContext; + + /* Thread-local context handling. This handles attempting to release the + * context which may have been left current when the thread is destroyed. + */ + class ThreadCtx { + public: + ~ThreadCtx(); + void set(ALCcontext *ctx) const noexcept { sLocalContext = ctx; } + }; + static thread_local ThreadCtx sThreadContext; + +public: + /* HACK: MinGW generates bad code when accessing an extern thread_local + * object. Add a wrapper function for it that only accesses it where it's + * defined. + */ +#ifdef __MINGW32__ + static ALCcontext *getThreadContext() noexcept; + static void setThreadContext(ALCcontext *context) noexcept; +#else + static ALCcontext *getThreadContext() noexcept { return sLocalContext; } + static void setThreadContext(ALCcontext *context) noexcept { sThreadContext.set(context); } +#endif + + /* Default effect that applies to sources that don't have an effect on send 0. */ + static ALeffect sDefaultEffect; + + DEF_NEWDEL(ALCcontext) + +#ifdef ALSOFT_EAX +public: + bool has_eax() const noexcept { return eax_is_initialized_; } + + bool eax_is_capable() const noexcept; + + + void eax_uninitialize() noexcept; + + + ALenum eax_eax_set( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size); + + ALenum eax_eax_get( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size); + + + void eax_update_filters(); + + void eax_commit_and_update_sources(); + + + void eax_set_last_error() noexcept; + + + EaxFxSlotIndex eax_get_previous_primary_fx_slot_index() const noexcept + { return eax_previous_primary_fx_slot_index_; } + EaxFxSlotIndex eax_get_primary_fx_slot_index() const noexcept + { return eax_primary_fx_slot_index_; } + + const ALeffectslot& eax_get_fx_slot(EaxFxSlotIndexValue fx_slot_index) const + { return eax_fx_slots_.get(fx_slot_index); } + ALeffectslot& eax_get_fx_slot(EaxFxSlotIndexValue fx_slot_index) + { return eax_fx_slots_.get(fx_slot_index); } + + void eax_commit_fx_slots() + { eax_fx_slots_.commit(); } + +private: + struct Eax + { + EAX50CONTEXTPROPERTIES context{}; + }; // Eax + + + bool eax_is_initialized_{}; + bool eax_is_tried_{}; + bool eax_are_legacy_fx_slots_unlocked_{}; + + long eax_last_error_{}; + unsigned long eax_speaker_config_{}; + + EaxFxSlotIndex eax_previous_primary_fx_slot_index_{}; + EaxFxSlotIndex eax_primary_fx_slot_index_{}; + EaxFxSlots eax_fx_slots_{}; + + EaxContextSharedDirtyFlags eax_context_shared_dirty_flags_{}; + + Eax eax_{}; + Eax eax_d_{}; + EAXSESSIONPROPERTIES eax_session_{}; + + ContextDirtyFlags eax_context_dirty_flags_{}; + + std::string eax_extension_list_{}; + + + [[noreturn]] + static void eax_fail( + const char* message); + + + void eax_initialize_extensions(); + + void eax_initialize(); + + + bool eax_has_no_default_effect_slot() const noexcept; + + void eax_ensure_no_default_effect_slot() const; + + bool eax_has_enough_aux_sends() const noexcept; + + void eax_ensure_enough_aux_sends() const; + + void eax_ensure_compatibility(); + + + unsigned long eax_detect_speaker_configuration() const; + void eax_update_speaker_configuration(); + + + void eax_set_last_error_defaults() noexcept; + + void eax_set_session_defaults() noexcept; + + void eax_set_context_defaults() noexcept; + + void eax_set_defaults() noexcept; + + void eax_initialize_sources(); + + + void eax_unlock_legacy_fx_slots(const EaxEaxCall& eax_call) noexcept; + + + void eax_dispatch_fx_slot( + const EaxEaxCall& eax_call); + + void eax_dispatch_source( + const EaxEaxCall& eax_call); + + + void eax_get_primary_fx_slot_id( + const EaxEaxCall& eax_call); + + void eax_get_distance_factor( + const EaxEaxCall& eax_call); + + void eax_get_air_absorption_hf( + const EaxEaxCall& eax_call); + + void eax_get_hf_reference( + const EaxEaxCall& eax_call); + + void eax_get_last_error( + const EaxEaxCall& eax_call); + + void eax_get_speaker_config( + const EaxEaxCall& eax_call); + + void eax_get_session( + const EaxEaxCall& eax_call); + + void eax_get_macro_fx_factor( + const EaxEaxCall& eax_call); + + void eax_get_context_all( + const EaxEaxCall& eax_call); + + void eax_get( + const EaxEaxCall& eax_call); + + + void eax_set_primary_fx_slot_id(); + + void eax_set_distance_factor(); + + void eax_set_air_absorbtion_hf(); + + void eax_set_hf_reference(); + + void eax_set_macro_fx_factor(); + + void eax_set_context(); + + void eax_initialize_fx_slots(); + + + void eax_update_sources(); + + + void eax_validate_primary_fx_slot_id( + const GUID& primary_fx_slot_id); + + void eax_validate_distance_factor( + float distance_factor); + + void eax_validate_air_absorption_hf( + float air_absorption_hf); + + void eax_validate_hf_reference( + float hf_reference); + + void eax_validate_speaker_config( + unsigned long speaker_config); + + void eax_validate_session_eax_version( + unsigned long eax_version); + + void eax_validate_session_max_active_sends( + unsigned long max_active_sends); + + void eax_validate_session( + const EAXSESSIONPROPERTIES& eax_session); + + void eax_validate_macro_fx_factor( + float macro_fx_factor); + + void eax_validate_context_all( + const EAX40CONTEXTPROPERTIES& context_all); + + void eax_validate_context_all( + const EAX50CONTEXTPROPERTIES& context_all); + + + void eax_defer_primary_fx_slot_id( + const GUID& primary_fx_slot_id); + + void eax_defer_distance_factor( + float distance_factor); + + void eax_defer_air_absorption_hf( + float air_absorption_hf); + + void eax_defer_hf_reference( + float hf_reference); + + void eax_defer_macro_fx_factor( + float macro_fx_factor); + + void eax_defer_context_all( + const EAX40CONTEXTPROPERTIES& context_all); + + void eax_defer_context_all( + const EAX50CONTEXTPROPERTIES& context_all); + + + void eax_defer_context_all( + const EaxEaxCall& eax_call); + + void eax_defer_primary_fx_slot_id( + const EaxEaxCall& eax_call); + + void eax_defer_distance_factor( + const EaxEaxCall& eax_call); + + void eax_defer_air_absorption_hf( + const EaxEaxCall& eax_call); + + void eax_defer_hf_reference( + const EaxEaxCall& eax_call); + + void eax_set_session( + const EaxEaxCall& eax_call); + + void eax_defer_macro_fx_factor( + const EaxEaxCall& eax_call); + + void eax_set( + const EaxEaxCall& eax_call); + + void eax_apply_deferred(); +#endif // ALSOFT_EAX +}; + +#define SETERR_RETURN(ctx, err, retval, ...) do { \ + (ctx)->setError((err), __VA_ARGS__); \ + return retval; \ +} while(0) + + +using ContextRef = al::intrusive_ptr; + +ContextRef GetContextRef(void); + +void UpdateContextProps(ALCcontext *context); + + +extern bool TrapALError; + + +#ifdef ALSOFT_EAX +ALenum AL_APIENTRY EAXSet( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size) noexcept; + +ALenum AL_APIENTRY EAXGet( + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_value, + ALuint property_value_size) noexcept; +#endif // ALSOFT_EAX + +#endif /* ALC_CONTEXT_H */ diff --git a/Engine/lib/openal-soft/alc/device.cpp b/Engine/lib/openal-soft/alc/device.cpp new file mode 100644 index 000000000..e06c0d748 --- /dev/null +++ b/Engine/lib/openal-soft/alc/device.cpp @@ -0,0 +1,90 @@ + +#include "config.h" + +#include "device.h" + +#include +#include + +#include "albit.h" +#include "alconfig.h" +#include "backends/base.h" +#include "core/bformatdec.h" +#include "core/bs2b.h" +#include "core/front_stablizer.h" +#include "core/hrtf.h" +#include "core/logging.h" +#include "core/mastering.h" +#include "core/uhjfilter.h" + + +namespace { + +using voidp = void*; + +} // namespace + + +ALCdevice::ALCdevice(DeviceType type) : DeviceBase{type} +{ } + +ALCdevice::~ALCdevice() +{ + TRACE("Freeing device %p\n", voidp{this}); + + Backend = nullptr; + + size_t count{std::accumulate(BufferList.cbegin(), BufferList.cend(), size_t{0u}, + [](size_t cur, const BufferSubList &sublist) noexcept -> size_t + { return cur + static_cast(al::popcount(~sublist.FreeMask)); })}; + if(count > 0) + WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s"); + + count = std::accumulate(EffectList.cbegin(), EffectList.cend(), size_t{0u}, + [](size_t cur, const EffectSubList &sublist) noexcept -> size_t + { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); + if(count > 0) + WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s"); + + count = std::accumulate(FilterList.cbegin(), FilterList.cend(), size_t{0u}, + [](size_t cur, const FilterSubList &sublist) noexcept -> size_t + { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); + if(count > 0) + WARN("%zu Filter%s not deleted\n", count, (count==1)?"":"s"); +} + +void ALCdevice::enumerateHrtfs() +{ + mHrtfList = EnumerateHrtf(configValue(nullptr, "hrtf-paths")); + if(auto defhrtfopt = configValue(nullptr, "default-hrtf")) + { + auto iter = std::find(mHrtfList.begin(), mHrtfList.end(), *defhrtfopt); + if(iter == mHrtfList.end()) + WARN("Failed to find default HRTF \"%s\"\n", defhrtfopt->c_str()); + else if(iter != mHrtfList.begin()) + std::rotate(mHrtfList.begin(), iter, iter+1); + } +} + +auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1 +{ + if(mContexts.load(std::memory_order_relaxed)->empty()) + return OutputMode1::Any; + + switch(FmtChans) + { + case DevFmtMono: return OutputMode1::Mono; + case DevFmtStereo: + if(mHrtf) + return OutputMode1::Hrtf; + else if(mUhjEncoder) + return OutputMode1::Uhj2; + return OutputMode1::StereoBasic; + case DevFmtQuad: return OutputMode1::Quad; + case DevFmtX51: return OutputMode1::X51; + case DevFmtX61: return OutputMode1::X61; + case DevFmtX71: return OutputMode1::X71; + case DevFmtAmbi3D: break; + } + return OutputMode1::Any; +} diff --git a/Engine/lib/openal-soft/alc/device.h b/Engine/lib/openal-soft/alc/device.h new file mode 100644 index 000000000..04931a5ab --- /dev/null +++ b/Engine/lib/openal-soft/alc/device.h @@ -0,0 +1,165 @@ +#ifndef ALC_DEVICE_H +#define ALC_DEVICE_H + +#include +#include +#include +#include +#include +#include + +#include "AL/alc.h" +#include "AL/alext.h" + +#include "alconfig.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "core/device.h" +#include "inprogext.h" +#include "intrusive_ptr.h" +#include "vector.h" + +#ifdef ALSOFT_EAX +#include "al/eax_x_ram.h" +#endif // ALSOFT_EAX + +struct ALbuffer; +struct ALeffect; +struct ALfilter; +struct BackendBase; + +using uint = unsigned int; + + +struct BufferSubList { + uint64_t FreeMask{~0_u64}; + ALbuffer *Buffers{nullptr}; /* 64 */ + + BufferSubList() noexcept = default; + BufferSubList(const BufferSubList&) = delete; + BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers} + { rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; } + ~BufferSubList(); + + BufferSubList& operator=(const BufferSubList&) = delete; + BufferSubList& operator=(BufferSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; } +}; + +struct EffectSubList { + uint64_t FreeMask{~0_u64}; + ALeffect *Effects{nullptr}; /* 64 */ + + EffectSubList() noexcept = default; + EffectSubList(const EffectSubList&) = delete; + EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects} + { rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; } + ~EffectSubList(); + + EffectSubList& operator=(const EffectSubList&) = delete; + EffectSubList& operator=(EffectSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; } +}; + +struct FilterSubList { + uint64_t FreeMask{~0_u64}; + ALfilter *Filters{nullptr}; /* 64 */ + + FilterSubList() noexcept = default; + FilterSubList(const FilterSubList&) = delete; + FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters} + { rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; } + ~FilterSubList(); + + FilterSubList& operator=(const FilterSubList&) = delete; + FilterSubList& operator=(FilterSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; } +}; + + +struct ALCdevice : public al::intrusive_ref, DeviceBase { + /* This lock protects the device state (format, update size, etc) from + * being from being changed in multiple threads, or being accessed while + * being changed. It's also used to serialize calls to the backend. + */ + std::mutex StateLock; + std::unique_ptr Backend; + + ALCuint NumMonoSources{}; + ALCuint NumStereoSources{}; + + // Maximum number of sources that can be created + uint SourcesMax{}; + // Maximum number of slots that can be created + uint AuxiliaryEffectSlotMax{}; + + std::string mHrtfName; + al::vector mHrtfList; + ALCenum mHrtfStatus{ALC_FALSE}; + + enum class OutputMode1 : ALCenum { + Any = ALC_ANY_SOFT, + Mono = ALC_MONO_SOFT, + Stereo = ALC_STEREO_SOFT, + StereoBasic = ALC_STEREO_BASIC_SOFT, + Uhj2 = ALC_STEREO_UHJ_SOFT, + Hrtf = ALC_STEREO_HRTF_SOFT, + Quad = ALC_QUAD_SOFT, + X51 = ALC_SURROUND_5_1_SOFT, + X61 = ALC_SURROUND_6_1_SOFT, + X71 = ALC_SURROUND_7_1_SOFT + }; + OutputMode1 getOutputMode1() const noexcept; + + using OutputMode = OutputMode1; + + std::atomic LastError{ALC_NO_ERROR}; + + // Map of Buffers for this device + std::mutex BufferLock; + al::vector BufferList; + + // Map of Effects for this device + std::mutex EffectLock; + al::vector EffectList; + + // Map of Filters for this device + std::mutex FilterLock; + al::vector FilterList; + +#ifdef ALSOFT_EAX + ALuint eax_x_ram_free_size{eax_x_ram_max_size}; +#endif // ALSOFT_EAX + + + ALCdevice(DeviceType type); + ~ALCdevice(); + + void enumerateHrtfs(); + + bool getConfigValueBool(const char *block, const char *key, bool def) + { return GetConfigValueBool(DeviceName.c_str(), block, key, def); } + + template + al::optional configValue(const char *block, const char *key) = delete; + + DEF_NEWDEL(ALCdevice) +}; + +template<> +inline al::optional ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueStr(DeviceName.c_str(), block, key); } +template<> +inline al::optional ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueInt(DeviceName.c_str(), block, key); } +template<> +inline al::optional ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueUInt(DeviceName.c_str(), block, key); } +template<> +inline al::optional ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueFloat(DeviceName.c_str(), block, key); } +template<> +inline al::optional ALCdevice::configValue(const char *block, const char *key) +{ return ConfigValueBool(DeviceName.c_str(), block, key); } + +#endif diff --git a/Engine/lib/openal-soft/Alc/effects/autowah.cpp b/Engine/lib/openal-soft/alc/effects/autowah.cpp similarity index 87% rename from Engine/lib/openal-soft/Alc/effects/autowah.cpp rename to Engine/lib/openal-soft/alc/effects/autowah.cpp index 2577eb30c..50c4a5adc 100644 --- a/Engine/lib/openal-soft/Alc/effects/autowah.cpp +++ b/Engine/lib/openal-soft/alc/effects/autowah.cpp @@ -20,16 +20,26 @@ #include "config.h" -#include -#include - #include +#include +#include +#include +#include + +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" -#include "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)}; @@ -146,10 +156,10 @@ void AutowahState::process(const size_t samplesToDo, */ sample = peak_gain * std::fabs(samplesIn[0][i]); a = (sample > env_delay) ? attack_rate : release_rate; - env_delay = lerp(sample, env_delay, a); + env_delay = lerpf(sample, env_delay, a); /* Calculate the cos and alpha components for this sample's filter. */ - w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * al::MathDefs::Tau(); + w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * (al::numbers::pi_v*2.0f); mEnv[i].cos_w0 = std::cos(w0); mEnv[i].alpha = std::sin(w0)/(2.0f * QFactor); } diff --git a/Engine/lib/openal-soft/alc/effects/base.h b/Engine/lib/openal-soft/alc/effects/base.h new file mode 100644 index 000000000..95695857a --- /dev/null +++ b/Engine/lib/openal-soft/alc/effects/base.h @@ -0,0 +1,26 @@ +#ifndef EFFECTS_BASE_H +#define EFFECTS_BASE_H + +#include "core/effects/base.h" + + +EffectStateFactory *NullStateFactory_getFactory(void); +EffectStateFactory *ReverbStateFactory_getFactory(void); +EffectStateFactory *StdReverbStateFactory_getFactory(void); +EffectStateFactory *AutowahStateFactory_getFactory(void); +EffectStateFactory *ChorusStateFactory_getFactory(void); +EffectStateFactory *CompressorStateFactory_getFactory(void); +EffectStateFactory *DistortionStateFactory_getFactory(void); +EffectStateFactory *EchoStateFactory_getFactory(void); +EffectStateFactory *EqualizerStateFactory_getFactory(void); +EffectStateFactory *FlangerStateFactory_getFactory(void); +EffectStateFactory *FshifterStateFactory_getFactory(void); +EffectStateFactory *ModulatorStateFactory_getFactory(void); +EffectStateFactory *PshifterStateFactory_getFactory(void); +EffectStateFactory* VmorpherStateFactory_getFactory(void); + +EffectStateFactory *DedicatedStateFactory_getFactory(void); + +EffectStateFactory *ConvolutionStateFactory_getFactory(void); + +#endif /* EFFECTS_BASE_H */ diff --git a/Engine/lib/openal-soft/Alc/effects/chorus.cpp b/Engine/lib/openal-soft/alc/effects/chorus.cpp similarity index 90% rename from Engine/lib/openal-soft/Alc/effects/chorus.cpp rename to Engine/lib/openal-soft/alc/effects/chorus.cpp index 365eaf332..99a2a68a7 100644 --- a/Engine/lib/openal-soft/Alc/effects/chorus.cpp +++ b/Engine/lib/openal-soft/alc/effects/chorus.cpp @@ -21,27 +21,33 @@ #include "config.h" #include +#include #include -#include #include #include -#include "alcmain.h" -#include "alcontext.h" +#include "alc/effects/base.h" #include "almalloc.h" +#include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" -#include "alu.h" -#include "core/ambidefs.h" -#include "effects/base.h" -#include "effectslot.h" -#include "math_defs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "core/resampler_limits.h" +#include "intrusive_ptr.h" #include "opthelpers.h" #include "vector.h" namespace { +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; @@ -144,7 +150,7 @@ void ChorusState::update(const ALCcontext *Context, const EffectSlot *Slot, mLfoScale = 4.0f / static_cast(mLfoRange); break; case ChorusWaveform::Sinusoid: - mLfoScale = al::MathDefs::Tau() / static_cast(mLfoRange); + mLfoScale = al::numbers::pi_v*2.0f / static_cast(mLfoRange); break; } @@ -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 "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" + +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/Engine/lib/openal-soft/Alc/effects/convolution.cpp b/Engine/lib/openal-soft/alc/effects/convolution.cpp similarity index 88% rename from Engine/lib/openal-soft/Alc/effects/convolution.cpp rename to Engine/lib/openal-soft/alc/effects/convolution.cpp index 22311bbb7..196238fcb 100644 --- a/Engine/lib/openal-soft/Alc/effects/convolution.cpp +++ b/Engine/lib/openal-soft/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 "almalloc.h" +#include "alnumbers.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/effectslot.h" #include "core/filters/splitter.h" #include "core/fmt_traits.h" -#include "core/logging.h" -#include "effects/base.h" -#include "effectslot.h" -#include "math_defs.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" #include "polyphase_resampler.h" +#include "vector.h" namespace { @@ -78,8 +91,13 @@ void LoadSamples(double *RESTRICT dst, const al::byte *src, const size_t srcstep inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept { - if(scaletype == AmbiScaling::FuMa) return AmbiScale::FromFuMa(); - if(scaletype == AmbiScaling::SN3D) return AmbiScale::FromSN3D(); + switch(scaletype) + { + case AmbiScaling::FuMa: return AmbiScale::FromFuMa(); + case AmbiScaling::SN3D: return AmbiScale::FromSN3D(); + case AmbiScaling::UHJ: return AmbiScale::FromUHJ(); + case AmbiScaling::N3D: break; + } return AmbiScale::FromN3D(); } @@ -102,6 +120,10 @@ struct ChanMap { float elevation; }; +constexpr float Deg2Rad(float x) noexcept +{ return static_cast(al::numbers::pi / 180.0 * x); } + + using complex_d = std::complex; constexpr size_t ConvolveUpdateSize{256}; @@ -190,8 +212,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 +241,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 +338,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 @@ -327,7 +349,7 @@ void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot, * to have its own output target since the main mixing buffer won't have an * LFE channel (due to being B-Format). */ - static const ChanMap MonoMap[1]{ + static constexpr ChanMap MonoMap[1]{ { FrontCenter, 0.0f, 0.0f } }, StereoMap[2]{ { FrontLeft, Deg2Rad(-45.0f), Deg2Rad(0.0f) }, @@ -374,13 +396,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(IsBFormat(mChannels)) + { + 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,11 +443,12 @@ void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot, } else { - ALCdevice *device{context->mDevice.get()}; + DeviceBase *device{context->mDevice}; al::span chanmap{}; switch(mChannels) { case FmtMono: chanmap = MonoMap; break; + case FmtSuperStereo: case FmtStereo: chanmap = StereoMap; break; case FmtRear: chanmap = RearMap; break; case FmtQuad: chanmap = QuadMap; break; @@ -416,6 +457,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; } @@ -424,9 +468,10 @@ void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot, { auto ScaleAzimuthFront = [](float azimuth, float scale) -> float { + constexpr float half_pi{al::numbers::pi_v*0.5f}; const float abs_azi{std::fabs(azimuth)}; - if(!(abs_azi >= al::MathDefs::Pi()*0.5f)) - return std::copysign(minf(abs_azi*scale, al::MathDefs::Pi()*0.5f), azimuth); + if(!(abs_azi >= half_pi)) + return std::copysign(minf(abs_azi*scale, half_pi), azimuth); return azimuth; }; diff --git a/Engine/lib/openal-soft/Alc/effects/dedicated.cpp b/Engine/lib/openal-soft/alc/effects/dedicated.cpp similarity index 83% rename from Engine/lib/openal-soft/Alc/effects/dedicated.cpp rename to Engine/lib/openal-soft/alc/effects/dedicated.cpp index 9fee7fd7b..671eb5ecb 100644 --- a/Engine/lib/openal-soft/Alc/effects/dedicated.cpp +++ b/Engine/lib/openal-soft/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 "almalloc.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" + +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/Engine/lib/openal-soft/Alc/effects/distortion.cpp b/Engine/lib/openal-soft/alc/effects/distortion.cpp similarity index 86% rename from Engine/lib/openal-soft/Alc/effects/distortion.cpp rename to Engine/lib/openal-soft/alc/effects/distortion.cpp index 09dae4c59..74cffd4a1 100644 --- a/Engine/lib/openal-soft/Alc/effects/distortion.cpp +++ b/Engine/lib/openal-soft/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 "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" #include "core/filters/biquad.h" -#include "effectslot.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.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,19 +65,19 @@ 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), + const float edge{minf(std::sin(al::numbers::pi_v*0.5f * props->Distortion.Edge), 0.99f)}; mEdgeCoeff = 2.0f * edge / (1.0f-edge); diff --git a/Engine/lib/openal-soft/Alc/effects/echo.cpp b/Engine/lib/openal-soft/alc/effects/echo.cpp similarity index 86% rename from Engine/lib/openal-soft/Alc/effects/echo.cpp rename to Engine/lib/openal-soft/alc/effects/echo.cpp index f782055f2..5d003718c 100644 --- a/Engine/lib/openal-soft/Alc/effects/echo.cpp +++ b/Engine/lib/openal-soft/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 "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" #include "core/filters/biquad.h" -#include "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 "almalloc.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" #include "core/filters/biquad.h" -#include "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/Engine/lib/openal-soft/Alc/effects/fshifter.cpp b/Engine/lib/openal-soft/alc/effects/fshifter.cpp similarity index 76% rename from Engine/lib/openal-soft/Alc/effects/fshifter.cpp rename to Engine/lib/openal-soft/alc/effects/fshifter.cpp index 1f881f9d6..def745c40 100644 --- a/Engine/lib/openal-soft/Alc/effects/fshifter.cpp +++ b/Engine/lib/openal-soft/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 "alcomplex.h" -#include "alcontext.h" -#include "alu.h" -#include "effectslot.h" -#include "math_defs.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" namespace { +using uint = unsigned int; using complex_d = std::complex; #define HIL_SIZE 1024 @@ -51,7 +61,7 @@ std::array InitHannWindow() /* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */ for(size_t i{0};i < HIL_SIZE>>1;i++) { - constexpr double scale{al::MathDefs::Pi() / double{HIL_SIZE}}; + constexpr double scale{al::numbers::pi / double{HIL_SIZE}}; const double val{std::sin(static_cast(i+1) * scale)}; ret[i] = ret[HIL_SIZE-1-i] = val * val; } @@ -63,6 +73,7 @@ alignas(16) const std::array HannWindow = InitHannWindow(); struct FshifterState final : public EffectState { /* Effect parameters */ size_t mCount{}; + size_t mPos{}; uint mPhaseStep[2]{}; uint mPhase[2]{}; double mSign[2]{}; @@ -83,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; @@ -92,10 +103,11 @@ 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 = FIFO_LATENCY; + mCount = 0; + mPos = FIFO_LATENCY; std::fill(std::begin(mPhaseStep), std::end(mPhaseStep), 0u); std::fill(std::begin(mPhase), std::end(mPhase), 0u); @@ -112,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); @@ -160,38 +172,41 @@ void FshifterState::process(const size_t samplesToDo, const al::span::Tau())}; + const double phase{phase_idx * (al::numbers::pi*2.0 / MixerFracOne)}; BufferOut[k] = static_cast(mOutdata[k].real()*std::cos(phase) + mOutdata[k].imag()*std::sin(phase)*mSign[c]); diff --git a/Engine/lib/openal-soft/Alc/effects/modulator.cpp b/Engine/lib/openal-soft/alc/effects/modulator.cpp similarity index 85% rename from Engine/lib/openal-soft/Alc/effects/modulator.cpp rename to Engine/lib/openal-soft/alc/effects/modulator.cpp index 267c73e7e..84561f5c3 100644 --- a/Engine/lib/openal-soft/Alc/effects/modulator.cpp +++ b/Engine/lib/openal-soft/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 "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" #include "core/filters/biquad.h" -#include "effectslot.h" -#include "vecmat.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" namespace { +using uint = unsigned int; + #define MAX_UPDATE_SAMPLES 128 #define WAVEFORM_FRACBITS 24 @@ -43,7 +53,7 @@ namespace { inline float Sin(uint index) { - constexpr float scale{al::MathDefs::Tau() / WAVEFORM_FRACONE}; + constexpr float scale{al::numbers::pi_v*2.0f / WAVEFORM_FRACONE}; return std::sin(static_cast(index) * scale); } @@ -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/Engine/lib/openal-soft/Alc/effects/null.cpp b/Engine/lib/openal-soft/alc/effects/null.cpp similarity index 79% rename from Engine/lib/openal-soft/Alc/effects/null.cpp rename to Engine/lib/openal-soft/alc/effects/null.cpp index 9d5892857..cda1420ee 100644 --- a/Engine/lib/openal-soft/Alc/effects/null.cpp +++ b/Engine/lib/openal-soft/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/Engine/lib/openal-soft/Alc/effects/pshifter.cpp b/Engine/lib/openal-soft/alc/effects/pshifter.cpp similarity index 80% rename from Engine/lib/openal-soft/Alc/effects/pshifter.cpp rename to Engine/lib/openal-soft/alc/effects/pshifter.cpp index 257742ed1..aa20c660d 100644 --- a/Engine/lib/openal-soft/Alc/effects/pshifter.cpp +++ b/Engine/lib/openal-soft/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 "alcomplex.h" -#include "alcontext.h" +#include "almalloc.h" +#include "alnumbers.h" #include "alnumeric.h" -#include "alu.h" -#include "effectslot.h" -#include "math_defs.h" +#include "alspan.h" +#include "core/bufferline.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "core/mixer/defs.h" +#include "intrusive_ptr.h" + +struct ContextBase; namespace { +using uint = unsigned int; using complex_d = std::complex; #define STFT_SIZE 1024 @@ -53,7 +63,7 @@ std::array InitHannWindow() /* Create lookup table of the Hann window for the desired size, i.e. STFT_SIZE */ for(size_t i{0};i < STFT_SIZE>>1;i++) { - constexpr double scale{al::MathDefs::Pi() / double{STFT_SIZE}}; + constexpr double scale{al::numbers::pi / double{STFT_SIZE}}; const double val{std::sin(static_cast(i+1) * scale)}; ret[i] = ret[STFT_SIZE-1-i] = val * val; } @@ -71,6 +81,7 @@ struct FrequencyBin { struct PshifterState final : public EffectState { /* Effect parameters */ size_t mCount; + size_t mPos; uint mPitchShiftI; double mPitchShift; @@ -92,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; @@ -101,10 +112,11 @@ 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 = FIFO_LATENCY; + mCount = 0; + mPos = FIFO_LATENCY; mPitchShiftI = MixerFracOne; mPitchShift = 1.0; @@ -120,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}; @@ -143,16 +155,16 @@ void PshifterState::process(const size_t samplesToDo, const al::span::Tau() / OVERSAMP}; + constexpr double expected_cycles{al::numbers::pi*2.0 / OVERSAMP}; for(size_t base{0u};base < samplesToDo;) { - const size_t todo{minz(STFT_SIZE-mCount, samplesToDo-base)}; + const size_t todo{minz(STFT_STEP-mCount, samplesToDo-base)}; /* Retrieve the output samples from the FIFO and fill in the new input * samples. */ - auto fifo_iter = mFIFO.begin() + mCount; + auto fifo_iter = mFIFO.begin()+mPos + mCount; std::transform(fifo_iter, fifo_iter+todo, mBufferOut.begin()+base, [](double d) noexcept -> float { return static_cast(d); }); @@ -161,14 +173,17 @@ void PshifterState::process(const size_t samplesToDo, const al::span(k)*expected_cycles}; /* Map delta phase into +/- Pi interval */ - int qpd{double2int(tmp / al::MathDefs::Pi())}; - tmp -= al::MathDefs::Pi() * (qpd + (qpd%2)); + int qpd{double2int(tmp / al::numbers::pi)}; + tmp -= al::numbers::pi * (qpd + (qpd%2)); /* Get deviation from bin frequency from the +/- Pi interval */ tmp /= expected_cycles; @@ -229,15 +244,14 @@ void PshifterState::process(const size_t samplesToDo, const al::span -#include -#include - -#include -#include #include +#include +#include #include +#include +#include +#include -#include "alcmain.h" -#include "alcontext.h" +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumbers.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/effectslot.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 "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<(1.0/al::numbers::sqrt2); +alignas(16) constexpr float LateA2B[NUM_LINES][NUM_LINES]{ + { 0.5f, 0.5f, 0.5f, 0.5f }, + { InvSqrt2, -InvSqrt2, 0.0f, 0.0f }, + { 0.0f, 0.0f, InvSqrt2, -InvSqrt2 }, + { 0.5f, 0.5f, -0.5f, -0.5f } +}; /* The all-pass and delay lines have a variable length dependent on the * effect's density parameter, which helps alter the perceived environment @@ -379,15 +402,15 @@ struct ReverbState final : public EffectState { /* Calculated parameters which indicate if cross-fading is needed after * an update. */ - float Density{AL_EAXREVERB_DEFAULT_DENSITY}; - float Diffusion{AL_EAXREVERB_DEFAULT_DIFFUSION}; - float DecayTime{AL_EAXREVERB_DEFAULT_DECAY_TIME}; - float HFDecayTime{AL_EAXREVERB_DEFAULT_DECAY_HFRATIO * AL_EAXREVERB_DEFAULT_DECAY_TIME}; - float LFDecayTime{AL_EAXREVERB_DEFAULT_DECAY_LFRATIO * AL_EAXREVERB_DEFAULT_DECAY_TIME}; - float ModulationTime{AL_EAXREVERB_DEFAULT_MODULATION_TIME}; - float ModulationDepth{AL_EAXREVERB_DEFAULT_MODULATION_DEPTH}; - float HFReference{AL_EAXREVERB_DEFAULT_HFREFERENCE}; - float LFReference{AL_EAXREVERB_DEFAULT_LFREFERENCE}; + float Density{1.0f}; + float Diffusion{1.0f}; + float DecayTime{1.49f}; + float HFDecayTime{0.83f * 1.49f}; + float LFDecayTime{1.0f * 1.49f}; + float ModulationTime{0.25f}; + float ModulationDepth{0.0f}; + float HFReference{5000.0f}; + float LFReference{250.0f}; } mParams; /* Master effect filters */ @@ -469,13 +492,13 @@ struct ReverbState final : public EffectState { const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), todo}; for(size_t c{0u};c < NUM_LINES;c++) { - DoMixRow(tmpspan, A2B[c], mEarlySamples[0].data(), mEarlySamples[0].size()); + DoMixRow(tmpspan, EarlyA2B[c], mEarlySamples[0].data(), mEarlySamples[0].size()); MixSamples(tmpspan, samplesOut, mEarly.CurrentGain[c], mEarly.PanGain[c], counter, offset); } for(size_t c{0u};c < NUM_LINES;c++) { - DoMixRow(tmpspan, A2B[c], mLateSamples[0].data(), mLateSamples[0].size()); + DoMixRow(tmpspan, LateA2B[c], mLateSamples[0].data(), mLateSamples[0].size()); MixSamples(tmpspan, samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], counter, offset); } @@ -489,7 +512,7 @@ struct ReverbState final : public EffectState { const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), todo}; for(size_t c{0u};c < NUM_LINES;c++) { - DoMixRow(tmpspan, A2B[c], mEarlySamples[0].data(), mEarlySamples[0].size()); + DoMixRow(tmpspan, EarlyA2B[c], mEarlySamples[0].data(), mEarlySamples[0].size()); /* Apply scaling to the B-Format's HF response to "upsample" it to * higher-order output. @@ -502,7 +525,7 @@ struct ReverbState final : public EffectState { } for(size_t c{0u};c < NUM_LINES;c++) { - DoMixRow(tmpspan, A2B[c], mLateSamples[0].data(), mLateSamples[0].size()); + DoMixRow(tmpspan, LateA2B[c], mLateSamples[0].data(), mLateSamples[0].size()); const float hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; mAmbiSplitter[1][c].processHfScale(tmpspan, hfscale); @@ -527,8 +550,8 @@ struct ReverbState final : public EffectState { void lateFaded(const size_t offset, const size_t todo, const float fade, const float fadeStep); - 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; @@ -556,16 +579,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 +608,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 +631,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 +689,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 { @@ -721,7 +745,7 @@ inline float CalcDensityGain(const float a) inline void CalcMatrixCoeffs(const float diffusion, float *x, float *y) { /* The matrix is of order 4, so n is sqrt(4 - 1). */ - constexpr float n{1.73205080756887719318f/*std::sqrt(3.0f)*/}; + constexpr float n{al::numbers::sqrt3_v}; const float t{diffusion * std::atan(n)}; /* Calculate the first mixing matrix coefficient. */ @@ -770,10 +794,8 @@ void T60Filter::calcCoeffs(const float length, const float lfDecayTime, void EarlyReflections::updateLines(const float density_mult, const float diffusion, const float decayTime, const float frequency) { - constexpr float sqrt1_2{0.70710678118654752440f/*1.0f/std::sqrt(2.0f)*/}; - /* Calculate the all-pass feed-back/forward coefficient. */ - VecAp.Coeff = diffusion*diffusion * sqrt1_2; + VecAp.Coeff = diffusion*diffusion * InvSqrt2; for(size_t i{0u};i < NUM_LINES;i++) { @@ -813,15 +835,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 +856,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) / @@ -863,8 +885,7 @@ void LateReverb::updateLines(const float density_mult, const float diffusion, DensityGain[1] = CalcDensityGain(CalcDecayCoeff(length, decayTimeWeighted)); /* Calculate the all-pass feed-back/forward coefficient. */ - constexpr float sqrt1_2{0.70710678118654752440f/*1.0f/std::sqrt(2.0f)*/}; - VecAp.Coeff = diffusion*diffusion * sqrt1_2; + VecAp.Coeff = diffusion*diffusion * InvSqrt2; for(size_t i{0u};i < NUM_LINES;i++) { @@ -881,7 +902,7 @@ void LateReverb::updateLines(const float density_mult, const float diffusion, * filter for each of its four lines. Also include the average * modulation delay (depth is half the max delay in samples). */ - length += lerp(LATE_ALLPASS_LENGTHS[i], late_allpass_avg, diffusion)*density_mult + + length += lerpf(LATE_ALLPASS_LENGTHS[i], late_allpass_avg, diffusion)*density_mult + Mod.Depth[1]/frequency; /* Calculate the T60 damping coefficients for each line. */ @@ -923,8 +944,6 @@ void ReverbState::updateDelayLine(const float earlyDelay, const float lateDelay, */ alu::Matrix GetTransformFromVector(const float *vec) { - constexpr float sqrt3{1.73205080756887719318f}; - /* Normalize the panning vector according to the N3D scale, which has an * extra sqrt(3) term on the directional components. Converting from OpenAL * to B-Format also requires negating X (ACN 1) and Z (ACN 3). Note however @@ -936,9 +955,9 @@ alu::Matrix GetTransformFromVector(const float *vec) float mag{std::sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2])}; if(mag > 1.0f) { - norm[0] = vec[0] / mag * -sqrt3; - norm[1] = vec[1] / mag * sqrt3; - norm[2] = vec[2] / mag * sqrt3; + norm[0] = vec[0] / mag * -al::numbers::sqrt3_v; + norm[1] = vec[1] / mag * al::numbers::sqrt3_v; + norm[2] = vec[2] / mag * al::numbers::sqrt3_v; mag = 1.0f; } else @@ -947,9 +966,9 @@ alu::Matrix GetTransformFromVector(const float *vec) * term. There's no need to renormalize the magnitude since it would * just be reapplied in the matrix. */ - norm[0] = vec[0] * -sqrt3; - norm[1] = vec[1] * sqrt3; - norm[2] = vec[2] * sqrt3; + norm[0] = vec[0] * -al::numbers::sqrt3_v; + norm[1] = vec[1] * al::numbers::sqrt3_v; + norm[2] = vec[2] * al::numbers::sqrt3_v; } return alu::Matrix{ @@ -985,10 +1004,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 +1043,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, @@ -1408,7 +1427,7 @@ void ReverbState::earlyFaded(const size_t offset, const size_t todo, const float void Modulation::calcDelays(size_t todo) { - constexpr float inv_scale{MOD_FRACONE / al::MathDefs::Tau()}; + constexpr float inv_scale{MOD_FRACONE / al::numbers::pi_v / 2.0f}; uint idx{Index}; const uint step{Step}; const float depth{Depth[0]}; @@ -1423,7 +1442,7 @@ void Modulation::calcDelays(size_t todo) void Modulation::calcFadedDelays(size_t todo, float fadeCount, float fadeStep) { - constexpr float inv_scale{MOD_FRACONE / al::MathDefs::Tau()}; + constexpr float inv_scale{MOD_FRACONE / al::numbers::pi_v / 2.0f}; uint idx{Index}; const uint step{Step}; const float depth{Depth[0]}; @@ -1498,7 +1517,7 @@ void ReverbState::lateUnfaded(const size_t offset, const size_t todo) * samples that were acquired above, and combined with the main * delay tap. */ - mTempSamples[j][i] = lerp(out0, out1, frac)*midGain + + mTempSamples[j][i] = lerpf(out0, out1, frac)*midGain + main_delay.Line[late_delay_tap++][j]*densityGain; ++i; } while(--td); @@ -1567,8 +1586,8 @@ void ReverbState::lateFaded(const size_t offset, const size_t todo, const float const float fade1{densityStep*fadeCount}; const float gfade0{oldMidGain + oldMidStep*fadeCount}; const float gfade1{midStep*fadeCount}; - mTempSamples[j][i] = lerp(out00, out01, frac)*gfade0 + - lerp(out10, out11, frac)*gfade1 + + mTempSamples[j][i] = lerpf(out00, out01, frac)*gfade0 + + lerpf(out10, out11, frac)*gfade1 + main_delay.Line[late_delay_tap0++][j]*fade0 + main_delay.Line[late_delay_tap1++][j]*fade1; ++i; diff --git a/Engine/lib/openal-soft/Alc/effects/vmorpher.cpp b/Engine/lib/openal-soft/alc/effects/vmorpher.cpp similarity index 79% rename from Engine/lib/openal-soft/Alc/effects/vmorpher.cpp rename to Engine/lib/openal-soft/alc/effects/vmorpher.cpp index ab21439c7..edc50eb18 100644 --- a/Engine/lib/openal-soft/Alc/effects/vmorpher.cpp +++ b/Engine/lib/openal-soft/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 "math_defs.h" +#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/ambidefs.h" +#include "core/bufferline.h" +#include "core/context.h" +#include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/mixer.h" +#include "intrusive_ptr.h" namespace { +using uint = unsigned int; + #define MAX_UPDATE_SAMPLES 256 #define NUM_FORMANTS 4 #define NUM_FILTERS 2 @@ -48,7 +71,7 @@ namespace { inline float Sin(uint index) { - constexpr float scale{al::MathDefs::Tau() / WAVEFORM_FRACONE}; + constexpr float scale{al::numbers::pi_v*2.0f / WAVEFORM_FRACONE}; return std::sin(static_cast(index) * scale)*0.5f + 0.5f; } @@ -80,7 +103,7 @@ struct FormantFilter FormantFilter() = default; FormantFilter(float f0norm, float gain) - : mCoeff{std::tan(al::MathDefs::Pi() * f0norm)}, mGain{gain} + : mCoeff{std::tan(al::numbers::pi_v * f0norm)}, mGain{gain} { } inline void process(const float *samplesIn, float *samplesOut, const size_t numInput) @@ -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})); @@ -287,7 +310,7 @@ void VmorpherState::process(const size_t samplesToDo, const al::spanCurrentGains, chandata->TargetGains, diff --git a/Engine/lib/openal-soft/Alc/inprogext.h b/Engine/lib/openal-soft/alc/inprogext.h similarity index 65% rename from Engine/lib/openal-soft/Alc/inprogext.h rename to Engine/lib/openal-soft/alc/inprogext.h index ea27a531c..9af80f12e 100644 --- a/Engine/lib/openal-soft/Alc/inprogext.h +++ b/Engine/lib/openal-soft/alc/inprogext.h @@ -28,23 +28,6 @@ AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, A #endif #endif -#ifndef AL_SOFT_callback_buffer -#define AL_SOFT_callback_buffer -#define AL_BUFFER_CALLBACK_FUNCTION_SOFT 0x19A0 -#define AL_BUFFER_CALLBACK_USER_PARAM_SOFT 0x19A1 -typedef ALsizei (AL_APIENTRY*LPALBUFFERCALLBACKTYPESOFT)(ALvoid *userptr, ALvoid *sampledata, ALsizei numsamples); -typedef void (AL_APIENTRY*LPALBUFFERCALLBACKSOFT)(ALuint buffer, ALenum format, ALsizei freq, LPALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr, ALbitfieldSOFT flags); -typedef void (AL_APIENTRY*LPALGETBUFFERPTRSOFT)(ALuint buffer, ALenum param, ALvoid **value); -typedef void (AL_APIENTRY*LPALGETBUFFER3PTRSOFT)(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3); -typedef void (AL_APIENTRY*LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid **values); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, LPALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr, ALbitfieldSOFT flags); -AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr); -AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2); -AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **ptr); -#endif -#endif - #ifndef AL_SOFT_bformat_hoa #define AL_SOFT_bformat_hoa #define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D @@ -66,6 +49,23 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint * #endif #endif +#ifndef AL_SOFT_hold_on_disconnect +#define AL_SOFT_hold_on_disconnect +#define AL_STOP_SOURCES_ON_DISCONNECT_SOFT 0x19AB +#endif + + +/* Non-standard export. Not part of any extension. */ +AL_API const ALchar* AL_APIENTRY alsoft_get_version(void); + + +/* Functions from abandoned extenions. Only here for binary compatibility. */ +AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb, + const ALuint *buffers); + +AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname); +AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/Engine/lib/openal-soft/Alc/panning.cpp b/Engine/lib/openal-soft/alc/panning.cpp similarity index 51% rename from Engine/lib/openal-soft/Alc/panning.cpp rename to Engine/lib/openal-soft/alc/panning.cpp index 1ac3bb044..00bf56625 100644 --- a/Engine/lib/openal-soft/Alc/panning.cpp +++ b/Engine/lib/openal-soft/alc/panning.cpp @@ -38,25 +38,26 @@ #include "AL/alext.h" #include "al/auxeffectslot.h" -#include "alcmain.h" +#include "albit.h" #include "alconfig.h" -#include "alcontext.h" +#include "alc/context.h" #include "almalloc.h" +#include "alnumbers.h" #include "alnumeric.h" #include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "alu.h" -#include "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 "math_defs.h" +#include "device.h" #include "opthelpers.h" @@ -132,88 +133,77 @@ void AllocChannels(ALCdevice *device, const size_t main_chans, const size_t real } -struct ChannelMap { - Channel ChanName; - float Config[MaxAmbi2DChannels]; +using ChannelCoeffs = std::array; +enum DecoderMode : bool { + SingleBand = false, + DualBand = true }; -bool MakeSpeakerMap(ALCdevice *device, const AmbDecConf *conf, uint (&speakermap)[MAX_OUTPUT_CHANNELS]) -{ - auto map_spkr = [device](const AmbDecConf::SpeakerConf &speaker) -> uint +template +struct DecoderConfig; + +template +struct DecoderConfig { + uint8_t mOrder{}; + bool mIs3D{}; + std::array mChannels{}; + DevAmbiScaling mScaling{}; + std::array mOrderGain{}; + std::array mCoeffs{}; +}; + +template +struct DecoderConfig { + uint8_t mOrder{}; + bool mIs3D{}; + std::array mChannels{}; + DevAmbiScaling mScaling{}; + std::array mOrderGain{}; + std::array mCoeffs{}; + std::array mOrderGainLF{}; + std::array mCoeffsLF{}; +}; + +template<> +struct DecoderConfig { + uint8_t mOrder{}; + bool mIs3D{}; + al::span mChannels; + DevAmbiScaling mScaling{}; + al::span mOrderGain; + al::span mCoeffs; + al::span mOrderGainLF; + al::span mCoeffsLF; + + template + DecoderConfig& operator=(const DecoderConfig &rhs) noexcept { - /* NOTE: AmbDec does not define any standard speaker names, however - * for this to work we have to by able to find the output channel - * the speaker definition corresponds to. Therefore, OpenAL Soft - * requires these channel labels to be recognized: - * - * LF = Front left - * RF = Front right - * LS = Side left - * RS = Side right - * LB = Back left - * RB = Back right - * CE = Front center - * CB = Back center - * - * Additionally, surround51 will acknowledge back speakers for side - * channels, and surround51rear will acknowledge side speakers for - * back channels, to avoid issues with an ambdec expecting 5.1 to - * use the side channels when the device is configured for back, - * and vice-versa. - */ - Channel ch{}; - if(speaker.Name == "LF") - ch = FrontLeft; - else if(speaker.Name == "RF") - ch = FrontRight; - else if(speaker.Name == "CE") - ch = FrontCenter; - else if(speaker.Name == "LS") - { - if(device->FmtChans == DevFmtX51Rear) - ch = BackLeft; - else - ch = SideLeft; - } - else if(speaker.Name == "RS") - { - if(device->FmtChans == DevFmtX51Rear) - ch = BackRight; - else - ch = SideRight; - } - else if(speaker.Name == "LB") - { - if(device->FmtChans == DevFmtX51) - ch = SideLeft; - else - ch = BackLeft; - } - else if(speaker.Name == "RB") - { - if(device->FmtChans == DevFmtX51) - ch = SideRight; - else - ch = BackRight; - } - else if(speaker.Name == "CB") - ch = BackCenter; - else - { - ERR("AmbDec speaker label \"%s\" not recognized\n", speaker.Name.c_str()); - return INVALID_CHANNEL_INDEX; - } - const uint chidx{GetChannelIdxByName(device->RealOut, ch)}; - if(chidx == INVALID_CHANNEL_INDEX) - ERR("Failed to lookup AmbDec speaker label %s\n", speaker.Name.c_str()); - return chidx; - }; - std::transform(conf->Speakers.get(), conf->Speakers.get()+conf->NumSpeakers, - std::begin(speakermap), map_spkr); - /* Return success if no invalid entries are found. */ - auto spkrmap_end = std::begin(speakermap) + conf->NumSpeakers; - return std::find(std::begin(speakermap), spkrmap_end, INVALID_CHANNEL_INDEX) == spkrmap_end; -} + mOrder = rhs.mOrder; + mIs3D = rhs.mIs3D; + mChannels = rhs.mChannels; + mScaling = rhs.mScaling; + mOrderGain = rhs.mOrderGain; + mCoeffs = rhs.mCoeffs; + mOrderGainLF = {}; + mCoeffsLF = {}; + return *this; + } + + template + DecoderConfig& operator=(const DecoderConfig &rhs) noexcept + { + mOrder = rhs.mOrder; + mIs3D = rhs.mIs3D; + mChannels = rhs.mChannels; + mScaling = rhs.mScaling; + mOrderGain = rhs.mOrderGain; + mCoeffs = rhs.mCoeffs; + mOrderGainLF = rhs.mOrderGainLF; + mCoeffsLF = rhs.mCoeffsLF; + return *this; + } +}; +using DecoderView = DecoderConfig; void InitNearFieldCtrl(ALCdevice *device, float ctrl_dist, uint order, bool is3d) @@ -222,38 +212,41 @@ void InitNearFieldCtrl(ALCdevice *device, float ctrl_dist, uint order, bool is3d static const uint chans_per_order3d[MaxAmbiOrder+1]{ 1, 3, 5, 7 }; /* NFC is only used when AvgSpeakerDist is greater than 0. */ - const char *devname{device->DeviceName.c_str()}; - if(!GetConfigValueBool(devname, "decoder", "nfc", 0) || !(ctrl_dist > 0.0f)) + if(!device->getConfigValueBool("decoder", "nfc", 0) || !(ctrl_dist > 0.0f)) return; device->AvgSpeakerDist = clampf(ctrl_dist, 0.1f, 10.0f); TRACE("Using near-field reference distance: %.2f meters\n", device->AvgSpeakerDist); + const float w1{SpeedOfSoundMetersPerSec / + (device->AvgSpeakerDist * static_cast(device->Frequency))}; + device->mNFCtrlFilter.init(w1); + auto iter = std::copy_n(is3d ? chans_per_order3d : chans_per_order2d, order+1u, std::begin(device->NumChannelsPerOrder)); std::fill(iter, std::end(device->NumChannelsPerOrder), 0u); } -void InitDistanceComp(ALCdevice *device, const AmbDecConf *conf, - const uint (&speakermap)[MAX_OUTPUT_CHANNELS]) +void InitDistanceComp(ALCdevice *device, const al::span channels, + const al::span dists) { - auto get_max = std::bind(maxf, _1, - std::bind(std::mem_fn(&AmbDecConf::SpeakerConf::Distance), _2)); - const float maxdist{std::accumulate(conf->Speakers.get(), - conf->Speakers.get()+conf->NumSpeakers, 0.0f, get_max)}; + const float maxdist{std::accumulate(std::begin(dists), std::end(dists), 0.0f, maxf)}; - const char *devname{device->DeviceName.c_str()}; - if(!GetConfigValueBool(devname, "decoder", "distance-comp", 1) || !(maxdist > 0.0f)) + if(!device->getConfigValueBool("decoder", "distance-comp", 1) || !(maxdist > 0.0f)) return; const auto distSampleScale = static_cast(device->Frequency) / SpeedOfSoundMetersPerSec; std::vector ChanDelay; + ChanDelay.reserve(device->RealOut.Buffer.size()); size_t total{0u}; - ChanDelay.reserve(conf->NumSpeakers + 1); - for(size_t i{0u};i < conf->NumSpeakers;i++) + for(size_t chidx{0};chidx < channels.size();++chidx) { - const AmbDecConf::SpeakerConf &speaker = conf->Speakers[i]; - const uint chan{speakermap[i]}; + const Channel ch{channels[chidx]}; + const uint idx{device->RealOut.ChannelIndex[ch]}; + if(idx == INVALID_CHANNEL_INDEX) + continue; + + const float distance{dists[chidx]}; /* Distance compensation only delays in steps of the sample rate. This * is a bit less accurate since the delay time falls to the nearest @@ -261,32 +254,31 @@ void InitDistanceComp(ALCdevice *device, const AmbDecConf *conf, * phase offsets. This means at 48khz, for instance, the distance delay * will be in steps of about 7 millimeters. */ - float delay{std::floor((maxdist - speaker.Distance)*distSampleScale + 0.5f)}; + float delay{std::floor((maxdist - distance)*distSampleScale + 0.5f)}; if(delay > float{MAX_DELAY_LENGTH-1}) { - ERR("Delay for speaker \"%s\" exceeds buffer length (%f > %d)\n", - speaker.Name.c_str(), delay, MAX_DELAY_LENGTH-1); + ERR("Delay for channel %u (%s) exceeds buffer length (%f > %d)\n", idx, + GetLabelFromChannel(ch), delay, MAX_DELAY_LENGTH-1); delay = float{MAX_DELAY_LENGTH-1}; } - ChanDelay.resize(maxz(ChanDelay.size(), chan+1)); - ChanDelay[chan].Length = static_cast(delay); - ChanDelay[chan].Gain = speaker.Distance / maxdist; - TRACE("Channel %u \"%s\" distance compensation: %u samples, %f gain\n", chan, - speaker.Name.c_str(), ChanDelay[chan].Length, ChanDelay[chan].Gain); + ChanDelay.resize(maxz(ChanDelay.size(), idx+1)); + ChanDelay[idx].Length = static_cast(delay); + ChanDelay[idx].Gain = distance / maxdist; + TRACE("Channel %s distance comp: %u samples, %f gain\n", GetLabelFromChannel(ch), + ChanDelay[idx].Length, ChanDelay[idx].Gain); /* Round up to the next 4th sample, so each channel buffer starts * 16-byte aligned. */ - total += RoundUp(ChanDelay[chan].Length, 4); + total += RoundUp(ChanDelay[idx].Length, 4); } if(total > 0) { auto chandelays = DistanceComp::Create(total); - std::copy(ChanDelay.cbegin(), ChanDelay.cend(), chandelays->mChannels.begin()); - chandelays->mChannels[0].Buffer = chandelays->mSamples.data(); + ChanDelay[0].Buffer = chandelays->mSamples.data(); auto set_bufptr = [](const DistanceComp::ChanData &last, const DistanceComp::ChanData &cur) -> DistanceComp::ChanData { @@ -294,7 +286,8 @@ void InitDistanceComp(ALCdevice *device, const AmbDecConf *conf, ret.Buffer = last.Buffer + RoundUp(last.Length, 4); return ret; }; - std::partial_sum(ChanDelay.begin(), ChanDelay.end(), ChanDelay.begin(), set_bufptr); + std::partial_sum(ChanDelay.begin(), ChanDelay.end(), chandelays->mChannels.begin(), + set_bufptr); device->ChannelDelays = std::move(chandelays); } } @@ -314,75 +307,158 @@ inline auto& GetAmbiLayout(DevAmbiLayout layouttype) noexcept } -using ChannelCoeffs = std::array; -enum DecoderMode : bool { - SingleBand = false, - DualBand = true -}; +DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, + DecoderConfig &decoder) +{ + DecoderView ret{}; -template -struct DecoderConfig; + decoder.mOrder = (conf->ChanMask > Ambi2OrderMask) ? uint8_t{3} : + (conf->ChanMask > Ambi1OrderMask) ? uint8_t{2} : uint8_t{1}; + decoder.mIs3D = (conf->ChanMask&AmbiPeriphonicMask) != 0; -template -struct DecoderConfig { - uint mOrder; - std::array mChannels; - std::array mOrderGain; - std::array mCoeffs; -}; - -template -struct DecoderConfig { - uint mOrder; - std::array mChannels; - std::array mOrderGain; - std::array mCoeffs; - std::array mOrderGainLF; - std::array mCoeffsLF; -}; - -template<> -struct DecoderConfig { - uint mOrder; - al::span mChannels; - al::span mOrderGain; - al::span mCoeffs; - al::span mOrderGainLF; - al::span mCoeffsLF; - - template - DecoderConfig& operator=(const DecoderConfig &rhs) noexcept + switch(conf->CoeffScale) { - mOrder = rhs.mOrder; - mChannels = rhs.mChannels; - mOrderGain = rhs.mOrderGain; - mCoeffs = rhs.mCoeffs; - mOrderGainLF = {}; - mCoeffsLF = {}; - return *this; + case AmbDecScale::N3D: decoder.mScaling = DevAmbiScaling::N3D; break; + case AmbDecScale::SN3D: decoder.mScaling = DevAmbiScaling::SN3D; break; + case AmbDecScale::FuMa: decoder.mScaling = DevAmbiScaling::FuMa; break; } - template - DecoderConfig& operator=(const DecoderConfig &rhs) noexcept + std::copy_n(std::begin(conf->HFOrderGain), + std::min(al::size(conf->HFOrderGain), al::size(decoder.mOrderGain)), + std::begin(decoder.mOrderGain)); + std::copy_n(std::begin(conf->LFOrderGain), + std::min(al::size(conf->LFOrderGain), al::size(decoder.mOrderGainLF)), + std::begin(decoder.mOrderGainLF)); + + std::array idx_map{}; + if(decoder.mIs3D) { - mOrder = rhs.mOrder; - mChannels = rhs.mChannels; - mOrderGain = rhs.mOrderGain; - mCoeffs = rhs.mCoeffs; - mOrderGainLF = rhs.mOrderGainLF; - mCoeffsLF = rhs.mCoeffsLF; - return *this; + uint flags{conf->ChanMask}; + auto elem = idx_map.begin(); + while(flags) + { + int acn{al::countr_zero(flags)}; + flags &= ~(1u<(acn); + ++elem; + } } -}; -using DecoderView = DecoderConfig; + else + { + uint flags{conf->ChanMask}; + auto elem = idx_map.begin(); + while(flags) + { + int acn{al::countr_zero(flags)}; + flags &= ~(1u<(al::popcount(conf->ChanMask)); + const auto hfmatrix = conf->HFMatrix; + const auto lfmatrix = conf->LFMatrix; + + uint chan_count{0}; + using const_speaker_span = al::span; + for(auto &speaker : const_speaker_span{conf->Speakers.get(), conf->NumSpeakers}) + { + /* NOTE: AmbDec does not define any standard speaker names, however + * for this to work we have to by able to find the output channel + * the speaker definition corresponds to. Therefore, OpenAL Soft + * requires these channel labels to be recognized: + * + * LF = Front left + * RF = Front right + * LS = Side left + * RS = Side right + * LB = Back left + * RB = Back right + * CE = Front center + * CB = Back center + * + * Additionally, surround51 will acknowledge back speakers for side + * channels, to avoid issues with an ambdec expecting 5.1 to use the + * back channels. + */ + Channel ch{}; + if(speaker.Name == "LF") + ch = FrontLeft; + else if(speaker.Name == "RF") + ch = FrontRight; + else if(speaker.Name == "CE") + ch = FrontCenter; + else if(speaker.Name == "LS") + ch = SideLeft; + else if(speaker.Name == "RS") + ch = SideRight; + else if(speaker.Name == "LB") + ch = (device->FmtChans == DevFmtX51) ? SideLeft : BackLeft; + else if(speaker.Name == "RB") + ch = (device->FmtChans == DevFmtX51) ? SideRight : BackRight; + else if(speaker.Name == "CB") + ch = BackCenter; + else + { + ERR("AmbDec speaker label \"%s\" not recognized\n", speaker.Name.c_str()); + continue; + } + + decoder.mChannels[chan_count] = ch; + for(size_t src{0};src < num_coeffs;++src) + { + const size_t dst{idx_map[src]}; + decoder.mCoeffs[chan_count][dst] = hfmatrix[chan_count][src]; + } + if(conf->FreqBands > 1) + { + for(size_t src{0};src < num_coeffs;++src) + { + const size_t dst{idx_map[src]}; + decoder.mCoeffsLF[chan_count][dst] = lfmatrix[chan_count][src]; + } + } + ++chan_count; + } + + if(chan_count > 0) + { + ret.mOrder = decoder.mOrder; + ret.mIs3D = decoder.mIs3D; + ret.mScaling = decoder.mScaling; + ret.mChannels = {decoder.mChannels.data(), chan_count}; + ret.mOrderGain = decoder.mOrderGain; + ret.mCoeffs = {decoder.mCoeffs.data(), chan_count}; + if(conf->FreqBands > 1) + { + ret.mOrderGainLF = decoder.mOrderGainLF; + ret.mCoeffsLF = {decoder.mCoeffsLF.data(), chan_count}; + } + } + return ret; +} constexpr DecoderConfig MonoConfig{ - 0, {{FrontCenter}}, + 0, false, {{FrontCenter}}, + DevAmbiScaling::N3D, {{1.0f}}, {{ {{1.0f}} }} }; constexpr DecoderConfig StereoConfig{ - 1, {{FrontLeft, FrontRight}}, + 1, false, {{FrontLeft, FrontRight}}, + DevAmbiScaling::N3D, {{1.0f, 1.0f}}, {{ {{5.00000000e-1f, 2.88675135e-1f, 5.52305643e-2f}}, @@ -390,7 +466,8 @@ constexpr DecoderConfig StereoConfig{ }} }; constexpr DecoderConfig QuadConfig{ - 2, {{BackLeft, FrontLeft, FrontRight, BackRight}}, + 2, false, {{BackLeft, FrontLeft, FrontRight, BackRight}}, + DevAmbiScaling::N3D, /*HF*/{{1.15470054e+0f, 1.00000000e+0f, 5.77350269e-1f}}, {{ {{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f, -1.29099445e-1f, 0.00000000e+0f}}, @@ -406,28 +483,29 @@ constexpr DecoderConfig QuadConfig{ {{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f, 1.29099445e-1f, 0.00000000e+0f}}, }} }; -constexpr DecoderConfig X51Config{ - 2, {{SideLeft, FrontLeft, FrontRight, SideRight}}, - {{1.0f, 1.0f, 1.0f}}, +constexpr DecoderConfig X51Config{ + 2, false, {{SideLeft, FrontLeft, FrontCenter, FrontRight, SideRight}}, + DevAmbiScaling::FuMa, + /*HF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, {{ - {{3.33000782e-1f, 1.89084803e-1f, -2.00042375e-1f, -2.12307769e-2f, -1.14579885e-2f}}, - {{1.88542860e-1f, 1.27709292e-1f, 1.66295695e-1f, 7.30571517e-2f, 2.10901184e-2f}}, - {{1.88542860e-1f, -1.27709292e-1f, 1.66295695e-1f, -7.30571517e-2f, 2.10901184e-2f}}, - {{3.33000782e-1f, -1.89084803e-1f, -2.00042375e-1f, 2.12307769e-2f, -1.14579885e-2f}}, - }} -}; -constexpr DecoderConfig X51RearConfig{ - 2, {{BackLeft, FrontLeft, FrontRight, BackRight}}, - {{1.0f, 1.0f, 1.0f}}, + {{5.67316000e-1f, 4.22920000e-1f, -3.15495000e-1f, -6.34490000e-2f, -2.92380000e-2f}}, + {{3.68584000e-1f, 2.72349000e-1f, 3.21616000e-1f, 1.92645000e-1f, 4.82600000e-2f}}, + {{1.83579000e-1f, 0.00000000e+0f, 1.99588000e-1f, 0.00000000e+0f, 9.62820000e-2f}}, + {{3.68584000e-1f, -2.72349000e-1f, 3.21616000e-1f, -1.92645000e-1f, 4.82600000e-2f}}, + {{5.67316000e-1f, -4.22920000e-1f, -3.15495000e-1f, 6.34490000e-2f, -2.92380000e-2f}}, + }}, + /*LF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, {{ - {{3.33000782e-1f, 1.89084803e-1f, -2.00042375e-1f, -2.12307769e-2f, -1.14579885e-2f}}, - {{1.88542860e-1f, 1.27709292e-1f, 1.66295695e-1f, 7.30571517e-2f, 2.10901184e-2f}}, - {{1.88542860e-1f, -1.27709292e-1f, 1.66295695e-1f, -7.30571517e-2f, 2.10901184e-2f}}, - {{3.33000782e-1f, -1.89084803e-1f, -2.00042375e-1f, 2.12307769e-2f, -1.14579885e-2f}}, + {{4.90109850e-1f, 3.77305010e-1f, -3.73106990e-1f, -1.25914530e-1f, 1.45133000e-2f}}, + {{1.49085730e-1f, 3.03561680e-1f, 1.53290060e-1f, 2.45112480e-1f, -1.50753130e-1f}}, + {{1.37654920e-1f, 0.00000000e+0f, 4.49417940e-1f, 0.00000000e+0f, 2.57844070e-1f}}, + {{1.49085730e-1f, -3.03561680e-1f, 1.53290060e-1f, -2.45112480e-1f, -1.50753130e-1f}}, + {{4.90109850e-1f, -3.77305010e-1f, -3.73106990e-1f, 1.25914530e-1f, 1.45133000e-2f}}, }} }; constexpr DecoderConfig X61Config{ - 2, {{SideLeft, FrontLeft, FrontRight, SideRight, BackCenter}}, + 2, false, {{SideLeft, FrontLeft, FrontRight, SideRight, BackCenter}}, + DevAmbiScaling::N3D, {{1.0f, 1.0f, 1.0f}}, {{ {{2.04460341e-1f, 2.17177926e-1f, -4.39996780e-2f, -2.60790269e-2f, -6.87239792e-2f}}, @@ -438,7 +516,8 @@ constexpr DecoderConfig X61Config{ }} }; constexpr DecoderConfig X71Config{ - 3, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight}}, + 3, false, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight}}, + DevAmbiScaling::N3D, /*HF*/{{1.22474487e+0f, 1.13151672e+0f, 8.66025404e-1f, 4.68689571e-1f}}, {{ {{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f, 7.96819073e-2f, 0.00000000e+0f}}, @@ -459,172 +538,89 @@ constexpr DecoderConfig X71Config{ }} }; -void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=false) +void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=false, + DecoderView decoder={}) { - DecoderView decoder{}; - switch(device->FmtChans) + if(!decoder.mOrder) { - case DevFmtMono: - decoder = MonoConfig; - break; - case DevFmtStereo: - decoder = StereoConfig; - break; - case DevFmtQuad: - decoder = QuadConfig; - break; - case DevFmtX51: - decoder = X51Config; - break; - case DevFmtX51Rear: - decoder = X51RearConfig; - break; - case DevFmtX61: - decoder = X61Config; - break; - case DevFmtX71: - 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); - - /* For DevFmtAmbi3D, the ambisonic order is already set. */ - const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)}; - std::transform(acnmap.begin(), acnmap.begin()+count, std::begin(device->Dry.AmbiMap), - [&n3dscale](const uint8_t &acn) noexcept -> BFChannelConfig - { return BFChannelConfig{1.0f/n3dscale[acn], acn}; } - ); - AllocChannels(device, count, 0); - - float nfc_delay{ConfigValueFloat(devname, "decoder", "nfc-ref-delay").value_or(0.0f)}; - if(nfc_delay > 0.0f) - InitNearFieldCtrl(device, nfc_delay * SpeedOfSoundMetersPerSec, device->mAmbiOrder, - true); - } - else - { - const bool dual_band{hqdec && !decoder.mCoeffsLF.empty()}; - al::vector chancoeffs, chancoeffslf; - for(size_t i{0u};i < decoder.mChannels.size();++i) + switch(device->FmtChans) { - 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; - } + case DevFmtMono: decoder = MonoConfig; break; + case DevFmtStereo: decoder = StereoConfig; break; + case DevFmtQuad: decoder = QuadConfig; break; + case DevFmtX51: decoder = X51Config; break; + case DevFmtX61: decoder = X61Config; break; + case DevFmtX71: decoder = X71Config; break; + case DevFmtAmbi3D: + auto&& acnmap = GetAmbiLayout(device->mAmbiLayout); + auto&& n3dscale = GetAmbiScales(device->mAmbiScale); - 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; + /* For DevFmtAmbi3D, the ambisonic order is already set. */ + const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)}; + std::transform(acnmap.begin(), acnmap.begin()+count, std::begin(device->Dry.AmbiMap), + [&n3dscale](const uint8_t &acn) noexcept -> BFChannelConfig + { return BFChannelConfig{1.0f/n3dscale[acn], acn}; }); + AllocChannels(device, count, 0); - 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); - } + float nfc_delay{device->configValue("decoder", "nfc-ref-delay").value_or(0.0f)}; + if(nfc_delay > 0.0f) + InitNearFieldCtrl(device, nfc_delay * SpeedOfSoundMetersPerSec, device->mAmbiOrder, + true); + return; + } + } + + const bool dual_band{hqdec && !decoder.mCoeffsLF.empty()}; + al::vector chancoeffs, chancoeffslf; + for(size_t i{0u};i < decoder.mChannels.size();++i) + { + const uint idx{GetChannelIdxByName(device->RealOut, decoder.mChannels[i])}; + if(idx == INVALID_CHANNEL_INDEX) + { + ERR("Failed to find %s channel in device\n", + GetLabelFromChannel(decoder.mChannels[i])); + continue; } - /* 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+1u;++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{decoder.mIs3D ? AmbiChannelsFromOrder(o) : + 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+1u;++o) + { + const float order_gain{decoder.mOrderGainLF[o]}; + const size_t order_max{decoder.mIs3D ? AmbiChannelsFromOrder(o) : + Ambi2DChannelsFromOrder(o)}; + for(;ambichan < order_max;++ambichan) + coeffs[ambichan] = decoder.mCoeffsLF[i][ambichan] * order_gain; + } } -} -void InitCustomPanning(ALCdevice *device, const bool hqdec, const bool stablize, - const AmbDecConf *conf, const uint (&speakermap)[MAX_OUTPUT_CHANNELS]) -{ - if(!hqdec && conf->FreqBands != 1) - ERR("Basic renderer uses the high-frequency matrix as single-band (xover_freq = %.0fhz)\n", - conf->XOverFreq); - device->mXOverFreq = conf->XOverFreq; + /* For non-DevFmtAmbi3D, set the ambisonic order. */ + device->mAmbiOrder = decoder.mOrder; - const uint order{(conf->ChanMask > Ambi2OrderMask) ? 3u : - (conf->ChanMask > Ambi1OrderMask) ? 2u : 1u}; - device->mAmbiOrder = order; - - size_t count; - if((conf->ChanMask&AmbiPeriphonicMask)) - { - count = AmbiChannelsFromOrder(order); - std::transform(AmbiIndex::FromACN().begin(), AmbiIndex::FromACN().begin()+count, - std::begin(device->Dry.AmbiMap), - [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); - } - else - { - count = Ambi2DChannelsFromOrder(order); - std::transform(AmbiIndex::FromACN2D().begin(), AmbiIndex::FromACN2D().begin()+count, - std::begin(device->Dry.AmbiMap), - [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); - } - AllocChannels(device, count, device->channelsFromFmt()); + const size_t ambicount{decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder) : + Ambi2DChannelsFromOrder(decoder.mOrder)}; + const al::span acnmap{decoder.mIs3D ? AmbiIndex::FromACN().data() : + AmbiIndex::FromACN2D().data(), ambicount}; + auto&& coeffscale = GetAmbiScales(decoder.mScaling); + std::transform(acnmap.begin(), acnmap.end(), std::begin(device->Dry.AmbiMap), + [&coeffscale](const uint8_t &acn) noexcept + { return BFChannelConfig{1.0f/coeffscale[acn], acn}; }); + AllocChannels(device, ambicount, device->channelsFromFmt()); std::unique_ptr stablizer; if(stablize) @@ -632,21 +628,16 @@ void InitCustomPanning(ALCdevice *device, const bool hqdec, const bool stablize, /* Only enable the stablizer if the decoder does not output to the * front-center channel. */ - size_t cidx{0}; - for(;cidx < conf->NumSpeakers;++cidx) - { - if(speakermap[cidx] == FrontCenter) - break; - } + const auto cidx = device->RealOut.ChannelIndex[FrontCenter]; bool hasfc{false}; - if(cidx < conf->NumSpeakers && conf->FreqBands != 1) + if(cidx < chancoeffs.size()) { - for(const auto &coeff : conf->LFMatrix[cidx]) + for(const auto &coeff : chancoeffs[cidx]) hasfc |= coeff != 0.0f; } - if(!hasfc && cidx < conf->NumSpeakers) + if(!hasfc && cidx < chancoeffslf.size()) { - for(const auto &coeff : conf->HFMatrix[cidx]) + for(const auto &coeff : chancoeffslf[cidx]) hasfc |= coeff != 0.0f; } if(!hasfc) @@ -657,31 +648,21 @@ void InitCustomPanning(ALCdevice *device, const bool hqdec, const bool stablize, } TRACE("Enabling %s-band %s-order%s ambisonic decoder\n", - (!hqdec || conf->FreqBands == 1) ? "single" : "dual", - (conf->ChanMask > Ambi2OrderMask) ? "third" : - (conf->ChanMask > Ambi1OrderMask) ? "second" : "first", - (conf->ChanMask&AmbiPeriphonicMask) ? " periphonic" : "" - ); - device->AmbiDecoder = BFormatDec::Create(conf, hqdec, count, device->Frequency, speakermap, - std::move(stablizer)); - - auto accum_spkr_dist = std::bind(std::plus{}, _1, - std::bind(std::mem_fn(&AmbDecConf::SpeakerConf::Distance), _2)); - const float accum_dist{std::accumulate(conf->Speakers.get(), - conf->Speakers.get()+conf->NumSpeakers, 0.0f, accum_spkr_dist)}; - InitNearFieldCtrl(device, accum_dist / static_cast(conf->NumSpeakers), order, - !!(conf->ChanMask&AmbiPeriphonicMask)); - - InitDistanceComp(device, conf, speakermap); + !dual_band ? "single" : "dual", + (decoder.mOrder > 2) ? "third" : + (decoder.mOrder > 1) ? "second" : "first", + decoder.mIs3D ? " periphonic" : ""); + device->AmbiDecoder = BFormatDec::Create(ambicount, chancoeffs, chancoeffslf, + device->mXOverFreq/static_cast(device->Frequency), std::move(stablizer)); } void InitHrtfPanning(ALCdevice *device) { - constexpr float Deg180{al::MathDefs::Pi()}; + constexpr float Deg180{al::numbers::pi_v}; constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/}; constexpr float Deg_45{Deg_90 / 2.0f /* 45 degrees*/}; constexpr float Deg135{Deg_45 * 3.0f /*135 degrees*/}; - constexpr float Deg_35{6.154797086e-01f /* 35~ 36 degrees*/}; + constexpr float Deg_35{6.154797087e-01f /* 35~ 36 degrees*/}; constexpr float Deg_69{1.205932499e+00f /* 69~ 70 degrees*/}; constexpr float Deg111{1.935660155e+00f /*110~111 degrees*/}; constexpr float Deg_21{3.648638281e-01f /* 20~ 21 degrees*/}; @@ -695,26 +676,41 @@ void InitHrtfPanning(ALCdevice *device) { EvRadians{-Deg_35}, AzRadians{ Deg_45} }, { EvRadians{-Deg_35}, AzRadians{ Deg135} }, }, AmbiPoints2O[]{ + { EvRadians{ 0.0f}, AzRadians{ 0.0f} }, + { EvRadians{ 0.0f}, AzRadians{ Deg180} }, + { EvRadians{ 0.0f}, AzRadians{-Deg_90} }, + { EvRadians{ 0.0f}, AzRadians{ Deg_90} }, + { EvRadians{ Deg_90}, AzRadians{ 0.0f} }, + { EvRadians{-Deg_90}, AzRadians{ 0.0f} }, + { EvRadians{ Deg_35}, AzRadians{-Deg_45} }, + { EvRadians{ Deg_35}, AzRadians{-Deg135} }, + { EvRadians{ Deg_35}, AzRadians{ Deg_45} }, + { EvRadians{ Deg_35}, AzRadians{ Deg135} }, { EvRadians{-Deg_35}, AzRadians{-Deg_45} }, { EvRadians{-Deg_35}, AzRadians{-Deg135} }, - { EvRadians{ Deg_35}, AzRadians{-Deg135} }, - { EvRadians{ Deg_35}, AzRadians{ Deg135} }, - { EvRadians{ Deg_35}, AzRadians{ Deg_45} }, { EvRadians{-Deg_35}, AzRadians{ Deg_45} }, { EvRadians{-Deg_35}, AzRadians{ Deg135} }, - { EvRadians{ Deg_35}, AzRadians{-Deg_45} }, - { EvRadians{-Deg_69}, AzRadians{-Deg_90} }, - { EvRadians{ Deg_69}, AzRadians{ Deg_90} }, - { EvRadians{-Deg_69}, AzRadians{ Deg_90} }, + }, AmbiPoints3O[]{ { EvRadians{ Deg_69}, AzRadians{-Deg_90} }, + { EvRadians{ Deg_69}, AzRadians{ Deg_90} }, + { EvRadians{-Deg_69}, AzRadians{-Deg_90} }, + { EvRadians{-Deg_69}, AzRadians{ Deg_90} }, { EvRadians{ 0.0f}, AzRadians{-Deg_69} }, { EvRadians{ 0.0f}, AzRadians{-Deg111} }, { EvRadians{ 0.0f}, AzRadians{ Deg_69} }, { EvRadians{ 0.0f}, AzRadians{ Deg111} }, - { EvRadians{-Deg_21}, AzRadians{ Deg180} }, - { EvRadians{ Deg_21}, AzRadians{ Deg180} }, { EvRadians{ Deg_21}, AzRadians{ 0.0f} }, + { EvRadians{ Deg_21}, AzRadians{ Deg180} }, { EvRadians{-Deg_21}, AzRadians{ 0.0f} }, + { EvRadians{-Deg_21}, AzRadians{ Deg180} }, + { EvRadians{ Deg_35}, AzRadians{-Deg_45} }, + { EvRadians{ Deg_35}, AzRadians{-Deg135} }, + { EvRadians{ Deg_35}, AzRadians{ Deg_45} }, + { EvRadians{ Deg_35}, AzRadians{ Deg135} }, + { EvRadians{-Deg_35}, AzRadians{-Deg_45} }, + { EvRadians{-Deg_35}, AzRadians{-Deg135} }, + { EvRadians{-Deg_35}, AzRadians{ Deg_45} }, + { EvRadians{-Deg_35}, AzRadians{ Deg135} }, }; static const float AmbiMatrix1O[][MaxAmbiChannels]{ { 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f }, @@ -726,42 +722,77 @@ void InitHrtfPanning(ALCdevice *device) { 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f }, { 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f }, }, AmbiMatrix2O[][MaxAmbiChannels]{ - { 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f }, - { 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f }, - { 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f }, - { 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f }, - { 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f }, - { 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f }, - { 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f }, - { 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f }, - { 5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f }, - { 5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f }, - { 5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f }, - { 5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f }, - { 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f }, - { 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f }, - { 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f }, - { 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f }, - { 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f }, - { 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f }, - { 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f }, - { 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f }, + { 7.142857143e-02f, 0.000000000e+00f, 0.000000000e+00f, 1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, 1.290994449e-01f, }, + { 7.142857143e-02f, 0.000000000e+00f, 0.000000000e+00f, -1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, 1.290994449e-01f, }, + { 7.142857143e-02f, 1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, -1.290994449e-01f, }, + { 7.142857143e-02f, -1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, -1.290994449e-01f, }, + { 7.142857143e-02f, 0.000000000e+00f, 1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 1.490711985e-01f, 0.000000000e+00f, 0.000000000e+00f, }, + { 7.142857143e-02f, 0.000000000e+00f, -1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 1.490711985e-01f, 0.000000000e+00f, 0.000000000e+00f, }, + { 7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, 9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, }, + { 7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, -9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, }, + { 7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, -9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, }, + { 7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, 9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, }, + { 7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, 9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, }, + { 7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, -9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, }, + { 7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, -9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, }, + { 7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, 9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, }, + }, AmbiMatrix3O[][MaxAmbiChannels]{ + { 5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }, + { 5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }, + { 5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }, + { 5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }, + { 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }, + { 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }, + { 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }, + { 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }, + { 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f, }, + { 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f, }, + { 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f, }, + { 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f, }, + { 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }, + { 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }, + { 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }, + { 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }, + { 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }, + { 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }, + { 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }, + { 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }, }; static const float AmbiOrderHFGain1O[MaxAmbiOrder+1]{ - 2.000000000e+00f, 1.154700538e+00f + /*ENRGY*/ 2.000000000e+00f, 1.154700538e+00f }, AmbiOrderHFGain2O[MaxAmbiOrder+1]{ - 2.357022604e+00f, 1.825741858e+00f, 9.428090416e-01f + /*ENRGY 2.357022604e+00f, 1.825741858e+00f, 9.428090416e-01f*/ + /*AMP 1.000000000e+00f, 7.745966692e-01f, 4.000000000e-01f*/ + /*RMS*/ 9.128709292e-01f, 7.071067812e-01f, 3.651483717e-01f + }, AmbiOrderHFGain3O[MaxAmbiOrder+1]{ + /*ENRGY 1.865086714e+00f, 1.606093894e+00f, 1.142055301e+00f, 5.683795528e-01f*/ + /*AMP 1.000000000e+00f, 8.611363116e-01f, 6.123336207e-01f, 3.047469850e-01f*/ + /*RMS*/ 8.340921354e-01f, 7.182670250e-01f, 5.107426573e-01f, 2.541870634e-01f }; static_assert(al::size(AmbiPoints1O) == al::size(AmbiMatrix1O), "First-Order Ambisonic HRTF mismatch"); static_assert(al::size(AmbiPoints2O) == al::size(AmbiMatrix2O), "Second-Order Ambisonic HRTF mismatch"); + static_assert(al::size(AmbiPoints3O) == al::size(AmbiMatrix3O), "Third-Order Ambisonic HRTF mismatch"); + + /* A 700hz crossover frequency provides tighter sound imaging at the sweet + * spot with ambisonic decoding, as the distance between the ears is closer + * to half this frequency wavelength, which is the optimal point where the + * response should change between optimizing phase vs volume. Normally this + * tighter imaging is at the cost of a smaller sweet spot, but since the + * listener is fixed in the center of the HRTF responses for the decoder, + * we don't have to worry about ever being out of the sweet spot. + * + * A better option here may be to have the head radius as part of the HRTF + * data set and calculate the optimal crossover frequency from that. + */ + device->mXOverFreq = 700.0f; /* Don't bother with HOA when using full HRTF rendering. Nothing needs it, * and it eases the CPU/memory load. */ device->mRenderMode = RenderMode::Hrtf; uint ambi_order{1}; - if(auto modeopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "hrtf-mode")) + if(auto modeopt = device->configValue(nullptr, "hrtf-mode")) { struct HrtfModeEntry { char name[8]; @@ -772,10 +803,11 @@ void InitHrtfPanning(ALCdevice *device) { "full", RenderMode::Hrtf, 1 }, { "ambi1", RenderMode::Normal, 1 }, { "ambi2", RenderMode::Normal, 2 }, + { "ambi3", RenderMode::Normal, 3 }, }; const char *mode{modeopt->c_str()}; - if(al::strcasecmp(mode, "basic") == 0 || al::strcasecmp(mode, "ambi3") == 0) + if(al::strcasecmp(mode, "basic") == 0) { ERR("HRTF mode \"%s\" deprecated, substituting \"%s\"\n", mode, "ambi2"); mode = "ambi2"; @@ -798,12 +830,18 @@ 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}; al::span AmbiOrderHFGain{AmbiOrderHFGain1O}; - if(ambi_order >= 2) + if(ambi_order >= 3) + { + AmbiPoints = AmbiPoints3O; + AmbiMatrix = AmbiMatrix3O; + AmbiOrderHFGain = AmbiOrderHFGain3O; + } + else if(ambi_order == 2) { AmbiPoints = AmbiPoints2O; AmbiMatrix = AmbiMatrix2O; @@ -837,69 +875,75 @@ void InitUhjPanning(ALCdevice *device) auto acnmap_begin = AmbiIndex::FromFuMa().begin(); std::transform(acnmap_begin, acnmap_begin + count, std::begin(device->Dry.AmbiMap), [](const uint8_t &acn) noexcept -> BFChannelConfig - { return BFChannelConfig{1.0f/AmbiScale::FromFuMa()[acn], acn}; }); + { return BFChannelConfig{1.0f/AmbiScale::FromUHJ()[acn], acn}; }); AllocChannels(device, count, device->channelsFromFmt()); } } // namespace -void aluInitRenderer(ALCdevice *device, int hrtf_id, HrtfRequestMode hrtf_appreq, - HrtfRequestMode hrtf_userreq) +void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional stereomode) { - const char *devname{device->DeviceName.c_str()}; - /* Hold the HRTF the device last used, in case it's used again. */ HrtfStorePtr old_hrtf{std::move(device->mHrtf)}; device->mHrtfState = nullptr; device->mHrtf = nullptr; device->mIrSize = 0; - device->HrtfName.clear(); + device->mHrtfName.clear(); device->mXOverFreq = 400.0f; device->mRenderMode = RenderMode::Normal; if(device->FmtChans != DevFmtStereo) { old_hrtf = nullptr; - if(hrtf_appreq == Hrtf_Enable) - device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; + if(stereomode && *stereomode == StereoEncoding::Hrtf) + device->mHrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; const char *layout{nullptr}; switch(device->FmtChans) { - case DevFmtQuad: layout = "quad"; break; - case DevFmtX51: /* fall-through */ - case DevFmtX51Rear: layout = "surround51"; break; - case DevFmtX61: layout = "surround61"; break; - case DevFmtX71: layout = "surround71"; break; - /* Mono, Stereo, and Ambisonics output don't use custom decoders. */ - case DevFmtMono: - case DevFmtStereo: - case DevFmtAmbi3D: - break; + case DevFmtQuad: layout = "quad"; break; + case DevFmtX51: layout = "surround51"; break; + case DevFmtX61: layout = "surround61"; break; + case DevFmtX71: layout = "surround71"; break; + /* Mono, Stereo, and Ambisonics output don't use custom decoders. */ + case DevFmtMono: + case DevFmtStereo: + case DevFmtAmbi3D: + break; } - uint speakermap[MAX_OUTPUT_CHANNELS]; - AmbDecConf *pconf{nullptr}; - AmbDecConf conf{}; + std::unique_ptr> decoder_store; + DecoderView decoder{}; + float speakerdists[MaxChannels]{}; + auto load_config = [device,&decoder_store,&decoder,&speakerdists](const char *config) + { + AmbDecConf conf{}; + if(auto err = conf.load(config)) + { + ERR("Failed to load layout file %s\n", config); + ERR(" %s\n", err->c_str()); + } + else if(conf.NumSpeakers > MAX_OUTPUT_CHANNELS) + ERR("Unsupported decoder speaker count %zu (max %d)\n", conf.NumSpeakers, + MAX_OUTPUT_CHANNELS); + else if(conf.ChanMask > Ambi3OrderMask) + ERR("Unsupported decoder channel mask 0x%04x (max 0x%x)\n", conf.ChanMask, + Ambi3OrderMask); + else + { + device->mXOverFreq = clampf(conf.XOverFreq, 100.0f, 1000.0f); + + decoder_store = std::make_unique>(); + decoder = MakeDecoderView(device, &conf, *decoder_store); + for(size_t i{0};i < decoder.mChannels.size();++i) + speakerdists[i] = conf.Speakers[i].Distance; + } + }; if(layout) { - if(auto decopt = ConfigValueStr(devname, "decoder", layout)) - { - if(auto err = conf.load(decopt->c_str())) - { - ERR("Failed to load layout file %s\n", decopt->c_str()); - ERR(" %s\n", err->c_str()); - } - else if(conf.NumSpeakers > MAX_OUTPUT_CHANNELS) - ERR("Unsupported decoder speaker count %zu (max %d)\n", conf.NumSpeakers, - MAX_OUTPUT_CHANNELS); - else if(conf.ChanMask > Ambi3OrderMask) - ERR("Unsupported decoder channel mask 0x%04x (max 0x%x)\n", conf.ChanMask, - Ambi3OrderMask); - else if(MakeSpeakerMap(device, &conf, speakermap)) - pconf = &conf; - } + if(auto decopt = device->configValue("decoder", layout)) + load_config(decopt->c_str()); } /* Enable the stablizer only for formats that have front-left, front- @@ -908,12 +952,26 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, HrtfRequestMode hrtf_appreq const bool stablize{device->RealOut.ChannelIndex[FrontCenter] != INVALID_CHANNEL_INDEX && device->RealOut.ChannelIndex[FrontLeft] != INVALID_CHANNEL_INDEX && device->RealOut.ChannelIndex[FrontRight] != INVALID_CHANNEL_INDEX - && GetConfigValueBool(devname, nullptr, "front-stablizer", 0) != 0}; - const bool hqdec{GetConfigValueBool(devname, "decoder", "hq-mode", 1) != 0}; - if(!pconf) - InitPanning(device, hqdec, stablize); - else - InitCustomPanning(device, hqdec, stablize, pconf, speakermap); + && device->getConfigValueBool(nullptr, "front-stablizer", 0) != 0}; + const bool hqdec{device->getConfigValueBool("decoder", "hq-mode", 1) != 0}; + InitPanning(device, hqdec, stablize, decoder); + if(decoder.mOrder > 0) + { + float accum_dist{0.0f}, spkr_count{0.0f}; + for(auto dist : speakerdists) + { + if(dist > 0.0f) + { + accum_dist += dist; + spkr_count += 1.0f; + } + } + if(spkr_count > 0) + { + InitNearFieldCtrl(device, accum_dist / spkr_count, decoder.mOrder, decoder.mIs3D); + InitDistanceComp(device, decoder.mChannels, speakerdists); + } + } if(auto *ambidec{device->AmbiDecoder.get()}) { device->PostProcess = ambidec->hasStablizer() ? &ALCdevice::ProcessAmbiDecStablized @@ -922,94 +980,72 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, HrtfRequestMode hrtf_appreq return; } - bool headphones{device->IsHeadphones}; - if(device->Type != DeviceType::Loopback) + + /* If HRTF is explicitly requested, or if there's no explicit request and + * the device is headphones, try to enable it. + */ + if(stereomode.value_or(StereoEncoding::Default) == StereoEncoding::Hrtf + || (!stereomode && device->Flags.test(DirectEar))) { - if(auto modeopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "stereo-mode")) - { - const char *mode{modeopt->c_str()}; - if(al::strcasecmp(mode, "headphones") == 0) - headphones = true; - else if(al::strcasecmp(mode, "speakers") == 0) - headphones = false; - else if(al::strcasecmp(mode, "auto") != 0) - ERR("Unexpected stereo-mode: %s\n", mode); - } - } - - if(hrtf_userreq == Hrtf_Default) - { - bool usehrtf = (headphones && hrtf_appreq != Hrtf_Disable) || - (hrtf_appreq == Hrtf_Enable); - if(!usehrtf) goto no_hrtf; - - device->HrtfStatus = ALC_HRTF_ENABLED_SOFT; - if(headphones && hrtf_appreq != Hrtf_Disable) - device->HrtfStatus = ALC_HRTF_HEADPHONES_DETECTED_SOFT; - } - else - { - if(hrtf_userreq != Hrtf_Enable) - { - if(hrtf_appreq == Hrtf_Enable) - device->HrtfStatus = ALC_HRTF_DENIED_SOFT; - goto no_hrtf; - } - device->HrtfStatus = ALC_HRTF_REQUIRED_SOFT; - } - - if(device->HrtfList.empty()) - device->HrtfList = EnumerateHrtf(device->DeviceName.c_str()); - - if(hrtf_id >= 0 && static_cast(hrtf_id) < device->HrtfList.size()) - { - const std::string &hrtfname = device->HrtfList[static_cast(hrtf_id)]; - if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) - { - device->mHrtf = std::move(hrtf); - device->HrtfName = hrtfname; - } - } - - if(!device->mHrtf) - { - for(const auto &hrtfname : device->HrtfList) + if(device->mHrtfList.empty()) + device->enumerateHrtfs(); + + if(hrtf_id >= 0 && static_cast(hrtf_id) < device->mHrtfList.size()) { + 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; - break; + device->mHrtfName = hrtfname; } } - } - if(device->mHrtf) - { - old_hrtf = nullptr; - - HrtfStore *hrtf{device->mHrtf.get()}; - device->mIrSize = hrtf->irSize; - if(auto hrtfsizeopt = ConfigValueUInt(devname, nullptr, "hrtf-size")) + if(!device->mHrtf) { - if(*hrtfsizeopt > 0 && *hrtfsizeopt < device->mIrSize) - device->mIrSize = maxu(*hrtfsizeopt, MinIrLength); + for(const auto &hrtfname : device->mHrtfList) + { + if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) + { + device->mHrtf = std::move(hrtf); + device->mHrtfName = hrtfname; + break; + } + } } - InitHrtfPanning(device); - device->PostProcess = &ALCdevice::ProcessHrtf; - return; - } - device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; + if(device->mHrtf) + { + old_hrtf = nullptr; -no_hrtf: + HrtfStore *hrtf{device->mHrtf.get()}; + device->mIrSize = hrtf->irSize; + if(auto hrtfsizeopt = device->configValue(nullptr, "hrtf-size")) + { + if(*hrtfsizeopt > 0 && *hrtfsizeopt < device->mIrSize) + device->mIrSize = maxu(*hrtfsizeopt, MinIrLength); + } + + InitHrtfPanning(device); + device->PostProcess = &ALCdevice::ProcessHrtf; + device->mHrtfStatus = ALC_HRTF_ENABLED_SOFT; + return; + } + } old_hrtf = nullptr; - device->mRenderMode = RenderMode::Pairwise; + if(stereomode.value_or(StereoEncoding::Default) == StereoEncoding::Uhj) + { + device->mUhjEncoder = std::make_unique(); + TRACE("UHJ enabled\n"); + InitUhjPanning(device); + device->PostProcess = &ALCdevice::ProcessUhj; + return; + } + device->mRenderMode = RenderMode::Pairwise; if(device->Type != DeviceType::Loopback) { - if(auto cflevopt = ConfigValueInt(device->DeviceName.c_str(), nullptr, "cf_level")) + if(auto cflevopt = device->configValue(nullptr, "cf_level")) { if(*cflevopt > 0 && *cflevopt <= 6) { @@ -1024,23 +1060,6 @@ no_hrtf: } } - if(auto encopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "stereo-encoding")) - { - const char *mode{encopt->c_str()}; - if(al::strcasecmp(mode, "uhj") == 0) - device->mRenderMode = RenderMode::Normal; - else if(al::strcasecmp(mode, "panpot") != 0) - ERR("Unexpected stereo-encoding: %s\n", mode); - } - if(device->mRenderMode == RenderMode::Normal) - { - device->Uhj_Encoder = std::make_unique(); - TRACE("UHJ enabled\n"); - InitUhjPanning(device); - device->PostProcess = &ALCdevice::ProcessUhj; - return; - } - TRACE("Stereo rendering\n"); InitPanning(device); device->PostProcess = &ALCdevice::ProcessAmbiDec; @@ -1049,7 +1068,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 +1117,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/Engine/lib/openal-soft/alsoftrc.sample b/Engine/lib/openal-soft/alsoftrc.sample index 3c964ada4..733389952 100644 --- a/Engine/lib/openal-soft/alsoftrc.sample +++ b/Engine/lib/openal-soft/alsoftrc.sample @@ -14,10 +14,17 @@ # block, while ALSA options would be in the [alsa/Name of Device] block. # Options marked as "(global)" are not influenced by the device. # -# The system-wide settings can be put in /etc/openal/alsoft.conf and user- -# specific override settings in $HOME/.alsoftrc. +# The system-wide settings can be put in /etc/xdg/alsoft.conf (as determined by +# the XDG_CONFIG_DIRS env var list, /etc/xdg being the default if unset) and +# user-specific override settings in $HOME/.config/alsoft.conf (as determined +# by the XDG_CONFIG_HOME env var). +# # For Windows, these settings should go into $AppData\alsoft.ini # +# An additional configuration file (alsoft.ini on Windows, alsoft.conf on other +# OSs) can be placed alongside the process executable for app-specific config +# settings. +# # Option and block names are case-senstive. The supplied values are only hints # and may not be honored (though generally it'll try to get as close as # possible). Note: options that are left unset may default to app- or system- @@ -48,10 +55,10 @@ ## channels: # Sets the output channel configuration. If left unspecified, one will try to # be detected from the system, and defaulting to stereo. The available values -# are: mono, stereo, quad, surround51, surround51rear, surround61, surround71, -# ambi1, ambi2, ambi3. Note that the ambi* configurations provide ambisonic -# channels of the given order (using ACN ordering and SN3D normalization by -# default), which need to be decoded to play correctly on speakers. +# are: mono, stereo, quad, surround51, surround61, surround71, ambi1, ambi2, +# ambi3. Note that the ambi* configurations provide ambisonic channels of the +# given order (using ACN ordering and SN3D normalization by default), which +# need to be decoded to play correctly on speakers. #channels = ## sample-type: @@ -102,7 +109,8 @@ ## ambi-format: # Specifies the channel order and normalization for the "ambi*" set of channel -# configurations. Valid settings are: fuma, ambix (or acn+sn3d), acn+n3d +# configurations. Valid settings are: fuma, acn+fuma, ambix (or acn+sn3d), or +# acn+n3d #ambi-format = ambix ## hrtf: @@ -182,14 +190,19 @@ #resampler = linear ## rt-prio: (global) -# Sets real-time priority for the mixing thread. Not all drivers may use this -# (eg. PortAudio) as they already control the priority of the mixing thread. -# 0 and negative values will disable it. Note that this may constitute a -# security risk since a real-time priority thread can indefinitely block -# normal-priority threads if it fails to wait. Disable this if it turns out to -# be a problem. +# Sets the real-time priority value for the mixing thread. Not all drivers may +# use this (eg. PortAudio) as those APIs already control the priority of the +# mixing thread. 0 and negative values will disable real-time priority. Note +# that this may constitute a security risk since a real-time priority thread +# can indefinitely block normal-priority threads if it fails to wait. Disable +# this if it turns out to be a problem. #rt-prio = 1 +## rt-time-limit: (global) +# On non-Windows systems, allows reducing the process's RLIMIT_RTTIME resource +# as necessary for acquiring real-time priority from RTKit. +#rt-time-limit = true + ## sources: # Sets the maximum number of allocatable sources. Lower values may help for # systems with apps that try to play more sounds than the CPU can handle. @@ -278,10 +291,7 @@ ## hq-mode: # Enables a high-quality ambisonic decoder. This mode is capable of frequency- # dependent processing, creating a better reproduction of 3D sound rendering -# over surround sound speakers. Enabling this also requires specifying decoder -# configuration files for the appropriate speaker configuration you intend to -# use (see the quad, surround51, etc options below). Currently, up to third- -# order decoding is supported. +# over surround sound speakers. #hq-mode = true ## distance-comp: @@ -346,6 +356,21 @@ # value of 0 means no change. #boost = 0 +## +## PipeWire backend stuff +## +[pipewire] + +## assume-audio: (global) +# Causes the backend to succeed initialization even if PipeWire reports no +# audio support. Currently, audio support is detected by the presence of audio +# source or sink nodes, although this can cause false negatives in cases where +# device availability during library initialization is spotty. Future versions +# of PipeWire are expected to have a more robust method to test audio support, +# but in the mean time this can be set to true to assume PipeWire has audio +# support even when no nodes may be reported at initialization time. +#assume-audio = false + ## ## PulseAudio backend stuff ## @@ -479,8 +504,7 @@ [jack] ## spawn-server: (global) -# Attempts to autospawn a JACK server whenever needed (initializing the -# backend, opening devices, etc). +# Attempts to autospawn a JACK server when initializing. #spawn-server = false ## custom-devices: (global) @@ -491,6 +515,12 @@ # given by the jack_get_ports function) for each enumerated device. #custom-devices = +## rt-mix: +# Renders samples directly in the real-time processing callback. This allows +# for lower latency and less overall CPU utilization, but can increase the +# risk of underruns when increasing the amount of work the mixer needs to do. +#rt-mix = true + ## connect-ports: # Attempts to automatically connect the client ports to physical server ports. # Client ports that fail to connect will leave the remaining channels @@ -504,6 +534,7 @@ # less than JACK's buffer update size, it will be clamped. This option may # be useful in case the server's update size is too small and doesn't give the # mixer time to keep enough audio available for the processing requests. +# Ignored when rt-mix is true. #buffer-size = 0 ## @@ -551,3 +582,30 @@ # Creates AMB format files using first-order ambisonics instead of a standard # single- or multi-channel .wav file. #bformat = false + +## +## EAX extensions stuff +## +[eax] + +## enable: (global) +# Sets whether to enable EAX extensions or not. +#enable = true + +## +## Per-game compatibility options (these should only be set in per-game config +## files, *NOT* system- or user-level!) +## +[game_compat] + +## reverse-x: (global) +# Reverses the local X (left-right) position of 3D sound sources. +#reverse-x = false + +## reverse-y: (global) +# Reverses the local Y (up-down) position of 3D sound sources. +#reverse-y = false + +## reverse-z: (global) +# Reverses the local Z (front-back) position of 3D sound sources. +#reverse-z = false diff --git a/Engine/lib/openal-soft/appveyor.yml b/Engine/lib/openal-soft/appveyor.yml index e54760b2f..c468d4e98 100644 --- a/Engine/lib/openal-soft/appveyor.yml +++ b/Engine/lib/openal-soft/appveyor.yml @@ -1,4 +1,4 @@ -version: 1.21.0.{build} +version: 1.22.0.{build} environment: APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 @@ -9,6 +9,12 @@ environment: - ARCH: x64 CFG: Release +after_build: +- 7z a ..\soft_oal.zip "%APPVEYOR_BUILD_FOLDER%\build\%CFG%\soft_oal.dll" "%APPVEYOR_BUILD_FOLDER%\README.md" "%APPVEYOR_BUILD_FOLDER%\COPYING" + +artifacts: +- path: soft_oal.zip + build_script: - cd build - cmake -G "%GEN%" -A %ARCH% -DALSOFT_BUILD_ROUTER=ON -DALSOFT_REQUIRE_WINMM=ON -DALSOFT_REQUIRE_DSOUND=ON -DALSOFT_REQUIRE_WASAPI=ON -DALSOFT_EMBED_HRTF_DATA=YES .. diff --git a/Engine/lib/openal-soft/common/albit.h b/Engine/lib/openal-soft/common/albit.h index 2c83ca08f..ad5962081 100644 --- a/Engine/lib/openal-soft/common/albit.h +++ b/Engine/lib/openal-soft/common/albit.h @@ -1,11 +1,11 @@ #ifndef AL_BIT_H #define AL_BIT_H +#include #include #include #if !defined(__GNUC__) && (defined(_WIN32) || defined(_WIN64)) #include -#include "opthelpers.h" #endif namespace al { @@ -21,19 +21,19 @@ enum class endian { /* This doesn't support mixed-endian. */ namespace detail_ { -constexpr inline bool EndianTest() noexcept +constexpr bool IsLittleEndian() noexcept { static_assert(sizeof(char) < sizeof(int), "char is too big"); constexpr int test_val{1}; - return static_cast(test_val); + return static_cast(test_val) ? true : false; } } // namespace detail_ enum class endian { - little = 0, - big = 1, - native = detail_::EndianTest() ? little : big + big = 0, + little = 1, + native = detail_::IsLittleEndian() ? little : big }; #endif @@ -73,6 +73,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,47 +96,47 @@ 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 b01010101{detail_::repbits(0x55)}; + constexpr fast_type b00110011{detail_::repbits(0x33)}; + constexpr fast_type b00001111{detail_::repbits(0x0f)}; + constexpr fast_type b00000001{detail_::repbits(0x01)}; - v = v - ((v >> 1) & m55); - v = (v & m33) + ((v >> 2) & m33); - v = (v + (v >> 4)) & m0f; - return static_cast((v * m01) >> ((sizeof(T)-1)*8)); + fast_type v{fast_type{val} - ((fast_type{val} >> 1) & b01010101)}; + v = (v & b00110011) + ((v >> 2) & b00110011); + v = (v + (v >> 4)) & b00001111; + return static_cast(((v * b00000001) >> ((sizeof(T)-1)*8)) & 0xff); } -#if defined(_WIN64) +#ifdef _WIN32 template -inline std::enable_if_t::value && std::is_unsigned::value, +inline std::enable_if_t::value && std::is_unsigned::value + && std::numeric_limits::digits <= 32, int> countr_zero(T v) { unsigned long idx{std::numeric_limits::digits}; - if_constexpr(std::numeric_limits::digits <= 32) - _BitScanForward(&idx, static_cast(v)); - else // std::numeric_limits::digits > 32 - _BitScanForward64(&idx, v); + _BitScanForward(&idx, static_cast(v)); return static_cast(idx); } -#elif defined(_WIN32) - template -inline std::enable_if_t::value && std::is_unsigned::value, +inline std::enable_if_t::value && std::is_unsigned::value + && 32 < std::numeric_limits::digits && std::numeric_limits::digits <= 64, int> countr_zero(T v) { unsigned long idx{std::numeric_limits::digits}; - if_constexpr(std::numeric_limits::digits <= 32) - _BitScanForward(&idx, static_cast(v)); - else if(!_BitScanForward(&idx, static_cast(v))) +#ifdef _WIN64 + _BitScanForward64(&idx, v); +#else + if(!_BitScanForward(&idx, static_cast(v))) { if(_BitScanForward(&idx, static_cast(v>>32))) idx += 32; } +#endif /* _WIN64 */ return static_cast(idx); } diff --git a/Engine/lib/openal-soft/common/albyte.h b/Engine/lib/openal-soft/common/albyte.h index b871164d8..be586869d 100644 --- a/Engine/lib/openal-soft/common/albyte.h +++ b/Engine/lib/openal-soft/common/albyte.h @@ -10,57 +10,7 @@ using uint = unsigned int; namespace al { -/* The "canonical" way to store raw byte data. Like C++17's std::byte, it's not - * treated as a character type and does not work with arithmatic ops. Only - * bitwise ops are allowed. - */ -enum class byte : unsigned char { }; - -template -constexpr std::enable_if_t::value,T> -to_integer(al::byte b) noexcept { return T(b); } - - -template -constexpr std::enable_if_t::value,al::byte> -operator<<(al::byte lhs, T rhs) noexcept { return al::byte(to_integer(lhs) << rhs); } - -template -constexpr std::enable_if_t::value,al::byte> -operator>>(al::byte lhs, T rhs) noexcept { return al::byte(to_integer(lhs) >> rhs); } - -template -constexpr std::enable_if_t::value,al::byte&> -operator<<=(al::byte &lhs, T rhs) noexcept { lhs = lhs << rhs; return lhs; } - -template -constexpr std::enable_if_t::value,al::byte&> -operator>>=(al::byte &lhs, T rhs) noexcept { lhs = lhs >> rhs; return lhs; } - -#define AL_DECL_OP(op, opeq) \ -template \ -constexpr std::enable_if_t::value,al::byte> \ -operator op (al::byte lhs, T rhs) noexcept \ -{ return al::byte(to_integer(lhs) op static_cast(rhs)); } \ - \ -template \ -constexpr std::enable_if_t::value,al::byte&> \ -operator opeq (al::byte &lhs, T rhs) noexcept { lhs = lhs op rhs; return lhs; } \ - \ -constexpr al::byte operator op (al::byte lhs, al::byte rhs) noexcept \ -{ return al::byte(lhs op to_integer(rhs)); } \ - \ -constexpr al::byte& operator opeq (al::byte &lhs, al::byte rhs) noexcept \ -{ lhs = lhs op rhs; return lhs; } - -AL_DECL_OP(|, |=) -AL_DECL_OP(&, &=) -AL_DECL_OP(^, ^=) - -#undef AL_DECL_OP - -constexpr al::byte operator~(al::byte b) noexcept -{ return al::byte(~to_integer(b)); } +using byte = unsigned char; } // namespace al diff --git a/Engine/lib/openal-soft/common/alcomplex.cpp b/Engine/lib/openal-soft/common/alcomplex.cpp index de10ede20..126e2c043 100644 --- a/Engine/lib/openal-soft/common/alcomplex.cpp +++ b/Engine/lib/openal-soft/common/alcomplex.cpp @@ -4,15 +4,95 @@ #include "alcomplex.h" #include +#include #include #include #include #include "albit.h" +#include "alnumbers.h" #include "alnumeric.h" -#include "math_defs.h" +#include "opthelpers.h" +namespace { + +using ushort = unsigned short; +using ushort2 = std::pair; + +/* Because std::array doesn't have constexpr non-const accessors in C++14. */ +template +struct our_array { + T mData[N]; +}; + +constexpr size_t BitReverseCounter(size_t log2_size) noexcept +{ + /* Some magic math that calculates the number of swaps needed for a + * sequence of bit-reversed indices when index < reversed_index. + */ + return (1u<<(log2_size-1)) - (1u<<((log2_size-1u)/2u)); +} + +template +constexpr auto GetBitReverser() noexcept +{ + static_assert(N <= sizeof(ushort)*8, "Too many bits for the bit-reversal table."); + + our_array ret{}; + const size_t fftsize{1u << N}; + size_t ret_i{0}; + + /* Bit-reversal permutation applied to a sequence of fftsize items. */ + for(size_t idx{1u};idx < fftsize-1;++idx) + { + size_t revidx{0u}, imask{idx}; + for(size_t i{0};i < N;++i) + { + revidx = (revidx<<1) | (imask&1); + imask >>= 1; + } + + if(idx < revidx) + { + ret.mData[ret_i].first = static_cast(idx); + ret.mData[ret_i].second = static_cast(revidx); + ++ret_i; + } + } + assert(ret_i == al::size(ret.mData)); + return ret; +} + +/* These bit-reversal swap tables support up to 10-bit indices (1024 elements), + * which is the largest used by OpenAL Soft's filters and effects. Larger FFT + * requests, used by some utilities where performance is less important, will + * use a slower table-less path. + */ +constexpr auto BitReverser2 = GetBitReverser<2>(); +constexpr auto BitReverser3 = GetBitReverser<3>(); +constexpr auto BitReverser4 = GetBitReverser<4>(); +constexpr auto BitReverser5 = GetBitReverser<5>(); +constexpr auto BitReverser6 = GetBitReverser<6>(); +constexpr auto BitReverser7 = GetBitReverser<7>(); +constexpr auto BitReverser8 = GetBitReverser<8>(); +constexpr auto BitReverser9 = GetBitReverser<9>(); +constexpr auto BitReverser10 = GetBitReverser<10>(); +constexpr al::span gBitReverses[11]{ + {}, {}, + BitReverser2.mData, + BitReverser3.mData, + BitReverser4.mData, + BitReverser5.mData, + BitReverser6.mData, + BitReverser7.mData, + BitReverser8.mData, + BitReverser9.mData, + BitReverser10.mData +}; + +} // namespace + void complex_fft(const al::span> buffer, const double sign) { const size_t fftsize{buffer.size()}; @@ -21,27 +101,33 @@ void complex_fft(const al::span> buffer, const double sign) */ const size_t log2_size{static_cast(al::countr_zero(fftsize))}; - /* Bit-reversal permutation applied to a sequence of fftsize items. */ - for(size_t idx{1u};idx < fftsize-1;++idx) + if(unlikely(log2_size >= al::size(gBitReverses))) { - size_t revidx{0u}, imask{idx}; - for(size_t i{0};i < log2_size;++i) + for(size_t idx{1u};idx < fftsize-1;++idx) { - revidx = (revidx<<1) | (imask&1); - imask >>= 1; - } + size_t revidx{0u}, imask{idx}; + for(size_t i{0};i < log2_size;++i) + { + revidx = (revidx<<1) | (imask&1); + imask >>= 1; + } - if(idx < revidx) - std::swap(buffer[idx], buffer[revidx]); + if(idx < revidx) + std::swap(buffer[idx], buffer[revidx]); + } } + else for(auto &rev : gBitReverses[log2_size]) + std::swap(buffer[rev.first], buffer[rev.second]); /* Iterative form of Danielson-Lanczos lemma */ + const double pi{al::numbers::pi * sign}; size_t step2{1u}; for(size_t i{0};i < log2_size;++i) { - const double arg{al::MathDefs::Pi() / static_cast(step2)}; + const double arg{pi / static_cast(step2)}; - const std::complex w{std::cos(arg), std::sin(arg)*sign}; + /* TODO: Would std::polar(1.0, arg) be any better? */ + const std::complex w{std::cos(arg), std::sin(arg)}; std::complex u{1.0, 0.0}; const size_t step{step2 << 1}; for(size_t j{0};j < step2;j++) diff --git a/Engine/lib/openal-soft/common/almalloc.h b/Engine/lib/openal-soft/common/almalloc.h index 58bd5eedc..711d02fd0 100644 --- a/Engine/lib/openal-soft/common/almalloc.h +++ b/Engine/lib/openal-soft/common/almalloc.h @@ -13,9 +13,11 @@ #include "pragmadefs.h" -[[gnu::alloc_align(1), gnu::alloc_size(2)]] void *al_malloc(size_t alignment, size_t size); -[[gnu::alloc_align(1), gnu::alloc_size(2)]] void *al_calloc(size_t alignment, size_t size); void al_free(void *ptr) noexcept; +[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]] +void *al_malloc(size_t alignment, size_t size); +[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]] +void *al_calloc(size_t alignment, size_t size); #define DISABLE_ALLOC() \ @@ -48,8 +50,8 @@ enum FamCount : size_t { }; #define DEF_FAM_NEWDEL(T, FamMem) \ static constexpr size_t Sizeof(size_t count) noexcept \ { \ - return std::max(sizeof(T), \ - decltype(FamMem)::Sizeof(count, offsetof(T, FamMem))); \ + return std::max(decltype(FamMem)::Sizeof(count, offsetof(T, FamMem)), \ + sizeof(T)); \ } \ \ void *operator new(size_t /*size*/, FamCount count) \ @@ -95,12 +97,15 @@ struct allocator { void deallocate(T *p, std::size_t) noexcept { al_free(p); } }; template -bool operator==(const allocator&, const allocator&) noexcept { return true; } +constexpr bool operator==(const allocator&, const allocator&) noexcept { return true; } template -bool operator!=(const allocator&, const allocator&) noexcept { return false; } +constexpr bool operator!=(const allocator&, const allocator&) noexcept { return false; } -template -[[gnu::assume_aligned(alignment)]] inline T* assume_aligned(T *ptr) noexcept { return ptr; } + +template +constexpr T* construct_at(T *ptr, Args&& ...args) + noexcept(std::is_nothrow_constructible::value) +{ return ::new(static_cast(ptr)) T{std::forward(args)...}; } /* At least VS 2015 complains that 'ptr' is unused when the given type's * destructor is trivial (a no-op). So disable that warning for this call. @@ -114,14 +119,14 @@ destroy_at(T *ptr) noexcept(std::is_nothrow_destructible::value) DIAGNOSTIC_POP template constexpr std::enable_if_t::value> -destroy_at(T *ptr) noexcept(std::is_nothrow_destructible::value) +destroy_at(T *ptr) noexcept(std::is_nothrow_destructible>::value) { for(auto &elem : *ptr) al::destroy_at(std::addressof(elem)); } template -constexpr void destroy(T first, T end) +constexpr void destroy(T first, T end) noexcept(noexcept(al::destroy_at(std::addressof(*first)))) { while(first != end) { @@ -132,7 +137,7 @@ constexpr void destroy(T first, T end) template constexpr std::enable_if_t::value,T> -destroy_n(T first, N count) +destroy_n(T first, N count) noexcept(noexcept(al::destroy_at(std::addressof(*first)))) { if(count != 0) { @@ -146,8 +151,8 @@ destroy_n(T first, N count) template -inline std::enable_if_t::value,T> -uninitialized_default_construct_n(T first, N count) +inline std::enable_if_t::value, +T> uninitialized_default_construct_n(T first, N count) { using ValueT = typename std::iterator_traits::value_type; T current{first}; @@ -172,10 +177,7 @@ uninitialized_default_construct_n(T first, N count) * trivially destructible. */ template::value> -struct FlexArrayStorage; - -template -struct FlexArrayStorage { +struct FlexArrayStorage { const size_t mSize; union { char mDummy; @@ -184,8 +186,8 @@ struct FlexArrayStorage { static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept { - return std::max(offsetof(FlexArrayStorage, mArray) + sizeof(T)*count, - sizeof(FlexArrayStorage)) + base; + const size_t len{sizeof(T)*count}; + return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base; } FlexArrayStorage(size_t size) : mSize{size} @@ -206,8 +208,8 @@ struct FlexArrayStorage { static constexpr size_t Sizeof(size_t count, size_t base) noexcept { - return std::max(offsetof(FlexArrayStorage, mArray) + sizeof(T)*count, - sizeof(FlexArrayStorage)) + base; + const size_t len{sizeof(T)*count}; + return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base; } FlexArrayStorage(size_t size) : mSize{size} @@ -248,7 +250,7 @@ struct FlexArray { static std::unique_ptr Create(index_type count) { void *ptr{al_calloc(alignof(FlexArray), Sizeof(count))}; - return std::unique_ptr{new(ptr) FlexArray{count}}; + return std::unique_ptr{al::construct_at(static_cast(ptr), count)}; } FlexArray(index_type size) : mStore{size} { } diff --git a/Engine/lib/openal-soft/common/alnumbers.h b/Engine/lib/openal-soft/common/alnumbers.h new file mode 100644 index 000000000..37a554100 --- /dev/null +++ b/Engine/lib/openal-soft/common/alnumbers.h @@ -0,0 +1,36 @@ +#ifndef COMMON_ALNUMBERS_H +#define COMMON_ALNUMBERS_H + +#include + +namespace al { + +namespace numbers { + +namespace detail_ { + template + using as_fp = std::enable_if_t::value, T>; +} // detail_ + +template +static constexpr auto pi_v = detail_::as_fp(3.141592653589793238462643383279502884L); + +template +static constexpr auto inv_pi_v = detail_::as_fp(0.318309886183790671537767526745028724L); + +template +static constexpr auto sqrt2_v = detail_::as_fp(1.414213562373095048801688724209698079L); + +template +static constexpr auto sqrt3_v = detail_::as_fp(1.732050807568877293527446341505872367L); + +static constexpr auto pi = pi_v; +static constexpr auto inv_pi = inv_pi_v; +static constexpr auto sqrt2 = sqrt2_v; +static constexpr auto sqrt3 = sqrt3_v; + +} // namespace numbers + +} // namespace al + +#endif /* COMMON_ALNUMBERS_H */ diff --git a/Engine/lib/openal-soft/common/alnumeric.h b/Engine/lib/openal-soft/common/alnumeric.h index c16f3e627..9e7a7f074 100644 --- a/Engine/lib/openal-soft/common/alnumeric.h +++ b/Engine/lib/openal-soft/common/alnumeric.h @@ -1,6 +1,8 @@ #ifndef AL_NUMERIC_H #define AL_NUMERIC_H +#include +#include #include #include #ifdef HAVE_INTRIN_H @@ -67,7 +69,7 @@ constexpr inline size_t clampz(size_t val, size_t min, size_t max) noexcept { return minz(max, maxz(min, val)); } -constexpr inline float lerp(float val1, float val2, float mu) noexcept +constexpr inline float lerpf(float val1, float val2, float mu) noexcept { return val1 + (val2-val1)*mu; } constexpr inline float cubic(float val1, float val2, float val3, float val4, float mu) noexcept { @@ -271,4 +273,27 @@ inline float fast_roundf(float f) noexcept #endif } + +template +constexpr const T& clamp(const T& value, const T& min_value, const T& max_value) noexcept +{ + return std::min(std::max(value, min_value), max_value); +} + +// Converts level (mB) to gain. +inline float level_mb_to_gain(float x) +{ + if(x <= -10'000.0f) + return 0.0f; + return std::pow(10.0f, x / 2'000.0f); +} + +// Converts gain to level (mB). +inline float gain_to_level_mb(float x) +{ + if (x <= 0.0f) + return -10'000.0f; + return maxf(std::log10(x) * 2'000.0f, -10'000.0f); +} + #endif /* AL_NUMERIC_H */ diff --git a/Engine/lib/openal-soft/common/aloptional.h b/Engine/lib/openal-soft/common/aloptional.h index 0244c56ff..6180d1618 100644 --- a/Engine/lib/openal-soft/common/aloptional.h +++ b/Engine/lib/openal-soft/common/aloptional.h @@ -15,147 +15,339 @@ struct in_place_t { }; constexpr nullopt_t nullopt{}; constexpr in_place_t in_place{}; +#define NOEXCEPT_AS(...) noexcept(noexcept(__VA_ARGS__)) +namespace detail_ { +/* Base storage struct for an optional. Defines a trivial destructor, for types + * that can be trivially destructed. + */ template::value> +struct optstore_base { + bool mHasValue{false}; + union { + char mDummy{}; + T mValue; + }; + + constexpr optstore_base() noexcept { } + template + constexpr explicit optstore_base(in_place_t, Args&& ...args) + noexcept(std::is_nothrow_constructible::value) + : mHasValue{true}, mValue{std::forward(args)...} + { } + ~optstore_base() = default; +}; + +/* Specialization needing a non-trivial destructor. */ +template +struct optstore_base { + bool mHasValue{false}; + union { + char mDummy{}; + T mValue; + }; + + constexpr optstore_base() noexcept { } + template + constexpr explicit optstore_base(in_place_t, Args&& ...args) + noexcept(std::is_nothrow_constructible::value) + : mHasValue{true}, mValue{std::forward(args)...} + { } + ~optstore_base() { if(mHasValue) al::destroy_at(std::addressof(mValue)); } +}; + +/* Next level of storage, which defines helpers to construct and destruct the + * stored object. + */ +template +struct optstore_helper : public optstore_base { + using optstore_base::optstore_base; + + template + constexpr void construct(Args&& ...args) noexcept(std::is_nothrow_constructible::value) + { + al::construct_at(std::addressof(this->mValue), std::forward(args)...); + this->mHasValue = true; + } + + constexpr void reset() noexcept + { + if(this->mHasValue) + al::destroy_at(std::addressof(this->mValue)); + this->mHasValue = false; + } + + constexpr void assign(const optstore_helper &rhs) + noexcept(std::is_nothrow_copy_constructible::value + && std::is_nothrow_copy_assignable::value) + { + if(!rhs.mHasValue) + this->reset(); + else if(this->mHasValue) + this->mValue = rhs.mValue; + else + this->construct(rhs.mValue); + } + + constexpr void assign(optstore_helper&& rhs) + noexcept(std::is_nothrow_move_constructible::value + && std::is_nothrow_move_assignable::value) + { + if(!rhs.mHasValue) + this->reset(); + else if(this->mHasValue) + this->mValue = std::move(rhs.mValue); + else + this->construct(std::move(rhs.mValue)); + } +}; + +/* Define copy and move constructors and assignment operators, which may or may + * not be trivial. + */ +template::value, + bool trivial_move = std::is_trivially_move_constructible::value, + /* Trivial assignment is dependent on trivial construction+destruction. */ + bool = trivial_copy && std::is_trivially_copy_assignable::value + && std::is_trivially_destructible::value, + bool = trivial_move && std::is_trivially_move_assignable::value + && std::is_trivially_destructible::value> struct optional_storage; -template -struct optional_storage { - bool mHasValue{false}; - union { - char mDummy; - T mValue; - }; +/* Some versions of GCC have issues with 'this' in the following noexcept(...) + * statements, so this macro is a workaround. + */ +#define _this std::declval() - optional_storage() { } - template - explicit optional_storage(in_place_t, Args&& ...args) - : mHasValue{true}, mValue{std::forward(args)...} - { } - ~optional_storage() = default; +/* Completely trivial. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage&) = default; + constexpr optional_storage(optional_storage&&) = default; + constexpr optional_storage& operator=(const optional_storage&) = default; + constexpr optional_storage& operator=(optional_storage&&) = default; }; +/* Non-trivial move assignment. */ template -struct optional_storage { - bool mHasValue{false}; - union { - char mDummy; - T mValue; - }; - - optional_storage() { } - template - explicit optional_storage(in_place_t, Args&& ...args) - : mHasValue{true}, mValue{std::forward(args)...} - { } - ~optional_storage() { if(mHasValue) al::destroy_at(std::addressof(mValue)); } +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage&) = default; + constexpr optional_storage(optional_storage&&) = default; + constexpr optional_storage& operator=(const optional_storage&) = default; + constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) + { this->assign(std::move(rhs)); return *this; } }; +/* Non-trivial move construction. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage&) = default; + constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) + { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } + constexpr optional_storage& operator=(const optional_storage&) = default; + constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) + { this->assign(std::move(rhs)); return *this; } +}; + +/* Non-trivial copy assignment. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage&) = default; + constexpr optional_storage(optional_storage&&) = default; + constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) + { this->assign(rhs); return *this; } + constexpr optional_storage& operator=(optional_storage&&) = default; +}; + +/* Non-trivial copy construction. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) + { if(rhs.mHasValue) this->construct(rhs.mValue); } + constexpr optional_storage(optional_storage&&) = default; + constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) + { this->assign(rhs); return *this; } + constexpr optional_storage& operator=(optional_storage&&) = default; +}; + +/* Non-trivial assignment. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage&) = default; + constexpr optional_storage(optional_storage&&) = default; + constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) + { this->assign(rhs); return *this; } + constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) + { this->assign(std::move(rhs)); return *this; } +}; + +/* Non-trivial assignment, non-trivial move construction. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage&) = default; + constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) + { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } + constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) + { this->assign(rhs); return *this; } + constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) + { this->assign(std::move(rhs)); return *this; } +}; + +/* Non-trivial assignment, non-trivial copy construction. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) + { if(rhs.mHasValue) this->construct(rhs.mValue); } + constexpr optional_storage(optional_storage&&) = default; + constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) + { this->assign(rhs); return *this; } + constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) + { this->assign(std::move(rhs)); return *this; } +}; + +/* Completely non-trivial. */ +template +struct optional_storage : public optstore_helper { + using optstore_helper::optstore_helper; + constexpr optional_storage() noexcept = default; + constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) + { if(rhs.mHasValue) this->construct(rhs.mValue); } + constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) + { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } + constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) + { this->assign(rhs); return *this; } + constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) + { this->assign(std::move(rhs)); return *this; } +}; + +#undef _this + +} // namespace detail_ + +#define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true + template class optional { - using storage_t = optional_storage; + using storage_t = detail_::optional_storage; - storage_t mStore; - - template - void doConstruct(Args&& ...args) - { - ::new(std::addressof(mStore.mValue)) T{std::forward(args)...}; - mStore.mHasValue = true; - } + storage_t mStore{}; public: using value_type = T; - optional() = default; - optional(nullopt_t) noexcept { } - optional(const optional &rhs) { if(rhs) doConstruct(*rhs); } - optional(optional&& rhs) { if(rhs) doConstruct(std::move(*rhs)); } + constexpr optional() = default; + constexpr optional(const optional&) = default; + constexpr optional(optional&&) = default; + constexpr optional(nullopt_t) noexcept { } template - explicit optional(in_place_t, Args&& ...args) + constexpr explicit optional(in_place_t, Args&& ...args) + NOEXCEPT_AS(storage_t{al::in_place, std::forward(args)...}) : mStore{al::in_place, std::forward(args)...} { } + template::value + && !std::is_same, al::in_place_t>::value + && !std::is_same, optional>::value + && std::is_convertible::value)> + constexpr optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward(rhs)}) + : mStore{al::in_place, std::forward(rhs)} + { } + template::value + && !std::is_same, al::in_place_t>::value + && !std::is_same, optional>::value + && !std::is_convertible::value)> + constexpr explicit optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward(rhs)}) + : mStore{al::in_place, std::forward(rhs)} + { } ~optional() = default; - optional& operator=(nullopt_t) noexcept { reset(); return *this; } - std::enable_if_t::value && std::is_copy_assignable::value, - optional&> operator=(const optional &rhs) - { - if(!rhs) - reset(); - else if(*this) - mStore.mValue = *rhs; - else - doConstruct(*rhs); - return *this; - } - std::enable_if_t::value && std::is_move_assignable::value, - optional&> operator=(optional&& rhs) - { - if(!rhs) - reset(); - else if(*this) - mStore.mValue = std::move(*rhs); - else - doConstruct(std::move(*rhs)); - return *this; - } + constexpr optional& operator=(const optional&) = default; + constexpr optional& operator=(optional&&) = default; + constexpr optional& operator=(nullopt_t) noexcept { mStore.reset(); return *this; } template - std::enable_if_t::value + constexpr std::enable_if_t::value && std::is_assignable::value && !std::is_same, optional>::value && (!std::is_same, T>::value || !std::is_scalar::value), optional&> operator=(U&& rhs) { - if(*this) + if(mStore.mHasValue) mStore.mValue = std::forward(rhs); else - doConstruct(std::forward(rhs)); + mStore.construct(std::forward(rhs)); return *this; } - const T* operator->() const { return std::addressof(mStore.mValue); } - T* operator->() { return std::addressof(mStore.mValue); } - const T& operator*() const& { return this->mValue; } - T& operator*() & { return mStore.mValue; } - const T&& operator*() const&& { return std::move(mStore.mValue); } - T&& operator*() && { return std::move(mStore.mValue); } + constexpr const T* operator->() const { return std::addressof(mStore.mValue); } + constexpr T* operator->() { return std::addressof(mStore.mValue); } + constexpr const T& operator*() const& { return mStore.mValue; } + constexpr T& operator*() & { return mStore.mValue; } + constexpr const T&& operator*() const&& { return std::move(mStore.mValue); } + constexpr T&& operator*() && { return std::move(mStore.mValue); } - operator bool() const noexcept { return mStore.mHasValue; } - bool has_value() const noexcept { return mStore.mHasValue; } + constexpr explicit operator bool() const noexcept { return mStore.mHasValue; } + constexpr bool has_value() const noexcept { return mStore.mHasValue; } - T& value() & { return mStore.mValue; } - const T& value() const& { return mStore.mValue; } - T&& value() && { return std::move(mStore.mValue); } - const T&& value() const&& { return std::move(mStore.mValue); } + constexpr T& value() & { return mStore.mValue; } + constexpr const T& value() const& { return mStore.mValue; } + constexpr T&& value() && { return std::move(mStore.mValue); } + constexpr const T&& value() const&& { return std::move(mStore.mValue); } template - T value_or(U&& defval) const& + constexpr T value_or(U&& defval) const& { return bool{*this} ? **this : static_cast(std::forward(defval)); } template - T value_or(U&& defval) && + constexpr T value_or(U&& defval) && { return bool{*this} ? std::move(**this) : static_cast(std::forward(defval)); } - void reset() noexcept + template + constexpr T& emplace(Args&& ...args) { - if(mStore.mHasValue) - al::destroy_at(std::addressof(mStore.mValue)); - mStore.mHasValue = false; + mStore.reset(); + mStore.construct(std::forward(args)...); + return mStore.mValue; } + template + constexpr std::enable_if_t&, Args&&...>::value, + T&> emplace(std::initializer_list il, Args&& ...args) + { + mStore.reset(); + mStore.construct(il, std::forward(args)...); + return mStore.mValue; + } + + constexpr void reset() noexcept { mStore.reset(); } }; template -inline optional> make_optional(T&& arg) +constexpr optional> make_optional(T&& arg) { return optional>{in_place, std::forward(arg)}; } template -inline optional make_optional(Args&& ...args) +constexpr optional make_optional(Args&& ...args) { return optional{in_place, std::forward(args)...}; } template -inline optional make_optional(std::initializer_list il, Args&& ...args) +constexpr optional make_optional(std::initializer_list il, Args&& ...args) { return optional{in_place, il, std::forward(args)...}; } +#undef REQUIRES +#undef NOEXCEPT_AS } // namespace al #endif /* AL_OPTIONAL_H */ diff --git a/Engine/lib/openal-soft/common/alspan.h b/Engine/lib/openal-soft/common/alspan.h index f2c42b165..4a0e0430f 100644 --- a/Engine/lib/openal-soft/common/alspan.h +++ b/Engine/lib/openal-soft/common/alspan.h @@ -9,22 +9,14 @@ namespace al { -template -constexpr auto size(T &cont) noexcept(noexcept(cont.size())) -> decltype(cont.size()) -{ return cont.size(); } - template constexpr auto size(const T &cont) noexcept(noexcept(cont.size())) -> decltype(cont.size()) { return cont.size(); } template -constexpr size_t size(T (&)[N]) noexcept +constexpr size_t size(const T (&)[N]) noexcept { return N; } -template -constexpr size_t size(std::initializer_list list) noexcept -{ return list.size(); } - template constexpr auto data(T &cont) noexcept(noexcept(cont.data())) -> decltype(cont.data()) diff --git a/Engine/lib/openal-soft/common/alstring.h b/Engine/lib/openal-soft/common/alstring.h index 6c4971903..f5127aede 100644 --- a/Engine/lib/openal-soft/common/alstring.h +++ b/Engine/lib/openal-soft/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/Engine/lib/openal-soft/common/comptr.h b/Engine/lib/openal-soft/common/comptr.h new file mode 100644 index 000000000..3dc574e80 --- /dev/null +++ b/Engine/lib/openal-soft/common/comptr.h @@ -0,0 +1,68 @@ +#ifndef COMMON_COMPTR_H +#define COMMON_COMPTR_H + +#include +#include + +#include "opthelpers.h" + + +template +class ComPtr { + T *mPtr{nullptr}; + +public: + ComPtr() noexcept = default; + ComPtr(const ComPtr &rhs) : mPtr{rhs.mPtr} { if(mPtr) mPtr->AddRef(); } + ComPtr(ComPtr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; } + ComPtr(std::nullptr_t) noexcept { } + explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { } + ~ComPtr() { if(mPtr) mPtr->Release(); } + + ComPtr& operator=(const ComPtr &rhs) + { + if(!rhs.mPtr) + { + if(mPtr) + mPtr->Release(); + mPtr = nullptr; + } + else + { + rhs.mPtr->AddRef(); + try { + if(mPtr) + mPtr->Release(); + mPtr = rhs.mPtr; + } + catch(...) { + rhs.mPtr->Release(); + throw; + } + } + return *this; + } + ComPtr& operator=(ComPtr&& rhs) + { + if(likely(&rhs != this)) + { + if(mPtr) mPtr->Release(); + mPtr = std::exchange(rhs.mPtr, nullptr); + } + return *this; + } + + explicit operator bool() const noexcept { return mPtr != nullptr; } + + T& operator*() const noexcept { return *mPtr; } + T* operator->() const noexcept { return mPtr; } + T* get() const noexcept { return mPtr; } + T** getPtr() noexcept { return &mPtr; } + + T* release() noexcept { return std::exchange(mPtr, nullptr); } + + void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); } + void swap(ComPtr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); } +}; + +#endif diff --git a/Engine/lib/openal-soft/common/intrusive_ptr.h b/Engine/lib/openal-soft/common/intrusive_ptr.h index cc82dea52..9e206a6b6 100644 --- a/Engine/lib/openal-soft/common/intrusive_ptr.h +++ b/Engine/lib/openal-soft/common/intrusive_ptr.h @@ -1,6 +1,8 @@ #ifndef INTRUSIVE_PTR_H #define INTRUSIVE_PTR_H +#include + #include "atomic.h" #include "opthelpers.h" @@ -60,6 +62,8 @@ public: intrusive_ptr& operator=(const intrusive_ptr &rhs) noexcept { + static_assert(noexcept(std::declval()->release()), "release must be noexcept"); + if(rhs.mPtr) rhs.mPtr->add_ref(); if(mPtr) mPtr->release(); mPtr = rhs.mPtr; @@ -67,14 +71,15 @@ public: } intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept { - if(mPtr) - mPtr->release(); - mPtr = rhs.mPtr; - rhs.mPtr = nullptr; + if(likely(&rhs != this)) + { + if(mPtr) mPtr->release(); + mPtr = std::exchange(rhs.mPtr, nullptr); + } return *this; } - operator bool() const noexcept { return mPtr != nullptr; } + explicit operator bool() const noexcept { return mPtr != nullptr; } T& operator*() const noexcept { return *mPtr; } T* operator->() const noexcept { return mPtr; } @@ -87,12 +92,7 @@ public: mPtr = ptr; } - T* release() noexcept - { - T *ret{mPtr}; - mPtr = nullptr; - return ret; - } + T* release() noexcept { return std::exchange(mPtr, nullptr); } void swap(intrusive_ptr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); } void swap(intrusive_ptr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); } diff --git a/Engine/lib/openal-soft/common/math_defs.h b/Engine/lib/openal-soft/common/math_defs.h deleted file mode 100644 index ba0071152..000000000 --- a/Engine/lib/openal-soft/common/math_defs.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef AL_MATH_DEFS_H -#define AL_MATH_DEFS_H - -constexpr float Deg2Rad(float x) noexcept { return x * 1.74532925199432955e-02f/*pi/180*/; } -constexpr float Rad2Deg(float x) noexcept { return x * 5.72957795130823229e+01f/*180/pi*/; } - -namespace al { - -template -struct MathDefs { }; - -template<> -struct MathDefs { - static constexpr float Pi() noexcept { return 3.14159265358979323846e+00f; } - static constexpr float Tau() noexcept { return 6.28318530717958647692e+00f; } -}; - -template<> -struct MathDefs { - static constexpr double Pi() noexcept { return 3.14159265358979323846e+00; } - static constexpr double Tau() noexcept { return 6.28318530717958647692e+00; } -}; - -} // namespace al - -#endif /* AL_MATH_DEFS_H */ diff --git a/Engine/lib/openal-soft/common/opthelpers.h b/Engine/lib/openal-soft/common/opthelpers.h index bb0b63fe3..e46c0f3a9 100644 --- a/Engine/lib/openal-soft/common/opthelpers.h +++ b/Engine/lib/openal-soft/common/opthelpers.h @@ -1,26 +1,50 @@ #ifndef OPTHELPERS_H #define OPTHELPERS_H +#include +#include + + #ifdef __has_builtin #define HAS_BUILTIN __has_builtin #else #define HAS_BUILTIN(x) (0) #endif +#ifdef __GNUC__ +#define force_inline [[gnu::always_inline]] +#elif defined(_MSC_VER) +#define force_inline __forceinline +#else +#define force_inline inline +#endif + #if defined(__GNUC__) || HAS_BUILTIN(__builtin_expect) -/* LIKELY optimizes the case where the condition is true. The condition is not - * required to be true, but it can result in more optimal code for the true - * path at the expense of a less optimal false path. +/* likely() optimizes for the case where the condition is true. The condition + * is not required to be true, but it can result in more optimal code for the + * true path at the expense of a less optimal false path. */ -#define LIKELY(x) (__builtin_expect(!!(x), !false)) -/* The opposite of LIKELY, optimizing the case where the condition is false. */ -#define UNLIKELY(x) (__builtin_expect(!!(x), false)) +template +force_inline constexpr bool likely(T&& expr) noexcept +{ return __builtin_expect(static_cast(std::forward(expr)), true); } +/* The opposite of likely(), optimizing for the case where the condition is + * false. + */ +template +force_inline constexpr bool unlikely(T&& expr) noexcept +{ return __builtin_expect(static_cast(std::forward(expr)), false); } #else -#define LIKELY(x) (!!(x)) -#define UNLIKELY(x) (!!(x)) +template +force_inline constexpr bool likely(T&& expr) noexcept +{ return static_cast(std::forward(expr)); } +template +force_inline constexpr bool unlikely(T&& expr) noexcept +{ return static_cast(std::forward(expr)); } #endif +#define LIKELY(x) (likely(x)) +#define UNLIKELY(x) (unlikely(x)) #if HAS_BUILTIN(__builtin_assume) /* Unlike LIKELY, ASSUME requires the condition to be true or else it invokes @@ -31,15 +55,33 @@ #elif defined(_MSC_VER) #define ASSUME __assume #elif defined(__GNUC__) -#define ASSUME(x) do { if(!(x)) __builtin_unreachable(); } while(0) +#define ASSUME(x) do { if(x) break; __builtin_unreachable(); } while(0) #else #define ASSUME(x) ((void)0) #endif -#if __cplusplus >= 201703L || defined(__cpp_if_constexpr) -#define if_constexpr if constexpr +namespace al { + +template +force_inline constexpr auto assume_aligned(T *ptr) noexcept +{ +#ifdef __cpp_lib_assume_aligned + return std::assume_aligned(ptr); +#elif defined(__clang__) || (defined(__GNUC__) && !defined(__ICC)) + return static_cast(__builtin_assume_aligned(ptr, alignment)); +#elif defined(_MSC_VER) + constexpr std::size_t alignment_mask{(1<(ptr)&alignment_mask) == 0) + return ptr; + __assume(0); +#elif defined(__ICC) + __assume_aligned(ptr, alignment); + return ptr; #else -#define if_constexpr if + return ptr; #endif +} + +} // namespace al #endif /* OPTHELPERS_H */ diff --git a/Engine/lib/openal-soft/common/phase_shifter.h b/Engine/lib/openal-soft/common/phase_shifter.h new file mode 100644 index 000000000..ace92c9a0 --- /dev/null +++ b/Engine/lib/openal-soft/common/phase_shifter.h @@ -0,0 +1,314 @@ +#ifndef PHASE_SHIFTER_H +#define PHASE_SHIFTER_H + +#ifdef HAVE_SSE_INTRINSICS +#include +#elif defined(HAVE_NEON) +#include +#endif + +#include +#include + +#include "alcomplex.h" +#include "alspan.h" + + +/* Implements a wide-band +90 degree phase-shift. Note that this should be + * given one sample less of a delay (FilterSize/2 - 1) compared to the direct + * signal delay (FilterSize/2) to properly align. + */ +template +struct PhaseShifterT { + static_assert(FilterSize >= 16, "FilterSize needs to be at least 16"); + static_assert((FilterSize&(FilterSize-1)) == 0, "FilterSize needs to be power-of-two"); + + alignas(16) std::array mCoeffs{}; + + /* Some notes on this filter construction. + * + * A wide-band phase-shift filter needs a delay to maintain linearity. A + * dirac impulse in the center of a time-domain buffer represents a filter + * passing all frequencies through as-is with a pure delay. Converting that + * to the frequency domain, adjusting the phase of each frequency bin by + * +90 degrees, then converting back to the time domain, results in a FIR + * filter that applies a +90 degree wide-band phase-shift. + * + * A particularly notable aspect of the time-domain filter response is that + * every other coefficient is 0. This allows doubling the effective size of + * the filter, by storing only the non-0 coefficients and double-stepping + * over the input to apply it. + * + * Additionally, the resulting filter is independent of the sample rate. + * The same filter can be applied regardless of the device's sample rate + * and achieve the same effect. + */ + PhaseShifterT() + { + using complex_d = std::complex; + constexpr size_t fft_size{FilterSize}; + constexpr size_t half_size{fft_size / 2}; + + auto fftBuffer = std::make_unique(fft_size); + std::fill_n(fftBuffer.get(), fft_size, complex_d{}); + fftBuffer[half_size] = 1.0; + + forward_fft({fftBuffer.get(), fft_size}); + for(size_t i{0};i < half_size+1;++i) + fftBuffer[i] = complex_d{-fftBuffer[i].imag(), fftBuffer[i].real()}; + for(size_t i{half_size+1};i < fft_size;++i) + fftBuffer[i] = std::conj(fftBuffer[fft_size - i]); + inverse_fft({fftBuffer.get(), fft_size}); + + auto fftiter = fftBuffer.get() + half_size + (FilterSize/2 - 1); + for(float &coeff : mCoeffs) + { + coeff = static_cast(fftiter->real() / double{fft_size}); + fftiter -= 2; + } + } + + void process(al::span dst, const float *RESTRICT src) const; + void processAccum(al::span dst, const float *RESTRICT src) const; + +private: +#if defined(HAVE_NEON) + /* There doesn't seem to be NEON intrinsics to do this kind of stipple + * shuffling, so there's two custom methods for it. + */ + static auto shuffle_2020(float32x4_t a, float32x4_t b) + { + float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 0))}; + ret = vsetq_lane_f32(vgetq_lane_f32(a, 2), ret, 1); + ret = vsetq_lane_f32(vgetq_lane_f32(b, 0), ret, 2); + ret = vsetq_lane_f32(vgetq_lane_f32(b, 2), ret, 3); + return ret; + } + static auto shuffle_3131(float32x4_t a, float32x4_t b) + { + float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 1))}; + ret = vsetq_lane_f32(vgetq_lane_f32(a, 3), ret, 1); + ret = vsetq_lane_f32(vgetq_lane_f32(b, 1), ret, 2); + ret = vsetq_lane_f32(vgetq_lane_f32(b, 3), ret, 3); + return ret; + } + static auto unpacklo(float32x4_t a, float32x4_t b) + { + float32x2x2_t result{vzip_f32(vget_low_f32(a), vget_low_f32(b))}; + return vcombine_f32(result.val[0], result.val[1]); + } + static auto unpackhi(float32x4_t a, float32x4_t b) + { + float32x2x2_t result{vzip_f32(vget_high_f32(a), vget_high_f32(b))}; + return vcombine_f32(result.val[0], result.val[1]); + } + static auto load4(float32_t a, float32_t b, float32_t c, float32_t d) + { + float32x4_t ret{vmovq_n_f32(a)}; + ret = vsetq_lane_f32(b, ret, 1); + ret = vsetq_lane_f32(c, ret, 2); + ret = vsetq_lane_f32(d, ret, 3); + return ret; + } +#endif +}; + +template +inline void PhaseShifterT::process(al::span dst, const float *RESTRICT src) const +{ +#ifdef HAVE_SSE_INTRINSICS + if(size_t todo{dst.size()>>1}) + { + auto *out = reinterpret_cast<__m64*>(dst.data()); + do { + __m128 r04{_mm_setzero_ps()}; + __m128 r14{_mm_setzero_ps()}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; + const __m128 s0{_mm_loadu_ps(&src[j*2])}; + const __m128 s1{_mm_loadu_ps(&src[j*2 + 4])}; + + __m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))}; + r04 = _mm_add_ps(r04, _mm_mul_ps(s, coeffs)); + + s = _mm_shuffle_ps(s0, s1, _MM_SHUFFLE(3, 1, 3, 1)); + r14 = _mm_add_ps(r14, _mm_mul_ps(s, coeffs)); + } + src += 2; + + __m128 r4{_mm_add_ps(_mm_unpackhi_ps(r04, r14), _mm_unpacklo_ps(r04, r14))}; + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + + _mm_storel_pi(out, r4); + ++out; + } while(--todo); + } + if((dst.size()&1)) + { + __m128 r4{_mm_setzero_ps()}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; + const __m128 s{_mm_setr_ps(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])}; + r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); + } + r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + + dst.back() = _mm_cvtss_f32(r4); + } + +#elif defined(HAVE_NEON) + + size_t pos{0}; + if(size_t todo{dst.size()>>1}) + { + do { + float32x4_t r04{vdupq_n_f32(0.0f)}; + float32x4_t r14{vdupq_n_f32(0.0f)}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; + const float32x4_t s0{vld1q_f32(&src[j*2])}; + const float32x4_t s1{vld1q_f32(&src[j*2 + 4])}; + + r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs); + r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs); + } + src += 2; + + float32x4_t r4{vaddq_f32(unpackhi(r04, r14), unpacklo(r04, r14))}; + float32x2_t r2{vadd_f32(vget_low_f32(r4), vget_high_f32(r4))}; + + vst1_f32(&dst[pos], r2); + pos += 2; + } while(--todo); + } + if((dst.size()&1)) + { + float32x4_t r4{vdupq_n_f32(0.0f)}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; + const float32x4_t s{load4(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])}; + r4 = vmlaq_f32(r4, s, coeffs); + } + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + dst[pos] = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + } + +#else + + for(float &output : dst) + { + float ret{0.0f}; + for(size_t j{0};j < mCoeffs.size();++j) + ret += src[j*2] * mCoeffs[j]; + + output = ret; + ++src; + } +#endif +} + +template +inline void PhaseShifterT::processAccum(al::span dst, const float *RESTRICT src) const +{ +#ifdef HAVE_SSE_INTRINSICS + if(size_t todo{dst.size()>>1}) + { + auto *out = reinterpret_cast<__m64*>(dst.data()); + do { + __m128 r04{_mm_setzero_ps()}; + __m128 r14{_mm_setzero_ps()}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; + const __m128 s0{_mm_loadu_ps(&src[j*2])}; + const __m128 s1{_mm_loadu_ps(&src[j*2 + 4])}; + + __m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))}; + r04 = _mm_add_ps(r04, _mm_mul_ps(s, coeffs)); + + s = _mm_shuffle_ps(s0, s1, _MM_SHUFFLE(3, 1, 3, 1)); + r14 = _mm_add_ps(r14, _mm_mul_ps(s, coeffs)); + } + src += 2; + + __m128 r4{_mm_add_ps(_mm_unpackhi_ps(r04, r14), _mm_unpacklo_ps(r04, r14))}; + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + + _mm_storel_pi(out, _mm_add_ps(_mm_loadl_pi(_mm_undefined_ps(), out), r4)); + ++out; + } while(--todo); + } + if((dst.size()&1)) + { + __m128 r4{_mm_setzero_ps()}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; + const __m128 s{_mm_setr_ps(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])}; + r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); + } + r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + + dst.back() += _mm_cvtss_f32(r4); + } + +#elif defined(HAVE_NEON) + + size_t pos{0}; + if(size_t todo{dst.size()>>1}) + { + do { + float32x4_t r04{vdupq_n_f32(0.0f)}; + float32x4_t r14{vdupq_n_f32(0.0f)}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; + const float32x4_t s0{vld1q_f32(&src[j*2])}; + const float32x4_t s1{vld1q_f32(&src[j*2 + 4])}; + + r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs); + r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs); + } + src += 2; + + float32x4_t r4{vaddq_f32(unpackhi(r04, r14), unpacklo(r04, r14))}; + float32x2_t r2{vadd_f32(vget_low_f32(r4), vget_high_f32(r4))}; + + vst1_f32(&dst[pos], vadd_f32(vld1_f32(&dst[pos]), r2)); + pos += 2; + } while(--todo); + } + if((dst.size()&1)) + { + float32x4_t r4{vdupq_n_f32(0.0f)}; + for(size_t j{0};j < mCoeffs.size();j+=4) + { + const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; + const float32x4_t s{load4(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])}; + r4 = vmlaq_f32(r4, s, coeffs); + } + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + dst[pos] += vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + } + +#else + + for(float &output : dst) + { + float ret{0.0f}; + for(size_t j{0};j < mCoeffs.size();++j) + ret += src[j*2] * mCoeffs[j]; + + output += ret; + ++src; + } +#endif +} + +#endif /* PHASE_SHIFTER_H */ diff --git a/Engine/lib/openal-soft/common/polyphase_resampler.cpp b/Engine/lib/openal-soft/common/polyphase_resampler.cpp index 88c4bc4bd..bb8f69a41 100644 --- a/Engine/lib/openal-soft/common/polyphase_resampler.cpp +++ b/Engine/lib/openal-soft/common/polyphase_resampler.cpp @@ -4,7 +4,7 @@ #include #include -#include "math_defs.h" +#include "alnumbers.h" #include "opthelpers.h" @@ -21,9 +21,9 @@ using uint = unsigned int; */ double Sinc(const double x) { - if UNLIKELY(std::abs(x) < Epsilon) + if(unlikely(std::abs(x) < Epsilon)) return 1.0; - return std::sin(al::MathDefs::Pi()*x) / (al::MathDefs::Pi()*x); + return std::sin(al::numbers::pi*x) / (al::numbers::pi*x); } /* The zero-order modified Bessel function of the first kind, used for the @@ -95,7 +95,7 @@ constexpr uint Gcd(uint x, uint y) */ constexpr uint CalcKaiserOrder(const double rejection, const double transition) { - const double w_t{2.0 * al::MathDefs::Pi() * transition}; + const double w_t{2.0 * al::numbers::pi * transition}; if LIKELY(rejection > 21.0) return static_cast(std::ceil((rejection - 7.95) / (2.285 * w_t))); return static_cast(std::ceil(5.79 / w_t)); diff --git a/Engine/lib/openal-soft/common/ringbuffer.cpp b/Engine/lib/openal-soft/common/ringbuffer.cpp index 918574990..0aec1d497 100644 --- a/Engine/lib/openal-soft/common/ringbuffer.cpp +++ b/Engine/lib/openal-soft/common/ringbuffer.cpp @@ -160,9 +160,9 @@ size_t RingBuffer::write(const void *src, size_t cnt) noexcept } -ll_ringbuffer_data_pair RingBuffer::getReadVector() const noexcept +auto RingBuffer::getReadVector() const noexcept -> DataPair { - ll_ringbuffer_data_pair ret; + DataPair ret; size_t w{mWritePtr.load(std::memory_order_acquire)}; size_t r{mReadPtr.load(std::memory_order_acquire)}; @@ -192,9 +192,9 @@ ll_ringbuffer_data_pair RingBuffer::getReadVector() const noexcept return ret; } -ll_ringbuffer_data_pair RingBuffer::getWriteVector() const noexcept +auto RingBuffer::getWriteVector() const noexcept -> DataPair { - ll_ringbuffer_data_pair ret; + DataPair ret; size_t w{mWritePtr.load(std::memory_order_acquire)}; size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask}; diff --git a/Engine/lib/openal-soft/common/ringbuffer.h b/Engine/lib/openal-soft/common/ringbuffer.h index fa8fce10d..2a3797b05 100644 --- a/Engine/lib/openal-soft/common/ringbuffer.h +++ b/Engine/lib/openal-soft/common/ringbuffer.h @@ -16,13 +16,6 @@ * single-consumer/single-provider operation. */ -struct ll_ringbuffer_data { - al::byte *buf; - size_t len; -}; -using ll_ringbuffer_data_pair = std::pair; - - struct RingBuffer { private: std::atomic mWritePtr{0u}; @@ -34,6 +27,13 @@ private: al::FlexArray mBuffer; public: + struct Data { + al::byte *buf; + size_t len; + }; + using DataPair = std::pair; + + RingBuffer(const size_t count) : mBuffer{count} { } /** Reset the read and write pointers to zero. This is not thread safe. */ @@ -44,13 +44,13 @@ public: * hold the current readable data. If the readable data is in one segment * the second segment has zero length. */ - ll_ringbuffer_data_pair getReadVector() const noexcept; + DataPair getReadVector() const noexcept; /** * The non-copying data writer. Returns two ringbuffer data pointers that * hold the current writeable data. If the writeable data is in one segment * the second segment has zero length. */ - ll_ringbuffer_data_pair getWriteVector() const noexcept; + DataPair getWriteVector() const noexcept; /** * Return the number of elements available for reading. This is the number @@ -98,6 +98,8 @@ public: void writeAdvance(size_t cnt) noexcept { mWritePtr.fetch_add(cnt, std::memory_order_acq_rel); } + size_t getElemSize() const noexcept { return mElemSize; } + /** * Create a new ringbuffer to hold at least `sz' elements of `elem_sz' * bytes. The number of elements is rounded up to the next power of two diff --git a/Engine/lib/openal-soft/common/threads.cpp b/Engine/lib/openal-soft/common/threads.cpp index e847d1f83..c782dc35c 100644 --- a/Engine/lib/openal-soft/common/threads.cpp +++ b/Engine/lib/openal-soft/common/threads.cpp @@ -90,30 +90,43 @@ bool semaphore::try_wait() noexcept #else -#if defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP) #include #ifdef HAVE_PTHREAD_NP_H #include #endif +#include + +namespace { + +using setname_t1 = int(*)(const char*); +using setname_t2 = int(*)(pthread_t, const char*); +using setname_t3 = int(*)(pthread_t, const char*, void*); + +void setname_caller(setname_t1 func, const char *name) +{ func(name); } + +void setname_caller(setname_t2 func, const char *name) +{ func(pthread_self(), name); } + +void setname_caller(setname_t3 func, const char *name) +{ func(pthread_self(), "%s", static_cast(const_cast(name))); } + +} // namespace void althrd_setname(const char *name) { #if defined(HAVE_PTHREAD_SET_NAME_NP) - pthread_set_name_np(pthread_self(), name); -#elif defined(PTHREAD_SETNAME_NP_ONE_PARAM) - pthread_setname_np(name); -#elif defined(PTHREAD_SETNAME_NP_THREE_PARAMS) - pthread_setname_np(pthread_self(), "%s", (void*)name); -#else - pthread_setname_np(pthread_self(), name); + setname_caller(pthread_set_name_np, name); +#elif defined(HAVE_PTHREAD_SETNAME_NP) + setname_caller(pthread_setname_np, name); #endif + /* Avoid unused function/parameter warnings. */ + std::ignore = name; + std::ignore = static_cast(&setname_caller); + std::ignore = static_cast(&setname_caller); + std::ignore = static_cast(&setname_caller); } -#else - -void althrd_setname(const char*) { } -#endif - #ifdef __APPLE__ namespace al { diff --git a/Engine/lib/openal-soft/common/vecmat.h b/Engine/lib/openal-soft/common/vecmat.h index d301cc308..78fd806e7 100644 --- a/Engine/lib/openal-soft/common/vecmat.h +++ b/Engine/lib/openal-soft/common/vecmat.h @@ -35,11 +35,20 @@ public: return *this; } - T normalize() + VectorR operator-(const VectorR &rhs) const noexcept { - const T length{std::sqrt(mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2])}; - if(length > std::numeric_limits::epsilon()) + const VectorR ret{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1], + mVals[2] - rhs.mVals[2], mVals[3] - rhs.mVals[3]}; + return ret; + } + + T normalize(T limit = std::numeric_limits::epsilon()) + { + limit = std::max(limit, std::numeric_limits::epsilon()); + const T length_sqr{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]}; + if(length_sqr > limit*limit) { + const T length{std::sqrt(length_sqr)}; T inv_length{T{1}/length}; mVals[0] *= inv_length; mVals[1] *= inv_length; diff --git a/Engine/lib/openal-soft/config.h.in b/Engine/lib/openal-soft/config.h.in index a28204eff..416b87d4b 100644 --- a/Engine/lib/openal-soft/config.h.in +++ b/Engine/lib/openal-soft/config.h.in @@ -1,6 +1,5 @@ -/* API declaration export attribute */ -#define AL_API ${EXPORT_DECL} -#define ALC_API ${EXPORT_DECL} +/* Define if deprecated EAX extensions are enabled */ +#cmakedefine ALSOFT_EAX /* Define if HRTF data is embedded in the library */ #cmakedefine ALSOFT_EMBED_HRTF_DATA @@ -17,6 +16,9 @@ /* Define if we have the getopt function */ #cmakedefine HAVE_GETOPT +/* Define if we have DBus/RTKit */ +#cmakedefine HAVE_RTKIT + /* Define if we have SSE CPU extensions */ #cmakedefine HAVE_SSE #cmakedefine HAVE_SSE2 @@ -32,6 +34,9 @@ /* Define if we have the OSS backend */ #cmakedefine HAVE_OSS +/* Define if we have the PipeWire backend */ +#cmakedefine HAVE_PIPEWIRE + /* Define if we have the Solaris backend */ #cmakedefine HAVE_SOLARIS @@ -107,11 +112,5 @@ /* Define if we have pthread_setname_np() */ #cmakedefine HAVE_PTHREAD_SETNAME_NP -/* Define if pthread_setname_np() only accepts one parameter */ -#cmakedefine PTHREAD_SETNAME_NP_ONE_PARAM - -/* Define if pthread_setname_np() accepts three parameters */ -#cmakedefine PTHREAD_SETNAME_NP_THREE_PARAMS - /* Define if we have pthread_set_name_np() */ #cmakedefine HAVE_PTHREAD_SET_NAME_NP diff --git a/Engine/lib/openal-soft/core/ambdec.cpp b/Engine/lib/openal-soft/core/ambdec.cpp index 8655f4e78..0df22bc95 100644 --- a/Engine/lib/openal-soft/core/ambdec.cpp +++ b/Engine/lib/openal-soft/core/ambdec.cpp @@ -179,6 +179,9 @@ al::optional load_ambdec_matrix(float (&gains)[MaxAmbiOrder+1], } // namespace +AmbDecConf::~AmbDecConf() = default; + + al::optional AmbDecConf::load(const char *fname) noexcept { al::ifstream f{fname}; @@ -198,7 +201,7 @@ al::optional AmbDecConf::load(const char *fname) noexcept return al::make_optional("Malformed line: "+buffer); if(command == "/description") - istr >> Description; + readline(istr, Description); else if(command == "/version") { istr >> Version; diff --git a/Engine/lib/openal-soft/core/ambdec.h b/Engine/lib/openal-soft/core/ambdec.h index b6aa12257..e1bcde26b 100644 --- a/Engine/lib/openal-soft/core/ambdec.h +++ b/Engine/lib/openal-soft/core/ambdec.h @@ -46,6 +46,8 @@ struct AmbDecConf { float HFOrderGain[MaxAmbiOrder+1]{}; CoeffArray *HFMatrix; + ~AmbDecConf(); + al::optional load(const char *fname) noexcept; }; diff --git a/Engine/lib/openal-soft/core/ambidefs.cpp b/Engine/lib/openal-soft/core/ambidefs.cpp new file mode 100644 index 000000000..2725748e6 --- /dev/null +++ b/Engine/lib/openal-soft/core/ambidefs.cpp @@ -0,0 +1,44 @@ + +#include "config.h" + +#include "ambidefs.h" + +#include + + +namespace { + +constexpr std::array Ambi3DDecoderHFScale{{ + 1.00000000e+00f, 1.00000000e+00f +}}; +constexpr std::array Ambi3DDecoderHFScale2O{{ + 7.45355990e-01f, 1.00000000e+00f, 1.00000000e+00f +}}; +constexpr std::array Ambi3DDecoderHFScale3O{{ + 5.89792205e-01f, 8.79693856e-01f, 1.00000000e+00f, 1.00000000e+00f +}}; + +inline auto& GetDecoderHFScales(uint order) noexcept +{ + if(order >= 3) return Ambi3DDecoderHFScale3O; + if(order == 2) return Ambi3DDecoderHFScale2O; + return Ambi3DDecoderHFScale; +} + +} // namespace + +auto AmbiScale::GetHFOrderScales(const uint in_order, const uint out_order) noexcept + -> std::array +{ + std::array ret{}; + + assert(out_order >= in_order); + + const auto &target = GetDecoderHFScales(out_order); + const auto &input = GetDecoderHFScales(in_order); + + for(size_t i{0};i < in_order+1;++i) + ret[i] = input[i] / target[i]; + + return ret; +} diff --git a/Engine/lib/openal-soft/core/ambidefs.h b/Engine/lib/openal-soft/core/ambidefs.h index a72f7b780..82a1a4e5b 100644 --- a/Engine/lib/openal-soft/core/ambidefs.h +++ b/Engine/lib/openal-soft/core/ambidefs.h @@ -97,6 +97,22 @@ struct AmbiScale { }}; return ret; } + static auto& FromUHJ() noexcept + { + static constexpr const std::array ret{{ + 1.000000000f, /* ACN 0 (W), sqrt(1) */ + 1.224744871f, /* ACN 1 (Y), sqrt(3/2) */ + 1.224744871f, /* ACN 2 (Z), sqrt(3/2) */ + 1.224744871f, /* ACN 3 (X), sqrt(3/2) */ + /* Higher orders not relevant for UHJ. */ + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, + }}; + return ret; + } + + /* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */ + static std::array GetHFOrderScales(const uint in_order, + const uint out_order) noexcept; }; struct AmbiIndex { diff --git a/Engine/lib/openal-soft/Alc/async_event.h b/Engine/lib/openal-soft/core/async_event.h similarity index 54% rename from Engine/lib/openal-soft/Alc/async_event.h rename to Engine/lib/openal-soft/core/async_event.h index 1ee58b105..750f38c9c 100644 --- a/Engine/lib/openal-soft/Alc/async_event.h +++ b/Engine/lib/openal-soft/core/async_event.h @@ -1,34 +1,40 @@ -#ifndef ALC_EVENT_H -#define ALC_EVENT_H +#ifndef CORE_EVENT_H +#define CORE_EVENT_H #include "almalloc.h" struct EffectState; -enum class VChangeState; 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 : uint { + /* End event thread processing. */ + KillThread = 0, + + /* User event types. */ + SourceStateChange = 1<<0, + BufferCompleted = 1<<1, + Disconnected = 1<<2, + + /* Internal events. */ + ReleaseEffectState = 65536, + }; + + enum class SrcState { + Reset, + Stop, + Play, + Pause + }; + uint EnumType{0u}; union { char dummy; struct { uint id; - VChangeState state; + SrcState state; } srcstate; struct { uint id; diff --git a/Engine/lib/openal-soft/Alc/bformatdec.cpp b/Engine/lib/openal-soft/core/bformatdec.cpp similarity index 52% rename from Engine/lib/openal-soft/Alc/bformatdec.cpp rename to Engine/lib/openal-soft/core/bformatdec.cpp index 9b2d9049d..606093c07 100644 --- a/Engine/lib/openal-soft/Alc/bformatdec.cpp +++ b/Engine/lib/openal-soft/core/bformatdec.cpp @@ -5,101 +5,20 @@ #include #include -#include #include -#include -#include +#include #include "almalloc.h" -#include "alu.h" -#include "core/ambdec.h" -#include "core/filters/splitter.h" +#include "alnumbers.h" +#include "filters/splitter.h" #include "front_stablizer.h" -#include "math_defs.h" +#include "mixer.h" #include "opthelpers.h" -namespace { - -constexpr std::array Ambi3DDecoderHFScale{{ - 1.00000000e+00f, 1.00000000e+00f -}}; -constexpr std::array Ambi3DDecoderHFScale2O{{ - 7.45355990e-01f, 1.00000000e+00f, 1.00000000e+00f -}}; -constexpr std::array Ambi3DDecoderHFScale3O{{ - 5.89792205e-01f, 8.79693856e-01f, 1.00000000e+00f, 1.00000000e+00f -}}; - -inline auto& GetDecoderHFScales(uint order) noexcept -{ - if(order >= 3) return Ambi3DDecoderHFScale3O; - if(order == 2) return Ambi3DDecoderHFScale2O; - return Ambi3DDecoderHFScale; -} - -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) + const al::span coeffslf, const float xover_f0norm, + std::unique_ptr stablizer) : mStablizer{std::move(stablizer)}, mDualBand{!coeffslf.empty()}, mChannelDec{inchans} { if(!mDualBand) @@ -113,6 +32,10 @@ BFormatDec::BFormatDec(const size_t inchans, const al::span co } else { + mChannelDec[0].mXOver.init(xover_f0norm); + for(size_t j{1};j < mChannelDec.size();++j) + mChannelDec[j].mXOver = mChannelDec[0].mXOver; + for(size_t j{0};j < mChannelDec.size();++j) { float *outcoeffs{mChannelDec[j].mGains.Dual[sHFBand]}; @@ -210,41 +133,35 @@ void BFormatDec::processStablize(const al::span OutBuffer, 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]; + /* Combine the delayed mid signal with the decoded mid signal. */ + float *tmpbuf{mStablizer->TempBuf.data()}; + auto tmpiter = std::copy(mStablizer->MidDelay.cbegin(), mStablizer->MidDelay.cend(), tmpbuf); + for(size_t i{0};i < SamplesToDo;++i,++tmpiter) + *tmpiter = OutBuffer[lidx][i] + OutBuffer[ridx][i]; /* Save the newest samples for next time. */ - std::copy_n(tmpbuf.cbegin(), mStablizer->MidDelay.size(), mStablizer->MidDelay.begin()); + std::copy_n(tmpbuf+SamplesToDo, 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). + /* Apply an all-pass on the signal in reverse. The future samples are + * included with the all-pass to reduce the error in the output samples + * (the smaller the delay, the more error is introduced). */ - mStablizer->MidFilter.applyAllpass(tmpbuf); - tmpbuf = tmpbuf.subspan(); - std::reverse(tmpbuf.begin(), tmpbuf.end()); + mStablizer->MidFilter.applyAllpassRev({tmpbuf, SamplesToDo+FrontStablizer::DelayLength}); /* Now apply the band-splitter, combining its phase shift with the reversed * phase shift, restoring the original phase on the split signal. */ - mStablizer->MidFilter.process(tmpbuf, mStablizer->MidHF.data(), mStablizer->MidLF.data()); + mStablizer->MidFilter.process({tmpbuf, SamplesToDo}, mStablizer->MidHF.data(), + mStablizer->MidLF.data()); /* This pans the separate low- and high-frequency signals between being on * the center channel and the left+right channels. The low-frequency signal * is panned 1/3rd toward center and the high-frequency signal is panned * 1/4th toward center. These values can be tweaked. */ - const float cos_lf{std::cos(1.0f/3.0f * (al::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))}; + const float cos_lf{std::cos(1.0f/3.0f * (al::numbers::pi_v*0.5f))}; + const float cos_hf{std::cos(1.0f/4.0f * (al::numbers::pi_v*0.5f))}; + const float sin_lf{std::sin(1.0f/3.0f * (al::numbers::pi_v*0.5f))}; + const float sin_hf{std::sin(1.0f/4.0f * (al::numbers::pi_v*0.5f))}; for(size_t i{0};i < SamplesToDo;i++) { const float m{mStablizer->MidLF[i]*cos_lf + mStablizer->MidHF[i]*cos_hf + mid[i]}; @@ -266,33 +183,10 @@ void BFormatDec::processStablize(const al::span OutBuffer, } -auto BFormatDec::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; -} - -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) + const float xover_f0norm, std::unique_ptr stablizer) { - return std::unique_ptr{new(FamCount(inchans)) - BFormatDec{inchans, coeffs, coeffslf, std::move(stablizer)}}; + return std::make_unique(inchans, coeffs, coeffslf, xover_f0norm, + std::move(stablizer)); } diff --git a/Engine/lib/openal-soft/Alc/bformatdec.h b/Engine/lib/openal-soft/core/bformatdec.h similarity index 60% rename from Engine/lib/openal-soft/Alc/bformatdec.h rename to Engine/lib/openal-soft/core/bformatdec.h index 7715d364d..7a27a5a42 100644 --- a/Engine/lib/openal-soft/Alc/bformatdec.h +++ b/Engine/lib/openal-soft/core/bformatdec.h @@ -1,5 +1,5 @@ -#ifndef BFORMATDEC_H -#define BFORMATDEC_H +#ifndef CORE_BFORMATDEC_H +#define CORE_BFORMATDEC_H #include #include @@ -7,12 +7,12 @@ #include "almalloc.h" #include "alspan.h" -#include "core/ambidefs.h" -#include "core/bufferline.h" -#include "core/devformat.h" -#include "core/filters/splitter.h" +#include "ambidefs.h" +#include "bufferline.h" +#include "devformat.h" +#include "filters/splitter.h" +#include "vector.h" -struct AmbDecConf; struct FrontStablizer; @@ -38,16 +38,19 @@ class BFormatDec { const std::unique_ptr mStablizer; const bool mDualBand{false}; - al::FlexArray mChannelDec; + /* TODO: This should ideally be a FlexArray, since ChannelDecoder is rather + * small and only a few are needed (3, 4, 5, 7, typically). But that can + * only be used in a standard layout struct, and a std::unique_ptr member + * (mStablizer) causes GCC and Clang to warn it's not. + */ + al::vector mChannelDec; public: - BFormatDec(const 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); + const al::span coeffslf, const float xover_f0norm, + std::unique_ptr stablizer); - bool hasStablizer() const noexcept { return mStablizer != nullptr; }; + 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, @@ -58,18 +61,11 @@ public: const FloatBufferLine *InSamples, const size_t lidx, const size_t ridx, const size_t cidx, const size_t SamplesToDo); - /* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */ - static std::array GetHFOrderScales(const uint in_order, - const uint out_order) noexcept; - - static std::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); + const float xover_f0norm, std::unique_ptr stablizer); - DEF_FAM_NEWDEL(BFormatDec, mChannelDec) + DEF_NEWDEL(BFormatDec) }; -#endif /* BFORMATDEC_H */ +#endif /* CORE_BFORMATDEC_H */ diff --git a/Engine/lib/openal-soft/core/bs2b.cpp b/Engine/lib/openal-soft/core/bs2b.cpp index 00207bc0e..303bf9bd9 100644 --- a/Engine/lib/openal-soft/core/bs2b.cpp +++ b/Engine/lib/openal-soft/core/bs2b.cpp @@ -27,8 +27,8 @@ #include #include +#include "alnumbers.h" #include "bs2b.h" -#include "math_defs.h" /* Set up all data. */ @@ -91,11 +91,11 @@ static void init(struct bs2b *bs2b) * $d = 1 / 2 / pi / $fc; * $x = exp(-1 / $d); */ - x = std::exp(-al::MathDefs::Tau() * Fc_lo / static_cast(bs2b->srate)); + x = std::exp(-al::numbers::pi_v*2.0f*Fc_lo/static_cast(bs2b->srate)); bs2b->b1_lo = x; bs2b->a0_lo = G_lo * (1.0f - x) * g; - x = std::exp(-al::MathDefs::Tau() * Fc_hi / static_cast(bs2b->srate)); + x = std::exp(-al::numbers::pi_v*2.0f*Fc_hi/static_cast(bs2b->srate)); bs2b->b1_hi = x; bs2b->a0_hi = (1.0f - G_hi * (1.0f - x)) * g; bs2b->a1_hi = -x * g; diff --git a/Engine/lib/openal-soft/core/bsinc_defs.h b/Engine/lib/openal-soft/core/bsinc_defs.h index 438652895..f29582318 100644 --- a/Engine/lib/openal-soft/core/bsinc_defs.h +++ b/Engine/lib/openal-soft/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/Engine/lib/openal-soft/core/bsinc_tables.cpp b/Engine/lib/openal-soft/core/bsinc_tables.cpp index 315e14488..a81167d23 100644 --- a/Engine/lib/openal-soft/core/bsinc_tables.cpp +++ b/Engine/lib/openal-soft/core/bsinc_tables.cpp @@ -9,7 +9,8 @@ #include #include -#include "math_defs.h" +#include "alnumbers.h" +#include "core/mixer/defs.h" namespace { @@ -24,9 +25,10 @@ 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); + return std::sin(al::numbers::pi*x) / (al::numbers::pi*x); } /* The zero-order modified Bessel function of the first kind, used for the @@ -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,12 +84,12 @@ 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()); + return (rejection - 7.95) / (2.285 * al::numbers::pi*2.0 * order); /* This enforces a minimum rejection of just above 21.18dB */ - return 5.79 / (order * al::MathDefs::Tau()); + return 5.79 / (al::numbers::pi*2.0 * order); } /* Calculates the beta value of the Kaiser window. Rejection is in dB. */ @@ -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/Engine/lib/openal-soft/Alc/buffer_storage.cpp b/Engine/lib/openal-soft/core/buffer_storage.cpp similarity index 85% rename from Engine/lib/openal-soft/Alc/buffer_storage.cpp rename to Engine/lib/openal-soft/core/buffer_storage.cpp index 7d3addddd..1c80e7ef6 100644 --- a/Engine/lib/openal-soft/Alc/buffer_storage.cpp +++ b/Engine/lib/openal-soft/core/buffer_storage.cpp @@ -3,7 +3,7 @@ #include "buffer_storage.h" -#include +#include uint BytesFromFmt(FmtType type) noexcept @@ -33,6 +33,10 @@ uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept case FmtX71: return 8; case FmtBFormat2D: return (ambiorder*2) + 1; case FmtBFormat3D: return (ambiorder+1) * (ambiorder+1); + case FmtUHJ2: return 2; + case FmtUHJ3: return 3; + case FmtUHJ4: return 4; + case FmtSuperStereo: return 2; } return 0; } diff --git a/Engine/lib/openal-soft/Alc/buffer_storage.h b/Engine/lib/openal-soft/core/buffer_storage.h similarity index 53% rename from Engine/lib/openal-soft/Alc/buffer_storage.h rename to Engine/lib/openal-soft/core/buffer_storage.h index b08dd1e06..ec9346814 100644 --- a/Engine/lib/openal-soft/Alc/buffer_storage.h +++ b/Engine/lib/openal-soft/core/buffer_storage.h @@ -1,9 +1,11 @@ -#ifndef ALC_BUFFER_STORAGE_H -#define ALC_BUFFER_STORAGE_H +#ifndef CORE_BUFFER_STORAGE_H +#define CORE_BUFFER_STORAGE_H #include #include "albyte.h" +#include "alnumeric.h" +#include "ambidefs.h" using uint = unsigned int; @@ -27,6 +29,10 @@ enum FmtChannels : unsigned char { FmtX71, /* (WFX order) */ FmtBFormat2D, FmtBFormat3D, + FmtUHJ2, /* 2-channel UHJ, aka "BHJ", stereo-compatible */ + FmtUHJ3, /* 3-channel UHJ, aka "THJ" */ + FmtUHJ4, /* 4-channel UHJ, aka "PHJ" */ + FmtSuperStereo, /* Stereo processed with Super Stereo. */ }; enum class AmbiLayout : unsigned char { @@ -37,6 +43,7 @@ enum class AmbiScaling : unsigned char { FuMa, SN3D, N3D, + UHJ, }; uint BytesFromFmt(FmtType type) noexcept; @@ -44,6 +51,27 @@ uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept; inline uint FrameSizeFromFmt(FmtChannels chans, FmtType type, uint ambiorder) noexcept { return ChannelsFromFmt(chans, ambiorder) * BytesFromFmt(type); } +constexpr bool IsBFormat(FmtChannels chans) noexcept +{ return chans == FmtBFormat2D || chans == FmtBFormat3D; } + +/* Super Stereo is considered part of the UHJ family here, since it goes + * through similar processing as UHJ, both result in a B-Format signal, and + * needs the same consideration as BHJ (three channel result with only two + * channel input). + */ +constexpr bool IsUHJ(FmtChannels chans) noexcept +{ return chans == FmtUHJ2 || chans == FmtUHJ3 || chans == FmtUHJ4 || chans == FmtSuperStereo; } + +/** Ambisonic formats are either B-Format or UHJ formats. */ +constexpr bool IsAmbisonic(FmtChannels chans) noexcept +{ return IsBFormat(chans) || IsUHJ(chans); } + +constexpr bool Is2DAmbisonic(FmtChannels chans) noexcept +{ + return chans == FmtBFormat2D || chans == FmtUHJ2 || chans == FmtUHJ3 + || chans == FmtSuperStereo; +} + using CallbackType = int(*)(void*, void*, int); @@ -65,8 +93,7 @@ struct BufferStorage { { return ChannelsFromFmt(mChannels, mAmbiOrder); } inline uint frameSizeFromFmt() const noexcept { return channelsFromFmt() * bytesFromFmt(); } - inline bool isBFormat() const noexcept - { return mChannels == FmtBFormat2D || mChannels == FmtBFormat3D; } + inline bool isBFormat() const noexcept { return IsBFormat(mChannels); } }; -#endif /* ALC_BUFFER_STORAGE_H */ +#endif /* CORE_BUFFER_STORAGE_H */ diff --git a/Engine/lib/openal-soft/core/bufferline.h b/Engine/lib/openal-soft/core/bufferline.h index 503e208d4..8b445f3ff 100644 --- a/Engine/lib/openal-soft/core/bufferline.h +++ b/Engine/lib/openal-soft/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/Engine/lib/openal-soft/core/context.cpp b/Engine/lib/openal-soft/core/context.cpp new file mode 100644 index 000000000..39fd85221 --- /dev/null +++ b/Engine/lib/openal-soft/core/context.cpp @@ -0,0 +1,138 @@ + +#include "config.h" + +#include + +#include "async_event.h" +#include "context.h" +#include "device.h" +#include "effectslot.h" +#include "logging.h" +#include "ringbuffer.h" +#include "voice.h" +#include "voice_change.h" + + +ContextBase::ContextBase(DeviceBase *device) : mDevice{device} +{ } + +ContextBase::~ContextBase() +{ + size_t count{0}; + ContextProps *cprops{mParams.ContextUpdate.exchange(nullptr, std::memory_order_relaxed)}; + if(cprops) + { + ++count; + delete cprops; + } + cprops = mFreeContextProps.exchange(nullptr, std::memory_order_acquire); + while(cprops) + { + std::unique_ptr old{cprops}; + cprops = old->next.load(std::memory_order_relaxed); + ++count; + } + TRACE("Freed %zu context property object%s\n", count, (count==1)?"":"s"); + + count = 0; + EffectSlotProps *eprops{mFreeEffectslotProps.exchange(nullptr, std::memory_order_acquire)}; + while(eprops) + { + std::unique_ptr old{eprops}; + eprops = old->next.load(std::memory_order_relaxed); + ++count; + } + TRACE("Freed %zu AuxiliaryEffectSlot property object%s\n", count, (count==1)?"":"s"); + + if(EffectSlotArray *curarray{mActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed)}) + { + al::destroy_n(curarray->end(), curarray->size()); + delete curarray; + } + + delete mVoices.exchange(nullptr, std::memory_order_relaxed); + + if(mAsyncEvents) + { + count = 0; + auto evt_vec = mAsyncEvents->getReadVector(); + if(evt_vec.first.len > 0) + { + al::destroy_n(reinterpret_cast(evt_vec.first.buf), evt_vec.first.len); + count += evt_vec.first.len; + } + if(evt_vec.second.len > 0) + { + al::destroy_n(reinterpret_cast(evt_vec.second.buf), evt_vec.second.len); + count += evt_vec.second.len; + } + if(count > 0) + TRACE("Destructed %zu orphaned event%s\n", count, (count==1)?"":"s"); + mAsyncEvents->readAdvance(count); + } +} + + +void ContextBase::allocVoiceChanges() +{ + constexpr size_t clustersize{128}; + + VoiceChangeCluster cluster{std::make_unique(clustersize)}; + for(size_t i{1};i < clustersize;++i) + cluster[i-1].mNext.store(std::addressof(cluster[i]), std::memory_order_relaxed); + cluster[clustersize-1].mNext.store(mVoiceChangeTail, std::memory_order_relaxed); + + mVoiceChangeClusters.emplace_back(std::move(cluster)); + mVoiceChangeTail = mVoiceChangeClusters.back().get(); +} + +void ContextBase::allocVoiceProps() +{ + constexpr size_t clustersize{32}; + + TRACE("Increasing allocated voice properties to %zu\n", + (mVoicePropClusters.size()+1) * clustersize); + + VoicePropsCluster cluster{std::make_unique(clustersize)}; + for(size_t i{1};i < clustersize;++i) + cluster[i-1].next.store(std::addressof(cluster[i]), std::memory_order_relaxed); + mVoicePropClusters.emplace_back(std::move(cluster)); + + VoicePropsItem *oldhead{mFreeVoiceProps.load(std::memory_order_acquire)}; + do { + mVoicePropClusters.back()[clustersize-1].next.store(oldhead, std::memory_order_relaxed); + } while(mFreeVoiceProps.compare_exchange_weak(oldhead, mVoicePropClusters.back().get(), + std::memory_order_acq_rel, std::memory_order_acquire) == false); +} + +void ContextBase::allocVoices(size_t addcount) +{ + constexpr size_t clustersize{32}; + /* Convert element count to cluster count. */ + addcount = (addcount+(clustersize-1)) / clustersize; + + if(addcount >= std::numeric_limits::max()/clustersize - mVoiceClusters.size()) + throw std::runtime_error{"Allocating too many voices"}; + const size_t totalcount{(mVoiceClusters.size()+addcount) * clustersize}; + TRACE("Increasing allocated voices to %zu\n", totalcount); + + auto newarray = VoiceArray::Create(totalcount); + while(addcount) + { + mVoiceClusters.emplace_back(std::make_unique(clustersize)); + --addcount; + } + + auto voice_iter = newarray->begin(); + for(VoiceCluster &cluster : mVoiceClusters) + { + for(size_t i{0};i < clustersize;++i) + *(voice_iter++) = &cluster[i]; + } + + if(auto *oldvoices = mVoices.exchange(newarray.release(), std::memory_order_acq_rel)) + { + mDevice->waitForMix(); + delete oldvoices; + } +} diff --git a/Engine/lib/openal-soft/core/context.h b/Engine/lib/openal-soft/core/context.h new file mode 100644 index 000000000..f5768629c --- /dev/null +++ b/Engine/lib/openal-soft/core/context.h @@ -0,0 +1,172 @@ +#ifndef CORE_CONTEXT_H +#define CORE_CONTEXT_H + +#include +#include +#include +#include +#include + +#include "almalloc.h" +#include "alspan.h" +#include "atomic.h" +#include "bufferline.h" +#include "threads.h" +#include "vecmat.h" +#include "vector.h" + +struct DeviceBase; +struct EffectSlot; +struct EffectSlotProps; +struct RingBuffer; +struct Voice; +struct VoiceChange; +struct VoicePropsItem; + +using uint = unsigned int; + + +constexpr float SpeedOfSoundMetersPerSec{343.3f}; + +constexpr float AirAbsorbGainHF{0.99426f}; /* -0.05dB */ + +enum class DistanceModel : unsigned char { + Disable, + Inverse, InverseClamped, + Linear, LinearClamped, + Exponent, ExponentClamped, + + Default = InverseClamped +}; + + +struct WetBuffer { + bool mInUse; + al::FlexArray mBuffer; + + WetBuffer(size_t count) : mBuffer{count} { } + + DEF_FAM_NEWDEL(WetBuffer, mBuffer) +}; +using WetBufferPtr = std::unique_ptr; + + +struct ContextProps { + std::array Position; + std::array Velocity; + std::array OrientAt; + std::array OrientUp; + float Gain; + float MetersPerUnit; + float AirAbsorptionGainHF; + + float DopplerFactor; + float DopplerVelocity; + float SpeedOfSound; + bool SourceDistanceModel; + DistanceModel mDistanceModel; + + std::atomic next; + + DEF_NEWDEL(ContextProps) +}; + +struct ContextParams { + /* Pointer to the most recent property values that are awaiting an update. */ + std::atomic ContextUpdate{nullptr}; + + alu::Vector Position{}; + alu::Matrix Matrix{alu::Matrix::Identity()}; + alu::Vector Velocity{}; + + float Gain{1.0f}; + float MetersPerUnit{1.0f}; + float AirAbsorptionGainHF{AirAbsorbGainHF}; + + float DopplerFactor{1.0f}; + float SpeedOfSound{SpeedOfSoundMetersPerSec}; /* in units per sec! */ + + bool SourceDistanceModel{false}; + DistanceModel mDistanceModel{}; +}; + +struct ContextBase { + DeviceBase *const mDevice; + + /* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit + * indicates if updates are currently happening). + */ + RefCount mUpdateCount{0u}; + std::atomic mHoldUpdates{false}; + std::atomic mStopVoicesOnDisconnect{true}; + + float mGainBoost{1.0f}; + + /* Linked lists of unused property containers, free to use for future + * updates. + */ + std::atomic mFreeContextProps{nullptr}; + std::atomic mFreeVoiceProps{nullptr}; + std::atomic mFreeEffectslotProps{nullptr}; + + /* The voice change tail is the beginning of the "free" elements, up to and + * *excluding* the current. If tail==current, there's no free elements and + * new ones need to be allocated. The current voice change is the element + * last processed, and any after are pending. + */ + VoiceChange *mVoiceChangeTail{}; + std::atomic mCurrentVoiceChange{}; + + void allocVoiceChanges(); + void allocVoiceProps(); + + + ContextParams mParams; + + using VoiceArray = al::FlexArray; + std::atomic mVoices{}; + std::atomic mActiveVoiceCount{}; + + void allocVoices(size_t addcount); + al::span getVoicesSpan() const noexcept + { + return {mVoices.load(std::memory_order_relaxed)->data(), + mActiveVoiceCount.load(std::memory_order_relaxed)}; + } + al::span getVoicesSpanAcquired() const noexcept + { + return {mVoices.load(std::memory_order_acquire)->data(), + mActiveVoiceCount.load(std::memory_order_acquire)}; + } + + + using EffectSlotArray = al::FlexArray; + std::atomic mActiveAuxSlots{nullptr}; + + std::thread mEventThread; + al::semaphore mEventSem; + std::unique_ptr mAsyncEvents; + std::atomic mEnabledEvts{0u}; + + /* Asynchronous voice change actions are processed as a linked list of + * VoiceChange objects by the mixer, which is atomically appended to. + * However, to avoid allocating each object individually, they're allocated + * in clusters that are stored in a vector for easy automatic cleanup. + */ + using VoiceChangeCluster = std::unique_ptr; + al::vector mVoiceChangeClusters; + + using VoiceCluster = std::unique_ptr; + al::vector mVoiceClusters; + + using VoicePropsCluster = std::unique_ptr; + al::vector mVoicePropClusters; + + + ContextBase(DeviceBase *device); + ContextBase(const ContextBase&) = delete; + ContextBase& operator=(const ContextBase&) = delete; + ~ContextBase(); +}; + +#endif /* CORE_CONTEXT_H */ diff --git a/Engine/lib/openal-soft/Alc/converter.cpp b/Engine/lib/openal-soft/core/converter.cpp similarity index 99% rename from Engine/lib/openal-soft/Alc/converter.cpp rename to Engine/lib/openal-soft/core/converter.cpp index f7d4fc46b..6a06b464f 100644 --- a/Engine/lib/openal-soft/Alc/converter.cpp +++ b/Engine/lib/openal-soft/core/converter.cpp @@ -12,7 +12,7 @@ #include "albit.h" #include "albyte.h" #include "alnumeric.h" -#include "core/fpu_ctrl.h" +#include "fpu_ctrl.h" struct CTag; struct CopyTag; diff --git a/Engine/lib/openal-soft/Alc/converter.h b/Engine/lib/openal-soft/core/converter.h similarity index 91% rename from Engine/lib/openal-soft/Alc/converter.h rename to Engine/lib/openal-soft/core/converter.h index ffcfbc18e..2d22ae387 100644 --- a/Engine/lib/openal-soft/Alc/converter.h +++ b/Engine/lib/openal-soft/core/converter.h @@ -1,12 +1,12 @@ -#ifndef CONVERTER_H -#define CONVERTER_H +#ifndef CORE_CONVERTER_H +#define CORE_CONVERTER_H #include #include #include "almalloc.h" -#include "core/devformat.h" -#include "core/mixer/defs.h" +#include "devformat.h" +#include "mixer/defs.h" using uint = unsigned int; @@ -56,4 +56,4 @@ struct ChannelConverter { void convert(const void *src, float *dst, uint frames) const; }; -#endif /* CONVERTER_H */ +#endif /* CORE_CONVERTER_H */ diff --git a/Engine/lib/openal-soft/core/dbus_wrap.cpp b/Engine/lib/openal-soft/core/dbus_wrap.cpp new file mode 100644 index 000000000..7f2217066 --- /dev/null +++ b/Engine/lib/openal-soft/core/dbus_wrap.cpp @@ -0,0 +1,46 @@ + +#include "config.h" + +#include "dbus_wrap.h" + +#ifdef HAVE_DYNLOAD + +#include +#include + +#include "logging.h" + + +void *dbus_handle{nullptr}; +#define DECL_FUNC(x) decltype(p##x) p##x{}; +DBUS_FUNCTIONS(DECL_FUNC) +#undef DECL_FUNC + +void PrepareDBus() +{ + static constexpr char libname[] = "libdbus-1.so.3"; + + auto load_func = [](auto &f, const char *name) -> void + { f = reinterpret_cast>(GetSymbol(dbus_handle, name)); }; +#define LOAD_FUNC(x) do { \ + load_func(p##x, #x); \ + if(!p##x) \ + { \ + WARN("Failed to load function %s\n", #x); \ + CloseLib(dbus_handle); \ + dbus_handle = nullptr; \ + return; \ + } \ +} while(0); + + dbus_handle = LoadLib(libname); + if(!dbus_handle) + { + WARN("Failed to load %s\n", libname); + return; + } + +DBUS_FUNCTIONS(LOAD_FUNC) +#undef LOAD_FUNC +} +#endif diff --git a/Engine/lib/openal-soft/core/dbus_wrap.h b/Engine/lib/openal-soft/core/dbus_wrap.h new file mode 100644 index 000000000..09eaacf93 --- /dev/null +++ b/Engine/lib/openal-soft/core/dbus_wrap.h @@ -0,0 +1,87 @@ +#ifndef CORE_DBUS_WRAP_H +#define CORE_DBUS_WRAP_H + +#include + +#include + +#include "dynload.h" + +#ifdef HAVE_DYNLOAD + +#include + +#define DBUS_FUNCTIONS(MAGIC) \ +MAGIC(dbus_error_init) \ +MAGIC(dbus_error_free) \ +MAGIC(dbus_bus_get) \ +MAGIC(dbus_connection_set_exit_on_disconnect) \ +MAGIC(dbus_connection_unref) \ +MAGIC(dbus_connection_send_with_reply_and_block) \ +MAGIC(dbus_message_unref) \ +MAGIC(dbus_message_new_method_call) \ +MAGIC(dbus_message_append_args) \ +MAGIC(dbus_message_iter_init) \ +MAGIC(dbus_message_iter_next) \ +MAGIC(dbus_message_iter_recurse) \ +MAGIC(dbus_message_iter_get_arg_type) \ +MAGIC(dbus_message_iter_get_basic) \ +MAGIC(dbus_set_error_from_message) + +extern void *dbus_handle; +#define DECL_FUNC(x) extern decltype(x) *p##x; +DBUS_FUNCTIONS(DECL_FUNC) +#undef DECL_FUNC + +#ifndef IN_IDE_PARSER +#define dbus_error_init (*pdbus_error_init) +#define dbus_error_free (*pdbus_error_free) +#define dbus_bus_get (*pdbus_bus_get) +#define dbus_connection_set_exit_on_disconnect (*pdbus_connection_set_exit_on_disconnect) +#define dbus_connection_unref (*pdbus_connection_unref) +#define dbus_connection_send_with_reply_and_block (*pdbus_connection_send_with_reply_and_block) +#define dbus_message_unref (*pdbus_message_unref) +#define dbus_message_new_method_call (*pdbus_message_new_method_call) +#define dbus_message_append_args (*pdbus_message_append_args) +#define dbus_message_iter_init (*pdbus_message_iter_init) +#define dbus_message_iter_next (*pdbus_message_iter_next) +#define dbus_message_iter_recurse (*pdbus_message_iter_recurse) +#define dbus_message_iter_get_arg_type (*pdbus_message_iter_get_arg_type) +#define dbus_message_iter_get_basic (*pdbus_message_iter_get_basic) +#define dbus_set_error_from_message (*pdbus_set_error_from_message) +#endif + +void PrepareDBus(); + +inline auto HasDBus() +{ + static std::once_flag init_dbus{}; + std::call_once(init_dbus, []{ PrepareDBus(); }); + return dbus_handle; +} + +#else + +constexpr bool HasDBus() noexcept { return true; } +#endif /* HAVE_DYNLOAD */ + + +namespace dbus { + +struct Error { + Error() { dbus_error_init(&mError); } + ~Error() { dbus_error_free(&mError); } + DBusError* operator->() { return &mError; } + DBusError &get() { return mError; } +private: + DBusError mError{}; +}; + +struct ConnectionDeleter { + void operator()(DBusConnection *c) { dbus_connection_unref(c); } +}; +using ConnectionPtr = std::unique_ptr; + +} // namespace dbus + +#endif /* CORE_DBUS_WRAP_H */ diff --git a/Engine/lib/openal-soft/core/devformat.cpp b/Engine/lib/openal-soft/core/devformat.cpp index d13ef3c61..c841b634f 100644 --- a/Engine/lib/openal-soft/core/devformat.cpp +++ b/Engine/lib/openal-soft/core/devformat.cpp @@ -26,7 +26,6 @@ uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept case DevFmtStereo: return 2; case DevFmtQuad: return 4; case DevFmtX51: return 6; - case DevFmtX51Rear: return 6; case DevFmtX61: return 7; case DevFmtX71: return 8; case DevFmtAmbi3D: return (ambiorder+1) * (ambiorder+1); @@ -56,7 +55,6 @@ const char *DevFmtChannelsString(DevFmtChannels chans) noexcept case DevFmtStereo: return "Stereo"; case DevFmtQuad: return "Quadraphonic"; case DevFmtX51: return "5.1 Surround"; - case DevFmtX51Rear: return "5.1 Surround (Rear)"; case DevFmtX61: return "6.1 Surround"; case DevFmtX71: return "7.1 Surround"; case DevFmtAmbi3D: return "Ambisonic 3D"; diff --git a/Engine/lib/openal-soft/core/devformat.h b/Engine/lib/openal-soft/core/devformat.h index 6b6fee77f..e6d30924d 100644 --- a/Engine/lib/openal-soft/core/devformat.h +++ b/Engine/lib/openal-soft/core/devformat.h @@ -17,10 +17,10 @@ enum Channel : unsigned char { SideLeft, SideRight, + TopCenter, TopFrontLeft, TopFrontCenter, TopFrontRight, - TopCenter, TopBackLeft, TopBackCenter, TopBackRight, @@ -50,9 +50,6 @@ enum DevFmtChannels : unsigned char { DevFmtX71, DevFmtAmbi3D, - /* Similar to 5.1, except using rear channels instead of sides */ - DevFmtX51Rear, - DevFmtChannelsDefault = DevFmtStereo }; #define MAX_OUTPUT_CHANNELS 16 diff --git a/Engine/lib/openal-soft/core/device.cpp b/Engine/lib/openal-soft/core/device.cpp new file mode 100644 index 000000000..2766c5e4e --- /dev/null +++ b/Engine/lib/openal-soft/core/device.cpp @@ -0,0 +1,23 @@ + +#include "config.h" + +#include "bformatdec.h" +#include "bs2b.h" +#include "device.h" +#include "front_stablizer.h" +#include "hrtf.h" +#include "mastering.h" + + +al::FlexArray DeviceBase::sEmptyContextArray{0u}; + + +DeviceBase::DeviceBase(DeviceType type) : Type{type}, mContexts{&sEmptyContextArray} +{ +} + +DeviceBase::~DeviceBase() +{ + auto *oldarray = mContexts.exchange(nullptr, std::memory_order_relaxed); + if(oldarray != &sEmptyContextArray) delete oldarray; +} diff --git a/Engine/lib/openal-soft/Alc/alcmain.h b/Engine/lib/openal-soft/core/device.h similarity index 61% rename from Engine/lib/openal-soft/Alc/alcmain.h rename to Engine/lib/openal-soft/core/device.h index e9309c40e..58a30f1b5 100644 --- a/Engine/lib/openal-soft/Alc/alcmain.h +++ b/Engine/lib/openal-soft/core/device.h @@ -1,46 +1,36 @@ -#ifndef ALC_MAIN_H -#define ALC_MAIN_H +#ifndef CORE_DEVICE_H +#define CORE_DEVICE_H + +#include -#include #include #include #include #include -#include -#include #include #include #include -#include -#include - -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/alext.h" #include "almalloc.h" -#include "alnumeric.h" #include "alspan.h" +#include "ambidefs.h" #include "atomic.h" -#include "core/ambidefs.h" -#include "core/bufferline.h" -#include "core/devformat.h" -#include "core/filters/splitter.h" -#include "core/mixer/defs.h" -#include "hrtf.h" -#include "inprogext.h" +#include "bufferline.h" +#include "devformat.h" +#include "filters/nfc.h" #include "intrusive_ptr.h" +#include "mixer/hrtfdefs.h" +#include "opthelpers.h" +#include "resampler_limits.h" +#include "uhjfilter.h" #include "vector.h" class BFormatDec; -struct ALbuffer; -struct ALeffect; -struct ALfilter; -struct BackendBase; -struct Compressor; -struct EffectState; -struct Uhj2Encoder; struct bs2b; +struct Compressor; +struct ContextBase; +struct DirectHrtfState; +struct HrtfStore; using uint = unsigned int; @@ -66,6 +56,14 @@ enum class RenderMode : unsigned char { Hrtf }; +enum class StereoEncoding : unsigned char { + Basic, + Uhj, + Hrtf, + + Default = Basic +}; + struct InputRemixMap { struct TargetMix { Channel channel; float mix; }; @@ -75,52 +73,6 @@ struct InputRemixMap { }; -struct BufferSubList { - uint64_t FreeMask{~0_u64}; - ALbuffer *Buffers{nullptr}; /* 64 */ - - BufferSubList() noexcept = default; - BufferSubList(const BufferSubList&) = delete; - BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers} - { rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; } - ~BufferSubList(); - - BufferSubList& operator=(const BufferSubList&) = delete; - BufferSubList& operator=(BufferSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; } -}; - -struct EffectSubList { - uint64_t FreeMask{~0_u64}; - ALeffect *Effects{nullptr}; /* 64 */ - - EffectSubList() noexcept = default; - EffectSubList(const EffectSubList&) = delete; - EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects} - { rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; } - ~EffectSubList(); - - EffectSubList& operator=(const EffectSubList&) = delete; - EffectSubList& operator=(EffectSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; } -}; - -struct FilterSubList { - uint64_t FreeMask{~0_u64}; - ALfilter *Filters{nullptr}; /* 64 */ - - FilterSubList() noexcept = default; - FilterSubList(const FilterSubList&) = delete; - FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters} - { rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; } - ~FilterSubList(); - - FilterSubList& operator=(const FilterSubList&) = delete; - FilterSubList& operator=(FilterSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; } -}; - - /* Maximum delay in samples for speaker distance compensation. */ #define MAX_DELAY_LENGTH 1024 @@ -176,10 +128,19 @@ enum { // Specifies if the device is currently running DeviceRunning, + // Specifies if the output plays directly on/in ears (headphones, headset, + // ear buds, etc). + DirectEar, + DeviceFlagsCount }; -struct ALCdevice : public al::intrusive_ref { +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{}; @@ -189,7 +150,6 @@ struct ALCdevice : public al::intrusive_ref { DevFmtChannels FmtChans{}; DevFmtType FmtType{}; - bool IsHeadphones{false}; uint mAmbiOrder{0}; float mXOverFreq{400.0f}; /* For DevFmtAmbi* output only, specifies the channel order and @@ -203,10 +163,7 @@ struct ALCdevice : public al::intrusive_ref { // Device flags std::bitset Flags{}; - // Maximum number of sources that can be created - uint SourcesMax{}; - // Maximum number of slots that can be created - uint AuxiliaryEffectSlotMax{}; + uint NumAuxSends{}; /* Rendering mode. */ RenderMode mRenderMode{RenderMode::Normal}; @@ -216,12 +173,22 @@ struct ALCdevice : public al::intrusive_ref { */ float AvgSpeakerDist{0.0f}; + /* The default NFC filter. Not used directly, but is pre-initialized with + * the control distance from AvgSpeakerDist. + */ + NfcFilter mNFCtrlFilter{}; + uint SamplesDone{0u}; std::chrono::nanoseconds ClockBase{0}; std::chrono::nanoseconds FixedLatency{0}; /* Temp storage used for mixer processing. */ - alignas(16) float SourceData[BufferLineSize + MaxResamplerPadding]; + static constexpr size_t MixerLineSize{BufferLineSize + MaxResamplerPadding + + UhjDecoder::sFilterDelay}; + static constexpr size_t MixerChannelsMax{16}; + using MixerBufferLine = std::array; + alignas(16) std::array mSampleData; + alignas(16) float ResampledData[BufferLineSize]; alignas(16) float FilteredData[BufferLineSize]; union { @@ -230,7 +197,7 @@ struct ALCdevice : public al::intrusive_ref { }; /* Persistent storage for HRTF mixing. */ - alignas(16) float2 HrtfAccumData[BufferLineSize + HrirLength + HrtfDirectDelay]; + alignas(16) float2 HrtfAccumData[BufferLineSize + HrirLength]; /* Mixing buffer used by the Dry mix and Real output. */ al::vector MixBuffer; @@ -250,7 +217,7 @@ struct ALCdevice : public al::intrusive_ref { uint mIrSize{0}; /* Ambisonic-to-UHJ encoder */ - std::unique_ptr Uhj_Encoder; + std::unique_ptr mUhjEncoder; /* Ambisonic decoder for speakers */ std::unique_ptr AmbiDecoder; @@ -258,7 +225,7 @@ struct ALCdevice : public al::intrusive_ref { /* Stereo-to-binaural filter */ std::unique_ptr Bs2b; - using PostProc = void(ALCdevice::*)(const size_t SamplesToDo); + using PostProc = void(DeviceBase::*)(const size_t SamplesToDo); PostProc PostProcess{nullptr}; std::unique_ptr Limiter; @@ -278,45 +245,13 @@ struct ALCdevice : public al::intrusive_ref { 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; + std::atomic*> mContexts{nullptr}; - ALCuint NumMonoSources{}; - ALCuint NumStereoSources{}; - ALCuint NumAuxSends{}; - - std::string HrtfName; - al::vector HrtfList; - ALCenum HrtfStatus{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); - ALCdevice(const ALCdevice&) = delete; - ALCdevice& operator=(const ALCdevice&) = delete; - ~ALCdevice(); + 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); } @@ -339,14 +274,24 @@ struct ALCdevice : public al::intrusive_ref { inline void postProcess(const size_t SamplesToDo) { if LIKELY(PostProcess) (this->*PostProcess)(SamplesToDo); } + void renderSamples(const al::span outBuffers, const uint numSamples); void renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep); /* Caller must lock the device state, and the mixer must not be running. */ - [[gnu::format(printf,2,3)]] void handleDisconnect(const char *msg, ...); +#ifdef __USE_MINGW_ANSI_STDIO + [[gnu::format(gnu_printf,2,3)]] +#else + [[gnu::format(printf,2,3)]] +#endif + void handleDisconnect(const char *msg, ...); - DEF_NEWDEL(ALCdevice) + DISABLE_ALLOC() + +private: + uint renderSamples(const uint numSamples); }; + /* Must be less than 15 characters (16 including terminating null) for * compatibility with pthread_setname_np limitations. */ #define MIXER_THREAD_NAME "alsoft-mixer" @@ -354,9 +299,6 @@ struct ALCdevice : public al::intrusive_ref { #define RECORD_THREAD_NAME "alsoft-record" -extern int RTPrioLevel; -void SetRTPriority(void); - /** * Returns the index for the given channel name (e.g. FrontCenter), or * INVALID_CHANNEL_INDEX if it doesn't exist. @@ -365,7 +307,4 @@ inline uint GetChannelIdxByName(const RealMixParams &real, Channel chan) noexcep { return real.ChannelIndex[chan]; } #define INVALID_CHANNEL_INDEX ~0u - -al::vector SearchDataFiles(const char *match, const char *subdir); - -#endif +#endif /* CORE_DEVICE_H */ diff --git a/Engine/lib/openal-soft/Alc/effects/base.h b/Engine/lib/openal-soft/core/effects/base.h similarity index 75% rename from Engine/lib/openal-soft/Alc/effects/base.h rename to Engine/lib/openal-soft/core/effects/base.h index b482bae22..3094f627b 100644 --- a/Engine/lib/openal-soft/Alc/effects/base.h +++ b/Engine/lib/openal-soft/core/effects/base.h @@ -1,24 +1,37 @@ -#ifndef EFFECTS_BASE_H -#define EFFECTS_BASE_H +#ifndef CORE_EFFECTS_BASE_H +#define CORE_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; @@ -189,24 +202,4 @@ struct EffectStateFactory { virtual al::intrusive_ptr create() = 0; }; - -EffectStateFactory *NullStateFactory_getFactory(void); -EffectStateFactory *ReverbStateFactory_getFactory(void); -EffectStateFactory *StdReverbStateFactory_getFactory(void); -EffectStateFactory *AutowahStateFactory_getFactory(void); -EffectStateFactory *ChorusStateFactory_getFactory(void); -EffectStateFactory *CompressorStateFactory_getFactory(void); -EffectStateFactory *DistortionStateFactory_getFactory(void); -EffectStateFactory *EchoStateFactory_getFactory(void); -EffectStateFactory *EqualizerStateFactory_getFactory(void); -EffectStateFactory *FlangerStateFactory_getFactory(void); -EffectStateFactory *FshifterStateFactory_getFactory(void); -EffectStateFactory *ModulatorStateFactory_getFactory(void); -EffectStateFactory *PshifterStateFactory_getFactory(void); -EffectStateFactory* VmorpherStateFactory_getFactory(void); - -EffectStateFactory *DedicatedStateFactory_getFactory(void); - -EffectStateFactory *ConvolutionStateFactory_getFactory(void); - -#endif /* EFFECTS_BASE_H */ +#endif /* CORE_EFFECTS_BASE_H */ diff --git a/Engine/lib/openal-soft/Alc/effectslot.cpp b/Engine/lib/openal-soft/core/effectslot.cpp similarity index 83% rename from Engine/lib/openal-soft/Alc/effectslot.cpp rename to Engine/lib/openal-soft/core/effectslot.cpp index 8abac2480..51fb8d468 100644 --- a/Engine/lib/openal-soft/Alc/effectslot.cpp +++ b/Engine/lib/openal-soft/core/effectslot.cpp @@ -5,8 +5,8 @@ #include -#include "alcontext.h" #include "almalloc.h" +#include "context.h" EffectSlotArray *EffectSlot::CreatePtrArray(size_t count) noexcept @@ -15,7 +15,7 @@ EffectSlotArray *EffectSlot::CreatePtrArray(size_t count) noexcept * space to store a sorted list during mixing. */ void *ptr{al_calloc(alignof(EffectSlotArray), EffectSlotArray::Sizeof(count*2))}; - return new(ptr) EffectSlotArray{count}; + return al::construct_at(static_cast(ptr), count); } EffectSlot::~EffectSlot() diff --git a/Engine/lib/openal-soft/Alc/effectslot.h b/Engine/lib/openal-soft/core/effectslot.h similarity index 93% rename from Engine/lib/openal-soft/Alc/effectslot.h rename to Engine/lib/openal-soft/core/effectslot.h index c1eb1cc3d..8b7b977ca 100644 --- a/Engine/lib/openal-soft/Alc/effectslot.h +++ b/Engine/lib/openal-soft/core/effectslot.h @@ -1,14 +1,13 @@ -#ifndef EFFECTSLOT_H -#define EFFECTSLOT_H +#ifndef CORE_EFFECTSLOT_H +#define CORE_EFFECTSLOT_H -#include +#include #include "almalloc.h" -#include "alcmain.h" +#include "device.h" #include "effects/base.h" #include "intrusive_ptr.h" - struct EffectSlot; struct WetBuffer; @@ -86,4 +85,4 @@ struct EffectSlot { DISABLE_ALLOC() }; -#endif /* EFFECTSLOT_H */ +#endif /* CORE_EFFECTSLOT_H */ diff --git a/Engine/lib/openal-soft/core/filters/biquad.cpp b/Engine/lib/openal-soft/core/filters/biquad.cpp index fefdc8e10..470b1cd3c 100644 --- a/Engine/lib/openal-soft/core/filters/biquad.cpp +++ b/Engine/lib/openal-soft/core/filters/biquad.cpp @@ -7,6 +7,7 @@ #include #include +#include "alnumbers.h" #include "opthelpers.h" @@ -16,7 +17,7 @@ void BiquadFilterR::setParams(BiquadType type, Real f0norm, Real gain, Rea // Limit gain to -100dB assert(gain > 0.00001f); - const Real w0{al::MathDefs::Tau() * f0norm}; + const Real w0{al::numbers::pi_v*2.0f * f0norm}; const Real sin_w0{std::sin(w0)}; const Real cos_w0{std::cos(w0)}; const Real alpha{sin_w0/2.0f * rcpQ}; diff --git a/Engine/lib/openal-soft/core/filters/biquad.h b/Engine/lib/openal-soft/core/filters/biquad.h index b2e2cfdb2..75a4009b6 100644 --- a/Engine/lib/openal-soft/core/filters/biquad.h +++ b/Engine/lib/openal-soft/core/filters/biquad.h @@ -6,8 +6,8 @@ #include #include +#include "alnumbers.h" #include "alspan.h" -#include "math_defs.h" /* Filters implementation is based on the "Cookbook formulae for audio @@ -40,11 +40,11 @@ enum class BiquadType { template class BiquadFilterR { /* Last two delayed components for direct form II. */ - Real mZ1{0.0f}, mZ2{0.0f}; + Real mZ1{0}, mZ2{0}; /* Transfer function coefficients "b" (numerator) */ - Real mB0{1.0f}, mB1{0.0f}, mB2{0.0f}; + Real mB0{1}, mB1{0}, mB2{0}; /* Transfer function coefficients "a" (denominator; a0 is pre-applied). */ - Real mA1{0.0f}, mA2{0.0f}; + Real mA1{0}, mA2{0}; void setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ); @@ -55,7 +55,7 @@ class BiquadFilterR { * \param slope 0 < slope <= 1 */ static Real rcpQFromSlope(Real gain, Real slope) - { return std::sqrt((gain + 1.0f/gain)*(1.0f/slope - 1.0f) + 2.0f); } + { return std::sqrt((gain + Real{1}/gain)*(Real{1}/slope - Real{1}) + Real{2}); } /** * Calculates the rcpQ (i.e. 1/Q) coefficient for filters, using the @@ -65,12 +65,12 @@ class BiquadFilterR { */ static Real rcpQFromBandwidth(Real f0norm, Real bandwidth) { - const Real w0{al::MathDefs::Tau() * f0norm}; - return 2.0f*std::sinh(std::log(Real{2.0f})/2.0f*bandwidth*w0/std::sin(w0)); + const Real w0{al::numbers::pi_v*Real{2} * f0norm}; + return 2.0f*std::sinh(std::log(Real{2})/Real{2}*bandwidth*w0/std::sin(w0)); } public: - void clear() noexcept { mZ1 = mZ2 = 0.0f; } + void clear() noexcept { mZ1 = mZ2 = Real{0}; } /** * Sets the filter state for the specified filter type and its parameters. diff --git a/Engine/lib/openal-soft/core/filters/nfc.cpp b/Engine/lib/openal-soft/core/filters/nfc.cpp index 9a28517c7..aa64c6130 100644 --- a/Engine/lib/openal-soft/core/filters/nfc.cpp +++ b/Engine/lib/openal-soft/core/filters/nfc.cpp @@ -62,26 +62,22 @@ NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept float b_00, g_0; float r; - nfc.base_gain = 1.0f; - nfc.gain = 1.0f; + /* Calculate bass-cut coefficients. */ + r = 0.5f * w1; + b_00 = B[1][0] * r; + g_0 = 1.0f + b_00; + + nfc.base_gain = 1.0f / g_0; + nfc.a1 = 2.0f * b_00 / g_0; /* Calculate bass-boost coefficients. */ r = 0.5f * w0; b_00 = B[1][0] * r; g_0 = 1.0f + b_00; - nfc.gain *= g_0; + nfc.gain = nfc.base_gain * g_0; nfc.b1 = 2.0f * b_00 / g_0; - /* Calculate bass-cut coefficients. */ - r = 0.5f * w1; - b_00 = B[1][0] * r; - g_0 = 1.0f + b_00; - - nfc.base_gain /= g_0; - nfc.gain /= g_0; - nfc.a1 = 2.0f * b_00 / g_0; - return nfc; } @@ -102,8 +98,15 @@ NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept float b_10, b_11, g_1; float r; - nfc.base_gain = 1.0f; - nfc.gain = 1.0f; + /* Calculate bass-cut coefficients. */ + r = 0.5f * w1; + b_10 = B[2][0] * r; + b_11 = B[2][1] * r * r; + g_1 = 1.0f + b_10 + b_11; + + nfc.base_gain = 1.0f / g_1; + nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; + nfc.a2 = 4.0f * b_11 / g_1; /* Calculate bass-boost coefficients. */ r = 0.5f * w0; @@ -111,21 +114,10 @@ NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept b_11 = B[2][1] * r * r; g_1 = 1.0f + b_10 + b_11; - nfc.gain *= g_1; + nfc.gain = nfc.base_gain * g_1; nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc.b2 = 4.0f * b_11 / g_1; - /* Calculate bass-cut coefficients. */ - r = 0.5f * w1; - b_10 = B[2][0] * r; - b_11 = B[2][1] * r * r; - g_1 = 1.0f + b_10 + b_11; - - nfc.base_gain /= g_1; - nfc.gain /= g_1; - nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; - nfc.a2 = 4.0f * b_11 / g_1; - return nfc; } @@ -149,8 +141,18 @@ NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept float b_00, g_0; float r; - nfc.base_gain = 1.0f; - nfc.gain = 1.0f; + /* Calculate bass-cut coefficients. */ + r = 0.5f * w1; + b_10 = B[3][0] * r; + b_11 = B[3][1] * r * r; + b_00 = B[3][2] * r; + g_1 = 1.0f + b_10 + b_11; + g_0 = 1.0f + b_00; + + nfc.base_gain = 1.0f / (g_1 * g_0); + nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; + nfc.a2 = 4.0f * b_11 / g_1; + nfc.a3 = 2.0f * b_00 / g_0; /* Calculate bass-boost coefficients. */ r = 0.5f * w0; @@ -160,25 +162,11 @@ NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept g_1 = 1.0f + b_10 + b_11; g_0 = 1.0f + b_00; - nfc.gain *= g_1 * g_0; + nfc.gain = nfc.base_gain * (g_1 * g_0); nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc.b2 = 4.0f * b_11 / g_1; nfc.b3 = 2.0f * b_00 / g_0; - /* Calculate bass-cut coefficients. */ - r = 0.5f * w1; - b_10 = B[3][0] * r; - b_11 = B[3][1] * r * r; - b_00 = B[3][2] * r; - g_1 = 1.0f + b_10 + b_11; - g_0 = 1.0f + b_00; - - nfc.base_gain /= g_1 * g_0; - nfc.gain /= g_1 * g_0; - nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; - nfc.a2 = 4.0f * b_11 / g_1; - nfc.a3 = 2.0f * b_00 / g_0; - return nfc; } @@ -191,7 +179,7 @@ void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept const float g_1{1.0f + b_10 + b_11}; const float g_0{1.0f + b_00}; - nfc->gain = nfc->base_gain * g_1 * g_0; + nfc->gain = nfc->base_gain * (g_1 * g_0); nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc->b2 = 4.0f * b_11 / g_1; nfc->b3 = 2.0f * b_00 / g_0; @@ -205,8 +193,20 @@ NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept float b_00, b_01, g_0; float r; - nfc.base_gain = 1.0f; - nfc.gain = 1.0f; + /* Calculate bass-cut coefficients. */ + r = 0.5f * w1; + b_10 = B[4][0] * r; + b_11 = B[4][1] * r * r; + b_00 = B[4][2] * r; + b_01 = B[4][3] * r * r; + g_1 = 1.0f + b_10 + b_11; + g_0 = 1.0f + b_00 + b_01; + + nfc.base_gain = 1.0f / (g_1 * g_0); + nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; + nfc.a2 = 4.0f * b_11 / g_1; + nfc.a3 = (2.0f*b_00 + 4.0f*b_01) / g_0; + nfc.a4 = 4.0f * b_01 / g_0; /* Calculate bass-boost coefficients. */ r = 0.5f * w0; @@ -217,28 +217,12 @@ NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept g_1 = 1.0f + b_10 + b_11; g_0 = 1.0f + b_00 + b_01; - nfc.gain *= g_1 * g_0; + nfc.gain = nfc.base_gain * (g_1 * g_0); nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc.b2 = 4.0f * b_11 / g_1; nfc.b3 = (2.0f*b_00 + 4.0f*b_01) / g_0; nfc.b4 = 4.0f * b_01 / g_0; - /* Calculate bass-cut coefficients. */ - r = 0.5f * w1; - b_10 = B[4][0] * r; - b_11 = B[4][1] * r * r; - b_00 = B[4][2] * r; - b_01 = B[4][3] * r * r; - g_1 = 1.0f + b_10 + b_11; - g_0 = 1.0f + b_00 + b_01; - - nfc.base_gain /= g_1 * g_0; - nfc.gain /= g_1 * g_0; - nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; - nfc.a2 = 4.0f * b_11 / g_1; - nfc.a3 = (2.0f*b_00 + 4.0f*b_01) / g_0; - nfc.a4 = 4.0f * b_01 / g_0; - return nfc; } @@ -252,7 +236,7 @@ void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept const float g_1{1.0f + b_10 + b_11}; const float g_0{1.0f + b_00 + b_01}; - nfc->gain = nfc->base_gain * g_1 * g_0; + nfc->gain = nfc->base_gain * (g_1 * g_0); nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc->b2 = 4.0f * b_11 / g_1; nfc->b3 = (2.0f*b_00 + 4.0f*b_01) / g_0; diff --git a/Engine/lib/openal-soft/core/filters/splitter.cpp b/Engine/lib/openal-soft/core/filters/splitter.cpp index 5cc670b70..e7f03756f 100644 --- a/Engine/lib/openal-soft/core/filters/splitter.cpp +++ b/Engine/lib/openal-soft/core/filters/splitter.cpp @@ -7,14 +7,14 @@ #include #include -#include "math_defs.h" +#include "alnumbers.h" #include "opthelpers.h" template void BandSplitterR::init(Real f0norm) { - const Real w{f0norm * al::MathDefs::Tau()}; + const Real w{f0norm * (al::numbers::pi_v*2)}; const Real cw{std::cos(w)}; if(cw > std::numeric_limits::epsilon()) mCoeff = (std::sin(w) - 1.0f) / cw; @@ -60,6 +60,41 @@ void BandSplitterR::process(const al::span input, Real *hpout, mApZ1 = ap_z1; } +template +void BandSplitterR::processHfScale(const al::span input, Real *RESTRICT output, + const Real hfscale) +{ + const Real ap_coeff{mCoeff}; + const Real lp_coeff{mCoeff*0.5f + 0.5f}; + Real lp_z1{mLpZ1}; + Real lp_z2{mLpZ2}; + Real ap_z1{mApZ1}; + auto proc_sample = [hfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real + { + /* Low-pass sample processing. */ + Real d{(in - lp_z1) * lp_coeff}; + Real lp_y{lp_z1 + d}; + lp_z1 = lp_y + d; + + d = (lp_y - lp_z2) * lp_coeff; + lp_y = lp_z2 + d; + lp_z2 = lp_y + d; + + /* All-pass sample processing. */ + Real ap_y{in*ap_coeff + ap_z1}; + ap_z1 = in - ap_y*ap_coeff; + + /* High-pass generated by removing the low-passed signal, which is then + * scaled and added back to the low-passed signal. + */ + return (ap_y-lp_y)*hfscale + lp_y; + }; + std::transform(input.begin(), input.end(), output, proc_sample); + mLpZ1 = lp_z1; + mLpZ2 = lp_z2; + mApZ1 = ap_z1; +} + template void BandSplitterR::processHfScale(const al::span samples, const Real hfscale) { @@ -95,7 +130,37 @@ void BandSplitterR::processHfScale(const al::span samples, const Rea } template -void BandSplitterR::applyAllpass(const al::span samples) const +void BandSplitterR::processScale(const al::span samples, const Real hfscale, const Real lfscale) +{ + const Real ap_coeff{mCoeff}; + const Real lp_coeff{mCoeff*0.5f + 0.5f}; + Real lp_z1{mLpZ1}; + Real lp_z2{mLpZ2}; + Real ap_z1{mApZ1}; + auto proc_sample = [hfscale,lfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real + { + Real d{(in - lp_z1) * lp_coeff}; + Real lp_y{lp_z1 + d}; + lp_z1 = lp_y + d; + + d = (lp_y - lp_z2) * lp_coeff; + lp_y = lp_z2 + d; + lp_z2 = lp_y + d; + + Real ap_y{in*ap_coeff + ap_z1}; + ap_z1 = in - ap_y*ap_coeff; + + /* Apply separate factors to the high and low frequencies. */ + return (ap_y-lp_y)*hfscale + lp_y*lfscale; + }; + std::transform(samples.begin(), samples.end(), samples.begin(), proc_sample); + mLpZ1 = lp_z1; + mLpZ2 = lp_z2; + mApZ1 = ap_z1; +} + +template +void BandSplitterR::applyAllpassRev(const al::span samples) const { const Real coeff{mCoeff}; Real z1{0.0f}; @@ -105,7 +170,7 @@ void BandSplitterR::applyAllpass(const al::span samples) const z1 = in - out*coeff; return out; }; - std::transform(samples.begin(), samples.end(), samples.begin(), proc_sample); + std::transform(samples.rbegin(), samples.rend(), samples.rbegin(), proc_sample); } diff --git a/Engine/lib/openal-soft/core/filters/splitter.h b/Engine/lib/openal-soft/core/filters/splitter.h index ba548c103..fa15bd509 100644 --- a/Engine/lib/openal-soft/core/filters/splitter.h +++ b/Engine/lib/openal-soft/core/filters/splitter.h @@ -18,18 +18,25 @@ public: BandSplitterR() = default; BandSplitterR(const BandSplitterR&) = default; BandSplitterR(Real f0norm) { init(f0norm); } + BandSplitterR& operator=(const BandSplitterR&) = default; void init(Real f0norm); void clear() noexcept { mLpZ1 = mLpZ2 = mApZ1 = 0.0f; } void process(const al::span input, Real *hpout, Real *lpout); - void processHfScale(const al::span samples, const Real hfscale); + void processHfScale(const al::span input, Real *output, const Real hfscale); - /* The all-pass portion of the band splitter. Applies the same phase shift - * without splitting the signal. Note that each use of this method is - * indepedent, it does not track history between calls. + void processHfScale(const al::span samples, const Real hfscale); + void processScale(const al::span samples, const Real hfscale, const Real lfscale); + + /** + * The all-pass portion of the band splitter. Applies the same phase shift + * without splitting the signal, in reverse. It starts from the back of the + * span and works toward the front, creating a phase shift of -n degrees + * instead of +n. Note that each use of this method is indepedent, it does + * not track history between calls. */ - void applyAllpass(const al::span samples) const; + void applyAllpassRev(const al::span samples) const; }; using BandSplitter = BandSplitterR; diff --git a/Engine/lib/openal-soft/core/fpu_ctrl.cpp b/Engine/lib/openal-soft/core/fpu_ctrl.cpp index b12f2c967..0cf0d6e72 100644 --- a/Engine/lib/openal-soft/core/fpu_ctrl.cpp +++ b/Engine/lib/openal-soft/core/fpu_ctrl.cpp @@ -7,7 +7,12 @@ #include #endif #ifdef HAVE_SSE_INTRINSICS -#include +#include +#ifndef _MM_DENORMALS_ZERO_MASK +/* Some headers seem to be missing these? */ +#define _MM_DENORMALS_ZERO_MASK 0x0040u +#define _MM_DENORMALS_ZERO_ON 0x0040u +#endif #endif #include "cpu_caps.h" @@ -20,8 +25,8 @@ void FPUCtl::enter() noexcept #if defined(HAVE_SSE_INTRINSICS) this->sse_state = _mm_getcsr(); unsigned int sseState{this->sse_state}; - sseState |= 0x8000; /* set flush-to-zero */ - sseState |= 0x0040; /* set denormals-are-zero */ + sseState &= ~(_MM_FLUSH_ZERO_MASK | _MM_DENORMALS_ZERO_MASK); + sseState |= _MM_FLUSH_ZERO_ON | _MM_DENORMALS_ZERO_ON; _mm_setcsr(sseState); #elif defined(__GNUC__) && defined(HAVE_SSE) diff --git a/Engine/lib/openal-soft/core/fpu_ctrl.h b/Engine/lib/openal-soft/core/fpu_ctrl.h index e6dc1fb2b..9554313ae 100644 --- a/Engine/lib/openal-soft/core/fpu_ctrl.h +++ b/Engine/lib/openal-soft/core/fpu_ctrl.h @@ -8,7 +8,7 @@ class FPUCtl { bool in_mode{}; public: - FPUCtl() noexcept { enter(); in_mode = true; }; + FPUCtl() noexcept { enter(); in_mode = true; } ~FPUCtl() { if(in_mode) leave(); } FPUCtl(const FPUCtl&) = delete; diff --git a/Engine/lib/openal-soft/Alc/front_stablizer.h b/Engine/lib/openal-soft/core/front_stablizer.h similarity index 84% rename from Engine/lib/openal-soft/Alc/front_stablizer.h rename to Engine/lib/openal-soft/core/front_stablizer.h index 0fedeb50e..3d328a8d5 100644 --- a/Engine/lib/openal-soft/Alc/front_stablizer.h +++ b/Engine/lib/openal-soft/core/front_stablizer.h @@ -1,12 +1,12 @@ -#ifndef ALC_FRONT_STABLIZER_H -#define ALC_FRONT_STABLIZER_H +#ifndef CORE_FRONT_STABLIZER_H +#define CORE_FRONT_STABLIZER_H #include #include #include "almalloc.h" -#include "core/bufferline.h" -#include "core/filters/splitter.h" +#include "bufferline.h" +#include "filters/splitter.h" struct FrontStablizer { @@ -33,4 +33,4 @@ struct FrontStablizer { DEF_FAM_NEWDEL(FrontStablizer, DelayBuf) }; -#endif /* ALC_FRONT_STABLIZER_H */ +#endif /* CORE_FRONT_STABLIZER_H */ diff --git a/Engine/lib/openal-soft/Alc/helpers.cpp b/Engine/lib/openal-soft/core/helpers.cpp similarity index 62% rename from Engine/lib/openal-soft/Alc/helpers.cpp rename to Engine/lib/openal-soft/core/helpers.cpp index 8c1c8562c..e4a94fe5c 100644 --- a/Engine/lib/openal-soft/Alc/helpers.cpp +++ b/Engine/lib/openal-soft/core/helpers.cpp @@ -1,25 +1,8 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2011 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ #include "config.h" +#include "helpers.h" + #include #include #include @@ -27,37 +10,49 @@ #include #include #include +#include #include +#include -#include "alcmain.h" #include "almalloc.h" #include "alfstream.h" +#include "alnumeric.h" +#include "aloptional.h" #include "alspan.h" #include "alstring.h" -#include "compat.h" -#include "core/logging.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 PathNamePair ret; - if(!ret.fname.empty() || !ret.path.empty()) - return ret; + static al::optional procbin; + if(procbin) return *procbin; auto fullpath = al::vector(256); - DWORD len; - while((len=GetModuleFileNameW(nullptr, fullpath.data(), static_cast(fullpath.size()))) == fullpath.size()) + 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()); - return ret; + procbin = al::make_optional(); + return *procbin; } fullpath.resize(len); @@ -69,14 +64,14 @@ const PathNamePair &GetProcBinary() if(sep != fullpath.rend()) { *sep = 0; - ret.fname = wstr_to_utf8(&*sep + 1); - ret.path = wstr_to_utf8(fullpath.data()); + procbin = al::make_optional(wstr_to_utf8(fullpath.data()), + wstr_to_utf8(&*sep + 1)); } else - ret.fname = wstr_to_utf8(fullpath.data()); + procbin = al::make_optional(std::string{}, wstr_to_utf8(fullpath.data())); - TRACE("Got binary: %s, %s\n", ret.path.c_str(), ret.fname.c_str()); - return ret; + TRACE("Got binary: %s, %s\n", procbin->path.c_str(), procbin->fname.c_str()); + return *procbin; } namespace { @@ -202,12 +197,21 @@ void SetRTPriority(void) #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 PathNamePair ret; - if(!ret.fname.empty() || !ret.path.empty()) - return ret; + static al::optional procbin; + if(procbin) return *procbin; al::vector pathname; #ifdef __FreeBSD__ @@ -241,6 +245,7 @@ const PathNamePair &GetProcBinary() pathname.insert(pathname.end(), procpath, procpath+strlen(procpath)); } #endif +#ifndef __SWITCH__ if(pathname.empty()) { static const char SelfLinkNames[][32]{ @@ -269,25 +274,25 @@ const PathNamePair &GetProcBinary() if(len <= 0) { WARN("Failed to readlink %s: %s\n", selfname, strerror(errno)); - return ret; + 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()) - { - ret.path = std::string(pathname.cbegin(), sep.base()-1); - ret.fname = std::string(sep.base(), pathname.cend()); - } + procbin = al::make_optional(std::string(pathname.cbegin(), sep.base()-1), + std::string(sep.base(), pathname.cend())); else - ret.fname = std::string(pathname.cbegin(), pathname.cend()); + procbin = al::make_optional(std::string{}, + std::string(pathname.cbegin(), pathname.cend())); - TRACE("Got binary: %s, %s\n", ret.path.c_str(), ret.fname.c_str()); - return ret; + TRACE("Got binary: \"%s\", \"%s\"\n", procbin->path.c_str(), procbin->fname.c_str()); + return *procbin; } namespace { @@ -406,30 +411,147 @@ al::vector SearchDataFiles(const char *ext, const char *subdir) return results; } +namespace { + +bool SetRTPriorityPthread(int prio) +{ + int err{ENOTSUP}; +#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) + /* Get the min and max priority for SCHED_RR. Limit the max priority to + * half, for now, to ensure the thread can't take the highest priority and + * go rogue. + */ + int rtmin{sched_get_priority_min(SCHED_RR)}; + int rtmax{sched_get_priority_max(SCHED_RR)}; + rtmax = (rtmax-rtmin)/2 + rtmin; + + struct sched_param param{}; + param.sched_priority = clampi(prio, rtmin, rtmax); +#ifdef SCHED_RESET_ON_FORK + err = pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, ¶m); + if(err == EINVAL) +#endif + err = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); + if(err == 0) return true; + +#else + + std::ignore = prio; +#endif + WARN("pthread_setschedparam failed: %s (%d)\n", std::strerror(err), err); + return false; +} + +bool SetRTPriorityRTKit(int prio) +{ +#ifdef HAVE_RTKIT + if(!HasDBus()) + { + WARN("D-Bus not available\n"); + return false; + } + dbus::Error error; + dbus::ConnectionPtr conn{dbus_bus_get(DBUS_BUS_SYSTEM, &error.get())}; + if(!conn) + { + WARN("D-Bus connection failed with %s: %s\n", error->name, error->message); + return false; + } + + /* Don't stupidly exit if the connection dies while doing this. */ + dbus_connection_set_exit_on_disconnect(conn.get(), false); + + auto limit_rttime = [](DBusConnection *c) -> int + { + using ulonglong = unsigned long long; + long long maxrttime{rtkit_get_rttime_usec_max(c)}; + if(maxrttime <= 0) return static_cast(std::abs(maxrttime)); + const ulonglong umaxtime{static_cast(maxrttime)}; + + struct rlimit rlim{}; + if(getrlimit(RLIMIT_RTTIME, &rlim) != 0) + return errno; + + TRACE("RTTime max: %llu (hard: %llu, soft: %llu)\n", umaxtime, ulonglong{rlim.rlim_max}, + ulonglong{rlim.rlim_cur}); + if(rlim.rlim_max > umaxtime) + { + rlim.rlim_max = static_cast(umaxtime); + rlim.rlim_cur = std::min(rlim.rlim_cur, rlim.rlim_max); + if(setrlimit(RLIMIT_RTTIME, &rlim) != 0) + return errno; + } + return 0; + }; + + int nicemin{}; + int err{rtkit_get_min_nice_level(conn.get(), &nicemin)}; + if(err == -ENOENT) + { + err = std::abs(err); + ERR("Could not query RTKit: %s (%d)\n", std::strerror(err), err); + return false; + } + int rtmax{rtkit_get_max_realtime_priority(conn.get())}; + TRACE("Maximum real-time priority: %d, minimum niceness: %d\n", rtmax, nicemin); + + if(rtmax > 0) + { + if(AllowRTTimeLimit) + { + err = limit_rttime(conn.get()); + if(err != 0) + WARN("Failed to set RLIMIT_RTTIME for RTKit: %s (%d)\n", + std::strerror(err), err); + } + + /* Limit the maximum real-time priority to half. */ + rtmax = (rtmax+1)/2; + prio = clampi(prio, 1, rtmax); + + TRACE("Making real-time with priority %d (max: %d)\n", prio, rtmax); + err = rtkit_make_realtime(conn.get(), 0, prio); + if(err == 0) return true; + + err = std::abs(err); + WARN("Failed to set real-time priority: %s (%d)\n", std::strerror(err), err); + } + /* Don't try to set the niceness for non-Linux systems. Standard POSIX has + * niceness as a per-process attribute, while the intent here is for the + * audio processing thread only to get a priority boost. Currently only + * Linux is known to have per-thread niceness. + */ +#ifdef __linux__ + if(nicemin < 0) + { + TRACE("Making high priority with niceness %d\n", nicemin); + err = rtkit_make_high_priority(conn.get(), 0, nicemin); + if(err == 0) return true; + + err = std::abs(err); + WARN("Failed to set high priority: %s (%d)\n", std::strerror(err), err); + } +#endif /* __linux__ */ + +#else + + std::ignore = prio; + WARN("D-Bus not supported\n"); +#endif + return false; +} + +} // namespace + void SetRTPriority() { -#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) - if(RTPrioLevel > 0) - { - struct sched_param param{}; - /* Use the minimum real-time priority possible for now (on Linux this - * should be 1 for SCHED_RR). - */ - param.sched_priority = sched_get_priority_min(SCHED_RR); - int err; -#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) - ERR("Failed to set real-time priority for thread: %s (%d)\n", std::strerror(err), err); - } -#else - /* Real-time priority not available */ - if(RTPrioLevel > 0) - ERR("Cannot set priority level for thread\n"); -#endif + if(RTPrioLevel <= 0) + return; + + if(SetRTPriorityPthread(RTPrioLevel)) + return; + if(SetRTPriorityRTKit(RTPrioLevel)) + return; } #endif diff --git a/Engine/lib/openal-soft/core/helpers.h b/Engine/lib/openal-soft/core/helpers.h new file mode 100644 index 000000000..f0bfcf1b5 --- /dev/null +++ b/Engine/lib/openal-soft/core/helpers.h @@ -0,0 +1,18 @@ +#ifndef CORE_HELPERS_H +#define CORE_HELPERS_H + +#include + +#include "vector.h" + + +struct PathNamePair { std::string path, fname; }; +const PathNamePair &GetProcBinary(void); + +extern int RTPrioLevel; +extern bool AllowRTTimeLimit; +void SetRTPriority(void); + +al::vector SearchDataFiles(const char *match, const char *subdir); + +#endif /* CORE_HELPERS_H */ diff --git a/Engine/lib/openal-soft/Alc/hrtf.cpp b/Engine/lib/openal-soft/core/hrtf.cpp similarity index 93% rename from Engine/lib/openal-soft/Alc/hrtf.cpp rename to Engine/lib/openal-soft/core/hrtf.cpp index 60d0aead3..d4d698159 100644 --- a/Engine/lib/openal-soft/Alc/hrtf.cpp +++ b/Engine/lib/openal-soft/core/hrtf.cpp @@ -1,22 +1,3 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2011 by Chris Robinson - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ #include "config.h" @@ -30,36 +11,34 @@ #include #include #include -#include #include #include #include #include -#include #include #include #include #include "albit.h" #include "albyte.h" -#include "alcmain.h" -#include "alconfig.h" #include "alfstream.h" #include "almalloc.h" +#include "alnumbers.h" #include "alnumeric.h" #include "aloptional.h" #include "alspan.h" -#include "core/filters/splitter.h" -#include "core/logging.h" -#include "math_defs.h" +#include "ambidefs.h" +#include "filters/splitter.h" +#include "helpers.h" +#include "logging.h" +#include "mixer/hrtfdefs.h" #include "opthelpers.h" #include "polyphase_resampler.h" +#include "vector.h" namespace { -using namespace std::placeholders; - struct HrtfEntry { std::string mDispName; std::string mFilename; @@ -100,7 +79,7 @@ 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)*/}; +constexpr auto PassthruCoeff = static_cast(1.0/al::numbers::sqrt2); std::mutex LoadedHrtfLock; al::vector LoadedHrtfs; @@ -185,8 +164,8 @@ struct IdxBlend { uint idx; float blend; }; */ IdxBlend CalcEvIndex(uint evcount, float ev) { - ev = (al::MathDefs::Pi()*0.5f + ev) * static_cast(evcount-1) / - al::MathDefs::Pi(); + ev = (al::numbers::pi_v*0.5f + ev) * static_cast(evcount-1) / + al::numbers::pi_v; uint idx{float2uint(ev)}; return IdxBlend{minu(idx, evcount-1), ev-static_cast(idx)}; @@ -197,8 +176,8 @@ IdxBlend CalcEvIndex(uint evcount, float ev) */ IdxBlend CalcAzIndex(uint azcount, float az) { - az = (al::MathDefs::Tau()+az) * static_cast(azcount) / - al::MathDefs::Tau(); + az = (al::numbers::pi_v*2.0f + az) * static_cast(azcount) / + (al::numbers::pi_v*2.0f); uint idx{float2uint(az)}; return IdxBlend{idx%azcount, az-static_cast(idx)}; @@ -213,7 +192,7 @@ IdxBlend CalcAzIndex(uint azcount, float az) 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 float dirfact{1.0f - (al::numbers::inv_pi_v/2.0f * spread)}; const auto *field = Hrtf->field; const auto *field_end = field + Hrtf->fdCount-1; @@ -285,7 +264,7 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, { using double2 = std::array; struct ImpulseResponse { - const HrirArray &hrir; + const ConstHrirSpan hrir; uint ldelay, rdelay; }; @@ -338,12 +317,19 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, 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 HrirArray &hrir{impres[c].hrir}; - const uint ldelay{hrir_delay_round(impres[c].ldelay - min_delay)}; - const uint rdelay{hrir_delay_round(impres[c].rdelay - min_delay)}; + 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) { @@ -368,11 +354,8 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, } tmpres.clear(); - max_delay -= min_delay; const uint max_length{minu(hrir_delay_round(max_delay) + irSize, HrirLength)}; - - TRACE("Skipped delay: %.2f, new max delay: %.2f, FIR length: %u\n", - min_delay/double{HrirDelayFracOne}, max_delay/double{HrirDelayFracOne}, + TRACE("New max delay: %.2f, FIR length: %u\n", max_delay/double{HrirDelayFracOne}, max_length); mIrSize = max_length; } @@ -385,19 +368,18 @@ std::unique_ptr CreateHrtfStore(uint rate, ushort irSize, 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 += sizeof(std::declval().field[0])*fields.size(); total = RoundUp(total, alignof(HrtfStore::Elevation)); /* Align for elevation infos */ - total += sizeof(Hrtf->elev[0])*elevs.size(); + total += sizeof(std::declval().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; + total += sizeof(std::declval().coeffs[0])*irCount; + total += sizeof(std::declval().delays[0])*irCount; - Hrtf.reset(new (al_calloc(16, total)) HrtfStore{}); + void *ptr{al_calloc(16, total)}; + std::unique_ptr Hrtf{al::construct_at(static_cast(ptr))}; if(!Hrtf) ERR("Out of memory allocating storage for %s.\n", filename); else @@ -426,13 +408,14 @@ std::unique_ptr CreateHrtfStore(uint rate, ushort irSize, auto delays_ = reinterpret_cast(base + offset); offset += sizeof(delays_[0])*irCount; - assert(offset == total); + if(unlikely(offset != total)) + throw std::runtime_error{"HrtfStore allocation size mismatch"}; /* 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_); + std::uninitialized_copy(fields.cbegin(), fields.cend(), field_); + std::uninitialized_copy(elevs.cbegin(), elevs.cend(), elev_); + std::uninitialized_copy_n(coeffs, irCount, coeffs_); + std::uninitialized_copy_n(delays, irCount, delays_); /* Finally, assign the storage pointers. */ Hrtf->field = field_; @@ -465,32 +448,47 @@ void MirrorLeftHrirs(const al::span elevs, HrirArray } +template +constexpr std::enable_if_t::value && num_bits < sizeof(T)*8, +T> fixsign(T value) noexcept +{ + constexpr auto signbit = static_cast(1u << (num_bits-1)); + return static_cast((value^signbit) - signbit); +} + +template +constexpr std::enable_if_t::value || num_bits == sizeof(T)*8, +T> fixsign(T value) noexcept +{ return value; } + template -inline T readle(std::istream &data) +inline std::enable_if_t readle(std::istream &data) { static_assert((num_bits&7) == 0, "num_bits must be a multiple of 8"); static_assert(num_bits <= sizeof(T)*8, "num_bits is too large for the type"); T ret{}; - if_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(!data.read(reinterpret_cast(&ret), num_bits/8)) + return static_cast(EOF); - 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; + return fixsign(ret); +} + +template +inline std::enable_if_t readle(std::istream &data) +{ + static_assert((num_bits&7) == 0, "num_bits must be a multiple of 8"); + static_assert(num_bits <= sizeof(T)*8, "num_bits is too large for the type"); + + T ret{}; + al::byte b[sizeof(T)]{}; + if(!data.read(reinterpret_cast(b), num_bits/8)) + return static_cast(EOF); + std::reverse_copy(std::begin(b), std::end(b), reinterpret_cast(&ret)); + + return fixsign(ret); } template<> @@ -1212,13 +1210,13 @@ al::span GetResource(int name) } // namespace -al::vector EnumerateHrtf(const char *devname) +al::vector EnumerateHrtf(al::optional pathopt) { std::lock_guard _{EnumeratedHrtfLock}; EnumeratedHrtfs.clear(); bool usedefaults{true}; - if(auto pathopt = ConfigValueStr(devname, nullptr, "hrtf-paths")) + if(pathopt) { const char *pathlist{pathopt->c_str()}; while(pathlist && *pathlist) @@ -1266,15 +1264,6 @@ al::vector EnumerateHrtf(const char *devname) for(auto &entry : EnumeratedHrtfs) list.emplace_back(entry.mDispName); - if(auto defhrtfopt = ConfigValueStr(devname, nullptr, "default-hrtf")) - { - auto iter = std::find(list.begin(), list.end(), *defhrtfopt); - if(iter == list.end()) - WARN("Failed to find default HRTF \"%s\"\n", defhrtfopt->c_str()); - else if(iter != list.begin()) - std::rotate(list.begin(), iter, iter+1); - } - return list; } diff --git a/Engine/lib/openal-soft/Alc/hrtf.h b/Engine/lib/openal-soft/core/hrtf.h similarity index 86% rename from Engine/lib/openal-soft/Alc/hrtf.h rename to Engine/lib/openal-soft/core/hrtf.h index a46b64e2d..9cf11efb8 100644 --- a/Engine/lib/openal-soft/Alc/hrtf.h +++ b/Engine/lib/openal-soft/core/hrtf.h @@ -1,5 +1,5 @@ -#ifndef ALC_HRTF_H -#define ALC_HRTF_H +#ifndef CORE_HRTF_H +#define CORE_HRTF_H #include #include @@ -7,12 +7,12 @@ #include #include "almalloc.h" +#include "aloptional.h" #include "alspan.h" #include "atomic.h" -#include "core/ambidefs.h" -#include "core/bufferline.h" -#include "core/filters/splitter.h" -#include "core/mixer/hrtfdefs.h" +#include "ambidefs.h" +#include "bufferline.h" +#include "mixer/hrtfdefs.h" #include "intrusive_ptr.h" #include "vector.h" @@ -58,7 +58,7 @@ struct AngularPoint { struct DirectHrtfState { - std::array mTemp; + std::array mTemp; /* HRTF filter state for dry buffer content */ uint mIrSize{0}; @@ -81,10 +81,10 @@ struct DirectHrtfState { }; -al::vector EnumerateHrtf(const char *devname); +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 /* ALC_HRTF_H */ +#endif /* CORE_HRTF_H */ diff --git a/Engine/lib/openal-soft/core/logging.cpp b/Engine/lib/openal-soft/core/logging.cpp index dd7f53c79..7ee7ff23a 100644 --- a/Engine/lib/openal-soft/core/logging.cpp +++ b/Engine/lib/openal-soft/core/logging.cpp @@ -25,23 +25,29 @@ void al_print(LogLevel level, FILE *logfile, const char *fmt, ...) std::va_list args, args2; va_start(args, fmt); va_copy(args2, args); - int msglen{std::vsnprintf(str, sizeof(stcmsg), fmt, args)}; - if UNLIKELY(msglen >= 0 && static_cast(msglen) >= sizeof(stcmsg)) + const int msglen{std::vsnprintf(str, sizeof(stcmsg), fmt, args)}; + if(unlikely(msglen >= 0 && static_cast(msglen) >= sizeof(stcmsg))) { dynmsg.resize(static_cast(msglen) + 1u); str = dynmsg.data(); - msglen = std::vsnprintf(str, dynmsg.size(), fmt, args2); + std::vsnprintf(str, dynmsg.size(), fmt, args2); } va_end(args2); va_end(args); - std::wstring wstr{utf8_to_wstr(str)}; if(gLogLevel >= level) { - fputws(wstr.c_str(), logfile); + fputs(str, logfile); fflush(logfile); } + /* OutputDebugStringW has no 'level' property to distinguish between + * informational, warning, or error debug messages. So only print them for + * non-Release builds. + */ +#ifndef NDEBUG + std::wstring wstr{utf8_to_wstr(str)}; OutputDebugStringW(wstr.c_str()); +#endif } #else @@ -59,12 +65,12 @@ void al_print(LogLevel level, FILE *logfile, const char *fmt, ...) std::va_list args, args2; va_start(args, fmt); va_copy(args2, args); - int msglen{std::vsnprintf(str, sizeof(stcmsg), fmt, args)}; - if UNLIKELY(msglen >= 0 && static_cast(msglen) >= sizeof(stcmsg)) + const int msglen{std::vsnprintf(str, sizeof(stcmsg), fmt, args)}; + if(unlikely(msglen >= 0 && static_cast(msglen) >= sizeof(stcmsg))) { dynmsg.resize(static_cast(msglen) + 1u); str = dynmsg.data(); - msglen = std::vsnprintf(str, dynmsg.size(), fmt, args2); + std::vsnprintf(str, dynmsg.size(), fmt, args2); } va_end(args2); va_end(args); diff --git a/Engine/lib/openal-soft/core/logging.h b/Engine/lib/openal-soft/core/logging.h index b931c27e1..81465929c 100644 --- a/Engine/lib/openal-soft/core/logging.h +++ b/Engine/lib/openal-soft/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/Engine/lib/openal-soft/core/mastering.cpp b/Engine/lib/openal-soft/core/mastering.cpp index e0cb2ca78..998504772 100644 --- a/Engine/lib/openal-soft/core/mastering.cpp +++ b/Engine/lib/openal-soft/core/mastering.cpp @@ -133,8 +133,8 @@ static void CrestDetector(Compressor *Comp, const uint SamplesToDo) { const float x2{clampf(x_abs * x_abs, 0.000001f, 1000000.0f)}; - y2_peak = maxf(x2, lerp(x2, y2_peak, a_crest)); - y2_rms = lerp(x2, y2_rms, a_crest); + y2_peak = maxf(x2, lerpf(x2, y2_peak, a_crest)); + y2_rms = lerpf(x2, y2_rms, a_crest); return y2_peak / y2_rms; }; auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; @@ -243,15 +243,15 @@ void GainCompressor(Compressor *Comp, const uint SamplesToDo) * above to compensate for the chained operating mode. */ const float x_L{-slope * y_G}; - y_1 = maxf(x_L, lerp(x_L, y_1, a_rel)); - y_L = lerp(y_1, y_L, a_att); + y_1 = maxf(x_L, lerpf(x_L, y_1, a_rel)); + y_L = lerpf(y_1, y_L, a_att); /* Knee width and make-up gain automation make use of a smoothed * measurement of deviation between the control signal and estimate. * The estimate is also used to bias the measurement to hot-start its * average. */ - c_dev = lerp(-(y_L+c_est), c_dev, a_adp); + c_dev = lerpf(-(y_L+c_est), c_dev, a_adp); if(autoPostGain) { @@ -334,7 +334,7 @@ std::unique_ptr Compressor::Create(const size_t NumChans, const floa size += sizeof(*Compressor::mHold); } - auto Comp = std::unique_ptr{new (al_calloc(16, size)) Compressor{}}; + auto Comp = CompressorPtr{al::construct_at(static_cast(al_calloc(16, size)))}; Comp->mNumChans = NumChans; Comp->mAuto.Knee = AutoKnee; Comp->mAuto.Attack = AutoAttack; @@ -361,17 +361,15 @@ std::unique_ptr Compressor::Create(const size_t NumChans, const floa { if(hold > 1) { - Comp->mHold = ::new (static_cast(Comp.get() + 1)) SlidingHold{}; + Comp->mHold = al::construct_at(reinterpret_cast(Comp.get() + 1)); Comp->mHold->mValues[0] = -std::numeric_limits::infinity(); Comp->mHold->mExpiries[0] = hold; Comp->mHold->mLength = hold; - Comp->mDelay = ::new(static_cast(Comp->mHold + 1)) FloatBufferLine[NumChans]; + Comp->mDelay = reinterpret_cast(Comp->mHold + 1); } else - { - Comp->mDelay = ::new(static_cast(Comp.get() + 1)) FloatBufferLine[NumChans]; - } - std::fill_n(Comp->mDelay, NumChans, FloatBufferLine{}); + Comp->mDelay = reinterpret_cast(Comp.get() + 1); + std::uninitialized_fill_n(Comp->mDelay, NumChans, FloatBufferLine{}); } Comp->mCrestCoeff = std::exp(-1.0f / (0.200f * SampleRate)); // 200ms diff --git a/Engine/lib/openal-soft/core/mastering.h b/Engine/lib/openal-soft/core/mastering.h index 322d36545..1a36937ca 100644 --- a/Engine/lib/openal-soft/core/mastering.h +++ b/Engine/lib/openal-soft/core/mastering.h @@ -100,5 +100,6 @@ struct Compressor { const float ThresholdDb, const float Ratio, const float KneeDb, const float AttackTime, const float ReleaseTime); }; +using CompressorPtr = std::unique_ptr; #endif /* CORE_MASTERING_H */ diff --git a/Engine/lib/openal-soft/core/mixer.cpp b/Engine/lib/openal-soft/core/mixer.cpp new file mode 100644 index 000000000..4618406be --- /dev/null +++ b/Engine/lib/openal-soft/core/mixer.cpp @@ -0,0 +1,126 @@ + +#include "config.h" + +#include "mixer.h" + +#include + +#include "alnumbers.h" +#include "devformat.h" +#include "device.h" +#include "mixer/defs.h" + +struct CTag; + + +MixerFunc MixSamples{Mix_}; + + +std::array CalcAmbiCoeffs(const float y, const float z, const float x, + const float spread) +{ + std::array coeffs; + + /* Zeroth-order */ + coeffs[0] = 1.0f; /* ACN 0 = 1 */ + /* First-order */ + coeffs[1] = al::numbers::sqrt3_v * y; /* ACN 1 = sqrt(3) * Y */ + coeffs[2] = al::numbers::sqrt3_v * z; /* ACN 2 = sqrt(3) * Z */ + coeffs[3] = al::numbers::sqrt3_v * x; /* ACN 3 = sqrt(3) * X */ + /* Second-order */ + const float xx{x*x}, yy{y*y}, zz{z*z}, xy{x*y}, yz{y*z}, xz{x*z}; + coeffs[4] = 3.872983346f * xy; /* ACN 4 = sqrt(15) * X * Y */ + coeffs[5] = 3.872983346f * yz; /* ACN 5 = sqrt(15) * Y * Z */ + coeffs[6] = 1.118033989f * (3.0f*zz - 1.0f); /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */ + coeffs[7] = 3.872983346f * xz; /* ACN 7 = sqrt(15) * X * Z */ + coeffs[8] = 1.936491673f * (xx - yy); /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */ + /* Third-order */ + coeffs[9] = 2.091650066f * (y*(3.0f*xx - yy)); /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */ + coeffs[10] = 10.246950766f * (z*xy); /* ACN 10 = sqrt(105) * Z * X * Y */ + coeffs[11] = 1.620185175f * (y*(5.0f*zz - 1.0f)); /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */ + coeffs[12] = 1.322875656f * (z*(5.0f*zz - 3.0f)); /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */ + coeffs[13] = 1.620185175f * (x*(5.0f*zz - 1.0f)); /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */ + coeffs[14] = 5.123475383f * (z*(xx - yy)); /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */ + coeffs[15] = 2.091650066f * (x*(xx - 3.0f*yy)); /* ACN 15 = sqrt(35/8) * X * (X*X - 3*Y*Y) */ + /* Fourth-order */ + /* ACN 16 = sqrt(35)*3/2 * X * Y * (X*X - Y*Y) */ + /* ACN 17 = sqrt(35/2)*3/2 * (3*X*X - Y*Y) * Y * Z */ + /* ACN 18 = sqrt(5)*3/2 * X * Y * (7*Z*Z - 1) */ + /* ACN 19 = sqrt(5/2)*3/2 * Y * Z * (7*Z*Z - 3) */ + /* ACN 20 = 3/8 * (35*Z*Z*Z*Z - 30*Z*Z + 3) */ + /* ACN 21 = sqrt(5/2)*3/2 * X * Z * (7*Z*Z - 3) */ + /* ACN 22 = sqrt(5)*3/4 * (X*X - Y*Y) * (7*Z*Z - 1) */ + /* ACN 23 = sqrt(35/2)*3/2 * (X*X - 3*Y*Y) * X * Z */ + /* ACN 24 = sqrt(35)*3/8 * (X*X*X*X - 6*X*X*Y*Y + Y*Y*Y*Y) */ + + if(spread > 0.0f) + { + /* Implement the spread by using a spherical source that subtends the + * angle spread. See: + * http://www.ppsloan.org/publications/StupidSH36.pdf - Appendix A3 + * + * When adjusted for N3D normalization instead of SN3D, these + * calculations are: + * + * ZH0 = -sqrt(pi) * (-1+ca); + * ZH1 = 0.5*sqrt(pi) * sa*sa; + * ZH2 = -0.5*sqrt(pi) * ca*(-1+ca)*(ca+1); + * ZH3 = -0.125*sqrt(pi) * (-1+ca)*(ca+1)*(5*ca*ca - 1); + * ZH4 = -0.125*sqrt(pi) * ca*(-1+ca)*(ca+1)*(7*ca*ca - 3); + * ZH5 = -0.0625*sqrt(pi) * (-1+ca)*(ca+1)*(21*ca*ca*ca*ca - 14*ca*ca + 1); + * + * The gain of the source is compensated for size, so that the + * loudness doesn't depend on the spread. Thus: + * + * ZH0 = 1.0f; + * ZH1 = 0.5f * (ca+1.0f); + * ZH2 = 0.5f * (ca+1.0f)*ca; + * ZH3 = 0.125f * (ca+1.0f)*(5.0f*ca*ca - 1.0f); + * ZH4 = 0.125f * (ca+1.0f)*(7.0f*ca*ca - 3.0f)*ca; + * ZH5 = 0.0625f * (ca+1.0f)*(21.0f*ca*ca*ca*ca - 14.0f*ca*ca + 1.0f); + */ + const float ca{std::cos(spread * 0.5f)}; + /* Increase the source volume by up to +3dB for a full spread. */ + const float scale{std::sqrt(1.0f + al::numbers::inv_pi_v/2.0f*spread)}; + + const float ZH0_norm{scale}; + const float ZH1_norm{scale * 0.5f * (ca+1.f)}; + const float ZH2_norm{scale * 0.5f * (ca+1.f)*ca}; + const float ZH3_norm{scale * 0.125f * (ca+1.f)*(5.f*ca*ca-1.f)}; + + /* Zeroth-order */ + coeffs[0] *= ZH0_norm; + /* First-order */ + coeffs[1] *= ZH1_norm; + coeffs[2] *= ZH1_norm; + coeffs[3] *= ZH1_norm; + /* Second-order */ + coeffs[4] *= ZH2_norm; + coeffs[5] *= ZH2_norm; + coeffs[6] *= ZH2_norm; + coeffs[7] *= ZH2_norm; + coeffs[8] *= ZH2_norm; + /* Third-order */ + coeffs[9] *= ZH3_norm; + coeffs[10] *= ZH3_norm; + coeffs[11] *= ZH3_norm; + coeffs[12] *= ZH3_norm; + coeffs[13] *= ZH3_norm; + coeffs[14] *= ZH3_norm; + coeffs[15] *= ZH3_norm; + } + + return coeffs; +} + +void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain, + const al::span gains) +{ + auto ambimap = mix->AmbiMap.cbegin(); + + auto iter = std::transform(ambimap, ambimap+mix->Buffer.size(), gains.begin(), + [coeffs,ingain](const BFChannelConfig &chanmap) noexcept -> float + { return chanmap.Scale * coeffs[chanmap.Index] * ingain; } + ); + std::fill(iter, gains.end(), 0.0f); +} diff --git a/Engine/lib/openal-soft/Alc/alu.h b/Engine/lib/openal-soft/core/mixer.h similarity index 74% rename from Engine/lib/openal-soft/Alc/alu.h rename to Engine/lib/openal-soft/core/mixer.h index 2aa1a6524..309f42249 100644 --- a/Engine/lib/openal-soft/Alc/alu.h +++ b/Engine/lib/openal-soft/core/mixer.h @@ -1,25 +1,18 @@ -#ifndef ALU_H -#define ALU_H +#ifndef CORE_MIXER_H +#define CORE_MIXER_H #include #include -#include +#include #include #include "alspan.h" -#include "core/ambidefs.h" -#include "core/bufferline.h" -#include "core/devformat.h" +#include "ambidefs.h" +#include "bufferline.h" +#include "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); @@ -27,35 +20,6 @@ using MixerFunc = void(*)(const al::span InSamples, 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, - Hrtf_Enable = 1, - Hrtf_Disable = 2, -}; - -void aluInit(void); - -void aluInitMixer(void); - -/* aluInitRenderer - * - * Set up the appropriate panning method and mixing method given the device - * properties. - */ -void aluInitRenderer(ALCdevice *device, int hrtf_id, HrtfRequestMode hrtf_appreq, - HrtfRequestMode hrtf_userreq); - -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 @@ -134,8 +98,4 @@ auto SetAmbiPanIdentity(T iter, I count, F func) -> std::enable_if_t 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/Engine/lib/openal-soft/core/mixer/hrtfbase.h b/Engine/lib/openal-soft/core/mixer/hrtfbase.h index 7419f9603..606f9d4ee 100644 --- a/Engine/lib/openal-soft/core/mixer/hrtfbase.h +++ b/Engine/lib/openal-soft/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,56 +84,24 @@ 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) { ASSUME(BufferSize > 0); - /* Add the existing signal directly to the accumulation buffer, unfiltered, - * and with a delay to align with the input delay. - */ - for(size_t i{0};i < BufferSize;++i) - { - AccumSamples[HrtfDirectDelay+i][0] += LeftOut[i]; - AccumSamples[HrtfDirectDelay+i][1] += RightOut[i]; - } - for(const FloatBufferLine &input : InSamples) { /* For dual-band processing, the signal needs extra scaling applied to - * the high frequency response. The band-splitter alone creates a - * frequency-dependent phase shift, which is not ideal. To counteract - * it, combine it with a backwards phase shift. + * the high frequency response. The band-splitter applies this scaling + * with a consistent phase shift regardless of the scale amount. */ - - /* Load the input signal backwards, into a temp buffer with delay - * padding. The delay serves to reduce the error caused by the IIR - * filter's phase shift on a partial input. - */ - al::span tempbuf{al::assume_aligned<16>(TempBuf), HrtfDirectDelay+BufferSize}; - auto tmpiter = std::reverse_copy(input.begin(), input.begin()+BufferSize, tempbuf.begin()); - std::copy(ChanState->mDelay.cbegin(), ChanState->mDelay.cend(), tmpiter); - - /* Save the unfiltered newest input samples for next time. */ - std::copy_n(tempbuf.begin(), ChanState->mDelay.size(), ChanState->mDelay.begin()); - - /* Apply the all-pass on the reversed signal and reverse the resulting - * sample array. This produces the forward response with a backwards - * phase shift (+n degrees becomes -n degrees). - */ - ChanState->mSplitter.applyAllpass(tempbuf); - tempbuf = tempbuf.subspan(); - std::reverse(tempbuf.begin(), tempbuf.end()); - - /* Now apply the HF scale with the band-splitter. This applies the - * forward phase shift, which cancels out with the backwards phase - * shift to get the original phase on the scaled signal. - */ - ChanState->mSplitter.processHfScale(tempbuf, ChanState->mHfScale); + ChanState->mSplitter.processHfScale({input.data(), BufferSize}, TempBuf, + ChanState->mHfScale); /* Now apply the HRIR coefficients to this channel. */ - const auto &Coeffs = ChanState->mCoeffs; + const float *RESTRICT tempbuf{al::assume_aligned<16>(TempBuf)}; + const ConstHrirSpan Coeffs{ChanState->mCoeffs}; for(size_t i{0u};i < BufferSize;++i) { const float insample{tempbuf[i]}; @@ -143,16 +111,18 @@ inline void MixDirectHrtfBase(FloatBufferLine &LeftOut, FloatBufferLine &RightOu ++ChanState; } + /* Add the HRTF signal to the existing "direct" signal. */ + float *RESTRICT left{al::assume_aligned<16>(LeftOut.data())}; + float *RESTRICT right{al::assume_aligned<16>(RightOut.data())}; for(size_t i{0u};i < BufferSize;++i) - LeftOut[i] = AccumSamples[i][0]; + left[i] += AccumSamples[i][0]; for(size_t i{0u};i < BufferSize;++i) - RightOut[i] = AccumSamples[i][1]; + right[i] += AccumSamples[i][1]; /* Copy the new in-progress accumulation values to the front and clear the * following samples for the next mix. */ - auto accum_iter = std::copy_n(AccumSamples+BufferSize, HrirLength+HrtfDirectDelay, - AccumSamples); + auto accum_iter = std::copy_n(AccumSamples+BufferSize, HrirLength, AccumSamples); std::fill_n(accum_iter, BufferSize, float2{}); } diff --git a/Engine/lib/openal-soft/core/mixer/hrtfdefs.h b/Engine/lib/openal-soft/core/mixer/hrtfdefs.h index 89a9bb8df..3c903ed89 100644 --- a/Engine/lib/openal-soft/core/mixer/hrtfdefs.h +++ b/Engine/lib/openal-soft/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" @@ -25,12 +26,12 @@ constexpr uint HrirMask{HrirLength - 1}; 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; @@ -44,7 +45,6 @@ struct HrtfFilter { struct HrtfChannelState { - std::array mDelay{}; BandSplitter mSplitter; float mHfScale{}; alignas(16) HrirArray mCoeffs{}; diff --git a/Engine/lib/openal-soft/core/mixer/mixer_c.cpp b/Engine/lib/openal-soft/core/mixer/mixer_c.cpp index ff9538a45..f3e6aa6ad 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_c.cpp +++ b/Engine/lib/openal-soft/core/mixer/mixer_c.cpp @@ -26,21 +26,22 @@ constexpr uint FracPhaseDiffOne{1 << FracPhaseBitDiff}; inline float do_point(const InterpState&, const float *RESTRICT vals, const uint) { return vals[0]; } inline float do_lerp(const InterpState&, const float *RESTRICT vals, const uint frac) -{ return lerp(vals[0], vals[1], static_cast(frac)*(1.0f/MixerFracOne)); } +{ return lerpf(vals[0], vals[1], static_cast(frac)*(1.0f/MixerFracOne)); } inline float do_cubic(const InterpState&, const float *RESTRICT vals, const uint frac) { return cubic(vals[0], vals[1], vals[2], vals[3], static_cast(frac)*(1.0f/MixerFracOne)); } inline float do_bsinc(const InterpState &istate, const float *RESTRICT vals, const uint frac) { const size_t m{istate.bsinc.m}; + ASSUME(m > 0); // Calculate the phase index and factor. const uint pi{frac >> FracPhaseBitDiff}; const float pf{static_cast(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)}; - const float *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/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp b/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp index f3e5f1303..a34689269 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp +++ b/Engine/lib/openal-soft/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; @@ -101,7 +101,7 @@ float *Resample_(const InterpState*, float *RESTRICT src, uint frac = static_cast(vgetq_lane_s32(frac4, 0)); do { - *(dst_iter++) = lerp(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); + *(dst_iter++) = lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); frac += increment; src += frac>>MixerFracBits; @@ -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) { @@ -243,7 +245,7 @@ void Mix_(const al::span InSamples, const al::span> 2}) + if(size_t todo{min_len >> 2}) { const float32x4_t four4{vdupq_n_f32(4.0f)}; const float32x4_t step4{vdupq_n_f32(step)}; diff --git a/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp b/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp index 23caf7976..fc4d8cc63 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp +++ b/Engine/lib/openal-soft/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)}; @@ -37,7 +36,17 @@ inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const Hrir * systems that support SSE, which is the only one that needs to know the * alignment of Values (which alternates between 8- and 16-byte aligned). */ - if(reinterpret_cast(Values)&0x8) + if(!(reinterpret_cast(Values)&15)) + { + for(size_t i{0};i < IrSize;i += 2) + { + const __m128 coeffs{_mm_load_ps(&Coeffs[i][0])}; + __m128 vals{_mm_load_ps(&Values[i][0])}; + vals = MLA4(vals, lrlr, coeffs); + _mm_store_ps(&Values[i][0], vals); + } + } + else { __m128 imp0, imp1; __m128 coeffs{_mm_load_ps(&Coeffs[0][0])}; @@ -62,16 +71,6 @@ inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const Hrir vals = _mm_add_ps(imp0, vals); _mm_storel_pi(reinterpret_cast<__m64*>(&Values[i][0]), vals); } - else - { - for(size_t i{0};i < IrSize;i += 2) - { - const __m128 coeffs{_mm_load_ps(&Coeffs[i][0])}; - __m128 vals{_mm_load_ps(&Values[i][0])}; - vals = MLA4(vals, lrlr, coeffs); - _mm_store_ps(&Values[i][0], vals); - } - } } } // namespace @@ -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) { @@ -210,7 +211,7 @@ void Mix_(const al::span InSamples, const al::span> 2}) + if(size_t todo{min_len >> 2}) { const __m128 four4{_mm_set1_ps(4.0f)}; const __m128 step4{_mm_set1_ps(step)}; diff --git a/Engine/lib/openal-soft/core/mixer/mixer_sse2.cpp b/Engine/lib/openal-soft/core/mixer/mixer_sse2.cpp index f91d5dcd9..2c0adb8a3 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_sse2.cpp +++ b/Engine/lib/openal-soft/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])}; @@ -78,7 +78,7 @@ float *Resample_(const InterpState*, float *RESTRICT src, uint frac = static_cast(_mm_cvtsi128_si32(frac4)); do { - *(dst_iter++) = lerp(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); + *(dst_iter++) = lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); frac += increment; src += frac>>MixerFracBits; diff --git a/Engine/lib/openal-soft/core/mixer/mixer_sse41.cpp b/Engine/lib/openal-soft/core/mixer/mixer_sse41.cpp index 032e5d544..cfedfd657 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_sse41.cpp +++ b/Engine/lib/openal-soft/core/mixer/mixer_sse41.cpp @@ -83,7 +83,7 @@ float *Resample_(const InterpState*, float *RESTRICT src, uint frac = static_cast(_mm_cvtsi128_si32(frac4)); do { - *(dst_iter++) = lerp(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); + *(dst_iter++) = lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); frac += increment; src += frac>>MixerFracBits; diff --git a/Engine/lib/openal-soft/core/resampler_limits.h b/Engine/lib/openal-soft/core/resampler_limits.h new file mode 100644 index 000000000..9d4cefdae --- /dev/null +++ b/Engine/lib/openal-soft/core/resampler_limits.h @@ -0,0 +1,12 @@ +#ifndef CORE_RESAMPLER_LIMITS_H +#define CORE_RESAMPLER_LIMITS_H + +/* Maximum number of samples to pad on the ends of a buffer for resampling. + * Note that the padding is symmetric (half at the beginning and half at the + * end)! + */ +constexpr int MaxResamplerPadding{48}; + +constexpr int MaxResamplerEdge{MaxResamplerPadding >> 1}; + +#endif /* CORE_RESAMPLER_LIMITS_H */ diff --git a/Engine/lib/openal-soft/core/rtkit.cpp b/Engine/lib/openal-soft/core/rtkit.cpp new file mode 100644 index 000000000..9210220ef --- /dev/null +++ b/Engine/lib/openal-soft/core/rtkit.cpp @@ -0,0 +1,232 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + Copyright 2009 Lennart Poettering + Copyright 2010 David Henningsson + Copyright 2021 Chris Robinson + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include "config.h" + +#include "rtkit.h" + +#include + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include + + +namespace dbus { + +constexpr int TypeString{'s'}; +constexpr int TypeVariant{'v'}; +constexpr int TypeInt32{'i'}; +constexpr int TypeUInt32{'u'}; +constexpr int TypeInt64{'x'}; +constexpr int TypeUInt64{'t'}; +constexpr int TypeInvalid{'\0'}; + +struct MessageDeleter { + void operator()(DBusMessage *m) { dbus_message_unref(m); } +}; +using MessagePtr = std::unique_ptr; + +} // namespace dbus + +namespace { + +inline pid_t _gettid() +{ +#ifdef __linux__ + return static_cast(syscall(SYS_gettid)); +#elif defined(__FreeBSD__) + long pid{}; + thr_self(&pid); + return static_cast(pid); +#else +#warning gettid not available + return 0; +#endif +} + +int translate_error(const char *name) +{ + if(strcmp(name, DBUS_ERROR_NO_MEMORY) == 0) + return -ENOMEM; + if(strcmp(name, DBUS_ERROR_SERVICE_UNKNOWN) == 0 + || strcmp(name, DBUS_ERROR_NAME_HAS_NO_OWNER) == 0) + return -ENOENT; + if(strcmp(name, DBUS_ERROR_ACCESS_DENIED) == 0 + || strcmp(name, DBUS_ERROR_AUTH_FAILED) == 0) + return -EACCES; + return -EIO; +} + +int rtkit_get_int_property(DBusConnection *connection, const char *propname, long long *propval) +{ + dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, + "org.freedesktop.DBus.Properties", "Get")}; + if(!m) return -ENOMEM; + + const char *interfacestr = RTKIT_SERVICE_NAME; + auto ready = dbus_message_append_args(m.get(), + dbus::TypeString, &interfacestr, + dbus::TypeString, &propname, + dbus::TypeInvalid); + if(!ready) return -ENOMEM; + + dbus::Error error; + dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1, + &error.get())}; + if(!r) return translate_error(error->name); + + if(dbus_set_error_from_message(&error.get(), r.get())) + return translate_error(error->name); + + int ret{-EBADMSG}; + DBusMessageIter iter{}; + dbus_message_iter_init(r.get(), &iter); + while(int curtype{dbus_message_iter_get_arg_type(&iter)}) + { + if(curtype == dbus::TypeVariant) + { + DBusMessageIter subiter{}; + dbus_message_iter_recurse(&iter, &subiter); + + while((curtype=dbus_message_iter_get_arg_type(&subiter)) != dbus::TypeInvalid) + { + if(curtype == dbus::TypeInt32) + { + dbus_int32_t i32{}; + dbus_message_iter_get_basic(&subiter, &i32); + *propval = i32; + ret = 0; + } + + if(curtype == dbus::TypeInt64) + { + dbus_int64_t i64{}; + dbus_message_iter_get_basic(&subiter, &i64); + *propval = i64; + ret = 0; + } + + dbus_message_iter_next(&subiter); + } + } + dbus_message_iter_next(&iter); + } + + return ret; +} + +} // namespace + +int rtkit_get_max_realtime_priority(DBusConnection *connection) +{ + long long retval{}; + int err{rtkit_get_int_property(connection, "MaxRealtimePriority", &retval)}; + return err < 0 ? err : static_cast(retval); +} + +int rtkit_get_min_nice_level(DBusConnection *connection, int *min_nice_level) +{ + long long retval{}; + int err{rtkit_get_int_property(connection, "MinNiceLevel", &retval)}; + if(err >= 0) *min_nice_level = static_cast(retval); + return err; +} + +long long rtkit_get_rttime_usec_max(DBusConnection *connection) +{ + long long retval{}; + int err{rtkit_get_int_property(connection, "RTTimeUSecMax", &retval)}; + return err < 0 ? err : retval; +} + +int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) +{ + if(thread == 0) + thread = _gettid(); + if(thread == 0) + return -ENOTSUP; + + dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, + "org.freedesktop.RealtimeKit1", "MakeThreadRealtime")}; + if(!m) return -ENOMEM; + + auto u64 = static_cast(thread); + auto u32 = static_cast(priority); + auto ready = dbus_message_append_args(m.get(), + dbus::TypeUInt64, &u64, + dbus::TypeUInt32, &u32, + dbus::TypeInvalid); + if(!ready) return -ENOMEM; + + dbus::Error error; + dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1, + &error.get())}; + if(!r) return translate_error(error->name); + + if(dbus_set_error_from_message(&error.get(), r.get())) + return translate_error(error->name); + + return 0; +} + +int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) +{ + if(thread == 0) + thread = _gettid(); + if(thread == 0) + return -ENOTSUP; + + dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, + "org.freedesktop.RealtimeKit1", "MakeThreadHighPriority")}; + if(!m) return -ENOMEM; + + auto u64 = static_cast(thread); + auto s32 = static_cast(nice_level); + auto ready = dbus_message_append_args(m.get(), + dbus::TypeUInt64, &u64, + dbus::TypeInt32, &s32, + dbus::TypeInvalid); + if(!ready) return -ENOMEM; + + dbus::Error error; + dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1, + &error.get())}; + if(!r) return translate_error(error->name); + + if(dbus_set_error_from_message(&error.get(), r.get())) + return translate_error(error->name); + + return 0; +} diff --git a/Engine/lib/openal-soft/core/rtkit.h b/Engine/lib/openal-soft/core/rtkit.h new file mode 100644 index 000000000..d4994e270 --- /dev/null +++ b/Engine/lib/openal-soft/core/rtkit.h @@ -0,0 +1,71 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foortkithfoo +#define foortkithfoo + +/*** + Copyright 2009 Lennart Poettering + Copyright 2010 David Henningsson + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include + +#include "dbus_wrap.h" + +/* This is the reference implementation for a client for + * RealtimeKit. You don't have to use this, but if do, just copy these + * sources into your repository */ + +#define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1" +#define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1" + +/* This is mostly equivalent to sched_setparam(thread, SCHED_RR, { + * .sched_priority = priority }). 'thread' needs to be a kernel thread + * id as returned by gettid(), not a pthread_t! If 'thread' is 0 the + * current thread is used. The returned value is a negative errno + * style error code, or 0 on success. */ +int rtkit_make_realtime(DBusConnection *system_bus, pid_t thread, int priority); + +/* This is mostly equivalent to setpriority(PRIO_PROCESS, thread, + * nice_level). 'thread' needs to be a kernel thread id as returned by + * gettid(), not a pthread_t! If 'thread' is 0 the current thread is + * used. The returned value is a negative errno style error code, or 0 + * on success.*/ +int rtkit_make_high_priority(DBusConnection *system_bus, pid_t thread, int nice_level); + +/* Return the maximum value of realtime priority available. Realtime requests + * above this value will fail. A negative value is an errno style error code. + */ +int rtkit_get_max_realtime_priority(DBusConnection *system_bus); + +/* Retreive the minimum value of nice level available. High prio requests + * below this value will fail. The returned value is a negative errno + * style error code, or 0 on success.*/ +int rtkit_get_min_nice_level(DBusConnection *system_bus, int *min_nice_level); + +/* Return the maximum value of RLIMIT_RTTIME to set before attempting a + * realtime request. A negative value is an errno style error code. + */ +long long rtkit_get_rttime_usec_max(DBusConnection *system_bus); + +#endif diff --git a/Engine/lib/openal-soft/core/uhjfilter.cpp b/Engine/lib/openal-soft/core/uhjfilter.cpp index 92f359010..7a68f1b17 100644 --- a/Engine/lib/openal-soft/core/uhjfilter.cpp +++ b/Engine/lib/openal-soft/core/uhjfilter.cpp @@ -3,273 +3,239 @@ #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 { -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.6512*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. 3-channel UHJ excludes Q, + * while 2-channel excludes Q and T. * - * The phase shift is done using a FIR filter derived from an FFT'd impulse - * with the desired shift. + * The phase shift is done using a linear FIR filter derived from an FFT'd + * impulse with the desired shift. */ -void Uhj2Encoder::encode(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const FloatBufferLine *InSamples, const size_t SamplesToDo) +void UhjEncoder::encode(float *LeftOut, float *RightOut, const FloatBufferLine *InSamples, + const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); - float *RESTRICT left{al::assume_aligned<16>(LeftOut.data())}; - float *RESTRICT right{al::assume_aligned<16>(RightOut.data())}; + float *RESTRICT left{al::assume_aligned<16>(LeftOut)}; + float *RESTRICT right{al::assume_aligned<16>(RightOut)}; const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0].data())}; const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1].data())}; const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2].data())}; - /* Combine the previously delayed 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. */ for(size_t i{0};i < SamplesToDo;++i,++miditer) *miditer += left[i] + right[i]; + + /* D = 0.6554516*Y */ + auto sideiter = mD.begin() + sFilterDelay; + std::transform(yinput, yinput+SamplesToDo, sideiter, + [](const float y) noexcept -> float { return 0.6554516f*y; }); for(size_t i{0};i < SamplesToDo;++i,++sideiter) *sideiter += left[i] - right[i]; - /* 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()); + 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.981532*S + 0.197484*j(0.828331*D + 0.767820*T) + * X = 0.418496*S - j(0.828331*D + 0.767820*T) + * Y = 0.795968*D - 0.676392*T + j(0.186633*S) + * Z = 1.023332*Q + * + * where j is a +90 degree phase shift. 3-channel UHJ excludes Q, while 2- + * channel excludes Q and T. + */ +void UhjDecoder::decode(const al::span samples, const size_t samplesToDo, + const size_t forwardSamples) +{ + ASSUME(samplesToDo > 0); + + { + const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; + const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + const float *RESTRICT t{al::assume_aligned<16>(samples[2])}; + + /* S = Left + Right */ + for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + mS[i] = left[i] + right[i]; + + /* D = Left - Right */ + for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + mD[i] = left[i] - right[i]; + + /* T */ + for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + mT[i] = t[i]; + } + + float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; + float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; + float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + + /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ + auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); + std::transform(mD.cbegin(), mD.cbegin()+samplesToDo+sFilterDelay, mT.cbegin(), tmpiter, + [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); + std::copy_n(mTemp.cbegin()+forwardSamples, mDTHistory.size(), mDTHistory.begin()); + PShift.process({xoutput, samplesToDo}, mTemp.data()); + + /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ + for(size_t i{0};i < samplesToDo;++i) + woutput[i] = 0.981532f*mS[i] + 0.197484f*xoutput[i]; + /* X = 0.418496*S - j(0.828331*D + 0.767820*T) */ + for(size_t i{0};i < samplesToDo;++i) + xoutput[i] = 0.418496f*mS[i] - xoutput[i]; + + /* Precompute j*S and store in youtput. */ + tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); + std::copy_n(mS.cbegin(), samplesToDo+sFilterDelay, tmpiter); + std::copy_n(mTemp.cbegin()+forwardSamples, mSHistory.size(), mSHistory.begin()); + PShift.process({youtput, samplesToDo}, mTemp.data()); + + /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ + for(size_t i{0};i < samplesToDo;++i) + youtput[i] = 0.795968f*mD[i] - 0.676392f*mT[i] + 0.186633f*youtput[i]; + + if(samples.size() > 3) + { + float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])}; + /* Z = 1.023332*Q */ + for(size_t i{0};i < samplesToDo;++i) + zoutput[i] = 1.023332f*zoutput[i]; + } +} + + +/* Super Stereo processing is done as: + * + * S = Left + Right + * D = Left - Right + * + * W = 0.6098637*S - 0.6896511*j*w*D + * X = 0.8624776*S + 0.7626955*j*w*D + * Y = 1.6822415*w*D - 0.2156194*j*S + * + * where j is a +90 degree phase shift. w is a variable control for the + * resulting stereo width, with the range 0 <= w <= 0.7. + */ +void UhjDecoder::decodeStereo(const al::span samples, const size_t samplesToDo, + const size_t forwardSamples) +{ + ASSUME(samplesToDo > 0); + + { + const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; + const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + + for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + mS[i] = left[i] + right[i]; + + /* Pre-apply the width factor to the difference signal D. Smoothly + * interpolate when it changes. + */ + const float wtarget{mWidthControl}; + const float wcurrent{unlikely(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth}; + if(likely(wtarget == wcurrent) || unlikely(forwardSamples == 0)) + { + for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + mD[i] = (left[i] - right[i]) * wcurrent; + } + else + { + const float wstep{(wtarget - wcurrent) / static_cast(forwardSamples)}; + float fi{0.0f}; + size_t i{0}; + for(;i < forwardSamples;++i) + { + mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi); + fi += 1.0f; + } + for(;i < samplesToDo+sFilterDelay;++i) + mD[i] = (left[i] - right[i]) * wtarget; + mCurrentWidth = wtarget; + } + } + + float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; + float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; + float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + + /* Precompute j*D and store in xoutput. */ + auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); + std::copy_n(mD.cbegin(), samplesToDo+sFilterDelay, tmpiter); + std::copy_n(mTemp.cbegin()+forwardSamples, mDTHistory.size(), mDTHistory.begin()); + PShift.process({xoutput, samplesToDo}, mTemp.data()); + + /* W = 0.6098637*S - 0.6896511*j*w*D */ + for(size_t i{0};i < samplesToDo;++i) + woutput[i] = 0.6098637f*mS[i] - 0.6896511f*xoutput[i]; + /* X = 0.8624776*S + 0.7626955*j*w*D */ + for(size_t i{0};i < samplesToDo;++i) + xoutput[i] = 0.8624776f*mS[i] + 0.7626955f*xoutput[i]; + + /* Precompute j*S and store in youtput. */ + tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); + std::copy_n(mS.cbegin(), samplesToDo+sFilterDelay, tmpiter); + std::copy_n(mTemp.cbegin()+forwardSamples, mSHistory.size(), mSHistory.begin()); + PShift.process({youtput, samplesToDo}, mTemp.data()); + + /* Y = 1.6822415*w*D - 0.2156194*j*S */ + for(size_t i{0};i < samplesToDo;++i) + youtput[i] = 1.6822415f*mD[i] - 0.2156194f*youtput[i]; } diff --git a/Engine/lib/openal-soft/core/uhjfilter.h b/Engine/lib/openal-soft/core/uhjfilter.h index c2cb8722a..0453def50 100644 --- a/Engine/lib/openal-soft/core/uhjfilter.h +++ b/Engine/lib/openal-soft/core/uhjfilter.h @@ -5,35 +5,80 @@ #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 UhjFilterBase { + /* 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}; + static constexpr 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{}; +struct UhjEncoder : public UhjFilterBase { + /* Delays and processing storage for the unfiltered signal. */ + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; /* History for the FIR filter. */ - alignas(16) std::array 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. + * signal. The input must use FuMa channel ordering and UHJ scaling (FuMa + * with an additional +3dB boost). */ - void encode(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, - const FloatBufferLine *InSamples, const size_t SamplesToDo); + void encode(float *LeftOut, float *RightOut, const FloatBufferLine *InSamples, + const size_t SamplesToDo); - DEF_NEWDEL(Uhj2Encoder) + DEF_NEWDEL(UhjEncoder) +}; + + +struct UhjDecoder : public UhjFilterBase { + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mT{}; + + alignas(16) std::array mDTHistory{}; + alignas(16) std::array mSHistory{}; + + alignas(16) std::array mTemp{}; + + float mCurrentWidth{-1.0f}; + + /** + * The width factor for Super Stereo processing. Can be changed in between + * calls to decodeStereo, with valid values being between 0...0.7. + */ + float mWidthControl{0.593f}; + + /** + * Decodes a 3- or 4-channel UHJ signal into a B-Format signal with FuMa + * channel ordering and UHJ scaling. For 3-channel, the 3rd channel may be + * attenuated by 'n', where 0 <= n <= 1. So to decode 2-channel UHJ, supply + * 3 channels with the 3rd channel silent (n=0). The B-Format signal + * reconstructed from 2-channel UHJ should not be run through a normal + * B-Format decoder, as it needs different shelf filters. + */ + void decode(const al::span samples, const size_t samplesToDo, + const size_t forwardSamples); + + /** + * Applies Super Stereo processing on a stereo signal to create a B-Format + * signal with FuMa channel ordering and UHJ scaling. The samples span + * should contain 3 channels, the first two being the left and right stereo + * channels, and the third left empty. + */ + void decodeStereo(const al::span samples, const size_t samplesToDo, + const size_t forwardSamples); + + using DecoderFunc = void (UhjDecoder::*)(const al::span samples, + const size_t samplesToDo, const size_t forwardSamples); + + DEF_NEWDEL(UhjDecoder) }; #endif /* CORE_UHJFILTER_H */ diff --git a/Engine/lib/openal-soft/Alc/uiddefs.cpp b/Engine/lib/openal-soft/core/uiddefs.cpp similarity index 100% rename from Engine/lib/openal-soft/Alc/uiddefs.cpp rename to Engine/lib/openal-soft/core/uiddefs.cpp diff --git a/Engine/lib/openal-soft/core/voice.cpp b/Engine/lib/openal-soft/core/voice.cpp new file mode 100644 index 000000000..e269c4a9d --- /dev/null +++ b/Engine/lib/openal-soft/core/voice.cpp @@ -0,0 +1,945 @@ + +#include "config.h" + +#include "voice.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "albyte.h" +#include "alnumeric.h" +#include "aloptional.h" +#include "alspan.h" +#include "alstring.h" +#include "ambidefs.h" +#include "async_event.h" +#include "buffer_storage.h" +#include "context.h" +#include "cpu_caps.h" +#include "devformat.h" +#include "device.h" +#include "filters/biquad.h" +#include "filters/nfc.h" +#include "filters/splitter.h" +#include "fmt_traits.h" +#include "logging.h" +#include "mixer.h" +#include "mixer/defs.h" +#include "mixer/hrtfdefs.h" +#include "opthelpers.h" +#include "resampler_limits.h" +#include "ringbuffer.h" +#include "vector.h" +#include "voice_change.h" + +struct CTag; +#ifdef HAVE_SSE +struct SSETag; +#endif +#ifdef HAVE_NEON +struct NEONTag; +#endif +struct CopyTag; + + +static_assert(!(sizeof(DeviceBase::MixerBufferLine)&15), + "DeviceBase::MixerBufferLine must be a multiple of 16 bytes"); +static_assert(!(MaxResamplerEdge&3), "MaxResamplerEdge is not a multiple of 4"); + +Resampler ResamplerDefault{Resampler::Linear}; + +namespace { + +using uint = unsigned int; + +using HrtfMixerFunc = void(*)(const float *InSamples, float2 *AccumSamples, const uint IrSize, + const MixHrtfFilter *hrtfparams, const size_t BufferSize); +using HrtfMixerBlendFunc = void(*)(const float *InSamples, float2 *AccumSamples, + const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, + const size_t BufferSize); + +HrtfMixerFunc MixHrtfSamples{MixHrtf_}; +HrtfMixerBlendFunc MixHrtfBlendSamples{MixHrtfBlend_}; + +inline MixerFunc SelectMixer() +{ +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return Mix_; +#endif +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return Mix_; +#endif + return Mix_; +} + +inline HrtfMixerFunc SelectHrtfMixer() +{ +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return MixHrtf_; +#endif +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return MixHrtf_; +#endif + return MixHrtf_; +} + +inline HrtfMixerBlendFunc SelectHrtfBlendMixer() +{ +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return MixHrtfBlend_; +#endif +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return MixHrtfBlend_; +#endif + return MixHrtfBlend_; +} + +} // namespace + +void Voice::InitMixer(al::optional resampler) +{ + if(resampler) + { + struct ResamplerEntry { + const char name[16]; + const Resampler resampler; + }; + constexpr ResamplerEntry ResamplerList[]{ + { "none", Resampler::Point }, + { "point", Resampler::Point }, + { "linear", Resampler::Linear }, + { "cubic", Resampler::Cubic }, + { "bsinc12", Resampler::BSinc12 }, + { "fast_bsinc12", Resampler::FastBSinc12 }, + { "bsinc24", Resampler::BSinc24 }, + { "fast_bsinc24", Resampler::FastBSinc24 }, + }; + + const char *str{resampler->c_str()}; + if(al::strcasecmp(str, "bsinc") == 0) + { + WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", str); + str = "bsinc12"; + } + else if(al::strcasecmp(str, "sinc4") == 0 || al::strcasecmp(str, "sinc8") == 0) + { + WARN("Resampler option \"%s\" is deprecated, using cubic\n", str); + str = "cubic"; + } + + auto iter = std::find_if(std::begin(ResamplerList), std::end(ResamplerList), + [str](const ResamplerEntry &entry) -> bool + { return al::strcasecmp(str, entry.name) == 0; }); + if(iter == std::end(ResamplerList)) + ERR("Invalid resampler: %s\n", str); + else + ResamplerDefault = iter->resampler; + } + + MixSamples = SelectMixer(); + MixHrtfBlendSamples = SelectHrtfBlendMixer(); + MixHrtfSamples = SelectHrtfMixer(); +} + + +namespace { + +void SendSourceStoppedEvent(ContextBase *context, uint id) +{ + RingBuffer *ring{context->mAsyncEvents.get()}; + auto evt_vec = ring->getWriteVector(); + if(evt_vec.first.len < 1) return; + + AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), + AsyncEvent::SourceStateChange)}; + evt->u.srcstate.id = id; + evt->u.srcstate.state = AsyncEvent::SrcState::Stop; + + ring->writeAdvance(1); +} + + +const float *DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, float *dst, + const al::span src, int type) +{ + switch(type) + { + case AF_None: + lpfilter.clear(); + hpfilter.clear(); + break; + + case AF_LowPass: + lpfilter.process(src, dst); + hpfilter.clear(); + return dst; + case AF_HighPass: + lpfilter.clear(); + hpfilter.process(src, dst); + return dst; + + case AF_BandPass: + DualBiquad{lpfilter, hpfilter}.process(src, dst); + return dst; + } + return src.data(); +} + + +template +inline void LoadSamples(const al::span dstSamples, const size_t dstOffset, + const al::byte *src, const size_t srcOffset, const FmtChannels srcChans, const size_t srcStep, + const size_t samples) noexcept +{ + constexpr size_t sampleSize{sizeof(typename al::FmtTypeTraits::Type)}; + auto s = src + srcOffset*srcStep*sampleSize; + if(srcChans == FmtUHJ2 || srcChans == FmtSuperStereo) + { + al::LoadSampleArray(dstSamples[0]+dstOffset, s, srcStep, samples); + al::LoadSampleArray(dstSamples[1]+dstOffset, s+sampleSize, srcStep, samples); + std::fill_n(dstSamples[2]+dstOffset, samples, 0.0f); + } + else + { + for(auto *dst : dstSamples) + { + al::LoadSampleArray(dst+dstOffset, s, srcStep, samples); + s += sampleSize; + } + } +} + +void LoadSamples(const al::span dstSamples, const size_t dstOffset, const al::byte *src, + const size_t srcOffset, const FmtType srcType, const FmtChannels srcChans, + const size_t srcStep, const size_t samples) noexcept +{ +#define HANDLE_FMT(T) case T: \ + LoadSamples(dstSamples, dstOffset, src, srcOffset, srcChans, srcStep, \ + samples); \ + break + + switch(srcType) + { + HANDLE_FMT(FmtUByte); + HANDLE_FMT(FmtShort); + HANDLE_FMT(FmtFloat); + HANDLE_FMT(FmtDouble); + HANDLE_FMT(FmtMulaw); + HANDLE_FMT(FmtAlaw); + } +#undef HANDLE_FMT +} + +void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, + const size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels, + const size_t srcStep, const size_t samplesToLoad, const al::span voiceSamples) +{ + const uint loopStart{buffer->mLoopStart}; + const uint loopEnd{buffer->mLoopEnd}; + ASSUME(loopEnd > loopStart); + + /* If current pos is beyond the loop range, do not loop */ + if(!bufferLoopItem || dataPosInt >= loopEnd) + { + /* Load what's left to play from the buffer */ + const size_t remaining{minz(samplesToLoad, buffer->mSampleLen-dataPosInt)}; + LoadSamples(voiceSamples, 0, buffer->mSamples, dataPosInt, sampleType, sampleChannels, + srcStep, remaining); + + if(const size_t toFill{samplesToLoad - remaining}) + { + for(auto *chanbuffer : voiceSamples) + { + auto srcsamples = chanbuffer + remaining - 1; + std::fill_n(srcsamples + 1, toFill, *srcsamples); + } + } + } + else + { + /* Load what's left of this loop iteration */ + const size_t remaining{minz(samplesToLoad, loopEnd-dataPosInt)}; + LoadSamples(voiceSamples, 0, buffer->mSamples, dataPosInt, sampleType, sampleChannels, + srcStep, remaining); + + /* Load repeats of the loop to fill the buffer. */ + const auto loopSize = static_cast(loopEnd - loopStart); + size_t samplesLoaded{remaining}; + while(const size_t toFill{minz(samplesToLoad - samplesLoaded, loopSize)}) + { + LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, loopStart, sampleType, + sampleChannels, srcStep, toFill); + samplesLoaded += toFill; + } + } +} + +void LoadBufferCallback(VoiceBufferItem *buffer, const size_t numCallbackSamples, + const FmtType sampleType, const FmtChannels sampleChannels, const size_t srcStep, + const size_t samplesToLoad, const al::span voiceSamples) +{ + /* Load what's left to play from the buffer */ + const size_t remaining{minz(samplesToLoad, numCallbackSamples)}; + LoadSamples(voiceSamples, 0, buffer->mSamples, 0, sampleType, sampleChannels, srcStep, + remaining); + + if(const size_t toFill{samplesToLoad - remaining}) + { + for(auto *chanbuffer : voiceSamples) + { + auto srcsamples = chanbuffer + remaining - 1; + std::fill_n(srcsamples + 1, toFill, *srcsamples); + } + } +} + +void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, + size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels, + const size_t srcStep, const size_t samplesToLoad, const al::span voiceSamples) +{ + /* Crawl the buffer queue to fill in the temp buffer */ + size_t samplesLoaded{0}; + while(buffer && samplesLoaded != samplesToLoad) + { + if(dataPosInt >= buffer->mSampleLen) + { + dataPosInt -= buffer->mSampleLen; + buffer = buffer->mNext.load(std::memory_order_acquire); + if(!buffer) buffer = bufferLoopItem; + continue; + } + + const size_t remaining{minz(samplesToLoad-samplesLoaded, buffer->mSampleLen-dataPosInt)}; + LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, dataPosInt, sampleType, + sampleChannels, srcStep, remaining); + + samplesLoaded += remaining; + if(samplesLoaded == samplesToLoad) + break; + + dataPosInt = 0; + buffer = buffer->mNext.load(std::memory_order_acquire); + if(!buffer) buffer = bufferLoopItem; + } + if(const size_t toFill{samplesToLoad - samplesLoaded}) + { + size_t chanidx{0}; + for(auto *chanbuffer : voiceSamples) + { + auto srcsamples = chanbuffer + samplesLoaded - 1; + std::fill_n(srcsamples + 1, toFill, *srcsamples); + ++chanidx; + } + } +} + + +void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &parms, + const float TargetGain, const uint Counter, uint OutPos, const bool IsPlaying, + DeviceBase *Device) +{ + const uint IrSize{Device->mIrSize}; + auto &HrtfSamples = Device->HrtfSourceData; + auto &AccumSamples = Device->HrtfAccumData; + + /* Copy the HRTF history and new input samples into a temp buffer. */ + auto src_iter = std::copy(parms.Hrtf.History.begin(), parms.Hrtf.History.end(), + std::begin(HrtfSamples)); + std::copy_n(samples, DstBufferSize, src_iter); + /* Copy the last used samples back into the history buffer for later. */ + if(likely(IsPlaying)) + std::copy_n(std::begin(HrtfSamples) + DstBufferSize, parms.Hrtf.History.size(), + parms.Hrtf.History.begin()); + + /* If fading and this is the first mixing pass, fade between the IRs. */ + uint fademix{0u}; + if(Counter && OutPos == 0) + { + fademix = minu(DstBufferSize, Counter); + + float gain{TargetGain}; + + /* The new coefficients need to fade in completely since they're + * replacing the old ones. To keep the gain fading consistent, + * interpolate between the old and new target gains given how much of + * the fade time this mix handles. + */ + if(Counter > fademix) + { + const float a{static_cast(fademix) / static_cast(Counter)}; + gain = lerpf(parms.Hrtf.Old.Gain, TargetGain, a); + } + + MixHrtfFilter hrtfparams{ + parms.Hrtf.Target.Coeffs, + parms.Hrtf.Target.Delay, + 0.0f, gain / static_cast(fademix)}; + MixHrtfBlendSamples(HrtfSamples, AccumSamples+OutPos, IrSize, &parms.Hrtf.Old, &hrtfparams, + fademix); + + /* Update the old parameters with the result. */ + parms.Hrtf.Old = parms.Hrtf.Target; + parms.Hrtf.Old.Gain = gain; + OutPos += fademix; + } + + if(fademix < DstBufferSize) + { + const uint todo{DstBufferSize - fademix}; + float gain{TargetGain}; + + /* Interpolate the target gain if the gain fading lasts longer than + * this mix. + */ + if(Counter > DstBufferSize) + { + const float a{static_cast(todo) / static_cast(Counter-fademix)}; + gain = lerpf(parms.Hrtf.Old.Gain, TargetGain, a); + } + + MixHrtfFilter hrtfparams{ + parms.Hrtf.Target.Coeffs, + parms.Hrtf.Target.Delay, + parms.Hrtf.Old.Gain, + (gain - parms.Hrtf.Old.Gain) / static_cast(todo)}; + MixHrtfSamples(HrtfSamples+fademix, AccumSamples+OutPos, IrSize, &hrtfparams, todo); + + /* Store the now-current gain for next time. */ + parms.Hrtf.Old.Gain = gain; + } +} + +void DoNfcMix(const al::span samples, FloatBufferLine *OutBuffer, DirectParams &parms, + const float *TargetGains, const uint Counter, const uint OutPos, DeviceBase *Device) +{ + using FilterProc = void (NfcFilter::*)(const al::span, float*); + static constexpr FilterProc NfcProcess[MaxAmbiOrder+1]{ + nullptr, &NfcFilter::process1, &NfcFilter::process2, &NfcFilter::process3}; + + float *CurrentGains{parms.Gains.Current.data()}; + MixSamples(samples, {OutBuffer, 1u}, CurrentGains, TargetGains, Counter, OutPos); + ++OutBuffer; + ++CurrentGains; + ++TargetGains; + + const al::span nfcsamples{Device->NfcSampleData, samples.size()}; + size_t order{1}; + while(const size_t chancount{Device->NumChannelsPerOrder[order]}) + { + (parms.NFCtrlFilter.*NfcProcess[order])(samples, nfcsamples.data()); + MixSamples(nfcsamples, {OutBuffer, chancount}, CurrentGains, TargetGains, Counter, OutPos); + OutBuffer += chancount; + CurrentGains += chancount; + TargetGains += chancount; + if(++order == MaxAmbiOrder+1) + break; + } +} + +} // namespace + +void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo) +{ + static constexpr std::array SilentTarget{}; + + ASSUME(SamplesToDo > 0); + + /* Get voice info */ + uint DataPosInt{mPosition.load(std::memory_order_relaxed)}; + uint DataPosFrac{mPositionFrac.load(std::memory_order_relaxed)}; + VoiceBufferItem *BufferListItem{mCurrentBuffer.load(std::memory_order_relaxed)}; + VoiceBufferItem *BufferLoopItem{mLoopBuffer.load(std::memory_order_relaxed)}; + const uint increment{mStep}; + if UNLIKELY(increment < 1) + { + /* If the voice is supposed to be stopping but can't be mixed, just + * stop it before bailing. + */ + if(vstate == Stopping) + mPlayState.store(Stopped, std::memory_order_release); + return; + } + + DeviceBase *Device{Context->mDevice}; + const uint NumSends{Device->NumAuxSends}; + + ResamplerFunc Resample{(increment == MixerFracOne && DataPosFrac == 0) ? + Resample_ : mResampler}; + + uint Counter{mFlags.test(VoiceIsFading) ? SamplesToDo : 0}; + if(!Counter) + { + /* No fading, just overwrite the old/current params. */ + for(auto &chandata : mChans) + { + { + DirectParams &parms = chandata.mDryParams; + if(!mFlags.test(VoiceHasHrtf)) + parms.Gains.Current = parms.Gains.Target; + else + parms.Hrtf.Old = parms.Hrtf.Target; + } + for(uint send{0};send < NumSends;++send) + { + if(mSend[send].Buffer.empty()) + continue; + + SendParams &parms = chandata.mWetParams[send]; + parms.Gains.Current = parms.Gains.Target; + } + } + } + else if UNLIKELY(!BufferListItem) + Counter = std::min(Counter, 64u); + + std::array SamplePointers; + const al::span MixingSamples{SamplePointers.data(), mChans.size()}; + auto offset_bufferline = [](DeviceBase::MixerBufferLine &bufline) noexcept -> float* + { return bufline.data() + MaxResamplerEdge; }; + std::transform(Device->mSampleData.end() - mChans.size(), Device->mSampleData.end(), + MixingSamples.begin(), offset_bufferline); + + const uint PostPadding{MaxResamplerEdge + + (mDecoder ? uint{UhjDecoder::sFilterDelay} : 0u)}; + uint buffers_done{0u}; + uint OutPos{0u}; + do { + /* Figure out how many buffer samples will be needed */ + uint DstBufferSize{SamplesToDo - OutPos}; + uint SrcBufferSize; + + if(increment <= MixerFracOne) + { + /* Calculate the last written dst sample pos. */ + uint64_t DataSize64{DstBufferSize - 1}; + /* Calculate the last read src sample pos. */ + DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits; + /* +1 to get the src sample count, include padding. */ + DataSize64 += 1 + PostPadding; + + /* Result is guaranteed to be <= BufferLineSize+PostPadding since + * we won't use more src samples than dst samples+padding. + */ + SrcBufferSize = static_cast(DataSize64); + } + else + { + uint64_t DataSize64{DstBufferSize}; + /* Calculate the end src sample pos, include padding. */ + DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits; + DataSize64 += PostPadding; + + if(DataSize64 <= DeviceBase::MixerLineSize - MaxResamplerEdge) + SrcBufferSize = static_cast(DataSize64); + else + { + /* If the source size got saturated, we can't fill the desired + * dst size. Figure out how many samples we can actually mix. + */ + SrcBufferSize = DeviceBase::MixerLineSize - MaxResamplerEdge; + + DataSize64 = SrcBufferSize - PostPadding; + DataSize64 = ((DataSize64<(DataSize64) & ~3u; + /* If the voice is stopping, only one mixing iteration will + * be done, so ensure it fades out completely this mix. + */ + if(unlikely(vstate == Stopping)) + Counter = std::min(Counter, DstBufferSize); + } + ASSUME(DstBufferSize > 0); + } + } + + if(unlikely(!BufferListItem)) + { + const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits}; + auto prevSamples = mPrevSamples.data(); + SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerEdge; + for(auto *chanbuffer : MixingSamples) + { + auto srcend = std::copy_n(prevSamples->data(), MaxResamplerPadding, + chanbuffer-MaxResamplerEdge); + + /* When loading from a voice that ended prematurely, only take + * the samples that get closest to 0 amplitude. This helps + * certain sounds fade out better. + */ + auto abs_lt = [](const float lhs, const float rhs) noexcept -> bool + { return std::abs(lhs) < std::abs(rhs); }; + auto srciter = std::min_element(chanbuffer, srcend, abs_lt); + + std::fill(srciter+1, chanbuffer + SrcBufferSize, *srciter); + + std::copy_n(chanbuffer-MaxResamplerEdge+srcOffset, prevSamples->size(), + prevSamples->data()); + ++prevSamples; + } + } + else + { + auto prevSamples = mPrevSamples.data(); + for(auto *chanbuffer : MixingSamples) + { + std::copy_n(prevSamples->data(), MaxResamplerEdge, chanbuffer-MaxResamplerEdge); + ++prevSamples; + } + if(mFlags.test(VoiceIsStatic)) + LoadBufferStatic(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, + mFmtChannels, mFrameStep, SrcBufferSize, MixingSamples); + else if(mFlags.test(VoiceIsCallback)) + { + if(!mFlags.test(VoiceCallbackStopped) && SrcBufferSize > mNumCallbackSamples) + { + const size_t byteOffset{mNumCallbackSamples*mFrameSize}; + const size_t needBytes{SrcBufferSize*mFrameSize - byteOffset}; + + const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData, + &BufferListItem->mSamples[byteOffset], static_cast(needBytes))}; + if(gotBytes < 0) + mFlags.set(VoiceCallbackStopped); + else if(static_cast(gotBytes) < needBytes) + { + mFlags.set(VoiceCallbackStopped); + mNumCallbackSamples += static_cast(gotBytes) / mFrameSize; + } + else + mNumCallbackSamples = SrcBufferSize; + } + LoadBufferCallback(BufferListItem, mNumCallbackSamples, mFmtType, mFmtChannels, + mFrameStep, SrcBufferSize, MixingSamples); + } + else + LoadBufferQueue(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, mFmtChannels, + mFrameStep, SrcBufferSize, MixingSamples); + + const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits}; + if(mDecoder) + { + SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerEdge; + ((*mDecoder).*mDecoderFunc)(MixingSamples, SrcBufferSize, + srcOffset * likely(vstate == Playing)); + } + /* Store the last source samples used for next time. */ + if(likely(vstate == Playing)) + { + prevSamples = mPrevSamples.data(); + for(auto *chanbuffer : MixingSamples) + { + /* Store the last source samples used for next time. */ + std::copy_n(chanbuffer-MaxResamplerEdge+srcOffset, prevSamples->size(), + prevSamples->data()); + ++prevSamples; + } + } + } + + auto voiceSamples = MixingSamples.begin(); + for(auto &chandata : mChans) + { + /* Resample, then apply ambisonic upsampling as needed. */ + float *ResampledData{Resample(&mResampleState, *voiceSamples, DataPosFrac, increment, + {Device->ResampledData, DstBufferSize})}; + ++voiceSamples; + + if(mFlags.test(VoiceIsAmbisonic)) + chandata.mAmbiSplitter.processScale({ResampledData, DstBufferSize}, + chandata.mAmbiHFScale, chandata.mAmbiLFScale); + + /* Now filter and mix to the appropriate outputs. */ + const al::span FilterBuf{Device->FilteredData}; + { + DirectParams &parms = chandata.mDryParams; + const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(), + {ResampledData, DstBufferSize}, mDirect.FilterType)}; + + if(mFlags.test(VoiceHasHrtf)) + { + const float TargetGain{parms.Hrtf.Target.Gain * likely(vstate == Playing)}; + DoHrtfMix(samples, DstBufferSize, parms, TargetGain, Counter, OutPos, + (vstate == Playing), Device); + } + else + { + const float *TargetGains{likely(vstate == Playing) ? parms.Gains.Target.data() + : SilentTarget.data()}; + if(mFlags.test(VoiceHasNfc)) + DoNfcMix({samples, DstBufferSize}, mDirect.Buffer.data(), parms, + TargetGains, Counter, OutPos, Device); + else + MixSamples({samples, DstBufferSize}, mDirect.Buffer, + parms.Gains.Current.data(), TargetGains, Counter, OutPos); + } + } + + for(uint send{0};send < NumSends;++send) + { + if(mSend[send].Buffer.empty()) + continue; + + SendParams &parms = chandata.mWetParams[send]; + const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(), + {ResampledData, DstBufferSize}, mSend[send].FilterType)}; + + const float *TargetGains{likely(vstate == Playing) ? parms.Gains.Target.data() + : SilentTarget.data()}; + MixSamples({samples, DstBufferSize}, mSend[send].Buffer, + parms.Gains.Current.data(), TargetGains, Counter, OutPos); + } + } + /* If the voice is stopping, we're now done. */ + if(unlikely(vstate == Stopping)) + break; + + /* Update positions */ + DataPosFrac += increment*DstBufferSize; + const uint SrcSamplesDone{DataPosFrac>>MixerFracBits}; + DataPosInt += SrcSamplesDone; + DataPosFrac &= MixerFracMask; + + OutPos += DstBufferSize; + Counter = maxu(DstBufferSize, Counter) - DstBufferSize; + + if(unlikely(!BufferListItem)) + { + /* Do nothing extra when there's no buffers. */ + } + else if(mFlags.test(VoiceIsStatic)) + { + if(BufferLoopItem) + { + /* Handle looping static source */ + const uint LoopStart{BufferListItem->mLoopStart}; + const uint LoopEnd{BufferListItem->mLoopEnd}; + if(DataPosInt >= LoopEnd) + { + assert(LoopEnd > LoopStart); + DataPosInt = ((DataPosInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart; + } + } + else + { + /* Handle non-looping static source */ + if(DataPosInt >= BufferListItem->mSampleLen) + { + BufferListItem = nullptr; + break; + } + } + } + else if(mFlags.test(VoiceIsCallback)) + { + /* Handle callback buffer source */ + if(SrcSamplesDone < mNumCallbackSamples) + { + const size_t byteOffset{SrcSamplesDone*mFrameSize}; + const size_t byteEnd{mNumCallbackSamples*mFrameSize}; + al::byte *data{BufferListItem->mSamples}; + std::copy(data+byteOffset, data+byteEnd, data); + mNumCallbackSamples -= SrcSamplesDone; + } + else + { + BufferListItem = nullptr; + mNumCallbackSamples = 0; + } + } + else + { + /* Handle streaming source */ + do { + if(BufferListItem->mSampleLen > DataPosInt) + break; + + DataPosInt -= BufferListItem->mSampleLen; + + ++buffers_done; + BufferListItem = BufferListItem->mNext.load(std::memory_order_relaxed); + if(!BufferListItem) BufferListItem = BufferLoopItem; + } while(BufferListItem); + } + } while(OutPos < SamplesToDo); + + mFlags.set(VoiceIsFading); + + /* Don't update positions and buffers if we were stopping. */ + if(unlikely(vstate == Stopping)) + { + mPlayState.store(Stopped, std::memory_order_release); + return; + } + + /* Capture the source ID in case it's reset for stopping. */ + const uint SourceID{mSourceID.load(std::memory_order_relaxed)}; + + /* Update voice info */ + mPosition.store(DataPosInt, std::memory_order_relaxed); + mPositionFrac.store(DataPosFrac, std::memory_order_relaxed); + mCurrentBuffer.store(BufferListItem, std::memory_order_relaxed); + if(!BufferListItem) + { + mLoopBuffer.store(nullptr, std::memory_order_relaxed); + mSourceID.store(0u, std::memory_order_relaxed); + } + std::atomic_thread_fence(std::memory_order_release); + + /* Send any events now, after the position/buffer info was updated. */ + const uint enabledevt{Context->mEnabledEvts.load(std::memory_order_acquire)}; + if(buffers_done > 0 && (enabledevt&AsyncEvent::BufferCompleted)) + { + RingBuffer *ring{Context->mAsyncEvents.get()}; + auto evt_vec = ring->getWriteVector(); + if(evt_vec.first.len > 0) + { + AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), + AsyncEvent::BufferCompleted)}; + evt->u.bufcomp.id = SourceID; + evt->u.bufcomp.count = buffers_done; + ring->writeAdvance(1); + } + } + + if(!BufferListItem) + { + /* If the voice just ended, set it to Stopping so the next render + * ensures any residual noise fades to 0 amplitude. + */ + mPlayState.store(Stopping, std::memory_order_release); + if((enabledevt&AsyncEvent::SourceStateChange)) + SendSourceStoppedEvent(Context, SourceID); + } +} + +void Voice::prepare(DeviceBase *device) +{ + /* Even if storing really high order ambisonics, we only mix channels for + * orders up to the device order. The rest are simply dropped. + */ + uint num_channels{(mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 3 : + ChannelsFromFmt(mFmtChannels, minu(mAmbiOrder, device->mAmbiOrder))}; + if(unlikely(num_channels > device->mSampleData.size())) + { + ERR("Unexpected channel count: %u (limit: %zu, %d:%d)\n", num_channels, + device->mSampleData.size(), mFmtChannels, mAmbiOrder); + num_channels = static_cast(device->mSampleData.size()); + } + if(mChans.capacity() > 2 && num_channels < mChans.capacity()) + { + decltype(mChans){}.swap(mChans); + decltype(mPrevSamples){}.swap(mPrevSamples); + } + mChans.reserve(maxu(2, num_channels)); + mChans.resize(num_channels); + mPrevSamples.reserve(maxu(2, num_channels)); + mPrevSamples.resize(num_channels); + + if(IsUHJ(mFmtChannels)) + { + mDecoder = std::make_unique(); + mDecoderFunc = (mFmtChannels == FmtSuperStereo) ? &UhjDecoder::decodeStereo + : &UhjDecoder::decode; + } + else + { + mDecoder = nullptr; + mDecoderFunc = nullptr; + } + + /* Clear the stepping value explicitly so the mixer knows not to mix this + * until the update gets applied. + */ + mStep = 0; + + /* Make sure the sample history is cleared. */ + std::fill(mPrevSamples.begin(), mPrevSamples.end(), HistoryLine{}); + + /* Don't need to set the VoiceIsAmbisonic flag if the device is not higher + * order than the voice. No HF scaling is necessary to mix it. + */ + if(mAmbiOrder && device->mAmbiOrder > mAmbiOrder) + { + const uint8_t *OrderFromChan{Is2DAmbisonic(mFmtChannels) ? + AmbiIndex::OrderFrom2DChannel().data() : AmbiIndex::OrderFromChannel().data()}; + const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder); + + const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; + for(auto &chandata : mChans) + { + chandata.mAmbiHFScale = scales[*(OrderFromChan++)]; + chandata.mAmbiLFScale = 1.0f; + chandata.mAmbiSplitter = splitter; + chandata.mDryParams = DirectParams{}; + chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; + std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); + } + /* 2-channel UHJ needs different shelf filters. However, we can't just + * use different shelf filters after mixing it and with any old speaker + * setup the user has. To make this work, we apply the expected shelf + * filters for decoding UHJ2 to quad (only needs LF scaling), and act + * as if those 4 quad channels are encoded right back onto first-order + * B-Format, which then upsamples to higher order as normal (only needs + * HF scaling). + * + * This isn't perfect, but without an entirely separate and limited + * UHJ2 path, it's better than nothing. + */ + if(mFmtChannels == FmtUHJ2) + { + mChans[0].mAmbiLFScale = 0.661f; + mChans[1].mAmbiLFScale = 1.293f; + mChans[2].mAmbiLFScale = 1.293f; + } + mFlags.set(VoiceIsAmbisonic); + } + else if(mFmtChannels == FmtUHJ2 && !device->mUhjEncoder) + { + /* 2-channel UHJ with first-order output also needs the shelf filter + * correction applied, except with UHJ output (UHJ2->B-Format->UHJ2 is + * identity, so don't mess with it). + */ + const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; + for(auto &chandata : mChans) + { + chandata.mAmbiHFScale = 1.0f; + chandata.mAmbiLFScale = 1.0f; + chandata.mAmbiSplitter = splitter; + chandata.mDryParams = DirectParams{}; + chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; + std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); + } + mChans[0].mAmbiLFScale = 0.661f; + mChans[1].mAmbiLFScale = 1.293f; + mChans[2].mAmbiLFScale = 1.293f; + mFlags.set(VoiceIsAmbisonic); + } + else + { + for(auto &chandata : mChans) + { + chandata.mDryParams = DirectParams{}; + chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; + std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); + } + mFlags.reset(VoiceIsAmbisonic); + } +} diff --git a/Engine/lib/openal-soft/Alc/voice.h b/Engine/lib/openal-soft/core/voice.h similarity index 71% rename from Engine/lib/openal-soft/Alc/voice.h rename to Engine/lib/openal-soft/core/voice.h index 4785fb6e1..70b808417 100644 --- a/Engine/lib/openal-soft/Alc/voice.h +++ b/Engine/lib/openal-soft/core/voice.h @@ -1,29 +1,40 @@ -#ifndef VOICE_H -#define VOICE_H +#ifndef CORE_VOICE_H +#define CORE_VOICE_H #include #include +#include +#include +#include +#include +#include "albyte.h" #include "almalloc.h" +#include "aloptional.h" #include "alspan.h" -#include "alu.h" +#include "bufferline.h" #include "buffer_storage.h" -#include "core/bufferline.h" -#include "core/devformat.h" -#include "core/filters/biquad.h" -#include "core/filters/nfc.h" -#include "core/filters/splitter.h" -#include "core/mixer/defs.h" -#include "core/mixer/hrtfdefs.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 ALCcontext; +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, @@ -37,6 +48,12 @@ enum class DirectMode : unsigned char { }; +/* Maximum number of extra source samples that may need to be loaded, for + * resampling or conversion purposes. + */ +constexpr uint MaxPostVoiceLoad{MaxResamplerEdge + UhjDecoder::sFilterDelay}; + + enum { AF_None = 0, AF_LowPass = 1, @@ -122,6 +139,7 @@ struct VoiceProps { std::array StereoPan; float Radius; + float EnhWidth; /** Direct filter and auxiliary send info. */ struct { @@ -147,13 +165,17 @@ struct VoicePropsItem : public VoiceProps { 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}; +enum : uint { + VoiceIsStatic, + VoiceIsCallback, + VoiceIsAmbisonic, + VoiceCallbackStopped, + VoiceIsFading, + VoiceHasHrtf, + VoiceHasNfc, + + VoiceFlagCount +}; struct Voice { enum State { @@ -191,11 +213,15 @@ struct Voice { FmtChannels mFmtChannels; FmtType mFmtType; uint mFrequency; - uint mSampleSize; + uint mFrameStep; /**< In steps of the sample type size. */ + uint mFrameSize; /**< In bytes. */ AmbiLayout mAmbiLayout; AmbiScaling mAmbiScaling; uint mAmbiOrder; + std::unique_ptr mDecoder; + UhjDecoder::DecoderFunc mDecoderFunc{}; + /** Current target parameters used for mixing. */ uint mStep{0}; @@ -203,7 +229,7 @@ struct Voice { InterpState mResampleState; - uint mFlags{}; + std::bitset mFlags{}; uint mNumCallbackSamples{0}; struct TargetData { @@ -213,10 +239,16 @@ struct Voice { TargetData mDirect; std::array mSend; - struct ChannelData { - alignas(16) std::array mPrevSamples; + /* The first MaxResamplerPadding/2 elements are the sample history from the + * previous mix, with an additional MaxResamplerPadding/2 elements that are + * now current (which may be overwritten if the buffer data is still + * available). + */ + using HistoryLine = std::array; + al::vector mPrevSamples{2}; - float mAmbiScale; + struct ChannelData { + float mAmbiHFScale, mAmbiLFScale; BandSplitter mAmbiSplitter; DirectParams mDryParams; @@ -225,16 +257,20 @@ struct Voice { al::vector mChans{2}; Voice() = default; - ~Voice() { delete mUpdate.exchange(nullptr, std::memory_order_acq_rel); } + ~Voice() = default; Voice(const Voice&) = delete; Voice& operator=(const Voice&) = delete; - void mix(const State vstate, ALCcontext *Context, const uint SamplesToDo); + 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 /* VOICE_H */ +#endif /* CORE_VOICE_H */ diff --git a/Engine/lib/openal-soft/Alc/voice_change.h b/Engine/lib/openal-soft/core/voice_change.h similarity index 100% rename from Engine/lib/openal-soft/Alc/voice_change.h rename to Engine/lib/openal-soft/core/voice_change.h diff --git a/Engine/lib/openal-soft/docs/env-vars.txt b/Engine/lib/openal-soft/docs/env-vars.txt index fee9ffb08..77a30c589 100644 --- a/Engine/lib/openal-soft/docs/env-vars.txt +++ b/Engine/lib/openal-soft/docs/env-vars.txt @@ -64,8 +64,13 @@ to it before passing in 3D coordinates. Depending on how exactly this is done, it can cause correct output for stereo but incorrect Z panning for surround sound (i.e., sounds that are supposed to be behind you sound like they're in front, and vice-versa). Setting this to "true" or "1" will negate the localized -Z coordinate to attempt to fix output for apps that have incorrect front/back -panning. +Z coordinate to flip front/back panning for 3D sources. + +__ALSOFT_REVERSE_Y +Same as for __ALSOFT_REVERSE_Z, but for Y (up/down) panning. + +__ALSOFT_REVERSE_X +Same as for __ALSOFT_REVERSE_Z, but for X (left/right) panning. __ALSOFT_SUSPEND_CONTEXT Due to the OpenAL spec not being very clear about them, behavior of the diff --git a/Engine/lib/openal-soft/examples/alconvolve.c b/Engine/lib/openal-soft/examples/alconvolve.c index d719abdb5..93fd2eb4a 100644 --- a/Engine/lib/openal-soft/examples/alconvolve.c +++ b/Engine/lib/openal-soft/examples/alconvolve.c @@ -438,7 +438,7 @@ int main(int argc, char **argv) } /* Define a macro to help load the function pointers. */ -#define LOAD_PROC(T, x) ((x) = (T)alGetProcAddress(#x)) +#define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) LOAD_PROC(LPALGENFILTERS, alGenFilters); LOAD_PROC(LPALDELETEFILTERS, alDeleteFilters); LOAD_PROC(LPALISFILTER, alIsFilter); diff --git a/Engine/lib/openal-soft/examples/alffplay.cpp b/Engine/lib/openal-soft/examples/alffplay.cpp index 6886a42da..c3f4c5058 100644 --- a/Engine/lib/openal-soft/examples/alffplay.cpp +++ b/Engine/lib/openal-soft/examples/alffplay.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -67,27 +68,6 @@ _Pragma("GCC diagnostic pop") #include "common/alhelpers.h" -extern "C" { -/* Undefine this to disable use of experimental extensions. Don't use for - * production code! Interfaces and behavior may change prior to being - * finalized. - */ -#define ALLOW_EXPERIMENTAL_EXTS - -#ifdef ALLOW_EXPERIMENTAL_EXTS -#ifndef AL_SOFT_callback_buffer -#define AL_SOFT_callback_buffer -typedef unsigned int ALbitfieldSOFT; -#define AL_BUFFER_CALLBACK_FUNCTION_SOFT 0x19A0 -#define AL_BUFFER_CALLBACK_USER_PARAM_SOFT 0x19A1 -typedef ALsizei (AL_APIENTRY*LPALBUFFERCALLBACKTYPESOFT)(ALvoid *userptr, ALvoid *sampledata, ALsizei numsamples); -typedef void (AL_APIENTRY*LPALBUFFERCALLBACKSOFT)(ALuint buffer, ALenum format, ALsizei freq, LPALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr, ALbitfieldSOFT flags); -typedef void (AL_APIENTRY*LPALGETBUFFERPTRSOFT)(ALuint buffer, ALenum param, ALvoid **value); -typedef void (AL_APIENTRY*LPALGETBUFFER3PTRSOFT)(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3); -typedef void (AL_APIENTRY*LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid **values); -#endif -#endif /* ALLOW_EXPERIMENTAL_EXTS */ -} namespace { @@ -109,18 +89,17 @@ const std::string AppName{"alffplay"}; ALenum DirectOutMode{AL_FALSE}; bool EnableWideStereo{false}; +bool EnableSuperStereo{false}; bool DisableVideo{false}; LPALGETSOURCEI64VSOFT alGetSourcei64vSOFT; LPALCGETINTEGER64VSOFT alcGetInteger64vSOFT; - -#ifdef AL_SOFT_events LPALEVENTCONTROLSOFT alEventControlSOFT; LPALEVENTCALLBACKSOFT alEventCallbackSOFT; -#endif -#ifdef AL_SOFT_callback_buffer LPALBUFFERCALLBACKSOFT alBufferCallbackSOFT; -#endif +ALenum FormatStereo8{AL_FORMAT_STEREO8}; +ALenum FormatStereo16{AL_FORMAT_STEREO16}; +ALenum FormatStereo32F{AL_FORMAT_STEREO_FLOAT32}; const seconds AVNoSyncThreshold{10}; @@ -146,7 +125,7 @@ enum class SyncMaster { Video, External, - Default = External + Default = Audio }; @@ -169,6 +148,11 @@ struct AVCodecCtxDeleter { }; using AVCodecCtxPtr = std::unique_ptr; +struct AVPacketDeleter { + void operator()(AVPacket *pkt) { av_packet_free(&pkt); } +}; +using AVPacketPtr = std::unique_ptr; + struct AVFrameDeleter { void operator()(AVFrame *ptr) { av_frame_free(&ptr); } }; @@ -186,80 +170,103 @@ using SwsContextPtr = std::unique_ptr; template -class PacketQueue { - std::mutex mMutex; - std::condition_variable mCondVar; - std::deque mPackets; +class DataQueue { + std::mutex mPacketMutex, mFrameMutex; + std::condition_variable mPacketCond; + std::condition_variable mInFrameCond, mOutFrameCond; + + std::deque mPackets; size_t mTotalSize{0}; bool mFinished{false}; - AVPacket *getPacket(std::unique_lock &lock) + AVPacketPtr getPacket() { + std::unique_lock plock{mPacketMutex}; while(mPackets.empty() && !mFinished) - mCondVar.wait(lock); - return mPackets.empty() ? nullptr : &mPackets.front(); - } + mPacketCond.wait(plock); + if(mPackets.empty()) + return nullptr; - void pop() - { - AVPacket *pkt = &mPackets.front(); - mTotalSize -= static_cast(pkt->size); - av_packet_unref(pkt); + auto ret = std::move(mPackets.front()); mPackets.pop_front(); + mTotalSize -= static_cast(ret->size); + return ret; } public: - ~PacketQueue() + int sendPacket(AVCodecContext *codecctx) { - for(AVPacket &pkt : mPackets) - av_packet_unref(&pkt); - mPackets.clear(); - mTotalSize = 0; + AVPacketPtr packet{getPacket()}; + + int ret{}; + { + std::unique_lock flock{mFrameMutex}; + while((ret=avcodec_send_packet(codecctx, packet.get())) == AVERROR(EAGAIN)) + mInFrameCond.wait_for(flock, milliseconds{50}); + } + mOutFrameCond.notify_one(); + + if(!packet) + { + if(!ret) return AVErrorEOF; + std::cerr<< "Failed to send flush packet: "< lock{mMutex}; - - AVPacket *pkt{getPacket(lock)}; - if(!pkt) return avcodec_send_packet(codecctx, nullptr); - - const int ret{avcodec_send_packet(codecctx, pkt)}; - if(ret != AVERROR(EAGAIN)) + int ret{}; { - if(ret < 0) - std::cerr<< "Failed to send packet: "< flock{mFrameMutex}; + while((ret=avcodec_receive_frame(codecctx, frame)) == AVERROR(EAGAIN)) + mOutFrameCond.wait_for(flock, milliseconds{50}); } + mInFrameCond.notify_one(); return ret; } void setFinished() { { - std::lock_guard _{mMutex}; + std::lock_guard _{mPacketMutex}; mFinished = true; } - mCondVar.notify_one(); + mPacketCond.notify_one(); + } + + void flush() + { + { + std::lock_guard _{mPacketMutex}; + mFinished = true; + + mPackets.clear(); + mTotalSize = 0; + } + mPacketCond.notify_one(); } bool put(const AVPacket *pkt) { { - std::unique_lock lock{mMutex}; - if(mTotalSize >= SizeLimit) + std::unique_lock lock{mPacketMutex}; + if(mTotalSize >= SizeLimit || mFinished) return false; - mPackets.push_back(AVPacket{}); - if(av_packet_ref(&mPackets.back(), pkt) != 0) + mPackets.push_back(AVPacketPtr{av_packet_alloc()}); + if(av_packet_ref(mPackets.back().get(), pkt) != 0) { mPackets.pop_back(); return true; } - mTotalSize += static_cast(mPackets.back().size); + mTotalSize += static_cast(mPackets.back()->size); } - mCondVar.notify_one(); + mPacketCond.notify_one(); return true; } }; @@ -273,7 +280,7 @@ struct AudioState { AVStream *mStream{nullptr}; AVCodecCtxPtr mCodecCtx; - PacketQueue<2*1024*1024> mPackets; + DataQueue<2*1024*1024> mQueue; /* Used for clock difference average computation */ seconds_d64 mClockDiffAvg{0}; @@ -326,15 +333,15 @@ struct AudioState { av_freep(&mSamples); } -#ifdef AL_SOFT_events - static void AL_APIENTRY EventCallback(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message, void *userParam); -#endif -#ifdef AL_SOFT_callback_buffer + static void AL_APIENTRY eventCallbackC(ALenum eventType, ALuint object, ALuint param, + ALsizei length, const ALchar *message, void *userParam) + { static_cast(userParam)->eventCallback(eventType, object, param, length, message); } + void eventCallback(ALenum eventType, ALuint object, ALuint param, ALsizei length, + const ALchar *message); + static ALsizei AL_APIENTRY bufferCallbackC(void *userptr, void *data, ALsizei size) { return static_cast(userptr)->bufferCallback(data, size); } ALsizei bufferCallback(void *data, ALsizei size); -#endif nanoseconds getClockNoLock(); nanoseconds getClock() @@ -348,7 +355,7 @@ struct AudioState { int getSync(); int decodeFrame(); bool readAudio(uint8_t *samples, unsigned int length, int &sample_skip); - void readAudio(int sample_skip); + bool readAudio(int sample_skip); int handler(); }; @@ -359,7 +366,7 @@ struct VideoState { AVStream *mStream{nullptr}; AVCodecCtxPtr mCodecCtx; - PacketQueue<14*1024*1024> mPackets; + DataQueue<14*1024*1024> mQueue; /* The pts of the currently displayed frame, and the time (av_gettime) it * was last updated - used to have running video pts @@ -381,7 +388,7 @@ struct VideoState { std::condition_variable mPictQCond; SDL_Texture *mImage{nullptr}; - int mWidth{0}, mHeight{0}; /* Logical image size (actual size may be larger) */ + int mWidth{0}, mHeight{0}; /* Full texture size */ bool mFirstUpdate{true}; std::atomic mEOS{false}; @@ -397,7 +404,7 @@ struct VideoState { nanoseconds getClock(); - void display(SDL_Window *screen, SDL_Renderer *renderer); + void display(SDL_Window *screen, SDL_Renderer *renderer, AVFrame *frame); void updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool redraw); int handler(); }; @@ -415,6 +422,10 @@ struct MovieState { AudioState mAudio; VideoState mVideo; + std::mutex mStartupMutex; + std::condition_variable mStartupCond; + bool mStartupDone{false}; + std::thread mParseThread; std::thread mAudioThread; std::thread mVideoThread; @@ -426,7 +437,7 @@ struct MovieState { { } ~MovieState() { - mQuit = true; + stop(); if(mParseThread.joinable()) mParseThread.join(); } @@ -434,6 +445,7 @@ struct MovieState { static int decode_interrupt_cb(void *ctx); bool prepare(); void setTitle(SDL_Window *window); + void stop(); nanoseconds getClock(); @@ -648,42 +660,32 @@ int AudioState::getSync() int AudioState::decodeFrame() { - while(!mMovie.mQuit.load(std::memory_order_relaxed)) - { - int ret; - while((ret=avcodec_receive_frame(mCodecCtx.get(), mDecodedFrame.get())) == AVERROR(EAGAIN)) - mPackets.sendTo(mCodecCtx.get()); - if(ret != 0) + do { + while(int ret{mQueue.receiveFrame(mCodecCtx.get(), mDecodedFrame.get())}) { - if(ret == AVErrorEOF) break; + if(ret == AVErrorEOF) return 0; std::cerr<< "Failed to receive frame: "<nb_samples <= 0); - if(mDecodedFrame->nb_samples <= 0) - continue; + /* If provided, update w/ pts */ + if(mDecodedFrame->best_effort_timestamp != AVNoPtsValue) + mCurrentPts = duration_cast(seconds_d64{av_q2d(mStream->time_base) * + static_cast(mDecodedFrame->best_effort_timestamp)}); - /* If provided, update w/ pts */ - if(mDecodedFrame->best_effort_timestamp != AVNoPtsValue) - mCurrentPts = duration_cast(seconds_d64{av_q2d(mStream->time_base) * - static_cast(mDecodedFrame->best_effort_timestamp)}); - - if(mDecodedFrame->nb_samples > mSamplesMax) - { - av_freep(&mSamples); - av_samples_alloc(&mSamples, nullptr, mCodecCtx->channels, mDecodedFrame->nb_samples, - mDstSampleFmt, 0); - mSamplesMax = mDecodedFrame->nb_samples; - } - /* Return the amount of sample frames converted */ - int data_size{swr_convert(mSwresCtx.get(), &mSamples, mDecodedFrame->nb_samples, - const_cast(mDecodedFrame->data), mDecodedFrame->nb_samples)}; - - av_frame_unref(mDecodedFrame.get()); - return data_size; + if(mDecodedFrame->nb_samples > mSamplesMax) + { + av_freep(&mSamples); + av_samples_alloc(&mSamples, nullptr, mCodecCtx->channels, mDecodedFrame->nb_samples, + mDstSampleFmt, 0); + mSamplesMax = mDecodedFrame->nb_samples; } + /* Return the amount of sample frames converted */ + int data_size{swr_convert(mSwresCtx.get(), &mSamples, mDecodedFrame->nb_samples, + const_cast(mDecodedFrame->data), mDecodedFrame->nb_samples)}; - return 0; + av_frame_unref(mDecodedFrame.get()); + return data_size; } /* Duplicates the sample at in to out, count times. The frame size is a @@ -752,11 +754,10 @@ bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_sk while(mSamplesPos >= mSamplesLen) { - int frame_len = decodeFrame(); - if(frame_len <= 0) break; - - mSamplesLen = frame_len; + mSamplesLen = decodeFrame(); mSamplesPos = std::min(mSamplesLen, sample_skip); + if(mSamplesLen <= 0) break; + sample_skip -= mSamplesPos; // Adjust the device start time and current pts by the amount we're @@ -777,12 +778,11 @@ bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_sk std::fill_n(samples, rem*mFrameSize, (mDstSampleFmt == AV_SAMPLE_FMT_U8) ? 0x80 : 0x00); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; - audio_size += rem; } return true; } -void AudioState::readAudio(int sample_skip) +bool AudioState::readAudio(int sample_skip) { size_t woffset{mWritePos.load(std::memory_order_acquire)}; while(mSamplesLen > 0) @@ -851,9 +851,9 @@ void AudioState::readAudio(int sample_skip) do { mSamplesLen = decodeFrame(); - if(mSamplesLen <= 0) break; - mSamplesPos = std::min(mSamplesLen, sample_skip); + if(mSamplesLen <= 0) return false; + sample_skip -= mSamplesPos; auto skip = nanoseconds{seconds{mSamplesPos}} / mCodecCtx->sample_rate; @@ -861,26 +861,25 @@ void AudioState::readAudio(int sample_skip) mCurrentPts += skip; } while(mSamplesPos >= mSamplesLen); } + + return true; } -#ifdef AL_SOFT_events -void AL_APIENTRY AudioState::EventCallback(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message, void *userParam) +void AL_APIENTRY AudioState::eventCallback(ALenum eventType, ALuint object, ALuint param, + ALsizei length, const ALchar *message) { - auto self = static_cast(userParam); - if(eventType == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT) { /* Temporarily lock the source mutex to ensure it's not between * checking the processed count and going to sleep. */ - std::unique_lock{self->mSrcMutex}.unlock(); - self->mSrcCond.notify_one(); + std::unique_lock{mSrcMutex}.unlock(); + mSrcCond.notify_one(); return; } - std::cout<< "\n---- AL Event on AudioState "< lock{self->mSrcMutex}; - self->mConnected.clear(std::memory_order_release); + std::lock_guard lock{mSrcMutex}; + mConnected.clear(std::memory_order_release); } - self->mSrcCond.notify_one(); + mSrcCond.notify_one(); } } -#endif -#ifdef AL_SOFT_callback_buffer ALsizei AudioState::bufferCallback(void *data, ALsizei size) { ALsizei got{0}; @@ -933,55 +930,81 @@ ALsizei AudioState::bufferCallback(void *data, ALsizei size) return got; } -#endif int AudioState::handler() { std::unique_lock srclock{mSrcMutex, std::defer_lock}; milliseconds sleep_time{AudioBufferTime / 3}; - ALenum fmt; -#ifdef AL_SOFT_events - const std::array evt_types{{ - AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, - AL_EVENT_TYPE_DISCONNECTED_SOFT}}; - if(alEventControlSOFT) - { - alEventControlSOFT(evt_types.size(), evt_types.data(), AL_TRUE); - alEventCallbackSOFT(EventCallback, this); - sleep_time = AudioBufferTotalTime; - } -#endif -#ifdef AL_SOFT_bformat_ex + struct EventControlManager { + const std::array evt_types{{ + AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, + AL_EVENT_TYPE_DISCONNECTED_SOFT}}; + + EventControlManager(milliseconds &sleep_time) + { + if(alEventControlSOFT) + { + alEventControlSOFT(static_cast(evt_types.size()), evt_types.data(), + AL_TRUE); + alEventCallbackSOFT(&AudioState::eventCallbackC, this); + sleep_time = AudioBufferTotalTime; + } + } + ~EventControlManager() + { + if(alEventControlSOFT) + { + alEventControlSOFT(static_cast(evt_types.size()), evt_types.data(), + AL_FALSE); + alEventCallbackSOFT(nullptr, nullptr); + } + } + }; + EventControlManager event_controller{sleep_time}; + const bool has_bfmt_ex{alIsExtensionPresent("AL_SOFT_bformat_ex") != AL_FALSE}; ALenum ambi_layout{AL_FUMA_SOFT}; ALenum ambi_scale{AL_FUMA_SOFT}; -#endif + + std::unique_ptr samples; + ALsizei buffer_len{0}; /* Find a suitable format for OpenAL. */ mDstChanLayout = 0; mFormat = AL_NONE; - if((mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP) && - alIsExtensionPresent("AL_EXT_FLOAT32")) + if((mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP + || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_DBL + || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_DBLP + || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S32 + || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S32P + || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S64 + || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S64P) + && alIsExtensionPresent("AL_EXT_FLOAT32")) { mDstSampleFmt = AV_SAMPLE_FMT_FLT; mFrameSize = 4; - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1 && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_71CHN32")) != AL_NONE && fmt != -1) + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 8; - mFormat = fmt; - } - if((mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 || - mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_51CHN32")) != AL_NONE && fmt != -1) - { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 6; - mFormat = fmt; + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 8; + mFormat = alGetEnumValue("AL_FORMAT_71CHN32"); + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 + || mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 6; + mFormat = alGetEnumValue("AL_FORMAT_51CHN32"); + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_QUAD) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 4; + mFormat = alGetEnumValue("AL_FORMAT_QUAD32"); + } } if(mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) { @@ -994,48 +1017,57 @@ int AudioState::handler() * have no way to specify if the source is actually B-Format (let alone * if it's 2D or 3D). */ - if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 && - alIsExtensionPresent("AL_EXT_BFORMAT") && - (fmt=alGetEnumValue("AL_FORMAT_BFORMAT3D_FLOAT32")) != AL_NONE && fmt != -1) + if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 + && alIsExtensionPresent("AL_EXT_BFORMAT")) { - int order{static_cast(std::sqrt(mCodecCtx->channels)) - 1}; - if((order+1)*(order+1) == mCodecCtx->channels || - (order+1)*(order+1) + 2 == mCodecCtx->channels) + /* Calculate what should be the ambisonic order from the number of + * channels, and confirm that's the number of channels. Opus allows + * an optional non-diegetic stereo stream with the B-Format stream, + * which we can ignore, so check for that too. + */ + auto order = static_cast(std::sqrt(mCodecCtx->channels)) - 1; + int channels{(order+1) * (order+1)}; + if(channels == mCodecCtx->channels || channels+2 == mCodecCtx->channels) { /* OpenAL only supports first-order with AL_EXT_BFORMAT, which * is 4 channels for 3D buffers. */ mFrameSize *= 4; - mFormat = fmt; + mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_FLOAT32"); } } - if(!mFormat) + if(!mFormat || mFormat == -1) { mDstChanLayout = AV_CH_LAYOUT_STEREO; mFrameSize *= 2; - mFormat = AL_FORMAT_STEREO_FLOAT32; + mFormat = FormatStereo32F; } } if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8 || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P) { mDstSampleFmt = AV_SAMPLE_FMT_U8; mFrameSize = 1; - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1 && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_71CHN8")) != AL_NONE && fmt != -1) + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 8; - mFormat = fmt; - } - if((mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 || - mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_51CHN8")) != AL_NONE && fmt != -1) - { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 6; - mFormat = fmt; + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 8; + mFormat = alGetEnumValue("AL_FORMAT_71CHN8"); + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 + || mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 6; + mFormat = alGetEnumValue("AL_FORMAT_51CHN8"); + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_QUAD) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 4; + mFormat = alGetEnumValue("AL_FORMAT_QUAD8"); + } } if(mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) { @@ -1043,45 +1075,49 @@ int AudioState::handler() mFrameSize *= 1; mFormat = AL_FORMAT_MONO8; } - if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 && - alIsExtensionPresent("AL_EXT_BFORMAT") && - (fmt=alGetEnumValue("AL_FORMAT_BFORMAT3D8")) != AL_NONE && fmt != -1) + if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 + && alIsExtensionPresent("AL_EXT_BFORMAT")) { - int order{static_cast(std::sqrt(mCodecCtx->channels)) - 1}; - if((order+1)*(order+1) == mCodecCtx->channels || - (order+1)*(order+1) + 2 == mCodecCtx->channels) + auto order = static_cast(std::sqrt(mCodecCtx->channels)) - 1; + int channels{(order+1) * (order+1)}; + if(channels == mCodecCtx->channels || channels+2 == mCodecCtx->channels) { mFrameSize *= 4; - mFormat = fmt; + mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_8"); } } - if(!mFormat) + if(!mFormat || mFormat == -1) { mDstChanLayout = AV_CH_LAYOUT_STEREO; mFrameSize *= 2; - mFormat = AL_FORMAT_STEREO8; + mFormat = FormatStereo8; } } - if(!mFormat) + if(!mFormat || mFormat == -1) { mDstSampleFmt = AV_SAMPLE_FMT_S16; mFrameSize = 2; - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1 && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_71CHN16")) != AL_NONE && fmt != -1) + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 8; - mFormat = fmt; - } - if((mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 || - mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_51CHN16")) != AL_NONE && fmt != -1) - { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 6; - mFormat = fmt; + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 8; + mFormat = alGetEnumValue("AL_FORMAT_71CHN16"); + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 + || mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 6; + mFormat = alGetEnumValue("AL_FORMAT_51CHN16"); + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_QUAD) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 4; + mFormat = alGetEnumValue("AL_FORMAT_QUAD16"); + } } if(mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) { @@ -1089,27 +1125,24 @@ int AudioState::handler() mFrameSize *= 1; mFormat = AL_FORMAT_MONO16; } - if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 && - alIsExtensionPresent("AL_EXT_BFORMAT") && - (fmt=alGetEnumValue("AL_FORMAT_BFORMAT3D16")) != AL_NONE && fmt != -1) + if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 + && alIsExtensionPresent("AL_EXT_BFORMAT")) { - int order{static_cast(std::sqrt(mCodecCtx->channels)) - 1}; - if((order+1)*(order+1) == mCodecCtx->channels || - (order+1)*(order+1) + 2 == mCodecCtx->channels) + auto order = static_cast(std::sqrt(mCodecCtx->channels)) - 1; + int channels{(order+1) * (order+1)}; + if(channels == mCodecCtx->channels || channels+2 == mCodecCtx->channels) { mFrameSize *= 4; - mFormat = fmt; + mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_16"); } } - if(!mFormat) + if(!mFormat || mFormat == -1) { mDstChanLayout = AV_CH_LAYOUT_STEREO; mFrameSize *= 2; - mFormat = AL_FORMAT_STEREO16; + mFormat = FormatStereo16; } } - void *samples{nullptr}; - ALsizei buffer_len{0}; mSamples = nullptr; mSamplesMax = 0; @@ -1120,7 +1153,7 @@ int AudioState::handler() if(!mDecodedFrame) { std::cerr<< "Failed to allocate audio frame" < mtx(64*64, 0.0); -#ifdef AL_SOFT_bformat_ex ambi_layout = AL_ACN_SOFT; ambi_scale = AL_SN3D_SOFT; if(has_bfmt_ex) @@ -1151,7 +1183,6 @@ int AudioState::handler() mtx[3 + 3*64] = 1.0; } else -#endif { std::cout<< "Found AL_EXT_BFORMAT" <(mBuffers.size()), mBuffers.data()); @@ -1188,7 +1219,6 @@ int AudioState::handler() const float angles[2]{static_cast(M_PI / 3.0), static_cast(-M_PI / 3.0)}; alSourcefv(mSource, AL_STEREO_ANGLES, angles); } -#ifdef AL_SOFT_bformat_ex if(has_bfmt_ex) { for(ALuint bufid : mBuffers) @@ -1197,50 +1227,56 @@ int AudioState::handler() alBufferi(bufid, AL_AMBISONIC_SCALING_SOFT, ambi_scale); } } +#ifdef AL_SOFT_UHJ + if(EnableSuperStereo) + alSourcei(mSource, AL_STEREO_MODE_SOFT, AL_SUPER_STEREO_SOFT); #endif if(alGetError() != AL_NO_ERROR) - goto finish; + return 0; -#ifdef AL_SOFT_callback_buffer + bool callback_ok{false}; if(alBufferCallbackSOFT) { - alBufferCallbackSOFT(mBuffers[0], mFormat, mCodecCtx->sample_rate, bufferCallbackC, this, - 0); + alBufferCallbackSOFT(mBuffers[0], mFormat, mCodecCtx->sample_rate, bufferCallbackC, this); alSourcei(mSource, AL_BUFFER, static_cast(mBuffers[0])); if(alGetError() != AL_NO_ERROR) { fprintf(stderr, "Failed to set buffer callback\n"); alSourcei(mSource, AL_BUFFER, 0); - buffer_len = static_cast(duration_cast(mCodecCtx->sample_rate * - AudioBufferTime).count() * mFrameSize); } else { mBufferDataSize = static_cast(duration_cast(mCodecCtx->sample_rate * AudioBufferTotalTime).count()) * mFrameSize; - mBufferData.reset(new uint8_t[mBufferDataSize]); + mBufferData = std::make_unique(mBufferDataSize); + std::fill_n(mBufferData.get(), mBufferDataSize, uint8_t{}); + mReadPos.store(0, std::memory_order_relaxed); - mWritePos.store(0, std::memory_order_relaxed); + mWritePos.store(mBufferDataSize/mFrameSize/2*mFrameSize, std::memory_order_relaxed); ALCint refresh{}; alcGetIntegerv(alcGetContextsDevice(alcGetCurrentContext()), ALC_REFRESH, 1, &refresh); sleep_time = milliseconds{seconds{1}} / refresh; + callback_ok = true; } } - else -#endif + if(!callback_ok) buffer_len = static_cast(duration_cast(mCodecCtx->sample_rate * AudioBufferTime).count() * mFrameSize); if(buffer_len > 0) - samples = av_malloc(static_cast(buffer_len)); + samples = std::make_unique(static_cast(buffer_len)); /* Prefill the codec buffer. */ - do { - const int ret{mPackets.sendTo(mCodecCtx.get())}; - if(ret == AVERROR(EAGAIN) || ret == AVErrorEOF) - break; - } while(1); + auto packet_sender = [this]() + { + while(1) + { + const int ret{mQueue.sendPacket(mCodecCtx.get())}; + if(ret == AVErrorEOF) break; + } + }; + auto sender = std::async(std::launch::async, packet_sender); srclock.lock(); if(alcGetInteger64vSOFT) @@ -1261,14 +1297,21 @@ int AudioState::handler() mCurrentPts += skip; } - while(!mMovie.mQuit.load(std::memory_order_relaxed) - && mConnected.test_and_set(std::memory_order_relaxed)) + while(1) { ALenum state; if(mBufferDataSize > 0) { alGetSourcei(mSource, AL_SOURCE_STATE, &state); - readAudio(getSync()); + /* If mQuit is set, don't actually quit until we can't get more + * audio, indicating we've reached the flush packet and the packet + * sender will also quit. + * + * If mQuit is not set, don't quit even if there's no more audio, + * so what's buffered has a chance to play to the real end. + */ + if(!readAudio(getSync()) && mMovie.mQuit.load(std::memory_order_relaxed)) + goto finish; } else { @@ -1291,14 +1334,19 @@ int AudioState::handler() /* Read the next chunk of data, filling the buffer, and queue * it on the source. */ - const bool got_audio{readAudio(static_cast(samples), - static_cast(buffer_len), sync_skip)}; - if(!got_audio) break; + const bool got_audio{readAudio(samples.get(), static_cast(buffer_len), + sync_skip)}; + if(!got_audio) + { + if(mMovie.mQuit.load(std::memory_order_relaxed)) + goto finish; + break; + } const ALuint bufid{mBuffers[mBufferIdx]}; mBufferIdx = static_cast((mBufferIdx+1) % mBuffers.size()); - alBufferData(bufid, mFormat, samples, buffer_len, mCodecCtx->sample_rate); + alBufferData(bufid, mFormat, samples.get(), buffer_len, mCodecCtx->sample_rate); alSourceQueueBuffers(mSource, 1, &bufid); ++queued; } @@ -1333,27 +1381,18 @@ int AudioState::handler() if(!startPlayback()) break; } - if(alGetError() != AL_NO_ERROR) - return false; + if(ALenum err{alGetError()}) + std::cerr<< "Got AL error: 0x"<sample_aspect_ratio.num == 0) + int frame_width{frame->width - static_cast(frame->crop_left + frame->crop_right)}; + int frame_height{frame->height - static_cast(frame->crop_top + frame->crop_bottom)}; + if(frame->sample_aspect_ratio.num == 0) aspect_ratio = 0.0; else { - aspect_ratio = av_q2d(mCodecCtx->sample_aspect_ratio) * mCodecCtx->width / - mCodecCtx->height; + aspect_ratio = av_q2d(frame->sample_aspect_ratio) * frame_width / + frame_height; } if(aspect_ratio <= 0.0) - aspect_ratio = static_cast(mCodecCtx->width) / mCodecCtx->height; + aspect_ratio = static_cast(frame_width) / frame_height; SDL_GetWindowSize(screen, &win_w, &win_h); h = win_h; @@ -1399,7 +1440,8 @@ void VideoState::display(SDL_Window *screen, SDL_Renderer *renderer) x = (win_w - w) / 2; y = (win_h - h) / 2; - SDL_Rect src_rect{ 0, 0, mWidth, mHeight }; + SDL_Rect src_rect{ static_cast(frame->crop_left), static_cast(frame->crop_top), + frame_width, frame_height }; SDL_Rect dst_rect{ x, y, w, h }; SDL_RenderCopy(renderer, mImage, &src_rect, &dst_rect); SDL_RenderPresent(renderer); @@ -1422,8 +1464,12 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re if(next_idx == mPictQWrite.load(std::memory_order_acquire)) break; Picture *nextvp{&mPictQ[next_idx]}; - if(clocktime < nextvp->mPts) - break; + if(clocktime < nextvp->mPts && !mMovie.mQuit.load(std::memory_order_relaxed)) + { + /* For the first update, ensure the first frame gets shown. */ + if(!mFirstUpdate || updated) + break; + } vp = nextvp; updated = true; @@ -1439,6 +1485,7 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re return; } + AVFrame *frame{vp->mFrame.get()}; if(updated) { mPictQRead.store(read_idx, std::memory_order_release); @@ -1447,40 +1494,39 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re /* allocate or resize the buffer! */ bool fmt_updated{false}; - if(!mImage || mWidth != mCodecCtx->width || mHeight != mCodecCtx->height) + if(!mImage || mWidth != frame->width || mHeight != frame->height) { fmt_updated = true; if(mImage) SDL_DestroyTexture(mImage); mImage = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, - mCodecCtx->coded_width, mCodecCtx->coded_height); + frame->width, frame->height); if(!mImage) std::cerr<< "Failed to create YV12 texture!" <width; - mHeight = mCodecCtx->height; + mWidth = frame->width; + mHeight = frame->height; + } - if(mFirstUpdate && mWidth > 0 && mHeight > 0) + int frame_width{frame->width - static_cast(frame->crop_left + frame->crop_right)}; + int frame_height{frame->height - static_cast(frame->crop_top + frame->crop_bottom)}; + if(mFirstUpdate && frame_width > 0 && frame_height > 0) + { + /* For the first update, set the window size to the video size. */ + mFirstUpdate = false; + + if(frame->sample_aspect_ratio.den != 0) { - /* For the first update, set the window size to the video size. */ - mFirstUpdate = false; - - int w{mWidth}; - int h{mHeight}; - if(mCodecCtx->sample_aspect_ratio.den != 0) - { - double aspect_ratio = av_q2d(mCodecCtx->sample_aspect_ratio); - if(aspect_ratio >= 1.0) - w = static_cast(w*aspect_ratio + 0.5); - else if(aspect_ratio > 0.0) - h = static_cast(h/aspect_ratio + 0.5); - } - SDL_SetWindowSize(screen, w, h); + double aspect_ratio = av_q2d(frame->sample_aspect_ratio); + if(aspect_ratio >= 1.0) + frame_width = static_cast(frame_width*aspect_ratio + 0.5); + else if(aspect_ratio > 0.0) + frame_height = static_cast(frame_height/aspect_ratio + 0.5); } + SDL_SetWindowSize(screen, frame_width, frame_height); } if(mImage) { - AVFrame *frame{vp->mFrame.get()}; void *pixels{nullptr}; int pitch{0}; @@ -1495,10 +1541,8 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re else { // Convert the image into YUV format that SDL uses - int coded_w{mCodecCtx->coded_width}; - int coded_h{mCodecCtx->coded_height}; - int w{mCodecCtx->width}; - int h{mCodecCtx->height}; + int w{frame->width}; + int h{frame->height}; if(!mSwscaleCtx || fmt_updated) { mSwscaleCtx.reset(sws_getContext( @@ -1511,8 +1555,8 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re /* point pict at the queue */ uint8_t *pict_data[3]; pict_data[0] = static_cast(pixels); - pict_data[1] = pict_data[0] + coded_w*coded_h; - pict_data[2] = pict_data[1] + coded_w*coded_h/4; + pict_data[1] = pict_data[0] + w*h; + pict_data[2] = pict_data[1] + w*h/4; int pict_linesize[3]; pict_linesize[0] = pitch; @@ -1523,15 +1567,15 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re 0, h, pict_data, pict_linesize); SDL_UnlockTexture(mImage); } - } - redraw = true; + redraw = true; + } } if(redraw) { /* Show the picture! */ - display(screen, renderer); + display(screen, renderer, frame); } if(updated) @@ -1560,11 +1604,15 @@ int VideoState::handler() { pict.mFrame = AVFramePtr{av_frame_alloc()}; }); /* Prefill the codec buffer. */ - do { - const int ret{mPackets.sendTo(mCodecCtx.get())}; - if(ret == AVERROR(EAGAIN) || ret == AVErrorEOF) - break; - } while(1); + auto packet_sender = [this]() + { + while(1) + { + const int ret{mQueue.sendPacket(mCodecCtx.get())}; + if(ret == AVErrorEOF) break; + } + }; + auto sender = std::async(std::launch::async, packet_sender); { std::lock_guard _{mDispPtsMutex}; @@ -1572,21 +1620,17 @@ int VideoState::handler() } auto current_pts = nanoseconds::zero(); - while(!mMovie.mQuit.load(std::memory_order_relaxed)) + while(1) { size_t write_idx{mPictQWrite.load(std::memory_order_relaxed)}; Picture *vp{&mPictQ[write_idx]}; /* Retrieve video frame. */ AVFrame *decoded_frame{vp->mFrame.get()}; - int ret; - while((ret=avcodec_receive_frame(mCodecCtx.get(), decoded_frame)) == AVERROR(EAGAIN)) - mPackets.sendTo(mCodecCtx.get()); - if(ret != 0) + while(int ret{mQueue.receiveFrame(mCodecCtx.get(), decoded_frame)}) { - if(ret == AVErrorEOF) break; + if(ret == AVErrorEOF) goto finish; std::cerr<< "Failed to receive frame: "< lock{mPictQMutex}; - while(write_idx == mPictQRead.load(std::memory_order_acquire) && - !mMovie.mQuit.load(std::memory_order_relaxed)) + while(write_idx == mPictQRead.load(std::memory_order_acquire)) mPictQCond.wait(lock); } } +finish: mEOS = true; std::unique_lock lock{mPictQMutex}; @@ -1667,6 +1708,9 @@ bool MovieState::prepare() av_dump_format(mFormatCtx.get(), 0, mFilename.c_str(), 0); mParseThread = std::thread{std::mem_fn(&MovieState::parse_handler), this}; + + std::unique_lock slock{mStartupMutex}; + while(!mStartupDone) mStartupCond.wait(slock); return true; } @@ -1689,9 +1733,9 @@ nanoseconds MovieState::getClock() nanoseconds MovieState::getMasterClock() { - if(mAVSyncType == SyncMaster::Video) + if(mAVSyncType == SyncMaster::Video && mVideo.mStream) return mVideo.getClock(); - if(mAVSyncType == SyncMaster::Audio) + if(mAVSyncType == SyncMaster::Audio && mAudio.mStream) return mAudio.getClock(); return getClock(); } @@ -1713,7 +1757,7 @@ int MovieState::streamComponentOpen(unsigned int stream_index) if(avcodec_parameters_to_context(avctx.get(), mFormatCtx->streams[stream_index]->codecpar)) return -1; - AVCodec *codec{avcodec_find_decoder(avctx->codec_id)}; + const AVCodec *codec{avcodec_find_decoder(avctx->codec_id)}; if(!codec || avcodec_open2(avctx.get(), codec, nullptr) < 0) { std::cerr<< "Unsupported codec: "<codec_id) @@ -1743,8 +1787,8 @@ int MovieState::streamComponentOpen(unsigned int stream_index) int MovieState::parse_handler() { - auto &audio_queue = mAudio.mPackets; - auto &video_queue = mVideo.mPackets; + auto &audio_queue = mAudio.mQueue; + auto &video_queue = mVideo.mQueue; int video_index{-1}; int audio_index{-1}; @@ -1759,6 +1803,12 @@ int MovieState::parse_handler() audio_index = streamComponentOpen(i); } + { + std::unique_lock slock{mStartupMutex}; + mStartupDone = true; + } + mStartupCond.notify_all(); + if(video_index < 0 && audio_index < 0) { std::cerr<< mFilename<<": could not open codecs" <stream_index == video_index) { - while(!mQuit.load(std::memory_order_acquire) && !video_queue.put(&packet)) + while(!mQuit.load(std::memory_order_acquire) && !video_queue.put(packet.get())) std::this_thread::sleep_for(milliseconds{100}); } - else if(packet.stream_index == audio_index) + else if(packet->stream_index == audio_index) { - while(!mQuit.load(std::memory_order_acquire) && !audio_queue.put(&packet)) + while(!mQuit.load(std::memory_order_acquire) && !audio_queue.put(packet.get())) std::this_thread::sleep_for(milliseconds{100}); } - av_packet_unref(&packet); + av_packet_unref(packet.get()); } /* Finish the queues so the receivers know nothing more is coming. */ - if(mVideo.mCodecCtx) video_queue.setFinished(); - if(mAudio.mCodecCtx) audio_queue.setFinished(); + video_queue.setFinished(); + audio_queue.setFinished(); /* all done - wait for it */ if(mVideoThread.joinable()) @@ -1817,6 +1867,13 @@ int MovieState::parse_handler() return 0; } +void MovieState::stop() +{ + mQuit = true; + mAudio.mQueue.flush(); + mVideo.mQueue.flush(); +} + // Helper class+method to print the time with human-readable formatting. struct PrettyTime { @@ -1939,7 +1996,6 @@ int main(int argc, char *argv[]) alGetProcAddress("alGetSourcei64vSOFT") ); } -#ifdef AL_SOFT_events if(alIsExtensionPresent("AL_SOFT_events")) { std::cout<< "Found AL_SOFT_events" <( alGetProcAddress("alEventCallbackSOFT")); } -#endif -#ifdef AL_SOFT_callback_buffer - if(alIsExtensionPresent("AL_SOFTX_callback_buffer")) + if(alIsExtensionPresent("AL_SOFT_callback_buffer")) { std::cout<< "Found AL_SOFT_callback_buffer" <( alGetProcAddress("alBufferCallbackSOFT")); } -#endif int fileidx{0}; for(;fileidx < argc;++fileidx) @@ -1986,6 +2039,28 @@ int main(int argc, char *argv[]) EnableWideStereo = true; } } + else if(strcmp(argv[fileidx], "-uhj") == 0) + { + if(!alIsExtensionPresent("AL_SOFT_UHJ")) + std::cerr<< "AL_SOFT_UHJ not supported for UHJ decoding" <(movState->getMasterClock()); if(cur_time != last_time) @@ -2023,19 +2098,21 @@ int main(int argc, char *argv[]) } bool force_redraw{false}; - if(have_evt) do { + SDL_Event event{}; + while(SDL_PollEvent(&event) != 0) + { switch(event.type) { case SDL_KEYDOWN: switch(event.key.keysym.sym) { case SDLK_ESCAPE: - movState->mQuit = true; + movState->stop(); eom_action = EomAction::Quit; break; case SDLK_n: - movState->mQuit = true; + movState->stop(); eom_action = EomAction::Next; break; @@ -2063,7 +2140,7 @@ int main(int argc, char *argv[]) break; case SDL_QUIT: - movState->mQuit = true; + movState->stop(); eom_action = EomAction::Quit; break; @@ -2101,7 +2178,7 @@ int main(int argc, char *argv[]) default: break; } - } while(SDL_PollEvent(&event)); + } movState->mVideo.updateVideo(screen, renderer, force_redraw); } diff --git a/Engine/lib/openal-soft/examples/alhrtf.c b/Engine/lib/openal-soft/examples/alhrtf.c index b8fc287fb..d878870e1 100644 --- a/Engine/lib/openal-soft/examples/alhrtf.c +++ b/Engine/lib/openal-soft/examples/alhrtf.c @@ -170,7 +170,7 @@ int main(int argc, char **argv) } /* Define a macro to help load the function pointers. */ -#define LOAD_PROC(d, T, x) ((x) = (T)alcGetProcAddress((d), #x)) +#define LOAD_PROC(d, T, x) ((x) = FUNCTION_CAST(T, alcGetProcAddress((d), #x))) LOAD_PROC(device, LPALCGETSTRINGISOFT, alcGetStringiSOFT); LOAD_PROC(device, LPALCRESETDEVICESOFT, alcResetDeviceSOFT); #undef LOAD_PROC diff --git a/Engine/lib/openal-soft/examples/allatency.c b/Engine/lib/openal-soft/examples/allatency.c index 5aa9e8645..ab4a4ebc1 100644 --- a/Engine/lib/openal-soft/examples/allatency.c +++ b/Engine/lib/openal-soft/examples/allatency.c @@ -164,7 +164,7 @@ int main(int argc, char **argv) } /* Define a macro to help load the function pointers. */ -#define LOAD_PROC(T, x) ((x) = (T)alGetProcAddress(#x)) +#define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) LOAD_PROC(LPALSOURCEDSOFT, alSourcedSOFT); LOAD_PROC(LPALSOURCE3DSOFT, alSource3dSOFT); LOAD_PROC(LPALSOURCEDVSOFT, alSourcedvSOFT); diff --git a/Engine/lib/openal-soft/examples/alloopback.c b/Engine/lib/openal-soft/examples/alloopback.c index 844efa743..7513458ba 100644 --- a/Engine/lib/openal-soft/examples/alloopback.c +++ b/Engine/lib/openal-soft/examples/alloopback.c @@ -149,7 +149,7 @@ int main(int argc, char *argv[]) } /* Define a macro to help load the function pointers. */ -#define LOAD_PROC(T, x) ((x) = (T)alcGetProcAddress(NULL, #x)) +#define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alcGetProcAddress(NULL, #x))) LOAD_PROC(LPALCLOOPBACKOPENDEVICESOFT, alcLoopbackOpenDeviceSOFT); LOAD_PROC(LPALCISRENDERFORMATSUPPORTEDSOFT, alcIsRenderFormatSupportedSOFT); LOAD_PROC(LPALCRENDERSAMPLESSOFT, alcRenderSamplesSOFT); diff --git a/Engine/lib/openal-soft/examples/almultireverb.c b/Engine/lib/openal-soft/examples/almultireverb.c index eb874061d..a77cc59e5 100644 --- a/Engine/lib/openal-soft/examples/almultireverb.c +++ b/Engine/lib/openal-soft/examples/almultireverb.c @@ -519,7 +519,7 @@ int main(int argc, char **argv) } /* Define a macro to help load the function pointers. */ -#define LOAD_PROC(T, x) ((x) = (T)alGetProcAddress(#x)) +#define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) LOAD_PROC(LPALGENFILTERS, alGenFilters); LOAD_PROC(LPALDELETEFILTERS, alDeleteFilters); LOAD_PROC(LPALISFILTER, alIsFilter); diff --git a/Engine/lib/openal-soft/examples/alreverb.c b/Engine/lib/openal-soft/examples/alreverb.c index 56acdd826..11a3ac6b2 100644 --- a/Engine/lib/openal-soft/examples/alreverb.c +++ b/Engine/lib/openal-soft/examples/alreverb.c @@ -259,7 +259,7 @@ int main(int argc, char **argv) } /* Define a macro to help load the function pointers. */ -#define LOAD_PROC(T, x) ((x) = (T)alGetProcAddress(#x)) +#define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) LOAD_PROC(LPALGENEFFECTS, alGenEffects); LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects); LOAD_PROC(LPALISEFFECT, alIsEffect); diff --git a/Engine/lib/openal-soft/examples/alstreamcb.cpp b/Engine/lib/openal-soft/examples/alstreamcb.cpp index 814e22149..e0dff4aad 100644 --- a/Engine/lib/openal-soft/examples/alstreamcb.cpp +++ b/Engine/lib/openal-soft/examples/alstreamcb.cpp @@ -45,18 +45,6 @@ #include "common/alhelpers.h" -#ifndef AL_SOFT_callback_buffer -#define AL_SOFT_callback_buffer -typedef unsigned int ALbitfieldSOFT; -#define AL_BUFFER_CALLBACK_FUNCTION_SOFT 0x19A0 -#define AL_BUFFER_CALLBACK_USER_PARAM_SOFT 0x19A1 -typedef ALsizei (AL_APIENTRY*LPALBUFFERCALLBACKTYPESOFT)(ALvoid *userptr, ALvoid *sampledata, ALsizei numsamples); -typedef void (AL_APIENTRY*LPALBUFFERCALLBACKSOFT)(ALuint buffer, ALenum format, ALsizei freq, LPALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr, ALbitfieldSOFT flags); -typedef void (AL_APIENTRY*LPALGETBUFFERPTRSOFT)(ALuint buffer, ALenum param, ALvoid **value); -typedef void (AL_APIENTRY*LPALGETBUFFER3PTRSOFT)(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3); -typedef void (AL_APIENTRY*LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid **values); -#endif - namespace { using std::chrono::seconds; @@ -130,18 +118,18 @@ struct StreamPlayer { mFormat = AL_NONE; if(mSfInfo.channels == 1) - mFormat = AL_FORMAT_MONO16; + mFormat = AL_FORMAT_MONO_FLOAT32; else if(mSfInfo.channels == 2) - mFormat = AL_FORMAT_STEREO16; + mFormat = AL_FORMAT_STEREO_FLOAT32; else if(mSfInfo.channels == 3) { if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) - mFormat = AL_FORMAT_BFORMAT2D_16; + mFormat = AL_FORMAT_BFORMAT2D_FLOAT32; } else if(mSfInfo.channels == 4) { if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) - mFormat = AL_FORMAT_BFORMAT3D_16; + mFormat = AL_FORMAT_BFORMAT3D_FLOAT32; } if(!mFormat) { @@ -153,7 +141,7 @@ struct StreamPlayer { } /* Set a 1s ring buffer size. */ - mBufferDataSize = static_cast(mSfInfo.samplerate*mSfInfo.channels) * sizeof(short); + mBufferDataSize = static_cast(mSfInfo.samplerate*mSfInfo.channels) * sizeof(float); mBufferData.reset(new ALbyte[mBufferDataSize]); mReadPos.store(0, std::memory_order_relaxed); mWritePos.store(0, std::memory_order_relaxed); @@ -219,7 +207,7 @@ struct StreamPlayer { bool prepare() { - alBufferCallbackSOFT(mBuffer, mFormat, mSfInfo.samplerate, bufferCallbackC, this, 0); + alBufferCallbackSOFT(mBuffer, mFormat, mSfInfo.samplerate, bufferCallbackC, this); alSourcei(mSource, AL_BUFFER, static_cast(mBuffer)); if(ALenum err{alGetError()}) { @@ -236,7 +224,7 @@ struct StreamPlayer { alGetSourcei(mSource, AL_SAMPLE_OFFSET, &pos); alGetSourcei(mSource, AL_SOURCE_STATE, &state); - const size_t frame_size{static_cast(mSfInfo.channels) * sizeof(short)}; + const size_t frame_size{static_cast(mSfInfo.channels) * sizeof(float)}; size_t woffset{mWritePos.load(std::memory_order_acquire)}; if(state != AL_INITIAL) { @@ -271,8 +259,8 @@ struct StreamPlayer { const size_t writable{roffset-woffset-1}; if(writable < frame_size) break; - sf_count_t num_frames{sf_readf_short(mSndfile, - reinterpret_cast(&mBufferData[woffset]), + sf_count_t num_frames{sf_readf_float(mSndfile, + reinterpret_cast(&mBufferData[woffset]), static_cast(writable/frame_size))}; if(num_frames < 1) break; @@ -290,8 +278,8 @@ struct StreamPlayer { (mBufferDataSize-woffset)}; if(writable < frame_size) break; - sf_count_t num_frames{sf_readf_short(mSndfile, - reinterpret_cast(&mBufferData[woffset]), + sf_count_t num_frames{sf_readf_float(mSndfile, + reinterpret_cast(&mBufferData[woffset]), static_cast(writable/frame_size))}; if(num_frames < 1) break; @@ -353,7 +341,7 @@ int main(int argc, char **argv) argv++; argc--; AudioManager almgr{&argv, &argc}; - if(!alIsExtensionPresent("AL_SOFTX_callback_buffer")) + if(!alIsExtensionPresent("AL_SOFT_callback_buffer")) { fprintf(stderr, "AL_SOFT_callback_buffer extension not available\n"); return 1; diff --git a/Engine/lib/openal-soft/examples/common/alhelpers.h b/Engine/lib/openal-soft/examples/common/alhelpers.h index 3752d2180..34f738647 100644 --- a/Engine/lib/openal-soft/examples/common/alhelpers.h +++ b/Engine/lib/openal-soft/examples/common/alhelpers.h @@ -18,6 +18,19 @@ void CloseAL(void); int altime_get(void); void al_nssleep(unsigned long nsec); +/* C doesn't allow casting between function and non-function pointer types, so + * with C99 we need to use a union to reinterpret the pointer type. Pre-C99 + * still needs to use a normal cast and live with the warning (C++ is fine with + * a regular reinterpret_cast). + */ +#if __STDC_VERSION__ >= 199901L +#define FUNCTION_CAST(T, ptr) (union{void *p; T f;}){ptr}.f +#elif defined(__cplusplus) +#define FUNCTION_CAST(T, ptr) reinterpret_cast(ptr) +#else +#define FUNCTION_CAST(T, ptr) (T)(ptr) +#endif + #ifdef __cplusplus } // extern "C" #endif diff --git a/Engine/lib/openal-soft/include/AL/alext.h b/Engine/lib/openal-soft/include/AL/alext.h index f80b0708a..1757f341e 100644 --- a/Engine/lib/openal-soft/include/AL/alext.h +++ b/Engine/lib/openal-soft/include/AL/alext.h @@ -579,6 +579,66 @@ AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values); #endif #endif +#ifndef ALC_SOFT_reopen_device +#define ALC_SOFT_reopen_device +typedef ALCboolean (ALC_APIENTRY*LPALCREOPENDEVICESOFT)(ALCdevice *device, + const ALCchar *deviceName, const ALCint *attribs); +#ifdef AL_ALEXT_PROTOTYPES +ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, const ALCchar *deviceName, + const ALCint *attribs); +#endif +#endif + +#ifndef AL_SOFT_callback_buffer +#define AL_SOFT_callback_buffer +#define AL_BUFFER_CALLBACK_FUNCTION_SOFT 0x19A0 +#define AL_BUFFER_CALLBACK_USER_PARAM_SOFT 0x19A1 +typedef ALsizei (AL_APIENTRY*ALBUFFERCALLBACKTYPESOFT)(ALvoid *userptr, ALvoid *sampledata, ALsizei numbytes); +typedef void (AL_APIENTRY*LPALBUFFERCALLBACKSOFT)(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr); +typedef void (AL_APIENTRY*LPALGETBUFFERPTRSOFT)(ALuint buffer, ALenum param, ALvoid **value); +typedef void (AL_APIENTRY*LPALGETBUFFER3PTRSOFT)(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3); +typedef void (AL_APIENTRY*LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid **values); +#ifdef AL_ALEXT_PROTOTYPES +AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr); +AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr); +AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2); +AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **ptr); +#endif +#endif + +#ifndef AL_SOFT_UHJ +#define AL_SOFT_UHJ +#define AL_FORMAT_UHJ2CHN8_SOFT 0x19A2 +#define AL_FORMAT_UHJ2CHN16_SOFT 0x19A3 +#define AL_FORMAT_UHJ2CHN_FLOAT32_SOFT 0x19A4 +#define AL_FORMAT_UHJ3CHN8_SOFT 0x19A5 +#define AL_FORMAT_UHJ3CHN16_SOFT 0x19A6 +#define AL_FORMAT_UHJ3CHN_FLOAT32_SOFT 0x19A7 +#define AL_FORMAT_UHJ4CHN8_SOFT 0x19A8 +#define AL_FORMAT_UHJ4CHN16_SOFT 0x19A9 +#define AL_FORMAT_UHJ4CHN_FLOAT32_SOFT 0x19AA + +#define AL_STEREO_MODE_SOFT 0x19B0 +#define AL_NORMAL_SOFT 0x0000 +#define AL_SUPER_STEREO_SOFT 0x0001 +#define AL_SUPER_STEREO_WIDTH_SOFT 0x19B1 +#endif + +#ifndef ALC_SOFT_output_mode +#define ALC_SOFT_output_mode +#define ALC_OUTPUT_MODE_SOFT 0x19AC +#define ALC_ANY_SOFT 0x19AD +/*#define ALC_MONO_SOFT 0x1500*/ +/*#define ALC_STEREO_SOFT 0x1501*/ +#define ALC_STEREO_BASIC_SOFT 0x19AE +#define ALC_STEREO_UHJ_SOFT 0x19AF +#define ALC_STEREO_HRTF_SOFT 0x19B2 +/*#define ALC_QUAD_SOFT 0x1503*/ +#define ALC_SURROUND_5_1_SOFT 0x1504 +#define ALC_SURROUND_6_1_SOFT 0x1505 +#define ALC_SURROUND_7_1_SOFT 0x1506 +#endif + #ifdef __cplusplus } #endif diff --git a/Engine/lib/openal-soft/presets/3D7.1.ambdec b/Engine/lib/openal-soft/presets/3D7.1.ambdec index 42b6a0bba..66e56501a 100644 --- a/Engine/lib/openal-soft/presets/3D7.1.ambdec +++ b/Engine/lib/openal-soft/presets/3D7.1.ambdec @@ -1,43 +1,61 @@ # AmbDec configuration # Written by Ambisonic Decoder Toolbox, version 8.0 -/description 3D7_2h1v_allrad_5200_rE_max_1_band +# input channel order: W Y Z X + +/description 3D7-noCenter_1h1v_pinv_even_energy_rV_max_rE_2_band + +# Similar to the the ITU-5.1-nocenter configuration, the front-center is +# declared here so that an appropriate distance may be set (for proper delaying +# or attenuating of dialog and such which feed it directly). It otherwise does +# not contribute to positional sound output due to its irregular position. /version 3 -/dec/chan_mask 1bf -/dec/freq_bands 1 -/dec/speakers 7 -/dec/coeff_scale fuma +/dec/chan_mask f +/dec/freq_bands 2 +/dec/speakers 6 +/dec/coeff_scale n3d -/opt/input_scale fuma -/opt/nfeff_comp output +/opt/input_scale n3d +/opt/nfeff_comp input /opt/delay_comp on /opt/level_comp on /opt/xover_freq 400.000000 /opt/xover_ratio 0.000000 /speakers/{ -# id dist azim elev conn +# id dist azim elev conn #----------------------------------------------------------------------- -add_spkr LF 1.500000 51.000000 24.000000 -add_spkr RF 1.500000 -51.000000 24.000000 -add_spkr CE 1.500000 0.000000 0.000000 -add_spkr LB 1.500000 180.000000 55.000000 -add_spkr RB 1.500000 0.000000 -55.000000 -add_spkr LS 1.500000 129.000000 -24.000000 -add_spkr RS 1.500000 -129.000000 -24.000000 +add_spkr FL 1.828800 51.000000 24.000000 +add_spkr FR 1.828800 -51.000000 24.000000 +add_spkr FC 1.828800 0.000000 0.000000 +add_spkr BL 1.828800 180.000000 55.000000 +add_spkr BR 1.828800 0.000000 -55.000000 +add_spkr SL 1.828800 129.000000 -24.000000 +add_spkr SR 1.828800 -129.000000 -24.000000 /} -/matrix/{ -order_gain 1.000000 0.774597 0.400000 0.000000 -add_row 0.325031 0.357638 0.206500 0.234037 0.202440 0.135692 0.116927 -0.098768 -add_row 0.325036 -0.357619 0.206537 0.234033 -0.202427 -0.135680 0.116934 -0.098768 -add_row 0.080073 -0.000010 -0.000296 0.155843 -0.000016 -0.000011 -0.000623 0.163306 -add_row 0.353556 0.000002 0.408453 -0.288377 -0.000004 -0.000003 -0.221039 0.077297 -add_row 0.325297 0.000008 -0.414018 0.232789 0.000004 0.000003 -0.232940 0.018311 -add_row 0.353558 0.352704 -0.203542 -0.290124 -0.191868 -0.134582 0.110616 -0.038294 -add_row 0.353556 -0.352691 -0.203576 -0.290115 0.191871 0.134585 0.110612 -0.038293 +/lfmatrix/{ +order_gain 1.00000000e+00 1.00000000e+00 0.000000 0.000000 +add_row 1.66669447e-01 2.04127551e-01 1.17487922e-01 1.66927066e-01 +add_row 1.66669447e-01 -2.04127551e-01 1.17487922e-01 1.66927066e-01 +add_row 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 +add_row 1.66669447e-01 0.00000000e+00 2.36070520e-01 -1.66153012e-01 +add_row 1.66669447e-01 0.00000000e+00 -2.36070520e-01 1.66153012e-01 +add_row 1.66669447e-01 2.04127551e-01 -1.17487922e-01 -1.66927066e-01 +add_row 1.66669447e-01 -2.04127551e-01 -1.17487922e-01 -1.66927066e-01 +/} + +/hfmatrix/{ +order_gain 1.73205081e+00 1.00000000e+00 0.000000 0.000000 +add_row 1.66669447e-01 2.04127551e-01 1.17487922e-01 1.66927066e-01 +add_row 1.66669447e-01 -2.04127551e-01 1.17487922e-01 1.66927066e-01 +add_row 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 +add_row 1.66669447e-01 0.00000000e+00 2.36070520e-01 -1.66153012e-01 +add_row 1.66669447e-01 0.00000000e+00 -2.36070520e-01 1.66153012e-01 +add_row 1.66669447e-01 2.04127551e-01 -1.17487922e-01 -1.66927066e-01 +add_row 1.66669447e-01 -2.04127551e-01 -1.17487922e-01 -1.66927066e-01 /} /end diff --git a/Engine/lib/openal-soft/presets/itu5.1.ambdec b/Engine/lib/openal-soft/presets/itu5.1.ambdec index 743860345..8f4b14e1b 100644 --- a/Engine/lib/openal-soft/presets/itu5.1.ambdec +++ b/Engine/lib/openal-soft/presets/itu5.1.ambdec @@ -1,7 +1,6 @@ # AmbDec configuration -# Written by Ambisonic Decoder Toolbox, version 8.0 -/description itu50_2h0p_allrad_5200_rE_max_1_band +/description itu50_2h0p_idhoa /version 3 @@ -15,7 +14,7 @@ /opt/delay_comp on /opt/level_comp on /opt/xover_freq 400.000000 -/opt/xover_ratio 3.000000 +/opt/xover_ratio 0.000000 /speakers/{ # id dist azim elev conn @@ -29,20 +28,20 @@ add_spkr RS 1.000000 -110.000000 0.000000 /lfmatrix/{ order_gain 1.000000 1.000000 1.000000 0.000000 -add_row 0.420330 0.330200 -0.312250 0.019350 -0.027010 -add_row 0.197700 0.288820 0.287820 0.049110 0.007420 -add_row 0.058030 0.000000 0.205970 0.000000 0.050790 -add_row 0.197700 -0.288820 0.287820 -0.049110 0.007420 -add_row 0.420330 -0.330200 -0.312250 -0.019350 -0.027010 +add_row 4.9010985e-1 3.7730501e-1 -3.7310699e-1 -1.2591453e-1 1.4513300e-2 +add_row 1.4908573e-1 3.0356168e-1 1.5329006e-1 2.4511248e-1 -1.5075313e-1 +add_row 1.3765492e-1 0.0000000e+0 4.4941794e-1 0.0000000e+0 2.5784407e-1 +add_row 1.4908573e-1 -3.0356168e-1 1.5329006e-1 -2.4511248e-1 -1.5075313e-1 +add_row 4.9010985e-1 -3.7730501e-1 -3.7310699e-1 1.2591453e-1 1.4513300e-2 /} /hfmatrix/{ -order_gain 1.000000 0.866025 0.500000 0.000000 -add_row 0.470934 0.378170 -0.400085 -0.082226 -0.044377 -add_row 0.208954 0.257988 0.230383 0.288520 -0.025085 -add_row 0.109403 -0.000002 0.194278 -0.000003 0.200863 -add_row 0.208950 -0.257989 0.230379 -0.288516 -0.025088 -add_row 0.470936 -0.378173 -0.400081 0.082228 -0.044372 +order_gain 1.000000 1.000000 1.000000 0.000000 +add_row 5.6731600e-1 4.2292000e-1 -3.1549500e-1 -6.3449000e-2 -2.9238000e-2 +add_row 3.6858400e-1 2.7234900e-1 3.2161600e-1 1.9264500e-1 4.8260000e-2 +add_row 1.8357900e-1 0.0000000e+0 1.9958800e-1 0.0000000e+0 9.6282000e-2 +add_row 3.6858400e-1 -2.7234900e-1 3.2161600e-1 -1.9264500e-1 4.8260000e-2 +add_row 5.6731600e-1 -4.2292000e-1 -3.1549500e-1 6.3449000e-2 -2.9238000e-2 /} /end diff --git a/Engine/lib/openal-soft/router/al.cpp b/Engine/lib/openal-soft/router/al.cpp index 4c8b0006d..aabb5f30e 100644 --- a/Engine/lib/openal-soft/router/al.cpp +++ b/Engine/lib/openal-soft/router/al.cpp @@ -11,31 +11,31 @@ std::atomic CurrentCtxDriver{nullptr}; #define DECL_THUNK1(R,n,T1) AL_API R AL_APIENTRY n(T1 a) \ { \ - DriverIface *iface = ThreadCtxDriver; \ + DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a); \ } #define DECL_THUNK2(R,n,T1,T2) AL_API R AL_APIENTRY n(T1 a, T2 b) \ { \ - DriverIface *iface = ThreadCtxDriver; \ + DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b); \ } #define DECL_THUNK3(R,n,T1,T2,T3) AL_API R AL_APIENTRY n(T1 a, T2 b, T3 c) \ { \ - DriverIface *iface = ThreadCtxDriver; \ + DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b, c); \ } #define DECL_THUNK4(R,n,T1,T2,T3,T4) AL_API R AL_APIENTRY n(T1 a, T2 b, T3 c, T4 d) \ { \ - DriverIface *iface = ThreadCtxDriver; \ + DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b, c, d); \ } #define DECL_THUNK5(R,n,T1,T2,T3,T4,T5) AL_API R AL_APIENTRY n(T1 a, T2 b, T3 c, T4 d, T5 e) \ { \ - DriverIface *iface = ThreadCtxDriver; \ + DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b, c, d, e); \ } @@ -46,7 +46,7 @@ std::atomic CurrentCtxDriver{nullptr}; */ AL_API ALenum AL_APIENTRY alGetError(void) { - DriverIface *iface = ThreadCtxDriver; + DriverIface *iface = GetThreadDriver(); if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); return iface ? iface->alGetError() : AL_NO_ERROR; } diff --git a/Engine/lib/openal-soft/router/alc.cpp b/Engine/lib/openal-soft/router/alc.cpp index 92fe69f7b..210f683e0 100644 --- a/Engine/lib/openal-soft/router/alc.cpp +++ b/Engine/lib/openal-soft/router/alc.cpp @@ -20,7 +20,7 @@ struct FuncExportEntry { const char *funcName; void *address; }; -static const std::array alcFunctions{{ +static const std::array alcFunctions{{ DECL(alcCreateContext), DECL(alcMakeContextCurrent), DECL(alcProcessContext), @@ -435,21 +435,21 @@ ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) */ if(idx < 0) { - DriverIface *oldiface = ThreadCtxDriver; + DriverIface *oldiface = GetThreadDriver(); if(oldiface) oldiface->alcSetThreadContext(nullptr); oldiface = CurrentCtxDriver.exchange(nullptr); if(oldiface) oldiface->alcMakeContextCurrent(nullptr); } else { - DriverIface *oldiface = ThreadCtxDriver; + DriverIface *oldiface = GetThreadDriver(); if(oldiface && oldiface != &DriverList[idx]) oldiface->alcSetThreadContext(nullptr); oldiface = CurrentCtxDriver.exchange(&DriverList[idx]); if(oldiface && oldiface != &DriverList[idx]) oldiface->alcMakeContextCurrent(nullptr); } - ThreadCtxDriver = nullptr; + SetThreadDriver(nullptr); return ALC_TRUE; } @@ -492,7 +492,7 @@ ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void) { - DriverIface *iface = ThreadCtxDriver; + DriverIface *iface = GetThreadDriver(); if(!iface) iface = CurrentCtxDriver.load(); return iface ? iface->alcGetCurrentContext() : nullptr; } @@ -898,10 +898,10 @@ ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) if(!context) { - DriverIface *oldiface = ThreadCtxDriver; + DriverIface *oldiface = GetThreadDriver(); if(oldiface && !oldiface->alcSetThreadContext(nullptr)) return ALC_FALSE; - ThreadCtxDriver = nullptr; + SetThreadDriver(nullptr); return ALC_TRUE; } @@ -910,10 +910,10 @@ ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) { if(DriverList[idx].alcSetThreadContext(context)) { - DriverIface *oldiface = ThreadCtxDriver; + DriverIface *oldiface = GetThreadDriver(); if(oldiface != &DriverList[idx]) { - ThreadCtxDriver = &DriverList[idx]; + SetThreadDriver(&DriverList[idx]); if(oldiface) oldiface->alcSetThreadContext(nullptr); } return ALC_TRUE; @@ -926,7 +926,7 @@ ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) { - DriverIface *iface = ThreadCtxDriver; + DriverIface *iface = GetThreadDriver(); if(iface) return iface->alcGetThreadContext(); return nullptr; } diff --git a/Engine/lib/openal-soft/router/router.cpp b/Engine/lib/openal-soft/router/router.cpp index 3d27af24b..67c34812a 100644 --- a/Engine/lib/openal-soft/router/router.cpp +++ b/Engine/lib/openal-soft/router/router.cpp @@ -24,6 +24,11 @@ thread_local DriverIface *ThreadCtxDriver; enum LogLevel LogLevel = LogLevel_Error; FILE *LogFile; +#ifdef __MINGW32__ +DriverIface *GetThreadDriver() noexcept { return ThreadCtxDriver; } +void SetThreadDriver(DriverIface *driver) noexcept { ThreadCtxDriver = driver; } +#endif + static void LoadDriverList(void); @@ -209,7 +214,10 @@ static void AddModule(HMODULE module, const WCHAR *name) if(newdrv.alcGetError(nullptr) == ALC_NO_ERROR) newdrv.ALCVer = MAKE_ALC_VER(alc_ver[0], alc_ver[1]); else + { + WARN("Failed to query ALC version for %ls, assuming 1.0\n", name); newdrv.ALCVer = MAKE_ALC_VER(1, 0); + } #undef LOAD_PROC #define LOAD_PROC(x) do { \ diff --git a/Engine/lib/openal-soft/router/router.h b/Engine/lib/openal-soft/router/router.h index 703354210..f49a96ef8 100644 --- a/Engine/lib/openal-soft/router/router.h +++ b/Engine/lib/openal-soft/router/router.h @@ -7,10 +7,11 @@ #include -#include -#include #include #include +#include +#include +#include #include "AL/alc.h" #include "AL/al.h" @@ -122,8 +123,9 @@ struct DriverIface { LPALSPEEDOFSOUND alSpeedOfSound{nullptr}; LPALDISTANCEMODEL alDistanceModel{nullptr}; - DriverIface(std::wstring name, HMODULE mod) - : Name(std::move(name)), Module(mod) + template + DriverIface(T&& name, HMODULE mod) + : Name(std::forward(name)), Module(mod) { } ~DriverIface() { @@ -138,6 +140,17 @@ extern std::vector DriverList; extern thread_local DriverIface *ThreadCtxDriver; extern std::atomic CurrentCtxDriver; +/* HACK: MinGW generates bad code when accessing an extern thread_local object. + * Add a wrapper function for it that only accesses it where it's defined. + */ +#ifdef __MINGW32__ +DriverIface *GetThreadDriver() noexcept; +void SetThreadDriver(DriverIface *driver) noexcept; +#else +inline DriverIface *GetThreadDriver() noexcept { return ThreadCtxDriver; } +inline void SetThreadDriver(DriverIface *driver) noexcept { ThreadCtxDriver = driver; } +#endif + class PtrIntMap { void **mKeys{nullptr}; diff --git a/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp b/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp index 8e6c7beac..baecf5258 100644 --- a/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp +++ b/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp @@ -28,6 +28,9 @@ static const struct { #ifdef HAVE_JACK { "jack", "JACK" }, #endif +#ifdef HAVE_PIPEWIRE + { "pipewire", "PipeWire" }, +#endif #ifdef HAVE_PULSEAUDIO { "pulse", "PulseAudio" }, #endif @@ -80,8 +83,7 @@ static const struct NameValuePair { { "Mono", "mono" }, { "Stereo", "stereo" }, { "Quadraphonic", "quad" }, - { "5.1 Surround (Side)", "surround51" }, - { "5.1 Surround (Rear)", "surround51rear" }, + { "5.1 Surround", "surround51" }, { "6.1 Surround", "surround61" }, { "7.1 Surround", "surround71" }, @@ -122,18 +124,21 @@ static const struct NameValuePair { { "Default", "" }, { "Pan Pot", "panpot" }, { "UHJ", "uhj" }, + { "Binaural", "hrtf" }, { "", "" } }, ambiFormatList[] = { { "Default", "" }, { "AmbiX (ACN, SN3D)", "ambix" }, - { "ACN, N3D", "acn+n3d" }, { "Furse-Malham", "fuma" }, + { "ACN, N3D", "acn+n3d" }, + { "ACN, FuMa", "acn+fuma" }, { "", "" } }, hrtfModeList[] = { { "1st Order Ambisonic", "ambi1" }, { "2nd Order Ambisonic", "ambi2" }, + { "3rd Order Ambisonic", "ambi3" }, { "Default (Full)", "" }, { "Full", "full" }, @@ -200,7 +205,11 @@ static QStringList getAllDataPaths(const QString &append) QString paths = qgetenv("XDG_DATA_DIRS"); if(paths.isEmpty()) paths = "/usr/local/share/:/usr/share/"; +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + list += paths.split(QChar(':'), Qt::SkipEmptyParts); +#else list += paths.split(QChar(':'), QString::SkipEmptyParts); +#endif #endif QStringList::iterator iter = list.begin(); while(iter != list.end()) @@ -445,8 +454,11 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->pulseFixRateCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->pulseAdjLatencyCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->pwireAssumeAudioCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->jackAutospawnCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->jackConnectPortsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->jackRtMixCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->jackBufferSizeSlider, &QSlider::valueChanged, this, &MainWindow::updateJackBufferSizeEdit); connect(ui->jackBufferSizeLine, &QLineEdit::editingFinished, this, &MainWindow::updateJackBufferSizeSlider); @@ -635,6 +647,8 @@ void MainWindow::loadConfig(const QString &fname) ui->channelConfigCombo->setCurrentIndex(0); if(channelconfig.isEmpty() == false) { + if(channelconfig == "surround51rear") + channelconfig = "surround51"; QString str{getNameFromValue(speakerModeList, channelconfig)}; if(!str.isEmpty()) { @@ -735,8 +749,7 @@ void MainWindow::loadConfig(const QString &fname) } } - bool hqmode{settings.value("decoder/hq-mode", true).toBool()}; - ui->decoderHQModeCheckBox->setChecked(hqmode); + ui->decoderHQModeCheckBox->setChecked(getCheckState(settings.value("decoder/hq-mode"))); ui->decoderDistCompCheckBox->setCheckState(getCheckState(settings.value("decoder/distance-comp"))); ui->decoderNFEffectsCheckBox->setCheckState(getCheckState(settings.value("decoder/nfc"))); double refdelay{settings.value("decoder/nfc-ref-delay", 0.0).toDouble()}; @@ -760,11 +773,9 @@ void MainWindow::loadConfig(const QString &fname) QString hrtfmode{settings.value("hrtf-mode").toString().trimmed()}; ui->hrtfmodeSlider->setValue(2); - ui->hrtfmodeLabel->setText(hrtfModeList[2].name); - /* The "basic" mode name is no longer supported, and "ambi3" is temporarily - * disabled. Use "ambi2" instead. - */ - if(hrtfmode == "basic" || hrtfmode == "ambi3") + ui->hrtfmodeLabel->setText(hrtfModeList[3].name); + /* The "basic" mode name is no longer supported. Use "ambi2" instead. */ + if(hrtfmode == "basic") hrtfmode = "ambi2"; for(int i = 0;hrtfModeList[i].name[0];i++) { @@ -917,8 +928,12 @@ void MainWindow::loadConfig(const QString &fname) ui->pulseFixRateCheckBox->setCheckState(getCheckState(settings.value("pulse/fix-rate"))); ui->pulseAdjLatencyCheckBox->setCheckState(getCheckState(settings.value("pulse/adjust-latency"))); + ui->pwireAssumeAudioCheckBox->setCheckState(settings.value("pipewire/assume-audio").toBool() + ? Qt::Checked : Qt::Unchecked); + ui->jackAutospawnCheckBox->setCheckState(getCheckState(settings.value("jack/spawn-server"))); ui->jackConnectPortsCheckBox->setCheckState(getCheckState(settings.value("jack/connect-ports"))); + ui->jackRtMixCheckBox->setCheckState(getCheckState(settings.value("jack/rt-mix"))); ui->jackBufferSizeLine->setText(settings.value("jack/buffer-size", QString()).toString()); updateJackBufferSizeSlider(); @@ -998,9 +1013,7 @@ void MainWindow::saveConfig(const QString &fname) const settings.setValue("output-limiter", getCheckValue(ui->outputLimiterCheckBox)); settings.setValue("dither", getCheckValue(ui->outputDitherCheckBox)); - settings.setValue("decoder/hq-mode", - ui->decoderHQModeCheckBox->isChecked() ? QString{/*"true"*/} : QString{"false"} - ); + settings.setValue("decoder/hq-mode", getCheckValue(ui->decoderHQModeCheckBox)); settings.setValue("decoder/distance-comp", getCheckValue(ui->decoderDistCompCheckBox)); settings.setValue("decoder/nfc", getCheckValue(ui->decoderNFEffectsCheckBox)); double refdelay = ui->decoderNFRefDelaySpinBox->value(); @@ -1127,8 +1140,12 @@ void MainWindow::saveConfig(const QString &fname) const settings.setValue("pulse/fix-rate", getCheckValue(ui->pulseFixRateCheckBox)); settings.setValue("pulse/adjust-latency", getCheckValue(ui->pulseAdjLatencyCheckBox)); + settings.setValue("pipewire/assume-audio", ui->pwireAssumeAudioCheckBox->isChecked() + ? QString{"true"} : QString{/*"false"*/}); + settings.setValue("jack/spawn-server", getCheckValue(ui->jackAutospawnCheckBox)); settings.setValue("jack/connect-ports", getCheckValue(ui->jackConnectPortsCheckBox)); + settings.setValue("jack/rt-mix", getCheckValue(ui->jackRtMixCheckBox)); settings.setValue("jack/buffer-size", ui->jackBufferSizeLine->text()); settings.setValue("alsa/device", ui->alsaDefaultDeviceLine->text()); diff --git a/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.ui b/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.ui index 77688cf08..ba8f83fb6 100644 --- a/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.ui +++ b/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.ui @@ -62,7 +62,7 @@ 110 50 - 76 + 80 31 @@ -111,7 +111,7 @@ float and converted to the output sample type as needed. 110 20 - 76 + 80 31 @@ -129,7 +129,7 @@ to stereo output. 380 20 - 96 + 100 31 @@ -437,12 +437,15 @@ frames needed for each mixing update. Pan Pot uses standard amplitude panning (aka -pair-wise, stereo pair, etc) between -30 and +30 -degrees, while UHJ creates a stereo-compatible -two-channel UHJ mix, which encodes some -surround sound information into stereo output -that can be decoded with a surround sound -receiver. +pair-wise, stereo pair, etc). + +UHJ creates a stereo-compatible two-channel +UHJ mix, which encodes some surround sound +information into stereo output that can be +decoded with a surround sound receiver. + +Binaural applies HRTF filters to create a sense +of 3D space with headphones. @@ -632,6 +635,9 @@ appropriate speaker configuration you intend to use. High Quality Mode: + + true + @@ -1219,6 +1225,11 @@ application or system to determine if it should be used. PulseAudio + + + PipeWire + + JACK @@ -1421,6 +1432,26 @@ drop-outs. + + + + + 20 + 10 + 161 + 21 + + + + Assumes PipeWire has support for audio, allowing +the backend to initialize even when no audio devices +are reported. + + + Assume audio support + + + @@ -1442,7 +1473,7 @@ drop-outs. 10 - 70 + 110 401 80 @@ -1450,7 +1481,8 @@ drop-outs. The update buffer size, in samples, that the backend will keep buffered to handle the server's real-time -processing requests. Must be a power of 2. +processing requests. Must be a power of 2. Ignored +when Real-time Mixing is used. Buffer Size @@ -1516,6 +1548,30 @@ processing requests. Must be a power of 2. true + + + + 20 + 70 + 141 + 21 + + + + Renders samples directly in the real-time +processing callback. This allows for lower +latency and less overall CPU utilization, but +can increase the risk of underruns when +increasing the amount of processing the +mixer needs to do. + + + Real-time Mixing + + + true + + @@ -2317,7 +2373,7 @@ added by the ALC_EXT_DEDICATED extension. 160 20 - 131 + 135 31 diff --git a/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp b/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp index ec000d723..7d091be88 100644 --- a/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp +++ b/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include "makemhr.h" diff --git a/Engine/lib/openal-soft/utils/openal-info.c b/Engine/lib/openal-soft/utils/openal-info.c index 1788d1181..959324cc0 100644 --- a/Engine/lib/openal-soft/utils/openal-info.c +++ b/Engine/lib/openal-soft/utils/openal-info.c @@ -22,6 +22,7 @@ * THE SOFTWARE. */ +#include #include #include #include @@ -33,83 +34,17 @@ #include "win_main_utf8.h" - -#ifndef ALC_ENUMERATE_ALL_EXT -#define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012 -#define ALC_ALL_DEVICES_SPECIFIER 0x1013 +/* C doesn't allow casting between function and non-function pointer types, so + * with C99 we need to use a union to reinterpret the pointer type. Pre-C99 + * still needs to use a normal cast and live with the warning (C++ is fine with + * a regular reinterpret_cast). + */ +#if __STDC_VERSION__ >= 199901L +#define FUNCTION_CAST(T, ptr) (union{void *p; T f;}){ptr}.f +#else +#define FUNCTION_CAST(T, ptr) (T)(ptr) #endif -#ifndef ALC_EXT_EFX -#define ALC_EFX_MAJOR_VERSION 0x20001 -#define ALC_EFX_MINOR_VERSION 0x20002 -#define ALC_MAX_AUXILIARY_SENDS 0x20003 -#endif - - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include - -static WCHAR *FromUTF8(const char *str) -{ - WCHAR *out = NULL; - int len; - - if((len=MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0)) > 0) - { - out = calloc(sizeof(WCHAR), (unsigned int)(len)); - MultiByteToWideChar(CP_UTF8, 0, str, -1, out, len); - } - return out; -} - -/* Override printf, fprintf, and fwrite so we can print UTF-8 strings. */ -static void al_fprintf(FILE *file, const char *fmt, ...) -{ - char str[1024]; - WCHAR *wstr; - va_list ap; - - va_start(ap, fmt); - vsnprintf(str, sizeof(str), fmt, ap); - va_end(ap); - - str[sizeof(str)-1] = 0; - wstr = FromUTF8(str); - if(!wstr) - fprintf(file, " %s", str); - else - fprintf(file, "%ls", wstr); - free(wstr); -} -#define fprintf al_fprintf -#define printf(...) al_fprintf(stdout, __VA_ARGS__) - -static size_t al_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *file) -{ - char str[1024]; - WCHAR *wstr; - size_t len; - - len = size * nmemb; - if(len > sizeof(str)-1) - len = sizeof(str)-1; - memcpy(str, ptr, len); - str[len] = 0; - - wstr = FromUTF8(str); - if(!wstr) - fprintf(file, " %s", str); - else - fprintf(file, "%ls", wstr); - free(wstr); - - return len / size; -} -#define fwrite al_fwrite -#endif - - #define MAX_WIDTH 80 static void printList(const char *list, char separator) @@ -227,7 +162,8 @@ static void printHRTFInfo(ALCdevice *device) return; } - alcGetStringiSOFT = (LPALCGETSTRINGISOFT)alcGetProcAddress(device, "alcGetStringiSOFT"); + alcGetStringiSOFT = FUNCTION_CAST(LPALCGETSTRINGISOFT, + alcGetProcAddress(device, "alcGetStringiSOFT")); alcGetIntegerv(device, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtfs); if(!num_hrtfs) @@ -245,6 +181,34 @@ static void printHRTFInfo(ALCdevice *device) checkALCErrors(device); } +static void printModeInfo(ALCdevice *device) +{ + if(alcIsExtensionPresent(device, "ALC_SOFT_output_mode")) + { + const char *modename = "(error)"; + ALCenum mode = 0; + + alcGetIntegerv(device, ALC_OUTPUT_MODE_SOFT, 1, &mode); + checkALCErrors(device); + switch(mode) + { + case ALC_ANY_SOFT: modename = "Unknown / unspecified"; break; + case ALC_MONO_SOFT: modename = "Mono"; break; + case ALC_STEREO_SOFT: modename = "Stereo (unspecified encoding)"; break; + case ALC_STEREO_BASIC_SOFT: modename = "Stereo (basic)"; break; + case ALC_STEREO_UHJ_SOFT: modename = "Stereo (UHJ)"; break; + case ALC_STEREO_HRTF_SOFT: modename = "Stereo (HRTF)"; break; + case ALC_QUAD_SOFT: modename = "Quadraphonic"; break; + case ALC_SURROUND_5_1_SOFT: modename = "5.1 Surround"; break; + case ALC_SURROUND_6_1_SOFT: modename = "6.1 Surround"; break; + case ALC_SURROUND_7_1_SOFT: modename = "7.1 Surround"; break; + } + printf("Output channel mode: %s\n", modename); + } + else + printf("Output mode extension not available\n"); +} + static void printALInfo(void) { printf("OpenAL vendor string: %s\n", alGetString(AL_VENDOR)); @@ -267,7 +231,7 @@ static void printResamplerInfo(void) return; } - alGetStringiSOFT = (LPALGETSTRINGISOFT)alGetProcAddress("alGetStringiSOFT"); + alGetStringiSOFT = FUNCTION_CAST(LPALGETSTRINGISOFT, alGetProcAddress("alGetStringiSOFT")); num_resamplers = alGetInteger(AL_NUM_RESAMPLERS_SOFT); def_resampler = alGetInteger(AL_DEFAULT_RESAMPLER_SOFT); @@ -289,26 +253,35 @@ static void printResamplerInfo(void) static void printEFXInfo(ALCdevice *device) { - ALCint major, minor, sends; - static const ALchar filters[][32] = { - "AL_FILTER_LOWPASS", "AL_FILTER_HIGHPASS", "AL_FILTER_BANDPASS", "" + static LPALGENFILTERS palGenFilters; + static LPALDELETEFILTERS palDeleteFilters; + static LPALFILTERI palFilteri; + static LPALGENEFFECTS palGenEffects; + static LPALDELETEEFFECTS palDeleteEffects; + static LPALEFFECTI palEffecti; + + static const ALint filters[] = { + AL_FILTER_LOWPASS, AL_FILTER_HIGHPASS, AL_FILTER_BANDPASS, + AL_FILTER_NULL }; char filterNames[] = "Low-pass,High-pass,Band-pass,"; - static const ALchar effects[][32] = { - "AL_EFFECT_EAXREVERB", "AL_EFFECT_REVERB", "AL_EFFECT_CHORUS", - "AL_EFFECT_DISTORTION", "AL_EFFECT_ECHO", "AL_EFFECT_FLANGER", - "AL_EFFECT_FREQUENCY_SHIFTER", "AL_EFFECT_VOCAL_MORPHER", - "AL_EFFECT_PITCH_SHIFTER", "AL_EFFECT_RING_MODULATOR", - "AL_EFFECT_AUTOWAH", "AL_EFFECT_COMPRESSOR", "AL_EFFECT_EQUALIZER", "" + static const ALint effects[] = { + AL_EFFECT_EAXREVERB, AL_EFFECT_REVERB, AL_EFFECT_CHORUS, + AL_EFFECT_DISTORTION, AL_EFFECT_ECHO, AL_EFFECT_FLANGER, + AL_EFFECT_FREQUENCY_SHIFTER, AL_EFFECT_VOCAL_MORPHER, + AL_EFFECT_PITCH_SHIFTER, AL_EFFECT_RING_MODULATOR, + AL_EFFECT_AUTOWAH, AL_EFFECT_COMPRESSOR, AL_EFFECT_EQUALIZER, + AL_EFFECT_NULL }; - static const ALchar dedeffects[][64] = { - "AL_EFFECT_DEDICATED_DIALOGUE", - "AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT", "" + static const ALint dedeffects[] = { + AL_EFFECT_DEDICATED_DIALOGUE, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, + AL_EFFECT_NULL }; char effectNames[] = "EAX Reverb,Reverb,Chorus,Distortion,Echo,Flanger," - "Frequency Shifter,Vocal Morpher,Pitch Shifter," - "Ring Modulator,Autowah,Compressor,Equalizer," - "Dedicated Dialog,Dedicated LFE,"; + "Frequency Shifter,Vocal Morpher,Pitch Shifter,Ring Modulator,Autowah," + "Compressor,Equalizer,Dedicated Dialog,Dedicated LFE,"; + ALCint major, minor, sends; + ALuint object; char *current; int i; @@ -318,6 +291,13 @@ static void printEFXInfo(ALCdevice *device) return; } + palGenFilters = FUNCTION_CAST(LPALGENFILTERS, alGetProcAddress("alGenFilters")); + palDeleteFilters = FUNCTION_CAST(LPALDELETEFILTERS, alGetProcAddress("alDeleteFilters")); + palFilteri = FUNCTION_CAST(LPALFILTERI, alGetProcAddress("alFilteri")); + palGenEffects = FUNCTION_CAST(LPALGENEFFECTS, alGetProcAddress("alGenEffects")); + palDeleteEffects = FUNCTION_CAST(LPALDELETEEFFECTS, alGetProcAddress("alDeleteEffects")); + palEffecti = FUNCTION_CAST(LPALEFFECTI, alGetProcAddress("alEffecti")); + alcGetIntegerv(device, ALC_EFX_MAJOR_VERSION, 1, &major); alcGetIntegerv(device, ALC_EFX_MINOR_VERSION, 1, &minor); if(checkALCErrors(device) == ALC_NO_ERROR) @@ -326,14 +306,17 @@ static void printEFXInfo(ALCdevice *device) if(checkALCErrors(device) == ALC_NO_ERROR) printf("Max auxiliary sends: %d\n", sends); + palGenFilters(1, &object); + checkALErrors(); + current = filterNames; - for(i = 0;filters[i][0];i++) + for(i = 0;filters[i] != AL_FILTER_NULL;i++) { char *next = strchr(current, ','); - ALenum val; + assert(next != NULL); - val = alGetEnumValue(filters[i]); - if(alGetError() != AL_NO_ERROR || val == 0 || val == -1) + palFilteri(object, AL_FILTER_TYPE, filters[i]); + if(alGetError() != AL_NO_ERROR) memmove(current, next+1, strlen(next)); else current = next+1; @@ -341,27 +324,31 @@ static void printEFXInfo(ALCdevice *device) printf("Supported filters:"); printList(filterNames, ','); + palDeleteFilters(1, &object); + palGenEffects(1, &object); + checkALErrors(); + current = effectNames; - for(i = 0;effects[i][0];i++) + for(i = 0;effects[i] != AL_EFFECT_NULL;i++) { char *next = strchr(current, ','); - ALenum val; + assert(next != NULL); - val = alGetEnumValue(effects[i]); - if(alGetError() != AL_NO_ERROR || val == 0 || val == -1) + palEffecti(object, AL_EFFECT_TYPE, effects[i]); + if(alGetError() != AL_NO_ERROR) memmove(current, next+1, strlen(next)); else current = next+1; } if(alcIsExtensionPresent(device, "ALC_EXT_DEDICATED")) { - for(i = 0;dedeffects[i][0];i++) + for(i = 0;dedeffects[i] != AL_EFFECT_NULL;i++) { char *next = strchr(current, ','); - ALenum val; + assert(next != NULL); - val = alGetEnumValue(dedeffects[i]); - if(alGetError() != AL_NO_ERROR || val == 0 || val == -1) + palEffecti(object, AL_EFFECT_TYPE, dedeffects[i]); + if(alGetError() != AL_NO_ERROR) memmove(current, next+1, strlen(next)); else current = next+1; @@ -369,14 +356,18 @@ static void printEFXInfo(ALCdevice *device) } else { - for(i = 0;dedeffects[i][0];i++) + for(i = 0;dedeffects[i] != AL_EFFECT_NULL;i++) { char *next = strchr(current, ','); + assert(next != NULL); memmove(current, next+1, strlen(next)); } } printf("Supported effects:"); printList(effectNames, ','); + + palDeleteEffects(1, &object); + checkALErrors(); } int main(int argc, char *argv[]) @@ -384,6 +375,11 @@ int main(int argc, char *argv[]) ALCdevice *device; ALCcontext *context; +#ifdef _WIN32 + /* OpenAL Soft gives UTF-8 strings, so set the console to expect that. */ + SetConsoleOutputCP(CP_UTF8); +#endif + if(argc > 1 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0)) { @@ -429,6 +425,7 @@ int main(int argc, char *argv[]) return 1; } + printModeInfo(device); printALInfo(); printResamplerInfo(); printEFXInfo(device); diff --git a/Engine/lib/openal-soft/utils/uhjdecoder.cpp b/Engine/lib/openal-soft/utils/uhjdecoder.cpp new file mode 100644 index 000000000..dc85ba568 --- /dev/null +++ b/Engine/lib/openal-soft/utils/uhjdecoder.cpp @@ -0,0 +1,538 @@ +/* + * 2-channel UHJ Decoder + * + * Copyright (c) Chris Robinson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "albit.h" +#include "albyte.h" +#include "alcomplex.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alspan.h" +#include "vector.h" +#include "opthelpers.h" +#include "phase_shifter.h" + +#include "sndfile.h" + +#include "win_main_utf8.h" + + +struct FileDeleter { + void operator()(FILE *file) { fclose(file); } +}; +using FilePtr = std::unique_ptr; + +struct SndFileDeleter { + void operator()(SNDFILE *sndfile) { sf_close(sndfile); } +}; +using SndFilePtr = std::unique_ptr; + + +using ubyte = unsigned char; +using ushort = unsigned short; +using uint = unsigned int; +using complex_d = std::complex; + +using byte4 = std::array; + + +constexpr ubyte SUBTYPE_BFORMAT_FLOAT[]{ + 0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, + 0xca, 0x00, 0x00, 0x00 +}; + +void fwrite16le(ushort val, FILE *f) +{ + ubyte data[2]{ static_cast(val&0xff), static_cast((val>>8)&0xff) }; + fwrite(data, 1, 2, f); +} + +void fwrite32le(uint val, FILE *f) +{ + ubyte data[4]{ static_cast(val&0xff), static_cast((val>>8)&0xff), + static_cast((val>>16)&0xff), static_cast((val>>24)&0xff) }; + fwrite(data, 1, 4, f); +} + +template +byte4 f32AsLEBytes(const float &value) = delete; + +template<> +byte4 f32AsLEBytes(const float &value) +{ + byte4 ret{}; + std::memcpy(ret.data(), &value, 4); + return ret; +} +template<> +byte4 f32AsLEBytes(const float &value) +{ + byte4 ret{}; + std::memcpy(ret.data(), &value, 4); + std::swap(ret[0], ret[3]); + std::swap(ret[1], ret[2]); + return ret; +} + + +constexpr uint BufferLineSize{1024}; + +using FloatBufferLine = std::array; +using FloatBufferSpan = al::span; + + +struct UhjDecoder { + constexpr static size_t sFilterDelay{1024}; + + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mT{}; + alignas(16) std::array mQ{}; + + /* History for the FIR filter. */ + alignas(16) std::array mDTHistory{}; + alignas(16) std::array mSHistory{}; + + alignas(16) std::array mTemp{}; + + void decode(const float *RESTRICT InSamples, const size_t InChannels, + const al::span OutSamples, const size_t SamplesToDo); + void decode2(const float *RESTRICT InSamples, const al::span OutSamples, + const size_t SamplesToDo); + + DEF_NEWDEL(UhjDecoder) +}; + +const PhaseShifterT PShift{}; + + +/* Decoding UHJ is done as: + * + * S = Left + Right + * D = Left - Right + * + * W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) + * X = 0.418496*S - j(0.828331*D + 0.767820*T) + * Y = 0.795968*D - 0.676392*T + j(0.186633*S) + * Z = 1.023332*Q + * + * where j is a +90 degree phase shift. 3-channel UHJ excludes Q, while 2- + * channel excludes Q and T. The B-Format signal reconstructed from 2-channel + * UHJ should not be run through a normal B-Format decoder, as it needs + * different shelf filters. + * + * NOTE: Some sources specify + * + * S = (Left + Right)/2 + * D = (Left - Right)/2 + * + * However, this is incorrect. It's halving Left and Right even though they + * were already halved during encoding, causing S and D to be half what they + * initially were at the encoding stage. This division is not present in + * Gerzon's original paper for deriving Sigma (S) or Delta (D) from the L and R + * signals. As proof, taking Y for example: + * + * Y = 0.795968*D - 0.676392*T + j(0.186633*S) + * + * * Plug in the encoding parameters, using ? as a placeholder for whether S + * and D should receive an extra 0.5 factor + * Y = 0.795968*(j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y)*? - + * 0.676392*(j(-0.1432*W + 0.6512*X) - 0.7071068*Y) + + * 0.186633*j(0.9396926*W + 0.1855740*X)*? + * + * * Move common factors in + * Y = (j(-0.3420201*0.795968*?*W + 0.5098604*0.795968*?*X) + 0.6554516*0.795968*?*Y) - + * (j(-0.1432*0.676392*W + 0.6512*0.676392*X) - 0.7071068*0.676392*Y) + + * j(0.9396926*0.186633*?*W + 0.1855740*0.186633*?*X) + * + * * Clean up extraneous groupings + * Y = j(-0.3420201*0.795968*?*W + 0.5098604*0.795968*?*X) + 0.6554516*0.795968*?*Y - + * j(-0.1432*0.676392*W + 0.6512*0.676392*X) + 0.7071068*0.676392*Y + + * j*(0.9396926*0.186633*?*W + 0.1855740*0.186633*?*X) + * + * * Move phase shifts together and combine them + * Y = j(-0.3420201*0.795968*?*W + 0.5098604*0.795968*?*X - -0.1432*0.676392*W - + * 0.6512*0.676392*X + 0.9396926*0.186633*?*W + 0.1855740*0.186633*?*X) + + * 0.6554516*0.795968*?*Y + 0.7071068*0.676392*Y + * + * * Reorder terms + * Y = j(-0.3420201*0.795968*?*W + 0.1432*0.676392*W + 0.9396926*0.186633*?*W + + * 0.5098604*0.795968*?*X + -0.6512*0.676392*X + 0.1855740*0.186633*?*X) + + * 0.7071068*0.676392*Y + 0.6554516*0.795968*?*Y + * + * * Move common factors out + * Y = j((-0.3420201*0.795968*? + 0.1432*0.676392 + 0.9396926*0.186633*?)*W + + * ( 0.5098604*0.795968*? + -0.6512*0.676392 + 0.1855740*0.186633*?)*X) + + * (0.7071068*0.676392 + 0.6554516*0.795968*?)*Y + * + * * Result w/ 0.5 factor: + * -0.3420201*0.795968*0.5 + 0.1432*0.676392 + 0.9396926*0.186633*0.5 = 0.04843*W + * 0.5098604*0.795968*0.5 + -0.6512*0.676392 + 0.1855740*0.186633*0.5 = -0.22023*X + * 0.7071068*0.676392 + 0.6554516*0.795968*0.5 = 0.73914*Y + * -> Y = j(0.04843*W + -0.22023*X) + 0.73914*Y + * + * * Result w/o 0.5 factor: + * -0.3420201*0.795968 + 0.1432*0.676392 + 0.9396926*0.186633 = 0.00000*W + * 0.5098604*0.795968 + -0.6512*0.676392 + 0.1855740*0.186633 = 0.00000*X + * 0.7071068*0.676392 + 0.6554516*0.795968 = 1.00000*Y + * -> Y = j(0.00000*W + 0.00000*X) + 1.00000*Y + * + * Not halving produces a result matching the original input. + */ +void UhjDecoder::decode(const float *RESTRICT InSamples, const size_t InChannels, + const al::span OutSamples, const size_t SamplesToDo) +{ + ASSUME(SamplesToDo > 0); + + float *woutput{OutSamples[0].data()}; + float *xoutput{OutSamples[1].data()}; + float *youtput{OutSamples[2].data()}; + + /* Add a delay to the input channels, to align it with the all-passed + * signal. + */ + + /* S = Left + Right */ + for(size_t i{0};i < SamplesToDo;++i) + mS[sFilterDelay+i] = InSamples[i*InChannels + 0] + InSamples[i*InChannels + 1]; + + /* D = Left - Right */ + for(size_t i{0};i < SamplesToDo;++i) + mD[sFilterDelay+i] = InSamples[i*InChannels + 0] - InSamples[i*InChannels + 1]; + + if(InChannels > 2) + { + /* T */ + for(size_t i{0};i < SamplesToDo;++i) + mT[sFilterDelay+i] = InSamples[i*InChannels + 2]; + } + if(InChannels > 3) + { + /* Q */ + for(size_t i{0};i < SamplesToDo;++i) + mQ[sFilterDelay+i] = InSamples[i*InChannels + 3]; + } + + /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ + auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); + std::transform(mD.cbegin(), mD.cbegin()+SamplesToDo+sFilterDelay, mT.cbegin(), tmpiter, + [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); + std::copy_n(mTemp.cbegin()+SamplesToDo, mDTHistory.size(), mDTHistory.begin()); + PShift.process({xoutput, SamplesToDo}, mTemp.data()); + + for(size_t i{0};i < SamplesToDo;++i) + { + /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ + woutput[i] = 0.981532f*mS[i] + 0.197484f*xoutput[i]; + /* X = 0.418496*S - j(0.828331*D + 0.767820*T) */ + xoutput[i] = 0.418496f*mS[i] - xoutput[i]; + } + + /* Precompute j*S and store in youtput. */ + tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); + std::copy_n(mS.cbegin(), SamplesToDo+sFilterDelay, tmpiter); + std::copy_n(mTemp.cbegin()+SamplesToDo, mSHistory.size(), mSHistory.begin()); + PShift.process({youtput, SamplesToDo}, mTemp.data()); + + for(size_t i{0};i < SamplesToDo;++i) + { + /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ + youtput[i] = 0.795968f*mD[i] - 0.676392f*mT[i] + 0.186633f*youtput[i]; + } + + if(OutSamples.size() > 3) + { + float *zoutput{OutSamples[3].data()}; + /* Z = 1.023332*Q */ + for(size_t i{0};i < SamplesToDo;++i) + zoutput[i] = 1.023332f*mQ[i]; + } + + std::copy(mS.begin()+SamplesToDo, mS.begin()+SamplesToDo+sFilterDelay, mS.begin()); + std::copy(mD.begin()+SamplesToDo, mD.begin()+SamplesToDo+sFilterDelay, mD.begin()); + std::copy(mT.begin()+SamplesToDo, mT.begin()+SamplesToDo+sFilterDelay, mT.begin()); + std::copy(mQ.begin()+SamplesToDo, mQ.begin()+SamplesToDo+sFilterDelay, mQ.begin()); +} + +/* This is an alternative equation for decoding 2-channel UHJ. Not sure what + * the intended benefit is over the above equation as this slightly reduces the + * amount of the original left response and has more of the phase-shifted + * forward response on the left response. + * + * This decoding is done as: + * + * S = Left + Right + * D = Left - Right + * + * W = 0.981530*S + j*0.163585*D + * X = 0.418504*S - j*0.828347*D + * Y = 0.762956*D + j*0.384230*S + * + * where j is a +90 degree phase shift. + * + * NOTE: As above, S and D should not be halved. The only consequence of + * halving here is merely a -6dB reduction in output, but it's still incorrect. + */ +void UhjDecoder::decode2(const float *RESTRICT InSamples, + const al::span OutSamples, const size_t SamplesToDo) +{ + ASSUME(SamplesToDo > 0); + + float *woutput{OutSamples[0].data()}; + float *xoutput{OutSamples[1].data()}; + float *youtput{OutSamples[2].data()}; + + /* S = Left + Right */ + for(size_t i{0};i < SamplesToDo;++i) + mS[sFilterDelay+i] = InSamples[i*2 + 0] + InSamples[i*2 + 1]; + + /* D = Left - Right */ + for(size_t i{0};i < SamplesToDo;++i) + mD[sFilterDelay+i] = InSamples[i*2 + 0] - InSamples[i*2 + 1]; + + /* Precompute j*D and store in xoutput. */ + auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); + std::copy_n(mD.cbegin(), SamplesToDo+sFilterDelay, tmpiter); + std::copy_n(mTemp.cbegin()+SamplesToDo, mDTHistory.size(), mDTHistory.begin()); + PShift.process({xoutput, SamplesToDo}, mTemp.data()); + + for(size_t i{0};i < SamplesToDo;++i) + { + /* W = 0.981530*S + j*0.163585*D */ + woutput[i] = 0.981530f*mS[i] + 0.163585f*xoutput[i]; + /* X = 0.418504*S - j*0.828347*D */ + xoutput[i] = 0.418504f*mS[i] - 0.828347f*xoutput[i]; + } + + /* Precompute j*S and store in youtput. */ + tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); + std::copy_n(mS.cbegin(), SamplesToDo+sFilterDelay, tmpiter); + std::copy_n(mTemp.cbegin()+SamplesToDo, mSHistory.size(), mSHistory.begin()); + PShift.process({youtput, SamplesToDo}, mTemp.data()); + + for(size_t i{0};i < SamplesToDo;++i) + { + /* Y = 0.762956*D + j*0.384230*S */ + youtput[i] = 0.762956f*mD[i] + 0.384230f*youtput[i]; + } + + std::copy(mS.begin()+SamplesToDo, mS.begin()+SamplesToDo+sFilterDelay, mS.begin()); + std::copy(mD.begin()+SamplesToDo, mD.begin()+SamplesToDo+sFilterDelay, mD.begin()); +} + + +int main(int argc, char **argv) +{ + if(argc < 2 || std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0) + { + printf("Usage: %s <[options] filename.wav...>\n\n" + " Options:\n" + " --general Use the general equations for 2-channel UHJ (default).\n" + " --alternative Use the alternative equations for 2-channel UHJ.\n" + "\n" + "Note: When decoding 2-channel UHJ to an .amb file, the result should not use\n" + "the normal B-Format shelf filters! Only 3- and 4-channel UHJ can accurately\n" + "reconstruct the original B-Format signal.", + argv[0]); + return 1; + } + + size_t num_files{0}, num_decoded{0}; + bool use_general{true}; + for(int fidx{1};fidx < argc;++fidx) + { + if(std::strcmp(argv[fidx], "--general") == 0) + { + use_general = true; + continue; + } + if(std::strcmp(argv[fidx], "--alternative") == 0) + { + use_general = false; + continue; + } + ++num_files; + SF_INFO ininfo{}; + SndFilePtr infile{sf_open(argv[fidx], SFM_READ, &ininfo)}; + if(!infile) + { + fprintf(stderr, "Failed to open %s\n", argv[fidx]); + continue; + } + if(sf_command(infile.get(), SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + { + fprintf(stderr, "%s is already B-Format\n", argv[fidx]); + continue; + } + uint outchans{}; + if(ininfo.channels == 2) + outchans = 3; + else if(ininfo.channels == 3 || ininfo.channels == 4) + outchans = static_cast(ininfo.channels); + else + { + fprintf(stderr, "%s is not a 2-, 3-, or 4-channel file\n", argv[fidx]); + continue; + } + printf("Converting %s from %d-channel UHJ%s...\n", argv[fidx], ininfo.channels, + (ininfo.channels == 2) ? use_general ? " (general)" : " (alternative)" : ""); + + std::string outname{argv[fidx]}; + auto lastslash = outname.find_last_of('/'); + if(lastslash != std::string::npos) + outname.erase(0, lastslash+1); + auto lastdot = outname.find_last_of('.'); + if(lastdot != std::string::npos) + outname.resize(lastdot+1); + outname += "amb"; + + FilePtr outfile{fopen(outname.c_str(), "wb")}; + if(!outfile) + { + fprintf(stderr, "Failed to create %s\n", outname.c_str()); + continue; + } + + fputs("RIFF", outfile.get()); + fwrite32le(0xFFFFFFFF, outfile.get()); // 'RIFF' header len; filled in at close + + fputs("WAVE", outfile.get()); + + fputs("fmt ", outfile.get()); + fwrite32le(40, outfile.get()); // 'fmt ' header len; 40 bytes for EXTENSIBLE + + // 16-bit val, format type id (extensible: 0xFFFE) + fwrite16le(0xFFFE, outfile.get()); + // 16-bit val, channel count + fwrite16le(static_cast(outchans), outfile.get()); + // 32-bit val, frequency + fwrite32le(static_cast(ininfo.samplerate), outfile.get()); + // 32-bit val, bytes per second + fwrite32le(static_cast(ininfo.samplerate)*sizeof(float)*outchans, outfile.get()); + // 16-bit val, frame size + fwrite16le(static_cast(sizeof(float)*outchans), outfile.get()); + // 16-bit val, bits per sample + fwrite16le(static_cast(sizeof(float)*8), outfile.get()); + // 16-bit val, extra byte count + fwrite16le(22, outfile.get()); + // 16-bit val, valid bits per sample + fwrite16le(static_cast(sizeof(float)*8), outfile.get()); + // 32-bit val, channel mask + fwrite32le(0, outfile.get()); + // 16 byte GUID, sub-type format + fwrite(SUBTYPE_BFORMAT_FLOAT, 1, 16, outfile.get()); + + fputs("data", outfile.get()); + fwrite32le(0xFFFFFFFF, outfile.get()); // 'data' header len; filled in at close + if(ferror(outfile.get())) + { + fprintf(stderr, "Error writing wave file header: %s (%d)\n", strerror(errno), errno); + continue; + } + + auto DataStart = ftell(outfile.get()); + + auto decoder = std::make_unique(); + auto inmem = std::make_unique(BufferLineSize*static_cast(ininfo.channels)); + auto decmem = al::vector, 16>(outchans); + auto outmem = std::make_unique(BufferLineSize*outchans); + + /* A number of initial samples need to be skipped to cut the lead-in + * from the all-pass filter delay. The same number of samples need to + * be fed through the decoder after reaching the end of the input file + * to ensure none of the original input is lost. + */ + size_t LeadIn{UhjDecoder::sFilterDelay}; + sf_count_t LeadOut{UhjDecoder::sFilterDelay}; + while(LeadOut > 0) + { + sf_count_t sgot{sf_readf_float(infile.get(), inmem.get(), BufferLineSize)}; + sgot = std::max(sgot, 0); + if(sgot < BufferLineSize) + { + const sf_count_t remaining{std::min(BufferLineSize - sgot, LeadOut)}; + std::fill_n(inmem.get() + sgot*ininfo.channels, remaining*ininfo.channels, 0.0f); + sgot += remaining; + LeadOut -= remaining; + } + + auto got = static_cast(sgot); + if(ininfo.channels > 2 || use_general) + decoder->decode(inmem.get(), static_cast(ininfo.channels), decmem, got); + else + decoder->decode2(inmem.get(), decmem, got); + if(LeadIn >= got) + { + LeadIn -= got; + continue; + } + + got -= LeadIn; + for(size_t i{0};i < got;++i) + { + /* Attenuate by -3dB for FuMa output levels. */ + constexpr auto inv_sqrt2 = static_cast(1.0/al::numbers::sqrt2); + for(size_t j{0};j < outchans;++j) + outmem[i*outchans + j] = f32AsLEBytes(decmem[j][LeadIn+i] * inv_sqrt2); + } + LeadIn = 0; + + size_t wrote{fwrite(outmem.get(), sizeof(byte4)*outchans, got, outfile.get())}; + if(wrote < got) + { + fprintf(stderr, "Error writing wave data: %s (%d)\n", strerror(errno), errno); + break; + } + } + + auto DataEnd = ftell(outfile.get()); + if(DataEnd > DataStart) + { + long dataLen{DataEnd - DataStart}; + if(fseek(outfile.get(), 4, SEEK_SET) == 0) + fwrite32le(static_cast(DataEnd-8), outfile.get()); // 'WAVE' header len + if(fseek(outfile.get(), DataStart-4, SEEK_SET) == 0) + fwrite32le(static_cast(dataLen), outfile.get()); // 'data' header len + } + fflush(outfile.get()); + ++num_decoded; + } + if(num_decoded == 0) + fprintf(stderr, "Failed to decode any input files\n"); + else if(num_decoded < num_files) + fprintf(stderr, "Decoded %zu of %zu files\n", num_decoded, num_files); + else + printf("Decoded %zu file%s\n", num_decoded, (num_decoded==1)?"":"s"); + return 0; +} diff --git a/Engine/lib/openal-soft/utils/uhjencoder.cpp b/Engine/lib/openal-soft/utils/uhjencoder.cpp new file mode 100644 index 000000000..b380ed865 --- /dev/null +++ b/Engine/lib/openal-soft/utils/uhjencoder.cpp @@ -0,0 +1,507 @@ +/* + * 2-channel UHJ Encoder + * + * Copyright (c) Chris Robinson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "almalloc.h" +#include "alnumbers.h" +#include "alspan.h" +#include "opthelpers.h" +#include "phase_shifter.h" +#include "vector.h" + +#include "sndfile.h" + +#include "win_main_utf8.h" + + +namespace { + +struct SndFileDeleter { + void operator()(SNDFILE *sndfile) { sf_close(sndfile); } +}; +using SndFilePtr = std::unique_ptr; + + +using uint = unsigned int; + +constexpr uint BufferLineSize{1024}; + +using FloatBufferLine = std::array; +using FloatBufferSpan = al::span; + + +struct UhjEncoder { + constexpr static size_t sFilterDelay{1024}; + + /* Delays and processing storage for the unfiltered signal. */ + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mT{}; + alignas(16) std::array mQ{}; + + /* History for the FIR filter. */ + alignas(16) std::array mWXHistory1{}; + alignas(16) std::array mWXHistory2{}; + + alignas(16) std::array mTemp{}; + + void encode(const al::span OutSamples, + const al::span InSamples, const size_t SamplesToDo); + + DEF_NEWDEL(UhjEncoder) +}; + +const PhaseShifterT PShift{}; + + +/* Encoding UHJ from B-Format is done as: + * + * S = 0.9396926*W + 0.1855740*X + * D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y + * + * Left = (S + D)/2.0 + * Right = (S - D)/2.0 + * T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y + * Q = 0.9772*Z + * + * where j is a wide-band +90 degree phase shift. T is excluded from 2-channel + * output, and Q is excluded from 2- and 3-channel output. + */ +void UhjEncoder::encode(const al::span OutSamples, + const al::span InSamples, const size_t SamplesToDo) +{ + const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0].data())}; + const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1].data())}; + const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2].data())}; + const float *RESTRICT zinput{al::assume_aligned<16>(InSamples[3].data())}; + + /* Combine the previously delayed S/D signal with the input. */ + + /* S = 0.9396926*W + 0.1855740*X */ + auto miditer = mS.begin() + sFilterDelay; + std::transform(winput, winput+SamplesToDo, xinput, miditer, + [](const float w, const float x) noexcept -> float + { return 0.9396926f*w + 0.1855740f*x; }); + + /* D = 0.6554516*Y */ + auto sideiter = mD.begin() + sFilterDelay; + std::transform(yinput, yinput+SamplesToDo, sideiter, + [](const float y) noexcept -> float { return 0.6554516f*y; }); + + /* D += j(-0.3420201*W + 0.5098604*X) */ + auto tmpiter = std::copy(mWXHistory1.cbegin(), mWXHistory1.cend(), mTemp.begin()); + std::transform(winput, winput+SamplesToDo, xinput, tmpiter, + [](const float w, const float x) noexcept -> float + { return -0.3420201f*w + 0.5098604f*x; }); + std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory1.size(), mWXHistory1.begin()); + PShift.processAccum({mD.data(), SamplesToDo}, mTemp.data()); + + /* Left = (S + D)/2.0 */ + float *RESTRICT left{al::assume_aligned<16>(OutSamples[0].data())}; + for(size_t i{0};i < SamplesToDo;i++) + left[i] = (mS[i] + mD[i]) * 0.5f; + /* Right = (S - D)/2.0 */ + float *RESTRICT right{al::assume_aligned<16>(OutSamples[1].data())}; + for(size_t i{0};i < SamplesToDo;i++) + right[i] = (mS[i] - mD[i]) * 0.5f; + + if(OutSamples.size() > 2) + { + /* T = -0.7071068*Y */ + sideiter = mT.begin() + sFilterDelay; + std::transform(yinput, yinput+SamplesToDo, sideiter, + [](const float y) noexcept -> float { return -0.7071068f*y; }); + + /* T += j(-0.1432*W + 0.6512*X) */ + tmpiter = std::copy(mWXHistory2.cbegin(), mWXHistory2.cend(), mTemp.begin()); + std::transform(winput, winput+SamplesToDo, xinput, tmpiter, + [](const float w, const float x) noexcept -> float + { return -0.1432f*w + 0.6512f*x; }); + std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory2.size(), mWXHistory2.begin()); + PShift.processAccum({mT.data(), SamplesToDo}, mTemp.data()); + + float *RESTRICT t{al::assume_aligned<16>(OutSamples[2].data())}; + for(size_t i{0};i < SamplesToDo;i++) + t[i] = mT[i]; + } + if(OutSamples.size() > 3) + { + /* Q = 0.9772*Z */ + sideiter = mQ.begin() + sFilterDelay; + std::transform(zinput, zinput+SamplesToDo, sideiter, + [](const float z) noexcept -> float { return 0.9772f*z; }); + + float *RESTRICT q{al::assume_aligned<16>(OutSamples[3].data())}; + for(size_t i{0};i < SamplesToDo;i++) + q[i] = mQ[i]; + } + + /* Copy the future samples to the front for next time. */ + std::copy(mS.cbegin()+SamplesToDo, mS.cbegin()+SamplesToDo+sFilterDelay, mS.begin()); + std::copy(mD.cbegin()+SamplesToDo, mD.cbegin()+SamplesToDo+sFilterDelay, mD.begin()); + std::copy(mT.cbegin()+SamplesToDo, mT.cbegin()+SamplesToDo+sFilterDelay, mT.begin()); + std::copy(mQ.cbegin()+SamplesToDo, mQ.cbegin()+SamplesToDo+sFilterDelay, mQ.begin()); +} + + +struct SpeakerPos { + int mChannelID; + float mAzimuth; + float mElevation; +}; + +/* Azimuth is counter-clockwise. */ +constexpr SpeakerPos StereoMap[2]{ + { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f }, + { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f }, +}, QuadMap[4]{ + { SF_CHANNEL_MAP_LEFT, 45.0f, 0.0f }, + { SF_CHANNEL_MAP_RIGHT, -45.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_LEFT, 135.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_RIGHT, -135.0f, 0.0f }, +}, X51Map[6]{ + { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f }, + { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f }, + { SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_SIDE_LEFT, 110.0f, 0.0f }, + { SF_CHANNEL_MAP_SIDE_RIGHT, -110.0f, 0.0f }, +}, X51RearMap[6]{ + { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f }, + { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f }, + { SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_LEFT, 110.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_RIGHT, -110.0f, 0.0f }, +}, X71Map[8]{ + { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f }, + { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f }, + { SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_LEFT, 150.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_RIGHT, -150.0f, 0.0f }, + { SF_CHANNEL_MAP_SIDE_LEFT, 90.0f, 0.0f }, + { SF_CHANNEL_MAP_SIDE_RIGHT, -90.0f, 0.0f }, +}, X714Map[12]{ + { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f }, + { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f }, + { SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_LEFT, 150.0f, 0.0f }, + { SF_CHANNEL_MAP_REAR_RIGHT, -150.0f, 0.0f }, + { SF_CHANNEL_MAP_SIDE_LEFT, 90.0f, 0.0f }, + { SF_CHANNEL_MAP_SIDE_RIGHT, -90.0f, 0.0f }, + { SF_CHANNEL_MAP_TOP_FRONT_LEFT, 45.0f, 35.0f }, + { SF_CHANNEL_MAP_TOP_FRONT_RIGHT, -45.0f, 35.0f }, + { SF_CHANNEL_MAP_TOP_REAR_LEFT, 135.0f, 35.0f }, + { SF_CHANNEL_MAP_TOP_REAR_RIGHT, -135.0f, 35.0f }, +}; + +constexpr auto GenCoeffs(double x /*+front*/, double y /*+left*/, double z /*+up*/) noexcept +{ + /* Coefficients are +3dB of FuMa. */ + return std::array{{ + 1.0f, + static_cast(al::numbers::sqrt2 * x), + static_cast(al::numbers::sqrt2 * y), + static_cast(al::numbers::sqrt2 * z) + }}; +} + +} // namespace + + +int main(int argc, char **argv) +{ + if(argc < 2 || std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0) + { + printf("Usage: %s \n\n", argv[0]); + return 1; + } + + uint uhjchans{2}; + size_t num_files{0}, num_encoded{0}; + for(int fidx{1};fidx < argc;++fidx) + { + if(strcmp(argv[fidx], "-bhj") == 0) + { + uhjchans = 2; + continue; + } + if(strcmp(argv[fidx], "-thj") == 0) + { + uhjchans = 3; + continue; + } + if(strcmp(argv[fidx], "-phj") == 0) + { + uhjchans = 4; + continue; + } + ++num_files; + + std::string outname{argv[fidx]}; + size_t lastslash{outname.find_last_of('/')}; + if(lastslash != std::string::npos) + outname.erase(0, lastslash+1); + size_t extpos{outname.find_last_of('.')}; + if(extpos != std::string::npos) + outname.resize(extpos); + outname += ".uhj.flac"; + + SF_INFO ininfo{}; + SndFilePtr infile{sf_open(argv[fidx], SFM_READ, &ininfo)}; + if(!infile) + { + fprintf(stderr, "Failed to open %s\n", argv[fidx]); + continue; + } + printf("Converting %s to %s...\n", argv[fidx], outname.c_str()); + + /* Work out the channel map, preferably using the actual channel map + * from the file/format, but falling back to assuming WFX order. + * + * TODO: Map indices when the channel order differs from the virtual + * speaker position maps. + */ + al::span spkrs; + auto chanmap = std::vector(static_cast(ininfo.channels), SF_CHANNEL_MAP_INVALID); + if(sf_command(infile.get(), SFC_GET_CHANNEL_MAP_INFO, chanmap.data(), + ininfo.channels*int{sizeof(int)}) == SF_TRUE) + { + static const std::array stereomap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT}}; + static const std::array quadmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, + SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}}; + static const std::array x51map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, + SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE, + SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}}; + static const std::array x51rearmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, + SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE, + SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}}; + static const std::array x71map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, + SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE, + SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT, + SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}}; + static const std::array x714map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, + SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE, + SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT, + SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT, + SF_CHANNEL_MAP_TOP_FRONT_LEFT, SF_CHANNEL_MAP_TOP_FRONT_RIGHT, + SF_CHANNEL_MAP_TOP_REAR_LEFT, SF_CHANNEL_MAP_TOP_REAR_RIGHT}}; + static const std::array ambi2dmap{{SF_CHANNEL_MAP_AMBISONIC_B_W, + SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y}}; + static const std::array ambi3dmap{{SF_CHANNEL_MAP_AMBISONIC_B_W, + SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y, + SF_CHANNEL_MAP_AMBISONIC_B_Z}}; + + auto match_chanmap = [](const al::span a, const al::span b) -> bool + { + return a.size() == b.size() + && std::mismatch(a.begin(), a.end(), b.begin(), b.end()).first == a.end(); + }; + if(match_chanmap(chanmap, stereomap)) + spkrs = StereoMap; + else if(match_chanmap(chanmap, quadmap)) + spkrs = QuadMap; + else if(match_chanmap(chanmap, x51map)) + spkrs = X51Map; + else if(match_chanmap(chanmap, x51rearmap)) + spkrs = X51RearMap; + else if(match_chanmap(chanmap, x71map)) + spkrs = X71Map; + else if(match_chanmap(chanmap, x714map)) + spkrs = X714Map; + else if(match_chanmap(chanmap, ambi2dmap) || match_chanmap(chanmap, ambi3dmap)) + { + /* Do nothing. */ + } + else + { + std::string mapstr; + if(chanmap.size() > 0) + { + mapstr = std::to_string(chanmap[0]); + for(int idx : al::span{chanmap}.subspan<1>()) + { + mapstr += ','; + mapstr += std::to_string(idx); + } + } + fprintf(stderr, " ... %zu channels not supported (map: %s)\n", chanmap.size(), + mapstr.c_str()); + continue; + } + } + else if(ininfo.channels == 2) + { + fprintf(stderr, " ... assuming WFX order stereo\n"); + spkrs = StereoMap; + } + else if(ininfo.channels == 6) + { + fprintf(stderr, " ... assuming WFX order 5.1\n"); + spkrs = X51Map; + } + else if(ininfo.channels == 8) + { + fprintf(stderr, " ... assuming WFX order 7.1\n"); + spkrs = X71Map; + } + else + { + fprintf(stderr, " ... unmapped %d-channel audio not supported\n", ininfo.channels); + continue; + } + + SF_INFO outinfo{}; + outinfo.frames = ininfo.frames; + outinfo.samplerate = ininfo.samplerate; + outinfo.channels = static_cast(uhjchans); + outinfo.format = SF_FORMAT_PCM_24 | SF_FORMAT_FLAC; + SndFilePtr outfile{sf_open(outname.c_str(), SFM_WRITE, &outinfo)}; + if(!outfile) + { + fprintf(stderr, " ... failed to create %s\n", outname.c_str()); + continue; + } + + auto encoder = std::make_unique(); + auto splbuf = al::vector(static_cast(9+ininfo.channels)+uhjchans); + auto ambmem = al::span{&splbuf[0], 4}; + auto encmem = al::span{&splbuf[4], 4}; + auto srcmem = al::span{splbuf[8].data(), BufferLineSize}; + auto outmem = al::span{splbuf[9].data(), BufferLineSize*uhjchans}; + + /* A number of initial samples need to be skipped to cut the lead-in + * from the all-pass filter delay. The same number of samples need to + * be fed through the encoder after reaching the end of the input file + * to ensure none of the original input is lost. + */ + size_t total_wrote{0}; + size_t LeadIn{UhjEncoder::sFilterDelay}; + sf_count_t LeadOut{UhjEncoder::sFilterDelay}; + while(LeadIn > 0 || LeadOut > 0) + { + auto inmem = outmem.data() + outmem.size(); + auto sgot = sf_readf_float(infile.get(), inmem, BufferLineSize); + + sgot = std::max(sgot, 0); + if(sgot < BufferLineSize) + { + const sf_count_t remaining{std::min(BufferLineSize - sgot, LeadOut)}; + std::fill_n(inmem + sgot*ininfo.channels, remaining*ininfo.channels, 0.0f); + sgot += remaining; + LeadOut -= remaining; + } + + for(auto&& buf : ambmem) + buf.fill(0.0f); + + auto got = static_cast(sgot); + if(spkrs.empty()) + { + /* B-Format is already in the correct order. It just needs a + * +3dB boost. + */ + constexpr float scale{al::numbers::sqrt2_v}; + const size_t chans{std::min(static_cast(ininfo.channels), 4u)}; + for(size_t c{0};c < chans;++c) + { + for(size_t i{0};i < got;++i) + ambmem[c][i] = inmem[i*static_cast(ininfo.channels)] * scale; + ++inmem; + } + } + else for(auto&& spkr : spkrs) + { + /* Skip LFE. Or mix directly into W? Or W+X? */ + if(spkr.mChannelID == SF_CHANNEL_MAP_LFE) + { + ++inmem; + continue; + } + + for(size_t i{0};i < got;++i) + srcmem[i] = inmem[i * static_cast(ininfo.channels)]; + ++inmem; + + constexpr auto Deg2Rad = al::numbers::pi / 180.0; + const auto coeffs = GenCoeffs( + std::cos(spkr.mAzimuth*Deg2Rad) * std::cos(spkr.mElevation*Deg2Rad), + std::sin(spkr.mAzimuth*Deg2Rad) * std::cos(spkr.mElevation*Deg2Rad), + std::sin(spkr.mElevation*Deg2Rad)); + for(size_t c{0};c < 4;++c) + { + for(size_t i{0};i < got;++i) + ambmem[c][i] += srcmem[i] * coeffs[c]; + } + } + + encoder->encode(encmem.subspan(0, uhjchans), ambmem, got); + if(LeadIn >= got) + { + LeadIn -= got; + continue; + } + + got -= LeadIn; + for(size_t c{0};c < uhjchans;++c) + { + constexpr float max_val{8388607.0f / 8388608.0f}; + auto clamp = [](float v, float mn, float mx) noexcept + { return std::min(std::max(v, mn), mx); }; + for(size_t i{0};i < got;++i) + outmem[i*uhjchans + c] = clamp(encmem[c][LeadIn+i], -1.0f, max_val); + } + LeadIn = 0; + + sf_count_t wrote{sf_writef_float(outfile.get(), outmem.data(), + static_cast(got))}; + if(wrote < 0) + fprintf(stderr, " ... failed to write samples: %d\n", sf_error(outfile.get())); + else + total_wrote += static_cast(wrote); + } + printf(" ... wrote %zu samples (%" PRId64 ").\n", total_wrote, int64_t{ininfo.frames}); + ++num_encoded; + } + if(num_encoded == 0) + fprintf(stderr, "Failed to encode any input files\n"); + else if(num_encoded < num_files) + fprintf(stderr, "Encoded %zu of %zu files\n", num_encoded, num_files); + else + printf("Encoded %s%zu file%s\n", (num_encoded > 1) ? "all " : "", num_encoded, + (num_encoded == 1) ? "" : "s"); + return 0; +} diff --git a/Engine/source/CMakeLists.txt b/Engine/source/CMakeLists.txt index df4c43389..208de57fd 100644 --- a/Engine/source/CMakeLists.txt +++ b/Engine/source/CMakeLists.txt @@ -89,6 +89,16 @@ endif (WIN32) # Handle terrain file(GLOB TORQUE_TERRAIN_SOURCES "terrain/*.cpp") +if (WIN32 AND TORQUE_D3D11) + file(GLOB TORQUE_TERRAIN_SOURCES_HLSL "terrain/hlsl/*.cpp") + set(TORQUE_TERRAIN_SOURCES ${TORQUE_TERRAIN_SOURCES} ${TORQUE_TERRAIN_SOURCES_HLSL}) +endif (WIN32 AND TORQUE_D3D11) + +if (TORQUE_OPENGL) + file(GLOB TORQUE_TERRAIN_SOURCES_GLSL "terrain/glsl/*.cpp") + set(TORQUE_TERRAIN_SOURCES ${TORQUE_TERRAIN_SOURCES} ${TORQUE_TERRAIN_SOURCES_GLSL}) +endif (TORQUE_OPENGL) + # Handle Materials file(GLOB TORQUE_MATERIALS_SOURCES "materials/*.cpp") @@ -118,6 +128,11 @@ file(GLOB TORQUE_I18N_SOURCES "i18n/*.cpp") # Handle Platform POSIX if (UNIX) file(GLOB TORQUE_PLATFORM_POSIX_SOURCES "platformPOSIX/*.cpp") + + if (TORQUE_CPU_X32 OR TORQUE_CPU_X64) + file(GLOB TORQUE_PLATFORM_X86_UNIX_SOURCES "platformX86UNIX/*.cpp") + set(TORQUE_PLATFORM_POSIX_SOURCES ${TORQUE_PLATFORM_POSIX_SOURCES} ${TORQUE_PLATFORM_X86_UNIX_SOURCES}) + endif (TORQUE_CPU_X32 OR TORQUE_CPU_X64) endif (UNIX) # Handle platformMac @@ -133,11 +148,15 @@ endif (WIN32) # Handle platformSDL file(GLOB TORQUE_PLATFORM_SDL_SOURCES "platformSDL/*.cpp" "platformSDL/threads/*.cpp") +# Handle platformX11 +if (UNIX AND NOT APPLE) + file(GLOB TORQUE_PLATFORM_X11_SOURCES "platformX11/*.cpp") +endif (UNIX AND NOT APPLE) ################# Start building libs ################### set(TORQUE_COMPILE_DEFINITIONS ICE_NO_DLL PCRE_STATIC TORQUE_ADVANCED_LIGHTING TORQUE_SHADERGEN - TORQUE_OPENGL TORQUE_OPCODE TORQUE_ASSIMP TORQUE_SDL TORQUE_COLLADA + TORQUE_OPCODE TORQUE_ASSIMP TORQUE_SDL TORQUE_COLLADA TORQUE_UNICODE UNICODE _UNICODE) # Set common linkages @@ -150,14 +169,13 @@ set (TORQUE_SOURCE_FILES "main/main.cpp" ${TORQUE_CINTERFACE_SOURCES} ${TORQUE_MATH_SOURCES} ${TORQUE_PLATFORM_SOURCES} ${TORQUE_ASSETS_SOURCES} ${TORQUE_UTIL_SOURCES} ${TORQUE_CORE_SOURCES} ${TORQUE_PERSISTENCE_SOURCES} ${TORQUE_MODULE_SOURCES} - ${TORQUE_PLATFORM_SDL_SOURCES} ${TORQUE_PLATFORM_MAC_SOURCES} ${TORQUE_PLATFORM_POSIX_SOURCES} + ${TORQUE_PLATFORM_SDL_SOURCES} ${TORQUE_PLATFORM_POSIX_SOURCES} ${TORQUE_WINDOW_MANAGER_SOURCES} ${TORQUE_SCENE_SOURCES} ${TORQUE_COLLISION_SOURCES} ${TORQUE_T3D_SOURCES} ${TORQUE_TS_SOURCES} ${TORQUE_SIM_SOURCES} ${TORQUE_MATERIALS_SOURCES} ${TORQUE_SHADERGEN_SOURCES} ${TORQUE_LIGHTING_SOURCES} ${TORQUE_GUI_SOURCES} ${TORQUE_ENVIRONMENT_SOURCES} ${TORQUE_TERRAIN_SOURCES} ${TORQUE_POSTFX_SOURCES} ${TORQUE_I18N_SOURCES} ${TORQUE_CONSOLE_SOURCES} ${TORQUE_SFX_SOURCES} ${TORQUE_GFX_SOURCES}) - # When on Windows, we need to link against winsock and windows codecs if (WIN32) set(TORQUE_LINK_LIBRARIES ${TORQUE_LINK_LIBRARIES} WS2_32.LIB windowscodecs.lib) @@ -172,9 +190,20 @@ endif (WIN32 AND TORQUE_D3D11) # Only link Apple frameworks when on an Apple platform if (APPLE) enable_language(OBJC) + set(TORQUE_SOURCE_FILES ${TORQUE_SOURCE_FILES} ${TORQUE_PLATFORM_MAC_SOURCES}) set(TORQUE_LINK_LIBRARIES ${TORQUE_LINK_LIBRARIES} "-framework Cocoa" "-framework AppKit" "-framework CoreData" "-framework Foundation") endif (APPLE) +if (TORQUE_OPENGL) + set(TORQUE_COMPILE_DEFINITIONS ${TORQUE_COMPILE_DEFINITIONS} TORQUE_OPENGL) +endif (TORQUE_OPENGL) + +# Linux requires X11 +if (UNIX AND NOT APPLE) + set(TORQUE_LINK_LIBRARIES ${TORQUE_LINK_LIBRARIES} "X11") + set(TORQUE_SOURCE_FILES ${TORQUE_SOURCE_FILES} ${TORQUE_PLATFORM_X11_SOURCES}) +endif (UNIX AND NOT APPLE) + # Search module directories set(TORQUE_MODULE_PATHS "${CMAKE_SOURCE_DIR}/Tools/CMake/modules") @@ -217,6 +246,7 @@ endif() target_compile_definitions(${TORQUE_APP_NAME} PUBLIC ${TORQUE_COMPILE_DEFINITIONS}) target_link_libraries(${TORQUE_APP_NAME} ${TORQUE_LINK_LIBRARIES}) target_include_directories(${TORQUE_APP_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_BINARY_DIR}/temp") +set_property(TARGET ${TORQUE_APP_NAME} PROPERTY CXX_STANDARD 17) if (WIN32) set_target_properties(${TORQUE_APP_NAME} PROPERTIES COMPILE_FLAGS "-D_CRT_SECURE_NO_WARNINGS /MP /Zc:wchar_t-") @@ -235,17 +265,18 @@ if (UNIX AND NOT APPLE) endif (UNIX AND NOT APPLE) # Process link libraries for dynamic links - we do this on OSX to ensure the binaries end up in the correct App directory +# as in the root CMake we force everything to be in game if (APPLE) get_target_property(GAME_LINK_LIBRARIES ${TORQUE_APP_NAME} LINK_LIBRARIES) foreach (GAME_LINK_LIBRARY ${GAME_LINK_LIBRARIES}) # For eg. OSX some links are not valid targets - for example frameworks provided by OS if (TARGET ${GAME_LINK_LIBRARY}) - get_target_property(LINK_LIBRARY_TYPE ${GAME_LINK_LIBRARY} TYPE) + get_target_property(LINK_LIBRARY_TYPE ${GAME_LINK_LIBRARY} TYPE) - # Only pay attention to shared libraries - if ("${LINK_LIBRARY_TYPE}" STREQUAL "SHARED_LIBRARY") - set_target_properties( ${GAME_LINK_LIBRARY} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "game/${TORQUE_APP_NAME}.app/Resources") - endif() + # Only pay attention to shared libraries and make them output to the app resources + if ("${LINK_LIBRARY_TYPE}" STREQUAL "SHARED_LIBRARY") + set_target_properties( ${GAME_LINK_LIBRARY} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${TORQUE_APP_GAME_DIRECTORY}/${TORQUE_APP_NAME}.app/Resources") + endif() endif() endforeach() endif (APPLE) \ No newline at end of file diff --git a/Templates/BaseGame/CMakeLists.txt b/Templates/BaseGame/CMakeLists.txt index 598b3aae7..e2ab3497c 100644 --- a/Templates/BaseGame/CMakeLists.txt +++ b/Templates/BaseGame/CMakeLists.txt @@ -1,22 +1,24 @@ file(GLOB TEMPLATE_FILES "*.bat" "*.command") -install(FILES ${TEMPLATE_FILES} DESTINATION .) +foreach(TEMPLATE_FILE ${TEMPLATE_FILES}) + file(COPY ${TEMPLATE_FILE} DESTINATION "${TORQUE_APP_ROOT_DIRECTORY}") +endforeach() # Perform installation minus scripts -install(DIRECTORY "game" "source" DESTINATION . - PATTERN "*.tscript" - EXCLUDE PATTERN) - +file(COPY "game" "source" DESTINATION "${TORQUE_APP_ROOT_DIRECTORY}" PATTERN "*.tscript" EXCLUDE PATTERN + PATTERN "*.in" EXCLUDE PATTERN) # Enumerate scripts and install with extension file(GLOB_RECURSE SCRIPT_FILES "game/*.tscript") foreach(ITEM ${SCRIPT_FILES}) get_filename_component( dir ${ITEM} DIRECTORY ) get_filename_component( scriptName ${ITEM} NAME ) - STRING(REGEX REPLACE "${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/" "${CMAKE_INSTALL_PREFIX}/" INSTALL_DIR ${dir}) + STRING(REGEX REPLACE "${CMAKE_SOURCE_DIR}/Templates/${TORQUE_TEMPLATE}/" "${TORQUE_APP_ROOT_DIRECTORY}/" INSTALL_DIR ${dir}) STRING(REGEX REPLACE ".tscript" ".${TORQUE_SCRIPT_EXTENSION}" newScriptName ${scriptName}) - install( FILES ${ITEM} DESTINATION ${INSTALL_DIR} RENAME ${newScriptName} ) + + # We can't use the file(COPY command to copy to a new file name so we copy as-is first then rename + file(COPY ${ITEM} DESTINATION ${INSTALL_DIR}) + file(RENAME ${INSTALL_DIR}/${scriptName} ${INSTALL_DIR}/${newScriptName}) endforeach() -# Once the full tree is installed, perform configurations on several files -CONFIGURE_FILE("game/main.tscript.in" "${CMAKE_BINARY_DIR}/main.${TORQUE_SCRIPT_EXTENSION}") -install(FILES "${CMAKE_BINARY_DIR}/main.${TORQUE_SCRIPT_EXTENSION}" DESTINATION "${CMAKE_INSTALL_PREFIX}/game") \ No newline at end of file +# Once the full tree is installed, perform configurations +CONFIGURE_FILE("game/main.tscript.in" "${TORQUE_APP_GAME_DIRECTORY}/main.${TORQUE_SCRIPT_EXTENSION}") \ No newline at end of file